From 781d1af3373cc22e8221b23add70655c5aee0cb1 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Wed, 9 Sep 2026 16:43:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=9D=E9=AA=8C=E9=92=88=E5=AF=B9?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 1 + .env.production | 3 +- config/vite.config.dev.ts | 2 + src/api/message.ts | 2 + src/api/module/factor.ts | 35 - src/api/module/pmn.ts | 8 - src/api/module/user.ts | 21 +- src/api/ops/alertPolicy.ts | 33 + src/api/ops/alertRecord.ts | 15 +- src/api/ops/alertRouting.ts | 74 ++ src/api/ops/asset.ts | 66 +- src/api/ops/automation.ts | 87 -- src/api/ops/bigScreen.ts | 34 +- src/api/ops/businessSystem.ts | 119 +++ src/api/ops/collection.ts | 180 ++++ src/api/ops/database.ts | 39 +- src/api/ops/dcControl.ts | 75 +- src/api/ops/device-interface.ts | 74 ++ src/api/ops/floor.ts | 4 +- src/api/ops/floorFile.ts | 3 +- src/api/ops/governance.ts | 14 +- src/api/ops/ipScan.ts | 10 +- src/api/ops/logs.ts | 73 ++ src/api/ops/middleware.ts | 83 +- src/api/ops/netarchTopo.ts | 24 +- src/api/ops/network-device.ts | 63 +- src/api/ops/noticeChannel.ts | 6 +- src/api/ops/pc.ts | 47 +- src/api/ops/platformRuntime.ts | 193 +++++ src/api/ops/rbac2.ts | 56 +- src/api/ops/report.ts | 122 +++ src/api/ops/room-device.ts | 105 +-- src/api/ops/security.ts | 135 ++- src/api/ops/server.ts | 75 +- src/api/ops/storage.ts | 178 +--- src/api/ops/systemSettings.ts | 67 ++ src/api/ops/three-machine-room.ts | 8 +- src/api/ops/traffic.ts | 35 + src/api/ops/url-device.ts | 145 ---- src/api/types.ts | 6 + src/components/navbar/index.vue | 23 +- src/composables/systemParameters.ts | 43 + src/layout/default-layout.vue | 43 +- src/router/local-menu-flat.ts | 19 +- src/router/local-menu-items.ts | 44 +- src/router/routes/modules/ops.ts | 23 +- src/store/modules/user/index.ts | 2 +- src/utils/iframe.ts | 13 + src/views/login/index.vue | 30 +- .../notice/components/ChannelDetailDialog.vue | 10 +- .../notice/components/ChannelFormDialog.vue | 94 +- .../notice/components/RoutingConfigPanel.vue | 332 +++++++ src/views/ops/pages/alert/notice/index.vue | 8 + .../alert/tackle/components/AssignDialog.vue | 82 ++ .../alert/tackle/components/CommentDialog.vue | 21 + src/views/ops/pages/alert/tackle/index.vue | 141 ++- .../ops/pages/assets/device/form/index.vue | 74 +- .../ops/pages/assets/device/list/index.vue | 228 ++++- .../components/CollectionTemplateEditor.vue | 107 +++ .../ops/pages/assets/supplier/list/index.vue | 8 +- src/views/ops/pages/automation/index.vue | 432 ---------- src/views/ops/pages/big-screen/index.vue | 194 +++-- .../components/HTTPCheckPanel.vue | 444 ++++++++++ .../ops/pages/business-system/detail.vue | 20 +- .../ops/pages/collection/bindings/index.vue | 48 ++ .../components/BindingFormModal.vue | 72 ++ .../pages/collection/credentials/index.vue | 135 +++ .../ops/pages/collection/nodes/index.vue | 115 +++ .../components/CameraPreviewDialog.vue | 4 +- .../components/DeviceManagementDialog.vue | 158 +--- .../pages/dc/components/ConfigBackupPanel.vue | 110 +++ .../dc/components/DeviceMasterFields.vue | 53 ++ .../components/ImportantInterfacesPanel.vue | 197 +++++ .../dc/components/PrimaryCollectionFields.vue | 55 ++ .../ops/pages/dc/components/SnmpOidEditor.vue | 343 -------- .../ops/pages/dc/components/deviceMaster.ts | 268 ++++++ .../dc/database/components/CollectDialog.vue | 92 +- .../pages/dc/database/components/Detail.vue | 731 ---------------- .../dc/database/components/DetailDialog.vue | 41 +- .../dc/database/components/FormDialog.vue | 346 ++------ .../database/components/QuickConfigDialog.vue | 79 -- .../dc/database/components/RemoteControl.vue | 302 ------- .../ops/pages/dc/database/config/columns.ts | 20 +- src/views/ops/pages/dc/database/index.vue | 37 +- src/views/ops/pages/dc/detail/index.vue | 807 +----------------- .../dc/device-collect/components/Detail.vue | 53 +- .../device-collect/components/FormDialog.vue | 435 +++------- .../components/QuickConfigDialog.vue | 245 ------ .../pages/dc/device-collect/config/columns.ts | 30 +- .../ops/pages/dc/device-collect/index.vue | 36 +- .../ops/pages/dc/device-overview/index.vue | 183 ++++ .../pages/dc/middleware/components/Detail.vue | 36 +- .../dc/middleware/components/FormDialog.vue | 296 ++----- .../components/QuickConfigDialog.vue | 87 -- .../middleware/components/RemoteControl.vue | 302 ------- .../ops/pages/dc/middleware/config/columns.ts | 25 +- src/views/ops/pages/dc/middleware/index.vue | 65 +- .../pages/dc/network/components/Detail.vue | 58 +- .../dc/network/components/FormDialog.vue | 500 +++-------- .../network/components/QuickConfigDialog.vue | 79 -- .../dc/network/components/RemoteControl.vue | 302 ------- .../ops/pages/dc/network/config/columns.ts | 15 +- .../pages/dc/network/config/search-form.ts | 18 +- src/views/ops/pages/dc/network/index.vue | 18 +- .../ops/pages/dc/pc/components/Detail.vue | 731 ---------------- .../ops/pages/dc/pc/components/FormDialog.vue | 347 ++++---- .../dc/pc/components/QuickConfigDialog.vue | 81 -- .../pages/dc/pc/components/RemoteControl.vue | 302 ------- src/views/ops/pages/dc/pc/config/columns.ts | 31 +- .../ops/pages/dc/pc/config/search-form.ts | 12 - src/views/ops/pages/dc/pc/index.vue | 468 ++-------- src/views/ops/pages/dc/remote/index.vue | 465 ---------- .../pages/dc/security/components/Detail.vue | 202 ++++- .../dc/security/components/FormDialog.vue | 576 ++++--------- .../security/components/QuickConfigDialog.vue | 259 ------ .../ops/pages/dc/security/config/columns.ts | 25 +- src/views/ops/pages/dc/security/index.vue | 73 +- .../components/HardwareDeviceConfigDialog.vue | 597 +------------ .../server/components/QuickConfigDialog.vue | 97 --- .../dc/server/components/RemoteControl.vue | 302 ------- .../dc/server/components/ServerDetail.vue | 772 ----------------- .../dc/server/components/ServerFormDialog.vue | 490 ++++------- .../ops/pages/dc/server/config/columns.ts | 32 +- .../ops/pages/dc/server/config/search-form.ts | 13 +- src/views/ops/pages/dc/server/index.vue | 220 ++--- .../dc/storage/components/StorageDetail.vue | 65 +- .../storage/components/StorageFormDialog.vue | 514 +++-------- .../ops/pages/dc/storage/config/columns.ts | 22 - src/views/ops/pages/dc/storage/index.vue | 20 +- .../dc/url-harvest/components/Detail.vue | 129 --- .../dc/url-harvest/components/FormDialog.vue | 216 ----- .../components/QuickConfigDialog.vue | 71 -- .../pages/dc/url-harvest/config/columns.ts | 71 -- .../dc/url-harvest/config/search-form.ts | 22 - src/views/ops/pages/dc/url-harvest/index.vue | 295 ------- .../feedback/components/CommentDialog.vue | 26 +- .../components/TicketDetailDialog.vue | 30 + .../feedback/components/TicketFormDialog.vue | 2 +- src/views/ops/pages/feedback/undo/index.vue | 17 +- src/views/ops/pages/governance/index.vue | 31 +- .../ops/pages/log-mgmt/entries/index.vue | 39 + .../pages/monitor/collection-health/index.vue | 505 ++++++++--- .../ops/pages/monitor/environment/index.vue | 44 +- .../ops/pages/monitor/log/TraceMonitor.vue | 191 +++++ src/views/ops/pages/monitor/log/index.vue | 18 +- src/views/ops/pages/monitor/network/index.vue | 8 +- .../ops/pages/monitor/security/index.vue | 85 +- src/views/ops/pages/monitor/storage/index.vue | 171 ++-- src/views/ops/pages/monitor/url/index.vue | 590 ------------- .../pages/monitor/virtualization/index.vue | 10 +- .../ops/pages/netarch/ip-scan-tasks/index.vue | 127 ++- .../components/TopologyListDialog.vue | 4 +- .../topo/components/EdgeActionDialog.vue | 13 +- .../topo/components/NodeActionDialog.vue | 18 +- .../topo/components/NodeDetailDialog.vue | 245 ++---- .../topo/components/NodeEditDialog.vue | 137 +-- .../pages/netarch/topo/components/Toolbar.vue | 21 +- src/views/ops/pages/netarch/topo/config.ts | 3 + .../pages/netarch/topo/hooks/useEdgeStyles.ts | 5 +- src/views/ops/pages/netarch/topo/index.vue | 234 +++-- src/views/ops/pages/netarch/topo/types.ts | 9 +- .../traffic/PassiveProbeObservations.vue | 88 ++ src/views/ops/pages/netarch/traffic/index.vue | 3 + src/views/ops/pages/report/designer/index.vue | 473 ++++++++++ src/views/ops/pages/report/history/index.vue | 3 + .../ops/pages/resource-context/index.vue | 254 +----- .../components/PermissionSettingDialog.vue | 181 ---- .../components/PermissionTree.vue | 229 ----- .../account-management/index.vue | 79 +- .../system-settings/audit-logs/index.vue | 8 +- .../system-settings/common-phrases/index.vue | 71 ++ .../system-settings/data-dictionary/index.vue | 83 ++ .../system-settings/parameters/index.vue | 60 ++ .../system-settings/system-logs/index.vue | 40 +- .../system-logs/session-logs.vue | 56 ++ src/views/ops/pages/user-permission/index.vue | 21 + .../user-permission/notification-groups.vue | 71 ++ .../pages/user-permission/organization.vue | 96 +++ src/views/ops/pages/user-permission/roles.vue | 93 ++ 179 files changed, 8533 insertions(+), 15030 deletions(-) delete mode 100644 src/api/module/factor.ts create mode 100644 src/api/ops/alertRouting.ts delete mode 100644 src/api/ops/automation.ts create mode 100644 src/api/ops/collection.ts create mode 100644 src/api/ops/device-interface.ts create mode 100644 src/api/ops/platformRuntime.ts create mode 100644 src/api/ops/systemSettings.ts delete mode 100644 src/api/ops/url-device.ts create mode 100644 src/composables/systemParameters.ts create mode 100644 src/utils/iframe.ts create mode 100644 src/views/ops/pages/alert/notice/components/RoutingConfigPanel.vue create mode 100644 src/views/ops/pages/alert/tackle/components/AssignDialog.vue create mode 100644 src/views/ops/pages/assets/supplier/components/CollectionTemplateEditor.vue delete mode 100644 src/views/ops/pages/automation/index.vue create mode 100644 src/views/ops/pages/business-system/components/HTTPCheckPanel.vue create mode 100644 src/views/ops/pages/collection/bindings/index.vue create mode 100644 src/views/ops/pages/collection/components/BindingFormModal.vue create mode 100644 src/views/ops/pages/collection/credentials/index.vue create mode 100644 src/views/ops/pages/collection/nodes/index.vue create mode 100644 src/views/ops/pages/dc/components/ConfigBackupPanel.vue create mode 100644 src/views/ops/pages/dc/components/DeviceMasterFields.vue create mode 100644 src/views/ops/pages/dc/components/ImportantInterfacesPanel.vue create mode 100644 src/views/ops/pages/dc/components/PrimaryCollectionFields.vue delete mode 100644 src/views/ops/pages/dc/components/SnmpOidEditor.vue create mode 100644 src/views/ops/pages/dc/components/deviceMaster.ts delete mode 100644 src/views/ops/pages/dc/database/components/Detail.vue delete mode 100644 src/views/ops/pages/dc/database/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/database/components/RemoteControl.vue delete mode 100644 src/views/ops/pages/dc/device-collect/components/QuickConfigDialog.vue create mode 100644 src/views/ops/pages/dc/device-overview/index.vue delete mode 100644 src/views/ops/pages/dc/middleware/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/middleware/components/RemoteControl.vue delete mode 100644 src/views/ops/pages/dc/network/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/network/components/RemoteControl.vue delete mode 100644 src/views/ops/pages/dc/pc/components/Detail.vue delete mode 100644 src/views/ops/pages/dc/pc/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/pc/components/RemoteControl.vue delete mode 100644 src/views/ops/pages/dc/remote/index.vue delete mode 100644 src/views/ops/pages/dc/security/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/server/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/server/components/RemoteControl.vue delete mode 100644 src/views/ops/pages/dc/server/components/ServerDetail.vue delete mode 100644 src/views/ops/pages/dc/url-harvest/components/Detail.vue delete mode 100644 src/views/ops/pages/dc/url-harvest/components/FormDialog.vue delete mode 100644 src/views/ops/pages/dc/url-harvest/components/QuickConfigDialog.vue delete mode 100644 src/views/ops/pages/dc/url-harvest/config/columns.ts delete mode 100644 src/views/ops/pages/dc/url-harvest/config/search-form.ts delete mode 100644 src/views/ops/pages/dc/url-harvest/index.vue create mode 100644 src/views/ops/pages/monitor/log/TraceMonitor.vue delete mode 100644 src/views/ops/pages/monitor/url/index.vue create mode 100644 src/views/ops/pages/netarch/traffic/PassiveProbeObservations.vue create mode 100644 src/views/ops/pages/report/designer/index.vue delete mode 100644 src/views/ops/pages/system-settings/account-management/components/PermissionSettingDialog.vue delete mode 100644 src/views/ops/pages/system-settings/account-management/components/PermissionTree.vue create mode 100644 src/views/ops/pages/system-settings/common-phrases/index.vue create mode 100644 src/views/ops/pages/system-settings/data-dictionary/index.vue create mode 100644 src/views/ops/pages/system-settings/parameters/index.vue create mode 100644 src/views/ops/pages/system-settings/system-logs/session-logs.vue create mode 100644 src/views/ops/pages/user-permission/index.vue create mode 100644 src/views/ops/pages/user-permission/notification-groups.vue create mode 100644 src/views/ops/pages/user-permission/organization.vue create mode 100644 src/views/ops/pages/user-permission/roles.vue diff --git a/.env.development b/.env.development index 57e1e68..7dea9fd 100644 --- a/.env.development +++ b/.env.development @@ -11,6 +11,7 @@ VITE_USE_MOCK=false # 开发环境通过 Vite 同源代理访问 API,避免浏览器跨域 VITE_API_BASE_URL=/api VITE_API_PROXY_TARGET=https://ops-api.apinb.com +VITE_BIG_SCREEN_VIEW_URL=/chart-view # 海康 WebSDK 开发代理(按实际摄像头/NVR 地址填写,不要提交账号密码) VITE_HIKVISION_PROXY_TARGET=http://192.168.1.101:80 diff --git a/.env.production b/.env.production index f1213e6..062f840 100644 --- a/.env.production +++ b/.env.production @@ -8,7 +8,8 @@ VITE_APP_DESCRIPTION="default standard template" VITE_USE_MOCK=false # API 基础URL -VITE_API_BASE_URL=https://ops-api.apinb.com +VITE_API_BASE_URL= +VITE_BIG_SCREEN_VIEW_URL=/view # 应用版本 VITE_APP_VERSION=1.0.0 diff --git a/config/vite.config.dev.ts b/config/vite.config.dev.ts index a761eee..f9c3afd 100644 --- a/config/vite.config.dev.ts +++ b/config/vite.config.dev.ts @@ -69,6 +69,8 @@ export default ({ mode }: ConfigEnv) => { '/mgt': proxyTarget(12436), '/Visual': proxyTarget(12438), '/visual': proxyTarget(12438), + '/Files': proxyTarget(12452), + '/files': proxyTarget(12452), } if (env.VITE_HIKVISION_PROXY_TARGET) { diff --git a/src/api/message.ts b/src/api/message.ts index 888ce79..cd74948 100644 --- a/src/api/message.ts +++ b/src/api/message.ts @@ -22,6 +22,8 @@ export interface MessageRecord { firstOccurredAt?: string lastOccurredAt?: string lastDeliveryId?: number + severity?: string + soundEnabled?: boolean } export type MessageListType = MessageRecord[] diff --git a/src/api/module/factor.ts b/src/api/module/factor.ts deleted file mode 100644 index eabf668..0000000 --- a/src/api/module/factor.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { request } from '@/api/request' - -/** 批次列表 */ -export const fetchLoanBatch = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/loan_batch', data) - -/** 上报列表 */ -export const fetchTransferWatch = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/transfer_watch', data) - -/** 回盘列表 */ -export const fetchTransferDown = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/transfer_down', data) - -/** 放款数据列表 */ -export const fetchDataFile = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/data_file', data) - -/** 回盘数据列表 */ -export const fetchDataReply = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/data_reply', data) - -/** 贷款支付数据列表 */ -export const fetchLoanPayment = (data: { page: number; size: number; status?: number }) => - request.post('/factor/v1/fetch/loan_payment', data) - -/** 获取日报数据 */ -export const fetchOverviewReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/overview', data) - -/** 获取日报数据 */ -export const fetchDailyReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/days', data) - -/** 获取周报数据 */ -export const fetchWeeklyReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/weeks', data) - -/** 获取月报数据 */ -export const fetchMonthlyReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/months', data) - -/** 获取批次详情 */ -export const getLoanBatchDetail = (data: { batch_id: number }) => request.post(`/factor/v1/get/batch`, data) diff --git a/src/api/module/pmn.ts b/src/api/module/pmn.ts index 28f4ecf..4367d33 100644 --- a/src/api/module/pmn.ts +++ b/src/api/module/pmn.ts @@ -34,11 +34,3 @@ export const modifyMenu = (data: MenuItem) => request.post('/rbac2/v1/pmn/modify export const deleteMenu = (data: { id: MenuItem['id'] }) => request.post('/rbac2/v1/pmn/del', data, { needWorkspace: true }) /** 更新菜单排序 */ export const updateMenuOrder = (data: { pmn_id: number; sort_key: number }[]) => request.post('/rbac2/v1/pmn/sort', data) - -/** 用户-给指定用户设置权限 */ -export const userPmn = (data: { code: string; list: { id: number; pmn_id: number }[] }) => - request.post('/rbac2/v1/pmn/user_pmn', data, { needWorkspace: true }) - -/** 用户-给指定用户设置权限 */ -export const userSetPmn = (data: { list: { id: number; pmn_id: number }[] }) => - request.post('/rbac2/v1/pmn/user/set_pmn', data, { needWorkspace: true }) diff --git a/src/api/module/user.ts b/src/api/module/user.ts index 6e078b4..2c4da7a 100644 --- a/src/api/module/user.ts +++ b/src/api/module/user.ts @@ -4,7 +4,9 @@ import { LoginRequest, UserItem } from '../types' /** 登录 */ export const login = (data: LoginRequest) => request.post('/rbac2/v1/login', data) /** 登出 */ -export const logout = () => request.post('/rbac2/v1/logout') +export const logout = () => request.post('/rbac2/v1/user/logout') +/** 更新在线状态 */ +export const heartbeat = () => request.post('/rbac2/v1/user/heartbeat') /** 获取菜单列表 */ export const getMenuList = () => request.post('/rbac2/v1/user/menu') /** 创建用户 */ @@ -17,19 +19,14 @@ export const fetchUserList = (data: { page: number; size: number; keyword?: stri export const modifyUser = (data: UserItem) => request.post('/rbac2/v1/user/modify', data) /** 删除用户 */ export const deleteUser = (data: { id: UserItem['id'] }) => request.post('/rbac2/v1/user/del', data) -/** 获取所有权限【树形】 */ -export const getUserPmnTree = (data: { id: UserItem['id']; workspace: string }) => request.post('/rbac2/v1/user/pmn_tree', data) - -/** 用户-获取所有权限【平面】 */ +/** 获取用户通过角色获得的权限 */ export const userPmn = (data: { id: UserItem['id']; workspace: string }) => request.post('/rbac2/v1/user/pmn', data, { needWorkspace: true }) -/** 用户-给指定用户设置权限 */ -export const userSetPmn = (data: { list: { id: number; pmn_id: number }[] }) => - request.post('/rbac2/v1/user/set_pmn', data, { needWorkspace: true }) - -/** 用户-给指定用户编辑权限 */ -export const userModifyPmn = (data: { id: UserItem['id']; list: number[] }) => - request.post('/rbac2/v1/user/modify_pmn', data, { needWorkspace: true }) +/** 在线用户 */ +export const fetchOnlineUsers = (data: { page: number; size: number; keyword?: string }) => request.post('/rbac2/v1/user/online', data) +/** 登录日志 */ +export const fetchLoginLogs = (data: { page: number; size: number; keyword?: string; result?: string }) => + request.post('/rbac2/v1/user/login-logs', data) /** 重置密码(短信) */ export const resetUserPassword = (data: { diff --git a/src/api/ops/alertPolicy.ts b/src/api/ops/alertPolicy.ts index 83e33fe..ea71a97 100644 --- a/src/api/ops/alertPolicy.ts +++ b/src/api/ops/alertPolicy.ts @@ -143,5 +143,38 @@ export interface PolicyOptionsParams { enabled?: boolean } +export type ServicePolicyCategory = 'database' | 'middleware' | 'host' | 'security' | 'storage' | 'room_device' + +export interface ServicePolicyBindingDetails { + category: ServicePolicyCategory + service_identity: string + source_version: number + policy_ids: number[] +} + +export interface AlertBindingSyncStatus { + category: ServicePolicyCategory + service_identity: string + source_version?: number + status: 'none' | 'pending' | 'processing' | 'succeeded' | 'failed' + attempt_count?: number + last_error?: string + last_http_code?: number + last_attempt_at?: string + next_attempt_at?: string +} + /** 获取告警策略下拉选项(不分页),支持 keyword 模糊搜索与 enabled 过滤 */ export const fetchPolicyOptions = (params?: PolicyOptionsParams) => request.get('/Alert/v1/policy/options', { params }) + +/** 获取资源当前绑定的告警策略 */ +export const fetchServicePolicyBindings = (category: ServicePolicyCategory, serviceIdentity: string) => + request.get<{ code: number; details?: ServicePolicyBindingDetails; message?: string }>('/Alert/v1/service-policy-bindings', { + params: { category, service_identity: serviceIdentity }, + }) + +/** 获取告警策略绑定同步状态 */ +export const fetchAlertBindingSyncStatus = (category: ServicePolicyCategory, serviceIdentity: string) => + request.get<{ code: number; details?: AlertBindingSyncStatus; message?: string }>('/DC-Control/v1/alert-binding-sync/status', { + params: { category, service_identity: serviceIdentity }, + }) diff --git a/src/api/ops/alertRecord.ts b/src/api/ops/alertRecord.ts index c993b67..e8dec72 100644 --- a/src/api/ops/alertRecord.ts +++ b/src/api/ops/alertRecord.ts @@ -17,6 +17,8 @@ export const fetchAlertRecords = (data: { resource_category?: string server_identity?: string ip?: string + resource_uid?: string + mine?: boolean }) => { return request.get('/Alert/v1/record/list', { params: data }) } @@ -24,13 +26,21 @@ export const fetchAlertRecords = (data: { /** 获取 告警记录详情 */ export const fetchAlertRecordDetail = (id: number) => request.get(`/Alert/v1/record/get/${id}`) +/** 按统一资源批量获取未恢复告警,供拓扑等视图复用。 */ +export const fetchActiveAlertsByResources = (resourceUids: string[], limitPerResource = 5) => + request.post('/Alert/v1/record/active/by-resources', { + resource_uids: resourceUids, + limit_per_resource: limitPerResource, + }) + /** 创建处理记录(执行确认/解决/屏蔽等操作) */ export const createAlertProcess = (data: { alert_record_id: number action: string // ack / resolve / silence / comment / assign / escalate / close comment?: string assign_to?: string - assign_to_id?: string + assign_to_id?: number + assign_role_id?: number silence_until?: string silence_reason?: string escalate_level?: string @@ -41,6 +51,9 @@ export const createAlertProcess = (data: { metadata?: string }) => request.post('/Alert/v1/process/create', data) +/** 获取“级别—状态—监控对象”告警分类计数。 */ +export const fetchAlertTree = (mine = false) => request.get('/Alert/v1/record/tree', { params: { mine } }) + /** 获取 告警处理记录列表 */ export const fetchAlertProcessList = (data: { alert_record_id?: number diff --git a/src/api/ops/alertRouting.ts b/src/api/ops/alertRouting.ts new file mode 100644 index 0000000..f20548c --- /dev/null +++ b/src/api/ops/alertRouting.ts @@ -0,0 +1,74 @@ +import { request } from '@/api/request' + +export interface MessageContentTemplate { + id?: number + code: string + name: string + description?: string + enabled: boolean + subject_template: string + body_template: string + recovery_subject_template: string + recovery_body_template: string +} + +export interface NotificationGroup { + id?: number + code: string + name: string + description?: string + enabled: boolean + member_user_ids: number[] | string +} + +export interface RoutingScope { + priority: number + resource_category?: string + business_system_id?: number + ip_cidr?: string + device_level?: string + label_matchers?: Record +} + +export interface ResponsibilityRule extends RoutingScope { + id?: number + code: string + name: string + description?: string + enabled: boolean + owner_user_id: number + subscriber_group_ids: number[] | string + cc_user_ids: number[] | string +} + +export interface NotificationPolicy extends RoutingScope { + id?: number + name: string + enabled: boolean + severity_filter: string[] | string + channel_ids: number[] | string + notification_group_ids: number[] | string + cc_user_ids: number[] | string +} + +type ListParams = { page?: number; page_size?: number; keyword?: string; enabled?: boolean | string } + +export const fetchMessageTemplateList = (params?: ListParams) => request.get('/Alert/v1/message-template/list', { params: params || {} }) +export const createMessageTemplate = (data: MessageContentTemplate) => request.post('/Alert/v1/message-template/create', data) +export const updateMessageTemplate = (data: MessageContentTemplate) => request.post('/Alert/v1/message-template/update', data) +export const deleteMessageTemplate = (id: number) => request.delete(`/Alert/v1/message-template/delete/${id}`) + +export const fetchNotificationGroupList = (params?: ListParams) => request.get('/Alert/v1/notification-group/list', { params: params || {} }) +export const createNotificationGroup = (data: NotificationGroup) => request.post('/Alert/v1/notification-group/create', data) +export const updateNotificationGroup = (data: NotificationGroup) => request.post('/Alert/v1/notification-group/update', data) +export const deleteNotificationGroup = (id: number) => request.delete(`/Alert/v1/notification-group/delete/${id}`) + +export const fetchResponsibilityList = (params?: ListParams) => request.get('/Alert/v1/responsibility/list', { params: params || {} }) +export const createResponsibility = (data: ResponsibilityRule) => request.post('/Alert/v1/responsibility/create', data) +export const updateResponsibility = (data: ResponsibilityRule) => request.post('/Alert/v1/responsibility/update', data) +export const deleteResponsibility = (id: number) => request.delete(`/Alert/v1/responsibility/delete/${id}`) + +export const fetchNotificationPolicyList = (params?: ListParams) => request.get('/Alert/v1/notification-policy/list', { params: params || {} }) +export const createNotificationPolicy = (data: NotificationPolicy) => request.post('/Alert/v1/notification-policy/create', data) +export const updateNotificationPolicy = (data: NotificationPolicy) => request.post('/Alert/v1/notification-policy/update', data) +export const deleteNotificationPolicy = (id: number) => request.delete(`/Alert/v1/notification-policy/delete/${id}`) diff --git a/src/api/ops/asset.ts b/src/api/ops/asset.ts index 818430e..eb49732 100644 --- a/src/api/ops/asset.ts +++ b/src/api/ops/asset.ts @@ -44,17 +44,33 @@ export interface AssetListParams { status?: string sort?: string order?: string + category_code?: string + manufacturer_code?: string + datacenter_id?: number + province_id?: number + city_id?: number + room_id?: number + rack_id?: number + department_id?: number + department?: string + business_system_id?: number + device_level?: string + projection_status?: string } /** 资产表单数据 */ export interface AssetForm { id?: number + resource_uid?: string asset_name: string asset_code: string category_id?: number model?: string - manufacturer?: string + manufacturer_id?: number + manufacturer_code?: string serial_number?: string + management_ip?: string + device_level?: 'core' | 'important' | 'normal' source_address?: string purchase_date?: string original_value?: number @@ -62,6 +78,8 @@ export interface AssetForm { warranty_period?: number warranty_expiry?: string department?: string + department_id?: number + business_system_id?: number user?: string status?: string location?: string @@ -74,48 +92,49 @@ export interface AssetForm { specifications?: string description?: string remarks?: string + attachments?: string } /** 获取资产列表(分页) */ export const fetchAssetList = (data?: AssetListParams) => { - return request.post('/Assets/v1/asset/list', data || {}) + return request.get('/Assets/v1/devices', { params: data || {} }) } /** 获取资产列表(不分页,下拉) */ export const fetchAssetAll = (params?: { keyword?: string }) => { - return request.get('/Assets/v1/asset/all', { params }) + return request.get('/Assets/v1/devices/options', { params }) } /** 获取资产详情 */ -export const fetchAssetDetail = (id: number) => { - return request.get(`/Assets/v1/asset/detail/${id}`) +export const fetchAssetDetail = (resourceUid: string) => { + return request.get(`/Assets/v1/devices/${encodeURIComponent(resourceUid)}`) } /** 创建资产 */ export const createAsset = (data: AssetForm) => { - return request.post('/Assets/v1/asset/create', data) + return request.post('/Assets/v1/devices', data) } /** 批量导入资产 */ export const bulkImportAssets = (data: { dry_run?: boolean; items: any[] }) => { - return request.post('/Assets/v1/asset/bulk_import', data) + return request.post('/Assets/v1/devices/bulk-import', data) } /** 更新资产 */ -export const updateAsset = (data: AssetForm) => { - return request.put('/Assets/v1/asset/update', data) +export const updateAsset = (resourceUid: string, data: AssetForm) => { + return request.put(`/Assets/v1/devices/${encodeURIComponent(resourceUid)}`, data) } /** 删除资产 */ -export const deleteAsset = (id: number) => { - return request.delete(`/Assets/v1/asset/delete/${id}`) +export const deleteAsset = (resourceUid: string) => { + return request.delete(`/Assets/v1/devices/${encodeURIComponent(resourceUid)}`) } /** 导出资产 */ export const exportAssets = (keyword?: string) => { const params: any = {} if (keyword) params.keyword = keyword - return request.get('/Assets/v1/asset/export', { params }) + return request.get('/Assets/v1/devices/export', { params }) } /** 获取资产分类列表(下拉) */ @@ -128,24 +147,7 @@ export const fetchSupplierOptions = () => { return request.get('/Assets/v1/supplier/all') } -/** 绑定资产和监控资源 */ -export const linkAssetResource = (data: { - asset_id: number - resource_uid: string - display_name?: string - business_system_id?: number - health_status?: string - alert_status?: string -}) => { - return request.post('/Assets/v1/asset/resource/link', data) -} - -/** 查询资产绑定的监控资源 */ -export const fetchAssetResourceBindings = (assetId: number) => { - return request.get(`/Assets/v1/asset/resource/${assetId}`) -} - -/** 解除资产和监控资源绑定 */ -export const unlinkAssetResource = (assetId: number, resourceUid: string) => { - return request.delete(`/Assets/v1/asset/resource/${assetId}`, { params: { resource_uid: resourceUid } }) +/** 获取生产厂商列表(下拉) */ +export const fetchManufacturerOptions = (keyword?: string) => { + return request.get('/Assets/v1/manufacturers/options', { params: { keyword } }) } diff --git a/src/api/ops/automation.ts b/src/api/ops/automation.ts deleted file mode 100644 index b0c84e1..0000000 --- a/src/api/ops/automation.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { request } from '@/api/request' - -export interface AutomationScript { - id: number - name: string - resource_category: string - command_template: string - approval_level: string - rollback_command?: string - rollback_required: boolean - description?: string - status: string - created_at?: string - updated_at?: string -} - -export interface AutomationExecution { - id: number - script_id: number - mode: string - status: string - resource_uid: string - rendered_command: string - incident_id?: number - ticket_id?: number - approval_level: string - approved_by?: string - approved_at?: string - operator?: string - started_at?: string - finished_at?: string - timeout_sec?: number - stdout?: string - stderr?: string - exit_code?: number - error?: string - audit_trail?: string - created_at?: string - updated_at?: string -} - -export interface AutomationRunRequest { - resource_uid: string - incident_id?: number - ticket_id?: number - operator?: string - approver?: string - approved?: boolean - mode?: 'execute' | 'rollback' - variables?: Record -} - -export interface PageResponse { - total: number - page: number - page_size: number - data: T[] -} - -export const fetchAutomationScripts = (params?: Record) => - request.get<{ details: PageResponse }>('/DC-Control/v1/automation/scripts', { params }) - -export const createAutomationScript = (data: Partial) => - request.post<{ details: AutomationScript }>('/DC-Control/v1/automation/scripts', data) - -export const updateAutomationScript = (id: number, data: Partial) => - request.put(`/DC-Control/v1/automation/scripts/${id}`, data) - -export const deleteAutomationScript = (id: number) => request.delete(`/DC-Control/v1/automation/scripts/${id}`) - -export const dryRunAutomationScript = (id: number, data: AutomationRunRequest) => - request.post<{ details: AutomationExecution }>(`/DC-Control/v1/automation/scripts/${id}/dry-run`, data) - -export const requestAutomationExecution = (id: number, data: AutomationRunRequest) => - request.post<{ details: AutomationExecution }>(`/DC-Control/v1/automation/scripts/${id}/executions`, data) - -export const executeAutomationScript = (id: number, data: AutomationRunRequest) => - request.post<{ details: AutomationExecution }>(`/DC-Control/v1/automation/scripts/${id}/execute`, data) - -export const rollbackAutomationScript = (id: number, data: AutomationRunRequest) => - request.post<{ details: AutomationExecution }>(`/DC-Control/v1/automation/scripts/${id}/rollback`, data) - -export const fetchAutomationExecutions = (params?: Record) => - request.get<{ details: PageResponse }>('/DC-Control/v1/automation/executions', { params }) - -export const approveAutomationExecution = (id: number, data: { approver: string; operator?: string }) => - request.post<{ details: AutomationExecution }>(`/DC-Control/v1/automation/executions/${id}/approve`, data) diff --git a/src/api/ops/bigScreen.ts b/src/api/ops/bigScreen.ts index e2fe527..c3731e9 100644 --- a/src/api/ops/bigScreen.ts +++ b/src/api/ops/bigScreen.ts @@ -17,8 +17,12 @@ export interface BigScreen { width: number height: number status: number - publish_status: 'draft' | 'published' | 'offline' | string - is_public?: boolean + publish_status: 'draft' | 'published' | string + dataset_code: 'network_topology' | 'it_resource_monitor' | 'business_monitor' | string + built_in_code?: string + is_builtin: boolean + carousel_enabled: boolean + carousel_order: number creator_name?: string created_at?: string updated_at?: string @@ -45,12 +49,15 @@ export interface BigScreenPublish { export interface BigScreenCarousel { id: number - name: string - screen_ids: Record interval_seconds: number enabled: boolean } +export interface BigScreenDataset { + code: string + name: string +} + export interface BigScreenSnapshot { id: number screen_id: number @@ -79,7 +86,10 @@ export const createBigScreen = (data: Partial) => export const updateBigScreen = (id: number, data: Partial) => request.put>(`/Mgt/v1/screen/${id}`, data) -export const publishBigScreen = (data: { screen_id: number; publish_type: 1 | 2; change_log?: string }) => +export const beginEditBigScreen = (id: number) => + request.post>(`/Mgt/v1/screen/${id}/begin-edit`) + +export const publishBigScreen = (data: { screen_id: number; publish_type: 1; change_log?: string }) => request.post>('/Mgt/v1/publish', data) export const fetchBigScreenPublishes = (params: { screen_id: number; page?: number; page_size?: number }) => @@ -91,11 +101,17 @@ export const fetchBigScreenGroups = () => export const createBigScreenGroup = (data: Partial) => request.post>('/Mgt/v1/big-screen/groups', data) -export const fetchBigScreenCarousels = () => - request.get>('/Mgt/v1/big-screen/carousels') +export const fetchBigScreenCarousel = () => + request.get>('/Mgt/v1/big-screen/carousel') -export const createBigScreenCarousel = (data: Partial) => - request.post>('/Mgt/v1/big-screen/carousels', data) +export const updateBigScreenCarousel = (data: Pick) => + request.put>('/Mgt/v1/big-screen/carousel', data) + +export const updateBigScreenCarouselItem = (id: number, data: { enabled: boolean; order: number }) => + request.put>(`/Mgt/v1/big-screen/screens/${id}/carousel`, data) + +export const fetchBigScreenDatasets = () => + request.get>('/Mgt/v1/public/big-screens/datasets') export const fetchBigScreenSnapshots = (params?: { screen_id?: number }) => request.get>('/Mgt/v1/big-screen/snapshots', { params }) diff --git a/src/api/ops/businessSystem.ts b/src/api/ops/businessSystem.ts index 7940552..cc66207 100644 --- a/src/api/ops/businessSystem.ts +++ b/src/api/ops/businessSystem.ts @@ -171,6 +171,101 @@ export interface ListResult { count: number } +/** HTTP/API 检测步骤请求头 */ +export interface BusinessHTTPHeader { + name: string + value: string +} + +/** HTTP/API 检测响应变量提取 */ +export interface BusinessHTTPExtract { + variable: string + source: 'json' | 'header' | 'cookie' + expression: string +} + +/** HTTP/API 检测断言 */ +export interface BusinessHTTPAssertion { + kind: 'status' | 'max_response_time_ms' | 'header_equals' | 'body_contains' | 'json_exists' | 'json_equals' + expression?: string + expected?: string +} + +/** HTTP/API 检测步骤 */ +export interface BusinessHTTPCheckStep { + code: string + name: string + method: 'GET' | 'HEAD' | 'POST' + path: string + headers?: Record + body?: string + read_only_confirmed?: boolean + extracts?: BusinessHTTPExtract[] + assertions?: BusinessHTTPAssertion[] +} + +export interface BusinessHTTPCheck { + id: number + business_system_id: number + name: string + base_url: string + collector_node_id: number + credential_id?: number + enabled: boolean + interval_seconds: number + timeout_seconds: number + version: number + next_run_at?: string + last_run_at?: string + last_status: string + last_duration_ms: number + last_error?: string + last_trace_id?: string + steps: BusinessHTTPCheckStep[] + collector_node?: { id: number; name: string; code: string; status: string } + credential?: { id: number; name: string; code: string; type: string } +} + +export interface BusinessHTTPCheckInput { + name: string + base_url: string + collector_node_id: number + credential_id?: number + enabled: boolean + interval_seconds: number + timeout_seconds: number + steps: BusinessHTTPCheckStep[] +} + +export interface BusinessHTTPCheckStepResult { + code: string + name: string + span_id: string + success: boolean + status_code: number + duration_ms: number + message: string + method: string + request_path: string +} + +export interface BusinessHTTPCheckRun { + id: number + http_check_id: number + business_system_id: number + collector_node_id: number + task_id: string + trigger_type: string + status: string + started_at?: string + finished_at?: string + duration_ms: number + failed_step_code?: string + message?: string + step_results_json: string + trace_id?: string +} + /** 获取业务系统列表 */ export const fetchBusinessSystems = (params?: BusinessSystemListParams) => request.get<{ code?: number; details?: PageResult; message?: string }>('/DC-Control/v1/business-systems', { params }) @@ -235,3 +330,27 @@ export const fetchBusinessDocumentLinks = (id: number) => /** 新增业务文档链接 */ export const createBusinessDocumentLink = (id: number, data: Partial) => request.post<{ code?: number; details?: BusinessDocumentLink; message?: string }>(`/DC-Control/v1/business-systems/${id}/documents`, data) + +/** 获取业务系统 HTTP/API 检测 */ +export const fetchBusinessHTTPChecks = (id: number) => + request.get<{ code?: number; details?: ListResult; message?: string }>(`/DC-Control/v1/business-systems/${id}/http-checks`) + +export const createBusinessHTTPCheck = (id: number, data: BusinessHTTPCheckInput) => + request.post<{ code?: number; details?: BusinessHTTPCheck; message?: string }>(`/DC-Control/v1/business-systems/${id}/http-checks`, data) + +export const updateBusinessHTTPCheck = (id: number, checkId: number, data: BusinessHTTPCheckInput) => + request.put<{ code?: number; details?: BusinessHTTPCheck; message?: string }>(`/DC-Control/v1/business-systems/${id}/http-checks/${checkId}`, data) + +export const deleteBusinessHTTPCheck = (id: number, checkId: number) => + request.delete(`/DC-Control/v1/business-systems/${id}/http-checks/${checkId}`) + +export const runBusinessHTTPCheck = (id: number, checkId: number) => + request.post<{ code?: number; details?: { task_id: string; status: string }; message?: string }>( + `/DC-Control/v1/business-systems/${id}/http-checks/${checkId}/run` + ) + +export const fetchBusinessHTTPCheckRuns = (id: number, checkId: number, limit = 50) => + request.get<{ code?: number; details?: ListResult; message?: string }>( + `/DC-Control/v1/business-systems/${id}/http-checks/${checkId}/runs`, + { params: { limit } } + ) diff --git a/src/api/ops/collection.ts b/src/api/ops/collection.ts new file mode 100644 index 0000000..babef81 --- /dev/null +++ b/src/api/ops/collection.ts @@ -0,0 +1,180 @@ +import { request } from '@/api/request' + +const PREFIX = '/DC-Control/v1/monitoring' + +export interface PageResult { + total: number + page: number + page_size: number + data: T[] +} + +export type CredentialType = 'snmp_v1' | 'snmp_v2c' | 'snmp_v3' | 'ssh' | 'database' | 'http_api' | 'redfish' | 'ipmi' + +export interface CollectionCredential { + id: number + code: string + name: string + type: CredentialType + username?: string + scope?: string + description?: string + enabled: boolean + secret_configured: boolean + created_at?: string + updated_at?: string +} + +export interface CredentialInput { + code: string + name: string + type: CredentialType + username?: string + scope?: string + description?: string + enabled?: boolean + secret?: Record +} + +export interface CollectionTemplate { + id: number + code: string + name: string + manufacturer_code: string + resource_category: string + protocol: string + version: number + model_matchers: string + metric_mappings: string + index_discoveries: string + inventory_mappings: string + read_only_queries: string + alert_rule_codes: string + content_digest: string + description?: string + adapter_verified: boolean + verification_note?: string + verified_at?: string + enabled: boolean +} + +export type CollectionTemplateInput = Omit + +export interface CollectorNode { + id: number + code: string + name: string + node_type: 'center' | 'regional' + region_code?: string + description?: string + enabled: boolean + status: string + capabilities: string + version?: string + queue_depth: number + last_heartbeat_at?: string + last_error?: string + enrollment_expires_at?: string +} + +export interface CollectorNodeInput { + code: string + name: string + node_type: 'center' | 'regional' + region_code?: string + description?: string + capabilities: string + enabled?: boolean +} + +export interface CollectionBinding { + id: number + name: string + resource_uid: string + collector_node_id: number + credential_id: number + template_id: number + template_version: number + protocol: string + target: string + port: number + interval_seconds: number + retry_policy: string + enabled: boolean + state: string + last_validation_error?: string + collector_node?: CollectorNode + credential?: CollectionCredential + template?: CollectionTemplate +} + +export interface CollectionBindingInput { + name: string + resource_uid: string + collector_node_id: number + credential_id: number + template_id: number + protocol: string + target: string + port: number + interval_seconds: number + retry_policy?: string + enabled?: boolean +} + +export interface HostAgentRegistration { + id: number + agent_id: string + resource_uid: string + claimed_resource_uid: string + hostname: string + version: string + status: 'confirmed' | 'pending_confirmation' + last_error: string + last_seen_at: string +} + +export const fetchCredentials = (params?: Record) => + request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/credentials`, { params }) + +export const createCredential = (data: CredentialInput) => request.post(`${PREFIX}/credentials`, data) +export const updateCredential = (id: number, data: CredentialInput) => request.put(`${PREFIX}/credentials/${id}`, data) +export const deleteCredential = (id: number) => request.delete(`${PREFIX}/credentials/${id}`) + +export const fetchCollectionTemplates = (params?: Record) => + request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/templates`, { params }) + +export const createCollectionTemplate = (data: CollectionTemplateInput) => request.post(`${PREFIX}/templates`, data) +export const updateCollectionTemplate = (id: number, data: CollectionTemplateInput) => request.put(`${PREFIX}/templates/${id}`, data) +export const deleteCollectionTemplate = (id: number) => request.delete(`${PREFIX}/templates/${id}`) +export const exportCollectionTemplate = (id: number) => request.get(`${PREFIX}/templates/${id}/export`) +export const importCollectionTemplate = (data: Record) => request.post(`${PREFIX}/templates/import`, data) + +export const fetchCollectorNodes = (params?: Record) => + request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/collector-nodes`, { params }) + +export const createCollectorNode = (data: CollectorNodeInput) => request.post(`${PREFIX}/collector-nodes`, data) +export const updateCollectorNode = (id: number, data: CollectorNodeInput) => request.put(`${PREFIX}/collector-nodes/${id}`, data) +export const deleteCollectorNode = (id: number) => request.delete(`${PREFIX}/collector-nodes/${id}`) +export const rotateCollectorNodeToken = (id: number) => request.post(`${PREFIX}/collector-nodes/${id}/installation-token`) + +export const fetchHostAgentRegistrations = (params?: Record) => + request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/host-agents`, { params }) + +export const confirmHostAgentRegistration = (id: number, resourceUid: string) => + request.post(`${PREFIX}/host-agents/${id}/confirm`, { resource_uid: resourceUid }) + +export const fetchCollectionBindings = (params?: Record) => + request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/bindings`, { params }) + +export const createCollectionBinding = (data: CollectionBindingInput) => request.post(`${PREFIX}/bindings`, data) +export const updateCollectionBinding = (id: number, data: CollectionBindingInput) => request.put(`${PREFIX}/bindings/${id}`, data) +export const deleteCollectionBinding = (id: number) => request.delete(`${PREFIX}/bindings/${id}`) + +export const fetchDiscoveryRuns = (params?: Record) => + request.get(`${PREFIX}/discovery-runs`, { params }) + +export const collectResourceNow = (resourceUid: string) => + request.post<{ code: number; details?: { task_ids: string[]; count: number }; message?: string }>( + `${PREFIX}/resources/${encodeURIComponent(resourceUid)}/collect-now` + ) diff --git a/src/api/ops/database.ts b/src/api/ops/database.ts index 3920a94..af4cac6 100644 --- a/src/api/ops/database.ts +++ b/src/api/ops/database.ts @@ -1,7 +1,7 @@ import { request } from '@/api/request' /** 数据库类型枚举 */ -export type DatabaseType = 'MySQL' | 'PostgreSQL' | 'Redis' | 'MongoDB' | 'DM' | 'KingBase' +export type DatabaseType = 'MySQL' | 'PostgreSQL' | 'Redis' | 'MongoDB' | 'DM' | 'KingBase' | 'Oracle' | 'OracleRAC' /** 数据库服务状态 */ export type DatabaseStatus = 'online' | 'offline' | 'error' @@ -13,19 +13,16 @@ export interface DatabaseService { updated_at: string deleted_at: string | null service_identity: string - server_identity: string name: string category: string type: DatabaseType host: string port: number - username: string - password: string // 列表接口恒为空字符串,详情接口不返回 database: string + tls_mode: 'require' | 'verify-ca' | 'verify-full' description: string enabled: boolean interval: number - extra: string tags: string status: DatabaseStatus status_code: number @@ -36,10 +33,9 @@ export interface DatabaseService { last_offline_time: string | null continuous_errors: number uptime: number - collect_on: boolean - collect_interval: number - collect_args: string - collect_last_result: string + product_version: string + adapter_status: 'verified' | 'unverified' | 'blocked' + last_data_time: string | null } /** 列表接口返回的分页数据 */ @@ -52,48 +48,32 @@ export interface DatabaseListResponse { /** 创建数据库服务请求参数 */ export interface CreateDatabaseParams { - service_identity?: string - server_identity?: string name: string category: string type: DatabaseType host: string port: number - username?: string - password?: string database?: string + tls_mode?: 'require' | 'verify-ca' | 'verify-full' description?: string enabled?: boolean interval?: number - extra?: string tags?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string } /** 更新数据库服务请求参数 */ export interface UpdateDatabaseParams { - service_identity?: string - server_identity?: string name?: string category?: string type?: DatabaseType host?: string port?: number - username?: string - password?: string database?: string + tls_mode?: 'require' | 'verify-ca' | 'verify-full' description?: string enabled?: boolean interval?: number - extra?: string tags?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string } /** 列表查询参数 */ @@ -107,14 +87,13 @@ export interface DatabaseQueryParams { /** 指标采集请求参数 */ export interface CollectMetricsParams { ids: number[] - persist?: boolean } /** 指标采集结果项 */ export interface CollectMetricsResultItem { id: number service_identity: string - metrics?: Record + task_ids?: string[] error?: string } @@ -164,7 +143,7 @@ export const fetchDatabaseDetail = (id: number) => request.get( * 创建数据库服务 * @param data 创建参数 */ -export const createDatabase = (data: CreateDatabaseParams) => request.post<{ message: string; id: number }>('/DC-Control/v1/database', data) +export const createDatabase = (data: CreateDatabaseParams) => request.post<{ message: string; id: number; service_identity: string }>('/DC-Control/v1/database', data) /** * 更新数据库服务 diff --git a/src/api/ops/dcControl.ts b/src/api/ops/dcControl.ts index 57a8cb0..831d050 100644 --- a/src/api/ops/dcControl.ts +++ b/src/api/ops/dcControl.ts @@ -68,6 +68,10 @@ export interface ControlResource { display_name: string description?: string status: string + asset_status?: string + manufacturer_code?: string + management_ip?: string + device_level?: string business_system_id?: number owner_user_id?: number department_id?: number @@ -86,23 +90,33 @@ export interface ControlResource { updated_at?: string } -export interface ControlResourcePayload { - resource_category?: string - service_identity?: string - display_name?: string - description?: string - status?: string - business_system_id?: number | null - owner_user_id?: number | null - department_id?: number | null - asset_id?: number | null - datacenter_id?: number | null - room_id?: number | null - rack_id?: number | null - unit_start?: number | null - unit_end?: number | null - location_ref?: string | null - tags?: string +export interface ControlResourceRuntimeMetric { + name: string + value: number + unit: string + type: string + timestamp: string +} + +export interface ControlResourceRuntime { + resource_uid: string + resource_category: string + service_identity: string + display_name: string + status: string + metrics_at?: string + metrics: ControlResourceRuntimeMetric[] +} + +export interface ControlResourceOverview { + master: ControlResource + runtime: ControlResourceRuntime | null + context: { + resource_uid: string + alert_query: { resource_uid: string } + log_query: { resource_uid: string } + topology_query: { resource_uid: string } + } } export interface ControlResourceType { @@ -169,6 +183,15 @@ export interface ControlCollectionStatus { updated_at?: string } +export interface ControlCollectionSummary { + enabled: number + ok: number + failed: number + unknown: number + overdue: number + as_of: string +} + export interface ResourceListParams { page?: number size?: number @@ -192,17 +215,10 @@ export const fetchControlResourceOptions = (params?: { resource_category?: strin params, }) -export const createControlResource = (data: ControlResourcePayload) => - request.post<{ code?: number; details?: ControlResource; message?: string }>('/DC-Control/v1/resources', data) - -export const updateControlResource = (id: number, data: ControlResourcePayload) => - request.put<{ code?: number; details?: ControlResource; message?: string }>(`/DC-Control/v1/resources/${id}`, data) - -export const deleteControlResource = (id: number) => - request.delete<{ code?: number; details?: { message: string }; message?: string }>(`/DC-Control/v1/resources/${id}`) - -export const backfillControlResources = () => - request.post<{ code?: number; details?: { message: string }; message?: string }>('/DC-Control/v1/resources/backfill') +export const fetchControlResourceOverview = (resourceUid: string) => + request.get<{ code?: number; details?: ControlResourceOverview; message?: string }>( + `/DC-Control/v1/resources/${encodeURIComponent(resourceUid)}/overview` + ) export const fetchControlResourceTypes = (params?: { page?: number; size?: number; keyword?: string }) => request.get<{ code?: number; details?: PageResult; message?: string }>('/DC-Control/v1/resource-types', { params }) @@ -245,3 +261,6 @@ export const fetchCollectionStatus = (params?: { request.get<{ code?: number; details?: PageResult; message?: string }>('/DC-Control/v1/collection/status', { params, }) + +export const fetchCollectionStatusSummary = () => + request.get<{ code?: number; details?: ControlCollectionSummary; message?: string }>('/DC-Control/v1/collection/status/summary') diff --git a/src/api/ops/device-interface.ts b/src/api/ops/device-interface.ts new file mode 100644 index 0000000..a8c1f3d --- /dev/null +++ b/src/api/ops/device-interface.ts @@ -0,0 +1,74 @@ +import { request } from '@/api/request' + +export interface ImportantInterfaceConfig { + id: number + resource_uid: string + interface_identity: string + purpose: string + expected_peer_resource_uid?: string + expected_peer_interface_identity?: string + enabled: boolean + last_condition: 'unknown' | 'normal' | 'interface_down' | 'neighbor_missing' + last_evaluated_at?: string + last_transition_at?: string +} + +export interface UnifiedDeviceInterface { + id: number + resource_uid: string + interface_identity: string + name: string + mac?: string + admin_state: string + oper_state: string + speed_bps: number + in_bytes: number + out_bytes: number + in_errors: number + out_errors: number + last_seen_at: string + important?: ImportantInterfaceConfig +} + +export interface DeviceConfigVersion { + id: number + resource_uid: string + task_id: string + template_code: string + template_version: number + collected_at: string + content_hash: string + filename: string + size: number + previous_version_id?: number +} + +const resourcePath = (resourceUid: string) => `/DC-Control/v1/monitoring/resources/${encodeURIComponent(resourceUid)}` + +export const fetchUnifiedDeviceInterfaces = (resourceUid: string) => + request.get(`${resourcePath(resourceUid)}/interfaces`) + +export const saveImportantInterface = ( + resourceUid: string, + interfaceIdentity: string, + data: { + purpose: string + expected_peer_resource_uid?: string + expected_peer_interface_identity?: string + } +) => request.put(`${resourcePath(resourceUid)}/interfaces/${encodeURIComponent(interfaceIdentity)}/importance`, data) + +export const removeImportantInterface = (resourceUid: string, interfaceIdentity: string) => + request.delete(`${resourcePath(resourceUid)}/interfaces/${encodeURIComponent(interfaceIdentity)}/importance`) + +export const triggerDeviceConfigBackup = (resourceUid: string) => + request.post(`${resourcePath(resourceUid)}/config-backups`) + +export const fetchDeviceConfigBackups = (resourceUid: string, params?: { page?: number; size?: number }) => + request.get(`${resourcePath(resourceUid)}/config-backups`, { params }) + +export const fetchDeviceConfigDiff = (resourceUid: string, baseId: number, targetId: number) => + request.get(`${resourcePath(resourceUid)}/config-backups/diff`, { params: { base_id: baseId, target_id: targetId } }) + +export const downloadDeviceConfig = (resourceUid: string, versionId: number) => + request.get(`${resourcePath(resourceUid)}/config-backups/${versionId}/download`, { responseType: 'blob' }) diff --git a/src/api/ops/floor.ts b/src/api/ops/floor.ts index b3825a2..ad43a67 100644 --- a/src/api/ops/floor.ts +++ b/src/api/ops/floor.ts @@ -13,10 +13,12 @@ export interface Floor { enabled?: boolean layout_plan?: string layout_plan_file_id?: string + layout_plan_name?: string + layout_plan_content_type?: string description?: string } -export type SaveFloorPayload = Omit +export type SaveFloorPayload = Omit interface ApiResponse { code: number diff --git a/src/api/ops/floorFile.ts b/src/api/ops/floorFile.ts index 97e8392..909087e 100644 --- a/src/api/ops/floorFile.ts +++ b/src/api/ops/floorFile.ts @@ -2,7 +2,6 @@ import { request } from '@/api/request' export interface PendingUpload { file_id: string - object_key: string upload: { method: string url: string @@ -17,7 +16,7 @@ interface ApiResponse { details: T } -/** 初始化楼层布局文件并直传 OSS。 */ +/** 初始化楼层布局文件并上传到当前全局存储。 */ export async function uploadFloorLayout(file: File): Promise { const response = await request.post>('/Assets/v1/floor/layout/init', { filename: file.name, diff --git a/src/api/ops/governance.ts b/src/api/ops/governance.ts index 61fe4e7..f8a2147 100644 --- a/src/api/ops/governance.ts +++ b/src/api/ops/governance.ts @@ -41,11 +41,21 @@ export interface CapacityRisk { metric_code: string unit: string current_value: number - threshold: number + status: '资源不足' | '配置合理' | '资源闲置' | '容量风险' | '数据不足' + idle_threshold: number + risk_threshold: number + shortage_threshold: number slope_per_hour: number risk_level: string predicted_at?: string + predicted_value?: number + confidence_low?: number + confidence_high?: number sample_count: number + data_start?: string + data_end?: string + data_span_hours: number + forecast_hours: number } export interface SloDashboardBucket { @@ -75,7 +85,7 @@ export const fetchAlertNoise = (params?: { hours?: number }) => export const fetchRuleSuggestions = (params?: { hours?: number }) => request.get('/Alert/v1/governance/suggestions', { params }) -export const fetchCapacityRisk = (params?: { resource_uid?: string; metric_code?: string }) => +export const fetchCapacityRisk = (params?: { resource_uid?: string; metric_type?: string; metric_code?: string }) => request.get('/DC-Control/v1/risk/capacity', { params }) export const fetchSloDashboard = () => request.get('/Feedback/v1/tickets/slo/dashboard') diff --git a/src/api/ops/ipScan.ts b/src/api/ops/ipScan.ts index 7329a2c..c5dac21 100644 --- a/src/api/ops/ipScan.ts +++ b/src/api/ops/ipScan.ts @@ -8,12 +8,16 @@ export interface IpScanTask { description?: string target_range: string port_range?: string - config?: string timeout?: number concurrency?: number + collector_node_id: number + candidate_credential_ids: string + allowed_protocols: string + default_location: string + default_resource_category: string + auto_onboard: boolean cron_expr?: string status?: string - server_id: number last_scan_time?: string next_scan_time?: string last_scan_status?: string @@ -42,7 +46,7 @@ export const fetchIpScanList = (params?: { page?: number; size?: number; keyword export const fetchIpScanDetail = (id: number) => request.get<{ code: number; details?: IpScanTask; message?: string }>(`/DC-Control/v1/ipscans/${id}`) -/** 触发一次扫描(会创建 scan_run 并调用 Agent);成功体见文档 { message: "scan triggered" } */ +/** 触发一次扫描,运行记录进入指定采集节点的等待队列。 */ export const triggerIpScan = (id: number) => request.post<{ code: number; details?: { message?: string }; message?: string }>(`/DC-Control/v1/ipscans/${id}/trigger`) diff --git a/src/api/ops/logs.ts b/src/api/ops/logs.ts index c897abb..73e9ac2 100644 --- a/src/api/ops/logs.ts +++ b/src/api/ops/logs.ts @@ -21,8 +21,12 @@ export interface LogEvent { normalized_detail: string device_name: string resource_type: string + resource_uid: string resource_id: string resource_name: string + business_system_id?: number + trace_id?: string + span_id?: string match_method: string severity_code: string trap_oid: string @@ -38,6 +42,61 @@ export interface LogEntriesParams { resource_id?: string dispatch_status?: string log_event_id?: number + trace_id?: string + resource_uid?: string + business_system_id?: number +} + +export interface TraceSummary { + trace_id: string + start_time: string + end_time: string + duration_ms: number + span_count: number + error_count: number + services: string + business_system_id?: number + resource_uid?: string +} + +export interface TraceSpan { + id: number + trace_id: string + span_id: string + parent_span_id?: string + service_name: string + operation_name: string + span_kind: string + start_time: string + end_time: string + duration_ms: number + status_code: string + status_message?: string + business_system_id?: number + resource_uid?: string + http_method?: string + http_route?: string + http_status_code?: number +} + +export interface TraceListParams { + page?: number + page_size?: number + trace_id?: string + service_name?: string + resource_uid?: string + business_system_id?: number + status?: 'ok' | 'error' | 'unset' + start_time?: string + end_time?: string +} + +export interface ServiceDependency { + source_service: string + target_service: string + call_count: number + error_count: number + average_ms: number } export interface LogEntriesResult { @@ -138,6 +197,7 @@ export interface AuditLog { actor_id: string actor_name: string action: string + log_category: 'operation' | 'data' object_type: string object_id: string operation_risk: string @@ -157,6 +217,7 @@ export interface AuditLogParams { source_service?: string actor_id?: string action?: string + log_category?: 'operation' | 'data' object_type?: string object_id?: string operation_risk?: string @@ -197,6 +258,18 @@ export function fetchLogEntries(params?: LogEntriesParams) { return request.get(`${BASE}/entries`, { params }) } +export function fetchTraces(params?: TraceListParams) { + return request.get(`${BASE}/traces`, { params }) +} + +export function fetchTraceDetail(traceId: string) { + return request.get(`${BASE}/traces/${encodeURIComponent(traceId)}`) +} + +export function fetchServiceDependencies(params?: TraceListParams) { + return request.get(`${BASE}/service-dependencies`, { params }) +} + export function fetchAlertOutbox(params?: AlertOutboxParams) { return request.get(`${BASE}/alert-outbox`, { params }) } diff --git a/src/api/ops/middleware.ts b/src/api/ops/middleware.ts index fa40460..6c98126 100644 --- a/src/api/ops/middleware.ts +++ b/src/api/ops/middleware.ts @@ -14,9 +14,11 @@ export interface MiddlewareItem { description: string enabled: boolean interval: number - extra: string tags: string - status_url: string + adapter_mode: 'http_status' | 'json' | 'prometheus' | 'jolokia' + product_version: string + adapter_status: 'verified' | 'unverified' | 'blocked' + last_data_time: string | null status: string status_code: number status_message: string @@ -26,11 +28,6 @@ export interface MiddlewareItem { last_offline_time: string | null continuous_errors: number uptime: number - agent_config: string - collect_on: boolean - collect_args: string - collect_interval: number - collect_last_result: string } /** 中间件列表响应 */ @@ -51,7 +48,6 @@ export interface MiddlewareListParams { /** 创建中间件请求参数 */ export interface MiddlewareCreateData { - service_identity?: string server_identity?: string name: string category: string @@ -59,19 +55,13 @@ export interface MiddlewareCreateData { description?: string enabled?: boolean interval?: number - extra?: string tags?: string - status_url?: string - agent_config?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number + adapter_mode?: 'http_status' | 'json' | 'prometheus' | 'jolokia' policy_ids?: number[] } /** 更新中间件请求参数 */ export interface MiddlewareUpdateData { - service_identity?: string server_identity?: string name?: string category?: string @@ -79,52 +69,11 @@ export interface MiddlewareUpdateData { description?: string enabled?: boolean interval?: number - extra?: string tags?: string - status_url?: string - agent_config?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string - status?: string - status_code?: number - status_message?: string - response_time?: number - last_check_time?: string - last_online_time?: string | null - last_offline_time?: string | null - continuous_errors?: number - uptime?: number + adapter_mode?: 'http_status' | 'json' | 'prometheus' | 'jolokia' policy_ids?: number[] } -/** 采集配置更新参数 */ -export interface MiddlewareCollectData { - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string -} - -/** 指标上报项 */ -export interface MetricItem { - service_identity: string - timestamp: string - [key: string]: any -} - -/** 指标上报请求参数 */ -export interface MetricsUploadData { - metrics: MetricItem[] -} - -/** 指标上报响应 */ -export interface MetricsUploadResponse { - message: string - count: number -} - /** 指标项 */ export interface MiddlewareMetric { metric_name: string @@ -177,7 +126,7 @@ export const fetchMiddlewareDetail = (id: number) => { /** 创建中间件服务 */ export const createMiddleware = (data: MiddlewareCreateData) => { - return request.post<{ message: string; id: number }>('/DC-Control/v1/middleware', data) + return request.post<{ message: string; id: number; service_identity: string }>('/DC-Control/v1/middleware', data) } /** 更新中间件服务 */ @@ -185,21 +134,11 @@ export const updateMiddleware = (id: number, data: MiddlewareUpdateData) => { return request.put<{ message: string }>(`/DC-Control/v1/middleware/${id}`, data) } -/** 部分更新采集配置 */ -export const patchMiddlewareCollect = (id: number, data: MiddlewareCollectData) => { - return request.patch<{ message: string }>(`/DC-Control/v1/middleware/${id}/collect`, data) -} - /** 删除中间件服务 */ export const deleteMiddleware = (id: number) => { return request.delete<{ message: string }>(`/DC-Control/v1/middleware/${id}`) } -/** 上报中间件服务指标(匿名接口) */ -export const uploadMiddlewareMetrics = (data: MetricsUploadData) => { - return request.post('/DC-Control/v1/middleware/metrics/upload', data) -} - /** 查询中间件服务指标元数据 */ export const fetchMiddlewareMetricsMeta = (params: MetricsMetaParams) => { return request.get('/DC-Control/v1/services/metrics/meta', { params }) @@ -217,13 +156,13 @@ export const MIDDLEWARE_TYPE_MAP: Record = { nginx: 'Nginx', apache: 'Apache', tomcat: 'Tomcat', - redis: 'Redis', kafka: 'Kafka', + activemq: 'ActiveMQ', + rocketmq: 'RocketMQ', + websphere: 'WebSphere', + generic: '通用中间件', rabbitmq: 'RabbitMQ', elasticsearch: 'Elasticsearch', - mysql: 'MySQL', - postgresql: 'PostgreSQL', - mongodb: 'MongoDB', other: '其他', } diff --git a/src/api/ops/netarchTopo.ts b/src/api/ops/netarchTopo.ts index 2eb0a38..100020c 100644 --- a/src/api/ops/netarchTopo.ts +++ b/src/api/ops/netarchTopo.ts @@ -19,6 +19,7 @@ export interface Topology { node_count?: number link_count?: number enable?: boolean + auto_discovery?: boolean created_at?: string updated_at?: string } @@ -41,6 +42,12 @@ export interface Link { description?: string created_at?: string updated_at?: string + discovery_source?: string + confirmed?: boolean + source_resource_uid?: string + source_interface_identity?: string + target_resource_uid?: string + target_interface_identity?: string } /** 拓扑分组 */ @@ -133,6 +140,9 @@ export const fetchTopologies = (data: { page: number; size: number; keyword?: st /** 获取拓扑详情 */ export const fetchTopologyDetail = (id: number) => request.get(`/DC-Control/v1/topologies/${id}`) +/** 获取统一资源对应的系统自动拓扑 */ +export const fetchAutomaticTopology = () => request.get('/DC-Control/v1/topologies/automatic') + /** 创建拓扑 */ export const createTopology = (data: Partial) => request.post('/DC-Control/v1/topologies', data) @@ -160,6 +170,13 @@ export const fetchImpactAnalysis = (params: { resource_uid: string; topology_id? export const createNode = (topologyId: number, data: Partial) => request.post(`/DC-Control/v1/topologies/${topologyId}/nodes`, data) +/** 将已有统一设备引用加入手工拓扑 */ +export const addResourceNodes = (topologyId: number, resource_uids: string[]) => + request.post<{ code: number; details?: { message: string; added: number } }>( + `/DC-Control/v1/topologies/${topologyId}/resource-nodes`, + { resource_uids } + ) + /** 更新节点 */ export const updateNode = (topologyId: number, nodeId: string, data: Partial) => request.put(`/DC-Control/v1/topologies/${topologyId}/nodes/${nodeId}`, data) @@ -171,13 +188,6 @@ export const deleteNode = (topologyId: number, nodeId: string) => request.delete export const updateNodesPositions = (topologyId: number, positions: Array<{ id: string; position: { x: number; y: number } }>) => request.put(`/DC-Control/v1/topologies/${topologyId}/nodes/positions`, { positions }) -/** 按资产 ID 批量导入节点(后端生成 node_id=asset-{id} 与 ref_type=asset) */ -export const batchImportAssetNodes = (topologyId: number, asset_ids: number[]) => - request.post<{ code: number; details?: { message: string; imported: number } }>( - `/DC-Control/v1/topologies/${topologyId}/nodes/batch-import`, - { asset_ids } - ) - // ==================== 链路管理接口 ==================== /** 获取链路列表 */ diff --git a/src/api/ops/network-device.ts b/src/api/ops/network-device.ts index 2cff098..32e8b2b 100644 --- a/src/api/ops/network-device.ts +++ b/src/api/ops/network-device.ts @@ -1,32 +1,24 @@ import { request } from '@/api/request' -export type NetworkDeviceProtocol = 'snmp' | 'ssh' | 'telnet' -export type NetworkDeviceSnmpVersion = 'v1' | 'v2c' | 'v3' export type NetworkDeviceStatus = 'online' | 'offline' | 'error' | 'unknown' export interface NetworkDeviceService { id: number + resource_uid: string + asset_id: number created_at: string updated_at: string deleted_at: string | null service_identity: string - server_identity: string name: string category: string type: string host: string - port: number - protocol: NetworkDeviceProtocol - community: string - snmp_version: NetworkDeviceSnmpVersion - username: string vendor: string model: string location: string description: string enabled: boolean - interval: number - extra: string tags: string status: NetworkDeviceStatus status_code: number @@ -37,6 +29,40 @@ export interface NetworkDeviceService { last_offline_time: string | null continuous_errors: number uptime: number + product_version: string + adapter_status: 'verified' | 'unverified' + last_data_time: string | null +} + +export interface DeviceInterfaceSnapshot { + timestamp: string + service_identity: string + device_host: string + if_index: number + if_name: string + mac_address: string + admin_status: string + oper_status: string + in_octets: number + out_octets: number + in_errors: number + out_errors: number + speed_bps: number +} + +export interface WirelessAccessPoint { + id: number + controller_resource_uid: string + ap_identity: string + name: string + mac_address: string + ip_address: string + serial_number: string + model: string + status: string + radio_count: number + client_count: number + last_seen_at: string } export interface NetworkDeviceListResponse { @@ -54,25 +80,18 @@ export interface NetworkDeviceQueryParams { } export interface CreateNetworkDeviceParams { + resource_uid: string + asset_id: number service_identity?: string - server_identity?: string name: string category: string type?: string host: string - port?: number - protocol?: NetworkDeviceProtocol - community?: string - snmp_version?: NetworkDeviceSnmpVersion - username?: string - password?: string vendor?: string model?: string location?: string description?: string enabled?: boolean - interval?: number - extra?: string tags?: string } @@ -89,4 +108,8 @@ export const createNetworkDevice = (data: CreateNetworkDeviceParams) => export const updateNetworkDevice = (id: number, data: UpdateNetworkDeviceParams) => request.put<{ message: string }>(`/DC-Control/v1/network-device-services/${id}`, data) -export const deleteNetworkDevice = (id: number) => request.delete<{ message: string }>(`/DC-Control/v1/network-device-services/${id}`) +export const fetchNetworkDeviceInterfaces = (id: number) => + request.get(`/DC-Control/v1/network-device-services/${id}/interfaces`) + +export const fetchNetworkDeviceAccessPoints = (id: number) => + request.get(`/DC-Control/v1/network-device-services/${id}/access-points`) diff --git a/src/api/ops/noticeChannel.ts b/src/api/ops/noticeChannel.ts index ea14293..5d94e74 100644 --- a/src/api/ops/noticeChannel.ts +++ b/src/api/ops/noticeChannel.ts @@ -15,7 +15,8 @@ export const createNoticeChannel = (data: { name: string type: string config: string - message_template?: string + message_template_id: number + in_app_sound_enabled?: boolean rate_limit?: number rate_limit_window?: number severity_filter?: string @@ -51,7 +52,8 @@ export const updateNoticeChannel = (data: { name?: string type?: string config?: string - message_template?: string + message_template_id?: number + in_app_sound_enabled?: boolean rate_limit?: number rate_limit_window?: number severity_filter?: string diff --git a/src/api/ops/pc.ts b/src/api/ops/pc.ts index 9bfad15..276e391 100644 --- a/src/api/ops/pc.ts +++ b/src/api/ops/pc.ts @@ -5,14 +5,6 @@ export interface PCListParams { page?: number size?: number keyword?: string - collect_on?: boolean -} - -/** 资源使用率信息 */ -export interface ResourceInfo { - value: number // 使用率百分比 - total?: string // 总量描述 - used?: string // 已用描述 } /** PC 数据结构 */ @@ -22,6 +14,8 @@ export interface PCItem { updated_at: string deleted_at: string | null pc_identity: string + resource_uid: string + asset_id: number name: string host: string ip_address: string @@ -32,19 +26,8 @@ export interface PCItem { server_type: string tags: string location: string - remote_access: string - remote_port: number - agent_config: string status: string last_check_time: string - collect_on: boolean - collect_args: string - collect_interval: number - collect_last_result: string - // 资源使用率(前端展示用,可能来自采集数据) - cpu_info?: ResourceInfo - memory_info?: ResourceInfo - disk_info?: ResourceInfo } /** PC 列表返回数据 */ @@ -57,6 +40,8 @@ export interface PCListResult { /** PC 创建参数 */ export interface PCCreateParams { + resource_uid: string + asset_id: number pc_identity?: string name: string host: string @@ -68,21 +53,11 @@ export interface PCCreateParams { server_type?: string tags?: string location?: string - remote_access?: string - remote_port?: number - agent_config?: string - status?: string - last_check_time?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string } /** PC 更新参数 */ export interface PCUpdateParams { id: number - pc_identity?: string name?: string host?: string ip_address?: string @@ -93,15 +68,6 @@ export interface PCUpdateParams { server_type?: string tags?: string location?: string - remote_access?: string - remote_port?: number - agent_config?: string - status?: string - last_check_time?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string } /** 获取PC列表(分页) */ @@ -123,8 +89,3 @@ export const createPC = (data: PCCreateParams) => { export const updatePC = (id: number, data: Omit) => { return request.put<{ message: string }>(`/DC-Control/v1/pcs/${id}`, data) } - -/** 删除PC */ -export const deletePC = (id: number) => { - return request.delete<{ message: string }>(`/DC-Control/v1/pcs/${id}`) -} diff --git a/src/api/ops/platformRuntime.ts b/src/api/ops/platformRuntime.ts new file mode 100644 index 0000000..fd14df9 --- /dev/null +++ b/src/api/ops/platformRuntime.ts @@ -0,0 +1,193 @@ +import { isAxiosError } from 'axios' +import { request } from '@/api/request' + +export interface RuntimeCheck { + name: string + required: boolean + ready: boolean + message?: string +} + +export interface RuntimeWorker { + name: string + running: boolean + queue_depth?: number + last_error?: string + last_error_at?: string + last_succeeded_at?: string +} + +export interface RuntimeScheduler { + running: boolean + last_error?: string +} + +export interface RuntimeDisk { + path: string + total_bytes: number + used_bytes: number + free_bytes: number + used_percent: number + minimum_free_bytes?: number + level: 'normal' | 'warning' | 'critical' +} + +export interface RuntimeStorage { + provider: 'local' | 'aliyun' | string + container: string + writable: boolean +} + +export interface NotificationQueue { + pending: number + retry_scheduled: number + uncertain: number + failed: number + backlog: number + oldest_pending?: string +} + +export interface PlatformServiceStatus { + key: string + name: string + reachable: boolean + ready: boolean + version: string + checked_at?: string + checks: RuntimeCheck[] + workers: RuntimeWorker[] + scheduler?: RuntimeScheduler + disk?: RuntimeDisk + storage?: RuntimeStorage + notification_queue?: NotificationQueue + error?: string +} + +interface ServiceSpec { + key: string + name: string + path: string +} + +const services: ServiceSpec[] = [ + { key: 'rbac2', name: '权限中心', path: '/rbac2/v1/runtime/status' }, + { 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' }, + { key: 'kb', name: '知识库', path: '/Kb/v1/runtime/status' }, + { key: 'mgt', name: '大屏管理', path: '/Mgt/v1/runtime/status' }, + { key: 'visual', name: '可视化服务', path: '/Visual/v1/runtime/status' }, + { key: 'files', name: '文件服务', path: '/Files/v1/runtime/status' }, +] + +const asRecord = (value: unknown): Record => (value && typeof value === 'object' ? (value as Record) : {}) + +const optionalString = (value: unknown): string | undefined => (typeof value === 'string' && value.length > 0 ? value : undefined) + +const normalizeWorker = (value: unknown, fallbackName = ''): RuntimeWorker => { + const record = asRecord(value) + return { + name: optionalString(record.name) || fallbackName, + running: record.running === true, + queue_depth: typeof record.queue_depth === 'number' ? record.queue_depth : undefined, + last_error: optionalString(record.last_error), + last_error_at: optionalString(record.last_error_at), + last_succeeded_at: optionalString(record.last_succeeded_at) || optionalString(record.last_success), + } +} + +const normalizeWorkers = (value: unknown): RuntimeWorker[] => { + if (Array.isArray(value)) return value.map((item) => normalizeWorker(item)) + const record = asRecord(value) + return Object.entries(record).map(([name, item]) => normalizeWorker(item, name)) +} + +const normalizeDisk = (value: unknown): RuntimeDisk | undefined => { + const record = asRecord(value) + const level = record.level + if (!['normal', 'warning', 'critical'].includes(level)) return undefined + if (typeof record.path !== 'string') return undefined + const values = [record.total_bytes, record.used_bytes, record.free_bytes, record.used_percent] + if (!values.every((item) => typeof item === 'number' && Number.isFinite(item))) return undefined + return record as RuntimeDisk +} + +const normalizeStorage = (value: unknown): RuntimeStorage | undefined => { + const record = asRecord(value) + if (typeof record.provider !== 'string' || typeof record.container !== 'string' || typeof record.writable !== 'boolean') { + return undefined + } + return record as RuntimeStorage +} + +const normalizeNotificationQueue = (value: unknown): NotificationQueue | undefined => { + const record = asRecord(value) + const fields = ['pending', 'retry_scheduled', 'uncertain', 'failed', 'backlog'] as const + if (!fields.every((field) => typeof record[field] === 'number' && Number.isFinite(record[field]))) return undefined + return { + pending: record.pending, + retry_scheduled: record.retry_scheduled, + uncertain: record.uncertain, + failed: record.failed, + backlog: record.backlog, + oldest_pending: optionalString(record.oldest_pending), + } +} + +const isRuntimePayload = (payload: unknown): boolean => { + const envelope = asRecord(payload) + const readiness = asRecord(envelope.readiness || envelope) + return typeof readiness.ready === 'boolean' +} + +const normalizePayload = (spec: ServiceSpec, payload: unknown): PlatformServiceStatus => { + const envelope = asRecord(payload) + const readiness = asRecord(envelope.readiness || envelope) + const workers = envelope.workers && envelope.readiness ? envelope.workers : readiness.workers + return { + key: spec.key, + name: spec.name, + reachable: true, + ready: readiness.ready === true, + version: typeof readiness.version === 'string' ? readiness.version : 'unknown', + checked_at: typeof readiness.checked_at === 'string' ? readiness.checked_at : undefined, + checks: Array.isArray(readiness.checks) ? (readiness.checks as RuntimeCheck[]) : [], + workers: normalizeWorkers(workers), + scheduler: readiness.scheduler ? (readiness.scheduler as RuntimeScheduler) : undefined, + disk: normalizeDisk(envelope.disk || readiness.disk), + storage: normalizeStorage(envelope.storage || readiness.storage), + notification_queue: normalizeNotificationQueue(envelope.notification_queue), + } +} + +const fetchServiceStatus = async (spec: ServiceSpec): Promise => { + try { + const payload = await request.get(spec.path) + return normalizePayload(spec, payload) + } catch (error) { + if (isAxiosError(error) && error.response?.data && isRuntimePayload(error.response.data)) { + return normalizePayload(spec, error.response.data) + } + return { + key: spec.key, + name: spec.name, + reachable: false, + ready: false, + version: 'unknown', + checks: [], + workers: [], + error: + isAxiosError(error) && error.response?.status + ? `网关返回 ${error.response.status}` + : error instanceof Error + ? error.message + : '服务无法连接', + } + } +} + +export const fetchPlatformRuntime = (): Promise => Promise.all(services.map(fetchServiceStatus)) diff --git a/src/api/ops/rbac2.ts b/src/api/ops/rbac2.ts index 6247b1b..3c9297d 100644 --- a/src/api/ops/rbac2.ts +++ b/src/api/ops/rbac2.ts @@ -9,7 +9,12 @@ export interface UserItem { username?: string email?: string phone?: string - status?: string + status?: number + department_id?: number + position_id?: number + department?: { id: number; name: string } + position?: { id: number; name: string } + roles?: { id: number; name: string }[] created_at?: string updated_at?: string } @@ -30,3 +35,52 @@ export interface UserListResponse { export const fetchUserList = async (data: any) => { return request.post('/rbac2/v1/user/list', data, { needWorkspace: true }) } + +export interface RoleItem { + id: number + name: string + description?: string + data_scope: 'all' | 'department' + status?: number + users?: UserItem[] +} + +export const fetchRoleList = (data: { page?: number; size?: number; keyword?: string; status?: number }) => + request.post('/rbac2/v1/role/fetch', data, { needWorkspace: true }) + +export const fetchRoleUsers = (roleId: number) => request.post('/rbac2/v1/role/user', { id: roleId }, { needWorkspace: true }) + +export interface DepartmentItem { + id: number + name: string + parent_id?: number + leader_id?: number + status?: number +} + +export interface PositionItem { + id: number + code: string + name: string + description?: string + status?: number +} + +export const fetchDepartmentList = (data: { page?: number; size?: number; keyword?: string }) => + request.post('/rbac2/v1/dpt/fetch', data, { needWorkspace: true }) +export const createDepartment = (data: Partial) => request.post('/rbac2/v1/dpt/create', data, { needWorkspace: true }) +export const updateDepartment = (data: Partial) => request.post('/rbac2/v1/dpt/modify', data, { needWorkspace: true }) +export const deleteDepartment = (id: number) => request.post('/rbac2/v1/dpt/del', { id }, { needWorkspace: true }) + +export const fetchPositionList = (data: { page?: number; size?: number; keyword?: string }) => + request.post('/rbac2/v1/position/fetch', data, { needWorkspace: true }) +export const createPosition = (data: Partial) => request.post('/rbac2/v1/position/create', data, { needWorkspace: true }) +export const updatePosition = (data: Partial) => request.post('/rbac2/v1/position/modify', data, { needWorkspace: true }) +export const deletePosition = (id: number) => request.post('/rbac2/v1/position/del', { id }, { needWorkspace: true }) + +export const createRole = (data: Partial) => request.post('/rbac2/v1/role/create', data, { needWorkspace: true }) +export const updateRole = (data: Partial) => request.post('/rbac2/v1/role/modify', data, { needWorkspace: true }) +export const deleteRole = (id: number) => request.post('/rbac2/v1/role/del', { id }, { needWorkspace: true }) +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 }) diff --git a/src/api/ops/report.ts b/src/api/ops/report.ts index 7bd4a64..385cd2d 100644 --- a/src/api/ops/report.ts +++ b/src/api/ops/report.ts @@ -19,6 +19,7 @@ export enum ReportType { SERVER = 'server', NETWORK_DEVICE = 'network_device', OPERATIONS = 'operations', + DESIGNER = 'designer', EVIDENCE = 'evidence', } @@ -48,6 +49,92 @@ export interface ReportRecord { finished_at?: string created_at: string updated_at: string + definition_id?: number + definition_version?: number + period_type?: 'daily' | 'monthly' +} + +export interface ReportDatasetField { + code: string + name: string + type: 'string' | 'number' | 'datetime' | 'boolean' + operators: string[] + aggregations: string[] +} + +export interface ReportDataset { + code: string + name: string + category: string + description: string + fields: ReportDatasetField[] + temporal: boolean + enabled: boolean + version: number +} + +export interface ReportSelectedField { + field: string + aggregation: string +} + +export interface ReportFilter { + field: string + operator: string + value: string | number | boolean | Array +} + +export interface ReportLayoutBlock { + type: 'table' | 'bar' | 'line' | 'stat' + title: string + x_field?: string + y_fields?: string[] +} + +export interface ReportDefinitionInput { + code: string + name: string + description: string + dataset_code: string + fields: ReportSelectedField[] + filters: ReportFilter[] + layout: { blocks: ReportLayoutBlock[] } + row_limit: number + enabled: boolean +} + +export interface ReportDefinition extends ReportDefinitionInput { + id: number + version: number + created_at: string + updated_at: string +} + +export interface ReportSchedule { + id: number + definition_id: number + period_type: 'daily' | 'monthly' + timezone: string + hour: number + minute: number + enabled: boolean + next_run_at?: string + last_run_at?: string + definition?: ReportDefinition +} + +export interface CapacityThreshold { + id: number + resource_category: string + metric_type: string + metric_code: string + display_name: string + idle_threshold: number + risk_threshold: number + shortage_threshold: number + min_samples: number + forecast_hours: number + enabled: boolean } export interface ReportListParams { @@ -242,6 +329,41 @@ export const fetchReportMetricsAvailable = (params: { export const fetchReportMetricsRegistry = (params: { data_source: string }) => request.get>('/DC-Control/v1/reports/metrics/registry', { params }) +export const fetchReportDatasets = () => request.get>('/DC-Control/v1/reports/datasets') + +export const fetchReportDefinitions = () => request.get>('/DC-Control/v1/reports/definitions') + +export const createReportDefinition = (data: ReportDefinitionInput) => + request.post>('/DC-Control/v1/reports/definitions', data) + +export const updateReportDefinition = (id: number, data: ReportDefinitionInput) => + request.put>(`/DC-Control/v1/reports/definitions/${id}`, data) + +export const deleteReportDefinition = (id: number) => request.delete(`/DC-Control/v1/reports/definitions/${id}`) + +export const generateReportDefinition = ( + id: number, + data: { period_type: 'daily' | 'monthly'; start_time: string; end_time: string } +) => request.post>(`/DC-Control/v1/reports/definitions/${id}/generate`, data) + +export const fetchReportSchedules = () => request.get>('/DC-Control/v1/reports/schedules') + +export const saveReportSchedule = (data: Omit) => + request.post>('/DC-Control/v1/reports/schedules', data) + +export const deleteReportSchedule = (id: number) => request.delete(`/DC-Control/v1/reports/schedules/${id}`) + +export const fetchCapacityThresholds = () => + request.get>('/DC-Control/v1/reports/capacity-thresholds') + +export const createCapacityThreshold = (data: Omit) => + request.post>('/DC-Control/v1/reports/capacity-thresholds', data) + +export const updateCapacityThreshold = (id: number, data: Omit) => + request.put>(`/DC-Control/v1/reports/capacity-thresholds/${id}`, data) + +export const deleteCapacityThreshold = (id: number) => request.delete(`/DC-Control/v1/reports/capacity-thresholds/${id}`) + /** 查看报表内容 */ export const fetchReportContent = (id: number) => request.get>>(`/DC-Control/v1/reports/${id}/content`) diff --git a/src/api/ops/room-device.ts b/src/api/ops/room-device.ts index 5ccae70..3a0438a 100644 --- a/src/api/ops/room-device.ts +++ b/src/api/ops/room-device.ts @@ -7,33 +7,16 @@ export interface RoomDeviceItem { updated_at: string deleted_at: string | null service_identity: string + resource_uid: string + asset_id: number name: string description: string room_id: number device_code?: string device_category: string type?: string - status_url?: string - agent_config: string - collect_method: 'api' | 'snmp' - snmp_target: string - snmp_port: number - snmp_version: 'v2c' | 'v3' - snmp_community: string - snmp_v3_security_level: string - snmp_v3_security_name: string - snmp_v3_auth_protocol: string - snmp_v3_auth_password: string - snmp_v3_priv_protocol: string - snmp_v3_priv_password: string - snmp_v3_context_name: string - snmp_timeout_ms: number - snmp_retries: number - snmp_oids: string + management_address?: string enabled: boolean - collect_on: boolean - collect_interval: number - collect_last_result: string status?: string status_code?: number status_message?: string @@ -66,82 +49,22 @@ export interface RoomDeviceListParams { /** 机房设备创建数据 */ export interface RoomDeviceCreateData { + resource_uid: string + asset_id: number service_identity?: string name: string description?: string room_id: number + device_code?: string device_category: string - agent_config?: string - collect_method?: 'api' | 'snmp' - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string + type?: string + management_address?: string enabled?: boolean - collect_on?: boolean - collect_interval?: number policy_ids?: number[] } /** 机房设备更新数据 */ -export interface RoomDeviceUpdateData { - name?: string - description?: string - room_id?: number - device_category?: string - agent_config?: string - collect_method?: 'api' | 'snmp' - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - enabled?: boolean - collect_on?: boolean - collect_interval?: number - policy_ids?: number[] -} - -/** 机房设备采集配置数据 */ -export interface RoomDeviceCollectData { - collect_method?: 'api' | 'snmp' - agent_config?: string - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - collect_on?: boolean - collect_interval?: number -} +export type RoomDeviceUpdateData = Partial /** 指标数据项 */ export interface MetricItem { @@ -195,16 +118,6 @@ export const updateRoomDevice = (id: number, data: RoomDeviceUpdateData) => { return request.put<{ message: string }>(`/DC-Control/v1/room-devices/${id}`, data) } -/** 删除机房设备 */ -export const deleteRoomDevice = (id: number) => { - return request.delete<{ message: string }>(`/DC-Control/v1/room-devices/${id}`) -} - -/** 更新采集配置 */ -export const patchRoomDeviceCollect = (id: number, data: RoomDeviceCollectData) => { - return request.patch<{ message: string }>(`/DC-Control/v1/room-devices/${id}/collect`, data) -} - /** 查询最新指标 */ export const fetchLatestMetrics = (serviceIdentity: string) => { return request.get('/DC-Control/v1/room-devices/metrics/latest', { diff --git a/src/api/ops/security.ts b/src/api/ops/security.ts index acd620f..41e3da4 100644 --- a/src/api/ops/security.ts +++ b/src/api/ops/security.ts @@ -1,43 +1,25 @@ import { request } from '@/api/request' +import type { DeviceInterfaceSnapshot, WirelessAccessPoint } from './network-device' /** 安全设备服务项 */ export interface SecurityServiceItem { id: number + resource_uid: string + asset_id: number created_at: string updated_at: string deleted_at: string | null service_identity: string - oid: string - server_identity: string name: string category: string type: string + host: string + vendor: string + model: string + location: string description: string enabled: boolean - interval: number - extra: string tags: string - status_url: string - agent_config: string - collect_method: 'api' | 'snmp' - snmp_target: string - snmp_port: number - snmp_version: 'v2c' | 'v3' - snmp_community: string - snmp_v3_security_level: string - snmp_v3_security_name: string - snmp_v3_auth_protocol: string - snmp_v3_auth_password: string - snmp_v3_priv_protocol: string - snmp_v3_priv_password: string - snmp_v3_context_name: string - snmp_timeout_ms: number - snmp_retries: number - snmp_oids: string - collect_on: boolean - collect_args: string - collect_interval: number - collect_last_result: string status: string status_code: number status_message: string @@ -47,6 +29,9 @@ export interface SecurityServiceItem { last_offline_time: string | null continuous_errors: number uptime: number + product_version: string + adapter_status: 'verified' | 'unverified' + last_data_time: string | null } /** 安全设备服务列表数据 */ @@ -67,45 +52,32 @@ export interface SecurityServiceListParams { /** 创建/更新安全设备服务请求参数 */ export interface SecurityServiceFormData { + resource_uid?: string + asset_id?: number service_identity?: string - oid?: string - server_identity?: string name?: string category?: string type?: string + host?: string + vendor?: string + model?: string + location?: string description?: string enabled?: boolean - interval?: number - extra?: string tags?: string - status_url?: string - agent_config?: string - collect_method?: 'api' | 'snmp' - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number policy_ids?: number[] } /** 安全设备指标项 */ export interface SecurityMetric { + timestamp?: string + service_identity?: string + type?: string metric_name: string metric_value: number metric_unit: string + series_key?: string + labels?: string } /** 安全设备最新指标数据 */ @@ -136,11 +108,6 @@ export const updateSecurityService = (id: number, data: Partial(`/DC-Control/v1/security/${id}`, data) } -/** 删除安全设备服务 */ -export const deleteSecurityService = (id: number) => { - return request.delete<{ code: number; message: string; details: { message: string } }>(`/DC-Control/v1/security/${id}`) -} - /** 获取最新时序指标 */ export const fetchSecurityMetricsLatest = (serviceIdentity: string) => { return request.get<{ code: number; message: string; details: SecurityMetricsLatestData }>('/DC-Control/v1/security/metrics/latest', { @@ -148,33 +115,35 @@ export const fetchSecurityMetricsLatest = (serviceIdentity: string) => { }) } -/** 采集配置补丁参数 */ -export interface SecurityServicePatchData { - collect_method?: 'api' | 'snmp' - agent_config?: string - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - collect_on?: boolean - collect_interval?: number +export interface SecurityMetricsTimeseriesQuery { + start_time?: string + end_time?: string + metric_name?: string } -/** 采集配置补丁 */ -export const patchSecurityServiceCollect = (id: number, data: SecurityServicePatchData) => { - return request.patch<{ code: number; message: string; details: { message: string } }>(`/DC-Control/v1/security/${id}/collect`, data) +export interface SecurityMetricsTimeseriesData { + service_identity: string + start_time: string + end_time: string + count: number + metrics: SecurityMetric[] } +/** 查询安全设备指标原始时序点 */ +export const fetchSecurityMetricsTimeseries = (serviceIdentity: string, params?: SecurityMetricsTimeseriesQuery) => { + const path = encodeURIComponent(serviceIdentity) + return request.get<{ code: number; message: string; details: SecurityMetricsTimeseriesData }>( + `/DC-Control/v1/security/metrics/timeseries/${path}`, + { params } + ) +} + +export const fetchSecurityInterfaces = (id: number) => + request.get<{ code: number; message: string; details: DeviceInterfaceSnapshot[] }>(`/DC-Control/v1/security/${id}/interfaces`) + +export const fetchSecurityAccessPoints = (id: number) => + request.get<{ code: number; message: string; details: WirelessAccessPoint[] }>(`/DC-Control/v1/security/${id}/access-points`) + /** 告警聚合查询参数 */ export interface SecurityMetricsAggregateParams { service_identity: string @@ -210,6 +179,12 @@ export const SECURITY_TYPE_MAP: Record = { ids: 'IDS', ips: 'IPS', vpn: 'VPN', + threat_awareness: '态势感知', + bastion_host: '堡垒机', + log_audit: '日志审计', + database_audit: '数据库审计', + internet_behavior: '上网行为管理', + security_gap: '网闸', other: '其他', } @@ -220,6 +195,12 @@ export const SECURITY_TYPE_OPTIONS = [ { label: 'IDS', value: 'ids' }, { label: 'IPS', value: 'ips' }, { label: 'VPN', value: 'vpn' }, + { label: '态势感知', value: 'threat_awareness' }, + { label: '堡垒机', value: 'bastion_host' }, + { label: '日志审计', value: 'log_audit' }, + { label: '数据库审计', value: 'database_audit' }, + { label: '上网行为管理', value: 'internet_behavior' }, + { label: '网闸', value: 'security_gap' }, { label: '其他', value: 'other' }, ] diff --git a/src/api/ops/server.ts b/src/api/ops/server.ts index a910c61..7b3d486 100644 --- a/src/api/ops/server.ts +++ b/src/api/ops/server.ts @@ -3,6 +3,7 @@ import { request } from '@/api/request' /** 服务器类型 */ export interface ServerItem { id: number + resource_uid: string created_at: string updated_at: string deleted_at: string | null @@ -18,17 +19,10 @@ export interface ServerItem { tags: string location: string asset_id?: number - remote_access: string - remote_port: number - agent_config: string + monitored_processes: string[] + monitored_ports: number[] status: string last_check_time: string - collect_on: boolean - collect_args: string - collect_interval: number - collect_last_result: string - is_ip_scan_server: boolean - ip_scan_port: number } /** 服务器列表响应 */ @@ -44,12 +38,11 @@ export interface ServerListParams { page?: number size?: number keyword?: string - collect_on?: boolean - is_ip_scan_server?: boolean } /** 创建/更新服务器请求参数 */ export interface ServerFormData { + resource_uid?: string server_identity?: string name?: string host?: string @@ -62,17 +55,8 @@ export interface ServerFormData { tags?: string location?: string asset_id?: number - remote_access?: string - remote_port?: number - agent_config?: string - status?: string - last_check_time?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string - is_ip_scan_server?: boolean - ip_scan_port?: number + monitored_processes?: string[] + monitored_ports?: number[] } /** 获取服务器列表(分页) */ @@ -95,11 +79,6 @@ export const updateServer = (id: number, data: Partial) => { return request.put<{ message: string }>(`/DC-Control/v1/servers/${id}`, data) } -/** 删除服务器 */ -export const deleteServer = (id: number) => { - return request.delete<{ message: string }>(`/DC-Control/v1/servers/${id}`) -} - /** 主机最新指标摘要(统计卡片,与 dc-control /servers/metrics/summary 一致) */ export interface HostMetricsUseStat { total_bytes: number @@ -138,6 +117,33 @@ export interface HostMetricsSummary { cpu?: HostMetricsCpuCard } +export interface HostMetricRow { + timestamp: string + server_identity: string + type: string + sub_type: string + metric_name: string + metric_value: number + metric_unit: string +} + +export interface HostLatestMetricsPayload { + server_identity: string + timestamp?: string + has_data: boolean + rows: HostMetricRow[] +} + +export interface HardwareSnapshotPayload { + has_data: boolean + protocol?: string + collected_at?: string + 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 HostNetworkTrafficPoint { time: string recv_mbps: number @@ -162,12 +168,15 @@ export const fetchServerMetricsSummary = (serverIdentity: string) => { }) } -/** 立即拉取一次主机指标并返回最新统计卡片 */ -export const collectServerMetricsNow = (serverIdentity: string) => { - return request.post<{ code: number; details?: HostMetricsSummary; message?: string }>('/DC-Control/v1/servers/metrics/collect', { - server_identity: serverIdentity, - }) -} +export const fetchServerLatestMetrics = (serverIdentity: string) => + request.get<{ code: number; details?: HostLatestMetricsPayload; message?: string }>('/DC-Control/v1/servers/metrics/latest', { + params: { server_identity: serverIdentity }, + }) + +export const fetchServerHardwareSnapshot = (serverIdentity: string) => + request.get<{ code: number; details?: HardwareSnapshotPayload; message?: string }>('/DC-Control/v1/servers/metrics/hardware', { + params: { server_identity: serverIdentity }, + }) /** 近 N 小时网络收/发速率(Mbps,相邻采样字节差分) */ export const fetchServerNetworkTraffic = (serverIdentity: string, hours = 6) => { diff --git a/src/api/ops/storage.ts b/src/api/ops/storage.ts index 9600949..7876f97 100644 --- a/src/api/ops/storage.ts +++ b/src/api/ops/storage.ts @@ -3,41 +3,22 @@ import { request } from '@/api/request' /** 存储设备服务项 */ export interface StorageItem { id: number + resource_uid: string + asset_id: number created_at: string updated_at: string deleted_at: string | null service_identity: string - oid: string - server_identity: string name: string category: string type: string + host: string + vendor: string + model: string + location: string description: string enabled: boolean - interval: number - extra: string tags: string - status_url: string - agent_config: string - collect_method: 'api' | 'snmp' - snmp_target: string - snmp_port: number - snmp_version: 'v2c' | 'v3' - snmp_community: string - snmp_v3_security_level: string - snmp_v3_security_name: string - snmp_v3_auth_protocol: string - snmp_v3_auth_password: string - snmp_v3_priv_protocol: string - snmp_v3_priv_password: string - snmp_v3_context_name: string - snmp_timeout_ms: number - snmp_retries: number - snmp_oids: string - collect_on: boolean - collect_args: string - collect_interval: number - collect_last_result: string status: string status_code: number status_message: string @@ -47,6 +28,9 @@ export interface StorageItem { last_offline_time: string | null continuous_errors: number uptime: number + product_version: string + adapter_status: 'verified' | 'unverified' + last_data_time: string | null } /** 存储设备列表响应 */ @@ -67,86 +51,53 @@ export interface StorageListParams { /** 创建存储设备请求参数 */ export interface StorageCreateData { + resource_uid?: string + asset_id?: number service_identity?: string name: string category?: string type: string - oid?: string - server_identity?: string + host?: string + vendor?: string + model?: string + location?: string description?: string enabled?: boolean - interval?: number - extra?: string tags?: string - status_url?: string - agent_config?: string - collect_method?: 'api' | 'snmp' - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number policy_ids?: number[] } /** 更新存储设备请求参数 */ export interface StorageUpdateData { + resource_uid?: string + asset_id?: number service_identity?: string - oid?: string - server_identity?: string name?: string category?: string type?: string + host?: string + vendor?: string + model?: string + location?: string description?: string enabled?: boolean - interval?: number - extra?: string tags?: string - status_url?: string - agent_config?: string - collect_method?: 'api' | 'snmp' - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - collect_on?: boolean - collect_args?: string - collect_interval?: number - collect_last_result?: string - status?: string - status_code?: number - status_message?: string - response_time?: number - last_check_time?: string - last_online_time?: string | null - last_offline_time?: string | null - continuous_errors?: number - uptime?: number policy_ids?: number[] } +export interface StorageComponent { + id: number + resource_uid: string + component_type: string + component_identity: string + name: string + status: string + capacity_bytes: number + used_bytes: number + attributes: string + last_seen_at: string +} + /** 指标项 */ export interface StorageMetricItem { metric_name: string @@ -182,10 +133,8 @@ export const updateStorage = (id: number, data: StorageUpdateData) => { return request.put<{ message: string }>(`/DC-Control/v1/storage/${id}`, data) } -/** 删除存储设备服务 */ -export const deleteStorage = (id: number) => { - return request.delete<{ message: string }>(`/DC-Control/v1/storage/${id}`) -} +export const fetchStorageComponents = (id: number) => + request.get(`/DC-Control/v1/storage/${id}/components`) /** 查询最新一批时序指标 */ export const fetchStorageMetricsLatest = (serviceIdentity: string) => { @@ -199,27 +148,21 @@ export interface StorageMonitorOptionItem { service_identity: string name: string type: string - server_identity: string + host: string + vendor: string + model: string status: string } -/** 监控大屏:汇总 current.host(与后端 storage_monitor 一致) */ -export interface StorageMonitorSummaryHost { - has_data: boolean - cpu_usage_percent?: number | null - memory_usage_percent?: number | null - swap_usage_percent?: number | null - disk_root_used_percent?: number | null - disk_root_avail_percent?: number | null - host_server_identity?: string - host_summary_timestamp?: string | null -} - export interface StorageMonitorSummaryCurrent { service_identity: string - controller_status: string - server_identity: string - host?: StorageMonitorSummaryHost | null + resource_uid: string + status: string + adapter_status: string + product_version: string + last_data_time?: string | null + component_count: number + abnormal_component_count: number } export interface StorageMonitorSummaryPayload { @@ -230,7 +173,7 @@ export interface StorageMonitorSummaryPayload { current?: StorageMonitorSummaryCurrent | null } -export const fetchStorageMonitorList = (params?: { keyword?: string; limit?: number }) => { +export const fetchStorageMonitorList = (params?: { keyword?: string }) => { return request.get<{ code: number details?: { data: StorageMonitorOptionItem[]; count: number } @@ -330,30 +273,3 @@ export const fetchStorageMetricsTimeseries = (serviceIdentity: string, params?: params, }) } - -/** 采集配置补丁 */ -export interface StoragePatchData { - collect_method?: 'api' | 'snmp' - agent_config?: string - snmp_target?: string - snmp_port?: number - snmp_version?: 'v2c' | 'v3' - snmp_community?: string - snmp_v3_security_level?: string - snmp_v3_security_name?: string - snmp_v3_auth_protocol?: string - snmp_v3_auth_password?: string - snmp_v3_priv_protocol?: string - snmp_v3_priv_password?: string - snmp_v3_context_name?: string - snmp_timeout_ms?: number - snmp_retries?: number - snmp_oids?: string - collect_on?: boolean - collect_interval?: number -} - -/** 采集配置补丁更新 */ -export const patchStorage = (id: number, data: StoragePatchData) => { - return request.patch<{ message: string }>(`/DC-Control/v1/storage/${id}/collect`, data) -} diff --git a/src/api/ops/systemSettings.ts b/src/api/ops/systemSettings.ts new file mode 100644 index 0000000..e7fbafb --- /dev/null +++ b/src/api/ops/systemSettings.ts @@ -0,0 +1,67 @@ +import { request } from '@/api/request' + +const BASE = '/Mgt/v1/settings' + +export interface DataDictionaryItem { + id?: number + category_code: string + item_code: string + label: string + value: string + sort: number + enabled: boolean + description: string +} + +export interface SystemParameter { + id: number + code: string + name: string + value: number + unit: string + minimum: number + maximum: number + description: string +} + +export interface CommonPhrase { + id?: number + category_code: string + content: string + sort: number + enabled: boolean +} + +export type PublicSystemParameters = Record + +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}`) + +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 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) +export const updateCommonPhrase = (id: number, data: CommonPhrase) => request.put(`${BASE}/phrases/${id}`, data) +export const deleteCommonPhrase = (id: number) => request.delete(`${BASE}/phrases/${id}`) + +export function unwrapSystemSettings(response: any): T { + return (response?.details ?? response?.data) as T +} + +export async function fetchDictionaryOptions(categoryCode: string): Promise> { + const response = await fetchDictionaries({ category_code: categoryCode, enabled: true }) + const rows = unwrapSystemSettings(response) || [] + return rows.map((item) => ({ label: item.label, value: item.value })) +} + +export async function fetchEnabledCommonPhrases(categoryCode: string): Promise { + const response = await fetchCommonPhrases({ page: 1, page_size: 100, category_code: categoryCode }) + const payload = unwrapSystemSettings<{ data?: CommonPhrase[] }>(response) || {} + return (payload.data || []).filter((item) => item.enabled) +} diff --git a/src/api/ops/three-machine-room.ts b/src/api/ops/three-machine-room.ts index fee1bc4..2143a64 100644 --- a/src/api/ops/three-machine-room.ts +++ b/src/api/ops/three-machine-room.ts @@ -33,8 +33,8 @@ export interface ThreeMachineRoomSize { depth: number } -/** 设备绑定的监控资源。 */ -export interface ThreeMachineRoomBinding { +/** 统一监控资源身份。 */ +export interface ThreeMachineRoomResourceIdentity { resource_uid: string resource_category?: string service_identity?: string @@ -52,7 +52,7 @@ export interface ThreeMachineRoomMetric { } /** 设备资源运行数据。 */ -export interface ThreeMachineRoomResource extends ThreeMachineRoomBinding { +export interface ThreeMachineRoomResource extends ThreeMachineRoomResourceIdentity { status?: string metrics_at?: string metrics?: ThreeMachineRoomMetric[] @@ -89,6 +89,7 @@ export interface ThreeMachineRoomUnit { /** 机柜中的资产设备。 */ export interface ThreeMachineRoomDevice { asset_id: number + resource_uid: string asset_code?: string asset_name?: string category_id?: number | null @@ -102,7 +103,6 @@ export interface ThreeMachineRoomDevice { power_consumption?: number transform?: ThreeMachineRoomTransform size?: ThreeMachineRoomSize - bindings?: ThreeMachineRoomBinding[] } /** 3D 场景机柜。 */ diff --git a/src/api/ops/traffic.ts b/src/api/ops/traffic.ts index e38f806..4f1ca00 100644 --- a/src/api/ops/traffic.ts +++ b/src/api/ops/traffic.ts @@ -84,6 +84,35 @@ export interface TrafficDashboardResponse { }> } +export interface NetworkObservation { + id: number + created_at: string + probe_id: string + source_kind: string + sensor_interface?: string + window_start: string + window_end: string + source_ip: string + destination_ip: string + source_port?: number + destination_port?: number + transport: string + application?: string + bytes: number + packets: number + connections: number + latency_ms?: number + http_method?: string + http_host?: string + http_path?: string + http_status_code?: number + tls_version?: string + tls_server_name?: string + tls_cert_subject?: string + tls_cert_issuer?: string + tls_cert_not_after?: string +} + export const fetchTrafficList = (params?: Record) => request.get<{ code: number; details?: TrafficListResponse; message?: string }>('/DC-Control/v1/traffic', { params, @@ -106,3 +135,9 @@ export const fetchRealtimeTraffic = (params?: Record) => export const fetchTrafficDashboard = (params?: Record) => request.get<{ code: number; details?: TrafficDashboardResponse; message?: string }>('/DC-Control/v1/traffic/dashboard', { params }) + +export const fetchNetworkObservations = (params?: Record) => + request.get<{ code: number; details?: { total: number; page: number; page_size: number; data: NetworkObservation[] }; message?: string }>( + '/DC-Control/v1/traffic/network-observations', + { params } + ) diff --git a/src/api/ops/url-device.ts b/src/api/ops/url-device.ts deleted file mode 100644 index 4821611..0000000 --- a/src/api/ops/url-device.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { request } from '@/api/request' - -export interface UrlDeviceItem { - id: number - created_at: string - updated_at: string - deleted_at: string | null - service_identity: string - name: string - description: string - target_url: string - method: string - enabled: boolean - interval: number - extra: string - tags: string - collect_on: boolean - collect_interval: number - collect_last_result: string - status: string - status_code: number - response_time: number - last_check_time: string - last_online_time: string | null - last_offline_time: string | null - continuous_errors: number -} - -export interface UrlDeviceListResponse { - total: number - page: number - page_size: number - data: UrlDeviceItem[] -} - -export interface UrlDeviceListParams { - page?: number - size?: number - keyword?: string - enabled?: boolean -} - -export interface UrlDeviceCreateData { - service_identity?: string - name: string - description?: string - target_url: string - method?: string - enabled?: boolean - interval?: number - extra?: string - tags?: string - collect_on?: boolean - collect_interval?: number - collect_last_result?: string -} - -export interface UrlDeviceUpdateData { - name?: string - description?: string - target_url?: string - method?: string - enabled?: boolean - interval?: number - extra?: string - tags?: string - collect_on?: boolean - collect_interval?: number - collect_last_result?: string - status?: string - status_code?: number - response_time?: number - last_check_time?: string - last_online_time?: string | null - last_offline_time?: string | null - continuous_errors?: number -} - -export interface UrlDeviceCollectData { - collect_on?: boolean - collect_interval?: number - collect_last_result?: string -} - -export const fetchUrlDeviceList = (params?: UrlDeviceListParams) => { - return request.get('/DC-Control/v1/url-devices', { params }) -} - -export const fetchUrlDeviceDetail = (id: number) => { - return request.get(`/DC-Control/v1/url-devices/${id}`) -} - -export const createUrlDevice = (data: UrlDeviceCreateData) => { - return request.post<{ message: string; id: number }>('/DC-Control/v1/url-devices', data) -} - -export const updateUrlDevice = (id: number, data: UrlDeviceUpdateData) => { - return request.put<{ message: string }>(`/DC-Control/v1/url-devices/${id}`, data) -} - -export const patchUrlDeviceCollect = (id: number, data: UrlDeviceCollectData) => { - return request.patch<{ message: string }>(`/DC-Control/v1/url-devices/${id}/collect`, data) -} - -export const deleteUrlDevice = (id: number) => { - return request.delete<{ message: string }>(`/DC-Control/v1/url-devices/${id}`) -} - -export interface UrlDeviceStatsOverview { - total: number - online: number - today_alert_count: number - avg_response_time_ms: number -} - -export interface ResponseTimePoint { - hour: string - avg_response_time_ms: number -} - -export interface UrlDeviceResponseTimeTrend { - points: ResponseTimePoint[] -} - -export interface StatusDistributionItem { - status: string - count: number -} - -export interface UrlDeviceStatusDistribution { - total: number - by_status: StatusDistributionItem[] -} - -export const fetchUrlDeviceStatsOverview = () => { - return request.get('/DC-Control/v1/url-devices/stats/overview') -} - -export const fetchUrlDeviceResponseTimeTrend = () => { - return request.get('/DC-Control/v1/url-devices/stats/response-time-trend') -} - -export const fetchUrlDeviceStatusDistribution = () => { - return request.get('/DC-Control/v1/url-devices/stats/status-distribution') -} diff --git a/src/api/types.ts b/src/api/types.ts index 72a6409..75bc78f 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -28,6 +28,12 @@ export interface UserItem { status?: number role_id?: number role_name?: string + department_id?: number + position_id?: number + role_ids?: number[] + department?: { id: number; name: string } + position?: { id: number; name: string } + roles?: { id: number; name: string }[] created_at?: string updated_at?: string } diff --git a/src/components/navbar/index.vue b/src/components/navbar/index.vue index 70dfcdf..34008d7 100644 --- a/src/components/navbar/index.vue +++ b/src/components/navbar/index.vue @@ -194,6 +194,27 @@ const fetchNotificationUnreadCount = async () => { const handleMessageBoxUnreadCountChange = (count: number) => { notificationUnreadCount.value = count } +const playAlertSound = (message: MessageRecord) => { + if (!message.soundEnabled || !['critical', 'major'].includes(message.severity || '')) return + const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext + if (!AudioContextClass) return + const context = new AudioContextClass() + const oscillator = context.createOscillator() + const gain = context.createGain() + oscillator.type = 'sine' + oscillator.frequency.value = message.severity === 'critical' ? 880 : 660 + gain.gain.setValueAtTime(0.16, context.currentTime) + gain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.6) + oscillator.connect(gain) + gain.connect(context.destination) + oscillator.start() + oscillator.stop(context.currentTime + 0.6) + oscillator.onended = () => context.close() +} +const handleRealtimeNotification = (message: MessageRecord) => { + playAlertSound(message) + fetchNotificationUnreadCount() +} const setPopoverVisible = () => { const event = new MouseEvent('click', { view: window, @@ -223,7 +244,7 @@ const toggleDrawerMenu = inject('toggleDrawerMenu') as () => void fetchNotificationUnreadCount() useNotificationSocket({ - onNotification: fetchNotificationUnreadCount, + onNotification: handleRealtimeNotification, onReconnectAfter: fetchNotificationUnreadCount, }) diff --git a/src/composables/systemParameters.ts b/src/composables/systemParameters.ts new file mode 100644 index 0000000..7adf73c --- /dev/null +++ b/src/composables/systemParameters.ts @@ -0,0 +1,43 @@ +import { fetchPublicSystemParameters, unwrapSystemSettings, type PublicSystemParameters } from '@/api/ops/systemSettings' + +export const SystemParameterCode = { + DeviceOfflineSeconds: 'device_offline_seconds', + PageRefreshSeconds: 'page_refresh_seconds', + AlertRecoveryWaitSeconds: 'alert_recovery_wait_seconds', + MonitoringRetentionDays: 'monitoring_retention_days', + OperationLogRetentionDays: 'operation_log_retention_days', + LoginBackgroundRotationSeconds: 'login_background_rotation_seconds', +} as const + +const requiredCodes = Object.values(SystemParameterCode) +let cached: PublicSystemParameters | null = null +let loading: Promise | null = null + +export async function loadSystemParameters(force = false): Promise { + if (cached && !force) return cached + if (loading && !force) return loading + loading = fetchPublicSystemParameters().then((response) => { + const values = unwrapSystemSettings(response) + if (!values || requiredCodes.some((code) => !Number.isInteger(values[code]))) { + throw new Error('系统参数未完整初始化') + } + cached = values + return values + }) + try { + return await loading + } finally { + loading = null + } +} + +export async function getSystemParameter(code: (typeof SystemParameterCode)[keyof typeof SystemParameterCode]): Promise { + const values = await loadSystemParameters() + const value = values[code] + if (!Number.isInteger(value)) throw new Error(`系统参数 ${code} 不存在`) + return value +} + +export function invalidateSystemParameters() { + cached = null +} diff --git a/src/layout/default-layout.vue b/src/layout/default-layout.vue index 370c4ef..cac735c 100644 --- a/src/layout/default-layout.vue +++ b/src/layout/default-layout.vue @@ -60,10 +60,12 @@ import TabBar from '@/components/tab-bar/index.vue' import usePermission from '@/hooks/permission' import useResponsive from '@/hooks/responsive' import { useAppStore, useUserStore } from '@/store' -import { computed, onMounted, provide, ref, watch } from 'vue' +import { buildAuthenticatedIframeUrl } from '@/utils/iframe' +import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import PageLayout from './page-layout.vue' import { getToken } from '@/utils/auth' +import { heartbeat } from '@/api/module/user' const isInit = ref(false) const appStore = useAppStore() @@ -91,24 +93,13 @@ const updateShowIframe = () => { const webUrl = route?.meta?.web_url if (isWebPage && webUrl && typeof webUrl === 'string') { - showIframe.value = true - // Add token as query parameter to the URL const token = getToken() - if (token && typeof token === 'string') { - // Parse the URL and add token parameter - try { - const url = new URL(webUrl) - url.searchParams.set('token', token) - iframeUrl.value = url.toString() - } catch (error) { - // If URL is invalid, use it as is - console.warn('Invalid URL provided for iframe:', webUrl) - iframeUrl.value = webUrl - } - } else { - // No token available, use URL as is - iframeUrl.value = webUrl + if (!token || typeof token !== 'string') { + throw new Error('当前登录凭证不存在,无法打开内嵌页面') } + + showIframe.value = true + iframeUrl.value = buildAuthenticatedIframeUrl(webUrl, token, window.location.origin) } else { showIframe.value = false iframeUrl.value = '' @@ -140,6 +131,7 @@ watch( } ) const drawerVisible = ref(false) +let heartbeatTimer: ReturnType | null = null const drawerCancel = () => { drawerVisible.value = false } @@ -148,6 +140,11 @@ provide('toggleDrawerMenu', () => { }) onMounted(() => { isInit.value = true + void heartbeat() + heartbeatTimer = window.setInterval(() => void heartbeat(), 60_000) +}) +onBeforeUnmount(() => { + if (heartbeatTimer) window.clearInterval(heartbeatTimer) }) @@ -251,6 +248,11 @@ onMounted(() => { } } + &.mobile .layout-content { + min-width: 0; + overflow-x: auto; + } + .arco-layout-sider-collapsed { .left-side { width: 50px; @@ -283,4 +285,11 @@ onMounted(() => { } } } + +@media (max-width: 991px) { + .layout .layout-content { + min-width: 0; + overflow-x: auto; + } +} diff --git a/src/router/local-menu-flat.ts b/src/router/local-menu-flat.ts index f38ca81..1cad0f7 100644 --- a/src/router/local-menu-flat.ts +++ b/src/router/local-menu-flat.ts @@ -45,7 +45,7 @@ export const localMenuFlatItems: MenuItem[] = [ type: 1, sort_key: 3, is_web_page: true, - web_url: 'https://ops2.apinb.com/view/#/project/items', + web_url: '/view/#/project/items', created_at: '2025-12-26T13:23:51.644296+08:00', }, { @@ -60,7 +60,7 @@ export const localMenuFlatItems: MenuItem[] = [ type: 1, sort_key: 4, is_web_page: true, - web_url: 'https://ops2.apinb.com/view/#/project/management', + web_url: '/view/#/project/management', created_at: '2026-01-25T10:44:15.33024+08:00', }, { @@ -349,21 +349,6 @@ export const localMenuFlatItems: MenuItem[] = [ sort_key: 17, created_at: '2025-12-26T13:23:51.938825+08:00', }, - { - id: 32, - identity: '019b591d-01f3-7698-9aad-9f9dbb3be203', - title: 'URL监控', - title_en: 'URL Monitoring', - code: 'ops:综合监控:url监控', - description: '综合监控 - URL监控', - app_id: 2, - parent_id: 23, - menu_path: '/monitor/url', - menu_icon: 'appstore', - type: 1, - sort_key: 18, - created_at: '2025-12-26T13:23:51.923432+08:00', - }, { id: 34, identity: '019b591d-0210-7e32-ad40-ebbf2f48282f', diff --git a/src/router/local-menu-items.ts b/src/router/local-menu-items.ts index 4da361b..7a313d0 100644 --- a/src/router/local-menu-items.ts +++ b/src/router/local-menu-items.ts @@ -78,7 +78,7 @@ export const localMenuItems: MenuItem[] = [ type: 1, sort_key: 2, is_web_page: true, - web_url: 'https://ops2.apinb.com/view/#/project/items', + web_url: '/view/#/project/items', created_at: '2025-12-26T13:23:51.644296+08:00', children: [], }, @@ -94,7 +94,7 @@ export const localMenuItems: MenuItem[] = [ type: 1, sort_key: 2, is_web_page: true, - web_url: 'https://ops2.apinb.com/view/#/project/management', + web_url: '/view/#/project/management', created_at: '2026-01-25T10:44:15.33024+08:00', children: [], }, @@ -182,22 +182,6 @@ export const localMenuItems: MenuItem[] = [ created_at: '2026-06-25T10:00:00+08:00', children: [], }, - { - id: 12090, - identity: '019ca000-0001-7000-8000-000000000090', - title: '自动化处置', - title_en: 'Automation', - code: 'ops:自动化处置', - description: '自动化脚本、审批、执行、回滚和审计记录', - app_id: 2, - menu_path: '/automation', - component: 'ops/pages/automation', - menu_icon: 'Thunderbolt', - type: 1, - sort_key: 2, - created_at: '2026-06-25T10:00:00+08:00', - children: [], - }, { id: 16, identity: '019b591d-00f4-73a0-bbdb-aa7da79ed390', @@ -327,10 +311,10 @@ export const localMenuItems: MenuItem[] = [ { id: 12022, identity: '019c7100-0001-7000-8000-000000000022', - title: '采集健康度', - title_en: 'Collection Health', - code: 'ops:综合监控:采集健康度', - description: '综合监控 - 采集任务最近状态与失败原因', + title: '平台运行状态', + title_en: 'Platform Status', + code: 'ops:综合监控:平台运行状态', + description: '综合监控 - 服务就绪、依赖、调度、队列和磁盘状态', app_id: 2, parent_id: 23, menu_path: '/monitor/collection-health', @@ -456,22 +440,6 @@ export const localMenuItems: MenuItem[] = [ created_at: '2025-12-26T13:23:51.938825+08:00', children: [], }, - { - id: 32, - identity: '019b591d-01f3-7698-9aad-9f9dbb3be203', - title: 'URL监控', - title_en: 'URL Monitoring', - code: 'ops:综合监控:url监控', - description: '综合监控 - URL监控', - app_id: 2, - parent_id: 23, - menu_path: '/monitor/url', - menu_icon: 'appstore', - type: 1, - sort_key: 5, - created_at: '2025-12-26T13:23:51.923432+08:00', - children: [], - }, { id: 34, identity: '019b591d-0210-7e32-ad40-ebbf2f48282f', diff --git a/src/router/routes/modules/ops.ts b/src/router/routes/modules/ops.ts index 419bc6e..cf478e9 100644 --- a/src/router/routes/modules/ops.ts +++ b/src/router/routes/modules/ops.ts @@ -12,6 +12,18 @@ const OPS: AppRouteRecordRaw = { order: 3, }, children: [ + { + path: 'device-overview/:resource_uid', + alias: ['/device-overview/:resource_uid'], + name: 'OpsDeviceOverview', + component: () => import('@/views/ops/pages/dc/device-overview/index.vue'), + meta: { + locale: '设备监控概览', + requiresAuth: true, + roles: ['*'], + hideInMenu: true, + }, + }, { path: 'resource-context', alias: ['/resource-context'], @@ -138,17 +150,6 @@ const OPS: AppRouteRecordRaw = { roles: ['*'], }, }, - { - path: 'automation', - alias: ['/automation'], - name: 'OpsAutomation', - component: () => import('@/views/ops/pages/automation/index.vue'), - meta: { - locale: 'menu.ops.automation', - requiresAuth: true, - roles: ['*'], - }, - }, // { // path: 'menu-management', // name: 'MenuManagement', diff --git a/src/store/modules/user/index.ts b/src/store/modules/user/index.ts index 6ae87b4..39ea320 100644 --- a/src/store/modules/user/index.ts +++ b/src/store/modules/user/index.ts @@ -86,7 +86,7 @@ const useUserStore = defineStore('user', { // Logout async logout() { try { - // await logout() + await logout() } finally { this.logoutCallBack() } diff --git a/src/utils/iframe.ts b/src/utils/iframe.ts new file mode 100644 index 0000000..7f10bfd --- /dev/null +++ b/src/utils/iframe.ts @@ -0,0 +1,13 @@ +export function buildAuthenticatedIframeUrl(webUrl: string, token: string, origin: string): string { + const url = new URL(webUrl, origin) + const hash = url.hash.slice(1) + const queryIndex = hash.indexOf('?') + const hashPath = queryIndex === -1 ? hash : hash.slice(0, queryIndex) + const hashQuery = queryIndex === -1 ? '' : hash.slice(queryIndex + 1) + const hashParams = new URLSearchParams(hashQuery) + + hashParams.set('token', token) + url.hash = `${hashPath}?${hashParams.toString()}` + + return url.toString() +} diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 29f474a..17ebe53 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -1,5 +1,5 @@ diff --git a/src/views/ops/pages/alert/notice/index.vue b/src/views/ops/pages/alert/notice/index.vue index 98ce042..e4005d3 100644 --- a/src/views/ops/pages/alert/notice/index.vue +++ b/src/views/ops/pages/alert/notice/index.vue @@ -1,5 +1,7 @@ + + + + + @@ -68,6 +75,7 @@ import { IconPlus } from '@arco-design/web-vue/es/icon' import SearchTable from '@/components/search-table/index.vue' import ChannelFormDialog from './components/ChannelFormDialog.vue' import ChannelDetailDialog from './components/ChannelDetailDialog.vue' +import RoutingConfigPanel from './components/RoutingConfigPanel.vue' import type { FormItem } from '@/components/search-form/types' import type { TableColumnData } from '@arco-design/web-vue/es/table/interface' import { fetchNoticeChannelList, deleteNoticeChannel } from '@/api/ops/noticeChannel' diff --git a/src/views/ops/pages/alert/tackle/components/AssignDialog.vue b/src/views/ops/pages/alert/tackle/components/AssignDialog.vue new file mode 100644 index 0000000..561d10a --- /dev/null +++ b/src/views/ops/pages/alert/tackle/components/AssignDialog.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/views/ops/pages/alert/tackle/components/CommentDialog.vue b/src/views/ops/pages/alert/tackle/components/CommentDialog.vue index 7347f9a..79c99de 100644 --- a/src/views/ops/pages/alert/tackle/components/CommentDialog.vue +++ b/src/views/ops/pages/alert/tackle/components/CommentDialog.vue @@ -12,6 +12,11 @@ + + + {{ phrase.content }} + + @@ -23,6 +28,7 @@ import { ref, watch } from 'vue' import { Message } from '@arco-design/web-vue' import { createAlertProcess } from '@/api/ops/alertRecord' +import { fetchEnabledCommonPhrases, type CommonPhrase } from '@/api/ops/systemSettings' interface Props { visible: boolean @@ -37,6 +43,7 @@ const emit = defineEmits<{ }>() const loading = ref(false) +const phrases = ref([]) const form = ref({ comment: '', }) @@ -49,10 +56,24 @@ watch( if (val) { alertId.value = String(props.alertRecordId) form.value.comment = '' + loadPhrases() } } ) +const loadPhrases = async () => { + try { + phrases.value = await fetchEnabledCommonPhrases('alert.handle') + } catch (error) { + console.error('加载告警常用语失败:', error) + phrases.value = [] + } +} + +const applyPhrase = (content?: string) => { + if (content) form.value.comment = content +} + const handleOk = async () => { if (!form.value.comment) { Message.error('请输入评论内容') diff --git a/src/views/ops/pages/alert/tackle/index.vue b/src/views/ops/pages/alert/tackle/index.vue index 9e7bd52..78a1311 100644 --- a/src/views/ops/pages/alert/tackle/index.vue +++ b/src/views/ops/pages/alert/tackle/index.vue @@ -1,5 +1,25 @@ + + +
+ +
+ {{ record.alert_name }} + {{ record.severity.name }} +
+

