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 @@
@@ -184,58 +182,18 @@
- {{ chartHint }}
-
-
-
-
+
-
-
-
-
- 共 {{ monitorSummary?.total_devices ?? storageList.length }} 台
-
-
-
-
-
- 在线
- 异常
- 离线
- 未知
-
-
- {{ formatDateTime(record.last_check_time) }}
-
-
- {{ record.response_time ? `${record.response_time.toFixed(1)} ms` : '-' }}
-
-
-
- {{ record.collect_on ? '开启' : '关闭' }}
-
-
-
-
@@ -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 },
-]