From f030f9c5c9b40310880edfcb6a5f3ddda2542b76 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Mon, 13 Apr 2026 20:57:41 +0800 Subject: [PATCH] fix --- src/api/ops/host-hardware.ts | 334 ++++++++ src/router/local-menu-flat.ts | 16 + src/router/local-menu-items.ts | 17 + .../components/HardwareDeviceConfigDialog.vue | 651 ++++++++++++++++ src/views/ops/pages/dc/server/index.vue | 27 +- .../ops/pages/monitor/host-hardware/index.vue | 736 ++++++++++++++++++ 6 files changed, 1780 insertions(+), 1 deletion(-) create mode 100644 src/api/ops/host-hardware.ts create mode 100644 src/views/ops/pages/dc/server/components/HardwareDeviceConfigDialog.vue create mode 100644 src/views/ops/pages/monitor/host-hardware/index.vue diff --git a/src/api/ops/host-hardware.ts b/src/api/ops/host-hardware.ts new file mode 100644 index 0000000..6b965de --- /dev/null +++ b/src/api/ops/host-hardware.ts @@ -0,0 +1,334 @@ +/** + * 服务器硬件监控页专用:对接 DC-Hardware 服务(/DC-Hardware/v1)。 + * 与「机房设备监控」等业务区分,勿混用命名。 + */ +import { request } from '@/api/request' + +const HW_PREFIX = '/DC-Hardware/v1' + +/** 与 bsm-sdk 成功响应一致:业务数据在 details */ +export interface HostHardwareApiEnvelope { + code?: number | string + message?: string + details?: T +} + +export interface HostHardwareDeviceDetailPayload { + device: HostHardwareDevice + status: HostHardwareStatus | null +} + +export interface HostHardwareDevice { + id: string + name?: string + ip: string + /** 设备类别,如 server/switch/storage;未返回时前端可按 server 展示 */ + type?: string + protocol: string + manufacturer?: string + model?: string + serial_number?: string + username?: string + password?: string + port?: number + snmp_port?: number + community?: string + snmp_version?: string + redfish_base_url?: string + redfish_tls_skip_verify?: boolean + ipmi_timeout_seconds?: number + snmp_timeout_seconds?: number + redfish_timeout_seconds?: number + ipmi_collect_enabled?: boolean + snmp_collect_enabled?: boolean + redfish_collect_enabled?: boolean + collect_interval?: number + asset_id?: string + server_identity?: string + enabled?: boolean + status?: string + tags?: string + description?: string + extra_config?: string +} + +/** 创建/更新 DC-Hardware 设备(与 CreateDeviceRequest 一致;密码在更新时为空则不修改) */ +export interface HostHardwareDeviceUpsert { + name: string + ip: string + /** 省略或空则服务端默认 server */ + type?: string + protocol: string + username?: string + password?: string + port?: number + snmp_port?: number + community?: string + snmp_version?: string + redfish_base_url?: string + redfish_tls_skip_verify?: boolean + location?: string + description?: string + tags?: string + asset_id?: string + server_identity?: string + extra_config?: string + ipmi_timeout_seconds?: number + snmp_timeout_seconds?: number + redfish_timeout_seconds?: number + ipmi_collect_enabled?: boolean + snmp_collect_enabled?: boolean + redfish_collect_enabled?: boolean + collect_interval?: number +} + +export interface HostHardwareDeviceListPayload { + total: number + page: string | number + page_size: string | number + data: HostHardwareDevice[] +} + +export interface HostHardwareStatus { + id?: string + device_id?: string + status?: string + power_status?: string + cpu_status?: string + memory_status?: string + disk_status?: string + fan_status?: string + temperature_status?: string + network_status?: string + psu_status?: string + raid_status?: string + last_check_time?: string + error_message?: string + raw_data?: string +} + +export interface HostHardwareMetricsRow { + id?: string + device_id?: string + metric_name: string + metric_type: string + metric_value: number + unit?: string + status?: string + threshold?: number + location?: string + collection_time?: string +} + +export interface HostHardwareLatestCollectionPayload { + device_id: string + collected_at?: string | null + status: HostHardwareStatus | null + metrics: HostHardwareMetricsRow[] + timescaledb?: boolean + message?: string + server_identity?: string +} + +interface RawCollectionMetric { + name?: string + type?: string + value?: number + unit?: string + status?: string + threshold?: number + location?: string +} + +interface RawCollectionRoot { + metrics?: RawCollectionMetric[] +} + +export interface NormalizedHostHardwareMetric { + name: string + type: string + value: number + unit: string + status: string + threshold?: number + location?: string +} + +export interface HostHardwareMetricHistoryPayload { + device_id: string + metric_name: string + start_time?: string + end_time?: string + data: HostHardwareMetricsRow[] + timescaledb?: boolean + message?: string +} + +export interface HostHardwareStatisticsRow { + device_id: string + stat_date: string + online_time?: number + offline_time?: number + warning_count?: number + critical_count?: number + avg_temperature?: number + max_temperature?: number + min_temperature?: number + avg_fan_speed?: number + avg_power_usage?: number + max_power_usage?: number + total_check_count?: number + success_check_count?: number + failed_check_count?: number + availability?: number +} + +export function isHostHardwareApiSuccess(res: HostHardwareApiEnvelope | null | undefined): boolean { + const c = res?.code + if (c === 0 || c === '0') return true + // 少数网关/代理以 200 表示业务成功 + if (c === 200 || c === '200') return true + return false +} + +export function unwrapHostHardwareDetails( + res: (HostHardwareApiEnvelope & { data?: T }) | null | undefined, +): T | null { + if (!res || !isHostHardwareApiSuccess(res)) return null + // 部分网关/SDK 将载荷放在 data 而非 details,与 logs 等模块一致做兼容 + return res.details ?? res.data ?? null +} + +/** 按 server_identity 拉取最新一整轮采集(JWT) */ +export function fetchHostHardwareLatestCollection(serverIdentity: string) { + return request.get>( + `${HW_PREFIX}/devices/by-server-identity/collection/latest`, + { params: { server_identity: serverIdentity } }, + ) +} + +/** 设备详情(含最新一条 status) */ +export function fetchHostHardwareDevice(deviceId: string) { + return request.get>( + `${HW_PREFIX}/devices/${encodeURIComponent(deviceId)}`, + ) +} + +/** 分页列表(可按 type / status / asset_id 筛选) */ +export function fetchHostHardwareDeviceList(params?: { + type?: string + status?: string + asset_id?: string + page?: number + page_size?: number +}) { + return request.get>(`${HW_PREFIX}/devices`, { + params, + }) +} + +/** 创建设备 */ +export function createHostHardwareDevice(data: HostHardwareDeviceUpsert) { + return request.post>(`${HW_PREFIX}/devices`, data) +} + +/** 更新设备(全量必填字段;密码留空则不修改) */ +export function updateHostHardwareDevice(deviceId: string, data: HostHardwareDeviceUpsert) { + return request.put>( + `${HW_PREFIX}/devices/${encodeURIComponent(deviceId)}`, + data, + ) +} + +/** 异步立即采集 */ +export function triggerHostHardwareCollect(deviceId: string) { + return request.post>(`${HW_PREFIX}/devices/${encodeURIComponent(deviceId)}/collect`) +} + +/** 启用监控 */ +export function enableHostHardwareDevice(deviceId: string) { + return request.post>(`${HW_PREFIX}/devices/${encodeURIComponent(deviceId)}/enable`) +} + +/** 禁用监控 */ +export function disableHostHardwareDevice(deviceId: string) { + return request.post>(`${HW_PREFIX}/devices/${encodeURIComponent(deviceId)}/disable`) +} + +/** 单指标历史曲线(JWT) */ +export function fetchHostHardwareMetricHistory( + deviceId: string, + metricName: string, + startTime?: string, + endTime?: string, +) { + return request.get>( + `${HW_PREFIX}/metrics/devices/${encodeURIComponent(deviceId)}/history`, + { + params: { + metric_name: metricName, + ...(startTime ? { start_time: startTime } : {}), + ...(endTime ? { end_time: endTime } : {}), + }, + }, + ) +} + +/** 日汇总统计 */ +export function fetchHostHardwareStatistics( + deviceId: string, + startDate?: string, + endDate?: string, +) { + return request.get>( + `${HW_PREFIX}/metrics/statistics`, + { + params: { + device_id: deviceId, + ...(startDate ? { start_date: startDate } : {}), + ...(endDate ? { end_date: endDate } : {}), + }, + }, + ) +} + +/** + * 合并 API metrics 与 raw_data 兜底(无时序库时 metrics 可能为空) + */ +export function normalizeHostHardwareMetrics( + status: HostHardwareStatus | null | undefined, + metricsFromApi: HostHardwareMetricsRow[] | null | undefined, +): NormalizedHostHardwareMetric[] { + const fromApi = metricsFromApi ?? [] + if (fromApi.length > 0) { + return fromApi.map((m) => ({ + name: m.metric_name, + type: m.metric_type, + value: m.metric_value, + unit: m.unit ?? '', + status: m.status ?? 'ok', + threshold: m.threshold, + location: m.location, + })) + } + const raw = status?.raw_data + if (!raw || typeof raw !== 'string') return [] + try { + const parsed = JSON.parse(raw) as RawCollectionRoot + const arr = parsed.metrics + if (!Array.isArray(arr)) return [] + return arr + .map((m) => ({ + name: String(m.name ?? ''), + type: String(m.type ?? ''), + value: typeof m.value === 'number' ? m.value : Number(m.value) || 0, + unit: String(m.unit ?? ''), + status: String(m.status ?? 'ok'), + threshold: m.threshold, + location: m.location ? String(m.location) : undefined, + })) + .filter((m) => m.name || m.type) + } catch { + return [] + } +} diff --git a/src/router/local-menu-flat.ts b/src/router/local-menu-flat.ts index 948a41e..df36902 100644 --- a/src/router/local-menu-flat.ts +++ b/src/router/local-menu-flat.ts @@ -227,6 +227,22 @@ export const localMenuFlatItems: MenuItem[] = [ sort_key: 13.5, created_at: '2026-04-11T10:00:00+08:00', }, + { + id: 12021, + identity: '019c7100-0001-7000-8000-000000000021', + title: '服务器硬件监控', + title_en: 'Server Hardware (OOB)', + code: 'ops:综合监控:服务器硬件监控', + description: '综合监控 - 服务器带外硬件(BMC/IPMI/Redfish 等,DC-Hardware)', + app_id: 2, + parent_id: 23, + menu_path: '/monitor/host-hardware', + menu_icon: 'appstore', + component: 'ops/pages/monitor/host-hardware', + type: 1, + sort_key: 13.52, + created_at: '2026-04-13T10:00:00+08:00', + }, { id: 27, identity: '019b591d-01a5-776f-ac4b-3cd896dd3f48', diff --git a/src/router/local-menu-items.ts b/src/router/local-menu-items.ts index 65c98ce..736ef7a 100644 --- a/src/router/local-menu-items.ts +++ b/src/router/local-menu-items.ts @@ -243,6 +243,23 @@ export const localMenuItems: MenuItem[] = [ created_at: '2026-04-11T10:00:00+08:00', children: [], }, + { + id: 12021, + identity: '019c7100-0001-7000-8000-000000000021', + title: '服务器硬件监控', + title_en: 'Server Hardware (OOB)', + code: 'ops:综合监控:服务器硬件监控', + description: '综合监控 - 服务器带外硬件(BMC/IPMI/Redfish 等,DC-Hardware)', + app_id: 2, + parent_id: 23, + menu_path: '/monitor/host-hardware', + menu_icon: 'appstore', + component: 'ops/pages/monitor/host-hardware', + type: 1, + sort_key: 5, + created_at: '2026-04-13T10:00:00+08:00', + children: [], + }, { id: 27, identity: '019b591d-01a5-776f-ac4b-3cd896dd3f48', diff --git a/src/views/ops/pages/dc/server/components/HardwareDeviceConfigDialog.vue b/src/views/ops/pages/dc/server/components/HardwareDeviceConfigDialog.vue new file mode 100644 index 0000000..414abeb --- /dev/null +++ b/src/views/ops/pages/dc/server/components/HardwareDeviceConfigDialog.vue @@ -0,0 +1,651 @@ + + + + + + + diff --git a/src/views/ops/pages/dc/server/index.vue b/src/views/ops/pages/dc/server/index.vue index ba7941b..535dde5 100644 --- a/src/views/ops/pages/dc/server/index.vue +++ b/src/views/ops/pages/dc/server/index.vue @@ -143,6 +143,12 @@ 详情 --> + + + 硬件设备配置 + @@ -195,13 +207,15 @@ import { IconDelete, IconRefresh, IconEye, - IconSettings + IconSettings, + IconStorage, } from '@arco-design/web-vue/es/icon' import type { FormItem } from '@/components/search-form/types' import SearchTable from '@/components/search-table/index.vue' import { searchFormConfig } from './config/search-form' import ServerFormDialog from './components/ServerFormDialog.vue' import QuickConfigDialog from './components/QuickConfigDialog.vue' +import HardwareDeviceConfigDialog from './components/HardwareDeviceConfigDialog.vue' import { columns as columnsConfig } from './config/columns' import { fetchServerList, @@ -221,6 +235,7 @@ const loading = ref(false) const tableData = ref([]) const formDialogVisible = ref(false) const quickConfigVisible = ref(false) +const hardwareConfigVisible = ref(false) const currentRecord = ref(null) const formModel = ref({ keyword: '', @@ -389,6 +404,16 @@ const handleQuickConfig = (record: any) => { quickConfigVisible.value = true } +// DC-Hardware 带外硬件设备配置 +const handleHardwareDeviceConfig = (record: any) => { + currentRecord.value = record + hardwareConfigVisible.value = true +} + +const handleHardwareConfigSuccess = () => { + fetchServers() +} + // 编辑服务器 const handleEdit = (record: any) => { currentRecord.value = record diff --git a/src/views/ops/pages/monitor/host-hardware/index.vue b/src/views/ops/pages/monitor/host-hardware/index.vue new file mode 100644 index 0000000..30efc4f --- /dev/null +++ b/src/views/ops/pages/monitor/host-hardware/index.vue @@ -0,0 +1,736 @@ + + + + + + +