feat: update delivery and resource frontends

This commit is contained in:
mac
2026-07-28 17:56:58 +08:00
parent f7be87c7d9
commit 93981b9aad
7 changed files with 561 additions and 133 deletions
+199 -60
View File
@@ -5,60 +5,37 @@
<h1>机器管理</h1>
<p>统一纳管物理机与虚机资源 · 基础数据来自 SINA CMDB</p>
</div>
<el-button type="primary">同步</el-button>
<el-button type="primary" :loading="syncing" @click="handleSync">同步</el-button>
</div>
<div class="stat-row">
<div class="stat-card">
<div class="label">资源总数</div>
<div class="value">{{ filteredResources.length }}</div>
<div class="delta">物理机 {{ physicalCount }} · 虚机 {{ vmCount }}</div>
<div class="value">{{ overview.total }}</div>
<div class="delta">物理机 {{ overview.physical }} · 虚机 {{ overview.virtual }}</div>
</div>
<div class="stat-card">
<div class="label">SINA CMDB</div>
<div class="value">368</div>
<div class="delta">物理机 146 · 虚机 222</div>
<div class="value">{{ overview.cmdb.total }}</div>
<div class="delta">物理机 {{ overview.cmdb.physical }} · 虚机 {{ overview.cmdb.virtual }}</div>
</div>
<div class="stat-card">
<div class="label">阿里云同步</div>
<div class="value">154</div>
<div class="delta">ECS · 增量同步</div>
<div class="value">{{ overview.aliyun.total }}</div>
<div class="delta">{{ overview.aliyun.label }}</div>
</div>
<div class="stat-card">
<div class="label">AWS / 七牛同步</div>
<div class="value">136</div>
<div class="delta">AWS 58 · 七牛 78</div>
<div class="value">{{ overview.awsQiniu.total }}</div>
<div class="delta">AWS {{ overview.awsQiniu.aws }} · 七牛 {{ overview.awsQiniu.qiniu }}</div>
</div>
<div class="stat-card">
<div class="label">最近一次同步</div>
<div class="value accent-pill">● 正常</div>
<div class="delta">07:39:10 · 新增 3 条</div>
<div class="value accent-pill">● {{ syncStatusText }}</div>
<div class="delta">{{ overview.lastSync.time }} · 新增 {{ overview.lastSync.created }} 条</div>
</div>
</div>
<div class="las-toolbar">
<el-select placeholder="全部资源类型">
<el-option label="全部资源类型" value="" />
<el-option label="物理机" value="physical" />
<el-option label="虚机" value="vm" />
</el-select>
<el-select placeholder="全部数据来源">
<el-option label="全部数据来源" value="" />
<el-option label="SINA CMDB" value="cmdb" />
<el-option label="阿里云同步" value="aliyun" />
<el-option label="AWS 同步" value="aws" />
<el-option label="七牛同步" value="qiniu" />
</el-select>
<el-select placeholder="全部业务线">
<el-option label="全部业务线" value="" />
<el-option label="kodo" value="kodo" />
<el-option label="linxi" value="linxi" />
<el-option label="xinfra" value="xinfra" />
<el-option label="las" value="las" />
</el-select>
<el-input placeholder="搜索 hostname / asset_number / IP" class="search-input" />
</div>
<div class="panel">
<div class="panel-head">
<h3>资源台账 · 物理机 / 虚机</h3>
@@ -78,31 +55,120 @@
<th>数据来源</th>
<th>状态</th>
</tr>
<tr class="filter-row">
<th>
<el-input
v-model="filters.hostname"
clearable
size="small"
placeholder="筛选主机名"
@clear="reloadFirstPage"
@keyup.enter="reloadFirstPage"
/>
</th>
<th>
<el-input
v-model="filters.assetNumber"
clearable
size="small"
placeholder="筛选资产编号"
@clear="reloadFirstPage"
@keyup.enter="reloadFirstPage"
/>
</th>
<th>
<el-select v-model="filters.type" clearable size="small" placeholder="全部" @change="reloadFirstPage">
<el-option label="物理机" value="physical" />
<el-option label="虚机" value="vm" />
</el-select>
</th>
<th>
<el-input
v-model="filters.location"
clearable
size="small"
placeholder="筛选机房"
@clear="reloadFirstPage"
@keyup.enter="reloadFirstPage"
/>
</th>
<th>
<el-input
v-model="filters.ip"
clearable
size="small"
placeholder="筛选 IP"
@clear="reloadFirstPage"
@keyup.enter="reloadFirstPage"
/>
</th>
<th>
<el-input
v-model="filters.spec"
clearable
size="small"
placeholder="筛选规格"
@clear="reloadFirstPage"
@keyup.enter="reloadFirstPage"
/>
</th>
<th>
<el-input
v-model="filters.businessLine"
clearable
size="small"
placeholder="筛选业务线"
@clear="reloadFirstPage"
@keyup.enter="reloadFirstPage"
/>
</th>
<th>
<el-select v-model="filters.source" clearable size="small" placeholder="全部" @change="reloadFirstPage">
<el-option label="SINA CMDB" value="cmdb" />
<el-option label="阿里云同步" value="aliyun" />
<el-option label="AWS 同步" value="aws" />
<el-option label="七牛同步" value="qiniu" />
</el-select>
</th>
<th>
<el-select v-model="filters.status" clearable size="small" placeholder="全部" @change="reloadFirstPage">
<el-option label="production" value="production" />
<el-option label="stock" value="stock" />
<el-option label="SREshutdown" value="SREshutdown" />
<el-option label="unmon" value="unmon" />
<el-option label="ignore" value="ignore" />
</el-select>
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filteredResources" :key="item.hostname" class="tr-hover">
<tr v-for="item in resources" :key="`${item.hostname}-${item.ip}`" class="tr-hover">
<td class="strong mono">{{ item.hostname }}</td>
<td class="mono text-xs">{{ item.asset_number }}</td>
<td class="mono text-xs">{{ item.assetNumber }}</td>
<td><span class="tag" :class="item.type === '物理机' ? '' : 'vm'">{{ item.type }}</span></td>
<td><span class="tag zone-a">{{ item.location }}</span></td>
<td class="mono">{{ item.ip }}</td>
<td class="mono text-xs">{{ item.spec }}</td>
<td class="mono">{{ item.business_line }}</td>
<td class="mono">{{ item.businessLine }}</td>
<td><span class="tag src-cmdb">{{ item.source }}</span></td>
<td class="status-text ok">● {{ item.status }}</td>
</tr>
<tr v-if="!loading && resources.length === 0">
<td colspan="9" class="empty-cell">暂无机器数据</td>
</tr>
</tbody>
</table>
<div class="pagination">
<span>共 {{ filteredResources.length }} 条 · 当前业务线:{{ currentName }}</span>
<span>共 {{ total }} 条</span>
<div class="pg-btns">
<span class="pg-btn disabled">‹</span>
<span class="pg-btn active">1</span>
<span class="pg-btn">2</span>
<span class="pg-btn">3</span>
<span class="pg-sep">…</span>
<span class="pg-btn">94</span>
<span class="pg-btn">›</span>
<el-pagination
v-model:current-page="page"
v-model:page-size="size"
background
layout="prev, pager, next"
:total="total"
@current-change="loadResources"
/>
</div>
</div>
</div>
@@ -111,24 +177,81 @@
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useBusinessLineMockProfile } from '@/utils/businessLineMock'
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { emptyMachineOverview, machineApi, type MachineResource } from '@/api/machine'
const { currentName } = useBusinessLineMockProfile()
const overview = ref(emptyMachineOverview)
const resources = ref<MachineResource[]>([])
const total = ref(0)
const page = ref(1)
const size = ref(20)
const loading = ref(false)
const syncing = ref(false)
const filters = reactive({
hostname: '',
assetNumber: '',
type: '',
location: '',
ip: '',
spec: '',
businessLine: '',
source: '',
status: '',
})
const resources = ref([
{ hostname: 'xs291', asset_number: 'SERV00003502', type: '物理机', location: '华东·杭州下沙', ip: '10.34.37.52', spec: '20C/192G/13.52T', business_line: 'kodo', source: 'SINA CMDB', status: 'production' },
{ hostname: 'yzh-las-014', asset_number: 'SERV00004187', type: '虚机', location: '华北·YZH', ip: '10.21.4.214', spec: '32C/128G/4.0T', business_line: 'las', source: 'SINA CMDB', status: 'production' },
{ hostname: 'jf-bm-118', asset_number: 'SERV00004290', type: '物理机', location: '华南·JF', ip: '10.45.2.18', spec: '16C/64G/2.0T', business_line: 'linxi', source: 'SINA CMDB', status: 'production' },
{ hostname: 'ali-ecs-sz-0231', asset_number: '—', type: '虚机', location: '阿里云·华南', ip: '172.18.4.31', spec: '16C/64G/500G', business_line: 'xinfra', source: '阿里云同步', status: 'production' },
{ hostname: 'aws-us-i-0a13fe2', asset_number: '—', type: '虚机', location: 'AWS · 美国', ip: '10.66.2.12', spec: '8C/32G/200G', business_line: 'las', source: 'AWS 同步', status: 'production' },
{ hostname: 'sg-las-007', asset_number: 'SERV00004511', type: '虚机', location: '七牛 · 新加坡', ip: '10.88.1.07', spec: '16C/64G/2.0T', business_line: 'las', source: '七牛同步', status: 'idle' },
{ hostname: 'hk-bm-001', asset_number: 'SERV00004602', type: '物理机', location: '香港 IDC', ip: '10.90.0.11', spec: '8C/32G/1.0T', business_line: 'xinfra', source: 'SINA CMDB', status: 'production' },
])
const syncStatusText = computed(() => {
if (overview.value.lastSync.status === 'normal') return '正常'
if (overview.value.lastSync.status === 'failed') return '失败'
return '未知'
})
const filteredResources = computed(() => resources.value.filter((item) => item.business_line === currentName.value))
const physicalCount = computed(() => filteredResources.value.filter((item) => item.type === '物理机').length)
const vmCount = computed(() => filteredResources.value.filter((item) => item.type === '虚机').length)
async function loadOverview() {
overview.value = await machineApi.getOverview()
}
async function loadResources() {
loading.value = true
try {
const data = await machineApi.listResources({
page: page.value,
size: size.value,
hostname: filters.hostname.trim(),
assetNumber: filters.assetNumber.trim(),
type: filters.type,
location: filters.location.trim(),
ip: filters.ip.trim(),
spec: filters.spec.trim(),
businessLine: filters.businessLine.trim(),
source: filters.source,
status: filters.status,
})
resources.value = data.items
total.value = data.total
} finally {
loading.value = false
}
}
async function reloadFirstPage() {
page.value = 1
await loadResources()
}
async function handleSync() {
syncing.value = true
try {
await machineApi.sync()
await Promise.all([loadOverview(), loadResources()])
ElMessage.success('机器数据同步完成')
} finally {
syncing.value = false
}
}
onMounted(async () => {
await Promise.all([loadOverview(), loadResources()])
})
</script>
<style scoped>
@@ -145,4 +268,20 @@ const vmCount = computed(() => filteredResources.value.filter((item) => item.typ
border-color: var(--tag-green-border);
background: var(--tag-green-bg);
}
.empty-cell {
color: var(--text-tertiary);
padding: 28px 0;
text-align: center;
}
.filter-row th {
background: var(--bg-elevated);
padding: 8px 10px;
}
.filter-row :deep(.el-input),
.filter-row :deep(.el-select) {
width: 100%;
}
</style>