Merge pull request #98 from ztkkOip/auth-login
fix(Auth login) 增加了一些主系统数据抽取
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export interface ContainerServiceSummary {
|
||||
businessLineId: number
|
||||
businessLineName: string
|
||||
namespaces: number
|
||||
clusters: number
|
||||
nodes: number
|
||||
readyNodes: number
|
||||
schedulableNodes: number
|
||||
workloads: number
|
||||
pods: number
|
||||
readyPods: number
|
||||
cpuUsed: number
|
||||
cpuTotal: number
|
||||
cpuPercent: number
|
||||
memoryUsed: number
|
||||
memoryTotal: number
|
||||
memoryPercent: number
|
||||
errors?: string[]
|
||||
}
|
||||
|
||||
export interface ContainerWorkload {
|
||||
name: string
|
||||
cluster: string
|
||||
namespace: string
|
||||
workload: string
|
||||
ready: string
|
||||
readyPods: number
|
||||
pods: number
|
||||
image: string
|
||||
biz: string
|
||||
status: string
|
||||
statusClass: string
|
||||
}
|
||||
|
||||
export const emptyContainerServiceSummary: ContainerServiceSummary = {
|
||||
businessLineId: 0,
|
||||
businessLineName: '',
|
||||
namespaces: 0,
|
||||
clusters: 0,
|
||||
nodes: 0,
|
||||
readyNodes: 0,
|
||||
schedulableNodes: 0,
|
||||
workloads: 0,
|
||||
pods: 0,
|
||||
readyPods: 0,
|
||||
cpuUsed: 0,
|
||||
cpuTotal: 0,
|
||||
cpuPercent: 0,
|
||||
memoryUsed: 0,
|
||||
memoryTotal: 0,
|
||||
memoryPercent: 0,
|
||||
}
|
||||
|
||||
export const containerServiceApi = {
|
||||
async getSummary(businessLineId: number): Promise<ContainerServiceSummary> {
|
||||
return authRequest(`/auth/api/v1/container-services/business-lines/${businessLineId}/summary`)
|
||||
},
|
||||
|
||||
async listWorkloads(businessLineId: number): Promise<{ items: ContainerWorkload[]; summary: ContainerServiceSummary }> {
|
||||
const data = await authRequest(`/auth/api/v1/container-services/business-lines/${businessLineId}/workloads`)
|
||||
return {
|
||||
items: Array.isArray(data.items) ? data.items : [],
|
||||
summary: data.summary || emptyContainerServiceSummary,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async function authRequest(path: string, init: RequestInit = {}) {
|
||||
const token = getToken()
|
||||
const response = await fetch(path, {
|
||||
...init,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...init.headers,
|
||||
},
|
||||
})
|
||||
const text = await response.text()
|
||||
const data = parseResponseBody(text)
|
||||
if (!response.ok) {
|
||||
const message = data?.error || data?.message || text || `HTTP ${response.status}`
|
||||
throw new Error(message)
|
||||
}
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseResponseBody(text: string) {
|
||||
if (!text.trim()) {
|
||||
return {}
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { error: text }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2>业务线管理</h2>
|
||||
<p>平台管理员维护全局业务线</p>
|
||||
<p>新增时自动查找或创建同名 namespace,并完成业务线绑定</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -105,7 +105,7 @@ async function createBusinessLine() {
|
||||
newName.value = ''
|
||||
await loadAllBusinessLines()
|
||||
await businessLineStore.loadMine()
|
||||
ElMessage.success('已新增业务线')
|
||||
ElMessage.success('已新增业务线,并完成同名 namespace 绑定')
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '新增业务线失败')
|
||||
} finally {
|
||||
|
||||
@@ -36,6 +36,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<h3>容器化服务 · 资源概览</h3>
|
||||
<span class="meta">{{ containerLoading ? '同步中...' : containerMetaText }}</span>
|
||||
</div>
|
||||
<div class="container-overview">
|
||||
<div class="container-metric">
|
||||
<span>命名空间</span>
|
||||
<strong>{{ containerSummary.namespaces }}</strong>
|
||||
<small>{{ currentName }} 业务线绑定</small>
|
||||
</div>
|
||||
<div class="container-metric">
|
||||
<span>工作负载</span>
|
||||
<strong>{{ containerSummary.workloads }}</strong>
|
||||
<small>Deployment</small>
|
||||
</div>
|
||||
<div class="container-metric">
|
||||
<span>Pod 实例</span>
|
||||
<strong>{{ containerSummary.pods }}</strong>
|
||||
<small>{{ containerSummary.readyPods }} ready</small>
|
||||
</div>
|
||||
<div class="container-metric">
|
||||
<span>CPU 已用</span>
|
||||
<strong>{{ formatMetric(containerSummary.cpuUsed) }}C</strong>
|
||||
<small>{{ containerSummary.cpuPercent }}% of allocatable</small>
|
||||
</div>
|
||||
<div class="container-metric">
|
||||
<span>Memory 已用</span>
|
||||
<strong>{{ formatMetric(containerSummary.memoryUsed) }}G</strong>
|
||||
<small>{{ containerSummary.memoryPercent }}% of allocatable</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cols-2">
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
@@ -121,13 +155,57 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { containerServiceApi, emptyContainerServiceSummary, type ContainerServiceSummary } from '@/api/containerService'
|
||||
import { useBusinessLineStore } from '@/stores/businessLine'
|
||||
import { useBusinessLineMockProfile } from '@/utils/businessLineMock'
|
||||
|
||||
const { currentName, profile } = useBusinessLineMockProfile()
|
||||
const businessLineStore = useBusinessLineStore()
|
||||
|
||||
const containerSummary = ref<ContainerServiceSummary>({ ...emptyContainerServiceSummary })
|
||||
const containerLoading = ref(false)
|
||||
const containerError = ref('')
|
||||
|
||||
const containerMetaText = computed(() => {
|
||||
if (containerError.value) {
|
||||
return containerError.value
|
||||
}
|
||||
const errors = containerSummary.value.errors?.length || 0
|
||||
return errors > 0 ? `数据源:Kubernetes 集群运行态 · ${errors} 项同步失败` : '数据源:Kubernetes 集群运行态'
|
||||
})
|
||||
|
||||
const loadContainerSummary = async () => {
|
||||
const businessLineId = businessLineStore.current?.id
|
||||
if (!businessLineId) {
|
||||
containerSummary.value = { ...emptyContainerServiceSummary }
|
||||
return
|
||||
}
|
||||
containerLoading.value = true
|
||||
containerError.value = ''
|
||||
try {
|
||||
containerSummary.value = await containerServiceApi.getSummary(businessLineId)
|
||||
} catch (error) {
|
||||
containerSummary.value = { ...emptyContainerServiceSummary }
|
||||
containerError.value = error instanceof Error ? error.message : '容器化服务数据同步失败'
|
||||
} finally {
|
||||
containerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
// 刷新数据
|
||||
loadContainerSummary()
|
||||
}
|
||||
|
||||
const formatMetric = (value: number) => Number(value || 0).toFixed(1).replace(/\.0$/, '')
|
||||
|
||||
watch(
|
||||
() => businessLineStore.current?.id,
|
||||
() => {
|
||||
loadContainerSummary()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -202,4 +280,40 @@ const refresh = () => {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.container-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.container-metric {
|
||||
min-width: 0;
|
||||
padding: 13px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--bg-panel-2);
|
||||
}
|
||||
|
||||
.container-metric span,
|
||||
.container-metric small {
|
||||
display: block;
|
||||
color: var(--text-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.container-metric strong {
|
||||
display: block;
|
||||
margin: 6px 0 4px;
|
||||
color: var(--text-hi);
|
||||
font-family: var(--mono);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.container-overview {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -107,14 +107,58 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<h3>容器化服务台账 · 按集群 / Namespace 同步</h3>
|
||||
<span class="meta">{{ containerLoading ? '同步中...' : containerMetaText }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>服务名</th>
|
||||
<th>集群</th>
|
||||
<th>Namespace</th>
|
||||
<th>工作负载</th>
|
||||
<th>Pod Ready</th>
|
||||
<th>镜像</th>
|
||||
<th>业务标签</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!containerLoading && containerServices.length === 0">
|
||||
<td colspan="8" class="text-dim">当前业务线暂无容器化服务</td>
|
||||
</tr>
|
||||
<tr v-for="service in containerServices" :key="`${service.cluster}-${service.namespace}-${service.name}`" class="tr-hover">
|
||||
<td class="strong mono">{{ service.name }}</td>
|
||||
<td><span class="tag">{{ service.cluster }}</span></td>
|
||||
<td class="mono">{{ service.namespace }}</td>
|
||||
<td>{{ service.workload }}</td>
|
||||
<td class="mono">{{ service.ready }}</td>
|
||||
<td class="mono text-xs">{{ service.image || '-' }}</td>
|
||||
<td class="mono">{{ service.biz }}</td>
|
||||
<td :class="['status-text', service.statusClass]">● {{ service.status }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination">
|
||||
<span>共 {{ containerServices.length }} 条容器化服务 · 当前业务线:{{ currentName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { containerServiceApi, emptyContainerServiceSummary, type ContainerServiceSummary, type ContainerWorkload } from '@/api/containerService'
|
||||
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' },
|
||||
@@ -125,8 +169,50 @@ const services = ref([
|
||||
{ name: 'las-order-svc', dc: '达拉斯 IDC', dcClass: '', biz: 'las', instances: 5, healthy: '5 / 5', ip: '10.66.2.20', status: '健康', statusClass: 'ok' },
|
||||
])
|
||||
|
||||
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 serviceInstances = computed(() => filteredServices.value.reduce((sum, service) => sum + service.instances, 0))
|
||||
const containerMetaText = computed(() => {
|
||||
if (containerError.value) {
|
||||
return containerError.value
|
||||
}
|
||||
const errors = containerSummary.value.errors?.length || 0
|
||||
return errors > 0 ? `数据源:Kubernetes Workload API · ${errors} 项同步失败` : '数据源:Kubernetes Workload API'
|
||||
})
|
||||
|
||||
const loadContainerServices = async () => {
|
||||
const businessLineId = businessLineStore.current?.id
|
||||
if (!businessLineId) {
|
||||
containerServices.value = []
|
||||
containerSummary.value = { ...emptyContainerServiceSummary }
|
||||
return
|
||||
}
|
||||
containerLoading.value = true
|
||||
containerError.value = ''
|
||||
try {
|
||||
const data = await containerServiceApi.listWorkloads(businessLineId)
|
||||
containerServices.value = data.items
|
||||
containerSummary.value = data.summary
|
||||
} catch (error) {
|
||||
containerServices.value = []
|
||||
containerSummary.value = { ...emptyContainerServiceSummary }
|
||||
containerError.value = error instanceof Error ? error.message : '容器化服务数据同步失败'
|
||||
} finally {
|
||||
containerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => businessLineStore.current?.id,
|
||||
() => {
|
||||
loadContainerServices()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -231,7 +231,7 @@ const operatorStateText = computed(() => {
|
||||
})
|
||||
const operatorStateDetail = computed(() => {
|
||||
if (!canManageBusinessLine.value) return '需要平台管理员或当前业务线管理员'
|
||||
if (!wayneNamespaces.value.length) return '先在业务线分配中绑定 Wayne namespace'
|
||||
if (!wayneNamespaces.value.length) return '新增业务线会自动绑定同名 namespace,也可在业务线分配中手动绑定'
|
||||
if (!hasWayneRoleBindingPermission.value) return '没有 Wayne 角色绑定权限'
|
||||
return `${wayneNamespaces.value.filter((item) => item.can_bind).length} 个 namespace 可授权`
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user