From 7f209b5fefe946b27c6f367b2c4b1ea363708a62 Mon Sep 17 00:00:00 2001 From: daxiong Date: Sun, 19 Apr 2026 20:57:48 +0800 Subject: [PATCH] feat --- .kilo/package-lock.json | 115 +++++++++++ .kilocode/package-lock.json | 115 +++++++++++ src/views/ops/pages/monitor/storage/index.vue | 190 ++++-------------- 3 files changed, 271 insertions(+), 149 deletions(-) create mode 100644 .kilo/package-lock.json create mode 100644 .kilocode/package-lock.json diff --git a/.kilo/package-lock.json b/.kilo/package-lock.json new file mode 100644 index 0000000..f78a498 --- /dev/null +++ b/.kilo/package-lock.json @@ -0,0 +1,115 @@ +{ + "name": ".kilo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@kilocode/plugin": "7.2.14" + } + }, + "node_modules/@kilocode/plugin": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.14.tgz", + "integrity": "sha512-mS+WA9HZIBH2qQ9ARA+v0q4MdQTSdfOvKbe4AOSkjP+P5hVA70OM/UVM9DVcvmjSOxU+wuUxmOy+j/EQIrgFmw==", + "license": "MIT", + "dependencies": { + "@kilocode/sdk": "7.2.14", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.1.97", + "@opentui/solid": ">=0.1.97" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } + } + }, + "node_modules/@kilocode/sdk": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.14.tgz", + "integrity": "sha512-Naz83lFrsbavuDp6UwxRuglOaSNvRBsZfcRNvb7RpWYAwbuJP0dBdhpXj6uO3ta5qxeQ2JzxKNC9Ffz+LCLLDg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/zod": { + "version": "4.1.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/.kilocode/package-lock.json b/.kilocode/package-lock.json new file mode 100644 index 0000000..65452b7 --- /dev/null +++ b/.kilocode/package-lock.json @@ -0,0 +1,115 @@ +{ + "name": ".kilocode", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@kilocode/plugin": "7.2.14" + } + }, + "node_modules/@kilocode/plugin": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.14.tgz", + "integrity": "sha512-mS+WA9HZIBH2qQ9ARA+v0q4MdQTSdfOvKbe4AOSkjP+P5hVA70OM/UVM9DVcvmjSOxU+wuUxmOy+j/EQIrgFmw==", + "license": "MIT", + "dependencies": { + "@kilocode/sdk": "7.2.14", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.1.97", + "@opentui/solid": ">=0.1.97" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } + } + }, + "node_modules/@kilocode/sdk": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.14.tgz", + "integrity": "sha512-Naz83lFrsbavuDp6UwxRuglOaSNvRBsZfcRNvb7RpWYAwbuJP0dBdhpXj6uO3ta5qxeQ2JzxKNC9Ffz+LCLLDg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/zod": { + "version": "4.1.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/views/ops/pages/monitor/storage/index.vue b/src/views/ops/pages/monitor/storage/index.vue index 664707d..b9ea8c8 100644 --- a/src/views/ops/pages/monitor/storage/index.vue +++ b/src/views/ops/pages/monitor/storage/index.vue @@ -140,7 +140,7 @@ {{ activeStorage.collect_on ? '开启' : '关闭' }} - · {{ activeStorage.collect_interval || '-' }}s + · {{ activeStorage.collect_interval || '-' }}s {{ formatDateTime(activeStorage.last_check_time) }} @@ -162,9 +162,7 @@ -

{{ chartHint }}

-
- - - +
+

{{ chartHint }}

+
+ + + +
- - - - - - - - - -
@@ -244,14 +202,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import dayjs from 'dayjs' import { Message } from '@arco-design/web-vue' import { useDebounceFn } from '@vueuse/core' -import { - IconDashboard, - IconStorage, - IconCodeSquare, - IconDriveFile, - IconFolder, - IconCheckCircleFill, -} from '@arco-design/web-vue/es/icon' +import { IconDashboard, IconStorage, IconCodeSquare, IconDriveFile, IconFolder, IconCheckCircleFill } from '@arco-design/web-vue/es/icon' import Chart from '@/components/chart/index.vue' import { fetchStorageList, @@ -270,19 +221,6 @@ const trendSelectedMetric = ref('') const trendMetricOptions = ref<{ label: string; value: string }[]>([]) const metricsOptionsLoading = ref(false) -interface TableDevice { - id: number - service_identity: string - name: string - type: string - model: string - status: string - collect_on: boolean - last_check_time: string - response_time: number -} - -const tableLoading = ref(false) const dropdownLoading = ref(false) const summaryLoading = ref(false) const chartLoading = ref(false) @@ -295,13 +233,9 @@ const monitorSummary = ref(null) const chartSeriesPoints = ref<{ time: string; value: number }[]>([]) -const filterType = ref(undefined) - let pollTimer: ReturnType | null = null -const activeStorage = computed(() => - storageList.value.find((s) => s.service_identity === selectedServiceIdentity.value), -) +const activeStorage = computed(() => storageList.value.find((s) => s.service_identity === selectedServiceIdentity.value)) const globalHint = computed(() => { const m = monitorSummary.value @@ -317,11 +251,6 @@ const globalHint = computed(() => { return `全量:${n} 台 · 在线 ${online} · 离线 ${offline}` }) -const typeFilterOptions = computed(() => { - const set = new Set(storageList.value.map((s) => s.type).filter(Boolean)) - return [...set].map((t) => ({ label: t, value: t })) -}) - function formatDateTime(v: string | undefined) { if (!v) return '-' const d = dayjs(v) @@ -349,7 +278,7 @@ const swapUsage = computed(() => currentHost.value?.swap_usage_percent ?? null) const swapHint = computed(() => { const h = currentHost.value - if (!h || (h.swap_usage_percent === null || h.swap_usage_percent === undefined)) { + if (!h || h.swap_usage_percent === null || h.swap_usage_percent === undefined) { return '无 Swap 或未上报' } return 'Swap 使用率' @@ -391,36 +320,6 @@ const controllerStatusColor = computed(() => { return 'gray' }) -function toTableRow(item: StorageItem): TableDevice { - return { - id: item.id, - service_identity: item.service_identity, - name: item.name, - type: item.type, - model: item.description || '-', - status: item.status, - collect_on: item.collect_on, - last_check_time: item.last_check_time, - response_time: item.response_time, - } -} - -const filteredDevicesForTable = computed(() => - storageList.value - .filter((item) => !filterType.value || item.type === filterType.value) - .map(toTableRow), -) - -function rowClassFn(record: TableDevice) { - return record.service_identity === selectedServiceIdentity.value ? 'storage-table-row--active' : '' -} - -function onTableRowClick(record: TableDevice) { - if (record?.service_identity) { - selectedServiceIdentity.value = record.service_identity - } -} - const chartHint = computed(() => { if (!selectedServiceIdentity.value) return '' if (!trendMetricOptions.value.length) { @@ -451,11 +350,15 @@ const ioChartOptions = computed(() => { return { tooltip: { trigger: 'axis' }, legend: { show: true, data: [name] }, - grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, + grid: { left: '3%', right: '4%', bottom: '40px', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: labels, + axisLabel: { + interval: 'auto', + fontSize: 11, + }, }, yAxis: { type: 'value', name: '指标值' }, series: [ @@ -554,7 +457,6 @@ const onMonitorSelectSearch = useDebounceFn(async (inputValue: string) => { }, 350) async function loadStorageList() { - tableLoading.value = true try { const res: any = await fetchStorageList({ page: 1, size: 100 }) if (res.code !== 0) { @@ -567,10 +469,7 @@ async function loadStorageList() { const list: StorageItem[] = page?.data ?? [] storageList.value = list syncOptionsFromList() - if ( - selectedServiceIdentity.value && - !list.some((s) => s.service_identity === selectedServiceIdentity.value) - ) { + if (selectedServiceIdentity.value && !list.some((s) => s.service_identity === selectedServiceIdentity.value)) { selectedServiceIdentity.value = undefined } if (!selectedServiceIdentity.value && list.length > 0) { @@ -580,8 +479,6 @@ async function loadStorageList() { Message.error(e?.message || '加载存储列表失败') storageList.value = [] storageOptions.value = [] - } finally { - tableLoading.value = false } } @@ -650,13 +547,9 @@ async function refreshMonitorSummary() { } /** 将原始 metrics 按自然小时桶取 metric_value 最大值,供折线图横轴为整点 */ -function bucketStorageMetricsHourlyMax( - metrics: Array<{ timestamp: string; metric_value: number }>, -): { time: string; value: number }[] { +function bucketStorageMetricsHourlyMax(metrics: Array<{ timestamp: string; metric_value: number }>): { time: string; value: number }[] { if (!metrics?.length) return [] - const sorted = [...metrics].sort( - (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), - ) + const sorted = [...metrics].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()) const hourToMax = new Map() for (const p of sorted) { const hourKey = dayjs(p.timestamp).startOf('hour').toISOString() @@ -717,17 +610,6 @@ onUnmounted(() => { pollTimer = null } }) - -const columns = [ - { title: '存储名称', dataIndex: 'name', width: 160 }, - { title: '类型', dataIndex: 'type', width: 100 }, - { title: '标识', dataIndex: 'service_identity', width: 180, ellipsis: true, tooltip: true }, - { title: '型号/描述', dataIndex: 'model', width: 160, ellipsis: true, tooltip: true }, - { title: '状态', slotName: 'status', width: 90 }, - { title: '周期采集', slotName: 'collect_on', width: 100 }, - { title: '最近检查', slotName: 'last_check', width: 160 }, - { title: '响应耗时', slotName: 'latency_col', width: 100 }, -]