{{ record.summary || record.description || '暂无摘要' }}

+
{{ getStatusText(record.status) }}{{ formatDateTime(record.starts_at) }}
+ + 确认 + 解决 + 转派 + +
+ +
@@ -109,6 +148,7 @@ + @@ -121,7 +161,7 @@ import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface' import SearchTable from '@/components/search-table/index.vue' import { searchFormConfig } from './config/search-form' import { columns } from './config/columns' -import { fetchAlertRecords } from '@/api/ops/alertRecord' +import { fetchAlertRecords, fetchAlertTree } from '@/api/ops/alertRecord' import { fetchAlertLevelOptions } from '@/api/ops/alertLevel' import AckDialog from './components/AckDialog.vue' @@ -129,6 +169,7 @@ import ResolveDialog from './components/ResolveDialog.vue' import SilenceDialog from './components/SilenceDialog.vue' import CommentDialog from './components/CommentDialog.vue' import DetailDialog from './components/DetailDialog.vue' +import AssignDialog from './components/AssignDialog.vue' const router = useRouter() @@ -142,6 +183,24 @@ const resolveDialogVisible = ref(false) const silenceDialogVisible = ref(false) const commentDialogVisible = ref(false) const detailDialogVisible = ref(false) +const assignDialogVisible = ref(false) +const mineOnly = ref(false) +const treeVisible = ref(false) +const treeRows = ref([]) + +const groupedTree = computed(() => { + const severityMap = new Map() + treeRows.value.forEach((row) => { + if (!severityMap.has(row.severity_code)) severityMap.set(row.severity_code, { code: row.severity_code, name: row.severity_name, priority: row.severity_priority, count: 0, statuses: new Map() }) + const severity = severityMap.get(row.severity_code) + severity.count += row.count + if (!severity.statuses.has(row.status)) severity.statuses.set(row.status, { code: row.status, count: 0, objects: [] }) + const status = severity.statuses.get(row.status) + status.count += row.count + status.objects.push({ code: row.resource_category || 'unknown', count: row.count }) + }) + return Array.from(severityMap.values()).sort((a, b) => a.priority - b.priority).map((item) => ({ ...item, statuses: Array.from(item.statuses.values()) })) +}) const pagination = reactive({ current: 1, @@ -172,6 +231,7 @@ const formItems = computed(() => onMounted(async () => { await loadSeverityOptions() handleSearch() + loadTree() }) const loadSeverityOptions = async () => { @@ -207,6 +267,21 @@ const handleReset = () => { loadData() } +const loadTree = async () => { + try { + const result: any = await fetchAlertTree(mineOnly.value) + treeRows.value = result.details || [] + } catch { + treeRows.value = [] + } +} + +const handleScopeChange = () => { + pagination.current = 1 + loadData() + loadTree() +} + const loadData = async () => { loading.value = true try { @@ -215,6 +290,7 @@ const loadData = async () => { page_size: pagination.pageSize, keyword: formModel.value.keyword || undefined, status: formModel.value.status || undefined, + mine: mineOnly.value, } if (formModel.value.severity_id !== '' && formModel.value.severity_id != null) { params.severity_id = formModel.value.severity_id @@ -252,7 +328,7 @@ const handleRowClick = (record: any, _ev: Event) => { detailDialogVisible.value = true } -type ProcessAction = 'ack' | 'resolve' | 'silence' | 'comment' +type ProcessAction = 'ack' | 'resolve' | 'silence' | 'comment' | 'assign' // 根据告警受理处理的状态(record.status)控制操作列按钮可点击性 // 约定: @@ -300,6 +376,11 @@ const handleComment = (record: any) => { commentDialogVisible.value = true } +const handleAssign = (record: any) => { + currentRecord.value = record + assignDialogVisible.value = true +} + const handleMoreAction = (action: string, record: any) => { currentRecord.value = record switch (action) { @@ -312,6 +393,9 @@ const handleMoreAction = (action: string, record: any) => { query: { alert_record_id: record.id }, }) break + case 'assign': + assignDialogVisible.value = true + break } } @@ -321,8 +405,13 @@ const handleMoreSelect = (value: string | number | Record | und const handleSuccess = () => { loadData() + loadTree() } +const objectName = (category: string) => ({ + host: '服务器/主机', network: '网络设备', security: '安全设备', storage: '存储设备', database: '数据库', middleware: '中间件', business: '业务系统', room_device: '机房设备', unknown: '未分类', +}[category] || category) + const handleRefresh = () => { loadData() Message.success('数据已刷新') @@ -434,4 +523,52 @@ export default { vertical-align: middle; } } + +.scope-card { + margin-bottom: 16px; +} + +.alert-tree { + margin-top: 12px; +} + +.tree-status { + display: flex; + gap: 16px; + align-items: center; + margin: 8px 0; +} + +.mobile-list { + display: none; +} + +@media (max-width: 768px) { + .desktop-table { + display: none; + } + + .mobile-list { + display: grid; + gap: 12px; + } + + .alert-card-title, + .alert-card-meta { + display: flex; + gap: 8px; + align-items: center; + justify-content: space-between; + } + + .alert-card-meta { + margin-bottom: 12px; + color: var(--color-text-3); + } + + .tree-status { + align-items: flex-start; + flex-direction: column; + } +} diff --git a/src/views/ops/pages/assets/device/form/index.vue b/src/views/ops/pages/assets/device/form/index.vue index 7b0a825..a0cd52b 100644 --- a/src/views/ops/pages/assets/device/form/index.vue +++ b/src/views/ops/pages/assets/device/form/index.vue @@ -52,8 +52,10 @@ - - + + + + @@ -120,7 +122,19 @@ - + + + + + + + + + {{ item.label }} + + + + @@ -261,11 +275,20 @@ import { computed, ref, onMounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import { Message } from '@arco-design/web-vue' import type { FormInstance } from '@arco-design/web-vue/es/form' -import { createAsset, updateAsset, fetchAssetDetail, fetchCategoryOptions, assetStatusOptions, AssetForm } from '@/api/ops/asset' +import { + createAsset, + updateAsset, + fetchAssetDetail, + fetchCategoryOptions, + fetchManufacturerOptions, + assetStatusOptions, + AssetForm, +} from '@/api/ops/asset' import { fetchAllSuppliers } from '@/api/ops/supplier' import { fetchDatacenterList } from '@/api/ops/rack' import { fetchFloorListByDatacenter } from '@/api/ops/floor' import { fetchRoomListByFloor } from '@/api/ops/room' +import { fetchDictionaryOptions } from '@/api/ops/systemSettings' const router = useRouter() const route = useRoute() @@ -273,7 +296,7 @@ const route = useRoute() // 页面状态 const isEdit = ref(false) const placementType = ref('unplaced') -const deviceId = ref(null) +const deviceResourceUID = ref('') const pageTitle = ref('新增设备') const loading = ref(false) const submitting = ref(false) @@ -281,13 +304,16 @@ const formRef = ref() // 下拉选项 const categoryOptions = ref([]) +const manufacturerOptions = ref([]) const supplierOptions = ref([]) const datacenterOptions = ref<{ label: string; value: number }[]>([]) const floorOptions = ref<{ label: string; value: number }[]>([]) const roomOptions = ref([]) +const deviceLevelOptions = ref>([]) // 加载状态 const categoryLoading = ref(false) +const manufacturerLoading = ref(false) const supplierLoading = ref(false) const datacenterLoading = ref(false) const floorLoading = ref(false) @@ -304,8 +330,11 @@ const createAssetForm = (data: any = {}): AssetForm => ({ asset_code: data.asset_code || '', category_id: data.category_id, model: data.model || '', - manufacturer: data.manufacturer || '', + manufacturer_id: data.manufacturer_id, + manufacturer_code: data.manufacturer_code || '', serial_number: data.serial_number || '', + management_ip: data.management_ip || '', + device_level: data.device_level || 'normal', source_address: data.source_address || '', purchase_date: data.purchase_date || '', original_value: data.original_value, @@ -313,6 +342,8 @@ const createAssetForm = (data: any = {}): AssetForm => ({ warranty_period: data.warranty_period, warranty_expiry: data.warranty_expiry || '', department: data.department || '', + department_id: data.department_id, + business_system_id: data.business_system_id, user: data.user || '', status: data.status || 'in_use', location: data.location || '', @@ -325,6 +356,7 @@ const createAssetForm = (data: any = {}): AssetForm => ({ specifications: data.specifications || '', description: data.description || '', remarks: data.remarks || '', + attachments: data.attachments || '[]', }) const form = ref(createAssetForm()) @@ -334,10 +366,14 @@ const locationLocked = computed(() => isEdit.value && placementType.value !== 'u const rules = { asset_name: [{ required: true, message: '请输入资产名称' }], asset_code: [{ required: true, message: '请输入资产编号' }], + category_id: [{ required: true, message: '请选择设备分类' }], + manufacturer_id: [{ required: true, message: '请选择生产厂商' }], + device_level: [{ required: true, message: '请选择设备等级' }], } // 加载下拉选项 const loadOptions = async () => { + deviceLevelOptions.value = await fetchDictionaryOptions('resource.device_level') // 加载资产分类 categoryLoading.value = true try { @@ -351,6 +387,18 @@ const loadOptions = async () => { categoryLoading.value = false } + manufacturerLoading.value = true + try { + const res = await fetchManufacturerOptions() + if (res.code === 0) { + manufacturerOptions.value = res.details + } + } catch (error) { + console.error('加载生产厂商失败:', error) + } finally { + manufacturerLoading.value = false + } + // 加载全部数据中心(默认列出全部,支持模糊搜索) await fetchDatacenters() } @@ -502,10 +550,10 @@ const handleFloorChange = async (value: number | undefined) => { const initPage = async () => { await loadOptions() - const id = route.query.id - if (id) { + const resourceUID = String(route.query.resource_uid || '').trim() + if (resourceUID) { isEdit.value = true - deviceId.value = Number(id) + deviceResourceUID.value = resourceUID pageTitle.value = '编辑设备' await loadDeviceDetail() } @@ -513,11 +561,11 @@ const initPage = async () => { // 加载设备详情 const loadDeviceDetail = async () => { - if (!deviceId.value) return + if (!deviceResourceUID.value) return loading.value = true try { - const res = await fetchAssetDetail(deviceId.value) + const res = await fetchAssetDetail(deviceResourceUID.value) if (res.code === 0) { placementType.value = res.details.placement_type form.value = createAssetForm(res.details) @@ -588,8 +636,8 @@ const handleSubmit = async () => { const data = { ...form.value } let res - if (isEdit.value && deviceId.value) { - res = await updateAsset({ ...data, id: deviceId.value }) + if (isEdit.value && deviceResourceUID.value) { + res = await updateAsset(deviceResourceUID.value, data) } else { res = await createAsset(data) } diff --git a/src/views/ops/pages/assets/device/list/index.vue b/src/views/ops/pages/assets/device/list/index.vue index 029c2d0..c4ab7ba 100644 --- a/src/views/ops/pages/assets/device/list/index.vue +++ b/src/views/ops/pages/assets/device/list/index.vue @@ -32,6 +32,14 @@ + + @@ -180,18 +207,22 @@ import { computed, reactive, ref } from 'vue' import { Message } from '@arco-design/web-vue' import { + beginEditBigScreen, createBigScreen, - createBigScreenCarousel, createBigScreenGroup, createBigScreenSnapshot, - fetchBigScreenCarousels, + fetchBigScreenCarousel, + fetchBigScreenDatasets, fetchBigScreenGroups, fetchBigScreenSnapshots, fetchBigScreens, publishBigScreen, updateBigScreen, + updateBigScreenCarousel, + updateBigScreenCarouselItem, type BigScreen, type BigScreenCarousel, + type BigScreenDataset, type BigScreenGroup, type BigScreenSnapshot, } from '@/api/ops/bigScreen' @@ -204,11 +235,16 @@ const pageUnauthorized = ref(false) const keyword = ref('') const screens = ref([]) const groups = ref([]) -const carousels = ref([]) +const carousel = ref(null) +const datasets = ref([]) const snapshots = ref([]) const currentScreen = ref(null) const editingScreen = ref(null) const screenModalVisible = ref(false) +const carouselItemVisible = ref(false) +const carouselItemScreen = ref(null) + +const carouselItemForm = reactive({ enabled: false, order: 0 }) const pagination = reactive({ current: 1, @@ -226,14 +262,16 @@ const screenForm = reactive>({ width: 1920, height: 1080, status: 1, - is_public: false, + dataset_code: 'it_resource_monitor', + carousel_enabled: false, + carousel_order: 0, }) const summaryItems = computed(() => [ { label: '大屏总数', value: pagination.total }, { label: '已发布', value: screens.value.filter((item) => item.publish_status === 'published').length }, { label: '分组', value: groups.value.length }, - { label: '轮播', value: carousels.value.length }, + { label: '加入轮播', value: screens.value.filter((item) => item.carousel_enabled).length }, ]) const canOperate = computed(() => !pageUnauthorized.value && !loading.value) @@ -286,9 +324,14 @@ const loadGroups = async () => { groups.value = responseData(res) || [] } -const loadCarousels = async () => { - const res = await fetchBigScreenCarousels() - carousels.value = responseData(res) || [] +const loadCarousel = async () => { + const res = await fetchBigScreenCarousel() + carousel.value = responseData(res) || null +} + +const loadDatasets = async () => { + const res = await fetchBigScreenDatasets() + datasets.value = responseData(res) || [] } const loadSnapshots = async () => { @@ -298,7 +341,7 @@ const loadSnapshots = async () => { const loadAll = async () => { partialMessage.value = '' - const results = await Promise.allSettled([loadScreens(), loadGroups(), loadCarousels(), loadSnapshots()]) + const results = await Promise.allSettled([loadScreens(), loadGroups(), loadCarousel(), loadDatasets(), loadSnapshots()]) if (results.some((item) => item.status === 'rejected')) { partialMessage.value = '分组、轮播或快照数据部分加载失败,主列表仍可使用。' } @@ -311,13 +354,15 @@ const handlePageChange = (page: number) => { const groupName = (id?: number) => groups.value.find((group) => group.id === id)?.name || '未分组' +const datasetName = (code?: string) => datasets.value.find((item) => item.code === code)?.name || code || '-' + const publishStatusText = (status?: string) => { - const map: Record = { draft: '草稿', published: '已发布', offline: '已下线' } + const map: Record = { draft: '草稿', published: '已发布' } return map[status || 'draft'] || status || '草稿' } const publishStatusColor = (status?: string) => { - const map: Record = { draft: 'gray', published: 'green', offline: 'orange' } + const map: Record = { draft: 'gray', published: 'green' } return map[status || 'draft'] || 'blue' } @@ -329,7 +374,9 @@ const resetScreenForm = () => { screenForm.width = 1920 screenForm.height = 1080 screenForm.status = 1 - screenForm.is_public = false + screenForm.dataset_code = 'it_resource_monitor' + screenForm.carousel_enabled = false + screenForm.carousel_order = 0 } const openCreateScreen = () => { @@ -338,15 +385,27 @@ const openCreateScreen = () => { screenModalVisible.value = true } -const openEditScreen = (screen: BigScreen) => { - editingScreen.value = screen - Object.assign(screenForm, screen) - screenModalVisible.value = true +const openEditScreen = async (screen: BigScreen) => { + if (screen.is_builtin) return + try { + let editable = screen + if (screen.publish_status === 'published') { + const res = await beginEditBigScreen(screen.id) + editable = responseData(res) || { ...screen, publish_status: 'draft' } + Message.info('已自动取消发布,可以开始编辑') + await loadScreens() + } + editingScreen.value = editable + Object.assign(screenForm, editable) + screenModalVisible.value = true + } catch (error: any) { + Message.error(error.message || '进入编辑失败') + } } const saveScreen = async () => { - if (!screenForm.name || !screenForm.goview_project_id) { - Message.warning('请填写名称和 GoView 项目 ID') + if (!screenForm.name || !screenForm.goview_project_id || !screenForm.dataset_code) { + Message.warning('请填写名称、GoView 项目 ID 和业务数据集') return } saving.value = true @@ -387,15 +446,44 @@ const createDefaultGroup = async () => { loadGroups() } -const createDefaultCarousel = async () => { - await createBigScreenCarousel({ - name: `轮播 ${carousels.value.length + 1}`, - interval_seconds: 30, - enabled: true, - screen_ids: { ids: screens.value.slice(0, 5).map((item) => item.id) }, - }) - Message.success('轮播已创建') - loadCarousels() +const saveCarousel = async () => { + if (!carousel.value) return + await updateBigScreenCarousel({ enabled: carousel.value.enabled, interval_seconds: carousel.value.interval_seconds }) + Message.success('轮播设置已保存') + loadCarousel() +} + +const openCarouselItem = (screen: BigScreen) => { + carouselItemScreen.value = screen + carouselItemForm.enabled = screen.carousel_enabled + carouselItemForm.order = screen.carousel_order + carouselItemVisible.value = true +} + +const saveCarouselItem = async () => { + if (!carouselItemScreen.value) return + saving.value = true + try { + await updateBigScreenCarouselItem(carouselItemScreen.value.id, carouselItemForm) + Message.success('大屏轮播设置已保存') + carouselItemVisible.value = false + await loadScreens() + } catch (error: any) { + Message.error(error.message || '保存轮播设置失败') + } finally { + saving.value = false + } +} + +const viewerBase = String(import.meta.env.VITE_BIG_SCREEN_VIEW_URL || '/chart-view').replace(/\/$/, '') + +const openPublicScreen = (screen: BigScreen) => { + const identity = screen.goview_project_id || screen.built_in_code || `screen-${screen.id}` + window.open(`${viewerBase}/#/chart/preview/${encodeURIComponent(identity)}?screenId=${screen.id}`, '_blank', 'noopener') +} + +const openPublicCarousel = () => { + window.open(`${viewerBase}/#/chart/preview/carousel?carousel=1`, '_blank', 'noopener') } const createSnapshotRecord = async () => { diff --git a/src/views/ops/pages/business-system/components/HTTPCheckPanel.vue b/src/views/ops/pages/business-system/components/HTTPCheckPanel.vue new file mode 100644 index 0000000..62512de --- /dev/null +++ b/src/views/ops/pages/business-system/components/HTTPCheckPanel.vue @@ -0,0 +1,444 @@ + + + + + diff --git a/src/views/ops/pages/business-system/detail.vue b/src/views/ops/pages/business-system/detail.vue index 8d8769b..0488ff7 100644 --- a/src/views/ops/pages/business-system/detail.vue +++ b/src/views/ops/pages/business-system/detail.vue @@ -2,10 +2,15 @@
@@ -39,6 +44,8 @@ + + @@ -189,6 +196,7 @@ import { computed, onMounted, reactive, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { Message } from '@arco-design/web-vue' import { IconApps, IconRefresh } from '@arco-design/web-vue/es/icon' +import HTTPCheckPanel from './components/HTTPCheckPanel.vue' import { createBusinessDocumentLink, createBusinessNote, @@ -461,6 +469,10 @@ const goBack = () => { router.push('/business-system') } +const openBusinessTopology = () => router.push({ path: '/business-topology', query: { id: businessSystemID.value } }) +const openLogs = () => router.push({ path: '/monitor/log', query: { tab: 'entries', business_system_id: businessSystemID.value } }) +const openTraces = () => router.push({ path: '/monitor/log', query: { tab: 'traces', business_system_id: businessSystemID.value } }) + onMounted(fetchAll) diff --git a/src/views/ops/pages/collection/bindings/index.vue b/src/views/ops/pages/collection/bindings/index.vue new file mode 100644 index 0000000..fb2c0e6 --- /dev/null +++ b/src/views/ops/pages/collection/bindings/index.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/views/ops/pages/collection/components/BindingFormModal.vue b/src/views/ops/pages/collection/components/BindingFormModal.vue new file mode 100644 index 0000000..4716c61 --- /dev/null +++ b/src/views/ops/pages/collection/components/BindingFormModal.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/views/ops/pages/collection/credentials/index.vue b/src/views/ops/pages/collection/credentials/index.vue new file mode 100644 index 0000000..8be813d --- /dev/null +++ b/src/views/ops/pages/collection/credentials/index.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/views/ops/pages/collection/nodes/index.vue b/src/views/ops/pages/collection/nodes/index.vue new file mode 100644 index 0000000..60758db --- /dev/null +++ b/src/views/ops/pages/collection/nodes/index.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/views/ops/pages/datacenter/three-machine-room/components/CameraPreviewDialog.vue b/src/views/ops/pages/datacenter/three-machine-room/components/CameraPreviewDialog.vue index 898a01f..4e90b61 100644 --- a/src/views/ops/pages/datacenter/three-machine-room/components/CameraPreviewDialog.vue +++ b/src/views/ops/pages/datacenter/three-machine-room/components/CameraPreviewDialog.vue @@ -285,8 +285,8 @@ function applyAssetCameraConfig(asset: Record): void { /** 获取资产详情中的摄像头连接配置。 */ async function loadAssetCameraConfig(): Promise { - if (!props.device?.asset_id) return - const response = (await fetchAssetDetail(props.device.asset_id)) as unknown + if (!props.device?.resource_uid) return + const response = (await fetchAssetDetail(props.device.resource_uid)) as unknown const responseRecord = asRecord(response) if (!responseRecord || Number(responseRecord.code) !== 0) { throw new Error(String(responseRecord?.message || '摄像头资产详情获取失败')) diff --git a/src/views/ops/pages/datacenter/three-machine-room/components/DeviceManagementDialog.vue b/src/views/ops/pages/datacenter/three-machine-room/components/DeviceManagementDialog.vue index b389a24..5f062a3 100644 --- a/src/views/ops/pages/datacenter/three-machine-room/components/DeviceManagementDialog.vue +++ b/src/views/ops/pages/datacenter/three-machine-room/components/DeviceManagementDialog.vue @@ -3,6 +3,7 @@ {{ device?.asset_code || '-' }} + {{ device?.resource_uid || '-' }} {{ device?.category_name || device?.category_code || '-' }} {{ placementTypeText }} {{ device.rack_id || '-' }} @@ -19,7 +20,7 @@
- 未绑定监控资源或暂无运行数据 + 暂无运行数据
@@ -37,30 +38,6 @@
-
-
- 监控资源绑定 - 刷新 -
-
- - {{ option.label }} - - - 绑定 -
-
-
-
- {{ binding.display_name || binding.resource_uid }} - {{ binding.resource_uid }} -
- 解除绑定 -
-
- -
- @@ -83,8 +83,7 @@ {{ detailData.id }} {{ detailData.name || '-' }} {{ detailData.database || '-' }} - {{ detailData.username || '-' }} - {{ detailData.server_identity || '-' }} + {{ detailData.tls_mode || '-' }} {{ detailData.category || '-' }} {{ tag }} @@ -94,23 +93,19 @@ - + {{ detailData.enabled ? '已启用' : '已禁用' }} - {{ detailData.interval ? `${detailData.interval}秒` : '-' }} - - {{ detailData.collect_on ? '已启用' : '已禁用' }} - - - {{ detailData.collect_interval ? `${detailData.collect_interval}秒` : '-' }} - - {{ detailData.collect_args || '-' }} - {{ detailData.collect_last_result || '-' }} - -
{{ formatJson(detailData.extra) || '-' }}
+ {{ detailData.interval ? `${detailData.interval}秒` : '-' }} + + {{ getAdapterStatusText(detailData.adapter_status) }} + {{ detailData.product_version || '-' }} + {{ formatTime(detailData.last_data_time) }} + 由统一凭证管理加密保存,数据库页面不保存或显示账号密码 + 在统一采集管理的设备采集绑定中维护节点、模板、凭证、目标和执行间隔
@@ -232,6 +227,9 @@ const getStatusText = (status?: string) => { return textMap[status || ''] || '-' } +const getAdapterStatusColor = (status?: string) => ({ verified: 'green', unverified: 'orange', blocked: 'red' }[status || ''] || 'gray') +const getAdapterStatusText = (status?: string) => ({ verified: '已验证', unverified: '待验证', blocked: '受阻' }[status || ''] || '待采集') + const formatStatusCode = (code?: number | null) => { if (code === null || code === undefined) return '-' return code @@ -274,15 +272,6 @@ const parseTags = (tags?: string) => { return tags.split(',').filter((t) => t.trim()) } -// 格式化JSON -const formatJson = (json?: string) => { - if (!json) return '' - try { - return JSON.stringify(JSON.parse(json), null, 2) - } catch { - return json - } -} diff --git a/src/views/ops/pages/dc/database/components/QuickConfigDialog.vue b/src/views/ops/pages/dc/database/components/QuickConfigDialog.vue deleted file mode 100644 index cafeb65..0000000 --- a/src/views/ops/pages/dc/database/components/QuickConfigDialog.vue +++ /dev/null @@ -1,79 +0,0 @@ - - - diff --git a/src/views/ops/pages/dc/database/components/RemoteControl.vue b/src/views/ops/pages/dc/database/components/RemoteControl.vue deleted file mode 100644 index 13cdb57..0000000 --- a/src/views/ops/pages/dc/database/components/RemoteControl.vue +++ /dev/null @@ -1,302 +0,0 @@ - - - - - diff --git a/src/views/ops/pages/dc/database/config/columns.ts b/src/views/ops/pages/dc/database/config/columns.ts index 018949d..8f69a48 100644 --- a/src/views/ops/pages/dc/database/config/columns.ts +++ b/src/views/ops/pages/dc/database/config/columns.ts @@ -50,10 +50,10 @@ export const columns = [ slotName: 'enabled', }, { - dataIndex: 'collect_on', - title: '进程内采集', - width: 120, - slotName: 'collect_on', + dataIndex: 'adapter_status', + title: '适配状态', + width: 110, + slotName: 'adapter_status', }, { dataIndex: 'status', @@ -68,16 +68,10 @@ export const columns = [ slotName: 'response_time', }, { - dataIndex: 'uptime', - title: '运行时长', - width: 120, - slotName: 'uptime', - }, - { - dataIndex: 'last_check_time', - title: '最后检查时间', + dataIndex: 'last_data_time', + title: '最后数据时间', width: 160, - slotName: 'last_check_time', + slotName: 'last_data_time', }, { dataIndex: 'tags', diff --git a/src/views/ops/pages/dc/database/index.vue b/src/views/ops/pages/dc/database/index.vue index bb21bc3..f81528f 100644 --- a/src/views/ops/pages/dc/database/index.vue +++ b/src/views/ops/pages/dc/database/index.vue @@ -57,10 +57,9 @@ - - - - - - - - - - - - - +
{{ item.metric_name }}
+
{{ metricSeriesLabel(item) }}
{{ item.metric_value }} {{ item.metric_unit || '' }}
+ 近 24 小时趋势 + + + + +
@@ -113,27 +111,99 @@ - - diff --git a/src/views/ops/pages/dc/security/components/QuickConfigDialog.vue b/src/views/ops/pages/dc/security/components/QuickConfigDialog.vue deleted file mode 100644 index 713c5c4..0000000 --- a/src/views/ops/pages/dc/security/components/QuickConfigDialog.vue +++ /dev/null @@ -1,259 +0,0 @@ - - - diff --git a/src/views/ops/pages/dc/security/config/columns.ts b/src/views/ops/pages/dc/security/config/columns.ts index 18dcdfc..1e64815 100644 --- a/src/views/ops/pages/dc/security/config/columns.ts +++ b/src/views/ops/pages/dc/security/config/columns.ts @@ -8,7 +8,7 @@ export const columns = [ }, { dataIndex: 'name', - title: '服务名称', + title: '设备名称', width: 150, ellipsis: true, tooltip: true, @@ -28,35 +28,12 @@ export const columns = [ return SECURITY_TYPE_MAP[record.type] || record.type }, }, - { - dataIndex: 'server_identity', - title: '服务器', - width: 150, - slotName: 'serverIdentity', - }, { dataIndex: 'enabled', title: '启用状态', width: 100, slotName: 'enabled', }, - { - dataIndex: 'collect_method', - title: '采集方式', - width: 100, - slotName: 'collectMethod', - }, - { - dataIndex: 'collect_on', - title: '采集状态', - width: 100, - slotName: 'collectOn', - }, - { - dataIndex: 'collect_interval', - title: '采集间隔(秒)', - width: 120, - }, { dataIndex: 'status', title: '运行状态', diff --git a/src/views/ops/pages/dc/security/index.vue b/src/views/ops/pages/dc/security/index.vue index b7710f8..2d1ef84 100644 --- a/src/views/ops/pages/dc/security/index.vue +++ b/src/views/ops/pages/dc/security/index.vue @@ -25,27 +25,12 @@ - - - - - 编辑 - diff --git a/src/views/ops/pages/dc/storage/config/columns.ts b/src/views/ops/pages/dc/storage/config/columns.ts index 139a420..9660458 100644 --- a/src/views/ops/pages/dc/storage/config/columns.ts +++ b/src/views/ops/pages/dc/storage/config/columns.ts @@ -20,34 +20,12 @@ export const columns = [ title: '类型', width: 120, }, - { - dataIndex: 'server_identity', - title: '服务器标识', - width: 150, - }, { dataIndex: 'enabled', title: '启用状态', width: 100, slotName: 'enabled', }, - { - dataIndex: 'collect_method', - title: '采集方式', - width: 100, - slotName: 'collect_method', - }, - { - dataIndex: 'collect_on', - title: '数据采集', - width: 100, - slotName: 'data_collection', - }, - { - dataIndex: 'collect_interval', - title: '采集间隔(秒)', - width: 120, - }, { dataIndex: 'status', title: '运行状态', diff --git a/src/views/ops/pages/dc/storage/index.vue b/src/views/ops/pages/dc/storage/index.vue index e164d9b..85aa0c4 100644 --- a/src/views/ops/pages/dc/storage/index.vue +++ b/src/views/ops/pages/dc/storage/index.vue @@ -35,17 +35,6 @@ - - -