From dae1bfae90e0f510b38c1916881ebfa482ddc5ee Mon Sep 17 00:00:00 2001 From: Hungerdream <1710233908@qq.com> Date: Thu, 30 Jul 2026 18:14:32 +0800 Subject: [PATCH] feat(frontend): polish cluster usage bars, task times and sidebar default - cluster list: rebuild CPU usage bars with progressbar semantics, NaN/out-of-range clamping and three-tier tone colors for cluster and node tables - task center: show task creation time next to runner with full timestamp on hover and invalid-date fallbacks - sidebar: collapse portal group by default --- frontend/src/components/Layout/AppSidebar.vue | 2 +- frontend/src/views/cluster/ClusterList.vue | 92 ++++++++++++++++++- frontend/src/views/task/TaskCenter.vue | 50 +++++++++- 3 files changed, 140 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Layout/AppSidebar.vue b/frontend/src/components/Layout/AppSidebar.vue index 0d9cc8c..d64a3db 100644 --- a/frontend/src/components/Layout/AppSidebar.vue +++ b/frontend/src/components/Layout/AppSidebar.vue @@ -100,7 +100,7 @@ import { taskLogApi } from '@/api/taskLog' const route = useRoute() const authStore = useAuthStore() const businessLineStore = useBusinessLineStore() -const portalExpanded = ref(true) +const portalExpanded = ref(false) const isPlatformAdmin = computed(() => authStore.isAdmin) const isBusinessLineAdmin = computed(() => businessLineStore.isCurrentAdmin) const sidebarBadges = ref({ diff --git a/frontend/src/views/cluster/ClusterList.vue b/frontend/src/views/cluster/ClusterList.vue index adfe783..224d237 100644 --- a/frontend/src/views/cluster/ClusterList.vue +++ b/frontend/src/views/cluster/ClusterList.vue @@ -39,7 +39,22 @@ ● {{ cluster.status }} {{ cluster.nodes }} - {{ cluster.cpu }}% +
+ + + + {{ normalizeUsage(cluster.cpu) }}% +
{{ cluster.version }} {{ cluster.calico }} @@ -80,7 +95,22 @@ {{ node.spec }} - @@ -112,6 +142,18 @@ const businessLineNodes = computed(() => { return nodes.value.filter((node) => node.cluster === selectedCluster.value) }) +function normalizeUsage(value: number) { + if (!Number.isFinite(value)) return 0 + return Math.min(100, Math.max(0, Math.round(value))) +} + +function usageTone(value: number) { + const normalized = normalizeUsage(value) + if (normalized >= 90) return 'critical' + if (normalized >= 70) return 'warning' + return 'normal' +} + async function loadClusters() { const businessLineId = businessLineStore.current?.id if (!businessLineId) { @@ -168,4 +210,50 @@ onMounted(loadClusters) .tag.biz { color: var(--text-mid); } + +.usage-cell { + width: max-content; + display: grid; + grid-template-columns: 112px 38px; + align-items: center; + gap: 10px; +} + +.usage-track { + width: 112px; + height: 8px; + display: block; + overflow: hidden; + border: 1px solid var(--line-soft); + border-radius: 4px; + background: var(--bar-bg); + box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.06); +} + +.usage-fill { + height: 100%; + display: block; + border-radius: 3px; + transition: width 240ms ease; +} + +.usage-fill.normal { + background: #20a67a; +} + +.usage-fill.warning { + background: var(--warn); +} + +.usage-fill.critical { + background: var(--err); +} + +.usage-value { + color: var(--text-mid); + font-size: 12px; + font-variant-numeric: tabular-nums; + text-align: right; + white-space: nowrap; +} diff --git a/frontend/src/views/task/TaskCenter.vue b/frontend/src/views/task/TaskCenter.vue index 93f2fdb..c1f24d0 100644 --- a/frontend/src/views/task/TaskCenter.vue +++ b/frontend/src/views/task/TaskCenter.vue @@ -34,7 +34,12 @@ ● {{ task.status_text }} {{ task.name }} - {{ task.runner }} + + {{ task.runner }} + + @@ -146,6 +151,33 @@ function formatTime(date: Date) { return date.toLocaleTimeString('zh-CN', { hour12: false }) } +function parseTaskTime(value: string) { + const date = new Date(value) + return Number.isNaN(date.getTime()) ? null : date +} + +function formatTaskTime(value: string) { + const date = parseTaskTime(value) + if (!date) { + return '-' + } + return date.toLocaleString('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }).replace('/', '-') +} + +function formatTaskFullTime(value: string) { + const date = parseTaskTime(value) + return date + ? date.toLocaleString('zh-CN', { hour12: false }) + : '时间未知' +} + watch( () => businessLineStore.current?.id, () => { @@ -228,6 +260,14 @@ onMounted(() => { gap: 5px; } +.task-meta { + min-width: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + .task-name, .task-runner, .selected-name { @@ -243,10 +283,18 @@ onMounted(() => { } .task-runner { + min-width: 0; color: var(--text-dim); font-size: 11px; } +.task-time { + flex: none; + color: var(--text-dim); + font-size: 10.5px; + white-space: nowrap; +} + .task-log-panel .panel-head { gap: 14px; }