From e29057a0bad02306e14292bdc071a73942fd3d41 Mon Sep 17 00:00:00 2001 From: D8D Developer Date: Fri, 27 Jun 2025 02:09:16 +0000 Subject: [PATCH] u --- src/client/admin/api/alert.ts | 89 - src/client/admin/api/alert_handle.ts | 75 - src/client/admin/api/alert_notify_config.ts | 63 - src/client/admin/api/auth.ts | 104 - src/client/admin/api/charts.ts | 66 - src/client/admin/api/device_alert_rule.ts | 78 - src/client/admin/api/device_instance.ts | 93 - src/client/admin/api/device_type.ts | 101 - src/client/admin/api/files.ts | 159 -- src/client/admin/api/index.ts | 41 - src/client/admin/api/inspections.ts | 301 --- src/client/admin/api/know_info.ts | 92 - src/client/admin/api/maps.ts | 62 - src/client/admin/api/messages.ts | 79 - src/client/admin/api/modbus_rtu_device.ts | 47 - src/client/admin/api/monitor.ts | 179 -- src/client/admin/api/monitor_charts.ts | 69 - src/client/admin/api/rack.ts | 60 - src/client/admin/api/rack_server.ts | 60 - src/client/admin/api/rack_server_type.ts | 69 - src/client/admin/api/sms.ts | 25 - src/client/admin/api/sys.ts | 47 - src/client/admin/api/theme.ts | 36 - src/client/admin/api/users.ts | 79 - src/client/admin/api/work_orders.ts | 308 --- src/client/admin/api/yangantubiao.png | Bin 1801 -> 0 bytes src/client/admin/api/zichan.ts | 61 - src/client/admin/api/zichan_area.ts | 60 - src/client/admin/api/zichan_category.ts | 60 - src/client/admin/api/zichan_transfer.ts | 54 - src/client/big/api.ts | 381 ---- src/client/big/client.tsx | 9 - .../big/components/AlarmDeviceTable.tsx | 63 - src/client/big/components/CustomCard.tsx | 27 - src/client/big/components/DataCenter.tsx | 158 -- src/client/big/components/MetricCards.tsx | 101 - src/client/big/components/PageTitle.tsx | 17 - .../big/components/ServerMonitorCharts.tsx | 289 --- .../big/components/three/ServerTooltip.tsx | 64 - .../big/components/three/ThreeJSRoom.tsx | 220 --- src/client/big/components/three/constants.ts | 55 - src/client/big/components/three/models.ts | 180 -- src/client/big/components/three/types.ts | 46 - src/client/big/components/three/utils.tsx | 174 -- src/client/big/components/utils.ts | 28 - src/client/big/components_three.tsx | 691 ------- src/client/big/title-bg.png | Bin 52161 -> 0 bytes src/server/api.ts | 49 +- src/server/api/init.ts | 192 -- src/server/api/migration.ts | 89 - src/server/renderer.tsx | 1 - src/share/monitorTypes.ts | 1754 ----------------- 52 files changed, 1 insertion(+), 7204 deletions(-) delete mode 100644 src/client/admin/api/alert.ts delete mode 100644 src/client/admin/api/alert_handle.ts delete mode 100644 src/client/admin/api/alert_notify_config.ts delete mode 100644 src/client/admin/api/auth.ts delete mode 100644 src/client/admin/api/charts.ts delete mode 100644 src/client/admin/api/device_alert_rule.ts delete mode 100644 src/client/admin/api/device_instance.ts delete mode 100644 src/client/admin/api/device_type.ts delete mode 100644 src/client/admin/api/files.ts delete mode 100644 src/client/admin/api/index.ts delete mode 100644 src/client/admin/api/inspections.ts delete mode 100644 src/client/admin/api/know_info.ts delete mode 100644 src/client/admin/api/maps.ts delete mode 100644 src/client/admin/api/messages.ts delete mode 100644 src/client/admin/api/modbus_rtu_device.ts delete mode 100644 src/client/admin/api/monitor.ts delete mode 100644 src/client/admin/api/monitor_charts.ts delete mode 100644 src/client/admin/api/rack.ts delete mode 100644 src/client/admin/api/rack_server.ts delete mode 100644 src/client/admin/api/rack_server_type.ts delete mode 100644 src/client/admin/api/sms.ts delete mode 100644 src/client/admin/api/sys.ts delete mode 100644 src/client/admin/api/theme.ts delete mode 100644 src/client/admin/api/users.ts delete mode 100644 src/client/admin/api/work_orders.ts delete mode 100644 src/client/admin/api/yangantubiao.png delete mode 100644 src/client/admin/api/zichan.ts delete mode 100644 src/client/admin/api/zichan_area.ts delete mode 100644 src/client/admin/api/zichan_category.ts delete mode 100644 src/client/admin/api/zichan_transfer.ts delete mode 100644 src/client/big/api.ts delete mode 100644 src/client/big/client.tsx delete mode 100644 src/client/big/components/AlarmDeviceTable.tsx delete mode 100644 src/client/big/components/CustomCard.tsx delete mode 100644 src/client/big/components/DataCenter.tsx delete mode 100644 src/client/big/components/MetricCards.tsx delete mode 100644 src/client/big/components/PageTitle.tsx delete mode 100644 src/client/big/components/ServerMonitorCharts.tsx delete mode 100644 src/client/big/components/three/ServerTooltip.tsx delete mode 100644 src/client/big/components/three/ThreeJSRoom.tsx delete mode 100644 src/client/big/components/three/constants.ts delete mode 100644 src/client/big/components/three/models.ts delete mode 100644 src/client/big/components/three/types.ts delete mode 100644 src/client/big/components/three/utils.tsx delete mode 100644 src/client/big/components/utils.ts delete mode 100644 src/client/big/components_three.tsx delete mode 100644 src/client/big/title-bg.png delete mode 100644 src/server/api/init.ts delete mode 100644 src/server/api/migration.ts delete mode 100644 src/share/monitorTypes.ts diff --git a/src/client/admin/api/alert.ts b/src/client/admin/api/alert.ts deleted file mode 100644 index af88d63..0000000 --- a/src/client/admin/api/alert.ts +++ /dev/null @@ -1,89 +0,0 @@ -import axios from 'axios'; -import { DeviceAlert } from "@/share/monitorTypes"; - -// 告警相关响应类型 -interface DeviceAlertDataResponse { - data: DeviceAlert[]; - total: number; - page: number; - pageSize: number; - } - - interface DeviceAlertResponse { - data: DeviceAlert; - message?: string; - } - - interface AlertCreateResponse { - data: DeviceAlert; - message: string; - } - - interface AlertUpdateResponse { - data: DeviceAlert; - message: string; - } - - interface AlertDeleteResponse { - message: string; - } - -// 告警API接口定义 -export const AlertAPI = { - // 获取告警数据 - getAlertData: async (params?: { - page?: number, - limit?: number, - alert_type?: string, - alert_level?: string, - start_time?: string, - end_time?: string - }): Promise => { - try { - const response = await axios.get(`/alerts`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个告警数据 - getAlert: async (id: number): Promise => { - try { - const response = await axios.get(`/alerts/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建告警数据 - createAlert: async (data: Partial): Promise => { - try { - const response = await axios.post(`/alerts`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新告警数据 - updateAlert: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/alerts/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除告警数据 - deleteAlert: async (id: number): Promise => { - try { - const response = await axios.delete(`/alerts/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/alert_handle.ts b/src/client/admin/api/alert_handle.ts deleted file mode 100644 index 88b5601..0000000 --- a/src/client/admin/api/alert_handle.ts +++ /dev/null @@ -1,75 +0,0 @@ -import axios from 'axios'; -import { AlertHandleLog } from "@/share/monitorTypes"; - -// 告警处理相关响应类型 -interface AlertHandleDataResponse { - data: AlertHandleLog[]; - total: number; - page: number; - pageSize: number; - } - - interface AlertHandleResponse { - data: AlertHandleLog; - message?: string; - } - -// 告警处理API接口定义 -export const AlertHandleAPI = { - // 获取告警处理数据 - getAlertHandleData: async (params?: { - page?: number, - limit?: number, - alert_id?: number, - handle_type?: string, - start_time?: string, - end_time?: string - }): Promise => { - try { - const response = await axios.get(`/alert-handles`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个告警处理数据 - getAlertHandle: async (id: number): Promise => { - try { - const response = await axios.get(`/alert-handles/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建告警处理数据 - createAlertHandle: async (data: Partial) => { - try { - const response = await axios.post(`/alert-handles`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新告警处理数据 - updateAlertHandle: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/alert-handles/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除告警处理数据 - deleteAlertHandle: async (id: number) => { - try { - const response = await axios.delete(`/alert-handles/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/alert_notify_config.ts b/src/client/admin/api/alert_notify_config.ts deleted file mode 100644 index f14fc76..0000000 --- a/src/client/admin/api/alert_notify_config.ts +++ /dev/null @@ -1,63 +0,0 @@ -import axios from 'axios'; -import { AlertNotifyConfig } from "@/share/monitorTypes"; - -// 告警通知配置相关响应类型 -interface AlertNotifyConfigDataResponse { - data: AlertNotifyConfig[]; - total: number; - page: number; - pageSize: number; - } - - interface AlertNotifyConfigResponse { - data: AlertNotifyConfig; - message?: string; - } - -// 告警通知配置API接口定义 -export const AlertNotifyConfigAPI = { - // 获取告警通知配置 - getAlertNotifyConfig: async (params?: { - page?: number, - limit?: number, - device_id?: number, - alert_level?: string - }): Promise => { - try { - const response = await axios.get(`/alert-notify-configs`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建告警通知配置 - createAlertNotifyConfig: async (data: Partial): Promise => { - try { - const response = await axios.post(`/alert-notify-configs`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新告警通知配置 - updateAlertNotifyConfig: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/alert-notify-configs/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除告警通知配置 - deleteAlertNotifyConfig: async (id: number): Promise => { - try { - const response = await axios.delete(`/alert-notify-configs/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/auth.ts b/src/client/admin/api/auth.ts deleted file mode 100644 index 275b341..0000000 --- a/src/client/admin/api/auth.ts +++ /dev/null @@ -1,104 +0,0 @@ -import axios from 'axios'; -import type { User } from '../@/share/types'; - -interface AuthLoginResponse { - message: string; - token: string; - refreshToken?: string; - user: User; -} - -interface AuthResponse { - message: string; - [key: string]: any; -} - -interface AuthAPIType { - login: (username: string, password: string, latitude?: number, longitude?: number) => Promise; - register: (username: string, email: string, password: string) => Promise; - logout: () => Promise; - getCurrentUser: () => Promise; - updateUser: (userId: number, userData: Partial) => Promise; - changePassword: (oldPassword: string, newPassword: string) => Promise; - requestPasswordReset: (email: string) => Promise; - resetPassword: (token: string, newPassword: string) => Promise; -} - -export const AuthAPI: AuthAPIType = { - login: async (username: string, password: string, latitude?: number, longitude?: number) => { - try { - const response = await axios.post('/auth/login', { - username, - password, - latitude, - longitude - }); - return response.data; - } catch (error) { - throw error; - } - }, - - register: async (username: string, email: string, password: string) => { - try { - const response = await axios.post('/auth/register', { username, email, password }); - return response.data; - } catch (error) { - throw error; - } - }, - - logout: async () => { - try { - const response = await axios.post('/auth/logout'); - return response.data; - } catch (error) { - throw error; - } - }, - - getCurrentUser: async () => { - try { - const response = await axios.get('/auth/me'); - return response.data; - } catch (error) { - throw error; - } - }, - - updateUser: async (userId: number, userData: Partial) => { - try { - const response = await axios.put(`/auth/users/${userId}`, userData); - return response.data; - } catch (error) { - throw error; - } - }, - - changePassword: async (oldPassword: string, newPassword: string) => { - try { - const response = await axios.post('/auth/change-password', { oldPassword, newPassword }); - return response.data; - } catch (error) { - throw error; - } - }, - - requestPasswordReset: async (email: string) => { - try { - const response = await axios.post('/auth/request-password-reset', { email }); - return response.data; - } catch (error) { - throw error; - } - }, - - resetPassword: async (token: string, newPassword: string) => { - try { - const response = await axios.post('/auth/reset-password', { token, newPassword }); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/charts.ts b/src/client/admin/api/charts.ts deleted file mode 100644 index 64c185e..0000000 --- a/src/client/admin/api/charts.ts +++ /dev/null @@ -1,66 +0,0 @@ -import axios from 'axios'; - -interface ChartDataResponse { - message: string; - data: T; -} - -interface UserActivityData { - date: string; - count: number; -} - -interface FileUploadsData { - month: string; - count: number; -} - -interface FileTypesData { - type: string; - value: number; -} - -interface DashboardOverviewData { - userCount: number; - fileCount: number; - articleCount: number; - todayLoginCount: number; -} - -export const ChartAPI = { - getUserActivity: async (): Promise> => { - try { - const response = await axios.get('/charts/user-activity'); - return response.data; - } catch (error) { - throw error; - } - }, - - getFileUploads: async (): Promise> => { - try { - const response = await axios.get('/charts/file-uploads'); - return response.data; - } catch (error) { - throw error; - } - }, - - getFileTypes: async (): Promise> => { - try { - const response = await axios.get('/charts/file-types'); - return response.data; - } catch (error) { - throw error; - } - }, - - getDashboardOverview: async (): Promise> => { - try { - const response = await axios.get('/charts/dashboard-overview'); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/device_alert_rule.ts b/src/client/admin/api/device_alert_rule.ts deleted file mode 100644 index 8013d1b..0000000 --- a/src/client/admin/api/device_alert_rule.ts +++ /dev/null @@ -1,78 +0,0 @@ -import axios from 'axios'; -import { DeviceAlert, DeviceAlertRule } from "@/share/monitorTypes"; - -// 告警相关响应类型 -interface DeviceAlertDataResponse { - data: DeviceAlert[]; - total: number; - page: number; - pageSize: number; - } - - interface DeviceAlertResponse { - data: DeviceAlert; - message?: string; - } - - -interface AlertDeleteResponse { - message: string; - } - -// 设备告警规则API接口定义 -export const DeviceAlertRuleAPI = { - // 获取设备告警规则 - getDeviceAlertRules: async (params?: { - page?: number, - limit?: number, - device_id?: number, - rule_type?: string - }): Promise => { - try { - const response = await axios.get(`/device-alert-rules`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个设备告警规则 - getDeviceAlertRule: async (id: number): Promise => { - try { - const response = await axios.get(`/device-alert-rules/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建设备告警规则 - createDeviceAlertRule: async (data: Partial): Promise => { - try { - const response = await axios.post(`/device-alert-rules`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新设备告警规则 - updateDeviceAlertRule: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/device-alert-rules/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除设备告警规则 - deleteDeviceAlertRule: async (id: number): Promise => { - try { - const response = await axios.delete(`/device-alert-rules/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/device_instance.ts b/src/client/admin/api/device_instance.ts deleted file mode 100644 index ad098cd..0000000 --- a/src/client/admin/api/device_instance.ts +++ /dev/null @@ -1,93 +0,0 @@ -import axios from "axios"; -import { DeviceInstance, DeviceType, ZichanInfo } from "@/share/monitorTypes"; - -// 设备实例API接口定义 -interface DeviceInstancesResponse { - data: DeviceInstance[]; - pagination: { - total: number; - current: number; - pageSize: number; - totalPages: number; - }; -} - -interface DeviceInstanceResponse { - data: DeviceInstance; - asset_info?: ZichanInfo; - type_info?: DeviceType; - message?: string; -} - -interface DeviceInstanceCreateResponse { - message: string; - data: DeviceInstance; -} - -interface DeviceInstanceUpdateResponse { - message: string; - data: DeviceInstance; -} - -interface DeviceInstanceDeleteResponse { - message: string; - id: number; -} - -export const DeviceInstanceAPI = { - // 获取设备实例列表 - getDeviceInstances: async (params?: { - page?: number, - limit?: number, - type_id?: number, - protocol?: string, - status?: number - }): Promise => { - try { - const response = await axios.get('/device/instances', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个设备实例信息 - getDeviceInstance: async (id: number): Promise => { - try { - const response = await axios.get(`/device/instances/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建设备实例 - createDeviceInstance: async (data: Partial): Promise => { - try { - const response = await axios.post('/device/instances', data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新设备实例 - updateDeviceInstance: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/device/instances/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除设备实例 - deleteDeviceInstance: async (id: number): Promise => { - try { - const response = await axios.delete(`/device/instances/${id}`); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/device_type.ts b/src/client/admin/api/device_type.ts deleted file mode 100644 index 9882291..0000000 --- a/src/client/admin/api/device_type.ts +++ /dev/null @@ -1,101 +0,0 @@ -import axios from "axios"; -import { DeviceType } from "@/share/monitorTypes"; - -// 设备类型API接口定义 -interface DeviceTypeResponse { - data: DeviceType[]; - pagination: { - total: number; - current: number; - pageSize: number; - totalPages: number; - }; -} - -interface DeviceTypeDetailResponse { - data: DeviceType; - message?: string; -} - -interface DeviceTypeCreateResponse { - message: string; - data: DeviceType; -} - -interface DeviceTypeUpdateResponse { - message: string; - data: DeviceType; -} - -interface DeviceTypeDeleteResponse { - message: string; - id: number; -} - -export const DeviceTypeAPI = { - // 获取设备类型列表 - getDeviceTypes: async (params?: { - page?: number, - pageSize?: number, - code?: string, - name?: string, - is_enabled?: boolean - }): Promise => { - try { - const response = await axios.get('/device/types', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个设备类型信息 - getDeviceType: async (id: number): Promise => { - try { - const response = await axios.get(`/device/types/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建设备类型 - createDeviceType: async (data: Partial): Promise => { - try { - const response = await axios.post('/device/types', data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新设备类型 - updateDeviceType: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/device/types/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除设备类型 - deleteDeviceType: async (id: number): Promise => { - try { - const response = await axios.delete(`/device/types/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取设备类型图标 - getTypeIcons: async (): Promise<{data: Record, success: boolean}> => { - try { - const response = await axios.get('/device/types/icons'); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/files.ts b/src/client/admin/api/files.ts deleted file mode 100644 index 49696a7..0000000 --- a/src/client/admin/api/files.ts +++ /dev/null @@ -1,159 +0,0 @@ -import axios from 'axios'; -import type { FileLibrary, FileCategory } from '../@/share/types'; -import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types'; - -interface FileUploadPolicyResponse { - message: string; - data: MinioUploadPolicy | OSSUploadPolicy; -} - -interface FileListResponse { - message: string; - data: { - list: FileLibrary[]; - pagination: { - current: number; - pageSize: number; - total: number; - }; - }; -} - -interface FileSaveResponse { - message: string; - data: FileLibrary; -} - -interface FileInfoResponse { - message: string; - data: FileLibrary; -} - -interface FileDeleteResponse { - message: string; -} - -interface FileCategoryListResponse { - data: FileCategory[]; - total: number; - page: number; - pageSize: number; -} - -interface FileCategoryCreateResponse { - message: string; - data: FileCategory; -} - -interface FileCategoryUpdateResponse { - message: string; - data: FileCategory; -} - -interface FileCategoryDeleteResponse { - message: string; -} - -export const FileAPI = { - getUploadPolicy: async (filename: string, prefix: string = 'uploads/', maxSize: number = 10 * 1024 * 1024): Promise => { - try { - const response = await axios.get('/upload/policy', { - params: { filename, prefix, maxSize } - }); - return response.data; - } catch (error) { - throw error; - } - }, - - saveFileInfo: async (fileData: Partial): Promise => { - try { - const response = await axios.post('/upload/save', fileData); - return response.data; - } catch (error) { - throw error; - } - }, - - getFileList: async (params?: { - page?: number, - pageSize?: number, - category_id?: number, - fileType?: string, - keyword?: string - }): Promise => { - try { - const response = await axios.get('/upload/list', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - getFileInfo: async (id: number): Promise => { - try { - const response = await axios.get(`/upload/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - updateDownloadCount: async (id: number): Promise => { - try { - const response = await axios.post(`/upload/${id}/download`); - return response.data; - } catch (error) { - throw error; - } - }, - - deleteFile: async (id: number): Promise => { - try { - const response = await axios.delete(`/upload/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - getCategories: async (params?: { - page?: number, - pageSize?: number, - search?: string - }): Promise => { - try { - const response = await axios.get('/file-categories', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - createCategory: async (data: Partial): Promise => { - try { - const response = await axios.post('/file-categories', data); - return response.data; - } catch (error) { - throw error; - } - }, - - updateCategory: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/file-categories/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - deleteCategory: async (id: number): Promise => { - try { - const response = await axios.delete(`/file-categories/${id}`); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/index.ts b/src/client/admin/api/index.ts deleted file mode 100644 index 889398b..0000000 --- a/src/client/admin/api/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios from 'axios'; - -// 基础配置 -export const API_BASE_URL = '/api'; -// 全局axios配置 -// axios.defaults.baseURL = API_BASE_URL; - -// 获取OSS完整URL -export const getOssUrl = (path: string): string => { - // 获取全局配置中的OSS_HOST,如果不存在使用默认值 - const ossHost = (window.CONFIG?.OSS_BASE_URL) || ''; - // 确保path不以/开头 - const ossPath = path.startsWith('/') ? path.substring(1) : path; - return `${ossHost}/${ossPath}`; -}; - -export * from './auth'; -export * from './users'; -export * from './files'; -export * from './theme'; -export * from './charts'; -export * from './messages'; -export * from './sys'; -export * from './know_info'; -export * from './maps'; -export * from './zichan' -export * from './zichan_category' -export * from './zichan_area' -export * from './zichan_transfer' -export * from './device_instance' -export * from './device_type' -export * from './rack' -export * from './rack_server' -export * from './rack_server_type' -export * from './monitor' -export * from './alert' -export * from './alert_handle' -export * from './alert_notify_config' -export * from './device_alert_rule' -export * from './monitor_charts' -export * from './work_orders' \ No newline at end of file diff --git a/src/client/admin/api/inspections.ts b/src/client/admin/api/inspections.ts deleted file mode 100644 index 8f6fa32..0000000 --- a/src/client/admin/api/inspections.ts +++ /dev/null @@ -1,301 +0,0 @@ -import axios from 'axios'; - -// 检查模板类型 -export interface InspectionTemplate { - id: number; - name: string; - description?: string; - items: InspectionItem[]; - createdAt: string; - updatedAt: string; -} - -export interface InspectionItem { - id: number; - name: string; - description?: string; - required: boolean; -} - -// 检查任务类型 -export interface InspectionTask { - id: number; - templateId: number; - name: string; - taskNo: string; - description?: string; - status: 'pending' | 'in_progress' | 'completed' | 'failed'; - startTime?: string; - endTime?: string; - createdAt: string; - updatedAt: string; - schedule_type?: 'manual' | 'scheduled' | 'yearly'; - cronExpression?: string; - intervalDays?: number; - deviceTypes?: string[]; - run_immediately?: boolean; - progress?: number; - checked_count?: number; - issues_found?: number; -} - -// 检查结果类型 -export interface InspectionResult { - id: number; - taskId: number; - itemId: number; - status: 'passed' | 'failed' | 'skipped'; - notes?: string; - createdAt: string; -} - -// 报告接收者类型 -export interface ReportReceiver { - id: number; - name: string; - email: string; - createdAt: string; -} - -// API响应类型 -export interface ListResponse { - data: T[]; - total: number; - page: number; - pageSize: number; -} - -export interface SingleResponse { - data: T; - message?: string; -} - -export interface CreateResponse { - data: T; - message: string; -} - -export interface UpdateResponse { - data: T; - message: string; -} - -export interface DeleteResponse { - message: string; -} - -// 检查API接口定义 -export const InspectionsAPI = { - // 模板相关API - getTemplates: async (params?: { - page?: number; - limit?: number; - name?: string; - }): Promise> => { - try { - const response = await axios.get('/inspections/templates', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - createTemplate: async (data: Omit): Promise> => { - try { - const response = await axios.post('/inspections/templates', data); - return response.data; - } catch (error) { - throw error; - } - }, - - updateTemplate: async (id: number, data: Partial): Promise> => { - try { - const response = await axios.put(`/inspections/templates/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - createAutoInspectionTask: async (data: { - taskNo: string; - cronExpression?: string; - intervalDays?: number; - deviceTypes?: string[]; - reportReceivers?: string[]; - }): Promise> => { - try { - const response = await axios.post('/inspections/auto-tasks', data); - return response.data; - } catch (error) { - throw error; - } - }, - - deleteTemplate: async (id: number): Promise => { - try { - const response = await axios.delete(`/inspections/templates/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 任务相关API - getTasks: async (params?: { - page?: number; - limit?: number; - status?: string; - templateId?: number; - }): Promise> => { - try { - const response = await axios.get('/inspections/tasks', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - createTask: async (data: Omit): Promise> => { - try { - const response = await axios.post('/inspections/tasks', data); - return response.data; - } catch (error) { - throw error; - } - }, - - createAutoTask: async (data: { - name: string; - intervalDays: number; - deviceTypes?: string[]; - }): Promise> => { - try { - const response = await axios.post('/inspections/tasks/auto', data); - return response.data; - } catch (error) { - throw error; - } - }, - - runManualTask: async (data: { - deviceTypes?: string[]; - }): Promise> => { - try { - const response = await axios.post('/inspections/tasks/manual', data); - return response.data; - } catch (error) { - throw error; - } - }, - - runTask: async (id: number): Promise> => { - try { - const response = await axios.post(`/inspections/tasks/${id}/run`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 结果相关API - getResults: async (params?: { - page?: number; - limit?: number; - taskId?: number; - status?: string; - }): Promise> => { - try { - const response = await axios.get('/inspections/results', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - exportReport: async (id: number): Promise => { - try { - const response = await axios.get(`/inspections/results/${id}/export`, { - responseType: 'blob' - }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 接收者相关API - getReceivers: async (params?: { - page?: number; - limit?: number; - }): Promise> => { - try { - const response = await axios.get('/inspections/receivers', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - addReceiver: async (data: Omit): Promise> => { - try { - const response = await axios.post('/inspections/receivers', data); - return response.data; - } catch (error) { - throw error; - } - }, - - removeReceiver: async (id: number): Promise => { - try { - const response = await axios.delete(`/inspections/receivers/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 新年巡检专用API - createNewYearTask: async (data: { - id: string; - time: string; - deviceType: string; - status: string; - issuesFound: number; - receiverId: string; - }): Promise> => { - try { - const response = await axios.post('/inspections/tasks/new-year', data); - return response.data; - } catch (error) { - throw error; - } - }, - - updateProgress: async (id: number, data: { - progress: number; - checkedCount: number; - issuesFound: number; - }): Promise> => { - try { - const response = await axios.put(`/inspections/tasks/${id}/progress`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - completeInspection: async (id: number, results: { - itemId: number; - status: 'passed' | 'failed' | 'skipped'; - notes?: string; - }[]): Promise> => { - try { - const response = await axios.post(`/inspections/tasks/${id}/complete`, { results }); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/know_info.ts b/src/client/admin/api/know_info.ts deleted file mode 100644 index 0c6320e..0000000 --- a/src/client/admin/api/know_info.ts +++ /dev/null @@ -1,92 +0,0 @@ -import axios from 'axios'; -import type { KnowInfo } from '../@/share/types'; - -export interface KnowInfoListResponse { - data: KnowInfo[]; - pagination: { - current: number; - pageSize: number; - total: number; - totalPages: number; - }; -} - -interface KnowInfoResponse { - data: KnowInfo; - message?: string; -} - -interface KnowInfoCreateResponse { - message: string; - data: KnowInfo; -} - -interface KnowInfoUpdateResponse { - message: string; - data: KnowInfo; -} - -interface KnowInfoDeleteResponse { - message: string; - id: number; -} - - -// 知识库API -export const KnowInfoAPI = { - // 获取知识库列表 - getKnowInfos: async (params?: { - page?: number; - pageSize?: number; - title?: string; - category?: string; - tags?: string; - }): Promise => { - try { - const response = await axios.get('/know-infos', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个知识详情 - getKnowInfo: async (id: number): Promise => { - try { - const response = await axios.get(`/know-infos/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建知识 - createKnowInfo: async (data: Partial): Promise => { - try { - const response = await axios.post('/know-infos', data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新知识 - updateKnowInfo: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/know-infos/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除知识 - deleteKnowInfo: async (id: number): Promise => { - try { - const response = await axios.delete(`/know-infos/${id}`); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/maps.ts b/src/client/admin/api/maps.ts deleted file mode 100644 index b5584a5..0000000 --- a/src/client/admin/api/maps.ts +++ /dev/null @@ -1,62 +0,0 @@ -import axios from 'axios'; -import type { - LoginLocation, LoginLocationDetail, -} from '../@/share/types'; - - -// 地图相关API的接口类型定义 -export interface LoginLocationResponse { - message: string; - data: LoginLocation[]; -} - -export interface LoginLocationDetailResponse { - message: string; - data: LoginLocationDetail; -} - -export interface LoginLocationUpdateResponse { - message: string; - data: LoginLocationDetail; -} - -// 地图相关API -export const MapAPI = { - // 获取地图标记点数据 - getMarkers: async (params?: { - startTime?: string; - endTime?: string; - userId?: number - }): Promise => { - try { - const response = await axios.get(`/map/markers`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取登录位置详情 - getLocationDetail: async (locationId: number): Promise => { - try { - const response = await axios.get(`/map/location/${locationId}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新登录位置信息 - updateLocation: async (locationId: number, data: { - longitude: number; - latitude: number; - location_name?: string; - }): Promise => { - try { - const response = await axios.put(`/map/location/${locationId}`, data); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/messages.ts b/src/client/admin/api/messages.ts deleted file mode 100644 index 9fa4d7b..0000000 --- a/src/client/admin/api/messages.ts +++ /dev/null @@ -1,79 +0,0 @@ -import axios from 'axios'; -import type { UserMessage, Message } from '../@/share/types'; - -interface MessagesResponse { - data: UserMessage[]; - pagination: { - total: number; - current: number; - pageSize: number; - totalPages: number; - }; -} - -interface MessageResponse { - data: Message; - message?: string; -} - -interface MessageCountResponse { - count: number; -} - -export const MessageAPI = { - getMessages: async (params?: { - page?: number, - pageSize?: number, - type?: string, - status?: string, - search?: string - }): Promise => { - try { - const response = await axios.get('/messages', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - sendMessage: async (data: { - title: string, - content: string, - type: string, - receiver_ids: number[] - }): Promise => { - try { - const response = await axios.post('/messages', data); - return response.data; - } catch (error) { - throw error; - } - }, - - getUnreadCount: async (): Promise => { - try { - const response = await axios.get('/messages/count/unread'); - return response.data; - } catch (error) { - throw error; - } - }, - - markAsRead: async (id: number): Promise => { - try { - const response = await axios.post(`/messages/${id}/read`); - return response.data; - } catch (error) { - throw error; - } - }, - - deleteMessage: async (id: number): Promise => { - try { - const response = await axios.delete(`/messages/${id}`); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/modbus_rtu_device.ts b/src/client/admin/api/modbus_rtu_device.ts deleted file mode 100644 index 9f7f35b..0000000 --- a/src/client/admin/api/modbus_rtu_device.ts +++ /dev/null @@ -1,47 +0,0 @@ -import axios from 'axios'; - -export interface DeviceMonitorData { - id: number; - device_id: number; - device_name: string; - protocol: string; - address: string; - metric_type: string; - metric_value: number; - unit?: string; - status: string; - collect_time: Date; -} - -export const ModbusRtuDeviceAPI = { - // 获取Modbus RTU设备监控数据 - getMonitorData: async (params: { - page?: number; - pageSize?: number; - device_id?: number; - device_name?: string; - protocol?: string; - address?: string; - metric_type?: string; - status?: string; - }) => { - const response = await axios.get('/modbus-rtu-device/monitor-data', { params }); - return { - data: response.data.data || [], - total: response.data.total || 0, - }; - }, - - // 测试Modbus RTU设备连接 - testConnection: async (data: { - protocol: string; - address: string; - baud_rate: number; - data_bits: number; - stop_bits: number; - parity: string; - }) => { - const response = await axios.post('/modbus-rtu-device/test-connection', data); - return response.data; - }, -}; \ No newline at end of file diff --git a/src/client/admin/api/monitor.ts b/src/client/admin/api/monitor.ts deleted file mode 100644 index 7145352..0000000 --- a/src/client/admin/api/monitor.ts +++ /dev/null @@ -1,179 +0,0 @@ -import axios from 'axios'; -import { DeviceMapStats, DeviceMonitorData, DeviceStatus, DeviceTreeNode, DeviceTreeStats, MapViewDevice } from "@/share/monitorTypes"; - -interface DeviceMonitorDataResponse { - data: DeviceMonitorData[]; - total: number; - page: number; - pageSize: number; - } - - interface DeviceMonitorResponse { - data: DeviceMonitorData; - message?: string; - } - - interface MonitorCreateResponse { - data: DeviceMonitorData; - message: string; - } - - interface MonitorUpdateResponse { - data: DeviceMonitorData; - message: string; - } - - interface MonitorDeleteResponse { - message: string; - } - -// 监控API接口定义 -export const MonitorAPI = { - // 获取监控数据 - getMonitorData: async (params?: { - page?: number, - limit?: number, - device_id?: number, - device_type?: string, - start_time?: string, - end_time?: string - }): Promise => { - try { - const response = await axios.get(`/monitor/data`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取设备树数据 - getDeviceTree: async (params?: { - status?: string, - keyword?: string - }): Promise<{ data: DeviceTreeNode[] }> => { - try { - const response = await axios.get(`/monitor/devices/tree`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取设备树统计数据 - getDeviceTreeStats: async (): Promise<{ data: DeviceTreeStats }> => { - try { - const response = await axios.get(`/monitor/devices/tree/statistics`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取设备地图数据 - getDeviceMapData: async (params?: { - type_code?: string, - device_status?: DeviceStatus, - keyword?: string, - device_id?: number - }): Promise<{ data: MapViewDevice[], stats: DeviceMapStats }> => { - try { - const response = await axios.get(`/monitor/devices/map`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个监控数据 - getMonitor: async (id: number): Promise => { - try { - const response = await axios.get(`/monitor/data/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建监控数据 - createMonitor: async (data: Partial): Promise => { - try { - const response = await axios.post(`/monitor/data`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新监控数据 - updateMonitor: async (id: number, data: Partial): Promise => { - try { - const response = await axios.put(`/monitor/data/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除监控数据 - deleteMonitor: async (id: number): Promise => { - try { - const response = await axios.delete(`/monitor/data/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; - - // 获取烟雾/水浸传感器状态 - export async function getDeviceStatus(params: { - device_id: number; - device_type: 'smoke' | 'water'; - }): Promise<{ - status: 0 | 1; - timestamp: string; - }> { - try { - const response = await axios.get(`/monitor/devices/smoke-water/status`, { params }); - return response.data; - } catch (error) { - throw error; - } - } - - // 获取烟雾/水浸传感器历史数据 - export async function getDeviceHistory(params: { - device_id: number; - device_type: 'smoke' | 'water'; - start_time: string; - end_time: string; - interval?: number; - }): Promise<{ - data: Array<{ - timestamp: string; - status: 0 | 1; - }>; - }> { - try { - const response = await axios.get(`/monitor/devices/smoke-water/history`, { params }); - return response.data; - } catch (error) { - throw error; - } - } - - // 获取最新温湿度数据 - export async function getLatestTemperatureHumidity(params: { - device_id?: number; - } = {}): Promise<{ - temperature: number; - humidity: number; - timestamp: string; - }> { - try { - const response = await axios.get(`/monitor/data/latest-temperature-humidity`, { params }); - return response.data; - } catch (error) { - throw error; - } - } \ No newline at end of file diff --git a/src/client/admin/api/monitor_charts.ts b/src/client/admin/api/monitor_charts.ts deleted file mode 100644 index 4b9107d..0000000 --- a/src/client/admin/api/monitor_charts.ts +++ /dev/null @@ -1,69 +0,0 @@ -import axios from 'axios'; -import { AlarmChartData, CategoryChartData, CategoryChartDataWithPercent, OnlineRateChartData, StateChartData, StateChartDataWithPercent } from "@/share/monitorTypes"; - -export const MonitorChartsAPI = { - // 资产分类数据查询 - fetchCategoryData: async (): Promise => { - const res = await axios.get(`/big/zichan_category_chart`); - - // 预先计算百分比 - const data = res.data; - const total = data.reduce((sum: number, item: CategoryChartData) => sum + item['设备数'], 0); - - // 为每个数据项添加百分比字段 - return data.map(item => ({ - ...item, - 百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0' - })); - }, - - // 在线率变化数据查询 - fetchOnlineRateData: async (params?: { - created_at_gte?: string; - created_at_lte?: string; - dimension?: string; - }): Promise => { - // 可选参数 - // const params = { - // created_at_gte: dayjs().subtract(7, 'day').format('YYYY-MM-DD HH:mm:ss'), - // created_at_lte: dayjs().format('YYYY-MM-DD HH:mm:ss'), - // dimension: 'day' - // }; - - const res = await axios.get(`/big/zichan_online_rate_chart`, { params }); - return res.data; - }, - - // 资产流转状态数据查询 - fetchStateData: async (): Promise => { - const res = await axios.get(`/big/zichan_state_chart`); - - // 预先计算百分比 - const data = res.data; - const total = data.reduce((sum: number, item: StateChartData) => sum + item['设备数'], 0); - - // 为每个数据项添加百分比字段 - return data.map(item => ({ - ...item, - 百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0' - })); - }, - - // 告警数据变化查询 - fetchAlarmData: async (params?: { - created_at_gte?: string; - created_at_lte?: string; - dimension?: string; - }): Promise => { - // 可选参数 - // const params = { - // created_at_gte: dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), - // created_at_lte: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss'), - // dimension: 'hour' - // }; - - - const res = await axios.get(`/big/zichan_alarm_chart`, { params }); - return res.data; - } - }; \ No newline at end of file diff --git a/src/client/admin/api/rack.ts b/src/client/admin/api/rack.ts deleted file mode 100644 index 73633e0..0000000 --- a/src/client/admin/api/rack.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from 'axios'; -import { RackInfo } from "@/share/monitorTypes"; -// 机柜管理API接口定义 -export const RackAPI = { - // 获取机柜列表 - getRackList: async (params?: { - page?: number, - limit?: number, - rack_name?: string, - rack_code?: string, - area?: string - }) => { - try { - const response = await axios.get(`/racks`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个机柜信息 - getRack: async (id: number) => { - try { - const response = await axios.get(`/racks/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建机柜 - createRack: async (data: Partial) => { - try { - const response = await axios.post(`/racks`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新机柜 - updateRack: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/racks/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除机柜 - deleteRack: async (id: number) => { - try { - const response = await axios.delete(`/racks/${id}`); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/rack_server.ts b/src/client/admin/api/rack_server.ts deleted file mode 100644 index da94483..0000000 --- a/src/client/admin/api/rack_server.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from 'axios'; -import { RackServer } from "@/share/monitorTypes"; -// 机柜服务器API接口定义 -export const RackServerAPI = { - // 获取机柜服务器列表 - getRackServerList: async (params?: { - page?: number, - limit?: number, - rack_id?: number, - asset_id?: number, - server_type?: string - }) => { - try { - const response = await axios.get(`/rack-servers`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个机柜服务器信息 - getRackServer: async (id: number) => { - try { - const response = await axios.get(`/rack-servers/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建机柜服务器 - createRackServer: async (data: Partial) => { - try { - const response = await axios.post(`/rack-servers`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新机柜服务器 - updateRackServer: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/rack-servers/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除机柜服务器 - deleteRackServer: async (id: number) => { - try { - const response = await axios.delete(`/rack-servers/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/rack_server_type.ts b/src/client/admin/api/rack_server_type.ts deleted file mode 100644 index e5fc415..0000000 --- a/src/client/admin/api/rack_server_type.ts +++ /dev/null @@ -1,69 +0,0 @@ -import axios from 'axios'; -import { RackServerType } from "@/share/monitorTypes"; - -// 机柜服务器类型API响应类型 -interface RackServerTypeResponse { - data: RackServerType[]; - pagination: { - total: number; - current: number; - pageSize: number; - }; - } -// 机柜服务器类型API接口定义 -export const RackServerTypeAPI = { - // 获取机柜服务器类型列表 - getRackServerTypeList: async (params?: { - page?: number, - limit?: number, - name?: string, - code?: string - }): Promise => { - try { - const response = await axios.get(`/rack-server-types`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个机柜服务器类型信息 - getRackServerType: async (id: number) => { - try { - const response = await axios.get(`/rack-server-types/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建机柜服务器类型 - createRackServerType: async (data: Partial) => { - try { - const response = await axios.post(`/rack-server-types`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新机柜服务器类型 - updateRackServerType: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/rack-server-types/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除机柜服务器类型 - deleteRackServerType: async (id: number) => { - try { - const response = await axios.delete(`/rack-server-types/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/sms.ts b/src/client/admin/api/sms.ts deleted file mode 100644 index c5ce2a3..0000000 --- a/src/client/admin/api/sms.ts +++ /dev/null @@ -1,25 +0,0 @@ -import axios from 'axios' - -export const smsApi = { - checkLogin: () => axios.get('/sms/check-login'), - login: (data: { username: string; password: string }) => - axios.post('/sms/login', data), - send: (data: { phone: string; content: string; taskId?: string }) => - axios.post('/sms/send', data), - getStatus: () => axios.get('/sms/status'), - getList: () => axios.get('/sms/list') -} - -export type SmsStatus = { - signalStrength: number - carrier: string - mode: string -} - -export type SmsItem = { - id: string - phone: string - content: string - status: string - createdAt: string -} \ No newline at end of file diff --git a/src/client/admin/api/sys.ts b/src/client/admin/api/sys.ts deleted file mode 100644 index 9f70dd6..0000000 --- a/src/client/admin/api/sys.ts +++ /dev/null @@ -1,47 +0,0 @@ -import axios from 'axios'; - -import type { - SystemSetting, SystemSettingGroupData, -} from '../@/share/types'; - -export const SystemAPI = { - // 获取所有系统设置 - getSettings: async (): Promise => { - try { - const response = await axios.get('/settings'); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 获取指定分组的系统设置 - getSettingsByGroup: async (group: string): Promise => { - try { - const response = await axios.get(`/settings/group/${group}`); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 更新系统设置 - updateSettings: async (settings: Partial[]): Promise => { - try { - const response = await axios.put('/settings', settings); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 重置系统设置 - resetSettings: async (): Promise => { - try { - const response = await axios.post('/settings/reset'); - return response.data.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/theme.ts b/src/client/admin/api/theme.ts deleted file mode 100644 index b2fc8a5..0000000 --- a/src/client/admin/api/theme.ts +++ /dev/null @@ -1,36 +0,0 @@ -import axios from 'axios'; -import type { ThemeSettings } from '@/share/types'; - -export interface ThemeSettingsResponse { - message: string; - data: ThemeSettings; -} - -export const ThemeAPI = { - getThemeSettings: async (): Promise => { - try { - const response = await axios.get('/theme'); - return response.data.data; - } catch (error) { - throw error; - } - }, - - updateThemeSettings: async (themeData: Partial): Promise => { - try { - const response = await axios.put('/theme', themeData); - return response.data.data; - } catch (error) { - throw error; - } - }, - - resetThemeSettings: async (): Promise => { - try { - const response = await axios.post('/theme/reset'); - return response.data.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/users.ts b/src/client/admin/api/users.ts deleted file mode 100644 index f0aa3b5..0000000 --- a/src/client/admin/api/users.ts +++ /dev/null @@ -1,79 +0,0 @@ -import axios from 'axios'; -import type { User } from '../@/share/types'; - -interface UsersResponse { - data: User[]; - pagination: { - total: number; - current: number; - pageSize: number; - totalPages: number; - }; -} - -interface UserResponse { - data: User; - message?: string; -} - -interface UserCreateResponse { - message: string; - data: User; -} - -interface UserUpdateResponse { - message: string; - data: User; -} - -interface UserDeleteResponse { - message: string; - id: number; -} - -export const UserAPI = { - getUsers: async (params?: { page?: number, limit?: number, search?: string }): Promise => { - try { - const response = await axios.get('/users', { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - getUser: async (userId: number): Promise => { - try { - const response = await axios.get(`/users/${userId}`); - return response.data; - } catch (error) { - throw error; - } - }, - - createUser: async (userData: Partial): Promise => { - try { - const response = await axios.post('/users', userData); - return response.data; - } catch (error) { - throw error; - } - }, - - updateUser: async (userId: number, userData: Partial): Promise => { - try { - const response = await axios.put(`/users/${userId}`, userData); - return response.data; - } catch (error) { - throw error; - } - }, - - deleteUser: async (userId: number): Promise => { - try { - const response = await axios.delete(`/users/${userId}`); - return response.data; - } catch (error) { - throw error; - } - } -}; \ No newline at end of file diff --git a/src/client/admin/api/work_orders.ts b/src/client/admin/api/work_orders.ts deleted file mode 100644 index 738bc57..0000000 --- a/src/client/admin/api/work_orders.ts +++ /dev/null @@ -1,308 +0,0 @@ -import axios from 'axios'; -import { WorkOrder, WorkOrderSettings, WorkOrderStatus, WorkOrderPriority } from '@/share/monitorTypes' - -// 分类数据缓存 -let categoriesCache: string[] | null = null; -let cacheExpireTime = 0; -const CACHE_EXPIRE_DURATION = 5 * 60 * 1000; // 5分钟缓存 - -interface WorkOrderListParams { - page?: number; - pageSize?: number; - status?: WorkOrderStatus; - priority?: WorkOrderPriority; - device_id?: number; - creator_id?: number; -} - -interface WorkOrderListResponse { - data: WorkOrder[]; - pagination: { - total: number; - current: number; - pageSize: number; - totalPages: number; - }; -} - -interface WorkOrderDetailResponse { - data: WorkOrder; - message?: string; -} - -interface WorkOrderCreateResponse { - message: string; - data: WorkOrder; -} - -interface WorkOrderUpdateResponse { - message: string; - data: WorkOrder; -} - -interface WorkOrderStatusChangeResponse { - message: string; - status: string; - history_id?: string; -} - -interface WorkOrderDeadlineResponse { - data: { - deadline: string; - remaining_hours: number; - is_overdue: boolean; - }; -} - -interface WorkOrderHistoryItem { - id: string; - status_from: string; - status_to: string; - operator: string; - comment?: string; - created_at: string; -} - -interface WorkOrderHistoryResponse { - data: WorkOrderHistoryItem[]; - pagination: { - total: number; - current: number; - pageSize: number; - }; -} - -interface WorkOrderAssignResponse { - message: string; - assignee: string; -} - -interface WorkOrderSettingsResponse { - data: WorkOrderSettings; - message?: string; -} - -interface WorkOrderCategoryResponse { - data: string[]; - message?: string; -} - -interface WorkOrderAttachmentResponse { - message: string; - data: { - id: string; - url: string; - name: string; - }; -} - -interface WorkOrderCommentResponse { - message: string; - data: { - id: string; - content: string; - created_at: string; - author: string; - }[]; -} - -export const WorkOrderAPI = { - getList: async (params?: WorkOrderListParams): Promise => { - try { - const response = await axios.get('/work-orders', { params }); - return response.data; - } catch (error) { - if (axios.isAxiosError(error)) { - const errorMessage = error.response?.data?.message || error.message; - throw new Error(`获取工单列表失败: ${errorMessage}`); - } - throw new Error('获取工单列表失败: 未知错误'); - } - }, - - create: async (data: { - title: string; - device_id?: number; - problem_desc: string; - problem_type: string; - priority: WorkOrderPriority; - deadline?: Date; - attachments?: Array<{ - id: string; - url: string; - name: string; - }>; - }): Promise => { - try { - const response = await axios.post('/work-orders', data); - return response.data; - } catch (error) { - throw error; - } - }, - - getDetail: async (id: string): Promise => { - try { - const response = await axios.get(`/work-orders/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - update: async (id: string, data: { - status?: WorkOrderStatus; - priority?: WorkOrderPriority; - feedback?: string; - assignee_id?: number; - deadline?: Date; - }): Promise => { - try { - const response = await axios.put(`/work-orders/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - changeStatus: async (id: string, status: string, operator: string, comment?: string): Promise => { - try { - const response = await axios.post(`/work-orders/${id}/status`, { - status, - operator, - comment - }); - return response.data; - } catch (error) { - throw error; - } - }, - - getDeadline: async (id: string): Promise => { - try { - const response = await axios.get(`/work-orders/${id}/deadline`); - return response.data; - } catch (error) { - throw error; - } - }, - - getHistory: async (id: string, page = 1, pageSize = 10): Promise => { - try { - const response = await axios.get(`/work-orders/${id}/history`, { - params: { page, pageSize } - }); - return response.data; - } catch (error) { - throw error; - } - }, - - getStatusHistory: async (id: string): Promise => { - try { - const response = await axios.get(`/work-orders/${id}/status-history`); - return response.data; - } catch (error) { - throw error; - } - }, - - assign: async (id: string, assignee: string): Promise => { - try { - const response = await axios.post(`/work-orders/${id}/assign`, { assignee }); - return response.data; - } catch (error) { - throw error; - } - }, - - getSettings: async (): Promise => { - try { - const response = await axios.get('/work-orders/settings'); - return response.data; - } catch (error) { - throw error; - } - }, - - updateSettings: async (data: WorkOrderSettings): Promise => { - try { - const response = await axios.post('/work-orders/settings', data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 新增分类相关API - getCategories: async (): Promise => { - // 检查缓存是否有效 - if (categoriesCache && Date.now() < cacheExpireTime) { - return { data: categoriesCache }; - } - - try { - const response = await axios.get('/work-orders/categories'); - categoriesCache = response.data.data; - cacheExpireTime = Date.now() + CACHE_EXPIRE_DURATION; - return response.data; - } catch (error) { - if (axios.isAxiosError(error)) { - const errorMessage = error.response?.data?.message || error.message; - throw new Error(`获取分类失败: ${errorMessage}`); - } - throw new Error('获取分类失败: 未知错误'); - } - }, - - // 新增附件相关API - uploadAttachment: async (id: string, file: File): Promise => { - try { - const formData = new FormData(); - formData.append('file', file); - const response = await axios.post(`/work-orders/${id}/attachments`, formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 新增评论相关API - getComments: async (id: string): Promise => { - try { - const response = await axios.get(`/work-orders/${id}/comments`); - return response.data; - } catch (error) { - throw error; - } - }, - - addComment: async (id: string, content: string): Promise => { - try { - const response = await axios.post(`/work-orders/${id}/comments`, { content }); - return response.data; - } catch (error) { - throw error; - } - }, - - exportList: async (params?: WorkOrderListParams): Promise => { - try { - const response = await axios.get('/work-orders/export', { - params, - responseType: 'blob' - }); - return response.data; - } catch (error) { - if (axios.isAxiosError(error)) { - const errorMessage = error.response?.data?.message || error.message; - throw new Error(`导出工单失败: ${errorMessage}`); - } - throw new Error('导出工单失败: 未知错误'); - } - }, -}; \ No newline at end of file diff --git a/src/client/admin/api/yangantubiao.png b/src/client/admin/api/yangantubiao.png deleted file mode 100644 index ed79dcbee809321a83dcbd685792a4771994f6c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1801 zcmV+k2ln`hP)Px#1am@3R0s$N2z&@+hyVZvvq?ljRCt{2oL^qsI1tBwBf8z!9Kk(7(+hxjWCI@* zF3@m-g%bo$06hWe1&I3zSsrnEfn{&ty@B^}`$w}64h2gNmTf5wpY`{tku@WAY)PZh z2%w{*qobpvqobpvqod=6L9P|I)%JI%uMWL51o*G#p1*QYLM(Qf^b|nv zv6OoN^^B@%&HnvFs{7BZKQ7{f$#Ytq7ZDSwW|B$V6_i!X7n{WP@a8>%CQr+n^uKZl zcRWvLz?O6dY>Cq%(gdk)BZeM>C@Oo-jQcW2RjQd{h{IAj;*2ytCGlWVl$rK4UY(E- zZ^72Wu$0puLd?%W3@FuY#H8;4L;#o8A%bM+K7idvx%r3*SNX3JIR==Zw$&{993Pzh zC>F0Th%w4VR>=t^)%;x&5AGZP$4Uq^gunFxMAh|3KK-RZZ(X#L5XE{7Szfb<26kNP z)ghDt6M!>^is{oIRZi;zthG^()6$9t0$8}zt3!|>oW#RSs@af9j8|47&Pa>XqTFC2 z)lH8{tlT~^e_sfEyDX~OI%c8AV2W0^#fv1~_d0w5?*iP@rc(U%)}Q`P3={1!;tDWBH}aq$*Z)kt`-LTb`NXsOFb5^ubS&z z54ne6xI2WnD)v`KV*@7fFeBFXuwf{vW~!;)(8#0t8Eg+BHRoejTF(O8M}J%p^GUIk zGvcprmZ7kXL8_Tz5>vCB7{{h_?n>)9@bmR=rvQxAZS#h5?%azDvz&=8n;H-Bw1^Ok zq2>26lvFn-D@4`~3w?o-p}YJ58slLh&ynipgo&&-jLiwMMrHxAc++IzDClS-Y_c=F zxihzCn+h?XE`82cc=ZE`zYeiEU)*X46CcI{JR5MOYqMFTq{X=}WnPEy1NnK$))-(} zc)*y1Xs;B4L}A|Jj9E-v4$JE`6Nqg!J0l~Qu$T^lwP&)U=2xJ?d3L&(4&L}~rwi$~ zq%&YkIs>+3tp;3cs7qiiZ3R1(6F|a1VG&_^eJ9nd)z--c4M;GIh!CYA+6rdCJYM)q zX2jk4W;#d+%9eU!{urXIU_&@}s^QIHZAscD;_@6p=fEW1za;wUMofvtZgsV01Ybmw z>%kc@jNe8Q zhXVTVBtD4iw>y)-k7HKv1UFzYPo`cRE|>lO=fg_Qtk0k3#8gp8jwi&6>%p*Q_bUlH z2b#tM04A)?Rw&3$d*<;Yk$CUk_DmI$;~6R+1%3%;z%t|Ikkh=R#jj&q7S3}T)7I~H zsqj801~cF?DLpdFiTOj7tW>v=eN_5zcvX#5w~-Z#BuqlA#)0w!=OZq;)s&yV?oX=Z z^F$#-mT;a--3*|i56#J)w0=1|5hhr}Cto}qcLQu0KTWO&XRhs&F!KdD3$+S|8p1^b z5aODLI0boTs^`8~X}-dHTH6oJz7#dusE!VM$ksD}fcRvV5%bA1Gs`Ps77-ScVhQ!6nvY!N z1;}!Bi21}d*CvVgQYNzITxN`Sj^z}6$WF%*mXA{^uOn@ zvc>=~R2~`kW5gf^%v}afjN<5FWADOp&ugd65tk(}mGohlEgkQ)i zY;ippmd=Sa^sFMN8^dfmuqF4{Dp)Zc)Q+m;iIk=ul5OY0pavwtR_B^@n3;_mM)h9= z05D3En>N#cQr$)+ci8TmgxRgTac02SNIu=L-%e`^YWgU_eB~ALp zqVx(`s~BN`z3{wW9!TX3+H|n1bUj3+i;!;mBFsgMlC}p=UJaOkc~})lJ+2$jT>5IJ zu11ICEcDo%JAGs;c>V&*?+Fv(yzYsbRP%n>AN(Okvx6b)>+_SX;5#C14F_UAO{RVl zy7Q>CpBv}C(oQ@lnI9y}RE~Q9_xaypX=c169^5~5eu;2iCn{}1OpjS%gh{AenT
    9exq;Xrj%SqG00000NkvXXu0mjfLAz8n diff --git a/src/client/admin/api/zichan.ts b/src/client/admin/api/zichan.ts deleted file mode 100644 index 5af80f4..0000000 --- a/src/client/admin/api/zichan.ts +++ /dev/null @@ -1,61 +0,0 @@ -import axios from "axios"; -import { DeviceCategory, DeviceStatus, ZichanInfo } from "@/share/monitorTypes"; - -// 资产管理API -export const ZichanAPI = { - // 获取资产列表 - getZichanList: async (params?: { - page?: number, - limit?: number, - asset_name?: string, - device_category?: DeviceCategory, - device_status?: DeviceStatus - }) => { - try { - const response = await axios.get(`/zichan`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个资产 - getZichan: async (id: number) => { - try { - const response = await axios.get(`/zichan/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建资产 - createZichan: async (data: Partial) => { - try { - const response = await axios.post(`/zichan`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新资产 - updateZichan: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/zichan/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除资产 - deleteZichan: async (id: number) => { - try { - const response = await axios.delete(`/zichan/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/zichan_area.ts b/src/client/admin/api/zichan_area.ts deleted file mode 100644 index a294878..0000000 --- a/src/client/admin/api/zichan_area.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from "axios"; -import { ZichanArea } from "@/share/monitorTypes"; - -// 资产归属区域API接口定义 -export const ZichanAreaAPI = { - // 获取资产归属区域列表 - getZichanAreaList: async (params?: { - page?: number, - limit?: number, - name?: string, - code?: string - }) => { - try { - const response = await axios.get(`/zichan-areas`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个资产归属区域信息 - getZichanArea: async (id: number) => { - try { - const response = await axios.get(`/zichan-areas/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建资产归属区域 - createZichanArea: async (data: Partial) => { - try { - const response = await axios.post(`/zichan-areas`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新资产归属区域 - updateZichanArea: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/zichan-areas/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除资产归属区域 - deleteZichanArea: async (id: number) => { - try { - const response = await axios.delete(`/zichan-areas/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/zichan_category.ts b/src/client/admin/api/zichan_category.ts deleted file mode 100644 index b012d63..0000000 --- a/src/client/admin/api/zichan_category.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from "axios"; -import { ZichanCategory } from "@/share/monitorTypes"; - -// 资产分类API接口定义 -export const ZichanCategoryAPI = { - // 获取资产分类列表 - getZichanCategoryList: async (params?: { - page?: number, - limit?: number, - name?: string, - code?: string - }) => { - try { - const response = await axios.get(`/zichan-categories`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个资产分类信息 - getZichanCategory: async (id: number) => { - try { - const response = await axios.get(`/zichan-categories/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建资产分类 - createZichanCategory: async (data: Partial) => { - try { - const response = await axios.post(`/zichan-categories`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新资产分类 - updateZichanCategory: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/zichan-categories/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除资产分类 - deleteZichanCategory: async (id: number) => { - try { - const response = await axios.delete(`/zichan-categories/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/admin/api/zichan_transfer.ts b/src/client/admin/api/zichan_transfer.ts deleted file mode 100644 index 2bcb74e..0000000 --- a/src/client/admin/api/zichan_transfer.ts +++ /dev/null @@ -1,54 +0,0 @@ -import axios from "axios"; -import { AssetTransferType, ZichanTransLog } from "@/share/monitorTypes"; -// 资产流转API接口定义 -export const ZichanTransferAPI = { - // 获取资产流转记录列表 - getTransferList: async (params?: { page?: number, limit?: number, asset_id?: number, asset_transfer?: AssetTransferType }) => { - try { - const response = await axios.get(`/zichan-transfer`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取资产流转记录详情 - getTransfer: async (id: number) => { - try { - const response = await axios.get(`/zichan-transfer/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建资产流转记录 - createTransfer: async (data: Partial) => { - try { - const response = await axios.post(`/zichan-transfer`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新资产流转记录 - updateTransfer: async (id: number, data: Partial) => { - try { - const response = await axios.put(`/zichan-transfer/${id}`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除资产流转记录 - deleteTransfer: async (id: number) => { - try { - const response = await axios.delete(`/zichan-transfer/${id}`); - return response.data; - } catch (error) { - throw error; - } - } - }; \ No newline at end of file diff --git a/src/client/big/api.ts b/src/client/big/api.ts deleted file mode 100644 index 441aaa9..0000000 --- a/src/client/big/api.ts +++ /dev/null @@ -1,381 +0,0 @@ -import axios from 'axios'; -import * as THREE from 'three'; -import { - AlarmDeviceData, - CategoryChartData, - CategoryChartDataWithPercent, - OnlineRateChartData, - StateChartData, - StateChartDataWithPercent, - AlarmChartData, - DeviceWithAssetInfo, -} from '@/share/monitorTypes'; - - -// API请求参数类型定义 -interface OnlineRateChartParams { - created_at?: { - $gte: string; - $lte: string; - }; - dimension?: { - $eq: 'hour' | 'day' | 'month'; - }; - pagination?: { - page: number; - pageSize: number; - }; -} - -interface AlarmChartParams { - created_at?: { - $gte: string; - $lte: string; - }; - dimension?: { - $eq: 'hour' | 'day' | 'month'; - }; -} - -interface DeviceInstancesParams { - is_deleted?: number; -} - -interface RackQueryParams { - is_deleted?: number; -} - -interface RackServerQueryParams { - rack_id?: number; - is_deleted?: number; -} - -interface RackServerTypeParams { - is_enabled?: number; - is_deleted?: number; - page?: number; - pageSize?: number; -} - -// 机柜数据 -interface RackData { - id: number; - rack_name: string; - rack_code: string; - capacity: number; - position_x: number; - position_y: number; - position_z: number; - area?: string; - room?: string; - remark?: string; - is_disabled: number; - is_deleted: number; - created_at: string; - updated_at: string; -} - -// 机柜服务器数据 -interface RackServerData { - id: number; - rack_id: number; - asset_id: number; - start_position: number; - size: number; - server_type?: string; - remark?: string; - is_disabled: number; - is_deleted: number; - created_at: string; - updated_at: string; - // 关联字段 asset - asset_name: string; - device_category: number; - ip_address?: string; - device_status: number; - network_status: number; - packet_loss: number; - cpu?: string; - memory?: string; - disk?: string; - // 关联字段 rack - rack_name: string; - rack_code: string; -} - -// 设备类型图标数据 -interface DeviceIconData { - id: number; - category_id: number; - icon: string; - icon_name?: string; - icon_type: string; - sort: number; - is_default: number; - is_disabled: number; - is_deleted: number; - created_at: string; - updated_at: string; - category?: { - name: string; - }; -} - -// 机柜服务器类型数据 -interface RackServerType { - /** 主键ID */ - id: number; - - /** 类型名称 */ - name: string; - - /** 类型编码 */ - code: string; - - /** 类型图片 */ - image_url?: string; - - /** 类型描述 */ - description?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: number; - - /** 是否被删除 (0否 1是) */ - is_deleted?: number; - - /** 创建时间 */ - created_at: string; - - /** 更新时间 */ - updated_at: string; -} - -// 服务器基本信息接口 -export interface ServerData { - id: number; - slot: number; - u: number; - type: number; - name: string; - ip: string; - cpu: string; - memory: string; - disk: string; - deviceStatus: number; - networkStatus: number; - packetLoss: number; -} - -// 设备状态和监控数据接口 -export interface DeviceStatusInfo { - status: 'online' | 'offline' | 'warning'; - usage: { - cpu: number; - memory: number; - disk: number; - }; -} - -// 机柜配置接口 -export interface RackConfig { - position: THREE.Vector3; - serverCount: number; - id: number; - name: string; - servers: ServerData[]; -} - -// 服务器图标接口 -export interface ServerIconConfig { - textureUrl: string | null; - color: number; -} - -export interface ServerIconConfigs { - [key: number]: ServerIconConfig; -} - - - - -const API_BASE_URL = '/api'; -// 从全局配置中获取OSS_HOST,如果不存在则使用默认值 -const OSS_BASE_URL = window.CONFIG?.OSS_BASE_URL || ''; - - -// 计算风险等级 -const calculateRiskLevel = (onlineRate: number): { level: string; color: string } => { - if (onlineRate >= 95) { - return { level: '健康', color: '#52c41a' }; // 绿色 - } else if (onlineRate >= 80) { - return { level: '风险低', color: '#faad14' }; // 黄色 - } else if (onlineRate >= 70) { - return { level: '风险中', color: '#faad14' }; // 黄色 - } else { - return { level: '高风险', color: '#f5222d' }; // 红色 - } -}; - -// 统一将查询函数提取到顶部 -export const queryFns = { - // 资产分类数据查询 - fetchCategoryData: async (): Promise => { - const res = await axios.get(`${API_BASE_URL}/big/zichan_category_chart`); - - // 预先计算百分比 - const data = res.data; - const total = data.reduce((sum: number, item: CategoryChartData) => sum + item['设备数'], 0); - - // 为每个数据项添加百分比字段 - return data.map(item => ({ - ...item, - 百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0' - })); - }, - - // 在线率变化数据查询 - fetchOnlineRateData: async (): Promise => { - // 直接使用扁平化参数 - const params = { - // created_at_gte: dayjs().subtract(7, 'day').format('YYYY-MM-DD HH:mm:ss'), - // created_at_lte: dayjs().format('YYYY-MM-DD HH:mm:ss'), - // dimension: 'day', - // page: 1, - // pageSize: 1000 - }; - - const res = await axios.get(`${API_BASE_URL}/big/zichan_online_rate_chart`, { params }); - return res.data; - }, - - // 资产流转状态数据查询 - fetchStateData: async (): Promise => { - const res = await axios.get(`${API_BASE_URL}/big/zichan_state_chart`); - - // 预先计算百分比 - const data = res.data; - const total = data.reduce((sum: number, item: StateChartData) => sum + item['设备数'], 0); - - // 为每个数据项添加百分比字段 - return data.map(item => ({ - ...item, - 百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0' - })); - }, - - // 告警数据变化查询 - fetchAlarmData: async (): Promise => { - // 直接使用扁平化参数 - const params = { - // created_at_gte: dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), - // created_at_lte: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss'), - // dimension: 'hour' - }; - - const res = await axios.get(`${API_BASE_URL}/big/zichan_alarm_chart`, { params }); - return res.data; - }, - - // 获取设备数据 - fetchDeviceMetrics: async () => { - const params = { - is_deleted: 0 - }; - const response = await axios.get(`${API_BASE_URL}/big/device-instances`, { params }); - - const devices = response.data; - const totalDevices = devices.length; - const onlineDevices = devices.filter(device => device.device_status === 1).length; - const offlineDevices = totalDevices - onlineDevices; - const onlineRate = totalDevices ? ((onlineDevices / totalDevices) * 100) : 0; - const riskLevel = calculateRiskLevel(onlineRate); - - return { - totalDevices, - onlineDevices, - offlineDevices, - onlineRate: onlineRate.toFixed(2), - riskLevel - }; - }, - - // 获取告警设备数据 - fetchTopAlarmDevices: async (): Promise => { - const response = await axios.get(`${API_BASE_URL}/big/zichan_alarm_device`); - return response.data; - }, - - // 获取机柜配置数据 - fetchRackConfigs: async (): Promise => { - const params: RackQueryParams = { - is_deleted: 0 - } - // 1. 获取机柜数据 - const rackResponse = await axios.get(`${API_BASE_URL}/big/rack`, { params }); - - const racks = rackResponse.data; - - // 2. 获取所有机柜的服务器数据 - const serverPromises = racks.map(async (rack: RackData) => { - const params: RackServerQueryParams = { - rack_id: rack.id, - is_deleted: 0 - } - const serverResponse = await axios.get(`${API_BASE_URL}/big/rack-server`, { params }); - - // 3. 获取每个服务器对应的基本信息 - let servers: ServerData[] = []; - try { - servers = serverResponse.data.map((server: RackServerData) => ({ - id: server.id, - slot: server.start_position, - u: server.size, - type: server.device_category, - name: server.asset_name || '', - ip: server.ip_address || '', - cpu: server.cpu || '', - memory: server.memory || '', - disk: server.disk || '', - deviceStatus: server.device_status || 0, - networkStatus: server.network_status || 0, - packetLoss: server.packet_loss || 0 - })); - } catch (error) { - console.error("error", error); - } - - // 返回包含服务器数据的机柜配置 - return { - position: new THREE.Vector3(Number(rack.position_x) || 0, Number(rack.position_y) || 0, Number(rack.position_z) || 0), - serverCount: rack.capacity || 42, - id: rack.id, - name: rack.rack_name, - servers - }; - }); - - // 等待所有数据获取完成 - return Promise.all(serverPromises); - }, - - // 获取服务器类型图标配置 - fetchDeviceIcons: async (): Promise => { - const params: RackServerTypeParams = { - is_enabled: 1, - is_deleted: 0, - page: 1, - pageSize: 100 - } - const response = await axios.get(`${API_BASE_URL}/big/rack/server/type/image`, { params }); - - const ossHost = OSS_BASE_URL; - return response.data.reduce((acc: ServerIconConfigs, type: RackServerType) => { - acc[type.id] = { - textureUrl: type.image_url ? `${ossHost}/${type.image_url}` : null, - color: 0x444444 - }; - return acc; - }, {}); - } -} diff --git a/src/client/big/client.tsx b/src/client/big/client.tsx deleted file mode 100644 index 2fdb46a..0000000 --- a/src/client/big/client.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import { DataCenterApp } from './components/DataCenter'; - -// 渲染应用 -const root = createRoot(document.getElementById('root') as HTMLElement); -root.render( - -); \ No newline at end of file diff --git a/src/client/big/components/AlarmDeviceTable.tsx b/src/client/big/components/AlarmDeviceTable.tsx deleted file mode 100644 index fb84656..0000000 --- a/src/client/big/components/AlarmDeviceTable.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { AlarmDeviceData } from '@/share/monitorTypes'; -import { bigClient } from '../../api'; - -export function AlarmDeviceTable() { - const { data = [], isLoading } = useQuery({ - queryKey: ['topAlarmDevices'], - queryFn: async () => { - const res = await bigClient['zichan-alarm-device'].$get({ - query:{ limit: 100 } - }); - if (res.status !== 200) throw new Error('Failed to fetch alarm devices'); - return await res.json(); - }, - refetchInterval: 30000 - }); - - if (isLoading) { - return ( -
    - 加载中... -
    - ); - } - - return ( -
    - {/* 表头 */} -
    -
    排名
    -
    告警次数
    -
    点位名称
    -
    - - {/* 表格内容 */} -
    - {data.map((item: AlarmDeviceData) => ( -
    -
    {item.rank}
    -
    {item.alarmCount}
    - - {item.deviceName} - -
    - ))} - - {data.length === 0 && ( -
    - 暂无数据 -
    - )} -
    -
    - ); -} \ No newline at end of file diff --git a/src/client/big/components/CustomCard.tsx b/src/client/big/components/CustomCard.tsx deleted file mode 100644 index 843cd1d..0000000 --- a/src/client/big/components/CustomCard.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { ReactNode } from 'react'; - -interface CustomCardProps { - title?: string; - children: ReactNode; - className?: string; - bodyStyle?: React.CSSProperties; - onClick?: () => void; -} - -export function CustomCard({ title, children, className = '', bodyStyle = {}, onClick }: CustomCardProps) { - return ( -
    - {title && ( -
    - {title} -
    - )} -
    - {children} -
    -
    - ); -} \ No newline at end of file diff --git a/src/client/big/components/DataCenter.tsx b/src/client/big/components/DataCenter.tsx deleted file mode 100644 index caebae7..0000000 --- a/src/client/big/components/DataCenter.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons'; -import { PageTitle } from './PageTitle'; -import { MetricCards } from './MetricCards'; -import { CustomCard } from './CustomCard'; -import { ServerMonitorCharts } from './ServerMonitorCharts'; -import { AlarmDeviceTable } from './AlarmDeviceTable'; -import { ThreeJSRoom } from './three/ThreeJSRoom'; - -const queryClient = new QueryClient(); - -export function DataCenter() { - const [isFullscreen, setIsFullscreen] = useState(false); - - const toggleFullscreen = useCallback(() => { - if (!document.fullscreenElement) { - document.documentElement.requestFullscreen(); - setIsFullscreen(true); - } else { - document.exitFullscreen().then(() => { - setIsFullscreen(false); - window.location.reload(); - }); - } - }, []); - - return ( -
    - {/* 顶部标题区域 */} -
    - - - {/* 全屏切换按钮 */} - -
    - - {/* 主要内容区域 */} -
    - {/* 顶部指标卡片 */} -
    - -
    - -
    - {/* 左侧图表区域 */} -
    -
    - {/* 饼图 */} - -
    - -
    -
    - - {/* 柱状图 */} - -
    - -
    -
    - - {/* 饼图2 */} - -
    - -
    -
    -
    -
    - - {/* 右侧区域 */} -
    - {/* 中间3D机房区域 */} - -
    - -
    -
    - - {/* 底部区域分为两列 */} -
    - {/* 左侧告警曲线图 */} - -
    - -
    -
    - - {/* 右侧告警设备表格 */} - -
    - -
    -
    -
    -
    -
    -
    -
    - ); -} - -export function DataCenterApp() { - return ( - - - - ); -} \ No newline at end of file diff --git a/src/client/big/components/MetricCards.tsx b/src/client/big/components/MetricCards.tsx deleted file mode 100644 index d11eadc..0000000 --- a/src/client/big/components/MetricCards.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { Card } from 'antd'; -import { bigClient } from '../../api'; -import { calculateRiskLevel, handleNavigate } from './utils'; - -export function MetricCards() { - const { data: metrics = { - totalDevices: 0, - onlineDevices: 0, - offlineDevices: 0, - onlineRate: "0.00", - riskLevel: { level: '健康', color: '#52c41a' } - }, isLoading } = useQuery({ - queryKey: ['deviceMetrics'], - queryFn: async () => { - const res = await bigClient['device-instances'].$get({ - query: { is_deleted: 0 } - }); - if (res.status !== 200) throw new Error('Failed to fetch device metrics'); - - const data = await res.json(); - const devices = data.data; - const totalDevices = devices.length; - const onlineDevices = devices.filter((device: any) => device.device_status === 1).length; - const offlineDevices = totalDevices - onlineDevices; - const onlineRate = totalDevices ? ((onlineDevices / totalDevices) * 100) : 0; - const riskLevel = calculateRiskLevel(onlineRate); - - return { - totalDevices, - onlineDevices, - offlineDevices, - onlineRate: onlineRate.toFixed(2), - riskLevel - }; - }, - refetchInterval: 30000, - refetchIntervalInBackground: true - }); - - const metricConfigs = [ - { - title: "设备数", - value: metrics.totalDevices, - link: "/admin/alarm/manage" - }, - { - title: "正常数", - value: metrics.onlineDevices, - link: "/admin/alarm/manage" - }, - { - title: "在线率", - value: `${metrics.onlineRate}%`, - link: "/admin/device/rate" - }, - { - title: "异常数", - value: metrics.offlineDevices, - link: "/admin/alert/manage" - }, - { - title: "风险等级", - value: metrics.riskLevel.level, - color: metrics.riskLevel.color, - link: undefined - } - ]; - - return ( - <> - {metricConfigs.map((metric, index) => ( -
    metric.link && handleNavigate(metric.link)} - className={metric.link ? "cursor-pointer" : undefined} - > - - {metric.title} - - {isLoading ? "-" : metric.value} - - -
    - ))} - - ); -} \ No newline at end of file diff --git a/src/client/big/components/PageTitle.tsx b/src/client/big/components/PageTitle.tsx deleted file mode 100644 index 72ba72c..0000000 --- a/src/client/big/components/PageTitle.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { getGlobalConfig } from '@/client/admin/utils'; -import React from 'react'; - -export function PageTitle() { - return ( -
    - {/* 背景图片 */} -
    - title background -
    -
    - ); -} \ No newline at end of file diff --git a/src/client/big/components/ServerMonitorCharts.tsx b/src/client/big/components/ServerMonitorCharts.tsx deleted file mode 100644 index 509e53d..0000000 --- a/src/client/big/components/ServerMonitorCharts.tsx +++ /dev/null @@ -1,289 +0,0 @@ -import React from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { Pie, Column, Line } from '@ant-design/plots'; -import { bigClient } from '../../api'; - -interface ServerMonitorChartsProps { - type?: 'pie' | 'column' | 'line' | 'pie2'; -} - -export function ServerMonitorCharts({ type = 'pie' }: ServerMonitorChartsProps) { - // 资产分类数据 - const { data: categoryData } = useQuery({ - queryKey: ['zichanCategory'], - queryFn: async () => { - const res = await bigClient.charts['zichan-category'].$get({ query:{} }); - if (res.status !== 200) throw new Error('Failed to fetch category data'); - const data = await res.json(); - const total = data.reduce((sum: number, item: any) => sum + item['设备数'], 0); - return data.map((item: any) => ({ - ...item, - 百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0' - })); - }, - enabled: type === 'pie' - }); - - // 在线率变化数据 - const { data: onlineRateData } = useQuery({ - queryKey: ['zichanOnlineRate'], - queryFn: async () => { - const res = await bigClient.charts['zichan-online-rate'].$get({ query:{} }); - if (res.status !== 200) throw new Error('Failed to fetch online rate data'); - return await res.json(); - }, - enabled: type === 'column' - }); - - // 资产流转状态数据 - const { data: stateData } = useQuery({ - queryKey: ['zichanState'], - queryFn: async () => { - const res = await bigClient.charts['zichan-state'].$get({ query:{} }); - if (res.status !== 200) throw new Error('Failed to fetch state data'); - const data = await res.json(); - const total = data.reduce((sum: number, item: any) => sum + item['设备数'], 0); - return data.map((item: any) => ({ - ...item, - 百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0' - })); - }, - enabled: type === 'pie2' - }); - - // 告警数据变化 - const { data: alarmData } = useQuery({ - queryKey: ['pingAlarm'], - queryFn: async () => { - const res = await bigClient.charts['zichan-alarm'].$get({ query:{} }); - if (res.status !== 200) throw new Error('Failed to fetch alarm data'); - return await res.json(); - }, - enabled: type === 'line' - }); - - const renderChart = () => { - switch (type) { - case 'pie': - return ( - { - // 只有占比超过5%的项才显示标签 - if (percent < 0.05) return null; - return `${设备分类}\n(${设备数})`; - }, - style: { - fill: '#fff', - fontSize: 12, - fontWeight: 500, - }, - transform: [{ type: 'overlapDodgeY' }], - }} - theme={{ - colors10: ['#36cfc9', '#ff4d4f', '#ffa940', '#73d13d', '#4096ff'], - }} - legend={false} - autoFit={true} - interaction={{ - tooltip: { - render: (_: any, { items, title }: { items: any[], title: string }) => { - if (!items || items.length === 0) return ''; - - // 获取当前选中项的数据 - const item = items[0]; - - // 根据value找到对应的完整数据项 - const fullData = categoryData?.find(d => d['设备数'] === item.value); - if (!fullData) return ''; - - return `
    -
    -
    - ${fullData['设备分类']} -
    -

    数量: ${item.value}

    -

    占比: ${fullData['百分比']}%

    -
    `; - } - } - }} - /> - ); - case 'column': - return ( - { - let content = items['time_interval']; - content += `\n(${(items['total_devices'])})`; - return content; - }, - }} - xAxis={{ - label: { - style: { - fill: '#fff', - }, - }, - }} - yAxis={{ - label: { - style: { - fill: '#fff', - }, - }, - }} - autoFit={true} - interaction={{ - tooltip:false - }} - /> - ); - case 'line': - return ( - { - const value = items['total_devices']; - - // if (value === 0) return null; - - // const maxValue = Math.max(...(alarmData || []).map(item => item.total_devices)); - - // if (value < maxValue * 0.3 && alarmData && alarmData.length > 8) return null; - - return `${items['time_interval']}\n(${value})`; - }, - transform: [{ type: 'overlapDodgeY' }], - }} - xAxis={{ - label: { - style: { - fill: '#fff', - }, - autoHide: true, - autoRotate: true, - }, - }} - yAxis={{ - label: { - style: { - fill: '#fff', - }, - }, - }} - autoFit={true} - interaction={{ - tooltip: { - render: (_: any, { items, title }: { items: any[], title: string }) => { - if (!items || items.length === 0) return ''; - - // 获取当前选中项的数据 - const item = items[0]; - - // 根据value找到对应的完整数据项 - const fullData = alarmData?.find(d => d.total_devices === item.value); - if (!fullData) return ''; - - return `
    -
    -
    - ${fullData.time_interval} -
    -

    数量: ${item.value}

    -
    `; - } - } - }} - /> - ); - case 'pie2': - return ( - { - // 只有占比超过5%的项才显示标签 - if (percent < 0.05) return null; - return `${资产流转}\n(${设备数})`; - }, - style: { - fill: '#fff', - fontSize: 12, - fontWeight: 500, - }, - transform: [{ type: 'overlapDodgeY' }], - }} - theme={{ - colors10: ['#36cfc9', '#ff4d4f', '#ffa940', '#73d13d', '#4096ff'], - }} - legend={{ - color: { - itemLabelFill: '#fff', - } - }} - autoFit={true} - interaction={{ - tooltip: { - render: (_: any, { items, title }: { items: any[], title: string }) => { - if (!items || items.length === 0) return ''; - - // 获取当前选中项的数据 - const item = items[0]; - - // 根据value找到对应的完整数据项 - const fullData = stateData?.find(d => d['设备数'] === item.value); - if (!fullData) return ''; - - return `
    -
    -
    - ${fullData['资产流转']} -
    -

    数量: ${item.value}

    -

    占比: ${fullData['百分比']}%

    -
    `; - } - } - }} - /> - ); - } - }; - - return ( -
    - {renderChart()} -
    - ); -} \ No newline at end of file diff --git a/src/client/big/components/three/ServerTooltip.tsx b/src/client/big/components/three/ServerTooltip.tsx deleted file mode 100644 index c5505ae..0000000 --- a/src/client/big/components/three/ServerTooltip.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import type { ServerStatus } from './types'; -import { UI_STYLES } from './constants'; - -interface ServerTooltipProps { - status: ServerStatus; -} - - -export const ServerTooltip: React.FC = ({ status }) => { - return ( - -
    -
    - - - - - 资产信息 -
    -
    -
    名称: {status.name}
    -
    IP地址: {status.ip}
    -
    - -
    - - - - - - 网络状态 -
    -
    -
    - 状态: -
    - {status.networkStatus === 1 ? '在线' : '离线'} - -
    -
    -
    丢包率: {status.packetLoss}%
    -
    - -
    - - - - - - - 配置信息 -
    -
    -
    CPU: {status.cpu || '-'}
    -
    内存: {status.memory || '-'}
    -
    硬盘: {status.disk || '-'}
    -
    -
    - ); -}; \ No newline at end of file diff --git a/src/client/big/components/three/ThreeJSRoom.tsx b/src/client/big/components/three/ThreeJSRoom.tsx deleted file mode 100644 index e79cc71..0000000 --- a/src/client/big/components/three/ThreeJSRoom.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import React, { useRef, useEffect } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import * as THREE from 'three'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; -import { ServerTooltip } from './ServerTooltip'; -import { useServerHover } from './utils'; -import { createFloor, ServerRack } from './models'; -import { COLORS } from './constants'; -import { bigClient } from '@/client/api'; -import { getGlobalConfig } from '@/client/admin/utils'; -import type { RackConfig, ServerData, ServerIconConfigs } from './types'; - -export function ThreeJSRoom() { - const containerRef = useRef(null); - const tooltipRef = useRef(null); - const racksRef = useRef([]); - const sceneRef = useRef<{ - scene: THREE.Scene; - camera: THREE.PerspectiveCamera; - renderer: THREE.WebGLRenderer; - controls: typeof OrbitControls; - cleanup: () => void; - } | null>(null); - - // 获取服务器类型图标 - const { data: rackServerTypes } = useQuery({ - queryKey: ['rackServerTypes'], - queryFn: async () => { - const res = await bigClient['rack-server-types'].$get({query:{}}); - if (res.status !== 200) throw new Error('Failed to fetch device icons'); - const data = await res.json(); - return data.data.reduce((acc: ServerIconConfigs, type) => { - acc[type.id] = { - textureUrl: type.imageUrl ? `${getGlobalConfig('OSS_BASE_URL')}/${type.imageUrl}` : null, - color: 0x444444 - }; - return acc; - }, {}); - } - }); - - // 获取机柜配置,添加自动刷新 - const { data: rackConfigs } = useQuery({ - queryKey: ['rackConfigs'], - queryFn: async () => { - const res = await bigClient['racks'].$get({query:{}}); - if (res.status !== 200) throw new Error('Failed to fetch rack configs'); - const racks = await res.json(); - - const serverPromises = racks.data.map(async (rack) => { - const serverResponse = await bigClient['rack-servers'].$get({ - query:{ - rackId: rack.id, - } - }); - - let servers:ServerData[] = []; - try { - if (serverResponse.status !== 200) throw new Error('Failed to fetch rack servers configs'); - const serverData = await serverResponse.json(); - servers = serverData.data.map((server) => ({ - id: server.id, - slot: server.startPosition, - u: server.size, - type: server.serverType || 0, - name: server.deviceInfo.zichanInfo?.assetName || '', - ip: server.deviceInfo.address || '', - cpu: server.deviceInfo.zichanInfo?.cpu || '', - memory: server.deviceInfo.zichanInfo?.memory || '', - disk: server.deviceInfo.zichanInfo?.disk || '', - deviceStatus: server.deviceInfo.zichanInfo?.deviceStatus || 0, - networkStatus: server.deviceInfo.zichanInfo?.networkStatus || 0, - packetLoss: server.deviceInfo.zichanInfo?.packetLoss || 0 - })); - } catch (error) { - console.error("error", error); - } - - return { - position: new THREE.Vector3(Number(rack.positionX) || 0, Number(rack.positionY) || 0, Number(rack.positionZ) || 0), - serverCount: rack.capacity || 42, - id: rack.id, - name: rack.rackName || '', - servers - }; - }); - - return Promise.all(serverPromises); - }, - refetchInterval: 30000, - refetchIntervalInBackground: true - }); - - // 初始化3D场景 - useEffect(() => { - if (!containerRef.current) return; - - // 初始化场景 - const scene = new THREE.Scene(); - scene.background = new THREE.Color(COLORS.BACKGROUND); - - const camera = new THREE.PerspectiveCamera( - 22, - containerRef.current.clientWidth / containerRef.current.clientHeight, - 0.1, - 1000 - ); - - const renderer = new THREE.WebGLRenderer({ antialias: true }); - renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight); - containerRef.current.appendChild(renderer.domElement); - - const controls = new OrbitControls(camera, renderer.domElement); - controls.enableDamping = true; - controls.maxPolarAngle = Math.PI / 2; - controls.minDistance = 6; - controls.maxDistance = 10; - controls.enablePan = false; - controls.target.set(0, 1, 0); - controls.enabled = false; - - // 添加地板 - const floor = createFloor(); - floor.position.y = -0.01; - scene.add(floor); - - // 设置场景光照 - const ambientLight = new THREE.AmbientLight(COLORS.LIGHTS.AMBIENT, 0.6); - scene.add(ambientLight); - - const directionalLight = new THREE.DirectionalLight(COLORS.LIGHTS.DIRECT, 0.8); - directionalLight.position.set(5, 5, 5); - scene.add(directionalLight); - - const spotLight = new THREE.SpotLight(COLORS.LIGHTS.SPOT, 0.8); - spotLight.position.set(0, 5, 0); - spotLight.angle = Math.PI / 4; - spotLight.penumbra = 0.1; - scene.add(spotLight); - - camera.position.set(0, 1, 1); - camera.lookAt(0, 1, 0); - - const { handleMouseMove, cleanup } = useServerHover({ - scene, - camera, - containerRef: containerRef as React.RefObject, - tooltipRef: tooltipRef as React.RefObject - }); - - containerRef.current.addEventListener('mousemove', handleMouseMove); - - // 动画循环 - const animate = () => { - requestAnimationFrame(animate); - controls.update(); - renderer.render(scene, camera); - }; - - animate(); - - // 保存场景引用 - sceneRef.current = { - scene, - camera, - renderer, - controls: controls as unknown as typeof OrbitControls, - cleanup: () => { - cleanup(); - if (containerRef.current) { - containerRef.current.removeEventListener('mousemove', handleMouseMove); - containerRef.current.removeChild(renderer.domElement); - } - } - }; - - // 使用 ResizeObserver 监听容器大小变化 - const resizeObserver = new ResizeObserver(() => { - if (!containerRef.current || !sceneRef.current) return; - const { camera, renderer } = sceneRef.current; - camera.aspect = containerRef.current.clientWidth / containerRef.current.clientHeight; - camera.updateProjectionMatrix(); - renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight); - }); - - resizeObserver.observe(containerRef.current); - - // 清理 - return () => { - resizeObserver.disconnect(); - sceneRef.current?.cleanup(); - }; - }, []); - - // 更新机柜数据 - useEffect(() => { - if (!sceneRef.current || !rackConfigs || !rackServerTypes) return; - - // 清除现有机柜 - racksRef.current.forEach(rack => { - rack.servers.forEach(server => { - sceneRef.current!.scene.remove(server); - }); - sceneRef.current!.scene.remove(rack.rack); - }); - racksRef.current = []; - - // 创建新机柜 - racksRef.current = rackConfigs.map(config => { - return new ServerRack(sceneRef.current!.scene, config, rackServerTypes); - }); - }, [rackConfigs, rackServerTypes]); - - return ( -
    -
    -
    -
    - ); -} \ No newline at end of file diff --git a/src/client/big/components/three/constants.ts b/src/client/big/components/three/constants.ts deleted file mode 100644 index 5915f04..0000000 --- a/src/client/big/components/three/constants.ts +++ /dev/null @@ -1,55 +0,0 @@ -// 颜色常量定义 -export const COLORS = { - // 背景色(调亮为 #002952) - BACKGROUND: 0x002952, - - // 地板颜色(相应调亮) - FLOOR: 0x1D5491, // 调亮地板颜色 - FLOOR_GRID: 0x2E66A3, // 相应调亮网格线条 - - // 机柜颜色 - RACK: { - FRAME: 0x2E5483, // 机柜框架颜色(调亮) - FRONT: 0x2E5483, // 机柜前面板 - SIDE: 0x274D70, // 机柜侧面板(稍暗) - }, - - // 服务器颜色 - SERVER: { - DEFAULT: 0x2E4D75, // 服务器默认颜色(调亮) - FRONT: 0x355882, // 服务器前面板(调亮) - STATUS: { - ONLINE: 0x00FF00, // 在线状态指示灯(绿色) - WARNING: 0xFFFF00, // 警告状态指示灯(黄色) - OFFLINE: 0xFF0000 // 离线状态指示灯(红色) - }, - HOVER: { - COLOR: 0x00DDFF, // 悬停发光颜色(青色) - INTENSITY: 0.05 // 悬停发光强度 - } - }, - - // 灯光颜色 - LIGHTS: { - AMBIENT: 0xCCE0FF, // 环境光(偏蓝色冷光) - DIRECT: 0xFFFFFF, // 直射光(白光) - SPOT: 0x00DDFF, // 聚光灯(青色) - } -} as const; - -// UI样式常量 -export const UI_STYLES = { - TOOLTIP: { - BACKGROUND: '#001529', - BORDER: '#00dff9', - BORDER_INNER: '#15243A', - SHADOW: '0 0 10px rgba(0,223,249,0.3)', - TEXT: '#ffffff', - PADDING: '12px 16px', - BORDER_RADIUS: '4px', - FONT_SIZE: '14px' - } -} as const; - -// 定义1U的高度(米) -export const U_HEIGHT = 0.04445; // 1U = 44.45mm = 0.04445m \ No newline at end of file diff --git a/src/client/big/components/three/models.ts b/src/client/big/components/three/models.ts deleted file mode 100644 index 35581e6..0000000 --- a/src/client/big/components/three/models.ts +++ /dev/null @@ -1,180 +0,0 @@ -import * as THREE from 'three'; -import { COLORS, U_HEIGHT } from './constants'; -import type { ServerData, ServerIconConfig, RackConfig, ServerIconConfigs, ServerStatus } from './types'; -import { getServerStatus } from './utils'; - -// 创建机柜模型 - 修改机柜的原点为底部中心 -export function createRack(position: THREE.Vector3): THREE.Mesh { - // 创建机柜的六个面 - const rackGeometry = new THREE.BoxGeometry(0.6, 2, 1); - - // 创建两种材质: - // 1. 通用材质 - 用于除前面外的所有面,双面渲染 - const commonMaterial = new THREE.MeshPhongMaterial({ - color: COLORS.RACK.SIDE, - side: THREE.DoubleSide, - opacity: 0.9, - }); - - // 2. 前面材质 - 半透明 - const frontMaterial = new THREE.MeshPhongMaterial({ - color: COLORS.RACK.FRONT, - transparent: true, - opacity: 0.1, - }); - - // 创建材质数组,按照几何体的面的顺序设置材质 - // BoxGeometry的面顺序:右、左、上、下、前、后 - const materials = [ - commonMaterial, // 右面 - commonMaterial, // 左面 - commonMaterial, // 上面 - commonMaterial, // 下面 - frontMaterial, // 前面 - commonMaterial, // 后面 - ]; - - const rack = new THREE.Mesh(rackGeometry, materials); - rack.position.copy(position); - rackGeometry.translate(0, 1, 0); - return rack; -} - -// 创建服务器模型 -export function createServer( - position: THREE.Vector3, - serverData: ServerData, - serverIconConfig: ServerIconConfig -): { server: THREE.Mesh } { - const config = serverIconConfig; - const U = serverData.u; - - const serverGeometry = new THREE.BoxGeometry( - 0.483, // 19英寸 ≈ 0.483米 - U * U_HEIGHT, // 将U数转换为实际高度 - 0.8 // 深度保持0.8米 - ); - - serverGeometry.translate(0, U * U_HEIGHT/2, 0); - - // 创建基础材质(用于侧面、顶面、底面和后面) - const baseMaterial = new THREE.MeshPhongMaterial({ - color: config.color, - shininess: 30, - }); - - // 创建前面的材质(用于贴图) - const frontMaterial = new THREE.MeshPhongMaterial({ - color: config.color, - shininess: 30, - map: null, - }); - - // 创建材质数组 - const materials = [ - baseMaterial, // 右面 - baseMaterial, // 左面 - baseMaterial, // 上面 - baseMaterial, // 下面 - frontMaterial, // 前面 - baseMaterial, // 后面 - ]; - - const server = new THREE.Mesh(serverGeometry, materials); - server.position.copy(position); - - // 加载贴图(如果有) - if (config.textureUrl) { - const textureLoader = new THREE.TextureLoader(); - textureLoader.load(config.textureUrl, (texture) => { - texture.minFilter = THREE.LinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.anisotropy = 16; - - texture.repeat.set(1, 1); - texture.offset.set(0, 0); - - frontMaterial.map = texture; - frontMaterial.needsUpdate = true; - }); - } - - return { server }; -} - -// 添加创建地板的函数 -export function createFloor(): THREE.Mesh { - const floorGeometry = new THREE.PlaneGeometry(10, 10); - const floorMaterial = new THREE.MeshPhongMaterial({ - color: COLORS.FLOOR, - side: THREE.DoubleSide - }); - - // 添加网格纹理 - const gridHelper = new THREE.GridHelper(10, 20, COLORS.FLOOR_GRID, COLORS.FLOOR_GRID); - gridHelper.rotation.x = Math.PI / 2; - - const floor = new THREE.Mesh(floorGeometry, floorMaterial); - floor.rotation.x = -Math.PI / 2; - floor.add(gridHelper); - - return floor; -} - -// 机柜类 -export class ServerRack { - rack: THREE.Mesh; - servers: THREE.Mesh[] = []; - statusLights: THREE.PointLight[] = []; - - constructor(scene: THREE.Scene, rackConfig: RackConfig, serverIconConfigs: ServerIconConfigs) { - // 创建机柜 - this.rack = createRack(rackConfig.position); - scene.add(this.rack); - - const bottomSpace = 0.04445; // 底部预留空间1U - const slotHeight = 0.04445; // 每个槽位高度1U - - // 使用配置中的服务器数据创建服务器 - rackConfig.servers.forEach((serverData: ServerData) => { - // 从底部开始计算高度 - const currentHeight = bottomSpace + (slotHeight * (serverData.slot - 1)); - - const serverPosition = new THREE.Vector3( - rackConfig.position.x, - rackConfig.position.y + currentHeight, - rackConfig.position.z - ); - - const serverIconConfig = serverIconConfigs[serverData.type]; - const { server } = createServer( - serverPosition, - serverData, - serverIconConfig - ); - - server.userData.type = 'server'; - server.userData.status = getServerStatus(serverData); - - this.servers.push(server); - scene.add(server); - }); - } - - // 更新服务器状态 - updateServerStatus(serverId: string, status: ServerStatus) { - const server = this.servers.find(s => s.userData.id === serverId); - if (server) { - server.userData.status = status; - const index = this.servers.indexOf(server); - if (index !== -1) { - const light = this.statusLights[index]; - light.color.setHex( - status.status === 'online' ? COLORS.SERVER.STATUS.ONLINE : - status.status === 'warning' ? COLORS.SERVER.STATUS.WARNING : - COLORS.SERVER.STATUS.OFFLINE - ); - } - } - } -} \ No newline at end of file diff --git a/src/client/big/components/three/types.ts b/src/client/big/components/three/types.ts deleted file mode 100644 index 98d0901..0000000 --- a/src/client/big/components/three/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -export interface ServerData { - id: number; - slot: number; - u: number; - type: number; - name: string; - ip: string; - cpu: string; - memory: string; - disk: string; - deviceStatus: number; - networkStatus: number; - packetLoss: number; -} - -export interface ServerIconConfig { - textureUrl: string | null; - color: number; -} - -export interface ServerIconConfigs { - [key: number]: ServerIconConfig; -} - -import * as THREE from 'three'; - -// 机柜配置接口 -export interface RackConfig { - position: THREE.Vector3; - serverCount: number; - id: number; - name: string; - servers: ServerData[]; -} - - -export interface DeviceStatusInfo { - status: 'online' | 'offline' | 'warning'; - usage: { - cpu: number; - memory: number; - disk: number; - }; -} - -export interface ServerStatus extends ServerData, DeviceStatusInfo {} \ No newline at end of file diff --git a/src/client/big/components/three/utils.tsx b/src/client/big/components/three/utils.tsx deleted file mode 100644 index 1f8d00a..0000000 --- a/src/client/big/components/three/utils.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import * as THREE from 'three'; -import { createRoot } from 'react-dom/client'; -import type { ServerData, ServerIconConfig, DeviceStatusInfo, ServerStatus } from './types'; -import { COLORS, UI_STYLES } from './constants'; -import { ServerTooltip } from './ServerTooltip'; - -export function useServerHover({ scene, camera, containerRef, tooltipRef }: { - scene: THREE.Scene; - camera: THREE.Camera; - containerRef: React.RefObject; - tooltipRef: React.RefObject; -}) { - const HOVER_COLOR = new THREE.Color(COLORS.SERVER.HOVER.COLOR); - const HOVER_INTENSITY = COLORS.SERVER.HOVER.INTENSITY; - let INTERSECTED: THREE.Mesh | null = null; - const raycaster = new THREE.Raycaster(); - const mouse = new THREE.Vector2(); - - const setMaterialEmissive = (materials: THREE.Material | THREE.Material[], color: THREE.Color, intensity: number) => { - if (Array.isArray(materials)) { - materials.forEach(material => { - if (material instanceof THREE.MeshPhongMaterial) { - material.emissive.copy(color); - material.emissiveIntensity = intensity; - material.needsUpdate = true; - } - }); - } else if (materials instanceof THREE.MeshPhongMaterial) { - materials.emissive.copy(color); - materials.emissiveIntensity = intensity; - materials.needsUpdate = true; - } - }; - - const resetMaterialEmissive = (materials: THREE.Material | THREE.Material[]) => { - if (Array.isArray(materials)) { - materials.forEach(material => { - if (material instanceof THREE.MeshPhongMaterial) { - material.emissive.setHex(0x000000); - material.emissiveIntensity = 0.2; - material.needsUpdate = true; - } - }); - } else if (materials instanceof THREE.MeshPhongMaterial) { - materials.emissive.setHex(0x000000); - materials.emissiveIntensity = 0.2; - materials.needsUpdate = true; - } - }; - - const handleMouseMove = (event: MouseEvent) => { - const rect = containerRef.current?.getBoundingClientRect(); - if (!rect) return; - - mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; - mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; - - raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(scene.children, true); - - if (intersects.length > 0) { - const found = intersects.find(item => - item.object instanceof THREE.Mesh && - item.object.userData.type === 'server' - ); - - if (found) { - const serverMesh = found.object as THREE.Mesh; - - if (INTERSECTED !== serverMesh) { - if (INTERSECTED) { - resetMaterialEmissive(INTERSECTED.material); - } - - INTERSECTED = serverMesh; - setMaterialEmissive(INTERSECTED.material, HOVER_COLOR, HOVER_INTENSITY); - - updateTooltip(event, INTERSECTED.userData.status); - } - } else { - resetHoverState(); - } - } else { - resetHoverState(); - } - }; - - const resetIntersected = () => { - if (INTERSECTED) { - resetMaterialEmissive(INTERSECTED.material); - } - }; - - const resetHoverState = () => { - resetIntersected(); - INTERSECTED = null; - if (tooltipRef.current) { - tooltipRef.current.style.display = 'none'; - } - }; - - let root: ReturnType | null = null; - - const updateTooltip = (event: MouseEvent, serverStatus: ServerStatus) => { - if (!tooltipRef.current) return; - - const containerRect = containerRef.current?.getBoundingClientRect(); - if (!containerRect) return; - - const tooltipX = event.clientX - containerRect.left; - const tooltipY = event.clientY - containerRect.top; - - const tooltipRect = tooltipRef.current.getBoundingClientRect(); - const tooltipWidth = tooltipRect.width; - const tooltipHeight = tooltipRect.height; - - const finalX = Math.min( - tooltipX + 10, - containerRect.width - tooltipWidth - 10 - ); - const finalY = Math.min( - tooltipY + 10, - containerRect.height - tooltipHeight - 10 - ); - - tooltipRef.current.style.left = `${finalX}px`; - tooltipRef.current.style.top = `${finalY}px`; - tooltipRef.current.style.display = 'block'; - - if (!root) { - root = createRoot(tooltipRef.current); - } - root.render(); - }; - - const cleanup = () => { - resetHoverState(); - if (root) { - root.unmount(); - root = null; - } - }; - - return { - handleMouseMove, - cleanup - }; -} - -// 将服务器状态根据设备状态转换为对应的status格式 -export const getServerStatus = (serverData: ServerData): ServerStatus => { - // 转换网络状态为UI显示状态 - let status: 'online' | 'offline' | 'warning' = 'offline'; - - // 根据设备状态判断显示状态 - if (serverData.deviceStatus === 0) { // 正常 - status = 'online'; - } else if (serverData.deviceStatus === 2) { // 故障 - status = 'warning'; - } else { // 其他情况(维护中、下线) - status = 'offline'; - } - - // 随机生成资源使用率用于展示 - return { - ...serverData, - status, - usage: { - cpu: Math.floor(Math.random() * 100), - memory: Math.floor(Math.random() * 100), - disk: Math.floor(Math.random() * 100) - } - }; -}; \ No newline at end of file diff --git a/src/client/big/components/utils.ts b/src/client/big/components/utils.ts deleted file mode 100644 index c076eee..0000000 --- a/src/client/big/components/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const pushStateAndTrigger = (url: string, target: string) => { - window.history.pushState({}, '', url); - window.dispatchEvent(new Event('popstate')); -} - -// 统一的链接处理函数 -export const handleNavigate = (url: string) => { - // 判断是否在iframe中 - const isInIframe = window.self !== window.top; - if (isInIframe) { - pushStateAndTrigger(url, 'top'); - } else { - window.open(url, '_blank'); - } -}; - -// 计算风险等级 -export const calculateRiskLevel = (onlineRate: number): { level: string; color: string } => { - if (onlineRate >= 95) { - return { level: '健康', color: '#52c41a' }; // 绿色 - } else if (onlineRate >= 80) { - return { level: '风险低', color: '#faad14' }; // 黄色 - } else if (onlineRate >= 70) { - return { level: '风险中', color: '#faad14' }; // 黄色 - } else { - return { level: '高风险', color: '#f5222d' }; // 红色 - } -}; \ No newline at end of file diff --git a/src/client/big/components_three.tsx b/src/client/big/components_three.tsx deleted file mode 100644 index 626eff6..0000000 --- a/src/client/big/components_three.tsx +++ /dev/null @@ -1,691 +0,0 @@ -import React, { useRef, useEffect } from 'react'; -import { useQuery } from '@tanstack/react-query'; - -import * as THREE from 'three'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - -import type { ServerData, ServerIconConfig, RackConfig, ServerIconConfigs,DeviceStatusInfo } from './api.js'; -import { bigClient } from '../api.js'; -import { getGlobalConfig } from '../admin/utils.js'; - -interface UseServerHoverProps { - scene: THREE.Scene; - camera: THREE.Camera; - containerRef: React.RefObject; - tooltipRef: React.RefObject; -} - -// 合并服务器基本信息和状态信息 -interface ServerStatus extends ServerData, DeviceStatusInfo {} - - - -// 颜色常量定义 -const COLORS = { - // 背景色(调亮为 #002952) - BACKGROUND: 0x002952, - - // 地板颜色(相应调亮) - FLOOR: 0x1D5491, // 调亮地板颜色 - FLOOR_GRID: 0x2E66A3, // 相应调亮网格线条 - - // 机柜颜色 - RACK: { - FRAME: 0x2E5483, // 机柜框架颜色(调亮) - FRONT: 0x2E5483, // 机柜前面板 - SIDE: 0x274D70, // 机柜侧面板(稍暗) - }, - - // 服务器颜色 - SERVER: { - DEFAULT: 0x2E4D75, // 服务器默认颜色(调亮) - FRONT: 0x355882, // 服务器前面板(调亮) - STATUS: { - ONLINE: 0x00FF00, // 在线状态指示灯(绿色) - WARNING: 0xFFFF00, // 警告状态指示灯(黄色) - OFFLINE: 0xFF0000 // 离线状态指示灯(红色) - }, - HOVER: { - COLOR: 0x00DDFF, // 悬停发光颜色(青色) - INTENSITY: 0.05 // 悬停发光强度 - } - }, - - // 灯光颜色 - LIGHTS: { - AMBIENT: 0xCCE0FF, // 环境光(偏蓝色冷光) - DIRECT: 0xFFFFFF, // 直射光(白光) - SPOT: 0x00DDFF, // 聚光灯(青色) - } -} as const; - -// UI样式常量 -const UI_STYLES = { - TOOLTIP: { - BACKGROUND: '#001529', - BORDER: '#00dff9', - BORDER_INNER: '#15243A', - SHADOW: '0 0 10px rgba(0,223,249,0.3)', - TEXT: '#ffffff', - PADDING: '12px 16px', - BORDER_RADIUS: '4px', - FONT_SIZE: '14px' - } -} as const; - -// 定义1U的高度(米) -const U_HEIGHT = 0.04445; // 1U = 44.45mm = 0.04445m - - -function useServerHover({ scene, camera, containerRef, tooltipRef }: UseServerHoverProps) { - const HOVER_COLOR = new THREE.Color(COLORS.SERVER.HOVER.COLOR); - const HOVER_INTENSITY = COLORS.SERVER.HOVER.INTENSITY; - let INTERSECTED: THREE.Mesh | null = null; - const raycaster = new THREE.Raycaster(); - const mouse = new THREE.Vector2(); - - const setMaterialEmissive = (materials: THREE.Material | THREE.Material[], color: THREE.Color, intensity: number) => { - if (Array.isArray(materials)) { - materials.forEach(material => { - if (material instanceof THREE.MeshPhongMaterial) { - material.emissive.copy(color); - material.emissiveIntensity = intensity; - material.needsUpdate = true; - } - }); - } else if (materials instanceof THREE.MeshPhongMaterial) { - materials.emissive.copy(color); - materials.emissiveIntensity = intensity; - materials.needsUpdate = true; - } - }; - - const resetMaterialEmissive = (materials: THREE.Material | THREE.Material[]) => { - if (Array.isArray(materials)) { - materials.forEach(material => { - if (material instanceof THREE.MeshPhongMaterial) { - material.emissive.setHex(0x000000); - material.emissiveIntensity = 0.2; - material.needsUpdate = true; - } - }); - } else if (materials instanceof THREE.MeshPhongMaterial) { - materials.emissive.setHex(0x000000); - materials.emissiveIntensity = 0.2; - materials.needsUpdate = true; - } - }; - - const handleMouseMove = (event: MouseEvent) => { - const rect = containerRef.current?.getBoundingClientRect(); - if (!rect) return; - - mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; - mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; - - raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(scene.children, true); - - if (intersects.length > 0) { - const found = intersects.find(item => - item.object instanceof THREE.Mesh && - item.object.userData.type === 'server' - ); - - if (found) { - const serverMesh = found.object as THREE.Mesh; - - if (INTERSECTED !== serverMesh) { - if (INTERSECTED) { - resetMaterialEmissive(INTERSECTED.material); - } - - INTERSECTED = serverMesh; - setMaterialEmissive(INTERSECTED.material, HOVER_COLOR, HOVER_INTENSITY); - - updateTooltip(event, INTERSECTED.userData.status); - } - } else { - resetHoverState(); - } - } else { - resetHoverState(); - } - }; - - const resetIntersected = () => { - if (INTERSECTED) { - resetMaterialEmissive(INTERSECTED.material); - } - }; - - const resetHoverState = () => { - resetIntersected(); - INTERSECTED = null; - if (tooltipRef.current) { - tooltipRef.current.style.display = 'none'; - } - }; - - const updateTooltip = (event: MouseEvent, serverStatus: ServerStatus) => { - if (tooltipRef.current) { - const containerRect = containerRef.current?.getBoundingClientRect(); - if (!containerRect) return; - - const tooltipX = event.clientX - containerRect.left; - const tooltipY = event.clientY - containerRect.top; - - const tooltipRect = tooltipRef.current.getBoundingClientRect(); - const tooltipWidth = tooltipRect.width; - const tooltipHeight = tooltipRect.height; - - const finalX = Math.min( - tooltipX + 10, - containerRect.width - tooltipWidth - 10 - ); - const finalY = Math.min( - tooltipY + 10, - containerRect.height - tooltipHeight - 10 - ); - - tooltipRef.current.style.left = `${finalX}px`; - tooltipRef.current.style.top = `${finalY}px`; - tooltipRef.current.innerHTML = ` -
    -
    - - - - - 资产信息 -
    -
    -
    名称: ${serverStatus.name}
    -
    IP地址: ${serverStatus.ip}
    -
    - -
    - - - - - - 网络状态 -
    -
    -
    - 状态: -
    - ${serverStatus.networkStatus === 1 ? '在线' : '离线'} - -
    -
    -
    丢包率: ${serverStatus.packetLoss}%
    -
    - -
    - - - - - - - 配置信息 -
    -
    -
    CPU: ${serverStatus.cpu || '-'}
    -
    内存: ${serverStatus.memory || '-'}
    -
    硬盘: ${serverStatus.disk || '-'}
    -
    -
    - `; - tooltipRef.current.style.display = 'block'; - } - }; - - const cleanup = () => { - resetHoverState(); - }; - - return { - handleMouseMove, - cleanup - }; -} - -// 创建机柜模型 - 修改机柜的原点为底部中心 -function createRack(position: THREE.Vector3): THREE.Mesh { - // 创建机柜的六个面 - const rackGeometry = new THREE.BoxGeometry(0.6, 2, 1); - - // 创建两种材质: - // 1. 通用材质 - 用于除前面外的所有面,双面渲染 - const commonMaterial = new THREE.MeshPhongMaterial({ - color: COLORS.RACK.SIDE, - side: THREE.DoubleSide, // 双面渲染 - opacity: 0.9, - }); - - // 2. 前面材质 - 半透明 - const frontMaterial = new THREE.MeshPhongMaterial({ - color: COLORS.RACK.FRONT, - transparent: true, - opacity: 0.1, // 前面设置为更透明 - }); - - // 创建材质数组,按照几何体的面的顺序设置材质 - // BoxGeometry的面顺序:右、左、上、下、前、后 - const materials = [ - commonMaterial, // 右面 - 双面渲染 - commonMaterial, // 左面 - 双面渲染 - commonMaterial, // 上面 - 双面渲染 - commonMaterial, // 下面 - 双面渲染 - frontMaterial, // 前面 - 半透明 - commonMaterial, // 后面 - 双面渲染 - ]; - - const rack = new THREE.Mesh(rackGeometry, materials); - rack.position.copy(position); - rackGeometry.translate(0, 1, 0); - return rack; -} - -// 创建服务器模型 -function createServer( - position: THREE.Vector3, - serverData: ServerData, - serverIconConfig: ServerIconConfig -): { server: THREE.Mesh } { - const config = serverIconConfig; - const U = serverData.u; - - const serverGeometry = new THREE.BoxGeometry( - 0.483, // 19英寸 ≈ 0.483米 - U * U_HEIGHT, // 将U数转换为实际高度 - 0.8 // 深度保持0.8米 - ); - - serverGeometry.translate(0, U * U_HEIGHT/2, 0); - - // 创建基础材质(用于侧面、顶面、底面和后面) - const baseMaterial = new THREE.MeshPhongMaterial({ - color: config.color, - shininess: 30, // 降低反光度 - }); - - // 创建前面的材质(用于贴图) - const frontMaterial = new THREE.MeshPhongMaterial({ - color: config.color, - shininess: 30, - map: null, // 初始化时设为null,等待贴图加载 - }); - - // 创建材质数组 - const materials = [ - baseMaterial, // 右面 - baseMaterial, // 左面 - baseMaterial, // 上面 - baseMaterial, // 下面 - frontMaterial, // 前面 - 用于贴图 - baseMaterial, // 后面 - ]; - - const server = new THREE.Mesh(serverGeometry, materials); - server.position.copy(position); - - // 加载贴图(如果有) - if (config.textureUrl) { - const textureLoader = new THREE.TextureLoader(); - textureLoader.load(config.textureUrl, (texture) => { - // 设置贴图属性以提高清晰度 - texture.minFilter = THREE.LinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.anisotropy = 16; // 增加各向异性过滤 - - // 调整贴图的重复和偏移 - texture.repeat.set(1, 1); - texture.offset.set(0, 0); - - // 更新材质 - frontMaterial.map = texture; - frontMaterial.needsUpdate = true; - }); - } - - return { server }; -} - -// 添加创建地板的函数 -function createFloor(): THREE.Mesh { - const floorGeometry = new THREE.PlaneGeometry(10, 10); - const floorMaterial = new THREE.MeshPhongMaterial({ - color: COLORS.FLOOR, - side: THREE.DoubleSide - }); - - // 添加网格纹理 - const gridHelper = new THREE.GridHelper(10, 20, COLORS.FLOOR_GRID, COLORS.FLOOR_GRID); - gridHelper.rotation.x = Math.PI / 2; - - const floor = new THREE.Mesh(floorGeometry, floorMaterial); - floor.rotation.x = -Math.PI / 2; - floor.add(gridHelper); - - return floor; -} - -class ServerRack { - rack: THREE.Mesh; - servers: THREE.Mesh[] = []; - statusLights: THREE.PointLight[] = []; - - constructor(scene: THREE.Scene, rackConfig: RackConfig, serverIconConfigs: ServerIconConfigs) { - // 创建机柜 - this.rack = createRack(rackConfig.position); - scene.add(this.rack); - - const bottomSpace = 0.04445; // 底部预留空间1U - const slotHeight = 0.04445; // 每个槽位高度1U - - // 使用配置中的服务器数据创建服务器 - rackConfig.servers.forEach((serverData: ServerData) => { - // 从底部开始计算高度 - const currentHeight = bottomSpace + (slotHeight * (serverData.slot - 1)); - - const serverPosition = new THREE.Vector3( - rackConfig.position.x, - rackConfig.position.y + currentHeight, - rackConfig.position.z - ); - - const serverIconConfig = serverIconConfigs[serverData.type]; - // console.log(serverIconConfig, serverIconConfigs); - // console.log(serverData); - // console.log(serverPosition); - const { server } = createServer( - serverPosition, - serverData, - serverIconConfig - ); - - server.userData.type = 'server'; - server.userData.status = getServerStatus(serverData); - - this.servers.push(server); - - scene.add(server); - }); - } - - // 更新服务器状态 - updateServerStatus(serverId: string, status: ServerStatus) { - const server = this.servers.find(s => s.userData.id === serverId); - if (server) { - server.userData.status = status; - const index = this.servers.indexOf(server); - if (index !== -1) { - const light = this.statusLights[index]; - light.color.setHex( - status.status === 'online' ? COLORS.SERVER.STATUS.ONLINE : - status.status === 'warning' ? COLORS.SERVER.STATUS.WARNING : - COLORS.SERVER.STATUS.OFFLINE - ); - } - } - } -} - -// 将服务器状态根据设备状态转换为对应的status格式 -const getServerStatus = (serverData: ServerData): ServerStatus => { - // 转换网络状态为UI显示状态 - let status: 'online' | 'offline' | 'warning' = 'offline'; - - // 根据设备状态判断显示状态 - if (serverData.deviceStatus === 0) { // 正常 - status = 'online'; - } else if (serverData.deviceStatus === 2) { // 故障 - status = 'warning'; - } else { // 其他情况(维护中、下线) - status = 'offline'; - } - - // 随机生成资源使用率用于展示 - return { - ...serverData, - status, - usage: { - cpu: Math.floor(Math.random() * 100), - memory: Math.floor(Math.random() * 100), - disk: Math.floor(Math.random() * 100) - } - }; -}; - -export function ThreeJSRoom() { - const containerRef = useRef(null); - const tooltipRef = useRef(null); - const racksRef = useRef([]); - const sceneRef = useRef<{ - scene: THREE.Scene; - camera: THREE.PerspectiveCamera; - renderer: THREE.WebGLRenderer; - controls: typeof OrbitControls; - cleanup: () => void; - } | null>(null); - - // 获取服务器类型图标 - const { data: rackServerTypes } = useQuery({ - queryKey: ['rackServerTypes'], - queryFn: async () => { - const res = await bigClient['rack-server-types'].$get({query:{}}); - if (res.status !== 200) throw new Error('Failed to fetch device icons'); - const data = await res.json(); - return data.data.reduce((acc: ServerIconConfigs, type) => { - acc[type.id] = { - textureUrl: type.imageUrl ? `${getGlobalConfig('OSS_BASE_URL')}/${type.imageUrl}` : null, - color: 0x444444 - }; - return acc; - }, {}); - } - }); - - // 获取机柜配置,添加自动刷新 - const { data: rackConfigs } = useQuery({ - queryKey: ['rackConfigs'], - queryFn: async () => { - const res = await bigClient['racks'].$get({query:{}}); - if (res.status !== 200) throw new Error('Failed to fetch rack configs'); - const racks = await res.json(); - // 2. 获取所有机柜的服务器数据 - const serverPromises = racks.data.map(async (rack) => { - const serverResponse = await bigClient['rack-servers'].$get({ - query:{ - rackId: rack.id, - } - }) - - // 3. 获取每个服务器对应的基本信息 - let servers: ServerData[] = []; - try { - if (serverResponse.status !== 200) throw new Error('Failed to fetch rack servers configs'); - const serverData = await serverResponse.json(); - servers = serverData.data.map((server) => ({ - id: server.id, - slot: server.startPosition, - u: server.size, - type: server.serverType || 0, - name: server.deviceInfo.zichanInfo?.assetName || '', - ip: server.deviceInfo.address || '', - cpu: server.deviceInfo.zichanInfo?.cpu || '', - memory: server.deviceInfo.zichanInfo?.memory || '', - disk: server.deviceInfo.zichanInfo?.disk || '', - deviceStatus: server.deviceInfo.zichanInfo?.deviceStatus || 0, - networkStatus: server.deviceInfo.zichanInfo?.networkStatus || 0, - packetLoss: server.deviceInfo.zichanInfo?.packetLoss || 0 - })); - } catch (error) { - console.error("error", error); - } - - // 返回包含服务器数据的机柜配置 - return { - position: new THREE.Vector3(Number(rack.positionX) || 0, Number(rack.positionY) || 0, Number(rack.positionZ) || 0), - serverCount: rack.capacity || 42, - id: rack.id, - name: rack.rackName || '', - servers - }; - }); - - // 等待所有数据获取完成 - return Promise.all(serverPromises); - }, - refetchInterval: 30000, - refetchIntervalInBackground: true - }); - - // 初始化3D场景 - useEffect(() => { - if (!containerRef.current) return; - - // 初始化场景 - const scene = new THREE.Scene(); - scene.background = new THREE.Color(COLORS.BACKGROUND); - - const camera = new THREE.PerspectiveCamera( - 22, - containerRef.current.clientWidth / containerRef.current.clientHeight, - 0.1, - 1000 - ); - - const renderer = new THREE.WebGLRenderer({ antialias: true }); - renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight); - containerRef.current.appendChild(renderer.domElement); - - const controls = new OrbitControls(camera, renderer.domElement); - controls.enableDamping = true; - controls.maxPolarAngle = Math.PI / 2; - controls.minDistance = 6; - controls.maxDistance = 10; - controls.enablePan = false; - controls.target.set(0, 1, 0); - controls.enabled = false; - - // 添加地板 - const floor = createFloor(); - floor.position.y = -0.01; - scene.add(floor); - - // 设置场景光照 - const ambientLight = new THREE.AmbientLight(COLORS.LIGHTS.AMBIENT, 0.6); - scene.add(ambientLight); - - const directionalLight = new THREE.DirectionalLight(COLORS.LIGHTS.DIRECT, 0.8); - directionalLight.position.set(5, 5, 5); - scene.add(directionalLight); - - const spotLight = new THREE.SpotLight(COLORS.LIGHTS.SPOT, 0.8); - spotLight.position.set(0, 5, 0); - spotLight.angle = Math.PI / 4; - spotLight.penumbra = 0.1; - scene.add(spotLight); - - camera.position.set(0, 1, 1); - camera.lookAt(0, 1, 0); - - const { handleMouseMove, cleanup } = useServerHover({ - scene, - camera, - containerRef: containerRef as React.RefObject, - tooltipRef: tooltipRef as React.RefObject - }); - - containerRef.current.addEventListener('mousemove', handleMouseMove); - - // 动画循环 - const animate = () => { - requestAnimationFrame(animate); - controls.update(); - renderer.render(scene, camera); - }; - - animate(); - - // 保存场景引用 - sceneRef.current = { - scene, - camera, - renderer, - controls: controls as unknown as typeof OrbitControls, - cleanup: () => { - cleanup(); - if (containerRef.current) { - containerRef.current.removeEventListener('mousemove', handleMouseMove); - containerRef.current.removeChild(renderer.domElement); - } - } - }; - - // 使用 ResizeObserver 监听容器大小变化 - const resizeObserver = new ResizeObserver(() => { - if (!containerRef.current || !sceneRef.current) return; - // console.log('resizeObserver', containerRef.current.clientWidth, containerRef.current.clientHeight); - const { camera, renderer } = sceneRef.current; - camera.aspect = containerRef.current.clientWidth / containerRef.current.clientHeight; - camera.updateProjectionMatrix(); - renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight); - }); - - resizeObserver.observe(containerRef.current); - - // 清理 - return () => { - resizeObserver.disconnect(); - sceneRef.current?.cleanup(); - }; - }, []); - - // 更新机柜数据 - useEffect(() => { - if (!sceneRef.current || !rackConfigs || !rackServerTypes) return; - - // 清除现有机柜 - racksRef.current.forEach(rack => { - rack.servers.forEach(server => { - sceneRef.current!.scene.remove(server); - }); - sceneRef.current!.scene.remove(rack.rack); - }); - racksRef.current = []; - - - // 创建新机柜 - racksRef.current = rackConfigs.map(config => { - return new ServerRack(sceneRef.current!.scene, config, rackServerTypes); - }); - - }, [rackConfigs, rackServerTypes]); - - return ( -
    -
    -
    -
    - {/* 工具提示内容由useServerHover处理 */} -
    -
    -
    - ); -} \ No newline at end of file diff --git a/src/client/big/title-bg.png b/src/client/big/title-bg.png deleted file mode 100644 index fcac9b38899e33b576143e621ff5a6700d04c0b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52161 zcmV*KKxMy)P)Vh}2^9oP zn7{xSKtaWvMaf7Ga{GU(`}Cadn(CQ3=UxQ$*7MwZ&Y9`1uC5N#Q*T$bQmPRgDWy{1 zYqiEIPBl=fHFnzl8Qpr#9%{lX%dq^rCTQFkXuKLwQ$v+mfi-B9c~`B_vKwfnldh?o z1giEY-G;0D_|U7kd}@`SpX)?=)qno1T*{k&e&X}XPllSefZlE=pye7+ue}V6AD6Cq z(`G>X4$!~)BlI5o0&Uj?W=w+`I~wY$i-1o*0=`>TPQPXw=!5$~4>$(cerMpj?|`@8 zfV%5Os1XkX3m25ruD&|3*;delPJlji0MM!x@bwg^(IcTQ`x`K4md#Jps1eYz74*-J zfFAgBV4EF)C5wUg-+{XJQm8Sbfp2G*c{f`f*k*g^K7*i-I?1l*zWxeo#6wVb{ulV{ z<8oSB_pQASbngS8Px&>_Wiw#mLg2l(p)S7wYU1m_+;44K;(c25 zei=;fnqSl9wh7vbT_>vbzBf<{54{HYV7k;%1hT8d3jR21irDdv&yLP%JJoL<*Tq< z9jmYZo0`M%^L6g?OWMxUt$)pLN3m`BUumuU(fa_c9qzgjm^r=lJb~rerY-cZ{s-9S zV4y=syT+ODD%6!10`I;BESO&&4wU|&e$Yo82kf$kU8j8X9@O181CKrcO#3DrI$bt{ zKIRmF)<e;dPAT38(_=scFpwJ$55C3-L4&dojBTPW9aR6 z2D)~GdhB6f&g_QgWCJzOilzqpVgohMkCz(kcRwi7?Kk=k-M4Fu`Sb2o58in+mMyK* zI!Np9R%-Pduhi1(hgQSx`#y}h-}X?iKRy@BmNor;i_ZptS1kD4Kr5XZ7HD;&l|DXl z%Ky;=z^nL6e1nl8n{pSBKl1+3K$aRehWd66^xg+Tciq|ohi|+ZDu+PU&pv^=@YV!lcvj1nLiaxgXwuj|mo$Gim&Ss*=f;JS$4Kt5 zVb7JsSj{sQ7=Ox%p_q&r&o2L#X5}RO3XXjzcPzQ`dFIpuD1-0(9+$I_Y#vX?BWXX@ zhFWUh25O)NT4l+MYgHcOL!Xg5b9_7f5rfUx@N%7F`A?!T)0K zKw0-<<;N(JHl>q{Vl>mWluohqV$#w)Yrt5hFV5TNCoxoP3Qx7Q3iD98Uw#HP>_K4I zV7q4UpO>Aw*v)PS{0vxc0}J-PHU{eY%Yn(0!VPak`yC41??_<#oh*nttm z0ZgA7rrn|&^dbF$Lk0lrtY>*Y|0LACx0rR4Sra)T0C(rz?K*4g9%jvhFFu93@hV`{ zGr+W|C9z9&+pH_}VTS`hJKSvA!_pIkCcHP%N4b(t&q!ny``l;I&8>oR+Wm@3?aNhjU z@Sa-2O;gmgq8$5u2F!;bd*w{t>+9)X(V^-CTcjT!?RbO73}V~l%b zXjJ5t^7WVH^z-LIkw*xnZ{Gn}s|~PjJL6?bMoabOr$}}|lK1fJnNVc>Y}pc6cRgU; z^?{BX12v7IX3Yq_OBT$xyh+Y=+8HpKWLK@%w7ft0una~oU2437)mNVbtySwCqokh5ac>7h2F0cwS%6ei~oTUPJl7aiVVCHXb#AzYWwtt3D;R0C?*a<_#)& zfr{Rt+)J2yoBDPKA2{}G4I*EJ<<{`)yv!PpSB^JpO-;yKl3kL&+B7kpRgxZlKB3H{ z4y85?>c(}>+pW|MYM5+9v@V!66Y7CGpq_jb>ZA8*>sh&rZ`u_&xGzAU>-rnoO=mBU zf_mU~;N|D-`oj^eAqb%T`KdNCN{g1jS6|p@Bu@+lrhE~uxw>p_qoPoRkq({grn9Gp zK|OM>0hnq1kx-kpfgN^*KIJT+`*t?6%6o4?J$NV7@WD_sr$N!$h^p442~?-fz~RSP z(7IC>Be#0&5vYgnvC&R^eEZs!qAoB3L; z0X1=g-BkG9%Jo`lpa%NU(2BM{HN3w!Py?;Hl!qPEmGb77hWVi~B3seCZ#5bP;T z?t;}RS@B?S%0MkZN^{S+V6)A0Qc&BzTUjsZ`ETDEx?k+&C5c2yE<1m zH2}a3)IinJ${!y;@$uQP0QiB^3huuR{o{v4vu0Rejl4`Y+1wZmjd$*HX0)63jR8H; zxCuZ;L;`l(wFjCtvv~+Sb?YbsYQOy4dX`YwXyQ#E@P->%Pu`g`%HA=Q=bNvf=FJ0| zHnql70&6KPb*I@gO(?^J_q=bRW=sbb%(H38gJt~^C_Qg3)YK_ufSEq0O||8vBaz;$bPXP1c#TF=TW93j3o_gT|s4qS(dERJi;GHuY>Wfb-V7%7aK=YQN z&NKrND*=FGAg2TXd+Ajyj|hzNKMn?G<#jT6VUs2Pxn&kjiyQ#h*J~C4EadS4V6P2o zznuZSbdmjAwix=mCH8OmQt0K&plj*xI}?_v;jiJZ;cpfHqp?b3cKI?WI_?P)keC7N z1{=jWXvhG^T%a=spt5kr5|A0?69Iq|S!=QZ?U-z)x>88x3W44lCR3J5tYvQ>U0?E7Zex1GKTs_ln(UW8jc}Hd@HWo7#2G3(pu36{DUG!CzV% z^w<&l#9skB_AH?a|0#=Zb`+kc?melrYLO-F9@ z>J9We3VP3d?MAoBlb{~C59+B$fvI1I);_hstTm;znxWLNcf=$96mIheZp&R`ch+ZtP6oJJqfIei$L3YXp#W zhhAss-S+u^>c%VQVaeiF$%_LAfBE%Fs1P`nH^A!i0iVIEUe^%?nAJ;p1({S_Rs|Uf zC#`SYSD2|jFhfS?|j4MHV+vnX$;PtW4utz z8GmF1B=3|hw}C$SEOSkbemV?$_{xh=bPyP{SWG zfU)j-6tMZ0z}DMA|Kj&XW<_2y7k`4W=HT3?68nD)!4cw@aF=*n0$z|Pr z{)r7IO<}B$|AjrvZs$Fqnl*EfMHLK`zpZs7 ziw`2_^&f-=cmB+C;p9|AL_eISOA~#TSK^_N#Y30B2FCx4kkW-nvkKYojT+?~+0sA_ z)IdKdTF#%7iaqPKzfYTo687gyEzfIilZihU2J@_UE%@zEVScoMq8?t}rWeJoJGrFL z{eE@)$-eM>>Tg8yp9QQc5T;nJUU#!nTZ-Ri!8@14$I~g-6ZMU+K8Je#DX5#SGG4&R zX1Ef_i9`BBQ)uSRwzL54_;D7@edVPPpruV`+wKT`_|JiT4zio2KKmFTk79S8u+tlX zx3o#_;Qr8kjs!YyYR~+gG~UYb&O~6L2hgtG+;|h~zDEN49b)zM>4(-kcf`XcYD+@2 zX|F>^a|DK2_t0iF0)NN7Xpi@pG@&dv+0IsL3cb^Az@X#JdeF%D<>ycj-U0RWW5#8BAT~sy4WvxU5wI4a8-uP8@v!2Rdfo0S??+CxD zixD!`S>yr?qHB8hwSqB`;#H&qm2B9kLLpVjRFq$RctHNh(AOsbfM` zar$AN0ledGhof7MYt+5B%*C8pYZ&95)R_FGBLEYMfE7j3NDpxDjj1MLjEgf&%fMeg zE0HxTpW-qJX)EDdw5%fOgO@q}VwZl=4)t3PGH1Qq=e_u6g2ilOLThAJK{NOH`sTM^xGWrR&kL7?Y!dHVK&)moGIBX1qWa8k>;k*W|YZz-P_B z6+8o??HYORDet+OO3%JT+bEh*mNQGntb8f{5xIRLgCrU3$N)KcqBT}lvq+^p6yosy z+n~=l8+z$d6E@HQy1wTN0kj{z2lcOWEwH;=Z)3F7WPpT*y7fO6s3ngW;z?e%1OgK{ zN=Ch{wgI|r3w`Wq7Bn6{*oI+tGQ9Dc4Jl1$@si<@&ICTNkNK@GzrX^<4wNQ<`S$CL z4vi^49Xs0QZPEn#z`oX7h0gC?y@fp!n8H{u_WeG3;qQ4=If?>3ozH(u!yi{CY!VM$nfjh(#@Bdh8-35H~(@L zDW9)zO?UiMx?JyenAhFU8IQ`PVH<%eT`Omj4}N$u`rt;rzY}t%b*)1KHBbYs(jWWSoL@&l$60LXxQme^EHFCVi#$SWEdovt>|?xWDNM65-hD6`>WY7q09wzON8ysU z*&e$8QT7~P;zPV2z89cPXLO{5BjQcbRQ5g)`d9yBqpJ`gOY4fOF9J+x=90Gv)rA1v zExSV>b26~!e)jt8S>`-pHFCHCuoI&7;ri{N_dNvqm;YlukSXl*i_Zf0+yaby(bKUq zYb^q{^_hP%Wf;$20>tmT&4h}^H&fC&Tcb5}w{4C5>W&*M$X)H3zjTyFm(8Ge-qW5p zTro+&UD3rtWYdF~KL!x$>}#=gU}|$+mdqKOGcsl6`EulvcxB1u-^-Fi)Je`dSz4kgUZvMQcYVchP z^|7Zd)mv@*kXpRBH{O~s${s^jknc*SRT}{Iy?5RBZFblT+wJrKUU_K&zWj8Jz~eWs zGgbqbN#I^^tx<5RY^c-W3u%n61O$md2EsQi-@e7cvEbd+Gt5f>LKX<%Uyp)c9{3SU zmz6h>U(in!BohD^U-ukX3O*S>l_LX~4AL&}lq~F8O7^mRI?zG-ej-CwKg`M({PxlX zGV``#zok0!zT@-B)UB6BDJZGzK>sYi$UCWIL?1$XBxwWyIMAK@tP#daSQp+tDR5hC zR%%7EEy2iIVB7H1ag~X$uEv67p|>>I&qzd{k4MmM>2?UKEbt+k51K1 znx}Z*VDpb3)k1tmaJ|R+N<@FjSD_EY_@#7&kf$VH=3FPWV+LQTzopVj^MofiZ&zMd zX&&YBW<87iwMeY4dH2L??z^@gz~}z~>d9f%8{P=$ojw)niVKXPP_Ma`J#&scX?-HE z4hkE6(O-bG&VlZ8gauP63?Tu>_umE(KqjVGS^z~+l33DPxuwkb!(to59rf=XFO!o z;JeBp2&o-jA8XGQZn=i_a3S!T&hcx}66%6KSy0yx2Yt)6X8zFwj6g9x` zmt`+~%9s@)(U{JDD}Yj)s8xCH zL~Mh#w62U0UpJICw$M4V+7u#ys^!GtIwX-*SY!KXQqLhBuIb^;$wAe*hqME)Ymi}I%O52od&P|gF6hCb$0D>qet)KkC%w?olc#R2aoK-cc5 zz3|`md|=}J=|@l_9;%FE35>I~!}-Pp{MYMUX27@JRW<)=1L&PKCd-%I51W&Q}7; zIJhE#Eiw6G?c?8*_@u6-yd|b=lYk{{QmfpWYbI^K|PsltnZuWWv#C#*4KPF;I*&z7Y_U~(N{+G%R0APW)$d{KP zL>Wlw$X)l+DtO}@q298d4Pf??pv*|V@M8I1YV8<#{_?fGquSc$>i*jo=>A7(-Dl7< zYUcFq@%5K)Rvj(nd!|+Y3}D|l+-Z|-^uhhds+XT%gz;mWr4A6y0*SyHGiK=Zzh@bX zlY1VZA>3D7hAjS~(Oux}gEy&{Cjn%F_ghRW{f+eN9)bY%mu$9x)WvR`H=l7>Z)|DCQAnrm@k1|ysn{+lrO8)F1&uR zPcB<3AJ@H5e`K|V)PH2aTgU2>YFCeMs@~FlX$&$W74HXNX;Ni)8&#z7men@m7~--^ za}evBTsLAn_?3$A3F;U5oi&a`J%#PAQ@n)n;kQq0yr`^ALQ*^P`A%q?)I8+rE2U$m zTRuMh^2{nAZ~m6_DUHdD^n6V6dZhY8D0MtjqGjLyy_ zkCfWw_AEDPfn9`j$!nKF{B6_;=(#J@q&F-u%NNE3?3ysq%YZGqSsCbDIXV+~&TL?c z7bft#@9a6j@4RU|fQ=Ulg-WLLe#tOOXX`oxslRfLMV`pIX)uCq3 z2)$4U1SWaO@JXSdTeY_50aG|d3Liabd?@#V1@>$@0;1`BVmjw{z4c9~XGO+KI&;`> z76P1?EU{XS zr#%DMKg+k)_q-jx>{Lo`(nq=AZC9G-kRLMrvyFm z{J}M*cW%5XRYosah#KiQnFea02KvDgMTs$I04w3St72yWm*(;03|W3Z;}61lwqmUg;(A5M zo3Hi07!MthXxxw6yWd5rL+iIzylP zFSCB42qOldz471X++P!E#OF;RrH?!j`q)!}wb!+Eef9~c`)>!xOP8Xq_`J!h_^+3k zjc&AYjR0Ca`heZ6Mq!P7-Am5{w_FSL`j`;VrK30Y+#fjo zclNjr0-7n>%R~2Aa5)mHQ1?ID>SW^UP!nFY>%nSiyPbeR$3gc#$ksLX{=nNiC6r)= z^wWw)Ug6y%lYC>hWB@Nh%3B+zgeiXp?{h*rfBoYGsMHV}(`AfrOot**EnF*5ZjcUP zy9DJ+wh9z%$8sL!+R*XfKt_tE_AaRiaOo$_=y!fSa>e%LW4iIn$wC`^)3g-Jp|3N#Au*HRuWr{xp{rMO)nBVW;R zRg}c~c$AER%!-xn$M_WOSKi!TspTP5Wjs zv~Gfd$DX1lPr4fm7tBd*LRcYGH-z?w0RYoq$4xrvV^5o`o*K3s6UNn)TyIri5DH%I z*o43*hO7=I{*GO*VS_fjulS1$&z~|5M<(asS|pwO9efJc{7Eh-hUF0#VzE5DJgI!2 zm$b0s<$Kqp1wpE9gfd0_vTITr>BTgbC(wbEj{PMs#F5EeF&(Aaz$=TlIS^h`!UhG! z(x=+6AfS_^5&BAM5iID_cmNCiL245zf9f-GEzpO%=C`wVPm)FI<4Q&Ami!m%9J`0K zT$%krrTNCv#M++gMbeGfKWh-xMT)PNKeNwfjUjk11$q5|x3SRI{q(_iHgEBMME>fj zzQpT?@Vm6g;Qcu^mvBCQyQbupbebrHiRnKly>esv)UkThu=G0Ad2}x1{X(jre^01W zD!pT?ZGq04 z0rTfV(Rp}uwk}G%=g)(pFo_f%XVWc=*Dl(3u%Pa@-&#YeBjP<{x;2Jw))iQDEo+n{ zkeRyJm!FjZ=H<1P_vfDgEm~QgBnN$x*YKQKz}H`eyr+F_f#Hodw&5Aut_ww8JLHvY z0Qb@YiNIje-AC_R<1B@YBoG@J0C2El*#jdTnCe>?Yc(^*2>@`C-{<8Sq-xW7Lf}>a zVF8w2r}@`<_iH`SnfTS#hB^rJA;}W~fHjlD*I)3WCFF-+N2KeN7%K~3LkP`lLJL3=XJ#BcFc-7Q|AgT|*`ZCny0mEK-HNri`>+jOn zl)-QgibrLLrD6PoKc228l8&vBd3`vzoyeTkCN;p!X%^@t4_XReyv*}pZPV6(ocgE} z?FKgr*F5TJlimQd3mh=2fYt0V7y5VS0z2$tH?9%C5f4M%eY4%9WkRKbA__D_8AiZfC1i=D^Y4q_J$>fsP%1N(F9NtoZ6;((6(X+x6wf5239l=^je|A0 zUxxb^=^wFoG|8S2eDXT^c_p449O3W20-vH310SPZMe;&q#K9lGt%LU>t{#bRte-^E z^5oK3PW44UZr`MYL3Q*{*d#`8!vCax!!hrfsVJfc&aj`T~GCit2$Uq^XK z?ak)8n2^cZR;Sa%#!0j;oOs<>M#X+d?-eC~z7d`$F}(x%bDdZRffg@bO)Xq78#Ss0 zdi6d{&7OH9W=>yNh<_%joIhP4P5f`w2Y`vT=(Y|H=<}I+`FVxcUM3ffCMoY*rO{ zbwN%@ydywLv`o&6=|^#$u=HZxgfxEI7>|_Ry%58JPE}b?L6H$&(t|ZHP=M8&J)~45 zA6ULbt_;2TX=43Y>erk%4*@O87Spd(cPWn`Yf5HjRFKkS=|IwFYTR+zSbbCJ{5lJ| z5y!PJk1!@w&TmQYP-%WC{TI=dR|h_JQ=e>1@HWoPFVs0`7pa~ZdA;9SN-xc2l74u( zVtTKhsc{*}!*!M7Bjn5KKdCyhKJWjjNLr~*!8k`)KXL9AuJN`GlB@JJE{j->AeSU7 zPafsV?|HvdYYgQr;yh~9*n*8~uWh|q$OE_9aL@$05=ct<$-uYaM)qu8?@Zk!-Mnw1 zrhWq~Sp>B2XgzNUd?k>S43uQVB(D%!S5b)QsZ)UY3!umgn2eqTS`%ozaJ~g`-I>4y z8qS$*LpD=LK>~qWuVwkSXk|T$2{d+KBY7lGEdjObu4j1?NZX;Km67=P05hGLNBPM} z+<8;$i9?>i1TfEK0Q15HP}8T`GXXnx0otq$tYhnFysoEwY0vC)Iw4PBN=s+h5xBg@ znk7at4`3DoDgb0X<5LuntxfU;9>5&nO#!1kUA6~q2=FFCrNP`3u!}VR%z8pB-yNr% z@*>tuhEzS7e44cdq-$sZfE%cReuNYNz_B&Ixt;-(2B4z=GJ=0b?&tCM)77wh?mJKh zi)$jltCGO&EFich;;rl5Przz_t>Sqh*LXUrkwD}OSp!`mE@Mik!ut|$8~=~a`+xo^ zsQYdKzWBsO`*8Yf-U52i@z6gz!bUkE@b-;Yq3*o}YV0Ve*)z+mnly&mVP{~_3BW!F z*$r!7ehT%}qo|><(w}_{MbS!(+Lhfv=6HA6%$)H{8~4=BS3!**3-#^nGVew;_N-s> zw%u)S6V}@~mV}V=$1NGV%c2;o{=w7{n z!;iDaVo(U`nbVj;TRM@otIYW@OJ=A zd!VhKPOM$OginbWcUhOM#AdZ#O0do8AYQ~miF8Oz-AUrpv88^x)= zRUZJ}vU^Kx({ll@?dQ^&$YUmzS3rTZE<3 z`M*{0A^w)?=)Py><8@U`r?EULea(M~i}IIrz@G?NdQxd#N-9&}75kLr^Iu+r>yEdO zp6gN4r}7>MQMn`hf|qHjjA`>6CvG!)|%*RBuZ}B$geS+P~(|@YZMEVBri>Xgv zSL|NVe9?JH31tX%jP+;nT4Zde+9T5M#CD6M5%ns_?duD%{7i4s94pD1$&=STl84VN zdHi`=DPJ%jg{nzEi~K}w#E*lYU2Z%w^!$=?pglR>{Mb&hsU2R_?i5lqmGz zg85K%E-rzu&70dZZs#x%n2e$Xu#)#Jd8LqHbAt{RIBwa>0-*#P8?PE|46MeZcF`gW z0#m4I6IyvK8&;XTZx_tByd9WI*BUng+O2Orhsm>-yo?Ft4glsQHZ1k>@1V$gnE>ha zN}k4x7FZAD`5wSbhR#_ttv61m&NdwxASsMBdDP6ASq7LXKY0=p7)`*hlVihv%a;Uzf&-Ijb$TbHh zr89qbD4mN-KrOAAXl+5D@`#6k4(JzyV*6^SD!;s+9CHFFW^Kcj#{q;-F0iA$Bw3KoytSuTH-wW>Hp}B1X#0G*yDcv8)_eit?`jT+1WH@~7TsAiSdp#NTOh z%Ppm;)YGX*oBJ|w_Bq*=%1Oj9PnoF}((wCAPG50wQmA{JRrLKea;;KP^U8Gn zBH&wScS2=lir`vK5vii`5_-UL!R}tYIt35^*k%h+7D)${$sV%e{fG%wO+{1owAk-d zh76ts`n%2>U*0`&LkrTIGZJ1>da0ZO8x(?jY#vOtXKYi3@V!X-O6E^)XUtB_ZWZi| zMWzf$Vr11Jr|iPds1niIYT?2b>f70~(Xq=~Xw&v2wRrJ0m_2iqJrH=+27o(n))YJJ z`W3$Vq7|NhvNj3(j8fx?gS+&Z)p+j3LFY^uF=K6Ty#N60-{%7>R$eL{_~?vGppo)& zjnfhNlC%;4!VTiFJdt}+z1=PUg)v$VLfKI;U#d-by9#Z|`DT?BT<4Rc1QH3n;Jxss zbmo^M)h(v~NPD=yfFCPU)E!F?UN7M~`&3+4seV;}4{tY7Mwg^UD1#XnQaX;bvEP=` zi<2ayq%S4pk=oej6>Cr5fFknY>1#qB(fLS4%3;5yy6`c|`%S7Y!q`aZRk8a*e@(T& zO6wt3w<2>`X1|NH2hW!pS4kqqJF72Be%)wCX*930R#q6Kd&V zpwlLnFL~(B`!-DHfLc16m(Fe@Pg?TSCBrFsy$}#h;Hx{&j=*06SP3*Gu$Vk*$y0?w z|1DW;J%y*lc5f2)80j?peBLOZj52B>{DQF6EDthx6#2`PmFwNpf#gB$_z}^X zf;@leTwVe(9Z{Nf+d=QXuklJnr%la~V42Y!AEbngS~5fK!* zhloBHHQ0~?P`lu72 z_dduT_wo5BP$P#!-Es{u{hRQNV4`NLLvP)~dPCE(9Zx(`j@VKuk)Ai-$^gImh~t5l zt;=mAQsmt!lVWKqvIu+ERr0q zM*H*k!~nRBx2*~iCymN}7bq{9hNsI>2*-BO9I3i7u(0s>WkLn{mHn;? z_G`&rlbc7d9~CJ_8V424>E3&D;q(zj6djYPz7p+(1+K|Olz&H_Lz<_{DmPYE#@W$< z?M!+$R!TzUQg*%yD8;TqMq~$>J*$~gYC76&upT;e9HgdBxgJXv*M2`|0#`9s zwQ1WJdmr=xKA5}~Mn6^S#nMGG+PsO@UM)CioslWeh?~a)euwd_|w1DPP)9P+;Vhz(#T3Hfg9$5q2hH+c(Hr_PbphU&t5-Jk$MJIDy55UCJ@Vr z@JP+wJobDHOh8$${^kXqHmFCWQb~Tz+L`~3w0~;CmlAT>rFwG^GAo0BPp3C!D4|XH zZ(rA`G${eSzF3g5`j8~giXu_pdCin1yb2lkdMQ*Ffp?y+g7GZ0x0r!-)SwJLUwHFW zOkuqJ6VO4G7mwlsn$Le(q|$XwJ#XA+WX=mzz@Bl1`&wT6BWx5&H;zqNm@pey`q((F zhJtqR=Y@y>NxxD;HeSC9=QdtX6lgo1Hl+{cD>u!P46~~@H{K#@;R0v^Ey)AKH~v*Z zWc-{s9*TH&?GC-izEF>s41e4^5rpBm8=g{>#%7#^5Mtoi`gQ>G-K)w9D z0c!P`e*(7H3fN``XaZ^}{Pft-*0Y4lC$C!yp?KSM#!FZqI?x(vk3QKNQ1JjjXYo3q zn?f@_`p=ckhn9pvgm;ylozP#Ck@P{1o!>lMkV;`j-jktSQvu`Wry^JJNa* zlM(m*w=JMda+6myc?>OC4Bf05u*KGfHyKNHO(PTX(E!pYc^QBBF4R3Yn{d#Y!Z#AQ zOy>wn{YR^Enw0(CBR8qD6Rq)Tlsj zr<{B;diCm6ksO}l$8^2sOT;YS{crX`^LutN_uGMstVSrxZgR$uUG|NZte zzdCNT5w5-F8r*dA&B6UnojRG%=Bux6K4;9BVdUCn=bdrZuYa9c2S=3F(Y(23K!(6) zq8DCx!F)PdsI1nlTc_&t@y8z*D$l34Crv8U?gTzBShx`9oO4c425lTT?6AWsDw7lS zleTT!n!ZFE04nLjSN-2bXuGyObCf>+`oHJk;m1dMDa6T!-Iq4_Nox)E4wAXnVLTF} zx3He&?@qbvry@o#$>=M*XBH-I^e$OwqVL-N^Cd?|qPipR3d?J4G>1R+-euP#j%Y*IMjMyTuI+6%g3+e0v7@1Gxz>hH_KkRJ zwK3=Bnl;f@+uHR9c^(t6{KU}k>|r{GmjKcuPlVp*KpUFbg{!{$VjGFY$wKQ1I+ORn zpF#iZaG=|^cD?uhWE=LG&J*^dlNca(TkEmh?`W$d11vuZb@xs7sEa_&%9cSJ`iM8J z&uCq=bZPYqiS^q<5B#}Z%MtkO+g6e$%;23@Y|(XqzYIuZP~~?`Kb=%=@SYR7smjZt zn^i7{2)eCThCOp-+#EW9SJ zr6KUEg!sF#KyKZi%vX}!yuP)}J{#*xp01O5NYYf&r;d|vk$=Zc%rVU-YOKE@(T-_auSp3xcoqA*F%x-1^7Olf|K z^#jf4davTrT&WnZ+)XtiDZKewTh+Mn*Lug@Hr0FVJ4THfxf5p1Se~-6tz=p?0ALp; z#wmL3b!u=>zc=yOM;$PFq)x!!a1aNtP2z$xi)y{bcKM1Luchlw8s*(D^ls%I9Ijr> zxSd=Nt1E&8S>zk{{7kA;hzN>FfDi+7D+SZI@tYgtlVTVslT^~vLsmN!Qc8@xNhRRz zO+?D`{Px=`tgIwd#&>~|1#6@q`iqYuFY~@l!a15_7|?B z9K%Nd61#uy2_9+d!Nnzt9y_2hGgqyvAB0`Ir6cHuj=yx$2w zVVt;YJZ}+s`TQB^%3EmHusSG@cT}}G^Hr>0mw3~8Dq0J5&LmG}9|Vka(?Hfhusp%D z<{-H~(IhF5(i=Zf5p?-ko=1M0E1^%gI%#iyaOXKRu*8p4}!6p}W ztzydU1ePxu1ot`E?kJ{E#FNIC!x>jY<6bl#m;_?#eGY+o{z>b>%XcYGo&-gp>hfCX z<4-r9x8#*Vp$pYz=Nm5*YdD1Ul%bG^bXM*Ozl7dl7hvN}pwGVyxZoV97oV}lNGJ10 z??K&nn=x+c6VI?fH+j-fcMHU#}>j3LzT!j3l^4FZ{1~2z_X@2Et-CUVsRl)Nm$l{F^ zzL`t=>+queJ@Q7K)L*gj#65{4~?A6ceC%^;K8p0@@`I(SKl%BxLcix2h&!u)PQtKJmXp`1{ zhuF<)H{3QR^j**jW7aXk_V|>Q_lN&H7GG^^w#6D+T7Iej>GBP`>bPQ&e`Ji3pmd z1dISiLVpYNEbs|_vS(?kPX2G+=lJFl&yJNUBQ8R!u%J9YL!OQ#mdfj2IuJdRm%j`I>r1=CHx-alx;J|aNtW@7bI**X7 zJXb~L`pmYD%%f2mh5Nzg1U_E83;y`c?Z*CdSCBCaqXC5ZkJnA5`$_#!4JN#@j#{{2 zrvAn6x7RbLKdB~;-w(59=&EgYD}YuF0N6J=wrj7@v)3rSZ0VM2MkJIK&#ZzwX%cu8?V3qC zo!!o{w)gL+beLB-t7qQBGEatBT7R7fHji*UC*uU=#cb?FECobMa;SLUPx!^gcLnoH z#S09-?pb}XiiL|bH%anEfqbFAo>ljzi+^Nf?`7#+D6q!C_j zV(Ei1RKXlZAm$$Xnoxr3;`4wnKd(LIS*L(+6!3Y3aG#SE(C$eBlnrdp=)XkaIE**-+IdY{yd;%3#dm2TcFkvfxraF z*49Fwbf&4BKCrI|X=ngu7e1PZ!W0tFd-(;%;E0~PnltzGMb`ph==A?1;|&9rHYJ?ysTGy9nsKDX{f+(5L_28dS-s>*S_&=E4Qm zgL&Ru=)OnV^Mm1Dsv`zFzfHM;#?RxH6DRWr{#M2l#SYXPtEnpq2%qQGW9Bq`xd8P>%j2BJgs;gb4}IMc$q# zop@rD8+pZ2xtCsgsR_d?0c2U{9glnEl?3!8BCybXzvte2lXUB^zdky5?u= z4(OT#2nnS8K+w&XUw#?&+O29aIU6XE$64i@9*cceU(@r~?PXc(!!`mkUWgQJ4KHPjG8C*RbV4FpL zn-kdV1I~^JNcJfP*!n>H6OTWR_10T2m8M0D7DhIdHaj>XQ2Fbxzc#-dpetQ-^>jo! z*`R%U%$qka)!vSrr#R6WXPz0{qp?Q$pLpVlo;V(ku`8#18)MbA&-n2o8V>-w8;QsdPTsq&DJb!1-fV%RZHc|;+H&HZ{{zsYf zYfTtviVX7DBTzSAZO;exZA%2u_Ua9N!Wnj*KpV+E{}k$iKS51+#jH01>asaB0ki{3 zn-i8VgSz`>s3G?PQ@#k(wpkl`*WS?o?^1v^w~^l7coph~D{RCSAE<5I1nAHS`WL@5 zUd{##UcAV7E2~EzFkp9z8Z`zwYz+N}zXFdw2sM6e2v%20v{~+`lk7&g^)@KJ0n{gH zBP9m`g1(VqyYtgXeBj%W`1=(bZ@xVyc^9o@$su_zMy|!?7;aB1vKEApM|t&gECSuI zcRb%n59vF*mMNECPoeDG{IOzLTX}%5ITpZO(`-{~6cmlDDS25^K4}@mwi(5RAUaPJyPiQsunr%P4et44D@RWv??}Ip=C`O^tUn^6 zWAM5P<0HXdyt)>8|3rR264S8s%9D|gkJP(%R{b*CAvMmpcoCVCsX9j&5)b|eQ<1bm zyBBzNq~=y>p5)hE*+tqbLUj$qgQV+SiSJffYgy|E0I>c z5YXH9d_XT28r@s zjJd24V}a9u0$A}Rez-9YnABcfT;Ak`gz@2JB<=0VBN`l3C__vyBJqB}z+UYw7J|i_ zuNw`W1^#o1{WP6pd|lDj^<$fjZ6}TGq+w&*Zfv7zlE$`eHOVLcCvH!mSbo}4fXBVl?^REouDfiwThk?bX%iPv|{ zDg|)cjwo4(y@1=xG~SS2ixC4gq*X<0sAR}=2}V{u8%d%1RvvKLeK>ST1*H*ApN=CuCHLc3d@-T$<8}!SQF@)f`UBA*<^C#TfYrZO* zh&Ece+{q|YX1|KM&8fL^;}I+)$Y&UppRqW<;!dG~$2#+%_7es@^jM*BIo&@@2*B=6 z9E*3ryra33_nwz=Vz^l6(A_uwN&=XP#tR*b#Uy(9_Vzhr$b9 z^vUGrJ!o2(7g0V(mRLi0FP3ZKC=?c1&1ho3@cj7W6~v|iSL$ZM=0l9F!q{xTTzAO4 z$-CuUoREV}d4!Oxh-N}PZJ#3K>D;r2)-2u|(z=l59ycy+(lPFKTo^^Dx?Z0SdKYwf za=}K*3dDx^m{VY*IjL}hSySVej6~tEMwy3$dt9gMi67?S=W`Wf$BYQ}bliQ3a94sF zgC`B-+X?~+&(}qf{gAS|4JSj&X@89=mL(F#3O3L^K8i_xN$f^DcJsbs7Ctx@_x>p5 zc)E(IU*Sq~lvwgd&HYYq?1yM8jlQ$crE?CP;FygV`_=7D=N4cT$nSS2oK#=m+Q0+V z-?1rd%(-0EXk)P@IFdxe<9`$81?Aq>1DaH@rxu_L&HJ}cqTnz3i~!D;hyh=cqA9V2 zdfHaT>VoJuI750`0y{uj*tCSk0i=Xb>vi@;VaMB_i#6&KaL{XVUPp0Y42EK|ZYHnK z+MK{i%l4Yb*y-ejRa2K!IN^0dBEt^2a~q1$%J(iq6;;YUwoxbYx&7_eNE{Jv(^@7r z99Q`H#M?zm9e==JOgL>Nu)gAew*L=K-g(`@xo-`e>jhp{K@L_orDJ~Id=MF_TU~N? zrs^4>Z?(e6TbCB6We@a#MK>2xORqAvrw)^Bk!|iEM?Q}WSxd@EdixOcs~g$C%SdIV zG&XuknW%}cKzh18yk6(8QV7pXdPta~ax{-Y!erj3>WNT-W#C`68rJEd|K`)%1%pt2 zbH#{knE z4rlbK6){c9JN)FPVk&}}t!cPFqLhB^j~*xsxGq40U>K7I@~hF2X+#4vwAAAJgXKaVs^D}OB+Rr& zd@EB++i#04P!iD89&yf9h8h`%VptP+*orY^G{4&YiE!F4%{z*7gp;$33rA20Np1aS zCLMB86CC=>VPGdfYh#4MDmKbEot5F!Gc7RuweB)x%YKZv*qoRC>$^THz#zQd{(>bX z*)wzl|Lsi8uLHj^_zBJ3IRp;q&W^i7FZ#;8QJqH@opvce~a# z#ZgW`$yo=X%yf97Z#cz;jaM0{VtIoxAga?sa7xw>m#v~;)$#zZJV4EP z)hKgLeBhjhCqsa92qNF?%|0ol2Fles1?vyB51s4SP0_*K!bl=+eutpxn>Vxprpr-S z2uy4u{<#c^Kn8oa?%mcaNL!<`a%=G>7~f70%Ia~`YkR*7z03-;RPc6! z3svq6x(r5c37$W5R=3m&qhzQIaxv_d!9Hc3uYu`i_sh?YBM8SBVA?B z%77AXR}|vw=WphNM}4NwPqnwtsf7OMZjCEzy$*tra+Kwk;8m=NxtkbZ<)Kiw#$P{8-b4ninPcbkKM;%es!wREwP$r zE+1L{91w&~#@_3XMSeErBQrX`L@p@r>D);^yFnP;^oCCH;&}}1*0Kd&p(AnXa z+usC>RHy{`KaCz$o*ZYL)eW!qe3GyVebyiQtx)iNX!rC*87p6_$(@bI^3P0aNAUW7 zD;bAdvg2VaFB|`9T8q))m9yfPtQu~ikBm~f*!^_N-Q^XMw~SL7-p{2%=*;WHPZ^x{ zyR3jKsC~81$p4m`9?9cvq2Z>Cy{Jqn1XWOiOlb!s4(6#@nWq!JxA2SNlXWBinZ(dQ zhq8I(hdK757E4q?C5T%V z_8GfHI=MB|7IB463Pdj9fO?=kJ}>Yp?x~toeb>NKWW;#^t2piu=Q1(j_b&@nk0Yk} z@__%=b$35lL0*M`F-yr zS=AMMA0CAx?k}M-{)2-t5Pg;I{Qf?XHQZ#s+Az~AP4W~~HleajVp4LC^TR&BEtz|Y z8wpy}G{+$3mhHpJdy~m|5NXB7nkFXX%lt5@gK9kc)2EhF&K@(eb1P{(>}~nr!E!b` zt!_cM>xox;`>KbNkzUPsn2KfP!-P*bFFo{=o%AV(`rbO)3{)Pdo8*1Zv8|C*SM*xv z*c|5PlX>tvjko7$a*sfF{3485_;2aUKyoSTq0`UNG2@-Q6nd+!8fU$h zhKQ`Mrj>P-nNf9{pI|ZY)BgTt#h(6jW2__<9b$gh`_*ttB6)FYFk{wT%(eE%-W;i` z7DCFsSe)HjJ*CPHJvOLz5Vei*Z`@FCnRkEac0igBxC@oN`Dy(%lz@O~-*B0hAx8wx zY|zLi)!!H#3{)Is8^KMQ_tJ@Wz>v1S@3MP=Ix?0m78`s-wqihH>`0FzZq<2}?d$CH z#DZ+5bmb7a7yqT5aJy6p_B(2^I|%eJ|IP%w8PSgFlzMmCRgMVNNm6C)L-x(T?s32- z+R#zNmOA3v%Nb$5gxO9cjX@CfKQik-mewqBUz*33z|9{*240w@Lx zay%5aTgrszn`4QeN2`i4sZq{^gyNf(hv)|ii=j{i8ayfhbRYk#U zIxtU=fbGk!bH7g4jF1HiEt@%1LNpnHK>KE2_$bc_BA`q)$vq#!{hR161yP$1nEof| z?DSXiu>0xd;xa!DM?P&Z%CC2T6TBk_b^qu{5uLrl*ZI_P=w19f@ZCje8+dle_kysx z*X#w_Iso-&R(jAN4Js6-Fo6x>y5&;`oedp12w(QZs28)R!fNqgL|%@IlR=LgZYqnM z*amF@l35(jLTqBh| zP|YL*{7Js^|Hp;()p@qxzr5x0{0RHEs0f5RThA`ihTpVs2Hy`eHFTr^!`er#T>Wzi zcWc8K%#+JBeP?dcxs7#x8JI7{FdumLgtX}9r%W&(dG4ntPglh%^VWNz%8Gv`3_ zw{oKI4ByMHiD7p$KAH;2dP_r%ew)f(%EqUMZUH{f%0l`1!^{d|7a|mp@Nal;(@F)? z-;W+QjRKZL3Yps3Y5abbC$dWuT1@T_7pjG5X%A~oj?UYQJCl6r7wTa?c(yO(u zv*jKO8`85q#dBXgGrfP3L%FuF>=Zzx6v5Lr%irI!RTXJt&}xGS1oeSE{V<)**21~S z$)bchL^0D|w!prr!W4jUvcp!^FE5UKS*uKrM#__~M4e_<-uDdLuu0geC5n;B3lp|q|!aTlBk7MYXFgfH*DBNR_C?#&gik%Bieg& z(6Z);R3MEAQ?_ISQ@ea?RQFV4WaMh=_96;7(#-j-6t^01FJ$E~sc@!HwnN*%tz>eV zlFi>_?D@nxybyycIL>>(Jvw#c?*CM@a+G?7iO21~EQfI@DZs0lH51Nd``!uvFR%o* z!CCJ9j^K*LQh80f{EQH%*B#ASVrEm^*>Wy~#bR5)%1;DxKjD10$57@K zIBn0j@#A?+sdJ$|suohz1rOG;Vr+UAKnH&i9_7sn4fHXX{-{C-+@=tVii@LAPvpJ4 z|FoW_^@LkH7@ySb7%EOnv#}_+XhFRDPPghZi{$rkhFd5XK+s`2}8StwZQLB5yX~HrRw*iw)h`@(5q&bC8@~%NCsEm~patonkpd z*cxWoeaac9Bz2VNvI%y~%DuI!)w&2*2Y-vMoqqel0{Dwp8YL?d<@V_bDd4a|ioNYi z6!mx!w&ize>S`SH7d2x)LcI)Q{2mbo8zRGN$}Sc`s4fy=!g03A(&eKe2#+xWMF!Ev zv!F^ah9_o(D-}XEu&n|4Q&EcIHUDUyAvVfvFeyWEXMI}@wKi7_u8f}K@Q#l=Nk4+jv%cK z$0-HKRwBB}042>jL4}@f@3%Q_Q=_m!qqpgRvRp-&4-p}EsYAgo*bq}OlcbaWPcKpd zhd&g7@9jZpae5y@r#<@RTYqD)xKtI+umfwEgA?dtr~_d`HcKWD7;IVk<@_UYu?4lM z1s?v@@&-qNLb9%Y#VJFjK(6Hh!5M7*g5oQQt>2E6E(CTq+Y2H^4Fv4|u20C$N{^WP%pfjRd*|K<^EWd) z|1!CG7-+dpx~bVF^?d|7t~fviZ3mVAf@55a;~{=n!ORk>VNm2Y)Mu{Y=5E%P@0rh} z++;g^9}XG$Qjb!vOalGT@@BYu@cD37Np)iW#zoa9cS5<$$WV=;QdlQcg|aMu#jt_h zqi|7kfiuC&Q|2vGbcFC$5YZGS)ab|W8*_aKpk6H%c($Zp(hh4eAgznh`MjB5O!Fyy za#0OWHD+2!yp=mCfdYb`JWVP&c!@g}8D%@>T1$u3_2+M@qw$IPog4d+LL;d~_5gA- z{MmU=8TN7`^IGeovWRq_d`f?3{qp^c4UIZjxmlp`4QrZtbGREhpK&LBgjZ1KuY4!^ zz~ff)vp=*y{IQQKS@bcr-tT9nm@?`9jT$9>hHfFz`SN}!Dyn>y*bSYO34I12(zLIi zvy)3R^SGLgx3!+!yNE~3F5k3DsyQ}G2yH@?Xf+q431g7_zR`~`qScfwZ^v$<_RU4# zb&^ZXUPS=c4{AJC`iUYD+XKj6T8C{Sd~;s^CE4YMcft z8kHIZ)yUuccn)1dG=YIJj5^?nR8#7;5Bo_f;i9k_1`QON8}E|97UO2>!BhyYt_Y*f zMH0ixQ%N;rEkv|)PYzb01=WDW1#7G>NJm@o82F){{R0pR6kN#SVn5V$v85nNnJ za3EVJbMTg-~3D$7KAf!a5H>J zeH*|cs(^zq$sCiKoVT7WQF&--WWZ<}i(o#kLb=2X^~dT&vZ^{?qSy#C+t5;VXd+L$62uLE>#2w2HcQ6O!$8+q@Q1*Hn>GSMK( z-#%nN_yfQh=lZs+>sXH7YL)%xZ$@0;3DT1IOF{Qiv1fdMgZJOA59IsPjHZwTfR!v- zm{AKxm5;FJ_2f1eQ z0%K9Z+5lU%Ok9E23hqh;_T8xU8dpe^2u9?0|1(s2B$5t2b>xpa;BR5CKbH0tF`s7r z+_Z33P#L3Lk+GFgWbnXu<)vW%2iRWAqxI&`pY8fgb5Nfrm@~*n>s(pOcG!_&o{o%l zYzhG~@W%aqYF=^#WUeEV@2%&9tPIIO#d0I0h;)S$Urc^Awif!HSVTsI;atV^E$?b> z#S=Yf2ti-BU8#-T5UW(Ea3A++;Nvfo^>f{3l%4%4&->9_$n*R#g`G4qG+vo(U+Xd~ zgcBjPP5wF@+Le&sFfg50eJ~T(FY0m*vSDQY7*Qm?^uL8K`4k_go@rDx;pZ?hb#(V* zwW8@2o2jax7c<2~p%6*vRg7|B28!H`*iWvqs_W5&oZki9&sSzgq=FXk>be&(S`kk| zgly!!G@PMUb4h>mA7)4mmFEX`7RAB1d$O+(OiZogm)<(x{@hQTyGBJwiIkAFjtENI zzn+Fmn?h#w)nbnz_UbHbZl{6BSo>H>in^ysH_Ga&3eGEBrg#GhJvi zUlym7;yc82-WIb75OX}d`PkjZzTPSRd)*%{KV_E5nlRnvpPM2sFnYeqF~D!&m)(FY zL-H?syl5Kk`3F|bW=WhKB^T~TO_sYYSG5MK{w)}i+qaF0zV0#}_^#jbHLv+)`A8=? zZK~+~+#C%3XeJux6YJV~vC6gJ3BnZX@@_QT#%OrkZ7%%6qMwOs`O+T5lPE1VD=Ol> zEcK%5Wk5z6FMfa}=6hc0$H^-+to2O%3%J>?^+404TGmc=a*^sGW)9i*ciZg~X( z(=8F=4dd6ui-$hk4Sfh1^8fta_ucatX_%F{pIAx_IiK}xf{;OS$Gj=spEX8C$Jy2C&Zn7h#sL4%-sn`|u&Ho`dpAyXcd zPx210yL{j!?2=L0rnnu%f(aF38_Q3Bm z%5&-oPfH_S+!q)#570&2*n3yd7h@^@Ek4_N`IxkC#crfXrk6Ly)GDd7O=I~Sb`;>) znWGb+P%a^Y{4a_CXKt*j23u6g3LG@(oyMeIJl2AR91H&!C^U|BJii_e_nul zwn>JBOF#eS!wqI(n4*J;=h?vXaf(F_PWYFe=5v&}jQIO5=G7AF4bp_y3GDr!qvvY{ zSVZ3pe=a#aZhypzyg%k2*S7nqvFNX1JNG>MF3!H5iZP)>Yy}=U&eRTbWpFqW%b*F8 zZ%N>O-|RJ(qHR1!S#3w|rq_}eFhHnbfBEn`jE`_P?DxVY*L-9pE5==Oiy3P$8=n5< zoDVlfTmPvwKlk$LYg3=4F<_^yt>%y}rDJ3`tkUuB!_QPj8mFboAE;mRlau=W&m>vg zOqrv4z#ALs4Dey?mzb)#OHCTy1aG9mrX8wZ+U99>w%eVW6v%U57#zKEI}*#ku|HdM zH6g=J)0;P9KuSEe8O5@<$mJ$^**%*e=vi@4IEIv6f4Af{jIf}izNSS_SD503GYMD` zv!B^;6TLO6cKQS5a)g{dS8a+eKF=RCslVd(at!!atinN&vxu6mKeL7jIg$Rk>B|lZ zNI*3Qt{aJsGZlUW@?6cnhI~)$=e0|UcJBg1IN^vSJ`bnC#)2jNVbPeGaDQUkI~#!C zTAlu?oRX3)8vE2bv#Lv2$W3u(3e0>UIxE3W#CwKi7|@!Jec3Kd!7>B}TJl$Vwb#&i z8bue8a)7Yb|>0MF_B zx?eB#|7=m;PV#O$2>CpoZwg+!dysPhRA=`S74!Ohq)rf2>3pK;g<96Xh*+VZ9{ zhKg^*NB-XLj~!%ddH7J(`989k9KSDRFVlh5@AKblJw|3rn6$*#vLH!RKaA6%pePTD z4|l1-lb+^($;*b@Ld}bR@C(6qX>e#^em)V~?;JVcMz^=E^y&qMS@Z>BVRAX#9e zI4eDs>H+AjysC!CzZ0Dp`T$vD@(0=pjdXpg4)Djdw{J8h??*v&@2*btpm%TBH4tIN zWlR4>e6_%0en7dvOJjU~e%t#^xq{xbcDvEKE}7d6FPMfh3}(mk45!qKAPM#Nz`uMz zns63)oXujc-{k}osMGzsmi4U1-|;N{{s6JWFVmI(a(ghW;)Crk2|rQ98s1M~ep4dQNPVS+jnH{b zJ5)UwE}142GpdjVu`3SmP(7M&4SNDe}n_etKi1S_8Z+Rhvqm;?ENt*?pc=fRNB zD7~`1b?6X+&azNl^zQ7ChA_KWUu3spai%ir5|@jiyXjlp&V?3hnmzcx6CCAwZ}y2L{a17;?2=)*a_nZ`u3<#s7J?w(#o`8}K|7t}%L z$b0Q`eB<=L`WI!hSfvSNgx$dElZ3@4yFj{tev*%5TUG?-sTSdl$p zHajX*D(t6WJ3#)tj^BTk#y3pS{Vk?3+!NE(stW4c}&*V5fcz-R=?`M(No(A)`y`7Q7NWW}mG-|wTG z$Ua2gunBDjXRAbl7F*x7&EC^kGs{WIIvck>Wop?+doD(8oR( z^J3Lsar+SoD}3!T@&ms}MfUPH<5vzxI#hd|z5m`a-;PtvFy}1L>MEc{&s-joVAF-V zTS3g40_NUDLS{o#?(Om#iwoao#0f~}_3qbp&0ngz{{}Ucs$vfl{jDHKpkDTKhUPVZ z^tc=S&m|@(u`et07l_Cz!gfOLsEOD2*$k@jXl)R;Y@a662kW)%t@z8F!9H95717QR z`0u!Ud+{^EzJw~~oJCnK@qfwSx|ty-#z$|h2p0m{^-a8XrY|ymfDce`IjTdBqLrh#R~JsVT|hdk0W?H`uQ; z-b8FBRvO`s_5P3tETKo>(31Zas}4iw9|+DPX1;~2hlN6yl}9k>4Ib8&vi#$U;NlKZ zzdw5hp+t5}4bsJrd}+j_a=BqB$GH)sb(w~&Vv8k>!(-9QXv&3eW?TMr1_|`mFrr;% z8p4#AOMm|*m-_pwEhSzFJyd8e_*Gk6V7u z3eH`EpI%m50nV2Iziq!R6%`#Bs$&^#8b`!(L zK!!PT9;F<*P~BgSoX6IiMWC$$4GCC2_B3x|~|OM!e$^#kUXA2%R3hFL!tY{rn#YNVF@e;_J_(BazD|e~=z9 zMFO=gugpPCK^@wf-@l_%+Y&sAC21<=LU3H=|N5#0@P-v)$xDiW_M?Au!2qSp?q*+)J1BVYV8ctC~l*5*Jjhc z#}HM9@dVxU7+1bsxYqx!EFeOrV$BeKTU6Z)YI^mkVH!1lD1NvM$q>a}`6KeU&(@r} zikEJLR%6JoIgzzSI{dq{r-GPRh}z@e2h#RitUp_?!2GbWvd8=M)kCyGQSAE8orvho z`;)Cv{pShsr}f_DMw{eBo|f*n4w(mE_lPe%CB#d$QzPTyz?_|Os$g3~^+s33)$Vb< zM!QwdiR?v$_4b^+eRKH#R#Y=0(J~2EbJcm6hc1!GmW3~{9BT3KGx{G3>uMtnW0@U#QN0W2=9D?1%^tr;QH-JzK zI%TmopwiJBXLh{ZW@7%JoJmyzZ%xG0gZ%+H73i{-B_q2nd}jxAT?`Rz8*f7UUC>@& zzsp&2CbDA?RnKw)V5%n_Pf!XUh)GeuT(>m9-hAOJ1eL;!x?0ovvZrWeZMSY$FPYbf zZ+3{YbHN)7Pa0ApOf7Fx6_la*bfN7#gPVvZ(w;AXm~?7kMYCLGz2cjt+iObTwe|DQ zGkl1AiQfA22Px9?9({rX+BLSEWjOVnGkxM8hj0s!To+3y8a-uN=|BBer`7()=P-29 z0pR0CabP)?*Y)mTG+GiC;7voSCL;NuJ!t`%s#9(ZC}^X);JOMO|1Z&=`uy^Ii6twO zNL%LlW_H`HXhaD{{as<){FbsBf?Gs8bdUkm;xMN4 zSGrgheU#WQWvM+m?1w-qB>YH+H6(J5wTjHFl2S_i$TxViiDlpZ>}L^*Xy0*1#z$?; z%p4dQ%ExB(;OA!U=QGFYXv>_E!AAiSktsHbUG+-G!N=m3tYwXjIfBx?+=PouRqQQ| zZL>Rk2si(u>FdW1xep#2q&r_66S9J<=ic3Cy~)>igkZ)XHrHLiEzbAJz~5uo-ET-H zpCZ!COgCPmP~4_0b%y*WmqVVuyIY~F0zCYPX4e&oS^ewKvB4OnSxB5&#~7b=nv^A8=a_x%;y-VrP5KTt%K zZNhWc9|U~T!v0%-^Y-<-DJvP76KIDI z)MdIsSc@%zQsE{>gXg^}i&E01I7L`booN^+wrgoWPM~TfZV(O;x5e>9jFXq+^dV9i zB|U@0jns1YZ{`VEBB7D;75rqbqR#$mn-n$%S8SKcID3%C zT`O;=S)oc{hYxVt>@*-L_Nq6U-Odkq&_y8<(5SQ$Zf!th%*Len73RVh?ynqfEPJD= zOM6kR1eG!Th|?sba=5;hIm82)*f31I$e13t$4W7GV2rPPn?MFM0dC2+UXDZ;e<*eB zv)}pMPSS@c#@L49&5RQp^=XB^~A?IG3iT)pW$*Y{wC^}JZn9c*bcY81 z`*dxm)5V$I{HQiuNf*3|S+CIwaca=~yLmJLGl@$(YqlcQnha-H1Jm%?e)=Q)McWwb-aTUg&- zA*B@5j|YPrux%fHHdg6Ymujmi*Kq@~p|dTiv+3$$h7Mb|#Of zb_u**!F--LoWh3I5Pkz+Z^OzJXYdwUJRRIgT$D&0$44D@r$x2)9a2n;dhpCAY+nyvS=|gB%*_UFWA!EVUIq_OxF{ zZ1SfO9$|(anXh2{avcVePihJjW5&H$ZRmP1R}i?@((QS>KRM0%^97UO%p3?_~N=3`WNv%&L8UhFfmx$TN7C=(=f?HSw?cA^%_7tVKE^PHl2Q(xpr9Sdd%=2W%5azWmwR0C zPhF|e9Hc*FwOL^`ZH@`Y?T+ax+;S7mh5-BH#MYL?htrAI*oVzS{{3(h5rQ2l=+LS_ zmDPO450JQ0)nmZ*^oKCOT8*-a*KU)zz$wnj-15C_|DVGX51og2!^16eojSk8442T= zs`HB+ohlr%UkI?rdY1(n z7pIN5!!iVjPdsFPN2RvFpMn+fg*Tf*5smf@#Jt$=tJ%yJtUxq#C&MfjiND7bYH(n5 ztYqxob2`xLm$!E3!i4@Ptj$E1*vHEZg)maAjXuQfi1=*w8lCAn9wB|)<5m*O| z5?dRXHzBe(47E+a2n4VHkq2w7i2oqPH%8K}?p8yJk>7@{PG=}}tLx2ZW8@~UUM_ka zN8`BeBINVlNkNwq;QMdoxE1J6oE~gPtCf-^aNG6J)jM)3fG_?-@A9(B{U&&wc@`{yH#fG_a;TP)G$m#(&Ykk{&MpxrLKX zc9#jhv@fZ4SkYKo!*@5~nGT0Uwe_LYQE}xpiX@o8C;YX!t07N}qkMFqyPvcHnJkKM zsn9?-YYD(T;nfHm1re|IFOuggY`syz%XPW5Ys7$@Ng{Haf8K9j_JQcO_X<%{nB#{I zKTp&tp$pWQ+^pUz5UfYV_w4PUx$VT0h$n;!YxBJ>XDpj1Jwf4L7L1p>B&{7uHXE4+ zqW7{)%4iP$$+;rd;tl_f+&AH5`Btf1rK^n|l59fy0O?|)qL?}mvI~+ub#<~q*6rw= zf<$q?3yy~zu+~7Z$1X->mM`rqUX!Z%8jq`iej@tYXNBDF#Aeo1%88Vs=C*LsCFE8Jo?w>6xmG&r%raZ#A7E~T_i3N2GKO~(G zY6it|LoOm*%s~zgYh|4}Th|6<=56Yk$^<2g3x~rd9;JdB+Qie;S5s;Qd>I**&+9ez zo+os+?uw-Y&3A>;vkucXsi++bL!!A$rVdeM=WbZ?1JWYCIk(Yoh z_Gej9TSD27Ute?ythXI>==?;V*;rUwkfJZHfVj5T=*=1w%ym4}M4Lf!1la&8j89Tt zeiN=PzL)PqBX+mKSgj?>-zz5&H35P70T3L1os=TQJAT<(1MOZ>fZFUj(2|l~?(20| z@6u$WV`Uw%1jXV^pB!YqU67M!&}Jhd)uY|Q-6YH0Ow8zHzcimVr?KHmKxdEoshS!e zHxy3r+j^Fu#6y21U$k1zmU}c7`(Gpq#PKgP#svPpvn+vLXmm*)2fWHhs{@Ck7n!rz zLUydb>Bfkw51u2hWN@s1R0T^Evtdv^5zNM(o_O|vS|u5mY=-`oZT?oD)VwB}a$;^0 zB%}Ki69Hz&J5_u?-WuFa-Tt=@A~J*>5s>fz7|RRqU4Y9MvVjs%wrNo0opgMTRE_{t zM(%u4DNN_qCkyAp!cfkMV}tLrkQeCxRw7@HM8o`_fHOD_J8$UK&I_T{gKo;*o=*rg z60tgKTvt`sxAtFmDL~S&i8^U1?H)r%rvR|LK{B5VYF7o^}c5DuNuY3L>kvV(ac@yfoPcZB2!T4 zN?UN)Ckz|$!jc;$5%K!RoQ}> zw#y{2L-^I3)WpvL8%dmJP7Ur8%}+G!?&`V+(!yQUsG;5tPd>*y=9qo3Q4=$!+-bIe zm5^r!X0f18^6HFkuQouw?csb@@;%QE%@%`Ev#KPG)u4smi6vSVof`jdQ&2P*0mAYd zq`+K(a!}gx{T04BK*0NmAGw*N`auE1OA*PUp1|K&% z+|(H(u+iHbwsP*epDM35AI)cO{|%cDIOyq3i1$A6I?p{;e6J;3sx^-Pw;dOQ*%pma zQqg+rsMHRHIq*gQ^@(=#iwDY@ES%0~RG+psfx%te(% zYfW^72TM7?AK&A;YRYkXYsVMJO!Wx+zrBgC1c@VHEF0X?#-1k6J8x8K?#`C%Qij}*g7zO_wHx6<5h zWPbE%qH8P!UKtmN30NHY-I-X%t&Mz~^^kwu+1Ebs6H>_x#4-r9^D-gdJUEHX?j`_9 zHIei!@Bf(V*Ep0yy-4IANQ!FPk?C=aC($H6yq34(?*5ZQ+qy39?O*NfF^b~5O3-@6 zVXGY7>zBR@uCJsnisNqn{7oF}f2d+7mhKZ&B=2Pp`%mPk9s(e}tS7W?+H15BKli%xEE`a3j3$*5IR5g1wNrj&qFve~TU z?bFszjqjLXaM!!BDj9_~=(u-52BRnBH$a1EgkCegI~Y@(FYgj~eULsDNdURe_&o*b z9wLF6?QaN<#@|W{2xHGx%P~9u zze87k!YF|9O5ZY69uT*YvtBHG$RobyZH{7Z8+C>M)c8Gr?4B^61_i%ErMoH`~B65@rSSKoYCIgVN0yLsFmFh{Q~ z57}}hf8ouu67Tga!mt=hIo!1GE{=T6XG74LUIV8-z%tj}e!bz6V2IX~muST+JAV4? zeHr(y>}S|hAEiwtPMu$^7UDc^xW&~Ax4`YAsPpkh*y<(o>ufp2`c@gvP?czl9shu+ z@od|oc=G*v{i#)0%(D|tONyX-Ka=y+mGYkdc>yZ+%nno5`2Gax zBl!vN<@}TT2Pzk;_2L8aZ9)e-L4fhwe4BgP2oz-6io-iE@9#<=q*ai7=W-&&YI}P9|Ifz3p zF@6J-A^46qr;~*9Zv<~%-X=gt`R8vsL1^T4#?7i?EeX;wDv2arco^8j6**lSB0Y+$)wlbQXdJ%6%+uOesJtn8xAs9uzM0DU6Og4ouYR>xn* z1t2+s%Es{V=#*_jJ1iJR;7Or=;fFmP2lcigATn3xZNYF@l{8tW2nUtnPEdOx<41bG zqxb%z=`Ez}@-u;>DnGalhueI)4(&Veeuw-4h$8oMDxF(X<3X2AxGZG}e*JKLR zKPOm$c7fu|SR}T9bTRqhF8=ox5+lH@GB&j+5hXhpwumGIJE=mM8`jVNR=9txidy8# z&Tr^5s_ve$aVl0NQ@q!@pNG1aILm!=DTBq7Xnl6V4RnDq`^r)oaDuHy(Ykqzp6iXF z&%)Z7z7KDr4ePWMj=+O44#vIC%G{p{L=GnA&=Lq9qtypPq0iu=~sGba=8ydwXPREM#X*o5Au^^I0&NEkH=LEK0@-sjQx8LrBmEZROfy<+##rUM98BDX3g~wuPk)EV!UbRA4UV?5 zXEjDR8F0yhoYQ2+ctPe;wg2|sVWc0S=+o!)Olytt$H{?bX7%}*>U0)4*THjwkPb#1 z7G=e_s~r}y-^@yP`kgB3bLlJQsPEa}UDx2QwpPA#eIplr@q=79nx}U78rd8Y>WTK2XkMWIgj&VYS_+{ds8YSAi63 zxa6T-?~v1o6LCc6l}T*RE`N$e;pv5DQ8%dr^d|etk!(erS>oY>0VJPK33B`i*n3W! zeR|4cf@dm*gKacB8tRQ>o0Ozgzz&BeTgLpvw7>h`bGZ-|+KlDm5a+qmme%1z&IA=y zBR<>W6XF!2hF9$#reZpUpRKEP3_d8S|7g9EY{+;S*xTMNOa2Uwem zw7~}_9Lc;~SgN z8>^Yn1+m)Hx*g9%WUk>N&qllY@_ERMjQbU4?J2!9Nv5_aW<#D_H|n7Z-6P3nk+lNU zP(h@k2ad}b6WGJ%l;HHn;C+BLeCCvjj3HGJXN$(SaxsJ2mUMfGb34!;wMaUtO6)X( zHvz4yJ9V`V1?nSC?t1>M&SNJWah2+`@rjU_lU?5y?$fwjG`FJnpoKb|Fjei(s2d)% z>#an175_V&C5*p^foYdR^tZ;n`yYZP~X8n4Y@XvFL>s632Yq@3A_HzmVh?Am=@JOvbx zc+btlbG|!@o9yRH0RflMb}dkWsT{~R`2O_pdFp$QXn;rN+@bGoU^+DpYlez(dVw?n z!lZd%#YJmF0X8IJhRrJvUXARu#6@cUeumR*u^cI5_we-xrmed*BD#z9LZ!N4zqzL2 zRq`ykjlD2v^8v(tbk8BIKoE`Yjek zi^wh;H%x}~-s!u;7~q2s2nED2Ji0Uh12-f!E;l6jvOD|`$Gd~21)WA>vlz9M-}nEs zkY85V5)z97Zlsqq=;~#BD<~M5u$wv-w`4vSsi{|1Ex8eYV#@kKAKw1~Mp~CXRJzfS@&Sqx-3=IP2J>DF|i}w-DW26ED>^_Z?_QkREeGH zzulD8pZGcFBi^nEXAyBiGxuN^8l>+#>*cGG<;f4jL#+4R|2>ivtHq*0a@Ie4l9|n~6*HS25_@X)v3Z_uguEzrh$1o+;S%@@z2~nh6+fLu z?b!S(2)F!Mkezyx+5f*!RAJFeuQcS*S0-LiLj^uCKn34N4L8@QO{1^kF|>$!v)aBY z7^}-_dp}wiH+a7%cRVa%$QqbJ;xsX@u~m{&Wep!o)_)AEy@7aDryf#sE`2BeOP{L( z0~FzuC$(wzMIf&0B_Q8NBW-criac$`gnG5{zdj8Ihr1k*%FhBz5HUuMoF)K@ZatE+ zsQeM0c8x5=J_TvJPu@fWRJ4X)ZBB6g9ZG{u6~*q;M>|-?x{vs*=SOwkf($6j_iP5| zQA%lOF0k4h{j*a*@fQi`woImE?|ZY4b8%Kw+HFqUj3SV3SwYu=KI)Ew}U@N!rK2#~+OFaY~~e zZsR-Lsh{i{xoG9QKdss8H=(|1T=R?vZ6L<0+n)rLGsYXTb}Pr|H(FZaX^IKSvRru5wszZFhV6m>~&tXWkJrj}NZT58L~mSH6w;AX^3Eys$g z;un%ub>2ojdc`l-zWcuxX5uv8J&n~MKl52_uxNXGn{$eNNAb-#_k??s5aDq84 zZJ%vSTWIXut#$AV7I=1BBs(3~TN1x8*dM;lq%sLa3YA*&du5!Qmfc>}a5(?7T4^d% zU0NXk!hykYYu`BzDEwUv@H8+?#Hg$L&t*jt0)RXiT=NCoB|nLqzzOi@D=< zM};@%h(H^N#*YyHr!%L4stc%p-%4==qkUG^-@<#SnALLdv0t3kh10eFIct9w-P0$)1y&(7kjPaXoFaExgR3M-brXJ`J5 zuAKTwA!5oF+7r3tTbCtYPU{TgcG(GQuJMp(qQ!!A>s)%^7ha$FJwwf<6)sHr)FWmKziCrr7flyXbS?5$QfgrpIuoOXyWz2sembpS2vTtHeXp#9x`9E;tqWkmTpdfIV$yP7+fXW3*nL{A z`Xotet4Wdi>QZv;+mK(@)4C1IT z5^Aiv<)*d0U(l3nSpP!LNh>(QVGPzK9b|-8mVqsRm-7NTwQuc5qGk&4KsI|k%V-^Y z)wbm<^ZDJ^<_#p2HP>AvUoM9{Cymn21taq?l-{m44M(pD+3@ifV58I!bp+sZBWmqv zZ*Y3XJHrdz%%%7Nm$3_34dq{xc%K9Nq%`7wy-y@c&!*Xf;E7ySgQ?l~gu|kDL(^-L zb)-v=LrILLlnpjI5D3Pk)Sk@yq0pfq5?%6S zFq{pcR(Iuasu=m|aqQ`}(23dcU2)USTAPX6Pn6DG8Sy~CqDNfQ7lbPN2gade|#8Fn@e-m*NKU5(d+C~o}W>l&P(iBJWiI4?S}G`YOiJlrL4!TvBs z3OVk-OjPvH3H+FSmFC-fBO`IU45ABdJ=#ij1iy=UjJ!?DX_ZgvYGev)B{j4PdZW#S zR%7r+wQgT9KSh6=1h*Kgj|U}XZSiHZ9L^MN1qH9G{NvWI^R0>K`qn`C(A&@r`ubjW z=f;85-LS3!Skre1$A%i(1gItaNIqP#4)n|T8X(??^35&?9*u>@WDCvI6a~f*WUedJ zgsn6rV_`NKqof6znyByGluUj&kcfJWj?LW?+R)_A=;8FClsiHnALVE(jmsSnW5T9& zH+*NSfKr`{F6Lk9%%#kB>I{ooklAwNxu#aMF8-9F`Oa$6_}M#dZ%Q}+^D_dSU^$C+&B?^IC}vQ+{c$*;YG7n#VI(T(XJgNUgsb zpUX1i&(0)(C^-^-M$zp}bm&tx2cu?Q%vVA-YgnfG*N5`<7SsjjCtZU6IObsGVNjUl z#m0&v5Y(Ww7=*;BtYZI`w=xWax6QTVC!trKezR|&} z%Pb-BMdY@){+KeZQ~{nlr1w{v`QaMQQg7Is&`jBU>NTx!UdmWlMIZ31z<5|%F&AaY z?WGS_Od_vYXGoI~Me_?T#$0UBLA`kE2p9fEI>xRr%HKURt=fG zCPn*bitXdN9yTZdyR{2Doj7>7#^AM0*MbRshJ)>06cJ>B(W%?6kFD3A?dF23Uq*d1 zy}=EE^Shj)sM?E0xBZ;nFufyJ4+-dY`lI zqqYX-sI{O$c_W~vIad#(lQt>Mq%ApwMF+Dv>n;E28eqDRQTtH0r7YHLAr*d1d z6XO-PA18KHA-^Yl&RjT({$T<+#boVX|94#+c)$P=PU_mpcUd%YixtQ=EGrN$kkYcbGCFJMrSb^GNEBR9$};A4-BO>emUyut%V+ z;GY+xuLaY-qiEhwUoazJbJ_7Th}<4C_>>sORD;aHe(UFUSFn{`2B!UMk#M74SKHUS z_nf*T^M5XP!|XPcqDT)}fA{SCIF^06Aq3oxOPi)xzTNLE8n(Ia$m0~jhOo7JA-L9O zdyROkJ?&o6Unc6@#O&p$#UfJc4Y8cTm;LSX6++Z~Zb|yLsg_4@o?Ug+`TVFV*=GataoObs|zV4%5Knfzw}aZk>vX}Uf= zqSGT85`GWm8K!>vcbJqv5(J|d8FPDW_#Ymm3mTdA;+gRB1DB zKOG!L<0!;0M8{&Y>@BpW3SNz5TO6GlcEJ<2(OcnZv1WKMdamAIjs2`pB`!|l^^OF% zID$C<=@!A~?aVX~=r6o8dIx1k&@`;Al6;P{0SbhUV*Y&|lPo=-+7BF4LuTL6!I>@K zkzJx8M95lsoBOu77)W125a~M`-|j=QD)^b5P`Ex=kn~3!z&vFEfHUm}Y6a=Y{3W#} zXamYhySd)&Q@_MqPDBCD$%D>HMu_U5g{{pqCdN%jqKG8%N=M_tK-tGZkDl!xrlQ+^S{Unrpf=Q0+PujEbT@r*A1h1g36$Kq_6gGk*PE_eyxjUT95k5+ z7!k45X4S;vXW)OE%Voet+>Qyk^c*+&$SR-gPQ5rhh-f_6?ap<;7p`4PRiRMHn^UC_ zwOG%FDi!|xZa3>K2sZ;bP0{Vd0ErON!)``^O3V0LO3Kqho+ zIGryr1k=_LR4WJ!9@764Gcm*2yjXK69(l*rQv$s7M_MJ(nFw;|aPvIONVQd9^A1)A zeO1{i_`w*iF5tdl;;wf9Wozo$(`lSf{#fNV691{7MUuuc|E1NUpV=vBNzXtYdKP*9 z7uL+tPmwX!D9IG9^;BBt(X&k$;pbb$u&*rDSxDw>nbc@kWGc&mvz&U>Oir~cR4Ou) zUQX+piuidK^Vw4|w*whKgg8HT@3(aVSGUX2OlS&OR|TsG_L`M9)xl+rI=C9P2%Q($NC**{s$9ngzV zaThpW)|U6Z0=B_dbJpO{Y7YV1={s5E44 zkf87FDT+~x@9qi0_^7(!(5I@r$_um_b_lvAr4Uwo&-HDO`CuVj*V)z=+eKfh=PuX` zyv4<8qM^R1P4M#}nQ`KEP!!i9471+!Xb1TE^+-tS?T=m|P5}^mDi`}|P-^QK196|4 zgvQgOe}+Y{q00NKHu3L}y)WuC=5Oc0!p#>f_o%5uB1x^U7qYQEG)?<;*w|GBkGF5< zm%M#p6->q!TFeIBR9pP;(M7$8nkm930+1bHuYjh93rSW;YXsS zt4j3(1Yu-VD02->^u(EzffJswV#>?;QXR2`j;)q3m zLlrhdQXip8IpK_Km7AyMDS_wrvjjX+3+ z-F{f~zD6BgFbqx9vDUo*>B+_C&pgAS4L#zl@9ARAx7)tI>8i~(W7uMM5Bs$Zd?{2Z zMHj!_`v&J=A7&ZBgk@V~mpQfC2f(6a=eiv$y&;AW+J|~-`#HImO zyAu0jDffjN;V76&Ep|Q|2M0kY(h7ZL(B($?bEP*Q+UC%}&BVk+a*BKa&VP90 z;6hjhyZA+e2osbWL)U)cwQ0U-)YxVj`;ETW!_m7aQ^-UngurwFA*cSSD`ri4-`ncm z&Sb@%l3MT>e;dnWaVtBfyc9|psfQ8R;3rxxJC!hfPs8m1G9=r?f2#+***EBSLt4QGf;W~gbMBYT~@$T3# zXoP~qN98m}b1>|w#U_8GnWo*gjw~C7AvVA&QV%}r-Nm$exCUC9TUgw)xTxcol1bef zQ%oU0!xRBF9P1~X_7-mwsepLd>vtYr);Sb2TH|8)1GL$G#n?>9v?l|PU0Ateq^A?> zLQRJ-=815Z`bfa{jizqp^j#VXk54nD$JF_KG-9>x@A=Y}nReXh9TdSv`4Tg%4u-Ui zQ~U8ENyOTfWh8z+|9?0BaZZwszKy6yt6HQBkCXxdQ}Fs(C-);`K_xy{A>nIn;;zdd zu8(JHUo6QbF?hlSrP^m=+-)W)CD@e2R%%h#_W!ln@!hcA|A~C?iPPufG9zKj->}d! zKSsU_(tT}=`<*L^U~V>bXCvO!SUK*v*39y$O}N=NLz^xEX9r%eF=b7f&P$zD?`QN> zTf}?3=d*g(R~?10+Hj-Mi?xsbbEc!SedoP^v)A5Y-mvfs?@va46rl9Q6!7+);QY4c z>CS%g$e~fo?VYBLQ3A{^IJ@%iD_&WjR#$V~O5?~SUKt%!z0MWd8oKi?1MZ#+dVd66 z?GKkM`nN@;^A;p`|M*(0y*(znt;!{lg3q8*a*;7hzf~cB!`S&yn!x=6!9#bo#Os;C zM+|~|+*zi4N5nB&+YXI>_b#V=u1nU0-LOaZA|uW>$NWg%ZmU-XBQX|B zyw!(!8P|l?!bAf7501p?ke>ejiHy9!z9OP$jQHFk#$g#~eCBO@(At~QfjxR*I_8f_ zTig(?9Z=lMi}02U3Ee11Jh|g_=`|9B;&lMNd%D^@|Gr2IceZFM;NP!mFEr?LCkv;j z&|-q=i@~9llUd6nOqgsAeWDlPB%m#dr!D2EyE5s#l8U7(t)ZtLTc>gR73c%f9ggF< zSnvm=cso`br{Xgsk=Af2p){tjy2T%zXI|8Fhfv#!xUz-stQMD(ils`i=9$Ui)(vcfH_BvtA(cs{(dWE8~ zmFtgvr@OnB-z16b@8GsMz8K>$98t-PIiSwS>uXwmiVO<)ZR%>S;aLBvCcb_rfVHs^ zq}cQ9C*r!{H7*pEiQuIH$9L25W9jn&U`rq7&A_n!^EYp`Eo`hte4~{(8P0Ei`3`y$hgr@&=4zcYM{}8SQv~i*zjLI2U&3p$7$YLwyJYmgfYP8 zV}5i~u$aeR?%^Jzfw6zlIe&Z8&Z{O6*4h~6dSfZ2IL)=~FcW6CSJvVn?4p);9T1C( z4!c5OVR8nwR%l0d!;(#M8k6X zm=WGJ3`cEmg-&kzTzy`6nm)$Av-ISDS^y4&RnsEi(T+r8!Cy3*56$YhH6LCJ+|t_M zV;U46)}aKHbTZ7S6f25w`NLj89)V8MNFE2TL@WlbJGIK|?)|#-PCnPf6DFh7e?xS; z12X$DR{J%(LgR4W>Pjg?0y}l*U=ZMDAeHR`Q~+JnpK+L&$|=gP`&WdbuiMDvq(&az z`_9h9u>Lo0{o}_bb3D56z`l?b|31{;ixeO?S@CADHTv)~V%swsx*3kxao#PA5W0zV ziquq0_uP>R5k@l<<67>pTB8SU`2T#(IBoxXtp_30s(+G^4rRNu$%)-O;j~=Bi0fKt z*YkDzr(q?!?VW0ianbGx_^F5s3Mqs8j={VPTmTs{WZ1(m3qY;O(2guKw{?>EoH*S1; z%iXJ75=5<~L^g7vSc{x)qRn8^9}kYz(2OM!`mjncfm8m`@k$Jhvh4ElYlI$Rm5}eJ z=%%UVp~~C!?`a1N-oc8PD~Np*e~?ZWQL8ojj+qYvkDc-r-KBtnnckIk6EsnX>v*5> zXCtG3N1YxBu=#eqbl8>6ytfzs=!?o~U@+<6xgD4~MP49IxZ10lL1L7P%f~27#0-~~ zRe=hIVN?$2Y?CsqCJ<&}Lv_Nr_vSn{iJP~}t?K)k{Uo*wnJ4RXD-^qtdl2M4daI3xob z;m>RvF2bt+PHgG`H*lmT>DPvV!UZWb*!C^qxk!0G7#nG4)!by<5Hq!bF-1W4`dMO5 z`{RU!bX~6^e^$TlPcMW;d)eMeo^mfj!pK@i1GAv6Frfvv!13_ENIVN^(HBst`*KFh z2f+o1@?6+s3$1zR0av_BU$Cf;9uUH;g|OO-Xnk!M_wB;jdX3T1q+^Mbc8b8BtlqR% zZ=7KCVZy>q(;+q4zgF3qSj4K*s^ z#;|C?x+VF!-&jDz<7z6Yo%nnty>_(hZ#(@xK~CS^x85QHMC@~l#O;t`ZpMMteyS-m z7IDEvFWMR8^SH(p#Ip8&i` zIWxU?KrYMbou8ij)kN?*@G3`|o3A7FYZJCv)}*1JGm7U~4r7y(O|QRyo4S%Pn^z7| z8humE^=Ts6b zfJ8Ex49}>+;UqAv4ByQ*ud?_~JDMZ3b7O&iCo*7k@~QE`l0~ZwI7$1qeFYq00oZW| zlIoS^Z{$GihI8p<#)F<8lvY#2*T9qFk*DI0y$$6;r)+z7DQcTnX#@JCp8r7mGXj~J z$zbULzdJiVt_pYB$haM9^@sje&kS<8mwQI_;rimw75K??@wXpIA9}GL-k#sqd8um8VF#Z34s7+df6P8tVM?}=rS7AuU<%0fr3+mDgIzIaS02L}Ja4$0pc!Gb z@N$nuA>9bTSbOG?|4;`~W)HtYA6^;qksL?d5z4ANt=}UPW!7u(Qu=nzH?gy(zea3c z|HOXX7aRGZ{Z{TjBB5TVqE3AW{gjZ&oJ(@f?=2QS=MnO# zKf8@xp%uEGcj#`NO#M5F22F|sJU-%+A$Wc6!krg<1FC~|j1oe@`P2tJ>f9cJyutcO zq2_`l$j%bLSZ{P;zjWTv6$TsHuu!ew9=u)@2E_Y#(UcMpLX&6uop3zKVihGy;8^8p z#{!GURPMWEtOj!lrA+3)+9=s&YuDlfbOl_(4cFHMMF0;FMF$|nvOPCI#w7PY)u@uel8sB? zHa+t$wYoGsnj9zPz_Yl?E1eim2wA)F{V+OA%XP}D{4w>#>^tsWk|eyQSXD#5@}eIU zypS9V9|pta{%diRv|>n5R5Zr<5}<=?^A?=ZKs%+!+>TV%Cd z?Us~@K{`?;!ez9-u36b_W$4iS2>HGEbKJP;?IvSsRE>N2lk zOpM~Pag0GGJ-=>Ok+DWzc|sS!FOfX4s5GvSZX)Xp?I$!V|0qp$4G({nepbfS>wMOe zs1^8KFt5sqpqMhAG?R<W!X zb6hK$db|ipnY`AkwLaR?D1_tg4u5(dzQ&+Z`xZkR-c(bU-Gam8B|Mx8|5OMCXpUT0 z*im1EqFs;r4cHktTIU`MSNG2l%uM}V={9mHf%L*Xv-gZ;b7{R&EFJTBCxrsMKj$S@ zZoigErl=D^SPY3cT1kmLAG3N;M0s4Cv9Jk`8m0cP>c#R=Xu>w_o=yj2Bh%Uz(xO`I zB=TIMDXE{C3DSV33Fum&x7jTomXhjl*ab=3B#8*fv~uON5;)d0QE{7ZzboR#;Hnas z+@yQ8Av|JL2kx1UTf|jx>S1m?F&WEvGRq%RVgNsGC%<-*Pzr))O=E`XJ)*p?iVdom ze||ubUj^pISrAj7)G7ZlAifSV5|fTlrrWRmyqaI%bNxZ zzZ_Wh2j8_xmIX|xe(Ex|SZg~};_=fJB^P1OK=;WY7&tR6wup%~j~)Rgby%*KZ#3KO zr~F|pCLgE&HZ9YMi4co~tBi7Br%@Fs`jR;gCC{V&l^~1hmYg0f+ekekWUr3NwRvFBCk+D;)c{$znkC0)1P7ohjJ#LnE8$=tog-bw*(jZ?Y$uzMad z86#_!Z%;u1eKWu4&^LRTK2_r$L`xck8rY%s`stks)8~vO>e_-{ZmA;KCRu!WRe;N^ zzmUD#Cb(UCV$=2gIL?K)tzWA?Sey2<#(eJGN)Th z#Q6#P+ZxKhX4~oH_PEjv-}J+U-G8v%KgzRY44k@q^QyK$SF1O!M7INanz;(}E0^ZSGV{m^BNj)g<8^~s#ydMb; zJSr>3lB>dm_@sOH749^Jz#V*I`*Aj9Gbbl+AJ>kHivBQV?dy&`RlT<-jbi}@Pt`Z)PQ)~pnaEUyRIPI_lL#d-ZE;IY05RFrtq-U|dY&4CU8Kb@!wN&ch> zAEGE^d=r~GRlQWuEl^+V8`<=Gp$Rils3>)-s9SFqs@tNbJ%cdV)9r_%qlp|}q94EH z&4&%a#cf}qrd)3CX2d^9CC5?st{{$by8HgW>dxGK#C^vY);RJPf!X$w7D<38%jIbw zeIB`uJGr~ff38yjnbz{+bUM|vk{Qmmh!;rKw zysJ5JUpE!c>gQ@p2d?m`Bo9Y6!hLUrC1u=kJt$+4f9_hJt%HMU?YMC zPa-k}rIqy7G0k;;=8Uz=h+%A*0*N=fYT!ce;B3blRRro81E-(x_Hg=9{9J!xGi{4h z63@|d&fa{7-Ub)FM*45hH!@0tXT^lT(_6h+Tdg?~YnlbeNcD(JYXUtQit_b_AWc}_ z#>5U{7u($6FHA+yRg>+$19lb3V+b6S{ewmO#DI|73!B^4E$^xC?{__Gni)yDDM7=# z`R6=6v(kUv=BWm3q>53(epG8BVme#b3vpX&;pon;#wNnGpGQbE42<;h;gtHPe+;&5 zp!I=-L?UPch8kC(M-Q7`3FUyOu}_c{=9F*C-Rh}pt>`Flc-pHew#WD*1aw!Mw^9ly zxU9xu*7P3@%AC7G4_=;sRdNN<3x`=Gp6Ke;_(h)rCk4Vf?W0$pg!)G|k4?Urf9^Y7 z6;YVKNNEXMk+8Kr8`3Aq2+NR=oGCo+&fz~Iz8=0xm^)eyK4?6T9w~jqVJ6N++V`D6 zxAa@?z7Nr}J?p}`v6qOrbx|lh2rAxJs$BF#YL=y?4;~Qt!W~Z6#Kr6%N-*>qdAys- zrj*Mun(W4cb54{lJrinOQ2Eek+@bJe|^@yE2b5R-c2i^Px-#xL@1DL z2LE`bD)rBRrtjGp=PrmEoxOcsWBb*TKI5JfQ1ft9>|0c%Sjp=i@WWr~*N;?jss+*r zJr}9IC&9~ZCK`L~LYGx3ZJNLR;fSc%jxu@Cy}gtPcvDdQG4NKq4nc1$j_lhJgPFr} zuc4=%Nke@_Z;&@!;6jjDKPuHq;1GkVti&}N099R7g*8XL-lDFdLYZ^XrTP??fykA1 zYGyY^KIC`SG8Oulj|OQ?X@H7LNzEa&o!{T&*@K0H9?)7y3LltXY~wm+7+uBi4Tr~V zip?DVn)ceDjBkluPw8&k<$twprG&p5wW>9dFRfH;NUWe$Q`hEC>=76@vPuO<#<&N? z#0e;E`82`cN~@TbW|$7%LonVx1NSa%LXd^SI;K%G^o%9+mpk1v`81T`wScIbGv2kf z_jxqCj9d}I&F13m(1aIM*18%?p->$ikf;e^PDZbHzLrb6qDiJ9m|A#l*{NpDrZn&7JI651onM(+kMWw=&4B9V~m*47ee&E zMacOKss1ue2202Sl&W86?;ddAH|lPh`^4+N&E%tBJu^kBlkJf!*d~xgx{3RkB3%gG zNx!{z7`DZre@yZDU7GddGsel=_=7w3b{OI+XwbsFh)x+ATRc}T0PXZf9cG)ARU@Tp z{B5^W0u3G5HpAtsnw#Ousw2d_l* z=ziVOWF`{0;Rde;N7E#E8-09A>XW=NKmJP*mbN5U^BuY&;KDsXe3|lc(tjL!0SdS$Z zn%f+3_$kJB8mv+8q@b{7VH|wRNzj_~<^)@rx^MFAN@n`E-u#2miV)IqCR|)N>P&7J z6*dJsL-^a&tCwXzh4rt^st_AnB7A~@L)LlcKTbx?a|?U17vYh&F5CChmH#tndk8;! zoH+tv-nkaBu#m{Jpm5(10shj|X$EvPy{Tn&+<%YVAKLWp=1swNIxpa}UMfkHtMipf zd#&3;<#J#X+S-kS^MH;KXE69Hd#tk=Yvj9vs`^SjN{hPuh$!UttefR&o&?48mMMI8 zL)hk0L06Q_F7oU69fW^S93DUZW_S1UGxkEfheyTl&bp+0WtU*lrCl!6G`%&It^9^8 z$&4`s_oAj&IKsgYtb>~-xbSDh;i;OI9xH76x}g3tlxDp(`LA+WQ`W1`#&&0sp&oX2 zuY~ld3lQJ};$r1Ix2UMOh=WOM9dSB5d*v?)teI6>wG18L4FWB+(UpfCI*7WV?_8Iyd^>mRb&W{SC z7&p(wc49cxSja8+1w8M6Y&&FJV$l|;UkkHAI?UJQyxYu+@Ye&}a*y4M-qFtfWlk%I zDZdBbn1wwXe;`nUlSD71@Y>mMw)9Sq7QY(47a0ed4NDTld~1gJ*PQNdMuSkE82w0Q zF?7x^II8x5MxCPm4--2egIcw)tOU*H1G-r1qp1j(StWu{_4TWX;2IVnvhm(Y2ou;H z%rqPk?udN8E&g_~yrf6t-YeVBR5Mdi8!%52LC`K(e`~@=%!094{|*+512U(3t=|F@ z?iGhe!cg}hi|0vB3v`fk4ge9@;gF!}5C;76#7+`{gY@bLX}ZFn?pKoS-b?j&{tZiVoM-0Nkma^1b%)et5#U8C)owB*F?6$a;;~Fz!#nEH%BiQeOBTG zc^NWPN67eHAC$zXQ}c)86C6zA#G{=Jo^;Wd>3McnPc#kZ^Dr_3Opn9w-J?{STG$rN zZ7j^E$#E^&lhZQ|nx219Vok*${PN&T;=#-tJr5zPoQxOD657Nt)mAus@uD_;>7Z&3 z?45+uN9>xyuqCn{lqvZv=s0cI^<5vGkfjneR2)ACn#~>&hno+&>4k-~TznE-Jq-c7 zCSp0azLAqxeUFRYphpg+&rn8NlLYJuI6g-lj_!4Oh1BlDLATndqvq(WW#e!%HsM}| zW7NOqI4`-{kX<~;Ak}&9y8?Sd-x=CAHk2f#wra^NgRJj>xhflNa56$lEoAn+1w^pu zpz~YlTAO^~Yoih4a?PAu(CchB<&iHIZD@3($QhJEYF9xrmP5!OHnp>6QZ*frz2I~Z z5AIl5^IPDaP`u%LVYR&xC8=V@HY^6XWt>|O75=qK`_Xp}vX0?inn>lbUZyoHJMbVM zbm4?xP)APN84>Z2OW`WUTib|Uc^dB19|qAI@c zq}*czkhP}AB5-W$|2zQC9TxD# z;Ycl(V2ejp#iZ{m2tE`v%EoOu;9ANz_~BLe2ys!HVYcECVvAZpvHN*B=<}L|mIk>+ zXsI=ONV!1#3S$E-ybQi}an5Mu#$Gx^Tz6%x?T6stlm-4gu{qM!ZB^dO^!Y;`DAWyh zeNhuha~XhB?#YO#z+#hY;{RN*%z?AzqP*%hJ}({FaQ2X1LKQo;VTcxQ7LqfLt_2K~ z>O4aamu}PPbNyj))sLN@_82trP^R0O)pe!vtN3WsD+NBme0lgr75zFq!)n*HCrG>$ zJcC$2t=QhamxZNoUt7eQ?^K3BhuI?!f(NhW(5v&)dvg5pfXW6j1 zTMRP<|Hf$y{2!V%L*pU!yc@Z1ynyG1Xf1!gOKHec5T*1?&Ew}NYqq#FH;Ci}eN!J#vgQLc-ou-(zZ z_F!(l$d$%Kmeov(zmC2n@_EfoW-YuPI>F;*iZqO2#5&vyQR#xq^y4DckVuc(1Vi`q z3Hl;`*Odb2ls)+qV>}F&$g^O8Aq8dhkbU0 zINM=@lE_-)47sG#7RoLF8vu}1WJEbN3;+CZYF~RkzduRfSBQ9zHOa0a2S;aH=oHNM z3Q@aW*`&P8hIUuteFk?;`Oi`dexrV>>ryv%?fS>aABKcCdM7wh@aDR4-295bsD`wz zh{_y_W!Dr(=LZPhhuaepjd9EbdhM9IAD^l`x7JjCuK0jGK?Gh2l&j{tS(*Ho#2VJn zV_7`8qWhi4oxSscIh!6xs&J#0_;qv(6U*2-Zok%_?>$18w)t1($+GU+S1GdJ@iV)-#G$d*T)A*8b2Ta>n)}9PyY%7dBUeEg#9sg3 z41gyJXqU}i1B$WBIFhQFpHwhrFT%kL*8Im0D1G-nqJ9UhvxUO;u$J|{SPPdvK~HQX z8E};}Ytzg%U;eoWX|x)B)cJtO>EwGo8yl?bDe(ZbO~^&W_=sBmpF*_c8%K;PYgC)m zv?*>Dq61w9lFOGJLE&0sES3d2&MpeQYGNb7;du5E9r-=)y05$ycUBvvI-YcY$9_yv zR%^UFshT{B3`l0F))WjBFHy$MoS<1T{55^oIbKILu{#wVF!Q~rW?yjgXBFzC=>ghq z2Fjg73i-r)zVLdkg!e|Dzpr4Y8W{9aXop3=lFG1~^q{c;HHHUY*N`)@)W_U9N^d!+ zN=z%}PgCOB+5Sdswia!WTGstEY{Y37g z>N||AZ9{^d+aSHQO^o0Yf}SKvYe;2G5yR$}Ox=x{6|-h&&COiWA)fSgZ=~(rV03en zD>Av-u_c_V;g*!k_Kk7sgN1tUjty20eFrOdhr0NMGpJ*2-1&v)KySub*iB|uJc*@K zT9*XED(GMP`E=;>XIBD6+B`^7h{?j_8aQYMe8&?H`Tb@DKeRCX zgL@~XEGMn|8u>af#e(Zy>2!N>aOFIo&DBBm^vogLk|6R-)nC_tQD-1ERouWA^-2b% zN-W&9Z9U1zl10<&cMi@lyaPLI{T2rC$=qohO-bMp_6iDjB_8-f$Y7W9%P4%dz5EjLlg9o&JFjf(c9i3dpf@A{XW;| z{#V}jeY?V^bNIHg?OvElbD+Zli*R4TpDV)TV?~+SvF4`=g)7HFVt(YxL>qI+d)p!p z3EuxXAdY$O{z%ZQ8PoRqqzqLzqI=;2uXygp%f3 zOcjrvFGdJo3}1H^P$~*0N8thB9SROoEb4n6a9SVZI*2B8+VR%E?s3WOxn$z9C`HI# zXp#14pri%k|DYEhaxLB;-ygqVM7K|`z3aQXD$DJFKeRW2h<|pU;|U=ekybgTo+rHW z#njf>ps!0&`uwjm^8Cxg`I0Xdb=8n^hT2x|+(%W&*;fPTl2*7_W zrT_l|b_j|0rc#Y=o2EZ2YvPh21nXm{Rlp(_6){>DbJg)^4}B1?mhx7(G03l45m-g&F@ z+=i)UJ9y`wOkthy8Yh5Bi01-@i*>`@_v!sImJY?}*?B0;L8EAK_BAh&bsK7@9O8nD zAulTnQMhn7%%AmC0ok}k9hAPXxZPy(it-SbU9sCWrGvv643TU$d4L)!ClJnR>eM2? zNWM-N&h_(8hGYYP$=c7&@5&p_!S>y`eV>>+qpFnItL4qNl~fkTRQBXn3C#W0u2u>i zdE1?kMTaio1~_p;^*LP^zEk994!?(q$hT0U z5aFb8(7s`0YkZ-xeFKQ+V3IndY)=ubuAND2hA3f!>K|4IZ@G^owYRrE`~E%&I{#b} zXo(cKft%-G&-1x$ofxfu4u2yx15u|V2_yu}p_CT+Jz#Dxuf!kS;s+xH8EQE7s z)7%d?T@u@^eE@Es!Hf)+SM6^(2tLsX{Asw`n5#&Gs1H=OB9b=?}d5m?p71 zRaI?ao@b(SR|InzO|(}_c8y8u_gROFqUWnEt1~7H&ZYACD3A$M>^cZTx<-~vuZCjH z{+QbCN#762oDGJH>w5zLiFMtF48bmY2%yQW|J$5WF zq`rfL!)Zre2_lEniCm3oo<%s<&p#QGjY}qLzx?JLc&6c;ZQnh+c8jT#s>@Ortdh-I z7eTS&I!8CRAlb{@-m#-0+ujb@=`N+l$^UQ!u)`@vl*l#hhx6HsF+aShb6N*mxtZI2 zPZ#pt%O&Lgbe#-`a~;J?Xp8Ir{R>#R)SrQzQ`D#fd2LgrZWiBs*$)Q~q&QmY;BcXcJQq}%+NO*1oXGavmiHKTe z1Oxu${uJ5-Rna?>DqTNvtfI{&9|39bRdgr@;HEMIPEErg65Et;gTxQ0Gy3_(F}2LB&4KH(vLKEg$r7Sjn``ASo}Z3J z&C^ttnz!=3f6o#_2RD|B=Q)YrX@n33&e?N!_zB0i8~E?*vYq$bH|YW?jf8E%g}P@DJX{f&KRfIy5*qoDFdO z>wJy8{{4H#k8kDm{&Y~tn*ul)Xzlzm%6t)2zFybbv!*^tR?ZHI&>sskd!Y0ez0soU zP^`^yTUEf_pN_?lP8oThc#!1%aLWHaKb7yd0tFy@{9hq?Z=da`RJCMu`HpQ6qlQEL z<+rVbC|(Mv-{f`KxW%tTRUd;F2;`WRn&PQSPdODid$nqTJg-Ba)Ywc5?o(b_B3PeujJgIyfYNBY+*w z26*|}xg~4QD+rXm07Hz&o*>WYZa~m*g>P|6r(h#g%`jikN zU3o&|;xaA8MVE8{tN?=I0D!q5gY-0dg0nP zf-B0`l64!-MU7hNV$|or+Eu`zgQvY(jsSMZ&v4IQ2Zsc31hB)o4wAo@m4)oo9oV!1 zV&1HYSi7n;&Kb+`(TMqZZ}+pE0H%nP1LM)sMEMEjCYdBa~sLtKj--U=Lqt%7inpbjavW( z3P61K^+v2;Qw7=CN6+Tef}hUz9oD9yZ*_kL~;T+@xjmd@hL|DJ2;%R zkjQKQIobOsj}O3P{A`l() @@ -46,15 +22,6 @@ api.use('/api/v1/*', async (c, next) => { api.use('/api/v1/*', async (c, next) => { if(!AppDataSource.isInitialized) { await AppDataSource.initialize(); - - // 初始化监控任务调度器 - try { - const monitorService = new MonitorService(AppDataSource); - const scheduler = new MonitorSchedulerService(AppDataSource, monitorService); - await scheduler.init(); - } catch (error) { - console.error('监控任务调度器初始化失败:', error); - } } await next(); }) @@ -82,24 +49,10 @@ if(!import.meta.env.PROD){ }) } -// Register routes -const routes = api - .route('/api/v1/init', initConfigRouter) - .route('/api/v1/init', initStatusRouter) - .route('/api/v1/base', base) + const userRoutes = api.route('/api/v1/users', usersRouter) -const zichanRoutes = api - .route('/api/v1/zichan', zichanRoute.getRoute) - .route('/api/v1/zichan', zichanRoute.getByIdRoute) - .route('/api/v1/zichan', zichanRoute.postRoute) - .route('/api/v1/zichan', zichanRoute.putByIdRoute) - .route('/api/v1/zichan', zichanRoute.deleteByIdRoute) - -const zichanCategoryRoutes = api.route('/api/v1/zichan-category', zichanCategoryRouter) - -const zichanAreaRoutes = api.route('/api/v1/zichan-area', zichanAreaRouter) const authRoutes = api .route('/api/v1/auth', authRoute.loginRoute.passwordRoute) diff --git a/src/server/api/init.ts b/src/server/api/init.ts deleted file mode 100644 index a71bfbd..0000000 --- a/src/server/api/init.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'; -import { ErrorSchema } from '../utils/errorHandler'; -import { AppDataSource } from '../data-source'; -import { UserEntity as User } from '../modules/users/user.entity'; -import { Role } from '../modules/users/role.entity'; -import * as bcrypt from 'bcrypt'; -import { generateJwtSecret } from '../utils/env-init'; -import { writeFileSync } from 'fs'; - -const initRouter = new OpenAPIHono(); - -// Zod 验证模式 -const DatabaseConfigSchema = z.object({ - host: z.string(), - port: z.number(), - username: z.string(), - password: z.string(), - database: z.string() -}); - -const AdminUserSchema = z.object({ - username: z.string().min(3), - password: z.string().min(8) -}); - -const InitConfigSchema = z.object({ - dbConfig: DatabaseConfigSchema, - adminUser: AdminUserSchema -}); - -// 检查初始化状态路由 -const statusRoute = createRoute({ - method: 'get', - path: '/status', - responses: { - 200: { - content: { - 'application/json': { - schema: z.object({ - initialized: z.boolean() - }) - } - }, - description: '返回系统初始化状态' - }, - 500: { - description: '初始化状态检查失败', - content: { - 'application/json': { - schema: ErrorSchema - } - } - } - } -}); - -const initStatusRouter = initRouter.openapi(statusRoute, async (c) => { - try { - const isInitialized = await checkInitialization(); - return c.json({ initialized: isInitialized }, 200); - } catch (error) { - return c.json({ - code: 500, - message: '初始化状态检查失败' - }, 500); - } -}); - -// 提交配置路由 -const configRoute = createRoute({ - method: 'post', - path: '/config', - request: { - body: { - content: { - 'application/json': { - schema: InitConfigSchema - } - } - } - }, - responses: { - 200: { - description: '系统初始化成功', - content: { - 'application/json': { - schema: z.object({ - success: z.boolean(), - jwtSecret: z.string().optional() - }) - } - } - }, - 400: { - description: '初始化失败', - content: { - 'application/json': { - schema: ErrorSchema - } - } - }, - 500: { - description: '服务器内部错误', - content: { - 'application/json': { - schema: ErrorSchema - } - } - } - } -}); - -const initConfigRouter = initRouter.openapi(configRoute, async (c) => { - const { dbConfig, adminUser } = c.req.valid('json'); - - if (await checkInitialization()) { - return c.json({ - code: 400, - message: '系统已初始化' - }, 400); - } - - try { - await validateDatabase(dbConfig); - await generateEnvFile(dbConfig); - const jwtSecret = generateJwtSecret(); - await createAdminUser(adminUser); - - return c.json({ - success: true, - jwtSecret - }, 200); - } catch (error) { - return c.json({ - code: 400, - message: error instanceof Error ? error.message : '未知错误' - }, 400); - } -}); - -export { initStatusRouter, initConfigRouter }; - - -async function checkInitialization(): Promise { - // 检查数据库是否已初始化 - return AppDataSource.isInitialized; -} - -async function validateDatabase(config: any) { - // 使用TypeORM验证数据库连接 - const testDataSource = AppDataSource.setOptions(config); - await testDataSource.initialize(); - await testDataSource.destroy(); -} - -async function generateEnvFile(config: any) { - const envContent = `DB_HOST=${config.host} -DB_PORT=${config.port} -DB_USERNAME=${config.username} -DB_PASSWORD=${config.password} -DB_DATABASE=${config.database} -`; - writeFileSync('.env', envContent); -} - -async function createAdminUser(userData: any) { - const userRepo = AppDataSource.getRepository(User); - const roleRepo = AppDataSource.getRepository(Role); - - // 检查是否已存在管理员 - const existingAdmin = await userRepo.findOne({ where: { username: userData.username } }); - if (existingAdmin) { - throw new Error('管理员用户已存在'); - } - - // 创建管理员角色 - let adminRole = await roleRepo.findOne({ where: { name: 'admin' } }); - if (!adminRole) { - adminRole = roleRepo.create({ name: 'admin', permissions: ['*'] }); - await roleRepo.save(adminRole); - } - - // 创建管理员用户 - const hashedPassword = await bcrypt.hash(userData.password, 10); - const adminUser = userRepo.create({ - username: userData.username, - password: hashedPassword, - roles: [adminRole] - }); - - await userRepo.save(adminUser); -} \ No newline at end of file diff --git a/src/server/api/migration.ts b/src/server/api/migration.ts deleted file mode 100644 index dcdccdd..0000000 --- a/src/server/api/migration.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { createRoute, OpenAPIHono } from '@hono/zod-openapi' -import { z } from 'zod' -import { AppDataSource } from '../data-source' -import { HTTPException } from 'hono/http-exception'; -import { authMiddleware } from '../middleware/auth.middleware'; -import { AuthContext } from '../types/context'; - -interface Migration { - name: string; - timestamp: number; -} - -const app = new OpenAPIHono() - -const MigrationResponseSchema = z.object({ - success: z.boolean(), - message: z.string(), - migrations: z.array(z.string()).optional() -}) - -const runMigrationRoute = createRoute({ - method: 'post', - path: '/migrations/run', - middleware: authMiddleware, - responses: { - 200: { - content: { - 'application/json': { - schema: MigrationResponseSchema - } - }, - description: 'Migrations executed successfully' - }, - 500: { - description: 'Migration failed' - } - } -}) - -const revertMigrationRoute = createRoute({ - method: 'post', - path: '/migrations/revert', - middleware: authMiddleware, - responses: { - 200: { - content: { - 'application/json': { - schema: MigrationResponseSchema - } - }, - description: 'Migration reverted successfully' - }, - 500: { - description: 'Revert failed' - } - } -}) - -app.openapi(runMigrationRoute, async (c) => { - try { - const migrations = await AppDataSource.runMigrations() - return c.json({ - success: true, - message: 'Migrations executed successfully', - migrations: migrations.map((m: Migration) => m.name) - }) - } catch (error) { - throw error - } -}) - -app.openapi(revertMigrationRoute, async (c) => { - try { - await AppDataSource.undoLastMigration() - const migrations = await AppDataSource.showMigrations() - return c.json({ - success: true, - message: 'Migration reverted successfully', - migrations: migrations - }) - } catch (error) { - throw error - } -}) - - - -export default app -export type AppType = typeof app \ No newline at end of file diff --git a/src/server/renderer.tsx b/src/server/renderer.tsx index ca67c78..c4a78ab 100644 --- a/src/server/renderer.tsx +++ b/src/server/renderer.tsx @@ -6,7 +6,6 @@ import process from 'node:process' // 全局配置常量 const GLOBAL_CONFIG: GlobalConfig = { OSS_BASE_URL: process.env.OSS_BASE_URL || 'https://oss.d8d.fun', - // API_BASE_URL: '/api', APP_NAME: process.env.APP_NAME || '多八多Aider', } diff --git a/src/share/monitorTypes.ts b/src/share/monitorTypes.ts deleted file mode 100644 index fadb894..0000000 --- a/src/share/monitorTypes.ts +++ /dev/null @@ -1,1754 +0,0 @@ -import { AuditStatus, DeleteStatus, EnableStatus } from './types' - -// 启用/禁用状态枚举 -export enum DisabledStatus { - DISABLED = 1, // 禁用 - ENABLED = 0 // 启用 -} - -// 备件标识枚举 是否备件 (0否 1是) -export enum IsSpare { - NO = 0, - YES = 1 -} - -// 设备通信协议类型枚举 -export enum DeviceProtocolType { - SNMP = 'SNMP', // 简单网络管理协议(网络设备管理) - HTTP = 'HTTP', // 超文本传输协议(Web服务) - MODBUS = 'MODBUS', // Modbus协议(工业自动化标准通信协议) - MQTT = 'MQTT', // 消息队列遥测传输(物联网消息协议) - SOCKET = 'SOCKET', // Socket通信(基础网络通信) - OPC = 'OPC', // OPC统一架构(工业设备互操作性标准) - RS485 = 'RS485', // RS485串行通信(工业现场总线) - TCP = 'TCP' // TCP网络协议(可靠的网络传输协议) -} - -// 设备通信协议类型中文映射 -export const DeviceProtocolTypeNameMap: Record = { - [DeviceProtocolType.SNMP]: 'SNMP', - [DeviceProtocolType.HTTP]: 'HTTP', - [DeviceProtocolType.MODBUS]: 'MODBUS', - [DeviceProtocolType.MQTT]: 'MQTT', - [DeviceProtocolType.SOCKET]: 'SOCKET', - [DeviceProtocolType.OPC]: 'OPC', - [DeviceProtocolType.RS485]: 'RS485', - [DeviceProtocolType.TCP]: 'TCP' -}; - -// 任务类型枚举 -export enum TaskType { - TEMPERATURE = 'temperature', - HUMIDITY = 'humidity', - SMOKE = 'smoke', - WATER = 'water', - NETWORK = 'network' -} - -// 统一的监控指标类型枚举 -export enum MetricType { - TEMPERATURE = 'temperature', - HUMIDITY = 'humidity', - VOLTAGE = 'voltage', - CPU_USAGE = 'cpu_usage', - MEMORY_USAGE = 'memory_usage', - DISK_USAGE = 'disk_usage', - SMOKE = 'smoke', - WATER = 'water', - NETWORK_TRAFFIC = 'network_traffic', - PING_TIME = 'ping_time', - PACKET_LOSS = 'packet_loss', - SNMP_RESPONSE_TIME = 'snmp_response_time', - SNMP_ERRORS = 'snmp_errors', - HTTP_RESPONSE_TIME = 'http_response_time', - HTTP_STATUS = 'http_status', - TCP_CONNECTION_TIME = 'tcp_connection_time', - CONNECTION_STATUS = 'connection_status' -} - -// 监控类型中文映射 -export const MetricTypeNameMap: Record = { - [MetricType.TEMPERATURE]: '温度', - [MetricType.HUMIDITY]: '湿度', - [MetricType.VOLTAGE]: '电压', - [MetricType.CPU_USAGE]: 'CPU使用率', - [MetricType.MEMORY_USAGE]: '内存使用率', - [MetricType.DISK_USAGE]: '磁盘使用率', - [MetricType.NETWORK_TRAFFIC]: '网络流量', - [MetricType.PING_TIME]: 'Ping时间', - [MetricType.PACKET_LOSS]: '丢包率', - [MetricType.SNMP_RESPONSE_TIME]: 'SNMP响应时间', - [MetricType.SNMP_ERRORS]: 'SNMP错误数', - [MetricType.HTTP_RESPONSE_TIME]: 'HTTP响应时间', - [MetricType.HTTP_STATUS]: 'HTTP状态码', - [MetricType.TCP_CONNECTION_TIME]: 'TCP连接时间', - [MetricType.CONNECTION_STATUS]: '连接状态', - [MetricType.WATER]: '水浸', - [MetricType.SMOKE]: '烟感', -}; - -// 处理类型枚举 -export enum HandleType { - CONFIRM = 'confirm', - RESOLVE = 'resolve', - IGNORE = 'ignore' -} - -// 处理类型中文映射 -export const HandleTypeNameMap: Record = { - [HandleType.CONFIRM]: '确认', - [HandleType.RESOLVE]: '解决', - [HandleType.IGNORE]: '忽略' -}; - -// 问题类型枚举 -export enum ProblemType { - DEVICE = 'device', - NETWORK = 'network', - POWER = 'power', - CONSTRUCTION = 'construction', - OTHER = 'other' -} - -// 问题类型中文映射 -export const ProblemTypeNameMap: Record = { - [ProblemType.DEVICE]: '设备故障', - [ProblemType.NETWORK]: '网络故障', - [ProblemType.POWER]: '电源故障', - [ProblemType.CONSTRUCTION]: '施工影响', - [ProblemType.OTHER]: '其他原因' -}; - -// 通知类型枚举 -export enum NotifyType { - SMS = 'sms', - EMAIL = 'email', - WECHAT = 'wechat' -} - -// 通知类型中文映射 -export const NotifyTypeNameMap: Record = { - [NotifyType.SMS]: '短信', - [NotifyType.EMAIL]: '邮件', - [NotifyType.WECHAT]: '微信' -}; - -// 告警等级枚举 -export enum AlertLevel { - MINOR = 0, // 次要 - NORMAL = 1, // 一般 - IMPORTANT = 2, // 重要 - URGENT = 3 // 紧急 -} - -// 告警等级中文映射 -export const AlertLevelNameMap: Record = { - [AlertLevel.MINOR]: '次要', - [AlertLevel.NORMAL]: '一般', - [AlertLevel.IMPORTANT]: '重要', - [AlertLevel.URGENT]: '紧急' -}; - -// 告警等级颜色映射 -export const AlertLevelColorMap: Record = { - [AlertLevel.MINOR]: 'blue', - [AlertLevel.NORMAL]: 'orange', - [AlertLevel.IMPORTANT]: 'red', - [AlertLevel.URGENT]: 'purple' -}; - -// // 设备查询状态枚举 -export enum DeviceQueryStatus { - ALL = 'all', - NORMAL = 'normal', - ERROR = 'error', - OFFLINE = 'offline' -} - -// 设备状态枚举(资产管理) -export enum DeviceStatus { - NORMAL = 0, // 正常 - MAINTAIN = 1, // 维护中 - FAULT = 2, // 故障 - OFFLINE = 3 // 下线 -} - -// 设备分类枚举 -export enum DeviceCategory { - SERVER = 1, // 服务器 - NETWORK = 2, // 网络设备 - STORAGE = 3, // 存储设备 - SECURITY = 4, // 安全设备 - OTHER = 5 // 其他设备 -} - -// 区域枚举 -export enum AreaType { - AREA_A = 1, // A区 - AREA_B = 2, // B区 - AREA_C = 3, // C区 - AREA_OTHER = 4 // 其他区域 -} - -// 资产状态枚举 -export enum AssetStatus { - IN_USE = 0, // 使用中 - IDLE = 1, // 闲置 - REPAIR = 2, // 维修中 - SCRAPPED = 3 // 已报废 -} - -// 网络状态枚举 -export enum NetworkStatus { - CONNECTED = 0, // 已连接 - DISCONNECTED = 1, // 已断开 - UNSTABLE = 2 // 不稳定 -} - -// 丢包状态枚举 -export enum PacketLossStatus { - NORMAL = 0, // 正常 - HIGH = 1 // 丢包 -} - -// 设备状态中文映射(资产管理) -export const DeviceStatusNameMap: Record = { - [DeviceStatus.NORMAL]: '正常', - [DeviceStatus.MAINTAIN]: '维护中', - [DeviceStatus.FAULT]: '故障', - [DeviceStatus.OFFLINE]: '下线' -}; - -// 设备状态颜色映射(资产管理) -export const DeviceStatusColorMap: Record = { - [DeviceStatus.NORMAL]: 'green', - [DeviceStatus.MAINTAIN]: 'blue', - [DeviceStatus.FAULT]: 'red', - [DeviceStatus.OFFLINE]: 'gray' -}; - -// 设备分类中文映射 -export const DeviceCategoryNameMap: Record = { - [DeviceCategory.SERVER]: '服务器', - [DeviceCategory.NETWORK]: '网络设备', - [DeviceCategory.STORAGE]: '存储设备', - [DeviceCategory.SECURITY]: '安全设备', - [DeviceCategory.OTHER]: '其他设备' -}; - -// 告警状态枚举 -export enum AlertStatus { - PENDING = 'pending', - HANDLING = 'handling', - RESOLVED = 'resolved', - IGNORED = 'ignored' -} - -// 告警状态中文映射 -export const AlertStatusNameMap: Record = { - [AlertStatus.PENDING]: '待处理', - [AlertStatus.HANDLING]: '处理中', - [AlertStatus.RESOLVED]: '已解决', - [AlertStatus.IGNORED]: '已忽略' -}; - -// 告警状态颜色映射 -export const AlertStatusColorMap: Record = { - [AlertStatus.PENDING]: 'red', - [AlertStatus.HANDLING]: 'blue', - [AlertStatus.RESOLVED]: 'green', - [AlertStatus.IGNORED]: 'gray' -}; - -// 设备在线状态枚举 -export enum OnlineStatus { - ONLINE = 'online', - OFFLINE = 'offline' -} - -// 设备在线状态中文映射 -export const OnlineStatusNameMap: Record = { - [OnlineStatus.ONLINE]: '在线', - [OnlineStatus.OFFLINE]: '离线' -}; - -// 设备在线状态颜色映射 -export const OnlineStatusColorMap: Record = { - [OnlineStatus.ONLINE]: 'green', - [OnlineStatus.OFFLINE]: 'red' -}; - -// 工单状态枚举 -export enum WorkOrderStatus { - PENDING = '待受理', // 待受理 - PROCESSING = '处理中', // 处理中 - REASSIGNED = '已改派', // 已改派 - COMPLETED = '已完成', // 已完成 - CLOSED = '已关闭' // 已关闭 -} - -// 工单状态中文映射 -export const WorkOrderStatusNameMap: Record = { - [WorkOrderStatus.PENDING]: '待受理', - [WorkOrderStatus.PROCESSING]: '处理中', - [WorkOrderStatus.REASSIGNED]: '已改派', - [WorkOrderStatus.COMPLETED]: '已完成', - [WorkOrderStatus.CLOSED]: '已关闭' -}; - -// 工单状态颜色映射 -export const WorkOrderStatusColorMap: Record = { - [WorkOrderStatus.PENDING]: 'orange', - [WorkOrderStatus.PROCESSING]: 'blue', - [WorkOrderStatus.REASSIGNED]: 'purple', - [WorkOrderStatus.COMPLETED]: 'green', - [WorkOrderStatus.CLOSED]: 'gray' -}; - -// 工单优先级枚举 -export enum WorkOrderPriority { - NORMAL = 0, // 普通 - IMPORTANT = 1, // 重要 - URGENT = 2 // 紧急 -} - -// 工单优先级中文映射 -export const WorkOrderPriorityNameMap: Record = { - [WorkOrderPriority.NORMAL]: '普通', - [WorkOrderPriority.IMPORTANT]: '重要', - [WorkOrderPriority.URGENT]: '紧急' -}; - -// 工单优先级颜色映射 -export const WorkOrderPriorityColorMap: Record = { - [WorkOrderPriority.NORMAL]: 'green', - [WorkOrderPriority.IMPORTANT]: 'orange', - [WorkOrderPriority.URGENT]: 'red' -}; - -// 工单操作类型枚举 -export enum WorkOrderAction { - CREATE = 'create', - ACCEPT = 'accept', - HANDLE = 'handle', - AUDIT = 'audit', - CLOSE = 'close' -} - -// 工单操作类型中文映射 -export const WorkOrderActionNameMap: Record = { - [WorkOrderAction.CREATE]: '创建', - [WorkOrderAction.ACCEPT]: '接受', - [WorkOrderAction.HANDLE]: '处理', - [WorkOrderAction.AUDIT]: '审核', - [WorkOrderAction.CLOSE]: '关闭' -}; - -// 服务器类型枚举 -export enum ServerType { - STANDARD = 'standard', - NETWORK = 'network', - STORAGE = 'storage', - SPECIAL = 'special' -} - -// 服务器类型中文映射 -export const ServerTypeNameMap: Record = { - [ServerType.STANDARD]: '标准服务器', - [ServerType.NETWORK]: '网络设备', - [ServerType.STORAGE]: '存储设备', - [ServerType.SPECIAL]: '特殊设备' -}; - - - -// 图表类型映射 -export const AlertTypeMap = { - temperature: { text: '温度异常', color: 'orange' }, - humidity: { text: '湿度异常', color: 'blue' }, - offline: { text: '设备离线', color: 'red' } -} as const; - -// 工单状态映射 -export const StatusMap = { - unread: { text: '未读', color: 'red' }, - read: { text: '已读', color: 'blue' }, - processed: { text: '已处理', color: 'green' } -} as const; - - -// 定义JSON数据结构接口 - -// 附件类型定义 -export interface Attachment { - /** 附件ID */ - id: string; - - /** 附件名称 */ - name: string; - - /** 附件访问地址 */ - url: string; - - /** 附件类型(如image/jpeg, application/pdf等) */ - type: string; - - /** 附件大小(字节) */ - size: number; - - /** 上传时间 */ - upload_time: string; -} - -// 通知项配置类型定义 -interface NotifyItem { - /** 通知项ID */ - id: string; - - /** 通知项类型 */ - type: string; - - /** 是否启用 */ - enabled: boolean; - - /** 通知配置参数 */ - config: Record; -} - -// 监控配置类型定义 -export interface MonitorConfig { - /** 监控间隔(秒) */ - interval: number; - - /** 监控指标列表 */ - metrics: Array<{ - /** 指标名称 */ - name: string; - - /** 指标类型 */ - type: string; - - /** 是否启用 */ - enabled: boolean; - - /** 阈值设置 */ - threshold?: { - /** 最小阈值 */ - min?: number; - - /** 最大阈值 */ - max?: number; - }; - }>; - - /** 通知设置 */ - notification: { - /** 是否启用通知 */ - enabled: boolean; - - /** 通知渠道列表 */ - channels: string[]; - }; -} - -// 告警规则类型定义 -export interface AlertRuleConfig { - /** 规则列表 */ - rules: Array<{ - /** 监控指标 */ - metric: string; - - /** 触发条件(如>、<、=等) */ - condition: string; - - /** 阈值 */ - threshold: number; - - /** 持续时间(秒) */ - duration: number; - - /** 告警等级 */ - level: AlertLevel; - }>; - - /** 动作列表 */ - actions: Array<{ - /** 动作类型 */ - type: string; - - /** 动作目标 */ - target: string; - - /** 通知模板 */ - template?: string; - }>; -} - -// 数据格式配置类型定义 -export interface DataSchema { - /** 版本号 */ - version: string; - - /** 属性定义 */ - properties: Record; - - /** 必填字段列表 */ - required: string[]; -} - -// 图标配置类型定义 -export interface IconConfig { - /** 图标尺寸 */ - size: { - /** 宽度 */ - width: number; - - /** 高度 */ - height: number; - }; - - /** 支持的文件格式 */ - format: string[]; - - /** 最大文件大小(KB) */ - maxSize: number; -} - -// 告警等级配置类型定义 -export interface AlertLevelConfig { - /** 等级定义 */ - levels: Record; - - /** 默认等级 */ - default: string; -} - -// 监控项配置类型定义 -export interface MonitorItemConfig { - /** 监控项列表 */ - items: Array<{ - /** 监控项名称 */ - name: string; - - /** 描述 */ - description: string; - - /** 监控项类型 */ - type: string; - - /** 单位 */ - unit: string; - - /** 默认是否启用 */ - defaultEnabled: boolean; - - /** 默认阈值 */ - defaultThresholds?: { - /** 最小阈值 */ - min?: number; - - /** 最大阈值 */ - max?: number; - }; - }>; -} - -// 常用语列表类型定义 -export interface CommonPhrase { - /** 常用语ID */ - id: string; - - /** 分类 */ - category: string; - - /** 内容 */ - content: string; - - /** 标签列表 */ - tags: string[]; -} - -// SLA配置类型定义 -export interface SLAConfig { - /** 响应时间(分钟) */ - responseTime: number; - - /** 解决时间(分钟) */ - resolveTime: number; - - /** 工作时间 */ - workingHours: { - /** 开始时间(HH:mm) */ - start: string; - - /** 结束时间(HH:mm) */ - end: string; - - /** 工作日(0-6,0代表周日) */ - workDays: number[]; - }; - - /** 升级规则 */ - escalationRules: Array<{ - /** 超时时间(分钟) */ - timeout: number; - - /** 动作 */ - action: string; - - /** 目标(如用户ID、角色等) */ - target: string[]; - }>; -} - -// 流程配置类型定义 -export interface WorkflowConfig { - /** 流程步骤 */ - steps: Array<{ - /** 步骤名称 */ - name: string; - - /** 允许操作的角色 */ - roles: string[]; - - /** 可执行的动作 */ - actions: string[]; - - /** 下一步可能的步骤 */ - nextSteps: string[]; - - /** 自动超时时间(分钟) */ - autoTimeout?: number; - }>; - - /** 初始步骤 */ - initialStep: string; -} - -// 告警处理记录表 -export interface AlertHandleLog { - /** 主键ID */ - id: number; - - /** 关联的告警ID */ - alert_id: number; - - /** 处理人ID */ - handler_id: number; - - /** 处理类型 */ - handle_type: HandleType; - - /** 问题类型 */ - problem_type: ProblemType; - - /** 处理结果 */ - handle_result?: string; - - /** 附件列表 */ - attachments?: Attachment[]; - - /** 是否禁用通知 (0否 1是) */ - notify_disabled?: number; - - /** 禁用的通知项配置 */ - notify_items?: NotifyItem[]; - - /** 处理时间 */ - handle_time: Date; - - /** 是否删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 告警通知配置表 -export interface AlertNotifyConfig { - /** 主键ID */ - id: number; - - /** 关联的设备ID */ - device_id: number; - - /** 告警等级 */ - alert_level: AlertLevel; - - /** 通知类型 */ - notify_type: NotifyType; - - /** 通知模板 */ - notify_template?: string; - - /** 通知用户ID列表 */ - notify_users?: number[]; - - /** 短信通知手机号(当notify_type为SMS时使用) */ - phone_number?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: EnableStatus; - - /** 是否删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 设备告警规则表 -export interface DeviceAlertRule { - /** 主键ID */ - id: number; - - /** 关联的设备ID */ - device_id: number; - - /** 监控指标类型 */ - metric_type: string; - - /** 最小阈值 */ - min_value?: number; - - /** 最大阈值 */ - max_value?: number; - - /** 持续时间(秒) */ - duration_seconds?: number; - - /** 告警等级 */ - alert_level: AlertLevel; - - /** 告警消息模板 */ - alert_message?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: EnableStatus; - - /** 是否删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 设备告警记录表 -export interface DeviceAlert { - /** 主键ID */ - id: number; - - /** 关联的设备ID */ - device_id: number; - - /** 设备名称 */ - device_name: string; - - /** 监控指标类型 */ - metric_type: string; - - /** 触发值 */ - metric_value: number; - - /** 告警等级 */ - alert_level: AlertLevel; - - /** 告警消息 */ - alert_message: string; - - /** 状态 */ - status: AlertStatus; - - /** 是否删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 设备分类图标表 -export interface DeviceCategoryIcon { - /** 主键ID */ - id: number; - - /** 关联的设备分类ID */ - category_id: number; - - /** 分类图标 */ - icon?: string; - - /** 图标名称 */ - icon_name?: string; - - /** 图标类型(svg/url等) */ - icon_type?: string; - - /** 排序 */ - sort?: number; - - /** 是否为默认图标 (0否 1是) */ - is_default?: number; - - /** 是否禁用 (0否 1是) */ - is_disabled?: number; - - /** 是否被删除 (0否 1是) */ - is_deleted?: number; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - - -// 设备实例表 -export interface DeviceInstance { - /** 关联资产ID */ - id: number; - - /** 设备类型ID */ - type_id: number; - - /** 通信协议(SNMP/HTTP/RS485/TCP等) */ - protocol: DeviceProtocolType; - - /** 通信地址 */ - address: string; - - /** 状态地址 */ - status_address?: string; - - /** 连接配置 */ - connection_config?: string; - - /** 采集间隔(秒) */ - collect_interval?: number; - - /** 最后采集时间 */ - last_collect_time?: Date; - - /** 备注 */ - remark?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: number; - - /** 是否删除 (0否 1是) */ - is_deleted?: number; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; - - /** 资产名称(来自zichan_info表) */ - asset_name?: string; - - /** 设备分类(来自zichan_info表) */ - device_category?: DeviceCategory; - - /** 归属区域(来自zichan_info表) */ - area?: AreaType; - - /** 供应商(来自zichan_info表) */ - supplier?: string; - - /** 设备状态(来自zichan_info表) */ - device_status?: DeviceStatus; -} - -// 设备监控数据表 -export interface DeviceMonitorData { - /** 主键ID */ - id: number; - - /** 关联的设备ID */ - device_id: number; - - /** 监控指标类型(temperature/humidity/smoke/water等) */ - metric_type: string; - - /** 监控值 */ - metric_value: number; - - /** 单位 */ - unit?: string; - - /** 状态 */ - status?: DeviceStatus; - - /** 采集时间 */ - collect_time: Date; - - /** 是否删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 设备类型表 -export interface DeviceType { - /** 主键ID */ - id: number; - - /** 类型名称 */ - name: string; - - /** 类型编码 */ - code: string; - - /** 设备类型图片URL */ - image_url?: string; - - /** 类型描述 */ - description?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: number; - - /** 是否删除 (0否 1是) */ - is_deleted?: number; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 操作日志表 -export interface OperationLog { - /** 主键ID */ - id: number; - - /** 操作人ID */ - operator_id: number; - - /** 操作类型 */ - operation_type: string; - - /** 操作内容 */ - operation_content?: string; - - /** 操作结果 */ - operation_result?: string; - - /** 操作IP */ - ip_address?: string; - - /** 是否删除 (0否 1是) */ - is_deleted?: number; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 机柜信息表 -export interface RackInfo { - /** 主键ID */ - id: number; - - /** 机柜名称 */ - rack_name?: string; - - /** 机柜编号 */ - rack_code?: string; - - /** 机柜可容纳设备数量,默认42U */ - capacity?: number; - - /** 机柜X轴位置坐标 */ - position_x?: number; - - /** 机柜Y轴位置坐标 */ - position_y?: number; - - /** 机柜Z轴位置坐标 */ - position_z?: number; - - /** 机柜所在区域 */ - area?: string; - - /** 机柜所在机房 */ - room?: string; - - /** 备注信息 */ - remark?: string; - - /** 是否禁用 (0否 1是) */ - is_disabled?: EnableStatus; - - /** 是否被删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 机柜服务器表 -export interface RackServer { - /** 主键ID */ - id: number; - - /** 关联的机柜ID */ - rack_id: number; - - /** 关联的资产ID */ - asset_id: number; - - /** 设备安装的起始U位 */ - start_position: number; - - /** 设备占用U数 */ - size?: number; - - /** 服务器类型 */ - server_type?: number; - - /** 备注信息 */ - remark?: string; - - /** 是否禁用 (0否 1是) */ - is_disabled?: EnableStatus; - - /** 是否被删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 机柜服务器类型表 -export interface RackServerType { - /** 主键ID */ - id: number; - - /** 类型名称 */ - name: string; - - /** 类型编码 */ - code: string; - - /** 类型图片 */ - image_url?: string; - - /** 类型描述 */ - description?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: EnableStatus; - - /** 是否被删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// // 工单表 -// export interface WorkOrder { -// /** 主键ID */ -// id: number; - -// /** 工单标题 */ -// title: string; - -// /** 关联设备ID */ -// device_id?: number; - -// /** 关联告警ID */ -// alert_id?: number; - -// /** 工单模板ID */ -// template_id?: number; - -// /** 工单内容 */ -// content?: string; - -// /** 工单状态 */ -// status: WorkOrderStatus; - -// /** 优先级 */ -// priority: WorkOrderPriority; - -// /** 创建人ID */ -// creator_id: number; - -// /** 处理人ID */ -// handler_id?: number; - -// /** 审核人ID */ -// auditor_id?: number; - -// /** 截止时间 */ -// deadline?: Date; - -// /** 处理结果 */ -// handle_result?: string; - -// /** 审核结果 */ -// audit_result?: string; - -// /** 是否删除 (0否 1是) */ -// is_deleted?: number; - -// /** 创建时间 */ -// created_at: Date; - -// /** 更新时间 */ -// updated_at: Date; -// } - -// // 工单处理记录表 -// export interface WorkOrderLog { -// /** 主键ID */ -// id: number; - -// /** 工单ID */ -// work_order_id: number; - -// /** 操作人ID */ -// operator_id: number; - -// /** 操作类型 */ -// action: WorkOrderAction; - -// /** 处理内容 */ -// content?: string; - -// /** 是否删除 (0否 1是) */ -// is_deleted?: number; - -// /** 创建时间 */ -// created_at: Date; - -// /** 更新时间 */ -// updated_at: Date; -// } - -// // 工单模板表 -// export interface WorkOrderTemplate { -// /** 主键ID */ -// id: number; - -// /** 模板名称 */ -// name: string; - -// /** 模板内容 */ -// content?: string; - -// /** 是否需要审核 (0否 1是) */ -// need_audit: number; - -// /** 默认处理人 */ -// default_handler_id?: number; - -// /** 默认完成时限(小时) */ -// default_deadline_hours?: number; - -// /** 常用语列表 */ -// common_phrases?: CommonPhrase[]; - -// /** SLA配置 */ -// sla_config?: SLAConfig; - -// /** 流程配置 */ -// workflow_config?: WorkflowConfig; - -// /** 是否启用 (0否 1是) */ -// is_enabled?: number; - -// /** 是否删除 (0否 1是) */ -// is_deleted?: number; - -// /** 创建时间 */ -// created_at: Date; - -// /** 更新时间 */ -// updated_at: Date; -// } - -// 资产信息表 -export interface ZichanInfo { - /** 主键ID */ - id: number; - - /** 资产名称 */ - asset_name?: string; - - /** 设备分类 */ - device_category?: DeviceCategory; - - /** 归属区域 */ - area?: AreaType; - - /** 供应商 */ - supplier?: string; - - /** 使用地址 */ - use_address?: string; - - /** 运行情况 */ - operation_status?: string; - - /** 是否审核 (0否 1是) */ - is_audited?: number; - - /** 审核状态 */ - audit_status?: AuditStatus; - - /** 资产状态 */ - asset_status?: AssetStatus; - - /** 入库数量 */ - stock_quantity?: number; - - /** 质保时间 */ - warranty_time?: Date; - - /** 品牌 */ - brand?: string; - - /** IP地址 */ - ip_address?: string; - - /** 设备状态 */ - device_status?: DeviceStatus; - - /** 网络状态 */ - network_status?: NetworkStatus; - - /** 丢包率 */ - packet_loss?: number; - - /** 图片 */ - images?: string; - - /** 是否备件 (0否 1是) */ - is_spare?: number; - - /** 是否被禁用 (0否 1是) */ - is_disabled?: number; - - /** 是否被删除 (0否 1是) */ - is_deleted?: number; - - /** 资产位置经度 */ - longitude?: number; - - /** 资产位置纬度 */ - latitude?: number; - - /** CPU信息 */ - cpu?: string; - - /** 内存信息 */ - memory?: string; - - /** 硬盘信息 */ - disk?: string; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 资产分类表 -export interface ZichanCategory { - /** 主键ID */ - id: number; - - /** 分类名称 */ - name: string; - - /** 分类编码 */ - code: string; - - /** 分类图片 */ - image_url?: string; - - /** 分类描述 */ - description?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: EnableStatus; - - /** 是否被删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 资产归属区域 -export interface ZichanArea { - /** 主键ID */ - id: number; - - /** 区域名称 */ - name: string; - - /** 区域编码 */ - code: string; - - /** 区域图片 */ - image_url?: string; - - /** 区域描述 */ - description?: string; - - /** 是否启用 (0否 1是) */ - is_enabled?: EnableStatus; - - /** 是否被删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; -} - -// 资产流转记录表 -export interface ZichanTransLog { - /** 主键ID */ - id: number; - - /** 资产流转 */ - asset_transfer?: AssetTransferType; - - /** 资产ID */ - asset_id?: number; - - /** 人员 */ - person?: string; - - /** 部门 */ - department?: string; - - /** 电话 */ - phone?: string; - - /** 流转事由 */ - transfer_reason?: string; - - /** 流转时间 */ - transfer_time?: Date | string; - - /** 是否被禁用 (0否 1是) */ - is_disabled?: number; - - /** 是否被删除 (0否 1是) */ - is_deleted?: number; - - /** 创建时间 */ - created_at: Date; - - /** 更新时间 */ - updated_at: Date; - - /** 关联的资产信息(查询时后端关联返回) */ - asset_info?: ZichanInfo; -} - -// 文件库接口 -export interface FileLibrary { - /** 主键ID */ - id: number; - - /** 文件名称 */ - file_name: string; - - /** 原始文件名 */ - original_filename?: string; - - /** 文件路径 */ - file_path: string; - - /** 文件类型 */ - file_type: string; - - /** 文件大小(字节) */ - file_size: number; - - /** 上传用户ID */ - uploader_id?: number; - - /** 上传者名称 */ - uploader_name?: string; - - /** 文件分类 */ - category_id?: number; - - /** 文件标签 */ - tags?: string; - - /** 文件描述 */ - description?: string; - - /** 下载次数 */ - download_count: number; - - /** 是否禁用 (0否 1是) */ - is_disabled?: EnableStatus; - - /** 是否被删除 (0否 1是) */ - is_deleted?: DeleteStatus; - - /** 创建时间 */ - created_at: string; - - /** 更新时间 */ - updated_at: string; -} - -// 文件分类接口 -export interface FileCategory { - id: number; - name: string; - code: string; - description?: string; - is_deleted?: DeleteStatus; - created_at: string; - updated_at: string; -} - -// 资产流转类型枚举 -export enum AssetTransferType { - STOCK = 0, // 在库 - BORROW = 1, // 借用 - RETURN = 2, // 归还 - LOST = 3, // 遗失 - MAINTAIN = 4 // 维护保养 -} - -// 资产流转类型名称映射 -export const AssetTransferTypeNameMap: Record = { - [AssetTransferType.STOCK]: '在库', - [AssetTransferType.BORROW]: '借用', - [AssetTransferType.RETURN]: '归还', - [AssetTransferType.LOST]: '遗失', - [AssetTransferType.MAINTAIN]: '维护保养' -}; - -// 资产流转类型颜色映射 -export const AssetTransferTypeColorMap: Record = { - [AssetTransferType.STOCK]: 'green', - [AssetTransferType.BORROW]: 'blue', - [AssetTransferType.RETURN]: 'cyan', - [AssetTransferType.LOST]: 'red', - [AssetTransferType.MAINTAIN]: 'orange' -}; - -// 添加图表类型定义(从大屏移植) -export interface CategoryChartData { - 设备分类: string; - 设备数: number; -} - -export interface CategoryChartDataWithPercent extends CategoryChartData { - 百分比: string; -} - -export interface OnlineRateChartData { - time_interval: string; - online_devices: number; - total_devices: number; -} - -export interface StateChartData { - 资产流转: string; - 设备数: number; -} - -export interface StateChartDataWithPercent extends StateChartData { - 百分比: string; -} - -export interface AlarmChartData { - time_interval: string; - total_devices: number; -} - -export interface AlarmDeviceData { - deviceName: string; - alarmCount: number; - rank: number; -} - -// 设备与资产信息结合的接口 -export interface DeviceWithAssetInfo { - id: number; - asset_name?: string; - device_category?: number; - ip_address?: string; - device_status?: DeviceStatus; - network_status?: NetworkStatus; - packet_loss?: PacketLossStatus; - cpu?: string; - memory?: string; - disk?: string; - is_deleted?: number; -} - -// 地图标记数据接口 - 基础定义 -export interface MarkerData { - longitude: number; - latitude: number; - isOnline?: string; - asset_name?: string; - type_image_url?: string; -} - -// 设备地图监控视图设备接口 -export interface MapViewDevice extends MarkerData { - id: number; - name?: string; - type_code: string; - device_category?: DeviceCategory; - device_status?: DeviceStatus; - description?: string; - address?: string; - protocol?: DeviceProtocolType; - last_update_time?: string; - area_code?: string; - area_name?: string; - image_url?: string; -} - -// 设备地图筛选条件 -export interface DeviceMapFilter { - type_code?: string; - device_category?: DeviceCategory[]; - device_status?: DeviceStatus; - area_code?: string[]; - keyword?: string; - device_id?: number; -} - -// 设备地图统计数据接口 -export interface DeviceMapStats { - total: number; - online: number; - offline: number; - error: number; - normal?: number; - fault?: number; - categoryStats?: { - category: DeviceCategory; - count: number; - name: string; - }[]; -} - -// 设备树统计数据类型 -export type DeviceTreeStats = Record; - -// 设备地图响应数据接口 -export interface DeviceMapDataResponse { - data: MapViewDevice[]; - stats: DeviceMapStats; - total?: number; - page?: number; - pageSize?: number; -} - -// 设备地图统计响应接口 -export interface DeviceMapStatsResponse { - data: DeviceMapStats; -} - -// 设备树节点类型枚举 -export enum DeviceTreeNodeType { - CATEGORY = 'category', - DEVICE = 'device' -} - -// 设备树节点状态枚举 -export enum DeviceTreeNodeStatus { - NORMAL = 'normal', - ERROR = 'error', - OFFLINE = 'offline', - WARNING = 'warning' -} - -// 设备树节点接口 -export interface DeviceTreeNode { - key: string; - title: string; - type: DeviceTreeNodeType; - status?: DeviceTreeNodeStatus; - icon?: string | null; - isLeaf?: boolean; - children?: DeviceTreeNode[]; -} - -export interface DeadlineInfo { - /** 进度百分比 */ - progress: number; - /** 剩余时间 */ - remainingTime: string; - /** 是否超时 */ - isOverdue: boolean; - /** 进度颜色 */ - color: string; - /** 进度文本 */ - text: string; -} - -export interface WorkOrderStatusHistory { - /** 状态 */ - status: WorkOrderStatus; - /** 操作人 */ - operator: string; - /** 操作时间 */ - operateTime: string; - /** 备注 */ - remark?: string; -} - -export interface WorkOrder { - /** 工单ID */ - id: string; - - /** 工单编号 */ - order_no: string; - - /** 工单标题 */ - title: string; - - /** 关联设备ID */ - device_id?: string; - - /** 设备名称 */ - device_name: string; - - /** 问题描述 */ - problem_desc: string; - - /** 问题分类 */ - problem_type: string; - - /** 故障等级 */ - priority: WorkOrderPriority; - - /** 工单状态 */ - status: WorkOrderStatus; - - /** 创建人ID */ - creator_id: string; - - /** 创建人姓名 */ - creator_name: string; - - /** 截止日期 */ - deadline: string; - - /** 创建时间 */ - created_at: string; - - /** 更新时间 */ - updated_at: string; - - /** 结果反馈 */ - feedback?: string; - - /** 附件列表 */ - attachments?: Array<{ - id: string; - name: string; - url: string; - size: number; - type: string; - }>; - - /** 当前处理人ID */ - assignee_id?: string; - - /** 当前处理人姓名 */ - assignee_name?: string; - - /** 状态历史记录 */ - statusHistory?: Array<{ - status: string; - timestamp: string; - operator: string; - }>; -} - -export interface WorkOrderSettings { - statusOptions: string[]; - priorityOptions: string[]; -} \ No newline at end of file