172 lines
5.5 KiB
Vue
172 lines
5.5 KiB
Vue
<template>
|
||
<div>
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>集群与容器</h1>
|
||
<p>统一建设容器池 · RKE2 上建 Wayne 后新服务的标准发布流程 · 数据源:Wayne 原生接口</p>
|
||
</div>
|
||
<el-button type="primary" :loading="loading" @click="loadClusters">同步</el-button>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-body">
|
||
<div v-if="error" class="error-line">{{ error }}</div>
|
||
<table v-loading="loading">
|
||
<thead>
|
||
<tr>
|
||
<th>集群</th>
|
||
<th>机房 / 区域</th>
|
||
<th>状态</th>
|
||
<th>节点</th>
|
||
<th>CPU 使用</th>
|
||
<th>RKE2 版本</th>
|
||
<th>Calico</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-if="!businessLineClusters.length">
|
||
<td colspan="8" class="text-dim">当前业务线暂无 Wayne 集群数据</td>
|
||
</tr>
|
||
<tr
|
||
v-for="cluster in businessLineClusters"
|
||
:key="cluster.name"
|
||
:class="['tr-hover', { selected: cluster.name === selectedCluster }]"
|
||
@click="selectedCluster = cluster.name"
|
||
>
|
||
<td class="strong">{{ cluster.name }}</td>
|
||
<td><span class="tag" :class="cluster.zoneClass">{{ cluster.zone }}</span></td>
|
||
<td :class="['status-text', cluster.statusClass]">● {{ cluster.status }}</td>
|
||
<td class="mono">{{ cluster.nodes }}</td>
|
||
<td>
|
||
<span class="bar-wrap"><span class="bar-fill" :class="cluster.cpuClass" :style="{ width: cluster.cpu + '%' }"></span></span>{{ cluster.cpu }}%
|
||
</td>
|
||
<td class="mono">{{ cluster.version }}</td>
|
||
<td class="mono">{{ cluster.calico }}</td>
|
||
<td><el-button text>详情</el-button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-head">
|
||
<h3>{{ selectedCluster || businessLineClusters[0]?.name || '未选择集群' }} · 节点列表</h3>
|
||
<span class="meta">node-label 多租户隔离</span>
|
||
</div>
|
||
<div class="panel-body">
|
||
<table v-loading="loading">
|
||
<thead>
|
||
<tr>
|
||
<th>节点</th>
|
||
<th>IP</th>
|
||
<th>业务线标签</th>
|
||
<th>污点 Taint</th>
|
||
<th>规格</th>
|
||
<th>CPU / Mem</th>
|
||
<th>状态</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-if="!businessLineNodes.length">
|
||
<td colspan="7" class="text-dim">当前集群暂无 Wayne 节点数据</td>
|
||
</tr>
|
||
<tr v-for="node in businessLineNodes" :key="node.name" class="tr-hover">
|
||
<td class="strong mono">{{ node.name }}</td>
|
||
<td class="mono">{{ node.ip }}</td>
|
||
<td><span class="tag biz">{{ node.label }}</span></td>
|
||
<td class="mono text-xs">{{ node.taint }}</td>
|
||
<td class="mono">{{ node.spec }}</td>
|
||
<td>
|
||
<template v-if="node.cpu > 0">
|
||
<span class="bar-wrap"><span class="bar-fill" :class="node.cpuClass" :style="{ width: node.cpu + '%' }"></span></span>{{ node.cpu }}%
|
||
</template>
|
||
<span v-else class="text-dim">-</span>
|
||
</td>
|
||
<td :class="['status-text', node.statusClass]">● {{ node.status }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, onMounted, ref, watch } from 'vue'
|
||
import { containerServiceApi, type ContainerCluster, type ContainerNode } from '@/api/containerService'
|
||
import { useBusinessLineStore } from '@/stores/businessLine'
|
||
|
||
const businessLineStore = useBusinessLineStore()
|
||
const clusters = ref<ContainerCluster[]>([])
|
||
const nodes = ref<ContainerNode[]>([])
|
||
const selectedCluster = ref('')
|
||
const loading = ref(false)
|
||
const error = ref('')
|
||
|
||
const businessLineClusters = computed(() => clusters.value)
|
||
|
||
const businessLineNodes = computed(() => {
|
||
if (!selectedCluster.value) return []
|
||
return nodes.value.filter((node) => node.cluster === selectedCluster.value)
|
||
})
|
||
|
||
async function loadClusters() {
|
||
const businessLineId = businessLineStore.current?.id
|
||
if (!businessLineId) {
|
||
clusters.value = []
|
||
nodes.value = []
|
||
selectedCluster.value = ''
|
||
return
|
||
}
|
||
loading.value = true
|
||
error.value = ''
|
||
try {
|
||
const data = await containerServiceApi.listClusters(businessLineId)
|
||
clusters.value = data.items
|
||
nodes.value = data.nodes
|
||
if (!clusters.value.some((cluster) => cluster.name === selectedCluster.value)) {
|
||
selectedCluster.value = clusters.value[0]?.name || ''
|
||
}
|
||
if (data.summary.errors?.length) {
|
||
error.value = data.summary.errors.join(';')
|
||
}
|
||
} catch (err) {
|
||
error.value = err instanceof Error ? err.message : 'Wayne 集群数据同步失败'
|
||
clusters.value = []
|
||
nodes.value = []
|
||
selectedCluster.value = ''
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => businessLineStore.current?.id,
|
||
() => {
|
||
void loadClusters()
|
||
},
|
||
)
|
||
|
||
onMounted(loadClusters)
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 公共样式已在 global.css 中定义 */
|
||
|
||
.tr-hover.selected {
|
||
background: rgba(22, 93, 255, 0.08);
|
||
}
|
||
|
||
.error-line {
|
||
margin-bottom: 12px;
|
||
color: var(--danger);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.tag.biz {
|
||
color: var(--text-mid);
|
||
}
|
||
</style>
|