diff --git a/config/vite.config.dev.ts b/config/vite.config.dev.ts index f9c3afd..22064b0 100644 --- a/config/vite.config.dev.ts +++ b/config/vite.config.dev.ts @@ -52,8 +52,6 @@ export default ({ mode }: ConfigEnv) => { '/DC-Control': proxyTarget(3031), '/dc-control': proxyTarget(3031), '/dc-network': proxyTarget(12429), - '/DC-Hardware': proxyTarget(12450), - '/dc-hardware': proxyTarget(12450), '/dc-host': proxyTarget(9030), '/dc-middleware': proxyTarget(12428), '/dc-database': proxyTarget(12580), diff --git a/src/api/ops/alertPolicy.ts b/src/api/ops/alertPolicy.ts index ea71a97..ba459b1 100644 --- a/src/api/ops/alertPolicy.ts +++ b/src/api/ops/alertPolicy.ts @@ -1,151 +1,255 @@ import { request } from '@/api/request' -/** 获取 告警策略列表 */ -export const fetchPolicyList = (data: { - page?: number - page_size?: number - keyword?: string +export type PolicyCategory = + | 'os' + | 'pc_os' + | 'server_hardware' + | 'service_process' + | 'network_device' + | 'security_device' + | 'storage' + | 'database' + | 'middleware' + | 'power_env' + | 'safety_env' + +export type ServicePolicyCategory = 'database' | 'middleware' | 'host' | 'pc' | 'network' | 'security' | 'storage' | 'room_device' +export type RuleType = 'static' | 'dynamic' +export type CompareOperator = '>' | '>=' | '<' | '<=' | '==' | '!=' +export type SuppressionType = 'dedup' | 'aggregate' | 'dependency' | 'throttle' | 'schedule' + +export interface AlertSeverity { + id: number + code: string + name: string + color?: string enabled?: boolean - priority?: number - created_at_start?: string - created_at_end?: string - order_by?: string - order?: string -}) => request.get('/Alert/v1/policy/list', { params: data }) +} -/** 获取 告警策略详情 */ -export const fetchPolicyDetail = (id: number) => request.get(`/Alert/v1/policy/get/${id}`) +export interface NotificationChannel { + id: number + code: string + name: string + type: string + enabled: boolean +} -/** 创建 告警策略 */ -export const createPolicy = (data: { +export interface MetricDefinition { + id: number + metric_code: string + metric_name: string + resource_category: string + resource_subtype: string + data_source: string + storage_metric_name: string + metric_type: string + unit: string + value_type: 'number' | 'boolean' + compare_operators: string + threshold_suggestion: string + collect_method: string + alert_supported: boolean +} + +export interface AlertRuleInput { + code: string name: string description?: string - enabled?: boolean - priority?: number + rule_type: RuleType + enabled: boolean + metric_name: string + data_source: string + threshold: number + compare_op: CompareOperator + duration: number + eval_interval: number + baseline_config?: string + severity_code: string labels?: string - template_id?: number - auto_create_ticket?: boolean - feedback_template_id?: number - dispatch_rule?: string -}) => request.post('/Alert/v1/policy/create', data) + annotations?: string +} -/** 更新 告警策略 */ -export const updatePolicy = (data: { +export interface AlertRule extends Omit { id: number - name?: string - description?: string - enabled?: boolean - priority?: number - labels?: string - template_id?: number - auto_create_ticket?: boolean - feedback_template_id?: number - dispatch_rule?: string -}) => request.post('/Alert/v1/policy/update', data) - -/** 删除 告警策略 */ -export const deletePolicy = (id: number) => request.delete(`/Alert/v1/policy/delete/${id}`) - -/** 获取 告警规则列表 */ -export const fetchRuleList = (data: { - policy_id?: number - page?: number - page_size?: number - keyword?: string - sort?: string - order?: string -}) => request.get('/Alert/v1/rule/list', { params: data }) - -/** 获取 告警规则详情 */ -export const fetchRuleDetail = (id: number) => request.get(`/Alert/v1/rule/get/${id}`) - -/** 创建 告警规则 */ -export const createRule = (data: { policy_id: number + metric_category: PolicyCategory + severity_id: number + severity?: AlertSeverity +} + +export interface PolicySuppressionRuleInput { + code: string name: string description?: string - rule_type: string - severity_id: number - enabled?: boolean - metric_name?: string - data_source?: string - threshold?: number - compare_op?: string - duration?: number - eval_interval?: number - baseline_config?: string - labels?: string - annotations?: string -}) => request.post('/Alert/v1/rule/create', data) + type: SuppressionType + enabled: boolean + priority: number + dedup_window?: number + dedup_keys?: string + aggregate_window?: number + group_by?: string + aggregate_count?: number + source_matchers?: string + target_matchers?: string + throttle_count?: number + throttle_window?: number + schedule?: string + matchers?: string +} -/** 更新 告警规则 */ -export const updateRule = (data: { +export interface PolicySuppressionRule extends PolicySuppressionRuleInput { id: number - name?: string - description?: string - rule_type?: string - severity_id?: number - enabled?: boolean - metric_name?: string - data_source?: string - threshold?: number - compare_op?: string - duration?: number - eval_interval?: number - baseline_config?: string - labels?: string - annotations?: string -}) => request.post('/Alert/v1/rule/update', data) + policy_id: number +} -/** 删除 告警规则 */ -export const deleteRule = (id: number) => request.delete(`/Alert/v1/rule/delete/${id}`) - -/** 获取 告警模板列表 */ -export const fetchTemplateList = (data: { page?: number; page_size?: number; keyword?: string }) => - request.get('/Alert/v1/template/list', { params: data }) - -/** 获取 告警级别列表 */ -export const fetchSeverityList = (data: { page?: number; page_size?: number; enabled?: string }) => - request.get('/Alert/v1/severity/list', { params: data }) - -/** 获取告警级别下拉选项(不分页),支持 keyword 模糊搜索与 enabled=true 过滤 */ -export const fetchSeverityOptions = (data?: { keyword?: string; enabled?: 'true' | 'false' }) => - request.get('/Alert/v1/severity/options', { - params: { - keyword: data?.keyword || undefined, - enabled: data?.enabled ?? 'true', - }, - }) - -/** 获取工单模板下拉选项 */ -export const fetchFeedbackTemplateOptions = (data?: { status?: 'active' | 'inactive' }) => - request.get('/Feedback/v1/templates/options', { params: data || {} }) - -/** 告警策略选项项 */ -export interface PolicyOptionItem { +export interface AlertPolicy { id: number created_at: string updated_at: string + code: string name: string description: string + category: PolicyCategory + tags: string enabled: boolean - priority: number - labels: string - template_id: number + is_system_preset: boolean auto_create_ticket: boolean feedback_template_id: number - dispatch_rule: string + binding_count: number + rules: AlertRule[] + channels: NotificationChannel[] + suppression_rules: PolicySuppressionRule[] } -/** 告警策略选项查询参数 */ +export interface PolicySaveInput { + code: string + name: string + description?: string + category: PolicyCategory + tags?: string + enabled: boolean + auto_create_ticket: boolean + feedback_template_id: number + channel_ids: number[] + rules: AlertRuleInput[] + suppression_rules: PolicySuppressionRuleInput[] +} + +export interface PolicyDocument { + format: 'ops-alert-policy' + version: 1 + policy: Record +} + +export const POLICY_CATEGORIES: Array<{ value: PolicyCategory; label: string }> = [ + { value: 'os', label: '服务器操作系统' }, + { value: 'pc_os', label: '办公 PC 操作系统' }, + { value: 'server_hardware', label: '服务器硬件' }, + { value: 'service_process', label: '服务器服务可用性' }, + { value: 'network_device', label: '网络设备' }, + { value: 'security_device', label: '安全设备' }, + { value: 'storage', label: '存储设备' }, + { value: 'database', label: '数据库' }, + { value: 'middleware', label: '中间件' }, + { value: 'power_env', label: '电力、UPS、空调与温湿度' }, + { value: 'safety_env', label: '消防、门禁、漏水与有害气体' }, +] + +export const POLICY_CATEGORY_DATA_SOURCE: Record = { + os: 'dc-host', + pc_os: 'dc-host', + server_hardware: 'dc-host', + service_process: 'dc-host', + network_device: 'dc-network', + security_device: 'dc-security', + storage: 'dc-storage', + database: 'dc-database', + middleware: 'dc-middleware', + power_env: 'dc-room-device', + safety_env: 'dc-room-device', +} + +export const DATA_SOURCES = [ + { value: 'dc-host', label: '主机与服务器指标' }, + { value: 'dc-network', label: '网络设备指标' }, + { value: 'dc-database', label: '数据库指标' }, + { value: 'dc-middleware', label: '中间件指标' }, + { value: 'dc-security', label: '安全设备指标' }, + { value: 'dc-storage', label: '存储设备指标' }, + { value: 'dc-room-device', label: '机房设备指标' }, +] + +export const COMPARE_OPERATORS = [ + { value: '>', label: '大于' }, + { value: '>=', label: '大于等于' }, + { value: '<', label: '小于' }, + { value: '<=', label: '小于等于' }, + { value: '==', label: '等于' }, + { value: '!=', label: '不等于' }, +] + +export const RULE_TYPES = [ + { value: 'static', label: '静态阈值' }, + { value: 'dynamic', label: '动态基线' }, +] + +export const SUPPRESSION_TYPE_OPTIONS = [ + { value: 'dedup', label: '去重' }, + { value: 'aggregate', label: '聚合' }, + { value: 'dependency', label: '依赖抑制' }, + { value: 'throttle', label: '限流' }, + { value: 'schedule', label: '定时屏蔽' }, +] + +export const getPolicyDataSource = (category: PolicyCategory | '') => (category ? POLICY_CATEGORY_DATA_SOURCE[category] : '') + +export const fetchPolicyList = (params: { + page?: number + page_size?: number + keyword?: string + enabled?: boolean + category?: PolicyCategory + is_system_preset?: boolean +}) => request.get('/Alert/v1/policy/list', { params }) + +export const fetchPolicyDetail = (id: number) => request.get(`/Alert/v1/policy/get/${id}`) +export const createPolicy = (data: PolicySaveInput) => request.post('/Alert/v1/policy/create', data) +export const updatePolicy = (data: Partial & { id: number }) => request.post('/Alert/v1/policy/update', data) +export const deletePolicy = (id: number) => request.delete(`/Alert/v1/policy/delete/${id}`) +export const copyPolicy = (data: { source_id: number; code: string; name: string }) => request.post('/Alert/v1/policy/copy', data) +export const exportPolicy = (id: number) => request.get(`/Alert/v1/policy/export/${id}`) +export const importPolicy = (data: PolicyDocument) => request.post('/Alert/v1/policy/import', data) + +export const fetchChannelList = (params?: { enabled?: boolean; keyword?: string; page?: number; page_size?: number }) => + request.get('/Alert/v1/channel/list', { params }) + +export const fetchSeverityOptions = (params?: { keyword?: string; enabled?: 'true' | 'false' }) => + request.get('/Alert/v1/severity/options', { + params: { keyword: params?.keyword || undefined, enabled: params?.enabled ?? 'true' }, + }) + +export const fetchFeedbackTemplateOptions = (params?: { status?: 'active' | 'inactive' }) => + request.get('/Feedback/v1/templates/options', { params: params || {} }) + +export const fetchMetricDefinitionOptions = (params: { policy_category: PolicyCategory; keyword?: string; alert_supported?: boolean }) => + request.get('/DC-Control/v1/metric-definitions/options', { + params: { + policy_category: params.policy_category, + keyword: params.keyword || undefined, + alert_supported: params.alert_supported ?? true, + }, + }) + +export type PolicyOptionItem = AlertPolicy + export interface PolicyOptionsParams { keyword?: string enabled?: boolean + resource_category?: ServicePolicyCategory } -export type ServicePolicyCategory = 'database' | 'middleware' | 'host' | 'security' | 'storage' | 'room_device' - export interface ServicePolicyBindingDetails { + resource_uid: string category: ServicePolicyCategory service_identity: string source_version: number @@ -153,6 +257,7 @@ export interface ServicePolicyBindingDetails { } export interface AlertBindingSyncStatus { + resource_uid: string category: ServicePolicyCategory service_identity: string source_version?: number @@ -164,17 +269,15 @@ export interface AlertBindingSyncStatus { next_attempt_at?: string } -/** 获取告警策略下拉选项(不分页),支持 keyword 模糊搜索与 enabled 过滤 */ -export const fetchPolicyOptions = (params?: PolicyOptionsParams) => request.get('/Alert/v1/policy/options', { params }) +export const fetchPolicyOptions = (params?: PolicyOptionsParams) => + request.get('/Alert/v1/policy/options', { params }) -/** 获取资源当前绑定的告警策略 */ -export const fetchServicePolicyBindings = (category: ServicePolicyCategory, serviceIdentity: string) => +export const fetchServicePolicyBindings = (resourceUID: string) => request.get<{ code: number; details?: ServicePolicyBindingDetails; message?: string }>('/Alert/v1/service-policy-bindings', { - params: { category, service_identity: serviceIdentity }, + params: { resource_uid: resourceUID }, }) -/** 获取告警策略绑定同步状态 */ -export const fetchAlertBindingSyncStatus = (category: ServicePolicyCategory, serviceIdentity: string) => +export const fetchAlertBindingSyncStatus = (resourceUID: string) => request.get<{ code: number; details?: AlertBindingSyncStatus; message?: string }>('/DC-Control/v1/alert-binding-sync/status', { - params: { category, service_identity: serviceIdentity }, + params: { resource_uid: resourceUID }, }) diff --git a/src/api/ops/alertRouting.ts b/src/api/ops/alertRouting.ts index f20548c..ead75f3 100644 --- a/src/api/ops/alertRouting.ts +++ b/src/api/ops/alertRouting.ts @@ -36,7 +36,9 @@ export interface ResponsibilityRule extends RoutingScope { name: string description?: string enabled: boolean - owner_user_id: number + owner_type: 'internal' | 'external' + owner_user_id?: number + external_contact_id?: number subscriber_group_ids: number[] | string cc_user_ids: number[] | string } diff --git a/src/api/ops/alertTemplate.ts b/src/api/ops/alertTemplate.ts deleted file mode 100644 index 992d904..0000000 --- a/src/api/ops/alertTemplate.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { request } from '@/api/request' - -// 告警模板类型定义 -export interface AlertTemplate { - id?: number - name: string - category: string - description?: string - enabled?: boolean - tags?: string - rules: AlertRule[] - channels: ChannelRef[] - suppression_rule_ids?: number[] - created_at?: string - updated_at?: string -} - -// 告警规则 -export interface AlertRule { - name: string - data_source: string - metric_name: string - rule_type: 'static' | 'dynamic' - compare_op: '>' | '>=' | '<' | '<=' | '==' | '!=' - threshold: string | number - duration: number - eval_interval: number - severity_code: string - baseline_config?: string - labels?: Record - annotations?: Record -} - -// 通知渠道引用 -export interface ChannelRef { - channel_id: number -} - -// 通知渠道 -export interface NotificationChannel { - id: number - name: string - type: string - config?: Record - enabled?: boolean - created_at?: string - updated_at?: string -} - -// 抑制规则 -export interface SuppressionRule { - id: number - name: string - description?: string - enabled?: boolean - matchers?: Record - created_at?: string - updated_at?: string -} - -// 告警级别 -export interface AlertSeverity { - id: number - code: string - name: string - color?: string - level?: number - description?: string - icon?: string - priority?: number - enabled?: boolean - is_default?: boolean - config?: any -} - -// 指标元数据 -export interface MetricMeta { - metric_name: string - metric_unit: string - type: string - last_timestamp: string -} - -// 指标聚合响应 -export interface MetricsAggregateResponse { - data_source: string - server_identity: string - count: number - metrics: MetricMeta[] -} - -// 模板分类选项 -export const TEMPLATE_CATEGORIES = [ - { value: 'os', label: '操作系统监控模板' }, - { value: 'server_hardware', label: '服务器硬件监控模板' }, - { value: 'network_device', label: '网络设备监控模板' }, - { value: 'security_device', label: '安全设备监控模板' }, - { value: 'storage', label: '存储设备监控模板' }, - { value: 'database', label: '数据库监控模板' }, - { value: 'middleware', label: '中间件监控模板' }, - { value: 'virtualization', label: '虚拟化监控模板' }, - { value: 'power_env', label: '电力/UPS/空调/温湿度模板' }, - { value: 'safety_env', label: '消防/门禁/漏水/有害气体模板' }, -] - -// 数据源选项 -export const DATA_SOURCES = [ - { value: 'dc-host', label: '主机/操作系统指标' }, - { value: 'dc-database', label: '数据库指标' }, - { value: 'dc-middleware', label: '中间件指标' }, - { value: 'dc-security', label: '安全设备指标' }, - { value: 'dc-storage', label: '存储设备指标' }, - { value: 'dc-room-device', label: '机房设备指标' }, -] - -// 比较运算符选项 -export const COMPARE_OPERATORS = [ - { value: '>', label: '大于' }, - { value: '>=', label: '大于等于' }, - { value: '<', label: '小于' }, - { value: '<=', label: '小于等于' }, - { value: '==', label: '等于' }, - { value: '!=', label: '不等于' }, -] - -// 规则类型选项 -export const RULE_TYPES = [ - { value: 'static', label: '静态阈值' }, - { value: 'dynamic', label: '动态阈值' }, -] - -// ==================== 告警模板接口 ==================== - -/** 获取告警模板列表 */ -export const fetchTemplateList = (params?: { page?: number; page_size?: number; name?: string; category?: string; enabled?: boolean }) => { - return request.get('/Alert/v1/template/list', { params }) -} - -/** 获取告警模板详情 */ -export const fetchTemplateDetail = (id: number) => { - return request.get(`/Alert/v1/template/get/${id}`) -} - -/** 创建告警模板 */ -export const createTemplate = (data: AlertTemplate) => { - return request.post('/Alert/v1/template/create', data) -} - -/** 更新告警模板 */ -export const updateTemplate = (data: AlertTemplate) => { - return request.post('/Alert/v1/template/update', data) -} - -/** 删除告警模板 */ -export const deleteTemplate = (id: number) => { - return request.delete(`/Alert/v1/template/delete/${id}`) -} - -// ==================== 通知渠道接口 ==================== - -/** 获取通知渠道列表 */ -export const fetchChannelList = (params?: { enabled?: boolean; keyword?: string }) => { - return request.get('/Alert/v1/channel/list', { params }) -} - -/** 获取通知渠道详情 */ -export const fetchChannelDetail = (id: number) => { - return request.get(`/Alert/v1/channel/get/${id}`) -} - -// ==================== 抑制规则接口 ==================== - -/** 获取抑制规则列表 */ -export const fetchSuppressionList = (params?: { enabled?: boolean; keyword?: string }) => { - return request.get('/Alert/v1/suppression/list', { params }) -} - -/** 获取抑制规则详情 */ -export const fetchSuppressionDetail = (id: number) => { - return request.get(`/Alert/v1/suppression/get/${id}`) -} - -// ==================== 告警级别接口 ==================== - -/** 获取告警级别列表 */ -export const fetchSeverityList = () => { - return request.get('/Alert/v1/severity/list') -} - -/** 获取告警级别下拉选项(不分页),支持 keyword 模糊搜索与 enabled=true 过滤 */ -export const fetchSeverityOptions = (params?: { keyword?: string; enabled?: 'true' | 'false' }) => { - return request.get('/Alert/v1/severity/options', { - params: { - keyword: params?.keyword || undefined, - enabled: params?.enabled ?? 'true', - }, - }) -} - -/** 按 code 获取告警级别 */ -export const fetchSeverityByCode = (code: string) => { - return request.get(`/Alert/v1/severity/get-by-code/${code}`) -} - -// ==================== 指标元数据接口 ==================== - -/** 获取指标元数据 */ -export const fetchMetricsMeta = (params: { data_source: string; server_identity?: string; keyword?: string; limit?: number }) => { - return request.get('/DC-Control/v1/services/metrics/meta', { params }) -} diff --git a/src/api/ops/asset.ts b/src/api/ops/asset.ts index eb49732..909b8c7 100644 --- a/src/api/ops/asset.ts +++ b/src/api/ops/asset.ts @@ -95,6 +95,34 @@ export interface AssetForm { attachments?: string } +export type DeviceLifecycleState = 'active' | 'deleting' | 'deleted' +export type DeviceProjectionStatus = 'pending' | 'synced' | 'failed' + +export interface DeviceLifecycleDetails { + resource_uid: string + lifecycle_state: DeviceLifecycleState + projection_status: DeviceProjectionStatus + projection_error: string + event_version: number + deleted_at?: string | null +} + +export interface DeleteDeviceDetails { + resource_uid: string + event_id: number + lifecycle_state: DeviceLifecycleState +} + +export interface DeleteDeviceWaitResult { + status: 'deleted' | 'processing' + lifecycle: DeviceLifecycleDetails +} + +const lifecyclePollIntervalMs = 500 +const lifecyclePollAttempts = 120 + +const waitLifecyclePollInterval = () => new Promise((resolve) => window.setTimeout(resolve, lifecyclePollIntervalMs)) + /** 获取资产列表(分页) */ export const fetchAssetList = (data?: AssetListParams) => { return request.get('/Assets/v1/devices', { params: data || {} }) @@ -110,6 +138,11 @@ export const fetchAssetDetail = (resourceUid: string) => { return request.get(`/Assets/v1/devices/${encodeURIComponent(resourceUid)}`) } +/** 获取设备生命周期,包含已完成软删除的设备 */ +export const fetchDeviceLifecycle = (resourceUid: string) => { + return request.get(`/Assets/v1/devices/${encodeURIComponent(resourceUid)}/lifecycle`) +} + /** 创建资产 */ export const createAsset = (data: AssetForm) => { return request.post('/Assets/v1/devices', data) @@ -130,6 +163,57 @@ export const deleteAsset = (resourceUid: string) => { return request.delete(`/Assets/v1/devices/${encodeURIComponent(resourceUid)}`) } +/** 等待指定版本完成资源投影 */ +export async function waitForDeviceProjection(resourceUid: string, eventVersion: number): Promise { + let latestError = '' + for (let attempt = 0; attempt < lifecyclePollAttempts; attempt += 1) { + const response: any = await fetchDeviceLifecycle(resourceUid) + if (response?.code !== 0 || !response?.details) { + throw new Error(response?.message || '查询设备投影状态失败') + } + const lifecycle = response.details as DeviceLifecycleDetails + if (lifecycle.lifecycle_state !== 'active') throw new Error('设备已进入删除流程,不能继续保存配置') + if (lifecycle.projection_status === 'synced' && Number(lifecycle.event_version) >= eventVersion) return lifecycle + if (lifecycle.projection_status === 'failed' && lifecycle.projection_error) latestError = lifecycle.projection_error + await waitLifecyclePollInterval() + } + const detail = latestError ? `:${latestError}` : '' + throw new Error(`设备主信息同步未完成${detail};设备主数据已保留,可稍后再次保存配置`) +} + +/** 发起设备删除,并等待后端完成跨服务清理 */ +export async function deleteDeviceAndWait(resourceUid: string): Promise { + const deleteResponse: any = await deleteAsset(resourceUid) + if (deleteResponse?.code !== 0 || !deleteResponse?.details) { + throw new Error(deleteResponse?.message || '发起设备删除失败') + } + const deletion = deleteResponse.details as DeleteDeviceDetails + if (deletion.lifecycle_state === 'deleted') { + return { + status: 'deleted', + lifecycle: { + resource_uid: resourceUid, + lifecycle_state: 'deleted', + projection_status: 'synced', + projection_error: '', + event_version: 0, + }, + } + } + let latest: DeviceLifecycleDetails | undefined + for (let attempt = 0; attempt < lifecyclePollAttempts; attempt += 1) { + const response: any = await fetchDeviceLifecycle(resourceUid) + if (response?.code !== 0 || !response?.details) { + throw new Error(response?.message || '查询设备删除状态失败') + } + latest = response.details as DeviceLifecycleDetails + if (latest.lifecycle_state === 'deleted') return { status: 'deleted', lifecycle: latest } + await waitLifecyclePollInterval() + } + if (!latest) throw new Error('未取得设备删除状态') + return { status: 'processing', lifecycle: latest } +} + /** 导出资产 */ export const exportAssets = (keyword?: string) => { const params: any = {} diff --git a/src/api/ops/businessSystem.ts b/src/api/ops/businessSystem.ts index cc66207..243592b 100644 --- a/src/api/ops/businessSystem.ts +++ b/src/api/ops/businessSystem.ts @@ -1,4 +1,5 @@ import { request } from '@/api/request' +import type { ServerServiceMonitor } from '@/api/ops/server' /** 业务系统基础信息 */ export interface BusinessSystemItem { @@ -6,6 +7,8 @@ export interface BusinessSystemItem { code: string name: string system_type: string + protection_level: string + network_type: string owner_user_id?: number department_id?: number description?: string @@ -15,6 +18,8 @@ export interface BusinessSystemItem { health_level?: string resource_count?: number active_incident_count?: number + active_alert_count?: number + highest_alert_severity?: BusinessAlertSeverity created_at?: string updated_at?: string } @@ -33,6 +38,54 @@ export interface BusinessSystemListParams { size?: number keyword?: string status?: string + system_type?: string + protection_level?: string + network_type?: string +} + +export interface BusinessSystemInput { + code: string + name: string + system_type: string + protection_level: string + network_type: string + owner_user_id?: number + department_id?: number + description: string + status: 'active' | 'disabled' +} + +export type BusinessSystemUpdateInput = Omit + +export interface BusinessAlertSeverity { + code: string + name: string + color: string + priority: number +} + +export interface BusinessResourceAlertSummary { + resource_uid: string + active_count: number + highest_severity?: BusinessAlertSeverity +} + +export interface BusinessAlertSummary { + active_count: number + highest_severity?: BusinessAlertSeverity + business_active_count: number + resources: BusinessResourceAlertSummary[] + active_incidents: Array<{ severity: string }> +} + +export interface BusinessAlertCredentialStatus { + configured: boolean + configured_at?: string + can_manage: boolean +} + +export interface BusinessAlertCredential { + access_token: string } /** 健康分扣分明细 */ @@ -58,6 +111,7 @@ export interface BusinessHealth { active_incidents: number sla_status: string } + alert_summary: BusinessAlertSummary } /** 业务系统资源成员 */ @@ -69,6 +123,24 @@ export interface BusinessResourceMember { criticality: string weight: number description?: string + reference_version: number +} + +export interface BusinessResourceServiceMonitorImpact { + link_id: number + service_monitor_id: number + stable_code: string + service_name: string + process_name: string + port: number +} + +export interface BusinessResourceDeleteConflict { + conflict_code: 'BUSINESS_RESOURCE_SERVICE_MONITOR_REFERENCED' | 'BUSINESS_RESOURCE_REFERENCE_CHANGED' + member_id: number + resource_uid: string + reference_version: number + service_monitors: BusinessResourceServiceMonitorImpact[] } /** 业务拓扑资源 */ @@ -84,6 +156,8 @@ export interface BusinessTopologyResource { owner_user_id?: number department_id?: number asset_id?: number + manufacturer_code?: string + model?: string tags?: string source_service?: string source_table?: string @@ -95,11 +169,17 @@ export interface BusinessTopologyResource { /** 业务拓扑节点 */ export interface BusinessTopologyNode { id?: number + topology_id?: number node_id: string resource_uid: string label: string type: string status: string + ref_type?: 'resource' | 'annotation' | 'region' | 'subtopology' + parent_id?: string + position_x?: number + position_y?: number + description?: string } /** 业务拓扑边 */ @@ -123,10 +203,17 @@ export interface BusinessDependency { /** 业务拓扑 */ export interface BusinessTopology { business_system_id: number + topology_id: number + topology?: { + id: number + name: string + business_system_id?: number + } resources: BusinessTopologyResource[] | null dependencies: BusinessDependency[] | null nodes: BusinessTopologyNode[] | null links: BusinessTopologyEdge[] | null + alert_summary: BusinessAlertSummary } /** 业务时间线项 */ @@ -171,6 +258,48 @@ export interface ListResult { count: number } +/** 业务系统与服务器服务监控项的关联 */ +export interface BusinessServiceMonitorLink { + id: number + business_system_id: number + service_monitor_id: number + service_monitor?: ServerServiceMonitor + created_at?: string + updated_at?: string +} + +/** 管理员对调用链中间件候选的处理结果。 */ +export interface BusinessMiddlewareCandidateDecision { + id: number + business_system_id: number + candidate_key: string + product: string + display_name: string + address?: string + source_service?: string + state: 'confirmed' | 'ignored' + middleware_resource_uid?: string + created_at?: string + updated_at?: string +} + +/** 当前业务系统已经纳入的中间件资源。 */ +export interface BusinessMiddlewareOption { + resource_uid: string + name: string + product: string +} + +export interface BusinessMiddlewareCandidateDecisionInput { + candidate_key: string + product: string + display_name: string + address?: string + source_service?: string + state: 'confirmed' | 'ignored' + middleware_resource_uid?: string +} + /** HTTP/API 检测步骤请求头 */ export interface BusinessHTTPHeader { name: string @@ -180,13 +309,13 @@ export interface BusinessHTTPHeader { /** HTTP/API 检测响应变量提取 */ export interface BusinessHTTPExtract { variable: string - source: 'json' | 'header' | 'cookie' + source: 'json' | 'xml' | 'header' | 'cookie' expression: string } /** HTTP/API 检测断言 */ export interface BusinessHTTPAssertion { - kind: 'status' | 'max_response_time_ms' | 'header_equals' | 'body_contains' | 'json_exists' | 'json_equals' + kind: 'status' | 'max_response_time_ms' | 'header_equals' | 'body_contains' | 'json_exists' | 'json_equals' | 'xml_exists' | 'xml_equals' expression?: string expected?: string } @@ -274,6 +403,41 @@ export const fetchBusinessSystems = (params?: BusinessSystemListParams) => export const fetchBusinessSystemDetail = (id: number) => request.get<{ code?: number; details?: BusinessSystemItem; message?: string }>(`/DC-Control/v1/business-systems/${id}`) +/** 获取不受分页限制的业务分类树数据源 */ +export const fetchBusinessSystemTreeSource = (status?: string) => + request.get<{ code?: number; details?: ListResult; message?: string }>('/DC-Control/v1/business-systems/tree-source', { + params: { status }, + }) + +/** 新增业务系统 */ +export const createBusinessSystem = (data: BusinessSystemInput) => + request.post<{ code?: number; details?: BusinessSystemItem; message?: string }>('/DC-Control/v1/business-systems', data) + +/** 编辑业务系统 */ +export const updateBusinessSystem = (id: number, data: BusinessSystemUpdateInput) => + request.put<{ code?: number; details?: BusinessSystemItem; message?: string }>(`/DC-Control/v1/business-systems/${id}`, data) + +/** 删除业务系统及其自身关联,不删除资源或服务监控项 */ +export const deleteBusinessSystem = (id: number) => request.delete(`/DC-Control/v1/business-systems/${id}`) + +/** 获取当前业务系统外部告警访问凭证状态 */ +export const fetchBusinessAlertCredentialStatus = (id: number) => + request.get<{ code?: number; details?: BusinessAlertCredentialStatus; message?: string }>( + `/DC-Control/v1/business-systems/${id}/alert-credential` + ) + +/** 生成或替换外部告警访问凭证 */ +export const generateBusinessAlertCredential = (id: number) => + request.post<{ code?: number; details?: BusinessAlertCredential; message?: string }>( + `/DC-Control/v1/business-systems/${id}/alert-credential/generate` + ) + +/** 查看当前有效的外部告警访问凭证明文 */ +export const revealBusinessAlertCredential = (id: number) => + request.get<{ code?: number; details?: BusinessAlertCredential; message?: string }>( + `/DC-Control/v1/business-systems/${id}/alert-credential/reveal` + ) + /** 获取业务健康摘要 */ export const fetchBusinessHealth = (id: number) => request.get<{ code?: number; details?: BusinessHealth; message?: string }>(`/DC-Control/v1/business-systems/${id}/health`) @@ -294,8 +458,52 @@ export interface BusinessResourceMemberPayload { export const createBusinessMember = (id: number, data: BusinessResourceMemberPayload) => request.post<{ code?: number; details?: BusinessResourceMember; message?: string }>(`/DC-Control/v1/business-systems/${id}/resources`, data) -export const deleteBusinessMember = (id: number, memberId: number) => - request.delete<{ code?: number; details?: { message: string }; message?: string }>(`/DC-Control/v1/business-systems/${id}/resources/${memberId}`) +export const deleteBusinessMember = ( + id: number, + memberId: number, + options?: { unlinkServiceMonitors: boolean; referenceVersion: number } +) => + request.delete<{ code?: number; details?: { message: string }; message?: string }>( + `/DC-Control/v1/business-systems/${id}/resources/${memberId}`, + options?.unlinkServiceMonitors + ? { params: { unlink_service_monitors: true, reference_version: options.referenceVersion } } + : undefined + ) + +/** 获取业务系统关联的服务器服务监控项 */ +export const fetchBusinessServiceMonitors = (id: number) => + request.get<{ code?: number; details?: ListResult; message?: string }>( + `/DC-Control/v1/business-systems/${id}/service-monitors` + ) + +/** 关联一个已经在服务器上配置好的服务监控项 */ +export const createBusinessServiceMonitor = (id: number, serviceMonitorId: number) => + request.post<{ code?: number; details?: BusinessServiceMonitorLink; message?: string }>( + `/DC-Control/v1/business-systems/${id}/service-monitors`, + { service_monitor_id: serviceMonitorId } + ) + +/** 仅解除业务关联,不删除服务器上的服务监控项 */ +export const deleteBusinessServiceMonitor = (id: number, linkId: number) => + request.delete<{ code?: number; details?: { message: string }; message?: string }>( + `/DC-Control/v1/business-systems/${id}/service-monitors/${linkId}` + ) + +export const fetchBusinessMiddlewareCandidateDecisions = (id: number) => + request.get<{ code?: number; details?: ListResult; message?: string }>( + `/DC-Control/v1/business-systems/${id}/middleware-candidates/decisions` + ) + +export const fetchBusinessMiddlewareOptions = (id: number) => + request.get<{ code?: number; details?: ListResult; message?: string }>( + `/DC-Control/v1/business-systems/${id}/middleware-resources` + ) + +export const decideBusinessMiddlewareCandidate = (id: number, data: BusinessMiddlewareCandidateDecisionInput) => + request.post<{ code?: number; details?: BusinessMiddlewareCandidateDecision; message?: string }>( + `/DC-Control/v1/business-systems/${id}/middleware-candidates/decision`, + data + ) /** 获取业务拓扑 */ export const fetchBusinessTopology = (id: number) => diff --git a/src/api/ops/collection.ts b/src/api/ops/collection.ts index babef81..3e6b725 100644 --- a/src/api/ops/collection.ts +++ b/src/api/ops/collection.ts @@ -49,7 +49,6 @@ export interface CollectionTemplate { index_discoveries: string inventory_mappings: string read_only_queries: string - alert_rule_codes: string content_digest: string description?: string adapter_verified: boolean diff --git a/src/api/ops/database.ts b/src/api/ops/database.ts index af4cac6..7c6f5bb 100644 --- a/src/api/ops/database.ts +++ b/src/api/ops/database.ts @@ -1,13 +1,17 @@ import { request } from '@/api/request' /** 数据库类型枚举 */ -export type DatabaseType = 'MySQL' | 'PostgreSQL' | 'Redis' | 'MongoDB' | 'DM' | 'KingBase' | 'Oracle' | 'OracleRAC' +export type DatabaseType = 'MySQL' | 'PostgreSQL' | 'TimescaleDB' | 'Redis' | 'MongoDB' | 'DM' | 'KingBase' | 'Oracle' | 'OracleRAC' + +/** 数据库传输加密模式 */ +export type DatabaseTLSMode = 'disable' | 'require' | 'verify-ca' | 'verify-full' /** 数据库服务状态 */ export type DatabaseStatus = 'online' | 'offline' | 'error' /** 数据库服务接口返回数据结构 */ export interface DatabaseService { + resource_uid: string id: number created_at: string updated_at: string @@ -19,7 +23,7 @@ export interface DatabaseService { host: string port: number database: string - tls_mode: 'require' | 'verify-ca' | 'verify-full' + tls_mode: DatabaseTLSMode description: string enabled: boolean interval: number @@ -54,11 +58,12 @@ export interface CreateDatabaseParams { host: string port: number database?: string - tls_mode?: 'require' | 'verify-ca' | 'verify-full' + tls_mode?: DatabaseTLSMode description?: string enabled?: boolean interval?: number tags?: string + policy_ids?: number[] } /** 更新数据库服务请求参数 */ @@ -69,11 +74,12 @@ export interface UpdateDatabaseParams { host?: string port?: number database?: string - tls_mode?: 'require' | 'verify-ca' | 'verify-full' + tls_mode?: DatabaseTLSMode description?: string enabled?: boolean interval?: number tags?: string + policy_ids?: number[] } /** 列表查询参数 */ @@ -113,8 +119,8 @@ export interface MetricMetaItem { /** 指标元数据响应 */ export interface MetricMetaResponse { data_source: string - collector_identity: string - server_identity: string + resource_uid: string + service_identity: string count: number metrics: MetricMetaItem[] } @@ -122,11 +128,51 @@ export interface MetricMetaResponse { /** 指标元数据查询参数 */ export interface MetricMetaQueryParams { data_source: string - server_identity?: string + resource_uid?: string keyword?: string limit?: number } +/** 数据库最新指标 */ +export interface DatabaseMetricItem { + timestamp: string + service_identity: string + type: string + metric_name: string + series_key: string + labels: string + metric_value: number + metric_unit: string +} + +/** 数据库最新一批指标响应 */ +export interface DatabaseMetricsLatestResponse { + resource_uid: string + service_identity: string + latest_timestamp: string | null + count: number + metrics: DatabaseMetricItem[] +} + +/** SQL TOP 行 */ +export interface DatabaseSQLTopItem { + rank: number + sql_id: string + sql_text: string + executions: number + metric_value: number + metric_unit: string + timestamp: string +} + +/** SQL TOP 响应 */ +export interface DatabaseSQLTopResponse { + resource_uid: string + service_identity: string + latest_timestamp: string | null + items: DatabaseSQLTopItem[] +} + /** * 获取数据库服务列表 * @param params 查询参数 @@ -143,7 +189,7 @@ export const fetchDatabaseDetail = (id: number) => request.get( * 创建数据库服务 * @param data 创建参数 */ -export const createDatabase = (data: CreateDatabaseParams) => request.post<{ message: string; id: number; service_identity: string }>('/DC-Control/v1/database', data) +export const createDatabase = (data: CreateDatabaseParams) => request.post<{ message: string; id: number; resource_uid: string; service_identity: string }>('/DC-Control/v1/database', data) /** * 更新数据库服务 @@ -172,3 +218,19 @@ export const collectDatabaseMetrics = (data: CollectMetricsParams) => */ export const fetchMetricMeta = (params: MetricMetaQueryParams) => request.get('/DC-Control/v1/services/metrics/meta', { params }) + +/** 查询数据库最新一批指标 */ +export const fetchDatabaseMetricsLatest = (resourceUid: string) => + request.get('/DC-Control/v1/database/metrics/latest', { params: { resource_uid: resourceUid } }) + +/** 查询 SQL 累计执行耗时排名 */ +export const fetchDatabaseSQLTopElapsed = (resourceUid: string) => + request.get('/DC-Control/v1/database/metrics/sql-top/elapsed', { params: { resource_uid: resourceUid } }) + +/** 查询 SQL 执行次数排名 */ +export const fetchDatabaseSQLTopCalls = (resourceUid: string) => + request.get('/DC-Control/v1/database/metrics/sql-top/calls', { params: { resource_uid: resourceUid } }) + +/** 查询 SQL 块读取量排名 */ +export const fetchDatabaseSQLTopIO = (resourceUid: string) => + request.get('/DC-Control/v1/database/metrics/sql-top/io', { params: { resource_uid: resourceUid } }) diff --git a/src/api/ops/dcControl.ts b/src/api/ops/dcControl.ts index 831d050..097051d 100644 --- a/src/api/ops/dcControl.ts +++ b/src/api/ops/dcControl.ts @@ -15,7 +15,8 @@ export interface LicenceQuotas { } export interface LicenceInfo { - id: string + enabled: boolean + id: string platform_name: string workspace: string issued_on: string @@ -58,6 +59,9 @@ export interface OptionItem { id?: number label: string value: string + resource_category?: string + manufacturer_code?: string + model?: string } export interface ControlResource { @@ -74,6 +78,8 @@ export interface ControlResource { device_level?: string business_system_id?: number owner_user_id?: number + responsibility_type?: 'none' | 'internal' | 'external' + external_contact_id?: number department_id?: number asset_id?: number datacenter_id?: number @@ -119,6 +125,24 @@ export interface ControlResourceOverview { } } +export interface ControlResourceTrendPoint { + time: string + value: number + count: number +} + +export interface ControlResourceTrend { + resource_uid: string + metric_name: string + metric_type: string + unit: string + window_hours: number + interval_seconds: number + start_time: string + end_time: string + points: ControlResourceTrendPoint[] +} + export interface ControlResourceType { id: number resource_category: string @@ -134,6 +158,10 @@ export interface ControlMetricDefinition { metric_code: string metric_name: string resource_category: string + resource_subtype: string + data_source: string + metric_type: string + storage_metric_name: string unit?: string threshold_suggestion?: string collect_method?: string @@ -220,6 +248,24 @@ export const fetchControlResourceOverview = (resourceUid: string) => `/DC-Control/v1/resources/${encodeURIComponent(resourceUid)}/overview` ) +export const fetchControlResourceMetricTrend = ( + resourceUid: string, + params?: { metric_name?: string; metric_type?: string; unit?: string; window_hours?: number } +) => + request.get<{ code?: number; details?: ControlResourceTrend; message?: string }>( + `/DC-Control/v1/resources/${encodeURIComponent(resourceUid)}/metrics/trend`, + { params } + ) + +export const saveControlResourceResponsibility = ( + resourceUid: string, + data: { responsibility_type: 'none' | 'internal' | 'external'; owner_user_id?: number; external_contact_id?: number } +) => + request.put<{ code?: number; details?: ControlResource; message?: string }>( + `/DC-Control/v1/resources/${encodeURIComponent(resourceUid)}/responsibility`, + data + ) + export const fetchControlResourceTypes = (params?: { page?: number; size?: number; keyword?: string }) => request.get<{ code?: number; details?: PageResult; message?: string }>('/DC-Control/v1/resource-types', { params }) @@ -231,8 +277,16 @@ export const fetchControlMetricDefinitions = (params?: { page?: number; size?: n params, }) -export const fetchControlMetricDefinitionOptions = (params?: { resource_category?: string }) => - request.get<{ code?: number; details?: { list: OptionItem[]; count: number }; message?: string }>( +export interface MetricDefinitionOptionParams { + resource_category?: string + resource_subtype?: string + data_source?: string + alert_supported?: boolean + keyword?: string +} + +export const fetchControlMetricDefinitionOptions = (params?: MetricDefinitionOptionParams) => + request.get<{ code?: number; details?: { list: ControlMetricDefinition[]; count: number }; message?: string }>( '/DC-Control/v1/metric-definitions/options', { params } ) diff --git a/src/api/ops/externalContact.ts b/src/api/ops/externalContact.ts new file mode 100644 index 0000000..ac3b59d --- /dev/null +++ b/src/api/ops/externalContact.ts @@ -0,0 +1,37 @@ +import { request } from '@/api/request' + +export interface ExternalContact { + id: number + code: string + name: string + phone: string + email: string + enabled: boolean + remarks: string + created_at?: string + updated_at?: string +} + +export interface ExternalContactInput { + id?: number + code: string + name: string + phone?: string + email?: string + enabled?: boolean + remarks?: string +} + +export const fetchExternalContacts = (params?: { page?: number; page_size?: number; keyword?: string; enabled?: boolean }) => + request.get('/Alert/v1/external-contacts/list', { params }) + +export const fetchExternalContactOptions = (keyword?: string) => + request.get('/Alert/v1/external-contacts/options', { params: { keyword } }) + +export const createExternalContact = (data: ExternalContactInput) => + request.post('/Alert/v1/external-contacts/create', data) + +export const updateExternalContact = (data: ExternalContactInput) => + request.post('/Alert/v1/external-contacts/update', data) + +export const deleteExternalContact = (id: number) => request.delete(`/Alert/v1/external-contacts/delete/${id}`) diff --git a/src/api/ops/governance.ts b/src/api/ops/governance.ts index f8a2147..cdca78d 100644 --- a/src/api/ops/governance.ts +++ b/src/api/ops/governance.ts @@ -38,9 +38,15 @@ export interface RuleSuggestion { export interface CapacityRisk { resource_uid: string + resource_category: string + display_name: string + object_type: string + object_name: string metric_code: string unit: string current_value: number + average_value: number + peak_value: number status: '资源不足' | '配置合理' | '资源闲置' | '容量风险' | '数据不足' idle_threshold: number risk_threshold: number @@ -55,6 +61,11 @@ export interface CapacityRisk { data_start?: string data_end?: string data_span_hours: number + analysis_window_hours: number + data_age_seconds: number + data_freshness: 'missing' | 'fresh' | 'stale' + insufficient_reason?: string + estimated_available_days?: number forecast_hours: number } @@ -79,8 +90,7 @@ export interface SloDashboard { export const fetchAlertQualityScores = (params?: { hours?: number }) => request.get('/Alert/v1/governance/quality', { params }) -export const fetchAlertNoise = (params?: { hours?: number }) => - request.get('/Alert/v1/governance/noise', { params }) +export const fetchAlertNoise = (params?: { hours?: number }) => request.get('/Alert/v1/governance/noise', { params }) export const fetchRuleSuggestions = (params?: { hours?: number }) => request.get('/Alert/v1/governance/suggestions', { params }) @@ -88,4 +98,12 @@ export const fetchRuleSuggestions = (params?: { hours?: number }) => export const fetchCapacityRisk = (params?: { resource_uid?: string; metric_type?: string; metric_code?: string }) => request.get('/DC-Control/v1/risk/capacity', { params }) +export const fetchCapacityRisks = (params: { + resource_uid: string + metric_type?: string + metric_code?: string + object_type?: string + object_name?: string +}) => request.get('/DC-Control/v1/risk/capacities', { params }) + export const fetchSloDashboard = () => request.get('/Feedback/v1/tickets/slo/dashboard') diff --git a/src/api/ops/goview.ts b/src/api/ops/goview.ts index 5956f00..026afb5 100644 --- a/src/api/ops/goview.ts +++ b/src/api/ops/goview.ts @@ -1,11 +1,16 @@ import { request } from '@/api/request' +export enum GoViewProjectState { + Draft = -1, + Published = 1, +} + // GoView 项目类型定义 export interface GoViewProject { - id: string + id: number identity: string projectName: string - state: -1 | 1 // -1 未发布, 1 已发布 + state: GoViewProjectState createTime: string updateTime?: string createUserId: number @@ -16,13 +21,30 @@ export interface GoViewProject { } export interface GoViewProjectData { - id: string + id: number + identity: string projectId: string content: string // JSON 格式字符串 createTime: string + updateTime: string createUserId: string } +export interface GoViewProjectDetail { + identity: string + content: string + projectName: string + state: GoViewProjectState + createTime: string + createUserId: number + indexImage: string + backgroundImage: string + isDelete: 0 | 1 + remarks: string + publishVersion: string + mgtScreenId: number +} + export interface OssInfo { BucketName: string bucketURL: string @@ -47,13 +69,13 @@ export const fetchOssInfo = () => { } /** 获取项目列表 */ -export const fetchProjectList = (params?: { page?: number; size?: number; state?: -1 | 1 }) => { +export const fetchProjectList = (params?: { page?: number; size?: number; state?: GoViewProjectState }) => { return request.get>('/Visual/v1/project/list', { params }) } /** 获取项目数据(公开接口,无需认证) */ export const fetchProjectData = (projectId: string) => { - return request.get>('/Visual/v1/project/getData', { + return request.get>('/Visual/v1/project/getData', { params: { projectId }, }) } @@ -61,7 +83,7 @@ export const fetchProjectData = (projectId: string) => { /** 创建项目 */ export const createProject = (data: { projectName: string - state: -1 | 1 + state?: GoViewProjectState createTime?: string createUserId?: number isDelete?: 0 | 1 @@ -91,8 +113,11 @@ export const deleteProject = (ids: string) => { } /** 发布/取消发布项目 */ -export const publishProject = (data: { identity: string; state: -1 | 1 }) => { - return request.put>('/Visual/v1/project/publish', data) +export const publishProject = (data: { identity: string; state: GoViewProjectState }) => { + return request.put>( + '/Visual/v1/project/publish', + data, + ) } /** 保存项目数据 */ diff --git a/src/api/ops/host-hardware.ts b/src/api/ops/host-hardware.ts deleted file mode 100644 index b64e8c9..0000000 --- a/src/api/ops/host-hardware.ts +++ /dev/null @@ -1,319 +0,0 @@ -/** - * 服务器硬件监控页专用:对接 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 - snmp_oids?: 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 - snmp_oids?: 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 - /** 是否启用监控调度 */ - enabled?: boolean -} - -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/api/ops/incident.ts b/src/api/ops/incident.ts index dc54f39..33fed69 100644 --- a/src/api/ops/incident.ts +++ b/src/api/ops/incident.ts @@ -26,6 +26,7 @@ export interface Incident { suspended_at?: string resolved_at?: string closed_at?: string + is_external_business: boolean } export const fetchIncidents = (params: { diff --git a/src/api/ops/logs.ts b/src/api/ops/logs.ts index 73e9ac2..e1caa10 100644 --- a/src/api/ops/logs.ts +++ b/src/api/ops/logs.ts @@ -99,6 +99,20 @@ export interface ServiceDependency { average_ms: number } +/** 调用链属性识别出的中间件候选;候选本身不会自动创建设备。 */ +export interface MiddlewareCandidate { + candidate_key: string + product: string + display_name: string + address?: string + source_service: string + business_system_id: number + first_seen_at: string + last_seen_at: string + span_count: number + error_count: number +} + export interface LogEntriesResult { total: number page: number @@ -184,11 +198,18 @@ export interface TrapShield { name: string enabled: boolean source_ip_cidr: string - oid_prefix: string + oid_prefixes: string[] interface_hint: string time_windows_json: string } +export interface TrapSuppressionOption { + oid_prefix: string + name: string + vendor: string + source: 'dictionary' | 'received' +} + export interface AuditLog { id: number created_at: string @@ -270,6 +291,12 @@ export function fetchServiceDependencies(params?: TraceListParams) { return request.get(`${BASE}/service-dependencies`, { params }) } +export function fetchMiddlewareCandidates(businessSystemId: number, startTime?: string) { + return request.get(`${BASE}/middleware-candidates`, { + params: { business_system_id: businessSystemId, start_time: startTime }, + }) +} + export function fetchAlertOutbox(params?: AlertOutboxParams) { return request.get(`${BASE}/alert-outbox`, { params }) } @@ -334,6 +361,10 @@ export function fetchTrapSuppressions() { return request.get(`${BASE}/trap-suppressions`) } +export function fetchTrapSuppressionOptions() { + return request.get(`${BASE}/trap-suppression-options`) +} + export function createTrapSuppression(data: Partial) { return request.post(`${BASE}/trap-suppressions`, data) } diff --git a/src/api/ops/middleware.ts b/src/api/ops/middleware.ts index 6c98126..cf6fdf2 100644 --- a/src/api/ops/middleware.ts +++ b/src/api/ops/middleware.ts @@ -2,11 +2,13 @@ import { request } from '@/api/request' /** 中间件服务项 */ export interface MiddlewareItem { + resource_uid: string id: number created_at: string updated_at: string deleted_at: string | null service_identity: string + server_resource_uid: string server_identity: string name: string category: string @@ -48,7 +50,7 @@ export interface MiddlewareListParams { /** 创建中间件请求参数 */ export interface MiddlewareCreateData { - server_identity?: string + server_resource_uid?: string name: string category: string type: string @@ -62,7 +64,7 @@ export interface MiddlewareCreateData { /** 更新中间件请求参数 */ export interface MiddlewareUpdateData { - server_identity?: string + server_resource_uid?: string name?: string category?: string type?: string @@ -100,8 +102,8 @@ export interface MetricMetaItem { /** 指标元数据响应 */ export interface MetricsMetaResponse { data_source: string - collector_identity: string - server_identity: string + resource_uid: string + service_identity: string count: number metrics: MetricMetaItem[] } @@ -109,7 +111,7 @@ export interface MetricsMetaResponse { /** 指标元数据请求参数 */ export interface MetricsMetaParams { data_source: string - server_identity?: string + resource_uid?: string keyword?: string limit?: number } @@ -126,7 +128,7 @@ export const fetchMiddlewareDetail = (id: number) => { /** 创建中间件服务 */ export const createMiddleware = (data: MiddlewareCreateData) => { - return request.post<{ message: string; id: number; service_identity: string }>('/DC-Control/v1/middleware', data) + return request.post<{ message: string; id: number; resource_uid: string; service_identity: string }>('/DC-Control/v1/middleware', data) } /** 更新中间件服务 */ @@ -145,9 +147,9 @@ export const fetchMiddlewareMetricsMeta = (params: MetricsMetaParams) => { } /** 获取最新时序指标 */ -export const fetchMiddlewareMetricsLatest = (serviceIdentity: string) => { +export const fetchMiddlewareMetricsLatest = (resourceUID: string) => { return request.get<{ code: number; message: string; details: MiddlewareMetricsLatestData }>('/DC-Control/v1/middleware/metrics/latest', { - params: { service_identity: serviceIdentity }, + params: { resource_uid: resourceUID }, }) } diff --git a/src/api/ops/netarchTopo.ts b/src/api/ops/netarchTopo.ts index 100020c..fd3cb33 100644 --- a/src/api/ops/netarchTopo.ts +++ b/src/api/ops/netarchTopo.ts @@ -20,6 +20,8 @@ export interface Topology { link_count?: number enable?: boolean auto_discovery?: boolean + business_system_id?: number + published_to_network_screen?: boolean created_at?: string updated_at?: string } @@ -48,6 +50,37 @@ export interface Link { source_interface_identity?: string target_resource_uid?: string target_interface_identity?: string + in_bps?: number + out_bps?: number + bandwidth_bps?: number + utilization_percent?: number + traffic_band?: 'low' | 'medium' | 'high' | 'insufficient' | 'down' + display_color?: string + data_status?: 'available' | 'insufficient' + data_time?: string +} + +export interface TopologyPresentation { + topology_id: number + node_size: number + show_node_name: boolean + show_node_ip: boolean + show_node_manufacturer: boolean + link_shape: 'straight' | 'curve' | 'step' + link_width: number + show_port_label: boolean + show_traffic_label: boolean + link_animated: boolean + traffic_low_threshold: number + traffic_high_threshold: number + traffic_low_color: string + traffic_medium_color: string + traffic_high_color: string + background_type: 'transparent' | 'solid' | 'gradient' + background_color: string + gradient_start_color: string + gradient_end_color: string + gradient_direction: 'top_to_bottom' | 'left_to_right' | 'diagonal_down_right' | 'diagonal_down_left' } /** 拓扑分组 */ @@ -87,6 +120,8 @@ export interface TopologyNode { ref_type?: string ref_id?: number sub_topology_id?: number | null + manufacturer_code?: string + model?: string created_at?: string updated_at?: string } @@ -161,6 +196,15 @@ export const fetchTopologyView = (id: number) => request.get(`/DC-Control/v1/top /** 获取拓扑图数据(用于前端可视化) */ export const fetchTopologyGraph = (id: number) => request.get(`/DC-Control/v1/topologies/${id}/graph`) +/** 获取并保存拓扑统一展示配置 */ +export const fetchTopologyPresentation = (id: number) => request.get(`/DC-Control/v1/topologies/${id}/presentation`) +export const saveTopologyPresentation = (id: number, data: TopologyPresentation) => + request.put(`/DC-Control/v1/topologies/${id}/presentation`, data) + +/** 发布到固定网络大屏 */ +export const publishTopologyToNetworkScreen = (id: number) => request.post(`/DC-Control/v1/topologies/${id}/publish-network-screen`) +export const fetchNetworkScreenPublication = () => request.get('/DC-Control/v1/topologies/network-screen-publication') + export const fetchImpactAnalysis = (params: { resource_uid: string; topology_id?: number; incident_id?: number; depth?: number }) => request.get('/DC-Control/v1/impact/analyze', { params }) diff --git a/src/api/ops/network-device.ts b/src/api/ops/network-device.ts index 32e8b2b..371c740 100644 --- a/src/api/ops/network-device.ts +++ b/src/api/ops/network-device.ts @@ -65,6 +65,40 @@ export interface WirelessAccessPoint { last_seen_at: string } +export interface NetworkDeviceMetric { + timestamp?: string + service_identity?: string + type?: string + metric_name: string + metric_value: number + metric_unit: string + series_key?: string + labels?: string +} + +export interface NetworkMetricsLatestData { + resource_uid: string + service_identity: string + latest_timestamp: string | null + count: number + metrics: NetworkDeviceMetric[] +} + +export interface NetworkMetricsTimeseriesQuery { + start_time?: string + end_time?: string + metric_name?: string +} + +export interface NetworkMetricsTimeseriesData { + resource_uid: string + service_identity: string + start_time: string + end_time: string + count: number + metrics: NetworkDeviceMetric[] +} + export interface NetworkDeviceListResponse { total: number page: number @@ -82,7 +116,6 @@ export interface NetworkDeviceQueryParams { export interface CreateNetworkDeviceParams { resource_uid: string asset_id: number - service_identity?: string name: string category: string type?: string @@ -93,6 +126,7 @@ export interface CreateNetworkDeviceParams { description?: string enabled?: boolean tags?: string + policy_ids?: number[] } export type UpdateNetworkDeviceParams = Partial @@ -113,3 +147,14 @@ export const fetchNetworkDeviceInterfaces = (id: number) => export const fetchNetworkDeviceAccessPoints = (id: number) => request.get(`/DC-Control/v1/network-device-services/${id}/access-points`) + +export const fetchNetworkMetricsLatest = (resourceUID: string) => + request.get('/DC-Control/v1/network-device-services/metrics/latest', { + params: { resource_uid: resourceUID }, + }) + +export const fetchNetworkMetricsTimeseries = (resourceUID: string, params?: NetworkMetricsTimeseriesQuery) => + request.get( + `/DC-Control/v1/network-device-services/metrics/timeseries/${encodeURIComponent(resourceUID)}`, + { params } + ) diff --git a/src/api/ops/noticeChannel.ts b/src/api/ops/noticeChannel.ts index 5d94e74..64498dd 100644 --- a/src/api/ops/noticeChannel.ts +++ b/src/api/ops/noticeChannel.ts @@ -12,6 +12,7 @@ export const fetchNoticeChannelDetail = (id: number) => { /** 创建通知渠道 */ export const createNoticeChannel = (data: { + code: string name: string type: string config: string @@ -34,12 +35,6 @@ export const createNoticeChannel = (data: { email_cc?: string[] email_bcc?: string[] email_security_mode?: 'none' | 'starttls' | 'tls' - sms_provider?: string - sms_access_key_id?: string - sms_access_key_secret?: string - sms_sign_name?: string - sms_template_code?: string - sms_phones?: string[] in_app_targets?: string[] in_app_aggregation_window?: number }) => { @@ -49,6 +44,7 @@ export const createNoticeChannel = (data: { /** 更新通知渠道 */ export const updateNoticeChannel = (data: { id: number + code?: string name?: string type?: string config?: string @@ -71,12 +67,6 @@ export const updateNoticeChannel = (data: { email_cc?: string[] email_bcc?: string[] email_security_mode?: 'none' | 'starttls' | 'tls' - sms_provider?: string - sms_access_key_id?: string - sms_access_key_secret?: string - sms_sign_name?: string - sms_template_code?: string - sms_phones?: string[] in_app_targets?: string[] in_app_aggregation_window?: number }) => { diff --git a/src/api/ops/pc.ts b/src/api/ops/pc.ts index 276e391..c45a8c8 100644 --- a/src/api/ops/pc.ts +++ b/src/api/ops/pc.ts @@ -42,7 +42,6 @@ export interface PCListResult { export interface PCCreateParams { resource_uid: string asset_id: number - pc_identity?: string name: string host: string ip_address?: string @@ -53,6 +52,7 @@ export interface PCCreateParams { server_type?: string tags?: string location?: string + policy_ids?: number[] } /** PC 更新参数 */ @@ -68,6 +68,7 @@ export interface PCUpdateParams { server_type?: string tags?: string location?: string + policy_ids?: number[] } /** 获取PC列表(分页) */ diff --git a/src/api/ops/platformRuntime.ts b/src/api/ops/platformRuntime.ts index fd14df9..efc5d0d 100644 --- a/src/api/ops/platformRuntime.ts +++ b/src/api/ops/platformRuntime.ts @@ -74,7 +74,6 @@ const services: ServiceSpec[] = [ { key: 'assets', name: '资产管理', path: '/Assets/v1/runtime/status' }, { key: 'dc-control', name: '监控控制中心', path: '/DC-Control/v1/runtime/status' }, { key: 'dc-network', name: '网络采集', path: '/dc-network/v1/runtime/status' }, - { key: 'dc-hardware', name: '硬件采集', path: '/DC-Hardware/v1/runtime/status' }, { key: 'alert', name: '告警中心', path: '/Alert/v1/runtime/status' }, { key: 'logs', name: '日志中心', path: '/Logs/v1/runtime/status' }, { key: 'feedback', name: '工单反馈', path: '/Feedback/v1/runtime/status' }, diff --git a/src/api/ops/rbac2.ts b/src/api/ops/rbac2.ts index 3c9297d..776f359 100644 --- a/src/api/ops/rbac2.ts +++ b/src/api/ops/rbac2.ts @@ -84,3 +84,20 @@ export const deleteRole = (id: number) => request.post('/rbac2/v1/role/del', { i export const fetchRolePermissions = (id: number) => request.post('/rbac2/v1/role/pmn', { id }, { needWorkspace: true }) export const updateRolePermissions = (id: number, list: number[]) => request.post('/rbac2/v1/role/modify_pmn', { id, list }, { needWorkspace: true }) + +export interface AlertDefaultReceiver { + id: number + name: string + account: string + phone: string + email: string +} + +export const fetchAlertDefaultReceiver = () => + request.get('/rbac2/v1/alert-receiver', { needWorkspace: true }) + +export const fetchAlertDefaultReceiverOptions = (keyword?: string) => + request.get('/rbac2/v1/alert-receiver/options', { params: { keyword }, needWorkspace: true }) + +export const updateAlertDefaultReceiver = (userId: number) => + request.post('/rbac2/v1/alert-receiver', { user_id: userId }, { needWorkspace: true }) diff --git a/src/api/ops/report.ts b/src/api/ops/report.ts index 385cd2d..6cd6b97 100644 --- a/src/api/ops/report.ts +++ b/src/api/ops/report.ts @@ -166,8 +166,8 @@ export interface DownloadedReport { export interface TrafficReportParams { /** topology:拓扑/NetFlow;snmp_devices:多设备 SNMP 接口流量汇总 */ traffic_mode?: 'topology' | 'snmp_devices' - /** traffic_mode=snmp_devices 时必填,service_identity 列表 */ - service_identities?: string[] + /** traffic_mode=snmp_devices 时必填,统一资源标识列表 */ + resource_uids?: string[] topology_id?: number link_id?: number node_id?: string @@ -203,8 +203,7 @@ export interface FaultReportParams { } export interface ServerReportParams { - server_ids?: number[] - server_identities?: string[] + resource_uids: string[] start_time: string end_time: string columns?: ('availability' | 'last_check_time' | 'cpu' | 'memory_phys' | 'memory_virt' | 'disk_io' | 'disk_usage' | 'daily_alerts')[] @@ -214,8 +213,7 @@ export interface ServerReportParams { /** POST /reports/generate report_type=network_device */ export interface NetworkDeviceReportParams { - network_device_service_ids?: number[] - service_identities?: string[] + resource_uids: string[] start_time: string end_time: string columns?: string[] @@ -225,7 +223,7 @@ export interface NetworkDeviceReportParams { export interface StatisticsReportParams { data_source: 'dc-host' | 'dc-network' | 'dc-database' | 'dc-middleware' metric_id: string - target_identities: string[] + resource_uids: string[] start_time: string end_time: string output_mode: 'scalar' | 'timeseries' @@ -248,7 +246,7 @@ export interface HistoryReportParams { export interface TopNReportParams { data_source: 'dc-host' | 'dc-network' | 'dc-database' | 'dc-middleware' | 'alert' metric_id: string - target_identities: string[] + resource_uids: string[] start_time: string end_time: string n?: number diff --git a/src/api/ops/room-device.ts b/src/api/ops/room-device.ts index 3a0438a..dda573d 100644 --- a/src/api/ops/room-device.ts +++ b/src/api/ops/room-device.ts @@ -51,7 +51,6 @@ export interface RoomDeviceListParams { export interface RoomDeviceCreateData { resource_uid: string asset_id: number - service_identity?: string name: string description?: string room_id: number @@ -80,6 +79,7 @@ export interface MetricItem { /** GET /room-devices/metrics/latest 的 details 载荷 */ export interface RoomDeviceMetricsLatestDetails { + resource_uid: string service_identity: string latest_timestamp?: string | null count: number @@ -119,9 +119,9 @@ export const updateRoomDevice = (id: number, data: RoomDeviceUpdateData) => { } /** 查询最新指标 */ -export const fetchLatestMetrics = (serviceIdentity: string) => { +export const fetchLatestMetrics = (resourceUID: string) => { return request.get('/DC-Control/v1/room-devices/metrics/latest', { - params: { service_identity: serviceIdentity }, + params: { resource_uid: resourceUID }, }) } diff --git a/src/api/ops/security.ts b/src/api/ops/security.ts index 41e3da4..bb5a011 100644 --- a/src/api/ops/security.ts +++ b/src/api/ops/security.ts @@ -54,7 +54,6 @@ export interface SecurityServiceListParams { export interface SecurityServiceFormData { resource_uid?: string asset_id?: number - service_identity?: string name?: string category?: string type?: string @@ -82,6 +81,7 @@ export interface SecurityMetric { /** 安全设备最新指标数据 */ export interface SecurityMetricsLatestData { + resource_uid: string service_identity: string latest_timestamp: string | null count: number @@ -109,9 +109,9 @@ export const updateSecurityService = (id: number, data: Partial { +export const fetchSecurityMetricsLatest = (resourceUID: string) => { return request.get<{ code: number; message: string; details: SecurityMetricsLatestData }>('/DC-Control/v1/security/metrics/latest', { - params: { service_identity: serviceIdentity }, + params: { resource_uid: resourceUID }, }) } @@ -122,6 +122,7 @@ export interface SecurityMetricsTimeseriesQuery { } export interface SecurityMetricsTimeseriesData { + resource_uid: string service_identity: string start_time: string end_time: string @@ -130,8 +131,8 @@ export interface SecurityMetricsTimeseriesData { } /** 查询安全设备指标原始时序点 */ -export const fetchSecurityMetricsTimeseries = (serviceIdentity: string, params?: SecurityMetricsTimeseriesQuery) => { - const path = encodeURIComponent(serviceIdentity) +export const fetchSecurityMetricsTimeseries = (resourceUID: string, params?: SecurityMetricsTimeseriesQuery) => { + const path = encodeURIComponent(resourceUID) return request.get<{ code: number; message: string; details: SecurityMetricsTimeseriesData }>( `/DC-Control/v1/security/metrics/timeseries/${path}`, { params } @@ -146,7 +147,7 @@ export const fetchSecurityAccessPoints = (id: number) => /** 告警聚合查询参数 */ export interface SecurityMetricsAggregateParams { - service_identity: string + resource_uid: string metric_name: string start_time: string end_time: string diff --git a/src/api/ops/server.ts b/src/api/ops/server.ts index 7b3d486..72242f7 100644 --- a/src/api/ops/server.ts +++ b/src/api/ops/server.ts @@ -19,10 +19,8 @@ export interface ServerItem { tags: string location: string asset_id?: number - monitored_processes: string[] - monitored_ports: number[] status: string - last_check_time: string + last_check_time: string | null } /** 服务器列表响应 */ @@ -43,7 +41,6 @@ export interface ServerListParams { /** 创建/更新服务器请求参数 */ export interface ServerFormData { resource_uid?: string - server_identity?: string name?: string host?: string ip_address?: string @@ -55,8 +52,91 @@ export interface ServerFormData { tags?: string location?: string asset_id?: number - monitored_processes?: string[] - monitored_ports?: number[] + policy_ids?: number[] +} + +/** 服务器上的业务服务监控项 */ +export interface ServerServiceMonitor { + id: number + server_id: number + resource_uid: string + stable_code: string + service_name: string + process_name: string + port: number + enabled: boolean + config_version: number + binding_version: number + last_state: 'available' | 'unavailable' | 'unknown' | 'removed' + last_message?: string + last_process_running?: boolean | null + last_port_listening?: boolean | null + last_available?: boolean | null + last_checked_at?: string | null + created_at?: string + updated_at?: string +} + +export interface ServerServiceMonitorInput { + service_name: string + process_name: string + port: number + enabled: boolean +} + +export interface ServerServiceMonitorOption { + id: number + stable_code: string + service_name: string + process_name: string + port: number + server_id: number + resource_uid: string + server_name: string +} + +export interface ServiceMonitorReference { + business_system_id: number + business_system_code: string + business_system_name: string +} + +export interface ServiceMonitorDeleteConflict { + conflict_code: 'SERVICE_MONITOR_REFERENCED' | 'SERVICE_MONITOR_REFERENCE_CHANGED' + binding_version: number + service_monitor_id: number + stable_code: string + business_systems: ServiceMonitorReference[] +} + +/** Linux Agent 只读采集的固定文件日志 */ +export interface ServerFileLogMonitor { + id: number + server_id: number + resource_uid: string + stable_code: string + name: string + file_path: string + encoding: 'utf-8' | 'gb18030' + multiline_mode: 'single_line' | 'start_pattern' + multiline_start_pattern: string + enabled: boolean + config_version: number + cursor_file_identity: string + cursor_offset: number + last_read_at?: string | null + last_error?: string + created_at?: string + updated_at?: string +} + +export interface ServerFileLogMonitorInput { + name: string + file_path: string + encoding: 'utf-8' | 'gb18030' + multiline_mode: 'single_line' | 'start_pattern' + multiline_start_pattern: string + enabled: boolean } /** 获取服务器列表(分页) */ @@ -79,6 +159,63 @@ export const updateServer = (id: number, data: Partial) => { return request.put<{ message: string }>(`/DC-Control/v1/servers/${id}`, data) } +export const fetchServerServiceMonitors = (serverId: number) => + request.get<{ code?: number; details?: { list: ServerServiceMonitor[]; count: number }; message?: string }>( + `/DC-Control/v1/servers/${serverId}/service-monitors` + ) + +export const createServerServiceMonitor = (serverId: number, data: ServerServiceMonitorInput) => + request.post<{ code?: number; details?: ServerServiceMonitor; message?: string }>( + `/DC-Control/v1/servers/${serverId}/service-monitors`, + data + ) + +export const updateServerServiceMonitor = (serverId: number, monitorId: number, data: ServerServiceMonitorInput) => + request.put<{ code?: number; details?: ServerServiceMonitor; message?: string }>( + `/DC-Control/v1/servers/${serverId}/service-monitors/${monitorId}`, + data + ) + +export const deleteServerServiceMonitor = ( + serverId: number, + monitorId: number, + options?: { unlinkReferences: boolean; bindingVersion: number } +) => + request.delete<{ code?: number; details?: { message: string }; message?: string }>( + `/DC-Control/v1/servers/${serverId}/service-monitors/${monitorId}`, + options?.unlinkReferences + ? { params: { unlink_references: true, binding_version: options.bindingVersion } } + : undefined + ) + +export const fetchServerServiceMonitorOptions = (businessSystemId: number, keyword?: string) => + request.get<{ code?: number; details?: { list: ServerServiceMonitorOption[]; count: number }; message?: string }>( + '/DC-Control/v1/service-monitors/options', + { params: { business_system_id: businessSystemId, keyword: keyword || undefined } } + ) + +export const fetchServerFileLogMonitors = (serverId: number) => + request.get<{ code?: number; details?: { list: ServerFileLogMonitor[]; count: number }; message?: string }>( + `/DC-Control/v1/servers/${serverId}/file-log-monitors` + ) + +export const createServerFileLogMonitor = (serverId: number, data: ServerFileLogMonitorInput) => + request.post<{ code?: number; details?: ServerFileLogMonitor; message?: string }>( + `/DC-Control/v1/servers/${serverId}/file-log-monitors`, + data + ) + +export const updateServerFileLogMonitor = (serverId: number, monitorId: number, data: ServerFileLogMonitorInput) => + request.put<{ code?: number; details?: ServerFileLogMonitor; message?: string }>( + `/DC-Control/v1/servers/${serverId}/file-log-monitors/${monitorId}`, + data + ) + +export const deleteServerFileLogMonitor = (serverId: number, monitorId: number) => + request.delete<{ code?: number; details?: { message: string }; message?: string }>( + `/DC-Control/v1/servers/${serverId}/file-log-monitors/${monitorId}` + ) + /** 主机最新指标摘要(统计卡片,与 dc-control /servers/metrics/summary 一致) */ export interface HostMetricsUseStat { total_bytes: number @@ -135,15 +272,44 @@ export interface HostLatestMetricsPayload { } export interface HardwareSnapshotPayload { + resource_uid: string + configured: boolean has_data: boolean protocol?: string collected_at?: string + overall_status: 'ok' | 'warning' | 'critical' | 'unknown' + bindings: HardwareCollectionBinding[] metrics: Array<{ code: string; value: number; unit: string; labels: Record }> inventory: Array<{ type: string; identity: string; name: string; status: string; attributes: Record }> events: Array<{ code: string; severity: string; summary: string; attributes: Record }> diagnostics: Array<{ code: string; level: string; message: string }> } +export interface HardwareCollectionBinding { + id: number + name: string + protocol: 'redfish' | 'ipmi' + target: string + port: number + interval_seconds: number + enabled: boolean + state: string + collector_node_name: string + credential_name: string + template_name: string + template_version: number +} + +export interface HardwareMetricHistoryPayload { + server_identity: string + metric_code: string + metric_name: string + sensor: string + start_time: string + end_time: string + points: Array<{ timestamp: string; value: number; unit: string }> +} + export interface HostNetworkTrafficPoint { time: string recv_mbps: number @@ -162,27 +328,39 @@ export interface HostNetworkTrafficPayload { } /** 最新一批主机指标(用于监控大屏卡片) */ -export const fetchServerMetricsSummary = (serverIdentity: string) => { +export const fetchServerMetricsSummary = (resourceUID: string) => { return request.get<{ code: number; details?: HostMetricsSummary; message?: string }>('/DC-Control/v1/servers/metrics/summary', { - params: { server_identity: serverIdentity }, + params: { resource_uid: resourceUID }, }) } -export const fetchServerLatestMetrics = (serverIdentity: string) => +export const fetchServerLatestMetrics = (resourceUID: string) => request.get<{ code: number; details?: HostLatestMetricsPayload; message?: string }>('/DC-Control/v1/servers/metrics/latest', { - params: { server_identity: serverIdentity }, + params: { resource_uid: resourceUID }, }) -export const fetchServerHardwareSnapshot = (serverIdentity: string) => +export const fetchServerHardwareSnapshot = (resourceUID: string) => request.get<{ code: number; details?: HardwareSnapshotPayload; message?: string }>('/DC-Control/v1/servers/metrics/hardware', { - params: { server_identity: serverIdentity }, + params: { resource_uid: resourceUID }, }) +export const fetchServerHardwareMetricHistory = ( + resourceUID: string, + metricCode: string, + sensor: string, + startTime: string, + endTime: string +) => + request.get<{ code: number; details?: HardwareMetricHistoryPayload; message?: string }>( + '/DC-Control/v1/servers/metrics/hardware/history', + { params: { resource_uid: resourceUID, metric_code: metricCode, sensor, start_time: startTime, end_time: endTime } } + ) + /** 近 N 小时网络收/发速率(Mbps,相邻采样字节差分) */ -export const fetchServerNetworkTraffic = (serverIdentity: string, hours = 6) => { +export const fetchServerNetworkTraffic = (resourceUID: string, hours = 6) => { return request.get<{ code: number; details?: HostNetworkTrafficPayload; message?: string }>( '/DC-Control/v1/servers/metrics/network-traffic', - { params: { server_identity: serverIdentity, hours } } + { params: { resource_uid: resourceUID, hours } } ) } @@ -225,10 +403,11 @@ export interface LatestResourceSummaryPayload { /** 按资产聚合项 */ export interface AssetMixedSummaryItem { asset_id: number + name: string virtual_count: number physical_count: number - physical_latest_cpu_usage?: number | null - physical_latest_mem_usage?: number | null + virtual_latest_cpu_usage: number | null + virtual_latest_mem_usage: number | null } export interface AssetMixedSummaryPayload { @@ -258,7 +437,7 @@ export const fetchLatestResourceSummary = () => { ) } -/** 接口4:按资产汇总 virtual 数量与 physical 最新指标 */ +/** 接口4:按资产汇总虚拟机名称、数量与最新指标 */ export const fetchAssetMixedSummary = () => { return request.get<{ code: number; details?: AssetMixedSummaryPayload; message?: string }>('/DC-Control/v1/servers/assets/mixed/summary') } diff --git a/src/api/ops/storage.ts b/src/api/ops/storage.ts index 7876f97..891562e 100644 --- a/src/api/ops/storage.ts +++ b/src/api/ops/storage.ts @@ -53,7 +53,6 @@ export interface StorageListParams { export interface StorageCreateData { resource_uid?: string asset_id?: number - service_identity?: string name: string category?: string type: string @@ -69,9 +68,6 @@ export interface StorageCreateData { /** 更新存储设备请求参数 */ export interface StorageUpdateData { - resource_uid?: string - asset_id?: number - service_identity?: string name?: string category?: string type?: string @@ -103,10 +99,13 @@ export interface StorageMetricItem { metric_name: string metric_value: number metric_unit: string + series_key?: string + labels?: string } /** 最新指标响应 */ export interface StorageMetricsLatestResponse { + resource_uid: string service_identity: string latest_timestamp: string | null count: number @@ -137,14 +136,15 @@ export const fetchStorageComponents = (id: number) => request.get(`/DC-Control/v1/storage/${id}/components`) /** 查询最新一批时序指标 */ -export const fetchStorageMetricsLatest = (serviceIdentity: string) => { +export const fetchStorageMetricsLatest = (resourceUID: string) => { return request.get('/DC-Control/v1/storage/metrics/latest', { - params: { service_identity: serviceIdentity }, + params: { resource_uid: resourceUID }, }) } /** 监控大屏:下拉列表(JWT,模糊 keyword) */ export interface StorageMonitorOptionItem { + resource_uid: string service_identity: string name: string type: string @@ -181,13 +181,13 @@ export const fetchStorageMonitorList = (params?: { keyword?: string }) => { }>('/DC-Control/v1/storage/monitor/list', { params }) } -export const fetchStorageMonitorSummary = (serviceIdentity?: string) => { +export const fetchStorageMonitorSummary = (resourceUID?: string) => { return request.get<{ code: number details?: StorageMonitorSummaryPayload message?: string }>('/DC-Control/v1/storage/monitor/summary', { - params: serviceIdentity ? { service_identity: serviceIdentity } : {}, + params: resourceUID ? { resource_uid: resourceUID } : {}, }) } @@ -214,7 +214,7 @@ export const fetchStorageMonitorCollectedMetrics = (deviceId: number) => { /** 存储指标匿名聚合(告警/大屏用) */ export interface StorageMetricsAggregateParams { - service_identity: string + resource_uid: string metric_name: string start_time: string end_time: string @@ -231,7 +231,7 @@ export interface StorageMetricsAggregateDetails { end_time: string } -/** 与 DC-Hardware GET /metrics/devices/:device_id 对齐:路径 service_identity,Query 可选 */ +/** 按统一设备标识和可选时间范围查询存储指标 */ export interface StorageMetricsTimeseriesQuery { start_time?: string end_time?: string @@ -256,12 +256,13 @@ export const fetchStorageMetricsAggregate = (params: StorageMetricsAggregatePara ) } -/** 存储指标时序原始点(JWT);路径为 service_identity,需 URL 编码 */ -export const fetchStorageMetricsTimeseries = (serviceIdentity: string, params?: StorageMetricsTimeseriesQuery) => { - const path = encodeURIComponent(serviceIdentity) +/** 存储指标时序原始点(JWT);路径为 resource_uid,需 URL 编码 */ +export const fetchStorageMetricsTimeseries = (resourceUID: string, params?: StorageMetricsTimeseriesQuery) => { + const path = encodeURIComponent(resourceUID) return request.get<{ code: number details?: { + resource_uid: string service_identity: string start_time: string end_time: string diff --git a/src/api/ops/suppression.ts b/src/api/ops/suppression.ts index afc348e..8dfb13d 100644 --- a/src/api/ops/suppression.ts +++ b/src/api/ops/suppression.ts @@ -11,7 +11,6 @@ export interface SuppressionRule { type: SuppressionType enabled?: boolean priority?: number - policy_id?: number // dedup 类型字段 dedup_window?: number dedup_keys?: string @@ -45,7 +44,6 @@ export interface SuppressionListParams { sort?: string order?: string type?: SuppressionType - policy_id?: number enabled?: boolean } @@ -56,7 +54,6 @@ export interface SuppressionCreateParams { description?: string enabled?: boolean priority?: number - policy_id?: number dedup_window?: number dedup_keys?: string aggregate_window?: number @@ -78,7 +75,6 @@ export interface SuppressionUpdateParams { description?: string enabled?: boolean priority?: number - policy_id?: number dedup_window?: number dedup_keys?: string aggregate_window?: number diff --git a/src/api/ops/systemSettings.ts b/src/api/ops/systemSettings.ts index e7fbafb..27586ec 100644 --- a/src/api/ops/systemSettings.ts +++ b/src/api/ops/systemSettings.ts @@ -34,8 +34,31 @@ export interface CommonPhrase { export type PublicSystemParameters = Record -export const fetchDictionaries = (params?: { category_code?: string; enabled?: boolean }) => - request.get(`${BASE}/dictionaries`, { params }) +export interface BrandingImage { + file_id: string + url: string + content_type: string +} + +export interface SystemBranding { + system_name: string + logo?: BrandingImage + login_backgrounds: BrandingImage[] + background_file_ids: string[] + cleanup_warnings?: string[] +} + +interface BrandingUploadInit { + file_id: string + upload: { + method: string + url: string + headers: Record + expires_at: string + } +} + +export const fetchDictionaries = (params?: { category_code?: string; enabled?: boolean }) => request.get(`${BASE}/dictionaries`, { params }) export const createDictionary = (data: DataDictionaryItem) => request.post(`${BASE}/dictionaries`, data) export const updateDictionary = (id: number, data: DataDictionaryItem) => request.put(`${BASE}/dictionaries/${id}`, data) export const deleteDictionary = (id: number) => request.delete(`${BASE}/dictionaries/${id}`) @@ -44,6 +67,35 @@ export const fetchSystemParameters = () => request.get(`${BASE}/parameters`) export const fetchPublicSystemParameters = () => request.get('/Mgt/v1/settings/parameters/public') export const updateSystemParameter = (code: string, value: number) => request.put(`${BASE}/parameters/${code}`, { value }) +export const fetchSystemBranding = () => request.get(`${BASE}/branding`) +export const fetchPublicSystemBranding = () => request.get(`${BASE}/branding/public`) +export const updateSystemBranding = (data: { system_name: string; logo_file_id?: string; background_file_ids: string[] }) => + request.put(`${BASE}/branding`, data) + +export async function uploadSystemBrandingImage(file: File): Promise { + const initResponse: any = await request.post(`${BASE}/branding/uploads/init`, { + filename: file.name, + size: file.size, + content_type: file.type, + }) + const pending = initResponse.details as BrandingUploadInit + if (initResponse.code !== 0 || !pending?.file_id || !pending.upload?.url || !pending.upload?.method) { + throw new Error(initResponse.message || '初始化品牌图片上传失败') + } + const uploadResponse = await fetch(pending.upload.url, { + method: pending.upload.method, + headers: pending.upload.headers, + body: file, + credentials: 'omit', + }) + if (!uploadResponse.ok) throw new Error(`上传品牌图片失败: HTTP ${uploadResponse.status}`) + const completeResponse: any = await request.post(`${BASE}/branding/uploads/complete`, { file_id: pending.file_id }) + if (completeResponse.code !== 0 || !completeResponse.details?.file_id) { + throw new Error(completeResponse.message || '确认品牌图片上传失败') + } + return completeResponse.details as BrandingImage +} + export const fetchCommonPhrases = (params?: { page?: number; page_size?: number; keyword?: string; category_code?: string }) => request.get(`${BASE}/phrases`, { params }) export const createCommonPhrase = (data: CommonPhrase) => request.post(`${BASE}/phrases`, data) diff --git a/src/api/ops/traffic-runtime.ts b/src/api/ops/traffic-runtime.ts index fb93683..b6bdc12 100644 --- a/src/api/ops/traffic-runtime.ts +++ b/src/api/ops/traffic-runtime.ts @@ -1,15 +1,15 @@ import { request } from '@/api/request' export interface TrafficRuntimeStatus { - status?: 'running' | 'stopped' | string - message?: string + status: 'running' | 'stopped' | 'started' + message: string } export const fetchTrafficRuntimeStatus = () => - request.get<{ code: number; details?: TrafficRuntimeStatus; message?: string }>('/dc-network/v1/api/traffic/status') + request.get<{ code: number; details: TrafficRuntimeStatus; message?: string }>('/DC-Control/v1/traffic/runtime') export const startTrafficRuntime = () => - request.post<{ code: number; details?: TrafficRuntimeStatus; message?: string }>('/dc-network/v1/api/traffic/start') + request.post<{ code: number; details: TrafficRuntimeStatus; message?: string }>('/DC-Control/v1/traffic/runtime/start') export const stopTrafficRuntime = () => - request.post<{ code: number; details?: TrafficRuntimeStatus; message?: string }>('/dc-network/v1/api/traffic/stop') + request.post<{ code: number; details: TrafficRuntimeStatus; message?: string }>('/DC-Control/v1/traffic/runtime/stop') diff --git a/src/components/footer/index.vue b/src/components/footer/index.vue index c7c6665..46498d1 100644 --- a/src/components/footer/index.vue +++ b/src/components/footer/index.vue @@ -1,6 +1,6 @@ 采集绑定 + + 服务监控 + + + 文件日志采集 + 监控详情 @@ -143,13 +149,19 @@ + + + + 暂无主机或带外硬件采集数据。 @@ -576,127 +446,59 @@ export default { padding: 16px; } -.page-toolbar { +.page-toolbar, +.toolbar-left, +.server-option, +.trend-toolbar { display: flex; - flex-wrap: wrap; align-items: center; +} + +.page-toolbar { justify-content: space-between; + flex-wrap: wrap; gap: 12px; margin-bottom: 16px; } -.toolbar-left { - display: flex; - align-items: center; +.toolbar-left, +.trend-toolbar { gap: 12px; } .toolbar-label { - font-size: 14px; color: var(--color-text-2); white-space: nowrap; } .server-select { - min-width: 260px; - max-width: 360px; + width: 360px; } .server-option { - display: flex; - justify-content: space-between; - gap: 12px; width: 100%; + justify-content: space-between; + gap: 16px; } -.server-option-name { - font-size: 14px; - color: var(--color-text-1); +.hardware-card { + margin-bottom: 16px; } -.server-option-ip { - font-size: 13px; - color: var(--color-text-3); - font-family: var(--font-family-mono); -} - -.toolbar-meta { - font-size: 13px; -} - -.hw-empty { - min-height: 220px; +.hardware-empty { + min-height: 240px; display: flex; align-items: center; justify-content: center; } -.hw-card { - margin-bottom: 16px; -} - -.hw-sub-item { - padding: 8px 10px; - border-radius: 8px; - background: var(--color-fill-1); - border: 1px solid var(--color-border-1); -} - -.hw-sub-label { - font-size: 12px; - color: var(--color-text-3); - margin-bottom: 6px; -} - -.hw-sub-dash { - color: var(--color-text-4); - font-size: 13px; -} - -.hw-stat-cell { - padding: 12px; - border-radius: 8px; - background: var(--color-fill-1); - border: 1px solid var(--color-border-1); -} - -.hw-stat-label { - font-size: 12px; - color: var(--color-text-3); - margin-bottom: 4px; -} - -.hw-stat-value { - font-size: 18px; - font-weight: 600; - color: var(--color-text-1); -} - -.hw-chart-toolbar { - display: flex; - align-items: center; - gap: 16px; - margin-bottom: 12px; +.trend-toolbar { flex-wrap: wrap; -} - -.hw-chart-wrap { - width: 100%; - min-height: 280px; -} - -.hw-raw-pre { - margin: 0; - max-height: 360px; - overflow: auto; - font-size: 12px; - line-height: 1.45; - white-space: pre-wrap; - word-break: break-all; + margin-bottom: 12px; } .text-muted { - font-size: 12px; color: var(--color-text-3); + font-size: 12px; } diff --git a/src/views/ops/pages/monitor/os/index.vue b/src/views/ops/pages/monitor/os/index.vue index 9561130..25fabdc 100644 --- a/src/views/ops/pages/monitor/os/index.vue +++ b/src/views/ops/pages/monitor/os/index.vue @@ -4,7 +4,7 @@
当前服务器
@@ -155,6 +155,7 @@ import { import { fetchAlertRecords } from '@/api/ops/alertRecord' interface ServerOption { + resource_uid: string server_identity: string name: string host: string @@ -191,12 +192,12 @@ const alertsLoading = ref(false) const recentAlerts = ref([]) const serverOptions = ref([]) -const selectedServerIdentity = ref(undefined) +const selectedResourceUID = ref(undefined) const summaryPayload = ref(null) const trafficPayload = ref(null) -const activeServer = computed(() => serverOptions.value.find((s) => s.server_identity === selectedServerIdentity.value)) +const activeServer = computed(() => serverOptions.value.find((s) => s.resource_uid === selectedResourceUID.value)) const emptyPieOptions = (): Record => ({ graphic: [ @@ -547,17 +548,17 @@ async function loadRecentAlertsForServer(server: ServerOption | undefined) { recentAlerts.value = [] return } - const targetSid = server.server_identity + const targetResourceUID = server.resource_uid try { const res: any = await fetchAlertRecords({ page: 1, page_size: 5, sort: 'starts_at', order: 'desc', - server_identity: targetSid, + resource_uid: targetResourceUID, }) /** 切换主机后丢弃过期响应,避免空结果仍显示上一台主机的列表 */ - if (selectedServerIdentity.value !== targetSid) { + if (selectedResourceUID.value !== targetResourceUID) { return } const code = res?.code @@ -570,7 +571,7 @@ async function loadRecentAlertsForServer(server: ServerOption | undefined) { const rows = normalizeAlertRecordList(res).slice(0, 5) recentAlerts.value = rows } catch (e: any) { - if (selectedServerIdentity.value === targetSid) { + if (selectedResourceUID.value === targetResourceUID) { recentAlerts.value = [] Message.error(e?.message || '加载最近告警失败') } @@ -679,6 +680,7 @@ async function loadServerOptions() { } const list: ServerItem[] = res.details?.data ?? [] serverOptions.value = list.map((r) => ({ + resource_uid: r.resource_uid, server_identity: r.server_identity, name: r.name, host: r.host, @@ -686,11 +688,11 @@ async function loadServerOptions() { os: [r.os, r.os_version].filter(Boolean).join(' ') || '-', location: r.location || '-', })) - if (selectedServerIdentity.value && !serverOptions.value.some((s) => s.server_identity === selectedServerIdentity.value)) { - selectedServerIdentity.value = undefined + if (selectedResourceUID.value && !serverOptions.value.some((s) => s.resource_uid === selectedResourceUID.value)) { + selectedResourceUID.value = undefined } - if (!selectedServerIdentity.value && serverOptions.value.length > 0) { - selectedServerIdentity.value = serverOptions.value[0].server_identity + if (!selectedResourceUID.value && serverOptions.value.length > 0) { + selectedResourceUID.value = serverOptions.value[0].resource_uid } } catch (e: any) { Message.error(e?.message || '加载服务器列表失败') @@ -701,22 +703,22 @@ async function loadServerOptions() { } async function refreshDashboard() { - const sid = selectedServerIdentity.value - if (!sid) { + const resourceUID = selectedResourceUID.value + if (!resourceUID) { summaryPayload.value = null trafficPayload.value = null recentAlerts.value = [] return } - const server = serverOptions.value.find((s) => s.server_identity === sid) + const server = serverOptions.value.find((s) => s.resource_uid === resourceUID) statsLoading.value = true chartLoading.value = true alertsLoading.value = true recentAlerts.value = [] try { const [sumRes, trafRes]: any[] = await Promise.all([ - fetchServerMetricsSummary(sid), - fetchServerNetworkTraffic(sid, 6), + fetchServerMetricsSummary(resourceUID), + fetchServerNetworkTraffic(resourceUID, 6), loadRecentAlertsForServer(server), ]) if (sumRes.code === 0) { @@ -743,8 +745,8 @@ async function refreshDashboard() { } } -watch(selectedServerIdentity, (sid) => { - if (!sid) { +watch(selectedResourceUID, (resourceUID) => { + if (!resourceUID) { summaryPayload.value = null trafficPayload.value = null recentAlerts.value = [] @@ -755,7 +757,7 @@ watch(selectedServerIdentity, (sid) => { onMounted(async () => { await loadServerOptions() - // 首次选中由 loadServerOptions 设置,watch(selectedServerIdentity) 会拉取指标 + // 首次选中由 loadServerOptions 设置,watch(selectedResourceUID) 会拉取指标 }) diff --git a/src/views/ops/pages/monitor/security/index.vue b/src/views/ops/pages/monitor/security/index.vue index df6cf3e..fafeb64 100644 --- a/src/views/ops/pages/monitor/security/index.vue +++ b/src/views/ops/pages/monitor/security/index.vue @@ -283,7 +283,7 @@ const fetchData = async () => { } const metricsRespList = await Promise.all( baseList.map((item) => - fetchSecurityMetricsLatest(item.service_identity).catch(() => ({ + fetchSecurityMetricsLatest(item.resource_uid).catch(() => ({ code: -1, details: { metrics: [] as SecurityMetric[] }, })) diff --git a/src/views/ops/pages/monitor/storage/index.vue b/src/views/ops/pages/monitor/storage/index.vue index 757881f..58b971c 100644 --- a/src/views/ops/pages/monitor/storage/index.vue +++ b/src/views/ops/pages/monitor/storage/index.vue @@ -4,7 +4,7 @@
当前设备
@@ -222,7 +222,7 @@ const chartLoading = ref(false) const storageList = ref([]) const storageOptions = ref([]) -const selectedServiceIdentity = ref(undefined) +const selectedResourceUID = ref(undefined) const monitorSummary = ref(null) @@ -230,7 +230,7 @@ const chartSeriesPoints = ref<{ time: string; value: number }[]>([]) 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.resource_uid === selectedResourceUID.value)) const globalHint = computed(() => { const m = monitorSummary.value @@ -275,7 +275,7 @@ const controllerStatusColor = computed(() => { }) const chartHint = computed(() => { - if (!selectedServiceIdentity.value) return '' + if (!selectedResourceUID.value) return '' if (!trendMetricOptions.value.length) { return '该设备暂无已上报的指标批次,无法选择指标;请确认采集已写入。' } @@ -332,6 +332,7 @@ const ioChartOptions = computed(() => { function syncOptionsFromList() { storageOptions.value = storageList.value.map((r) => ({ + resource_uid: r.resource_uid, service_identity: r.service_identity, name: r.name, type: r.type, @@ -343,10 +344,10 @@ function syncOptionsFromList() { } function mergeMonitorOptionAsRow(opt: StorageMonitorOptionItem) { - if (storageList.value.some((s) => s.service_identity === opt.service_identity)) return + if (storageList.value.some((s) => s.resource_uid === opt.resource_uid)) return storageList.value.push({ id: 0, - resource_uid: '', + resource_uid: opt.resource_uid, asset_id: 0, created_at: '', updated_at: '', @@ -377,10 +378,10 @@ function mergeMonitorOptionAsRow(opt: StorageMonitorOptionItem) { }) } -function onDeviceSelectChange(sid: string | undefined) { - if (!sid) return - if (!storageList.value.some((s) => s.service_identity === sid)) { - const opt = storageOptions.value.find((o) => o.service_identity === sid) +function onDeviceSelectChange(resourceUID: string | undefined) { + if (!resourceUID) return + if (!storageList.value.some((s) => s.resource_uid === resourceUID)) { + const opt = storageOptions.value.find((o) => o.resource_uid === resourceUID) if (opt) mergeMonitorOptionAsRow(opt) } } @@ -419,11 +420,11 @@ async function loadStorageList() { } storageList.value = list syncOptionsFromList() - if (selectedServiceIdentity.value && !list.some((s) => s.service_identity === selectedServiceIdentity.value)) { - selectedServiceIdentity.value = undefined + if (selectedResourceUID.value && !list.some((s) => s.resource_uid === selectedResourceUID.value)) { + selectedResourceUID.value = undefined } - if (!selectedServiceIdentity.value && list.length > 0) { - selectedServiceIdentity.value = list[0].service_identity + if (!selectedResourceUID.value && list.length > 0) { + selectedResourceUID.value = list[0].resource_uid } } catch (e: any) { Message.error(e?.message || '加载存储列表失败') @@ -434,8 +435,8 @@ async function loadStorageList() { async function refreshCollectedMetricNames() { trendMetricOptions.value = [] - const sid = selectedServiceIdentity.value?.trim() - if (!sid) { + const resourceUID = selectedResourceUID.value?.trim() + if (!resourceUID) { trendSelectedMetric.value = '' return } @@ -451,7 +452,7 @@ async function refreshCollectedMetricNames() { } } if (!list.length) { - const res: any = await fetchStorageMetricsLatest(sid) + const res: any = await fetchStorageMetricsLatest(resourceUID) if (res.code === 0 && res.details?.metrics?.length) { list = res.details.metrics } @@ -482,8 +483,8 @@ async function refreshCollectedMetricNames() { async function refreshMonitorSummary() { summaryLoading.value = true try { - const sid = selectedServiceIdentity.value?.trim() || undefined - const res: any = await fetchStorageMonitorSummary(sid) + const resourceUID = selectedResourceUID.value?.trim() || undefined + const res: any = await fetchStorageMonitorSummary(resourceUID) if (res.code === 0) { monitorSummary.value = res.details ?? null } else { @@ -514,18 +515,13 @@ function bucketStorageMetricsHourlyMax(metrics: Array<{ timestamp: string; metri async function refreshTimeseries() { chartSeriesPoints.value = [] - const svc = selectedServiceIdentity.value - if (!svc) return + const resourceUID = selectedResourceUID.value + if (!resourceUID) return const metric = trendSelectedMetric.value.trim() if (!metric) return - const end = dayjs() - const start = end.subtract(24, 'hour') - const startStr = start.toISOString() - const endStr = end.toISOString() chartLoading.value = true try { - const q = { start_time: startStr, end_time: endStr, metric_name: metric } - const r: any = await fetchStorageMetricsTimeseries(svc, q) + const r: any = await fetchStorageMetricsTimeseries(resourceUID, { metric_name: metric }) if (r.code === 0 && r.details?.metrics?.length) { chartSeriesPoints.value = bucketStorageMetricsHourlyMax(r.details.metrics) } @@ -541,7 +537,7 @@ async function refreshSelectionDetails() { await Promise.all([refreshMonitorSummary(), refreshTimeseries()]) } -watch(selectedServiceIdentity, () => { +watch(selectedResourceUID, () => { void refreshSelectionDetails() }) diff --git a/src/views/ops/pages/monitor/virtualization/index.vue b/src/views/ops/pages/monitor/virtualization/index.vue index ee62f06..71d8b22 100644 --- a/src/views/ops/pages/monitor/virtualization/index.vue +++ b/src/views/ops/pages/monitor/virtualization/index.vue @@ -130,7 +130,7 @@ - +
{{ host.name }} @@ -142,16 +142,16 @@
CPU - {{ host.cpu }}% + {{ formatUsage(host.cpu) }}
- +
内存 - {{ host.memory }}% + {{ formatUsage(host.memory) }}
- +
虚拟机数 @@ -197,9 +197,10 @@ const loading = ref(true) // 宿主机状态 const hostStatus = ref< Array<{ + assetId: number name: string - cpu: number - memory: number + cpu: number | null + memory: number | null vmCount: number physicalCount: number }> @@ -324,6 +325,8 @@ const memoryPhysicalText = computed(() => formatMaxTwoDecimals(memPhysicalGb.val const showVirtualEmptyTip = computed(() => !loading.value && stats.value.total === 0) const toGb = (bytes: number) => bytes / 1024 / 1024 / 1024 const safeNum = (v: number | null | undefined) => (typeof v === 'number' ? v : 0) +const usageValue = (value: number | null) => (typeof value === 'number' ? Number(value.toFixed(2)) : null) +const formatUsage = (value: number | null) => (value === null ? '--' : `${value}%`) const hourLabel = (v: string) => { const d = new Date(v) if (Number.isNaN(d.getTime())) return v @@ -378,9 +381,10 @@ const fetchData = async () => { if (mixedRes.code === 0 && mixedRes.details?.data) { hostStatus.value = (mixedRes.details.data as AssetMixedSummaryItem[]).map((item) => ({ - name: `Asset-${item.asset_id}`, - cpu: Number(safeNum(item.physical_latest_cpu_usage).toFixed(2)), - memory: Number(safeNum(item.physical_latest_mem_usage).toFixed(2)), + assetId: item.asset_id, + name: item.name, + cpu: usageValue(item.virtual_latest_cpu_usage), + memory: usageValue(item.virtual_latest_mem_usage), vmCount: item.virtual_count || 0, physicalCount: item.physical_count || 0, })) @@ -388,7 +392,7 @@ const fetchData = async () => { hostStatus.value = [] } } catch (e: any) { - Message.error(e?.message || '加载虚拟化监控数据失败') + Message.error(e?.message || '加载虚拟机监控数据失败') } finally { loading.value = false } diff --git a/src/views/ops/pages/netarch/topo-group/components/TopologyListDialog.vue b/src/views/ops/pages/netarch/topo-group/components/TopologyListDialog.vue index 91fdbdb..68a5b6e 100644 --- a/src/views/ops/pages/netarch/topo-group/components/TopologyListDialog.vue +++ b/src/views/ops/pages/netarch/topo-group/components/TopologyListDialog.vue @@ -64,15 +64,28 @@ + + + - + diff --git a/src/views/ops/pages/resource-context/index.vue b/src/views/ops/pages/resource-context/index.vue index 23a7f69..26174bc 100644 --- a/src/views/ops/pages/resource-context/index.vue +++ b/src/views/ops/pages/resource-context/index.vue @@ -74,7 +74,7 @@ {{ businessSystemName(record.business_system_id) }}