From e42612e75f3c6b081f2c9d06ef1099f96faa8956 Mon Sep 17 00:00:00 2001 From: Nexus Agent Date: Fri, 12 Jun 2026 05:44:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(watch):=20=E7=9B=91=E6=B5=8B=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=9B=9B=E6=A7=BD=E5=90=8C=E5=B1=8F=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E8=B6=8B=E5=8A=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 资源趋势 2×2 四图;探针记录默认全部槽位;表头内存/硬盘中文。 Co-authored-by: Cursor --- .../2026-06-12-watch-metrics-four-slots.md | 18 +++ .../watch/WatchProbeRecordsTable.vue | 4 +- frontend/src/pages/WatchMetricsPage.vue | 127 +++++++++++++----- 3 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 docs/changelog/2026-06-12-watch-metrics-four-slots.md diff --git a/docs/changelog/2026-06-12-watch-metrics-four-slots.md b/docs/changelog/2026-06-12-watch-metrics-four-slots.md new file mode 100644 index 00000000..40545a2b --- /dev/null +++ b/docs/changelog/2026-06-12-watch-metrics-four-slots.md @@ -0,0 +1,18 @@ +# 监测历史:4 槽同时展示趋势 + +**日期**: 2026-06-12 + +## 变更摘要 + +- 资源趋势 Tab 改为 **2×2 四槽同屏**,每槽独立曲线;共用时间范围与指标选择 +- 探针记录 Tab 默认展示 **全部槽位** 记录(不再单选服务器) +- 探针表头 MEM/DISK 改为「内存」「硬盘」 + +## 涉及文件 + +- `frontend/src/pages/WatchMetricsPage.vue` +- `frontend/src/components/watch/WatchProbeRecordsTable.vue` + +## 验证 + +`npm run type-check`;打开 `#/watch-metrics`,4 槽图表同屏;探针记录含多机数据。 diff --git a/frontend/src/components/watch/WatchProbeRecordsTable.vue b/frontend/src/components/watch/WatchProbeRecordsTable.vue index 893981fe..345d528b 100644 --- a/frontend/src/components/watch/WatchProbeRecordsTable.vue +++ b/frontend/src/components/watch/WatchProbeRecordsTable.vue @@ -37,8 +37,8 @@ const headers = [ { title: '状态', key: 'probe_status', width: 100 }, { title: '来源', key: 'source', width: 72 }, { title: 'CPU', key: 'cpu_pct', width: 64 }, - { title: 'MEM', key: 'mem_pct', width: 64 }, - { title: 'DISK', key: 'disk_pct', width: 64 }, + { title: '内存', key: 'mem_pct', width: 64 }, + { title: '硬盘', key: 'disk_pct', width: 64 }, { title: '↑', key: 'net_up_bps', width: 88 }, { title: '↓', key: 'net_down_bps', width: 88 }, { title: '耗时', key: 'duration_ms', width: 72 }, diff --git a/frontend/src/pages/WatchMetricsPage.vue b/frontend/src/pages/WatchMetricsPage.vue index 3c67e654..74ab7e5c 100644 --- a/frontend/src/pages/WatchMetricsPage.vue +++ b/frontend/src/pages/WatchMetricsPage.vue @@ -8,11 +8,11 @@ import { useWatchPins } from '@/composables/useWatchPins' defineOptions({ name: 'WatchMetricsPage' }) +const WATCH_SLOT_COUNT = 4 + const route = useRoute() const tab = ref('trend') const hours = ref(24) -const serverId = ref(null) -const sessionId = ref(null) const points = ref([]) const loading = ref(false) const selectedSeries = ref>([ @@ -21,21 +21,47 @@ const selectedSeries = ref - slots.value - .filter((s) => !s.empty && s.server_id) - .map((s) => ({ title: s.server_name || `ID ${s.server_id}`, value: s.server_id! })), +const slotPanels = computed(() => + Array.from({ length: WATCH_SLOT_COUNT }, (_, slotIndex) => { + const slot = slots.value.find((s) => s.slot_index === slotIndex) + const empty = !slot || slot.empty + return { + slotIndex, + empty, + serverId: empty ? null : (slot.server_id ?? null), + serverName: empty ? '' : (slot.server_name || `ID ${slot.server_id}`), + } + }), ) +const pinnedServerIds = computed(() => + slotPanels.value + .map((p) => p.serverId) + .filter((id): id is number => id != null), +) + +const pointsByServer = computed(() => { + const map = new Map() + for (const p of points.value) { + const sid = p.server_id + if (sid == null) continue + const list = map.get(sid) ?? [] + list.push(p) + map.set(sid, list) + } + return map +}) + async function loadTrend() { - if (!serverId.value) { + const ids = pinnedServerIds.value + if (!ids.length) { points.value = [] return } loading.value = true try { const data = await http.get<{ points: WatchTrendPoint[] }>('/watch/metrics', { - server_ids: String(serverId.value), + server_ids: ids.join(','), hours: hours.value, }) points.value = data.points || [] @@ -46,22 +72,18 @@ async function loadTrend() { onMounted(async () => { await refreshPins() - const qSid = route.query.server_id - const qSess = route.query.session_id - if (qSid) serverId.value = Number(qSid) - if (qSess) sessionId.value = Number(qSess) - if (!serverId.value && serverOptions.value.length) { - serverId.value = serverOptions.value[0].value - } + const qTab = route.query.tab + if (qTab === 'records') tab.value = 'records' }) -watch([serverId, hours], loadTrend, { immediate: true }) +watch([pinnedServerIds, hours], loadTrend, { immediate: true, deep: true }) + +