From 189885d73c305370b9e8617d7a5c5e4366127cd4 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Wed, 3 Jun 2026 00:09:20 +0300 Subject: [PATCH] feat(detmir): focus security incidents portal card --- adk-rust/RUNBOOK.md | 16 +++++++++ .../crates/detmir-grafana-check/src/main.rs | 27 ++++++++++++++- adk-rust/crates/detmir-portal/src/main.rs | 25 ++++++++++++++ .../crates/detmir-portal/src/static/app.js | 34 ++++++++++++++++++- 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/adk-rust/RUNBOOK.md b/adk-rust/RUNBOOK.md index b82e7e0..99f6888 100644 --- a/adk-rust/RUNBOOK.md +++ b/adk-rust/RUNBOOK.md @@ -1603,6 +1603,22 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg failed units stayed `0`. Local Playwright smoke against a temporary portal instance verified `Инциденты ИБ`, `В работу`, persisted UI state, disabled ack button, and zero JS errors. + - `Инциденты ИБ` tightened to a DLP-focused card: the tab now filters the + card to DLP/incident/case items, adds a synthetic DLP-count incident when + `dlp_counts.warn/fail` is non-zero, and shows only DLP/Grafana dashboard + links (`detmir-dlp-security`, `detmir-dlp-management`, + `awatch-dlp-overview`, Grafana catalog). Worktime, 1C, AW UI, and other + non-incident operational links are intentionally not shown in this card. + Local Playwright smoke verified DLP incident rendering, dashboard links, + no Worktime/1C text in the card, and zero JS errors. + - during this deploy, `detmir-grafana-check` was corrected so empty + detail-only panels for employees/applications are WARN, not FAIL. The + mandatory freshness/summary panels still fail the check when stale or + empty. This removed a false red state shortly after midnight when + `today` detail rows were legitimately empty. Production verification: + Grafana check `ok=true` with `fail=0`, `detmir-auto --no-heal` rc `0`, + portal health `true`, `detmir-status` `OK / ok_for_operator=true`, and + failed units `0`. Отложить: diff --git a/adk-rust/crates/detmir-grafana-check/src/main.rs b/adk-rust/crates/detmir-grafana-check/src/main.rs index 9ec9ca1..b7dad83 100644 --- a/adk-rust/crates/detmir-grafana-check/src/main.rs +++ b/adk-rust/crates/detmir-grafana-check/src/main.rs @@ -232,8 +232,14 @@ fn build_report(cli: &Cli) -> Result { && first_number .map(|value| value > cli.max_freshness_minutes) .unwrap_or(true); - let status = if rows < cli.min_panel_rows || freshness_failed { + let empty_optional_panel = + rows < cli.min_panel_rows && is_optional_empty_panel(&target.panel_title); + let status = if freshness_failed + || (rows < cli.min_panel_rows && !empty_optional_panel) + { Status::Fail + } else if empty_optional_panel { + Status::Warn } else { Status::Ok }; @@ -572,6 +578,11 @@ fn is_freshness_panel(title: &str) -> bool { title.to_lowercase().contains("свеж") } +fn is_optional_empty_panel(title: &str) -> bool { + let title = title.to_lowercase(); + title.contains("прилож") || title.contains("сотрудник") +} + fn frame_row_count(value: &Value) -> usize { value .get("frames") @@ -708,4 +719,18 @@ mod tests { assert_eq!(targets[0].panel_id, 5); assert_eq!(targets[0].datasource_uid, "influxdb_aw"); } + + #[test] + fn application_panels_are_optional_when_empty() { + assert!(is_optional_empty_panel( + "Сегодня: доказанная работа по приложениям" + )); + assert!(is_optional_empty_panel( + "Сегодня: приложения и подтверждения" + )); + assert!(is_optional_empty_panel( + "Сегодня: активность по сотрудникам" + )); + assert!(!is_optional_empty_panel("Сегодня: активное время")); + } } diff --git a/adk-rust/crates/detmir-portal/src/main.rs b/adk-rust/crates/detmir-portal/src/main.rs index 196862e..f4f49e0 100644 --- a/adk-rust/crates/detmir-portal/src/main.rs +++ b/adk-rust/crates/detmir-portal/src/main.rs @@ -182,6 +182,9 @@ struct PortalLinks { portal: String, grafana_dashboards: String, detmir_activitywatch: String, + dlp_security_dashboard: String, + dlp_management_dashboard: String, + dlp_overview_dashboard: String, aw_ui: String, worktime_report: String, file1c_brief: String, @@ -689,6 +692,22 @@ fn build_incidents(snapshot: &Snapshot, state: &IncidentStateFile) -> Vec 0 || fail > 0 { + incidents.push(incident_item( + if fail > 0 { "FAIL" } else { "WARN" }, + "dlp", + "dlp_counts", + &format!("DLP requires review: warn={warn}, fail={fail}"), + &snapshot.generated_at_utc, + "/portal/incidents", + state, + )); + } + } incidents } @@ -923,6 +942,12 @@ fn links() -> PortalLinks { detmir_activitywatch: "/d/detmir-aw-main/detmir-activitywatch?orgId=1&from=now-48h&to=now&timezone=browser&var-host=SHARKON2025&refresh=5m" .to_string(), + dlp_security_dashboard: + "/d/detmir-dlp-security?orgId=1&from=now-30d&to=now&timezone=browser".to_string(), + dlp_management_dashboard: + "/d/detmir-dlp-management?orgId=1&from=now-30d&to=now&timezone=browser".to_string(), + dlp_overview_dashboard: + "/d/awatch-dlp-overview?orgId=1&from=now-30d&to=now&timezone=browser".to_string(), aw_ui: "/r/aw/".to_string(), worktime_report: "/reports/worktime/management?format=html&host=SHARKON2025".to_string(), file1c_brief: "/r/file1c/brief".to_string(), diff --git a/adk-rust/crates/detmir-portal/src/static/app.js b/adk-rust/crates/detmir-portal/src/static/app.js index 24be661..7eeb7c8 100644 --- a/adk-rust/crates/detmir-portal/src/static/app.js +++ b/adk-rust/crates/detmir-portal/src/static/app.js @@ -73,6 +73,16 @@ function renderLinks(links) { `; } +function renderDlpLinks(links) { + const link = (text, href) => `${escapeHtml(text)}`; + return ``; +} + function renderSourceList(data) { const sources = [ ["DetMir", data.detmir_status], @@ -181,14 +191,36 @@ function renderIncidentsList(items) { `).join("")}`; } +function isDlpIncident(item) { + const text = `${item?.kind || ""} ${item?.source || ""} ${item?.summary || ""}`.toLowerCase(); + return text.includes("dlp") || text.includes("incident") || text.includes("case") || text.includes("иб"); +} + +function renderDlpIncidentsList(items) { + const dlpItems = (items || []).filter(isDlpIncident); + if (dlpItems.length === 0) return `

Активных DLP/ИБ-инцидентов нет.

`; + return renderIncidentsList(dlpItems); +} + function renderIncidents(data) { + const links = state.links || {}; return `

Инциденты ИБ

- ${renderIncidentsList(data)} +
+
+

DLP-инциденты

+ ${renderDlpIncidentsList(data)} +
+
+

Графики и дашборды

+ ${renderDlpLinks(links)} +
+
`; } async function refresh() { + if (!state.links) state.links = await loadJson("/links"); const summary = await loadJson("/summary"); renderSummary(summary); const content = document.getElementById("content");