From a92faf98ecc815da8272bbd6a9e11dd9e7e274ca Mon Sep 17 00:00:00 2001 From: guanyuankai Date: Tue, 4 Nov 2025 14:52:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=91=8A=E8=AD=A6=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 + dist-electron/main.js | 1 + electron/main.ts | 1 + package-lock.json | 187 +++++++++++++++++++ package.json | 2 + src/App.vue | 46 +++-- src/api/alarms.ts | 113 ++++++++++++ src/api/system.ts | 58 +++++- src/components/Sidebar.vue | 56 +++--- src/main.ts | 3 +- src/router/index.ts | 14 +- src/views/AlarmManager.vue | 357 +++++++++++++++++++++++++++++++++++++ src/views/Alarms.vue | 241 +++++++++++++++++++++++++ src/views/StatusPage.vue | 284 ++++++++++++++++++----------- 14 files changed, 1223 insertions(+), 146 deletions(-) create mode 100644 src/api/alarms.ts create mode 100644 src/views/AlarmManager.vue create mode 100644 src/views/Alarms.vue diff --git a/.gitignore b/.gitignore index a547bf3..8dd7b52 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ dist-ssr *.njsproj *.sln *.sw? + +dist/ +build/ +config/ +tmp/ +test-data/ diff --git a/dist-electron/main.js b/dist-electron/main.js index cc158ce..520e6ff 100644 --- a/dist-electron/main.js +++ b/dist-electron/main.js @@ -19,6 +19,7 @@ function createWindow() { } }); win.maximize(); + win.webContents.openDevTools(); win.webContents.on("did-finish-load", () => { win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString()); }); diff --git a/electron/main.ts b/electron/main.ts index 3ca15fb..ffe5868 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -37,6 +37,7 @@ function createWindow() { }, }); win.maximize(); + win.webContents.openDevTools(); // Test active push message to Renderer-process. win.webContents.on("did-finish-load", () => { diff --git a/package-lock.json b/package-lock.json index c9a5d1c..a9d6f3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "edge-proxy-manager", "version": "0.0.0", "dependencies": { + "@codemirror/lang-json": "^6.0.2", "@element-plus/icons-vue": "^2.3.2", "axios": "^1.13.0", "element-plus": "^2.11.5", "vue": "^3.4.21", + "vue-codemirror": "^6.1.1", "vue-router": "^4.6.3" }, "devDependencies": { @@ -71,6 +73,100 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz", + "integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", + "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", + "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -905,6 +1001,41 @@ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz", + "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.3.tgz", + "integrity": "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@malept/cross-spawn-promise": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", @@ -983,6 +1114,12 @@ "node": ">= 10.0.0" } }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2470,6 +2607,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2651,6 +2804,12 @@ "node": ">= 10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5138,6 +5297,12 @@ "node": ">=8" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -5475,6 +5640,22 @@ } } }, + "node_modules/vue-codemirror": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vue-codemirror/-/vue-codemirror-6.1.1.tgz", + "integrity": "sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg==", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "6.x", + "@codemirror/language": "6.x", + "@codemirror/state": "6.x", + "@codemirror/view": "6.x" + }, + "peerDependencies": { + "codemirror": "6.x", + "vue": "3.x" + } + }, "node_modules/vue-router": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz", @@ -5507,6 +5688,12 @@ "typescript": ">=5.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 9798626..c7a9f94 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ "preview": "vite preview" }, "dependencies": { + "@codemirror/lang-json": "^6.0.2", "@element-plus/icons-vue": "^2.3.2", "axios": "^1.13.0", "element-plus": "^2.11.5", "vue": "^3.4.21", + "vue-codemirror": "^6.1.1", "vue-router": "^4.6.3" }, "devDependencies": { diff --git a/src/App.vue b/src/App.vue index 9610b9e..4b64bb4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,8 +1,11 @@ + + diff --git a/src/api/alarms.ts b/src/api/alarms.ts new file mode 100644 index 0000000..6af6f2b --- /dev/null +++ b/src/api/alarms.ts @@ -0,0 +1,113 @@ +import axios from "axios"; + +const API_BASE_URL = "http://192.168.0.33:8080"; + +const apiClient = axios.create({ + baseURL: API_BASE_URL, + timeout: 5000, +}); + +export interface AlarmRule { + rule_id: string; + device_id: string; + data_point_name: string; + compare_type: "GT" | "LT" | "EQ"; // 限制为已知类型 + threshold: number; + level: "INFO" | "WARNING" | "CRITICAL"; // 限制为已知类型 + debounce_seconds: number; + alarm_mqtt_topic?: string; // 可选 + message_template: string; + clear_message_template?: string; // 可选 + + // [重要] 用于前端的额外状态 + _isNew?: boolean; // 标记这是不是一条新添加的规则 +} + +export interface AlarmEvent { + event_id?: number; + rule_id: string; + device_id: string; + status: "ACTIVE" | "CLEARED"; + level: "INFO" | "WARNING" | "CRITICAL"; // 假设的级别 + message: string; + trigger_value: number; + timestamp_ms: number; +} + +export const getActiveAlarms = async (): Promise => { + try { + const response = await apiClient.get("/api/alarms/active"); + return response.data; + } catch (error) { + console.error("API 请求失败 (getActiveAlarms):", error); + throw new Error("无法获取活动告警"); + } +}; + +export const getAlarmHistory = async ( + limit: number = 200 +): Promise => { + try { + const response = await apiClient.get( + `/api/alarms/history?limit=${limit}` + ); + return response.data; + } catch (error) { + console.error("API 请求失败 (getAlarmHistory):", error); + throw new Error("无法获取告警历史"); + } +}; + +export const postClearAlarm = async ( + rule_id: string, + device_id: string +): Promise => { + try { + const response = await apiClient.post("/api/alarms/clear", { + rule_id, + device_id, + }); + return response.data; + } catch (error) { + console.error("API 请求失败 (postClearAlarm):", error); + throw new Error("无法清除告警"); + } +}; + +export const postReloadRules = async (): Promise => { + try { + const response = await apiClient.post("/api/alarms/reload"); + return response.data; + } catch (error) { + console.error("API 请求失败 (postReloadRules):", error); + throw new Error("无法重载规则"); + } +}; + +export const getAlarmConfig = async (): Promise => { + try { + const response = await apiClient.get("/api/alarms/config"); + if (typeof response.data === 'object') { + return JSON.stringify(response.data, null, 2); + } + return response.data; + } catch (error) { + console.error("API 请求失败 (getAlarmConfig):", error); + throw new Error("无法获取告警配置"); + } +}; + +export const postAlarmConfig = async (jsonContent: string): Promise => { + try { + const response = await apiClient.post("/api/alarms/config", jsonContent, { + headers: { 'Content-Type': 'application/json' } + }); + return response.data; + } catch (error) { + console.error("API 请求失败 (postAlarmConfig):", error); + if (axios.isAxiosError(error) && error.response?.status === 400) { + throw new Error("保存失败:JSON 格式或规则 schema 无效。请检查 C++ 服务日志。"); + } + throw new Error("无法保存告警配置"); + } +}; diff --git a/src/api/system.ts b/src/api/system.ts index 4b8900f..180db18 100644 --- a/src/api/system.ts +++ b/src/api/system.ts @@ -1,11 +1,24 @@ import axios from "axios"; +// --------------------------------------------- +// 接口类型定义 (您已有的) +// --------------------------------------------- export interface SystemStatus { cpu_usage_percentage: number; memory_total_kb: number; memory_free_kb: number; memory_usage_percentage: number; } +export interface SystemId { + deviceID: string; +} +export interface Device { + id: string; + type: string; + is_running: boolean; + connection_details: Record; +} +export type DevicesResponse = Device[]; const API_BASE_URL = "http://192.168.0.33:8080"; @@ -19,7 +32,50 @@ export const getSystemStatus = async (): Promise => { const response = await apiClient.get("/api/system/status"); return response.data; } catch (error) { - console.error("API 请求失败:", error); + console.error("API 请求失败 (getSystemStatus):", error); throw new Error("无法连接到边缘代理"); } }; + +export const getSystemId = async (): Promise => { + try { + const response = await apiClient.get("/api/system/id"); + + if ( + !response.data || + typeof response.data.deviceID !== "string" || + response.data.deviceID.trim() === "" + ) { + console.error("API 响应格式错误: ", response.data); + throw new Error("从API获取的ID数据格式不正确"); + } + + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + console.error("API 请求失败 (getSystemId):", error); + throw new Error("无法连接到边缘代理"); + } else { + throw error; + } + } +}; + +export const getDevices = async (): Promise => { + try { + const response = await apiClient.get("/api/devices"); + if (!Array.isArray(response.data)) { + console.error("API 响应格式错误 (getDevices):", response.data); + throw new Error("从API获取的设备数据格式不正确"); + } + + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + console.error("API 请求失败 (getDevices):", error); + throw new Error("无法连接到边缘代理"); + } else { + throw error; + } + } +}; diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 0329dff..bf9f6e3 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -25,9 +25,16 @@ import {
  • - + - 任务管理 + 告警展示 + +
  • + +
  • + + + 告警管理
  • @@ -61,26 +68,35 @@ import { diff --git a/src/main.ts b/src/main.ts index 6df3243..dcb7a5f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,8 @@ import "./style.css"; import App from "./App.vue"; import ElementPlus from "element-plus"; import router from "./router"; - +import "element-plus/dist/index.css"; +import "element-plus/theme-chalk/dark/css-vars.css"; const app = createApp(App); app.use(router); diff --git a/src/router/index.ts b/src/router/index.ts index a59ef64..df96bd5 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -2,7 +2,8 @@ import { createRouter, createWebHashHistory } from "vue-router"; import StatusPage from "../views/StatusPage.vue"; -import TasksPage from "../views/TasksPage.vue"; +import AlarmPage from "../views/Alarms.vue"; +import AlarmManager from "../views/AlarmManager.vue"; const routes = [ { @@ -11,9 +12,14 @@ const routes = [ component: StatusPage, }, { - path: "/tasks", - name: "Tasks", - component: TasksPage, + path: "/alarms", + name: "Alarms", + component: AlarmPage, + }, + { + path: "/alarmsManager", + name: "AlarmManager", + component: AlarmManager, }, // ... 您未来可以从这里添加更多路由 ]; diff --git a/src/views/AlarmManager.vue b/src/views/AlarmManager.vue new file mode 100644 index 0000000..aa04529 --- /dev/null +++ b/src/views/AlarmManager.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/src/views/Alarms.vue b/src/views/Alarms.vue new file mode 100644 index 0000000..79c4374 --- /dev/null +++ b/src/views/Alarms.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/src/views/StatusPage.vue b/src/views/StatusPage.vue index 09e0567..f7440da 100644 --- a/src/views/StatusPage.vue +++ b/src/views/StatusPage.vue @@ -1,19 +1,55 @@