From c5355e16c24358149961144417694ac890eaf92c Mon Sep 17 00:00:00 2001 From: bot_dev1 Date: Wed, 5 Aug 2026 10:27:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20issue=20#135=20?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=8E=A7=E5=88=B6=E5=8F=B0=EF=BC=88web/admin?= =?UTF-8?q?=EF=BC=9A=E6=A8=A1=E5=9E=8B/=E7=9F=A5=E8=AF=86=E5=BA=93/?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E7=A1=AE=E8=AE=A4/=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/admin/README.md | 30 ++++ web/admin/admin.css | 67 ++++++++ web/admin/admin.js | 368 +++++++++++++++++++++++++++++++++++++++++++ web/admin/index.html | 119 ++++++++++++++ 4 files changed, 584 insertions(+) create mode 100644 web/admin/README.md create mode 100644 web/admin/admin.css create mode 100644 web/admin/admin.js create mode 100644 web/admin/index.html diff --git a/web/admin/README.md b/web/admin/README.md new file mode 100644 index 0000000..1ecdf78 --- /dev/null +++ b/web/admin/README.md @@ -0,0 +1,30 @@ +# web/admin — iAOP 管理控制台 + +issue #135 / PRD 8.1/8.2。纯静态单页(无构建链): +模型管理 / 知识库管理 / 告警确认 / 审计查询。 + +## 使用 + +```bash +# 从 web/ 根目录起服务(依赖 ../auth 守卫与 ../chat/citations.json、 +# ../cockpit/plans/alarm_panel.ti.json 相对路径) +cd web && python -m http.server 8084 +# 打开 http://127.0.0.1:8084/admin/index.html +``` + +加载到 `../auth/auth.js` 时强制登录(未登录跳登录页);纯静态独立运行时降级免登录演示。 + +## 页面与后端语义对齐 + +| 页签 | 能力 | 对齐 | +|------|------|------| +| 模型管理 | 模板列表、版本/阶段视图(dev/staging/prod 徽标)、注册新版本(semver 递增校验)、promote 逐级提升、rollback 回退、全程审计 | `core/model-framework/template_registry.py`(Stage/next_stage/_log) | +| 知识库管理 | 文档列表(标题/来源/章节数/索引状态)、上传并索引(浏览器端)、来源引用查看 | `core/rag-kb/` 文档资产 + `web/chat/citations.json` 编译产物 | +| 告警确认 | 告警列表(P0/P1/P2 配色取自 `alarm_panel.ti.json`)、详情(LLM 解释 + SOP)、状态机 待处理→已确认→已闭环(P0 先确认后闭环) | PRD 8.1 Alert 状态机、`alert_rules.py` severity、`alarm_config.py` 配色 | +| 审计查询 | 统一审计日志(时间/操作者/动作/资源/原因)、关键词+动作筛选、导出 JSON | `template_registry._log` / `prompts.drain_audit` 风格(#134 UserStore 审计) | + +## 边界 + +数据持久化在 localStorage(Demo 级;模型/告警含 Ti 示例种子数据); +后端 HTTP 服务就绪后替换各 Store 读写即可,页面结构不变。 +LLM 解释直连推理服务 `http://39.101.182.167:30800/v1/chat/completions`。 diff --git a/web/admin/admin.css b/web/admin/admin.css new file mode 100644 index 0000000..0a55310 --- /dev/null +++ b/web/admin/admin.css @@ -0,0 +1,67 @@ +/* iAOP 管理控制台样式(issue #135)。深色主题,与 cockpit/studio 一致。 */ +:root { + --bg: #0b1220; --surface: #13203a; --fg: #e6edf6; --muted: #8aa0bd; + --accent: #18d3c8; --warn: #f5a623; --line: rgba(255,255,255,.06); +} +* { box-sizing: border-box; } +body { margin: 0; font-family: "Microsoft YaHei", "PingFang SC", sans-serif; + background: var(--bg); color: var(--fg); } + +#topbar { display: flex; align-items: center; justify-content: space-between; + padding: 10px 16px; background: var(--surface); border-bottom: 1px solid var(--line); } +h1 { font-size: 18px; color: var(--accent); margin: 0; } +h1 .sub { font-size: 12px; color: var(--muted); font-weight: 400; } +#topbar-right { display: flex; gap: 14px; align-items: center; font-size: 13px; } +#topbar-right a { color: var(--accent); text-decoration: none; } +.hint { font-size: 11px; color: #64748b; } + +#tabs { display: flex; gap: 4px; padding: 8px 16px 0; } +.tab { padding: 8px 18px; border: 1px solid var(--line); border-bottom: none; + border-radius: 8px 8px 0 0; background: transparent; color: var(--muted); + cursor: pointer; font-size: 14px; } +.tab.active { background: var(--surface); color: var(--accent); } + +.page { display: none; padding: 12px 16px 24px; } +.page.active { display: block; } +.panel { background: var(--surface); border: 1px solid var(--line); + border-radius: 8px; padding: 14px 16px; } +.panel h2 { font-size: 14px; margin: 0 0 10px; color: var(--accent); } +.row { display: flex; align-items: center; gap: 10px; margin: 10px 0; flex-wrap: wrap; } + +select, input { padding: 7px 10px; border-radius: 6px; font-size: 13px; + border: 1px solid var(--line); background: var(--bg); color: var(--fg); } +button { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; + background: #1e293b; color: var(--fg); font-size: 12px; } +button.primary { background: var(--accent); color: #04202a; } +button:disabled { opacity: .4; cursor: not-allowed; } + +table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 13px; } +th, td { padding: 7px 8px; border-bottom: 1px solid var(--line); text-align: left; } +th { color: var(--muted); font-weight: 400; } + +/* 模型阶段徽标 */ +.stage { padding: 2px 10px; border-radius: 10px; font-size: 12px; } +.stage-dev { background: rgba(138,160,189,.15); color: var(--muted); } +.stage-staging { background: rgba(245,166,35,.15); color: var(--warn); } +.stage-prod { background: rgba(46,204,113,.15); color: #2ecc71; } + +/* 知识库 */ +.detail { margin-top: 12px; background: var(--bg); border-radius: 6px; padding: 10px; + font-size: 12px; color: var(--muted); max-height: 260px; overflow-y: auto; } +.idx-ok { color: #2ecc71; } +.idx-pending { color: var(--warn); } + +/* 告警 */ +.split-alerts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } +.alert-item { border-radius: 6px; padding: 8px 12px; margin-bottom: 6px; font-size: 13px; + border-left: 3px solid transparent; cursor: pointer; + display: flex; gap: 10px; align-items: center; } +.alert-item:hover { outline: 1px solid var(--line); } +.alert-item .sev { font-weight: 700; } +.alert-item .st { margin-left: auto; font-size: 11px; color: var(--muted); } +#alert-detail { font-size: 13px; line-height: 1.7; } +#alert-detail .llm { background: var(--bg); border-radius: 6px; padding: 10px; + white-space: pre-wrap; margin: 8px 0; } +#alert-detail .sop { border-left: 3px solid var(--warn); padding: 4px 10px; + color: var(--muted); font-size: 12px; } +@media (max-width: 1000px) { .split-alerts { grid-template-columns: 1fr; } } diff --git a/web/admin/admin.js b/web/admin/admin.js new file mode 100644 index 0000000..0f2440e --- /dev/null +++ b/web/admin/admin.js @@ -0,0 +1,368 @@ +/* iAOP 管理控制台(issue #135 / PRD 8.1/8.2)。 + * + * 纯静态单页(无构建链),语义对齐后端内核: + * - 模型管理:core/model-framework/template_registry.py —— semver 校验、 + * dev→staging→prod 逐级 promote、rollback 回退、全程 _log 审计; + * - 知识库管理:文档列表来自 web/chat/citations.json(模板 rag-kb 资产编译产物), + * 上传在浏览器端索引(Demo;rag-kb HTTP 服务就绪后替换为真实索引管线); + * - 告警确认:PRD 8.1 状态机 pending→acked→closed(P0 必须先确认再闭环, + * 高利害人工确认),severity 配色取自 cockpit 的 alarm_panel.ti.json; + * - 审计查询:统一读取 UserStore 审计日志(#134),支持筛选与导出。 + * + * 数据持久化在 localStorage(Demo 级);后端 HTTP 服务就绪后替换 Store 读写即可。 + */ +"use strict"; + +var INFER_BASE = "http://39.101.182.167:30800"; +var MODELS_KEY = "iaop.admin.models.v1"; +var ALERTS_KEY = "iaop.admin.alerts.v1"; +var KB_KEY = "iaop.admin.kb.v1"; + +var currentUserName = "anonymous"; +var severityStyles = { + P0: { fg: "#ff3b30", bg: "rgba(255,59,48,0.12)", border: "#ff3b30" }, + P1: { fg: "#f5a623", bg: "rgba(245,166,35,0.12)", border: "#f5a623" }, + P2: { fg: "#3aa0ff", bg: "rgba(58,160,255,0.10)" } +}; + +/* ---------------- 工具 ---------------- */ +function el(tag, cls, text) { + var n = document.createElement(tag); + if (cls) n.className = cls; + if (text !== undefined) n.textContent = text; + return n; +} +function load(key, fallback) { + try { var v = localStorage.getItem(key); return v ? JSON.parse(v) : fallback; } + catch (e) { return fallback; } +} +function save(key, obj) { localStorage.setItem(key, JSON.stringify(obj)); } +function audit(action, resource, reason) { + if (typeof UserStore !== "undefined") UserStore.audit(action, resource, reason, currentUserName); +} +function download(filename, text) { + var a = document.createElement("a"); + a.href = URL.createObjectURL(new Blob([text], { type: "application/json" })); + a.download = filename; a.click(); URL.revokeObjectURL(a.href); +} + +/* ---------------- 模型管理(template_registry 语义) ---------------- */ +var STAGES = ["dev", "staging", "prod"]; +var SEMVER_RE = /^\d+\.\d+\.\d+$/; +function semverKey(v) { + var p = v.split(".").map(Number); + return p[0] * 1e6 + p[1] * 1e3 + p[2]; +} +function seedModels() { + return [ + { name: "quality_forecast", version: "1.2.0", stage: "prod", author: "bot_dev1", + updated_at: "2026-08-01T09:00:00Z" }, + { name: "anomaly_detection", version: "1.0.3", stage: "staging", author: "bot_dev1", + updated_at: "2026-08-02T09:00:00Z" }, + { name: "process_optimizer", version: "0.9.1", stage: "dev", author: "engineer", + updated_at: "2026-08-03T09:00:00Z" }, + { name: "cross_process_optimizer", version: "0.5.0", stage: "dev", author: "engineer", + updated_at: "2026-08-04T09:00:00Z" } + ]; +} +function getModels() { + var m = load(MODELS_KEY, null); + if (!m) { m = seedModels(); save(MODELS_KEY, m); } + return m; +} +function renderModels() { + var tbody = document.getElementById("model-tbody"); + tbody.innerHTML = ""; + getModels().forEach(function (m, i) { + var tr = el("tr"); + tr.appendChild(el("td", null, m.name)); + tr.appendChild(el("td", null, m.version)); + var tdStage = el("td"); + tdStage.appendChild(el("span", "stage stage-" + m.stage, m.stage)); + tr.appendChild(tdStage); + tr.appendChild(el("td", null, m.author)); + tr.appendChild(el("td", null, (m.updated_at || "").slice(0, 10))); + var ops = el("td"); + var idx = STAGES.indexOf(m.stage); + if (idx < STAGES.length - 1) { + var btn = el("button", null, "提升到 " + STAGES[idx + 1]); + btn.onclick = function () { promoteModel(i); }; + ops.appendChild(btn); + } else { + ops.appendChild(el("span", "hint", "已达 prod")); + } + if (idx > 0) { + var rb = el("button", null, "回滚到 " + STAGES[idx - 1]); + rb.style.marginLeft = "6px"; + rb.onclick = function () { rollbackModel(i); }; + ops.appendChild(rb); + } + tr.appendChild(ops); + tbody.appendChild(tr); + }); +} +function registerModel() { + var name = document.getElementById("model-name").value.trim(); + var version = document.getElementById("model-version").value.trim(); + if (!name || !SEMVER_RE.test(version)) { alert("模型名必填,版本须为 semver(如 1.0.0)"); return; } + var models = getModels(); + var existing = models.filter(function (m) { return m.name === name; }); + if (existing.length) { + var latest = existing.reduce(function (a, b) { + return semverKey(a.version) > semverKey(b.version) ? a : b; + }); + if (semverKey(version) <= semverKey(latest.version)) { + alert("版本必须大于当前最新 " + latest.version); return; + } + } + models.push({ name: name, version: version, stage: "dev", + author: currentUserName, updated_at: new Date().toISOString() }); + save(MODELS_KEY, models); + audit("create", "model", name + "@" + version + " 注册(dev)"); + renderModels(); +} +function promoteModel(i) { + var models = getModels(); + var m = models[i]; + var next = STAGES[STAGES.indexOf(m.stage) + 1]; + if (!next) return; + m.stage = next; m.updated_at = new Date().toISOString(); + save(MODELS_KEY, models); + audit("promote", "model", m.name + "@" + m.version + " → " + next); + renderModels(); +} +function rollbackModel(i) { + var models = getModels(); + var m = models[i]; + var prev = STAGES[STAGES.indexOf(m.stage) - 1]; + if (!prev) return; + m.stage = prev; m.updated_at = new Date().toISOString(); + save(MODELS_KEY, models); + audit("rollback", "model", m.name + "@" + m.version + " → " + prev); + renderModels(); +} + +/* ---------------- 知识库管理 ---------------- */ +function getKbDocs() { return load(KB_KEY, []); } +function renderKb() { + var tbody = document.getElementById("kb-tbody"); + tbody.innerHTML = ""; + getKbDocs().forEach(function (d, i) { + var tr = el("tr"); + tr.appendChild(el("td", null, d.title)); + tr.appendChild(el("td", null, d.source)); + tr.appendChild(el("td", null, String(d.sections.length))); + tr.appendChild(el("td", "idx-ok", d.status || "已索引")); + var ops = el("td"); + var view = el("button", null, "查看引用"); + view.onclick = function () { + var box = document.getElementById("kb-detail"); + box.hidden = false; + box.innerHTML = ""; + box.appendChild(el("div", null, "《" + d.title + "》 " + d.source)); + d.sections.forEach(function (s) { + box.appendChild(el("div", null, "§ " + s.heading + " — " + s.snippet)); + }); + }; + ops.appendChild(view); + tr.appendChild(ops); + tbody.appendChild(tr); + }); +} +function loadKbFromCitations() { + // 首选:web/chat/citations.json(模板知识库编译产物,含真实来源路径) + fetch("../chat/citations.json").then(function (r) { return r.ok ? r.json() : null; }) + .then(function (d) { + if (d && d.documents && !getKbDocs().length) { + save(KB_KEY, d.documents.map(function (doc) { + return { title: doc.title, source: doc.source, + sections: doc.sections, status: "已索引" }; + })); + } + renderKb(); + }).catch(function () { renderKb(); }); +} +function uploadKb() { + var f = document.getElementById("kb-file").files[0]; + if (!f) { alert("请选择 .md/.txt 文档"); return; } + var reader = new FileReader(); + reader.onload = function () { + var text = String(reader.result || ""); + var titleM = text.match(/^#\s+(.+)$/m); + var docs = getKbDocs(); + docs.push({ + title: titleM ? titleM[1].trim() : f.name.replace(/\.\w+$/, ""), + source: "upload/" + f.name, + sections: [{ heading: "全文", snippet: text.replace(/\s+/g, " ").slice(0, 160) }], + status: "已索引(本地)" + }); + save(KB_KEY, docs); + audit("create", "kb_doc", f.name + " 上传并索引"); + document.getElementById("kb-hint").textContent = f.name + " 已索引"; + renderKb(); + }; + reader.readAsText(f, "utf-8"); +} + +/* ---------------- 告警确认(PRD 8.1 状态机) ---------------- */ +var ALERT_STATUS = { pending: "待处理", acked: "已确认", closed: "已闭环" }; +function seedAlerts() { + var now = Date.now(); + return [ + { id: "ALM-2001", severity: "P0", point: "CLF-01.TEMP", + message: "氯化炉温度骤升越上限(920℃ > 900℃)", + time: new Date(now - 12 * 60000).toLocaleString("zh-CN"), status: "pending", + sop: "1) 现场确认热电偶;2) 降氯气进料 10%;3) 通知值班长;4) 必要时切旁路。" }, + { id: "ALM-2002", severity: "P1", point: "RF-01.VAC", + message: "还原炉真空度波动超阈值", + time: new Date(now - 45 * 60000).toLocaleString("zh-CN"), status: "pending", + sop: "1) 检查真空泵组;2) 核查泄漏点;3) 加强监控 30 分钟。" }, + { id: "ALM-2003", severity: "P2", point: "ST-01.STEAM", + message: "蒸汽流量低于经验区间", + time: new Date(now - 90 * 60000).toLocaleString("zh-CN"), status: "acked", + sop: "记录趋势,纳入班报跟踪。" } + ]; +} +function getAlerts() { + var a = load(ALERTS_KEY, null); + if (!a) { a = seedAlerts(); save(ALERTS_KEY, a); } + return a; +} +function renderAlerts() { + var filter = document.getElementById("alert-filter").value; + var box = document.getElementById("alert-list"); + box.innerHTML = ""; + var rank = { P0: 3, P1: 2, P2: 1 }; + getAlerts() + .filter(function (a) { return !filter || a.status === filter; }) + .sort(function (a, b) { return rank[b.severity] - rank[a.severity]; }) + .forEach(function (a) { + var st = severityStyles[a.severity] || {}; + var item = el("div", "alert-item"); + item.style.color = st.fg || "inherit"; + item.style.background = st.bg || "transparent"; + if (st.border) item.style.borderLeftColor = st.border; + item.appendChild(el("span", "sev", a.severity)); + item.appendChild(el("span", null, a.id + " · " + a.message)); + item.appendChild(el("span", "st", ALERT_STATUS[a.status])); + item.onclick = function () { renderAlertDetail(a.id); }; + box.appendChild(item); + }); +} +function renderAlertDetail(id) { + var a = getAlerts().find(function (x) { return x.id === id; }); + var box = document.getElementById("alert-detail"); + if (!a) return; + box.innerHTML = ""; + box.appendChild(el("div", null, a.id + " · " + a.severity + " · " + a.point)); + box.appendChild(el("div", "hint", a.message + " · " + a.time + " · 状态:" + ALERT_STATUS[a.status])); + var llm = el("div", "llm", "点击「生成 LLM 解释」获取可能原因与处置建议(PRD 场景A)"); + box.appendChild(llm); + var btnLlm = el("button", "primary", "生成 LLM 解释"); + btnLlm.onclick = function () { + btnLlm.disabled = true; + llm.textContent = "生成中…"; + fetch(INFER_BASE + "/v1/chat/completions", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ model: "iaop-ti-cl4-v1", max_tokens: 512, messages: [ + { role: "system", content: "你是工业报警解释助手,输出【可能原因】与【处置建议】。" }, + { role: "user", content: "告警 " + a.severity + " 点位 " + a.point + ":" + a.message } + ] }) + }).then(function (r) { return r.json(); }) + .then(function (d) { + llm.textContent = (d.choices && d.choices[0] && d.choices[0].message.content) || "(空响应)"; + }) + .catch(function (e) { llm.textContent = "解释生成失败:" + e.message; }) + .finally(function () { btnLlm.disabled = false; }); + }; + box.appendChild(btnLlm); + box.appendChild(el("div", "sop", "SOP:" + a.sop)); + // 状态流转按钮(P0 高利害:必须先确认再闭环,PRD 8.1) + var row = el("div", "row"); + if (a.status === "pending") { + var ack = el("button", "primary", "确认告警"); + ack.onclick = function () { transitionAlert(a.id, "acked"); }; + row.appendChild(ack); + if (a.severity === "P0") row.appendChild(el("span", "hint", "P0 高利害:确认后仍需人工复核闭环")); + } + if (a.status === "acked") { + var close = el("button", "primary", "闭环"); + close.onclick = function () { transitionAlert(a.id, "closed"); }; + row.appendChild(close); + } + box.appendChild(row); +} +function transitionAlert(id, to) { + var alerts = getAlerts(); + var a = alerts.find(function (x) { return x.id === id; }); + if (!a) return; + a.status = to; + save(ALERTS_KEY, alerts); + audit(to === "acked" ? "ack" : "close", "alert", a.id + " " + a.severity + " → " + ALERT_STATUS[to]); + renderAlerts(); renderAlertDetail(id); +} + +/* ---------------- 审计查询 ---------------- */ +function renderAudit() { + var q = document.getElementById("audit-q").value.trim(); + var action = document.getElementById("audit-action").value; + var tbody = document.getElementById("audit-tbody"); + tbody.innerHTML = ""; + var log = (typeof UserStore !== "undefined") ? UserStore.auditLog() : []; + log.slice().reverse() + .filter(function (e) { + if (action && e.action !== action) return false; + if (q && (e.actor + e.resource + e.reason).indexOf(q) < 0) return false; + return true; + }) + .forEach(function (e) { + var tr = el("tr"); + tr.appendChild(el("td", null, e.time.slice(0, 19).replace("T", " "))); + tr.appendChild(el("td", null, e.actor)); + tr.appendChild(el("td", null, e.action)); + tr.appendChild(el("td", null, e.resource)); + tr.appendChild(el("td", null, e.reason)); + tbody.appendChild(tr); + }); +} + +/* ---------------- 启动 ---------------- */ +(function init() { + function boot(user) { + if (user) { + currentUserName = user.username; + document.getElementById("who").textContent = user.username + " · " + user.role; + } + document.querySelectorAll(".tab").forEach(function (tab) { + tab.onclick = function () { + document.querySelectorAll(".tab").forEach(function (t) { t.classList.remove("active"); }); + document.querySelectorAll(".page").forEach(function (p) { p.classList.remove("active"); }); + tab.classList.add("active"); + document.getElementById("page-" + tab.dataset.page).classList.add("active"); + }; + }); + document.getElementById("btn-register").onclick = registerModel; + document.getElementById("btn-kb-upload").onclick = uploadKb; + document.getElementById("alert-filter").onchange = renderAlerts; + document.getElementById("btn-audit-filter").onclick = renderAudit; + document.getElementById("btn-audit-export").onclick = function () { + download("audit-export-" + Date.now() + ".json", + JSON.stringify(typeof UserStore !== "undefined" ? UserStore.auditLog() : [], null, 2)); + }; + // severity 配色资产(与驾驶舱同源;取不到用内置兜底) + fetch("../cockpit/plans/alarm_panel.ti.json").then(function (r) { + return r.ok ? r.json() : null; + }).then(function (p) { + if (p && p.severityStyles) { severityStyles = p.severityStyles; renderAlerts(); } + }).catch(function () {}); + renderModels(); loadKbFromCitations(); renderAlerts(); renderAudit(); + } + // 认证守卫(#150 IAOP_AUTH,在则强制登录;纯静态独立运行时降级免登录演示) + if (typeof IAOP_AUTH !== "undefined") { + IAOP_AUTH.requireLoginElseRedirect("../auth/login.html").then(function (user) { + if (user) boot(user); + }); + } else { + boot(null); + } +})(); diff --git a/web/admin/index.html b/web/admin/index.html new file mode 100644 index 0000000..7a24249 --- /dev/null +++ b/web/admin/index.html @@ -0,0 +1,119 @@ + + + + + +iAOP 管理控制台 + + + + +
+

iAOP 管理控制台 模型 / 知识库 / 告警 / 审计

+ +
+ + + + +
+
+

模型模板列表(阶段:dev → staging → prod,对齐 template_registry)

+
+ + + + 语义对齐 core/model-framework/template_registry.py:promote 逐级提升、rollback 回退、全程审计 +
+ + + +
模型版本阶段作者更新时间操作
+
+
+ + +
+
+

知识库文档(来源:templates/*/rag-kb/documents,经 build_citations 索引)

+
+ + + +
+ + + +
文档来源章节数索引状态操作
+ +
+
+ + +
+
+
+

告警列表(状态机:待处理 → 已确认 → 已闭环,PRD 8.1)

+
+ + severity 配色来自 alarm_panel.ti.yaml(P0 红 / P1 黄 / P2 蓝) +
+
+
+
+

告警详情(LLM 解释 + SOP)

+

选择左侧告警查看详情

+
+
+
+ + +
+
+

操作审计日志

+
+ + + + +
+ + + +
时间操作者动作资源结果/原因
+
+
+ + + + + +