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 }} |
- {{ node.cpu }}%
+
+
+
+
+ {{ normalizeUsage(node.cpu) }}%
+
-
|
@@ -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;
}