feat: update delivery and resource frontends
This commit is contained in:
@@ -3,16 +3,16 @@
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>服务管理</h1>
|
||||
<p>同步全部机房 Consul 注册中心服务目录,统一查看服务名 / 服务 IP / 业务标签 / 实例数</p>
|
||||
<p>同步基础服务实例台账,统一查看服务名 / 服务 IP / 业务标签 / 实例数</p>
|
||||
</div>
|
||||
<el-button type="primary">同步</el-button>
|
||||
<el-button type="primary" :loading="serviceLoading" @click="loadServiceLedger">同步</el-button>
|
||||
</div>
|
||||
|
||||
<div class="stat-row">
|
||||
<div class="stat-card">
|
||||
<div class="label">接入 Consul Datacenter</div>
|
||||
<div class="value">7</div>
|
||||
<div class="delta">国内 3 · 海外 4</div>
|
||||
<div class="label">接入 Datacenter</div>
|
||||
<div class="value">{{ serviceDatacenters }}</div>
|
||||
<div class="delta">来自基础服务实例台账</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">服务总数</div>
|
||||
@@ -22,17 +22,17 @@
|
||||
<div class="stat-card">
|
||||
<div class="label">服务实例总数</div>
|
||||
<div class="value">{{ serviceInstances }}</div>
|
||||
<div class="delta">所有机房注册 IP 汇总</div>
|
||||
<div class="delta">当前业务线实例汇总</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">健康实例占比</div>
|
||||
<div class="value accent">98.6%</div>
|
||||
<div class="delta warn">12 个实例 critical</div>
|
||||
<div class="value accent">{{ serviceHealthPercent }}%</div>
|
||||
<div class="delta warn">{{ unhealthyInstances }} 个实例异常</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">最近同步</div>
|
||||
<div class="value accent-pill">● 正常</div>
|
||||
<div class="delta">07:41:05 · 间隔 30s</div>
|
||||
<div class="value accent-pill">● {{ serviceError ? '异常' : '正常' }}</div>
|
||||
<div class="delta">{{ serviceMetaText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<h3>服务台账 · 按 Consul Datacenter 同步</h3>
|
||||
<span class="meta">数据源:Consul Catalog API</span>
|
||||
<span class="meta">{{ serviceLoading ? '同步中...' : serviceMetaText }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table>
|
||||
@@ -82,6 +82,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!serviceLoading && filteredServices.length === 0">
|
||||
<td colspan="7" class="text-dim">当前业务线暂无基础服务实例</td>
|
||||
</tr>
|
||||
<tr v-for="service in filteredServices" :key="service.name" class="tr-hover">
|
||||
<td class="strong mono">{{ service.name }}</td>
|
||||
<td><span class="tag" :class="service.dcClass">{{ service.dc }}</span></td>
|
||||
@@ -154,28 +157,45 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { containerServiceApi, emptyContainerServiceSummary, type ContainerServiceSummary, type ContainerWorkload } from '@/api/containerService'
|
||||
import { deliveryApi, type MySQLServiceLedgerItem } from '@/api/delivery'
|
||||
import { useBusinessLineStore } from '@/stores/businessLine'
|
||||
import { useBusinessLineMockProfile } from '@/utils/businessLineMock'
|
||||
|
||||
const { currentName } = useBusinessLineMockProfile()
|
||||
const businessLineStore = useBusinessLineStore()
|
||||
|
||||
const services = ref([
|
||||
{ name: 'kodo-gateway-svc', dc: 'YZH', dcClass: 'zone-a', biz: 'kodo', instances: 12, healthy: '12 / 12', ip: '10.21.4.51', status: '健康', statusClass: 'ok' },
|
||||
{ name: 'kodo-upload-svc', dc: 'XS', dcClass: 'zone-b', biz: 'kodo', instances: 10, healthy: '10 / 10', ip: '10.34.37.66', status: '健康', statusClass: 'ok' },
|
||||
{ name: 'linxi-render-svc', dc: 'JF', dcClass: 'zone-c', biz: 'linxi', instances: 6, healthy: '6 / 6', ip: '10.45.2.30', status: '健康', statusClass: 'ok' },
|
||||
{ name: 'xinfra-portal-svc', dc: 'YZH', dcClass: 'zone-a', biz: 'xinfra', instances: 8, healthy: '8 / 8', ip: '10.21.4.88', status: '健康', statusClass: 'ok' },
|
||||
{ name: 'las-search-api', dc: 'XS', dcClass: 'zone-b', biz: 'las', instances: 18, healthy: '17 / 18', ip: '10.34.37.20', status: '部分异常', statusClass: 'warn' },
|
||||
{ name: 'las-order-svc', dc: '达拉斯 IDC', dcClass: '', biz: 'las', instances: 5, healthy: '5 / 5', ip: '10.66.2.20', status: '健康', statusClass: 'ok' },
|
||||
])
|
||||
interface ServiceLedgerRow {
|
||||
name: string
|
||||
dc: string
|
||||
dcClass: string
|
||||
biz: string
|
||||
instances: number
|
||||
healthy: string
|
||||
healthyCount: number
|
||||
ip: string
|
||||
status: string
|
||||
statusClass: string
|
||||
}
|
||||
|
||||
const services = ref<ServiceLedgerRow[]>([])
|
||||
const serviceLoading = ref(false)
|
||||
const serviceError = ref('')
|
||||
|
||||
const containerServices = ref<ContainerWorkload[]>([])
|
||||
const containerSummary = ref<ContainerServiceSummary>({ ...emptyContainerServiceSummary })
|
||||
const containerLoading = ref(false)
|
||||
const containerError = ref('')
|
||||
|
||||
const filteredServices = computed(() => services.value.filter((service) => service.biz === currentName.value))
|
||||
const filteredServices = computed(() => services.value)
|
||||
const serviceInstances = computed(() => filteredServices.value.reduce((sum, service) => sum + service.instances, 0))
|
||||
const healthyInstances = computed(() => filteredServices.value.reduce((sum, service) => sum + service.healthyCount, 0))
|
||||
const unhealthyInstances = computed(() => Math.max(serviceInstances.value - healthyInstances.value, 0))
|
||||
const serviceDatacenters = computed(() => new Set(filteredServices.value.map((service) => service.dc).filter(Boolean)).size)
|
||||
const serviceHealthPercent = computed(() => {
|
||||
if (serviceInstances.value === 0) return '0.0'
|
||||
return ((healthyInstances.value / serviceInstances.value) * 100).toFixed(1)
|
||||
})
|
||||
const serviceMetaText = computed(() => serviceError.value || '数据源:deployment_results')
|
||||
const containerMetaText = computed(() => {
|
||||
if (containerError.value) {
|
||||
return containerError.value
|
||||
@@ -184,6 +204,42 @@ const containerMetaText = computed(() => {
|
||||
return errors > 0 ? `数据源:Kubernetes Workload API · ${errors} 项同步失败` : '数据源:Kubernetes Workload API'
|
||||
})
|
||||
|
||||
const loadServiceLedger = async () => {
|
||||
const businessLineId = businessLineStore.current?.id
|
||||
if (!businessLineId) {
|
||||
services.value = []
|
||||
return
|
||||
}
|
||||
serviceLoading.value = true
|
||||
serviceError.value = ''
|
||||
try {
|
||||
const items = await deliveryApi.listMySQLServices(businessLineId)
|
||||
services.value = items.map(serviceLedgerRow)
|
||||
} catch (error) {
|
||||
services.value = []
|
||||
serviceError.value = error instanceof Error ? error.message : '基础服务实例同步失败'
|
||||
} finally {
|
||||
serviceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function serviceLedgerRow(item: MySQLServiceLedgerItem): ServiceLedgerRow {
|
||||
const instances = item.instances || 1
|
||||
const healthy = item.healthy || 0
|
||||
return {
|
||||
name: item.name,
|
||||
dc: item.datacenter || '-',
|
||||
dcClass: 'zone-a',
|
||||
biz: item.business_tag || currentName.value,
|
||||
instances,
|
||||
healthy: `${healthy} / ${instances}`,
|
||||
healthyCount: healthy,
|
||||
ip: item.address || '-',
|
||||
status: item.status || '未知',
|
||||
statusClass: item.status_class || 'warn',
|
||||
}
|
||||
}
|
||||
|
||||
const loadContainerServices = async () => {
|
||||
const businessLineId = businessLineStore.current?.id
|
||||
if (!businessLineId) {
|
||||
@@ -209,6 +265,7 @@ const loadContainerServices = async () => {
|
||||
watch(
|
||||
() => businessLineStore.current?.id,
|
||||
() => {
|
||||
loadServiceLedger()
|
||||
loadContainerServices()
|
||||
},
|
||||
{ immediate: true },
|
||||
|
||||
Reference in New Issue
Block a user