feat(portal): localize management UI
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -54,10 +54,28 @@ function escapeHtml(value) {
|
||||
|
||||
function displayText(value) {
|
||||
return String(value ?? "")
|
||||
.replaceAll("Risk Narrative", "Главный вывод")
|
||||
.replaceAll("Risk Heatmap", "Карта рисков")
|
||||
.replaceAll("Business Risk", "Риски подразделений")
|
||||
.replaceAll("Security Correlation", "Связь рисков и активности")
|
||||
.replaceAll("Incident Candidates", "Требует проверки")
|
||||
.replaceAll("Incident Candidate", "Требует проверки")
|
||||
.replaceAll("Critical Candidates", "Срочно проверить")
|
||||
.replaceAll("Open Cases", "Активные расследования")
|
||||
.replaceAll("Agent Coverage", "Полнота данных")
|
||||
.replaceAll("Coverage SLA", "Полнота данных")
|
||||
.replaceAll("Agent Quality", "Качество данных")
|
||||
.replaceAll("Trust Score", "Уровень доверия")
|
||||
.replaceAll("Trust KPI", "Достоверность показателей")
|
||||
.replaceAll("Correlation Score", "Уровень взаимосвязи")
|
||||
.replaceAll("Forensics Readiness", "Готовность к расследованию")
|
||||
.replaceAll("Executive Dashboard", "Сводка руководителя")
|
||||
.replaceAll("Executive Summary", "Краткий вывод")
|
||||
.replaceAll("Cases", "Расследования")
|
||||
.replaceAll("Workforce", "Работа сотрудников")
|
||||
.replaceAll("workforce", "работа сотрудников")
|
||||
.replaceAll("Security", "Безопасность")
|
||||
.replaceAll("Forensics", "Расследования")
|
||||
.replaceAll("Forensics", "Материалы расследования")
|
||||
.replaceAll("UEBA риск", "Индекс риска")
|
||||
.replaceAll("UEBA", "оценка риска")
|
||||
.replaceAll("DLP/ИБ", "ИБ")
|
||||
@@ -88,6 +106,7 @@ function displayText(value) {
|
||||
.replaceAll("Management report", "Сводка руководителя")
|
||||
.replaceAll("Drill-down", "Разбор")
|
||||
.replaceAll("KPI", "показатель")
|
||||
.replaceAll("SLA", "полнота данных")
|
||||
.replaceAll("items", "записи")
|
||||
.replaceAll("medium risk", "средний риск")
|
||||
.replaceAll("low risk", "низкий риск")
|
||||
@@ -102,6 +121,11 @@ function displayText(value) {
|
||||
.replaceAll("dept:", "подразделение:")
|
||||
.replace(/\byes\b/g, "да")
|
||||
.replace(/\bno\b/g, "нет")
|
||||
.replace(/\bsource\b/g, "источник")
|
||||
.replace(/\bunknown\b/g, "неизвестно")
|
||||
.replace(/\baccepted\b/g, "принято")
|
||||
.replace(/\bopen_cases\b/g, "активные расследования")
|
||||
.replace(/\bcritical_candidates\b/g, "срочно проверить")
|
||||
.replaceAll("daily", "день")
|
||||
.replaceAll("weekly", "неделя")
|
||||
.replaceAll("monthly", "месяц")
|
||||
@@ -114,6 +138,10 @@ function ui(value) {
|
||||
return escapeHtml(displayText(value));
|
||||
}
|
||||
|
||||
function tooltip(value) {
|
||||
return `title="${ui(value)}"`;
|
||||
}
|
||||
|
||||
function renderSummary(summary, readiness) {
|
||||
const global = document.getElementById("globalStatus");
|
||||
global.className = `status-pill ${statusClass(summary.severity)}`;
|
||||
@@ -475,17 +503,17 @@ function renderExecutiveDashboard(report) {
|
||||
<section class="card executive-dashboard-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Сводка руководителя</h3>
|
||||
<p class="muted">Что происходит в организации прямо сейчас: доверие к KPI, покрытие, риски, дела и готовность расследований.</p>
|
||||
<h3 ${tooltip("Главная сводка для руководителя: что происходит в организации прямо сейчас.")}>Сводка руководителя</h3>
|
||||
<p class="muted">Что происходит в организации прямо сейчас: достоверность показателей, полнота данных, риски, активные расследования и готовность к расследованию.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(executiveDashboardStatus(dashboard))}">${ui(dashboard.forensics_readiness || "UNKNOWN")}</span>
|
||||
</div>
|
||||
<div class="quality-grid">
|
||||
<div><span class="muted">Trust KPI</span><strong>${ui(optionalPercent(dashboard.trust_kpi_score))}</strong></div>
|
||||
<div><span class="muted">Покрытие агентов</span><strong>${ui(optionalPercent(dashboard.agent_coverage_pct))}</strong></div>
|
||||
<div ${tooltip("Можно ли доверять рассчитанным показателям активности.")}><span class="muted">Достоверность показателей</span><strong>${ui(optionalPercent(dashboard.trust_kpi_score))}</strong></div>
|
||||
<div ${tooltip("Какая часть рабочих мест прислала свежие подтвержденные данные.")}><span class="muted">Полнота данных</span><strong>${ui(optionalPercent(dashboard.agent_coverage_pct))}</strong></div>
|
||||
<div><span class="muted">Высокий риск</span><strong>${ui(highRisk.length)}</strong></div>
|
||||
<div><span class="muted">Кандидаты</span><strong>${ui(candidates.length)}</strong></div>
|
||||
<div><span class="muted">Открытые дела</span><strong>${ui(dashboard.open_cases ?? 0)}</strong></div>
|
||||
<div ${tooltip("Сколько записей нужно срочно проверить вручную.")}><span class="muted">Срочно проверить</span><strong>${ui(candidates.length)}</strong></div>
|
||||
<div ${tooltip("Сколько расследований сейчас в работе.")}><span class="muted">Активные расследования</span><strong>${ui(dashboard.open_cases ?? 0)}</strong></div>
|
||||
<div><span class="muted">Закрыто за 30 дней</span><strong>${ui(dashboard.resolved_cases_30d ?? 0)}</strong></div>
|
||||
</div>
|
||||
<div class="list compact-list executive-summary-list">
|
||||
@@ -505,14 +533,14 @@ function renderRiskNarrative(report) {
|
||||
<section class="card risk-narrative-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Связанная картина риска</h3>
|
||||
<h3 ${tooltip("Главный управленческий вывод: что случилось, почему это риск и чем это подтверждается.")}>Главный вывод</h3>
|
||||
<p class="muted">Главный управленческий вывод: что происходит, почему это риск и какие слои это подтверждают.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(narrativeStatus)}">${ui(narrativeStatus)}</span>
|
||||
</div>
|
||||
<div class="list compact-list executive-summary-list">
|
||||
<div class="row compact-row"><strong>Главная причина риска</strong><span class="muted">${ui(summary.main_risk_cause || "связанный риск не выражен")}</span><span class="badge ${statusClass(narrativeStatus)}">${ui(narrativeStatus)}</span></div>
|
||||
<div class="row compact-row"><strong>Подтверждающие слои</strong><span class="muted">Trust KPI · Agent Coverage · Business Risk · Risk Heatmap · Security Correlation · Incident Candidates · Cases</span><span></span></div>
|
||||
<div class="row compact-row"><strong>Подтверждающие слои</strong><span class="muted">Достоверность показателей · Полнота данных · Риски подразделений · Карта рисков · Связь рисков и активности · Требует проверки · Расследования</span><span></span></div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
@@ -898,7 +926,7 @@ function renderDepartmentLeaderCard(report) {
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Карточка руководителя подразделения</h3>
|
||||
<p class="muted">Read-only срез для поручения разбора ответственному.</p>
|
||||
<p class="muted">Срез только для чтения, чтобы поручить разбор ответственному.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(row.status)}">${ui(row.risk)}</span>
|
||||
</div>
|
||||
@@ -1418,7 +1446,7 @@ function renderDlpEvidence(evidence) {
|
||||
<span class="muted">${escapeHtml(item.message || item.file_path || item.rule_id || item.event_id)}</span>
|
||||
<div class="muted small">${item.source_file ? "Файл: " + escapeHtml(item.source_file) + " · " : ""}${item.screenshot_sha256 ? "Контрольный хеш: " + escapeHtml(item.screenshot_sha256) : ui(item.blocked_reason || "без скрина")}</div>
|
||||
</div>
|
||||
<span class="badge ${item.screenshot_available ? "status-ok" : "status-warn"}">${item.screenshot_available ? "СКРИН" : "МЕТА"}</span>
|
||||
<span class="badge ${item.screenshot_available ? "status-ok" : "status-warn"}">${item.screenshot_available ? "скриншот" : "метаданные"}</span>
|
||||
<div class="actions">
|
||||
${item.preview_url ? `<a class="small-button" href="${escapeHtml(item.preview_url)}" target="_blank" rel="noopener noreferrer">Открыть</a>` : ""}
|
||||
${item.download_url ? `<a class="small-button" href="${escapeHtml(item.download_url)}" target="_blank" rel="noopener noreferrer">Скачать</a>` : ""}
|
||||
@@ -1440,7 +1468,7 @@ function buildAutoInvestigation(report) {
|
||||
const summary = risk.label || risk.code || risky?.reason || "Система сформировала риск-сигнал для ручной проверки";
|
||||
return {
|
||||
incident_id: `auto-${String(department).toLowerCase().replace(/[^a-zа-я0-9]+/gi, "-").replace(/^-|-$/g, "") || "risk"}`,
|
||||
risk_id: risk.code || risk.label || "workforce-ueba-risk",
|
||||
risk_id: risk.code || risk.label || "risk-check",
|
||||
department,
|
||||
owner,
|
||||
activity_index: risky?.activityText || "нет данных",
|
||||
@@ -1448,18 +1476,18 @@ function buildAutoInvestigation(report) {
|
||||
status,
|
||||
summary,
|
||||
why_it_is_risk: risky?.reason || risk.value || "риск может указывать на просадку активности, отклонение от нормы или событие безопасности",
|
||||
what_to_check: risky?.check || risk.recommendation || "проверить первичные события ActivityWatch, RDP/1C активность, процессы и сетевые сигналы",
|
||||
what_to_check: risky?.check || risk.recommendation || "проверить первичные события ActivityWatch, удаленный доступ, активность в 1С, процессы и сетевые сигналы",
|
||||
recommended_actions: [
|
||||
"назначить ответственного за ручную проверку",
|
||||
"сопоставить риск с журналами активности и бизнес-задачей",
|
||||
"зафиксировать вывод в отчете по инциденту"
|
||||
],
|
||||
evidence: [
|
||||
"RDP activity: проверяется по событиям рабочего времени",
|
||||
"process activity: проверяется по процессам и приложениям",
|
||||
"network activity: проверяется по сетевым сигналам",
|
||||
"proxy activity: подключается как внешний источник",
|
||||
"pfSense events: учитываются при наличии интеграционного слоя"
|
||||
"удаленные сеансы: проверяются по событиям рабочего времени",
|
||||
"процессы: проверяются по приложениям",
|
||||
"сеть: проверяется по сетевым сигналам",
|
||||
"внешний шлюз: подключается как дополнительный источник",
|
||||
"периметр: учитывается при наличии интеграционного слоя"
|
||||
],
|
||||
generated_at: generated
|
||||
};
|
||||
@@ -1477,20 +1505,20 @@ function renderAutoInvestigationCard(report) {
|
||||
<span class="badge ${statusClass(card.status)}">${ui(card.status)}</span>
|
||||
</div>
|
||||
<div class="investigation-grid">
|
||||
<div><span class="muted">incident_id</span><strong>${ui(card.incident_id)}</strong></div>
|
||||
<div><span class="muted">risk_id</span><strong>${ui(card.risk_id)}</strong></div>
|
||||
<div><span class="muted">department</span><strong>${ui(card.department)}</strong></div>
|
||||
<div><span class="muted">owner</span><strong>${ui(card.owner)}</strong></div>
|
||||
<div><span class="muted">activity_index</span><strong>${ui(card.activity_index)}</strong></div>
|
||||
<div><span class="muted">deviation</span><strong>${ui(card.deviation)}</strong></div>
|
||||
<div><span class="muted">generated_at</span><strong>${ui(card.generated_at)}</strong></div>
|
||||
<div><span class="muted">Номер</span><strong>${ui(card.incident_id)}</strong></div>
|
||||
<div><span class="muted">Риск</span><strong>${ui(card.risk_id)}</strong></div>
|
||||
<div><span class="muted">Подразделение</span><strong>${ui(card.department)}</strong></div>
|
||||
<div><span class="muted">Ответственный</span><strong>${ui(card.owner)}</strong></div>
|
||||
<div><span class="muted">Индекс активности</span><strong>${ui(card.activity_index)}</strong></div>
|
||||
<div><span class="muted">Отклонение</span><strong>${ui(card.deviation)}</strong></div>
|
||||
<div><span class="muted">Сформировано</span><strong>${ui(card.generated_at)}</strong></div>
|
||||
</div>
|
||||
<div class="list compact-list">
|
||||
<div class="row compact-row"><strong>summary</strong><span class="muted">${ui(card.summary)}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>why_it_is_risk</strong><span class="muted">${ui(card.why_it_is_risk)}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>what_to_check</strong><span class="muted">${ui(card.what_to_check)}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>recommended_actions</strong><span class="muted">${ui(card.recommended_actions.join("; "))}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>evidence</strong><span class="muted">${ui(card.evidence.join("; "))}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>Краткое описание</strong><span class="muted">${ui(card.summary)}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>Почему это риск</strong><span class="muted">${ui(card.why_it_is_risk)}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>Что проверить</strong><span class="muted">${ui(card.what_to_check)}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>Рекомендуемые действия</strong><span class="muted">${ui(card.recommended_actions.join("; "))}</span><span></span></div>
|
||||
<div class="row compact-row"><strong>Материалы расследования</strong><span class="muted">${ui(card.evidence.join("; "))}</span><span></span></div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
@@ -1664,8 +1692,8 @@ function fallbackAgentQualityExplain(quality) {
|
||||
if (source === "wts_api" && !hasError) {
|
||||
return {
|
||||
status: "OK",
|
||||
title: "Данные агента подтверждают KPI",
|
||||
summary: "Сессии собраны основным способом через Windows WTS API; индекс активности можно использовать как рабочий управленческий KPI.",
|
||||
title: "Данные агента подтверждают показатели",
|
||||
summary: "Сессии собраны основным способом Windows; индекс активности можно использовать как рабочий управленческий показатель.",
|
||||
recommendation: "Использовать отчет как подтвержденный оперативный срез.",
|
||||
kpi_accepted: true
|
||||
};
|
||||
@@ -1674,7 +1702,7 @@ function fallbackAgentQualityExplain(quality) {
|
||||
return {
|
||||
status: "DEGRADED",
|
||||
title: "Диагностический режим агента",
|
||||
summary: "Диагностический режим, данные не засчитываются в KPI.",
|
||||
summary: "Диагностический режим, данные не засчитываются в показатели активности.",
|
||||
recommendation: "Проверить доступность WTS API и права запуска агента.",
|
||||
kpi_accepted: false
|
||||
};
|
||||
@@ -1684,7 +1712,7 @@ function fallbackAgentQualityExplain(quality) {
|
||||
status: "DEGRADED",
|
||||
title: "Достоверность данных снижена",
|
||||
summary: `Коллектор передал ошибку: ${q.collector_error}`,
|
||||
recommendation: "Восстановить основной путь WTS API перед использованием отчета как доказательной базы.",
|
||||
recommendation: "Восстановить основной способ сбора Windows перед использованием отчета как доказательной базы.",
|
||||
kpi_accepted: false
|
||||
};
|
||||
}
|
||||
@@ -1708,17 +1736,17 @@ function renderAgentQuality(quality, explain) {
|
||||
<section class="card agent-quality-card" id="trust-kpi-section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Достоверность данных агента</h3>
|
||||
<h3 ${tooltip("Показывает, можно ли использовать данные агента для управленческих показателей.")}>Качество данных</h3>
|
||||
<p class="muted">${ui(e.title || "Оценка доверия к данным агента")}</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||
</div>
|
||||
<p class="quality-summary">${ui(e.summary || "")}</p>
|
||||
<div class="quality-decision">
|
||||
<div><span class="muted">Принято в KPI</span><strong>${accepted ? "да" : "нет"}</strong></div>
|
||||
<div><span class="muted">Участвует в показателях</span><strong>${accepted ? "да" : "нет"}</strong></div>
|
||||
<div><span class="muted">Источник</span><strong>${ui(source)}</strong></div>
|
||||
</div>
|
||||
${warn ? `<div class="quality-warning">Внимание. Данные активности собраны не основным способом. Точность определения активности и RDP-сессий может быть снижена.</div>` : ""}
|
||||
${warn ? `<div class="quality-warning">Внимание. Данные активности собраны не основным способом. Точность определения активности и удаленных сеансов может быть снижена.</div>` : ""}
|
||||
<p class="muted">${ui(e.recommendation || "")}</p>
|
||||
<details class="quality-details">
|
||||
<summary>Технические детали</summary>
|
||||
@@ -1726,7 +1754,7 @@ function renderAgentQuality(quality, explain) {
|
||||
<div><span class="muted">Источник коллектора</span><strong>${ui(source)}</strong></div>
|
||||
<div><span class="muted">Всего сессий</span><strong>${escapeHtml(q.sessions_collected_total ?? 0)}</strong></div>
|
||||
<div><span class="muted">Активных сессий</span><strong>${escapeHtml(q.active_sessions_total ?? 0)}</strong></div>
|
||||
<div><span class="muted">RDP-сессий</span><strong>${escapeHtml(q.rdp_sessions_total ?? 0)}</strong></div>
|
||||
<div><span class="muted">Удаленных сеансов</span><strong>${escapeHtml(q.rdp_sessions_total ?? 0)}</strong></div>
|
||||
<div><span class="muted">Ошибка коллектора</span><strong>${q.collector_error ? ui(q.collector_error) : "нет"}</strong></div>
|
||||
</div>
|
||||
</details>
|
||||
@@ -1744,22 +1772,22 @@ function renderAgentQualityHistory(history, summary) {
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Стабильность агента за 7 дней</h3>
|
||||
<p class="muted">Показывает, можно ли доверять недельному KPI, а не только текущему срезу.</p>
|
||||
<p class="muted">Показывает, можно ли доверять недельным показателям, а не только текущему срезу.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||
</div>
|
||||
<div class="quality-decision">
|
||||
<div><span class="muted">OK дней</span><strong>${escapeHtml(s.ok_days ?? 0)}</strong></div>
|
||||
<div><span class="muted">Проблемных дней</span><strong>${escapeHtml(unstableDays)}</strong></div>
|
||||
<div><span class="muted">KPI принят</span><strong>${escapeHtml(s.kpi_accepted_pct ?? 0)}%</strong></div>
|
||||
<div><span class="muted">Показатели подтверждены</span><strong>${escapeHtml(s.kpi_accepted_pct ?? 0)}%</strong></div>
|
||||
</div>
|
||||
${Number(s.ok_days || 0) < 5 ? `<div class="quality-warning">KPI требует валидации: нестабильный сбор данных агента.</div>` : ""}
|
||||
${Number(s.ok_days || 0) < 5 ? `<div class="quality-warning">Показатели требуют проверки: нестабильный сбор данных агента.</div>` : ""}
|
||||
<details class="quality-details">
|
||||
<summary>История по дням</summary>
|
||||
<div class="list compact-list">${items.length ? items.map(item => `
|
||||
<div class="row compact-row">
|
||||
<strong>${escapeHtml(item.date || "-")}</strong>
|
||||
<span class="muted">source=${ui(item.source || "unknown")} · KPI=${item.kpi_accepted ? "да" : "нет"}${item.collector_error ? ` · ${ui(item.collector_error)}` : ""}</span>
|
||||
<span class="muted">источник=${ui(item.source || "unknown")} · показатели=${item.kpi_accepted ? "да" : "нет"}${item.collector_error ? ` · ${ui(item.collector_error)}` : ""}</span>
|
||||
<span class="badge ${statusClass(item.status)}">${ui(item.status || "UNKNOWN")}</span>
|
||||
</div>
|
||||
`).join("") : `<div class="row compact-row"><strong>История</strong><span class="muted">История качества агента за период отсутствует.</span><span class="badge status-unknown">UNKNOWN</span></div>`}</div>
|
||||
@@ -1781,7 +1809,7 @@ function renderAgentQualityNodes(nodes, summary) {
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Качество данных по рабочим местам</h3>
|
||||
<p class="muted">Какие узлы подтверждают KPI, а какие снижают доверие к управленческой аналитике.</p>
|
||||
<p class="muted">Какие рабочие места подтверждают показатели, а какие снижают доверие к управленческой аналитике.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||
</div>
|
||||
@@ -1789,9 +1817,9 @@ function renderAgentQualityNodes(nodes, summary) {
|
||||
<div><span class="muted">Всего узлов</span><strong>${escapeHtml(s.total_nodes ?? 0)}</strong></div>
|
||||
<div><span class="muted">OK</span><strong>${escapeHtml(s.ok_nodes ?? 0)}</strong></div>
|
||||
<div><span class="muted">Проблемных</span><strong>${escapeHtml(Number(s.degraded_nodes || 0) + Number(s.unknown_nodes || 0))}</strong></div>
|
||||
<div><span class="muted">KPI принят</span><strong>${escapeHtml(s.accepted_kpi_nodes_pct ?? 0)}%</strong></div>
|
||||
<div><span class="muted">Показатели подтверждены</span><strong>${escapeHtml(s.accepted_kpi_nodes_pct ?? 0)}%</strong></div>
|
||||
</div>
|
||||
${Number(s.total_nodes || 0) > 0 && Number(s.accepted_kpi_nodes_pct || 0) < 80 ? `<div class="quality-warning">KPI требует проверки: менее 80% узлов дают подтвержденные данные.</div>` : ""}
|
||||
${Number(s.total_nodes || 0) > 0 && Number(s.accepted_kpi_nodes_pct || 0) < 80 ? `<div class="quality-warning">Показатели требуют проверки: менее 80% рабочих мест дают подтвержденные данные.</div>` : ""}
|
||||
<div class="table-scroll">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
@@ -1800,7 +1828,7 @@ function renderAgentQualityNodes(nodes, summary) {
|
||||
<th>Статус</th>
|
||||
<th>Источник</th>
|
||||
<th>Последняя телеметрия</th>
|
||||
<th>KPI</th>
|
||||
<th>Показатели</th>
|
||||
<th>Рекомендация</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1840,8 +1868,8 @@ function renderAgentCoverageSla(sla) {
|
||||
<section class="card agent-coverage-card" id="agent-coverage-section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Покрытие агентов</h3>
|
||||
<p class="muted">Показывает, насколько KPI репрезентативен по всему парку рабочих мест.</p>
|
||||
<h3 ${tooltip("Показывает, какая часть рабочих мест присылает свежие данные.")}>Полнота данных</h3>
|
||||
<p class="muted">Показывает, насколько показатели репрезентативны по всему парку рабочих мест.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||
</div>
|
||||
@@ -1853,8 +1881,8 @@ function renderAgentCoverageSla(sla) {
|
||||
<div><span class="muted">Покрытие</span><strong>${escapeHtml(s.coverage_pct ?? 0)}%</strong></div>
|
||||
<div><span class="muted">Свежесть</span><strong>${escapeHtml(s.freshness_pct ?? 0)}%</strong></div>
|
||||
</div>
|
||||
${status === "CRITICAL" ? `<div class="quality-warning">Покрытие агентов критически недостаточно: KPI не может считаться репрезентативным.</div>` : ""}
|
||||
${status === "WARNING" ? `<div class="quality-warning">KPI требует проверки: часть рабочих мест не присылает свежую телеметрию.</div>` : ""}
|
||||
${status === "CRITICAL" ? `<div class="quality-warning">Полнота данных критически недостаточна: показатели нельзя считать репрезентативными.</div>` : ""}
|
||||
${status === "WARNING" ? `<div class="quality-warning">Показатели требуют проверки: часть рабочих мест не присылает свежую телеметрию.</div>` : ""}
|
||||
${status === "UNKNOWN" ? `<p class="muted">Список ожидаемых рабочих мест не настроен.</p>` : ""}
|
||||
<div class="table-scroll">
|
||||
<table class="data-table">
|
||||
@@ -1904,7 +1932,7 @@ function renderBusinessRisk(items) {
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Риски подразделений</h3>
|
||||
<p class="muted">Организационные зоны риска по доверию к KPI, активности, тренду и проблемным узлам.</p>
|
||||
<p class="muted">Организационные зоны риска по достоверности показателей, активности, тренду и проблемным рабочим местам.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(worst)}">${ui(worst)}</span>
|
||||
</div>
|
||||
@@ -1948,8 +1976,8 @@ function renderRiskHeatmap(items) {
|
||||
<section class="card risk-heatmap-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Карта рисков подразделений</h3>
|
||||
<p class="muted">Где одновременно слабый Trust KPI, низкая активность, плохое покрытие агентов, кандидаты и открытые дела.</p>
|
||||
<h3 ${tooltip("Таблица показывает подразделения, где одновременно есть несколько признаков риска.")}>Карта рисков</h3>
|
||||
<p class="muted">Где одновременно снижена достоверность показателей, низкая активность, неполные данные, записи на проверку и активные расследования.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(worst)}">${ui(worst)}</span>
|
||||
</div>
|
||||
@@ -1958,11 +1986,11 @@ function renderRiskHeatmap(items) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Подразделение</th>
|
||||
<th>Trust KPI</th>
|
||||
<th>Достоверность</th>
|
||||
<th>Активность</th>
|
||||
<th>Покрытие</th>
|
||||
<th>Риск</th>
|
||||
<th>Дела</th>
|
||||
<th>Расследования</th>
|
||||
<th>Связи</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1975,7 +2003,7 @@ function renderRiskHeatmap(items) {
|
||||
<td>${ui(riskPercentText(item.agent_coverage_pct))}</td>
|
||||
<td>
|
||||
<span class="badge ${statusClass(item.heat_level)}">${ui(item.heat_level || "UNKNOWN")}</span><br>
|
||||
<span class="muted small">${ui(item.business_risk_level || "UNKNOWN")} · кандидаты ${ui(item.critical_candidates ?? 0)}</span>
|
||||
<span class="muted small">${ui(item.business_risk_level || "UNKNOWN")} · проверить ${ui(item.critical_candidates ?? 0)}</span>
|
||||
</td>
|
||||
<td>${ui(item.open_cases ?? 0)}</td>
|
||||
<td>${renderRiskLayerLinks(item.links)}</td>
|
||||
@@ -2031,8 +2059,8 @@ function renderSecurityCorrelation(items) {
|
||||
<section class="card security-correlation-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Корреляция Workforce и Security</h3>
|
||||
<p class="muted">Связь между падением активности, доверием к KPI, кандидатами в инциденты и открытыми делами. Инциденты автоматически не создаются.</p>
|
||||
<h3 ${tooltip("Показывает, где проблемы активности совпадают с рисками проверки.")}>Связь рисков и активности</h3>
|
||||
<p class="muted">Связь между падением активности, достоверностью показателей, записями на проверку и активными расследованиями. Инциденты автоматически не создаются.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||
</div>
|
||||
@@ -2041,11 +2069,11 @@ function renderSecurityCorrelation(items) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Подразделение</th>
|
||||
<th>Trust KPI</th>
|
||||
<th>Достоверность</th>
|
||||
<th>Активность</th>
|
||||
<th>Бизнес-риск</th>
|
||||
<th>Security</th>
|
||||
<th>Корреляция</th>
|
||||
<th>Риск подразделения</th>
|
||||
<th>Проверки</th>
|
||||
<th>Взаимосвязь</th>
|
||||
<th>Причина</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -2056,7 +2084,7 @@ function renderSecurityCorrelation(items) {
|
||||
<td>${ui(riskPercentText(item.trust_kpi_score))}</td>
|
||||
<td>${ui(riskPercentText(item.activity_score))}</td>
|
||||
<td><span class="badge ${statusClass(item.business_risk_level)}">${ui(item.business_risk_level || "UNKNOWN")}</span></td>
|
||||
<td>кандидаты ${ui(item.critical_candidates ?? 0)} · дела ${ui(item.open_cases ?? 0)}</td>
|
||||
<td>проверить ${ui(item.critical_candidates ?? 0)} · расследования ${ui(item.open_cases ?? 0)}</td>
|
||||
<td><strong>${ui(Number(item.correlation_score || 0))}/100</strong></td>
|
||||
<td>${ui(item.explanation || item.correlation_reason || "связь не выражена")}</td>
|
||||
</tr>
|
||||
@@ -2066,7 +2094,7 @@ function renderSecurityCorrelation(items) {
|
||||
<td>UNKNOWN</td>
|
||||
<td>UNKNOWN</td>
|
||||
<td><span class="badge status-unknown">UNKNOWN</span></td>
|
||||
<td>кандидаты 0 · дела 0</td>
|
||||
<td>проверить 0 · расследования 0</td>
|
||||
<td>0/100</td>
|
||||
<td>недостаточно данных по подразделениям</td>
|
||||
</tr>
|
||||
@@ -2082,7 +2110,7 @@ function businessRiskReasons(item) {
|
||||
const reasons = Array.isArray(item?.reasons) && item.reasons.length
|
||||
? item.reasons.join("; ")
|
||||
: "существенных причин не найдено";
|
||||
return `${reasons}. Trust ${item?.trust_score ?? 0}%, активность ${item?.activity_score ?? 0}%, тренд ${businessTrendText(item?.trend)}, проблемных узлов ${item?.problem_nodes_count ?? 0}`;
|
||||
return `${reasons}. Доверие ${item?.trust_score ?? 0}%, активность ${item?.activity_score ?? 0}%, тренд ${businessTrendText(item?.trend)}, проблемных рабочих мест ${item?.problem_nodes_count ?? 0}`;
|
||||
}
|
||||
|
||||
function renderBusinessRiskTimeline(history, summary) {
|
||||
@@ -2102,7 +2130,7 @@ function renderBusinessRiskTimeline(history, summary) {
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Динамика рисков</h3>
|
||||
<p class="muted">Как менялся бизнес-риск подразделений по накопленной daily history.</p>
|
||||
<p class="muted">Как менялись риски подразделений по накопленной ежедневной истории.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||
</div>
|
||||
@@ -2155,8 +2183,8 @@ function renderRiskIncidentCandidates(items) {
|
||||
<section class="card risk-candidates-card" id="risk-candidates-section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>Кандидаты в инциденты</h3>
|
||||
<p class="muted">Read-only очередь ручной проверки. Реальные инциденты автоматически не создаются.</p>
|
||||
<h3 ${tooltip("Очередь ситуаций, которые нужно проверить вручную перед созданием расследования.")}>Требует проверки</h3>
|
||||
<p class="muted">Очередь ручной проверки. Реальные инциденты автоматически не создаются.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(worst)}">${ui(worst)}</span>
|
||||
</div>
|
||||
@@ -2164,7 +2192,7 @@ function renderRiskIncidentCandidates(items) {
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Номер</th>
|
||||
<th>Подразделение</th>
|
||||
<th>Узел</th>
|
||||
<th>Риск</th>
|
||||
@@ -2201,7 +2229,7 @@ function renderRiskIncidentCandidates(items) {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${rows.length ? `<p class="muted small">Показаны кандидаты для проверки, а не автоматически подтвержденные инциденты.</p>` : ""}
|
||||
${rows.length ? `<p class="muted small">Показаны записи для проверки, а не автоматически подтвержденные инциденты.</p>` : ""}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
@@ -2269,7 +2297,7 @@ function reviewStatusText(status) {
|
||||
|
||||
function candidateReasonText(item) {
|
||||
const evidence = Array.isArray(item?.evidence) && item.evidence.length
|
||||
? ` Evidence: ${item.evidence.join("; ")}`
|
||||
? ` Материалы: ${item.evidence.join("; ")}`
|
||||
: "";
|
||||
return `${item?.reason || "требуется проверка"}.${evidence}`;
|
||||
}
|
||||
@@ -2362,7 +2390,7 @@ function settingRows(report) {
|
||||
["Рабочий день", `${workdayHours} ч`, `роль: ${policy.role_label || activeRole.label || policy.default_role || "default"}`],
|
||||
["Порог WARN", ">= 15 баллов", "любой ненормальный риск попадает в очередь проверки"],
|
||||
["Порог FAIL", ">= 70 баллов", "высокий риск требует приоритетного разбора"],
|
||||
["Источник правил", policy.configured ? basename(policy.path) : "встроенные правила", risk.policy_configured ? `UEBA policy: ${basename(risk.policy_path)}` : "UEBA policy: встроенная модель"],
|
||||
["Источник правил", policy.configured ? basename(policy.path) : "встроенные правила", risk.policy_configured ? `правила оценки риска: ${basename(risk.policy_path)}` : "правила оценки риска: встроенная модель"],
|
||||
["Дата последнего пересчета", report?.generated_at_utc || "-", `версия политики: ${risk.policy_version || "ueba-rule-v1"}`],
|
||||
];
|
||||
}
|
||||
@@ -2373,7 +2401,7 @@ function renderSettings(report) {
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="section-title">Настройки</h2>
|
||||
<p class="muted">Read-only параметры расчета и границы интерпретации данных.</p>
|
||||
<p class="muted">Параметры расчета и границы интерпретации данных. Экран открыт только для чтения.</p>
|
||||
</div>
|
||||
<span class="badge status-ok">только чтение</span>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<aside class="sidebar" aria-label="Основная навигация">
|
||||
<div class="sidebar-brand">
|
||||
<strong>AWatch-rus</strong>
|
||||
<span>Workforce-first</span>
|
||||
<span>Активность и риски</span>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<button class="tab is-active" data-tab="operator">Обзор</button>
|
||||
@@ -30,7 +30,7 @@
|
||||
<div class="brand">
|
||||
<p class="eyebrow">AWatch-rus</p>
|
||||
<h1>Пульс организации</h1>
|
||||
<p class="lead">Workforce, UEBA и события безопасности за выбранный период</p>
|
||||
<p class="lead">Активность сотрудников, риски подразделений и материалы расследований за выбранный период</p>
|
||||
</div>
|
||||
<div class="topbar-side" aria-label="Параметры отчета">
|
||||
<div class="control-strip">
|
||||
|
||||
+60
-60
@@ -5,24 +5,24 @@
|
||||
Портал является рабочим кабинетом для трех ролей:
|
||||
|
||||
- руководитель: пульс организации, активность, подразделения, риски;
|
||||
- ИБ: риск-сигналы, события безопасности, evidence;
|
||||
- ИБ: риск-сигналы, события безопасности, материалы проверки;
|
||||
- расследователь: карточка инцидента, причины, проверяемые источники, отчет.
|
||||
|
||||
## Главные экраны
|
||||
|
||||
- `Обзор` - Executive Dashboard.
|
||||
- `Обзор` - сводка руководителя и главный вывод.
|
||||
- `Сотрудники` - карточки сотрудников и объяснение индекса.
|
||||
- `Подразделения` - сравнение групп, тренды и ответственные.
|
||||
- `Риски` - read-only контроль безопасности.
|
||||
- `Расследования` - инциденты, evidence и автоматическая read-only карточка расследования.
|
||||
- `Сетевой периметр` - read-only контекст внешних сетевых сигналов.
|
||||
- `Отчеты` - Markdown/PDF/JSON и управленческий текст.
|
||||
- `Настройки` - read-only параметры расчета.
|
||||
- `Риски` - контроль безопасности только для чтения.
|
||||
- `Расследования` - инциденты, материалы проверки и автоматическая карточка расследования только для чтения.
|
||||
- `Сетевой периметр` - внешний сетевой контекст только для чтения.
|
||||
- `Отчеты` - текстовый отчет, печать/PDF, выгрузка данных и управленческий текст.
|
||||
- `Настройки` - параметры расчета только для чтения.
|
||||
|
||||
## Загрузка и актуальность данных
|
||||
|
||||
В верхней части портала есть глобальная полоса `Data Loading & Refresh
|
||||
Status`. Она показывает, что сейчас происходит с данными, и не смешивает
|
||||
В верхней части портала есть глобальная полоса `Загрузка и актуальность
|
||||
данных`. Она показывает, что сейчас происходит с данными, и не смешивает
|
||||
состояния `нет данных` и `данные еще загружаются`.
|
||||
|
||||
Статусы:
|
||||
@@ -35,13 +35,13 @@ Status`. Она показывает, что сейчас происходит
|
||||
завершилось;
|
||||
- `ERROR` - первичная загрузка не удалась, актуальных данных на экране нет.
|
||||
|
||||
Во время `LOADING` портал показывает skeleton placeholders и текст `Данные
|
||||
Во время `LOADING` портал показывает заготовки карточек и текст `Данные
|
||||
загружаются`; пустые таблицы и сообщения `Нет данных` не выводятся до
|
||||
завершения загрузки. После успешного обновления отображается время последнего
|
||||
обновления. При `STALE` старый экран остается доступным, но сверху появляется
|
||||
предупреждение о неудачном обновлении.
|
||||
|
||||
## Executive Dashboard
|
||||
## Сводка руководителя
|
||||
|
||||
Показывает:
|
||||
|
||||
@@ -50,16 +50,16 @@ Status`. Она показывает, что сейчас происходит
|
||||
- WARN/FAIL подразделения;
|
||||
- критические риски;
|
||||
- недельный тренд;
|
||||
- достоверность данных агента;
|
||||
- качество данных;
|
||||
- топ-5 лучших и проблемных подразделений;
|
||||
- Heat Map;
|
||||
- карту рисков;
|
||||
- блок `Требует внимания`.
|
||||
|
||||
## Достоверность данных агента
|
||||
## Качество данных
|
||||
|
||||
Портал показывает карточку `Достоверность данных агента` в `Обзор` и
|
||||
Портал показывает карточку `Качество данных` в `Обзор` и
|
||||
`Отчеты`. Это управленческий вывод о том, можно ли использовать текущий
|
||||
`Индекс активности` и отчеты как подтвержденный KPI.
|
||||
`Индекс активности` и отчеты как подтвержденные показатели.
|
||||
|
||||
API сохраняет старое поле `agent_quality` и дополнительно отдает
|
||||
`agent_quality_explain`:
|
||||
@@ -68,32 +68,32 @@ API сохраняет старое поле `agent_quality` и дополнит
|
||||
- `title`;
|
||||
- `summary`;
|
||||
- `recommendation`;
|
||||
- `kpi_accepted`.
|
||||
- `kpi_accepted` - участвуют ли данные в показателях.
|
||||
|
||||
Цвета статусов:
|
||||
|
||||
- `OK` - зеленый, KPI подтвержден основным источником;
|
||||
- `OK` - зеленый, показатели подтверждены основным источником;
|
||||
- `WARNING` - желтый, данные собраны резервным способом;
|
||||
- `DEGRADED` - оранжевый, данные нельзя использовать как доказательный KPI;
|
||||
- `UNKNOWN` - серый, агент не передал diagnostics.
|
||||
- `DEGRADED` - оранжевый, данные нельзя использовать как доказательные показатели;
|
||||
- `UNKNOWN` - серый, агент не передал диагностику качества.
|
||||
|
||||
Карточка сначала показывает управленческий вывод, принято ли значение в KPI, и
|
||||
Карточка сначала показывает управленческий вывод, участвуют ли данные в показателях, и
|
||||
рекомендацию. Технические поля доступны в раскрываемом блоке:
|
||||
|
||||
- источник коллектора;
|
||||
- всего сессий;
|
||||
- активных сессий;
|
||||
- RDP-сессий;
|
||||
- удаленных сеансов;
|
||||
- ошибка коллектора, если она есть.
|
||||
|
||||
Если diagnostics отсутствует, статус `UNKNOWN`, рекомендация - обновить Rust
|
||||
Если диагностика отсутствует, статус `UNKNOWN`, рекомендация - обновить Rust
|
||||
agent и проверить поступление telemetry JSONL. Если источник `local_fallback`,
|
||||
портал прямо пишет: `Диагностический режим, данные не засчитываются в KPI`.
|
||||
портал прямо пишет: `Диагностический режим, данные не засчитываются в показатели активности`.
|
||||
|
||||
## Стабильность агента за 7 дней
|
||||
|
||||
Портал показывает мини-блок `Стабильность агента за 7 дней`. Он отвечает на
|
||||
вопрос, можно ли доверять недельному KPI, а не только текущему срезу.
|
||||
вопрос, можно ли доверять недельным показателям, а не только текущему срезу.
|
||||
|
||||
`GET /api/reports` отдает:
|
||||
|
||||
@@ -113,16 +113,16 @@ agent и проверить поступление telemetry JSONL. Если и
|
||||
|
||||
- сколько дней были `OK`;
|
||||
- сколько дней были `WARNING`, `DEGRADED` или `UNKNOWN`;
|
||||
- процент дней, когда KPI был принят.
|
||||
- процент дней, когда показатели были подтверждены.
|
||||
|
||||
Если за 7 дней меньше 5 дней `OK`, портал и executive summary показывают:
|
||||
`KPI требует валидации: нестабильный сбор данных агента`.
|
||||
Если за 7 дней меньше 5 дней `OK`, портал и краткий вывод показывают:
|
||||
`Показатели требуют проверки: нестабильный сбор данных агента`.
|
||||
|
||||
## Качество данных по рабочим местам
|
||||
|
||||
Портал показывает карточку `Качество данных по рабочим местам`. Она отвечает
|
||||
на управленческий вопрос: какие узлы дают подтвержденные данные, а какие
|
||||
снижают доверие к KPI.
|
||||
снижают доверие к показателям.
|
||||
|
||||
`GET /api/reports` отдает:
|
||||
|
||||
@@ -150,15 +150,15 @@ agent и проверить поступление telemetry JSONL. Если и
|
||||
- `unknown_nodes`;
|
||||
- `accepted_kpi_nodes_pct`.
|
||||
|
||||
Если `accepted_kpi_nodes_pct < 80` и узлы есть, executive summary показывает:
|
||||
`KPI требует проверки: менее 80% узлов дают подтвержденные данные`.
|
||||
Если `accepted_kpi_nodes_pct < 80` и узлы есть, краткий вывод показывает:
|
||||
`Показатели требуют проверки: менее 80% рабочих мест дают подтвержденные данные`.
|
||||
|
||||
Главная страница не перегружается: показывается сводка и таблица максимум из
|
||||
10 проблемных узлов. Источник `local_fallback` никогда не подтверждает KPI.
|
||||
10 проблемных узлов. Источник `local_fallback` никогда не подтверждает показатели.
|
||||
|
||||
## SLA покрытия агентов
|
||||
## Полнота данных
|
||||
|
||||
Портал показывает карточку `Покрытие агентов`. Она отвечает на вопрос,
|
||||
Портал показывает карточку `Полнота данных`. Она отвечает на вопрос,
|
||||
насколько текущий `Индекс активности` репрезентативен по всему парку рабочих
|
||||
мест.
|
||||
|
||||
@@ -187,51 +187,51 @@ agent и проверить поступление telemetry JSONL. Если и
|
||||
|
||||
- `expected_nodes` - сколько рабочих мест ожидается по конфигу;
|
||||
- `reporting_nodes_24h` - сколько узлов прислали свежую за 24 часа
|
||||
телеметрию, принятую в KPI;
|
||||
телеметрию, принятую в показатели;
|
||||
- `stale_nodes` - узлы есть в telemetry, но последняя запись старше 24 часов;
|
||||
- `missing_nodes` - узлы из expected list не найдены в telemetry;
|
||||
- `coverage_pct` - процент узлов, подтверждающих KPI;
|
||||
- `coverage_pct` - процент узлов, подтверждающих показатели;
|
||||
- `freshness_pct` - процент узлов со свежей telemetry независимо от качества
|
||||
источника;
|
||||
- `sla_status` - `OK`, `WARNING`, `CRITICAL` или `UNKNOWN`;
|
||||
- `sla_status` - статус полноты данных: `OK`, `WARNING`, `CRITICAL` или `UNKNOWN`;
|
||||
- `problem_nodes` - максимум выводится в UI по 10 проблемным узлам.
|
||||
|
||||
Правила SLA:
|
||||
Правила полноты данных:
|
||||
|
||||
- `coverage_pct >= 90%` - `OK`;
|
||||
- `75-89%` - `WARNING`;
|
||||
- `<75%` - `CRITICAL`;
|
||||
- если expected list отсутствует или пустой - `UNKNOWN`.
|
||||
|
||||
Если статус `CRITICAL`, executive summary показывает:
|
||||
`Покрытие агентов критически недостаточно: KPI не может считаться репрезентативным`.
|
||||
Если статус `CRITICAL`, краткий вывод показывает:
|
||||
`Полнота данных критически недостаточна: показатели нельзя считать репрезентативными`.
|
||||
|
||||
Если статус `WARNING`, executive summary показывает:
|
||||
`KPI требует проверки: часть рабочих мест не присылает свежую телеметрию`.
|
||||
Если статус `WARNING`, краткий вывод показывает:
|
||||
`Показатели требуют проверки: часть рабочих мест не присылает свежую телеметрию`.
|
||||
|
||||
Источник `local_fallback` может повышать `freshness_pct`, если телеметрия
|
||||
свежая, но не повышает `coverage_pct` и не засчитывается как подтвержденный
|
||||
KPI.
|
||||
свежая, но не повышает `coverage_pct` и не засчитывается как подтвержденные
|
||||
показатели.
|
||||
|
||||
## Risk -> Investigation
|
||||
## Переход от риска к расследованию
|
||||
|
||||
Кнопка `Открыть расследование` переводит пользователя в read-only карточку.
|
||||
Кнопка `Открыть расследование` переводит пользователя в карточку только для чтения.
|
||||
|
||||
Карточка содержит:
|
||||
|
||||
- `incident_id`;
|
||||
- `risk_id`;
|
||||
- `department`;
|
||||
- `owner`;
|
||||
- `activity_index`;
|
||||
- `deviation`;
|
||||
- `status`;
|
||||
- `summary`;
|
||||
- `why_it_is_risk`;
|
||||
- `what_to_check`;
|
||||
- `recommended_actions`;
|
||||
- `evidence`;
|
||||
- `generated_at`.
|
||||
- номер;
|
||||
- риск;
|
||||
- подразделение;
|
||||
- ответственного;
|
||||
- индекс активности;
|
||||
- отклонение;
|
||||
- статус;
|
||||
- краткое описание;
|
||||
- почему это риск;
|
||||
- что проверить;
|
||||
- рекомендуемые действия;
|
||||
- материалы расследования;
|
||||
- дату формирования.
|
||||
|
||||
Предупреждение обязательно:
|
||||
|
||||
@@ -247,4 +247,4 @@ Smoke-тест:
|
||||
node scripts/detmir-portal-tabs-smoke.mjs
|
||||
```
|
||||
|
||||
Тест проверяет все вкладки, read-only настройки и переход Risk -> Investigation.
|
||||
Тест проверяет все вкладки, настройки только для чтения и переход от риска к расследованию.
|
||||
|
||||
@@ -136,20 +136,20 @@ async function main() {
|
||||
);
|
||||
const readyBodyText = await page.locator("#content").innerText({ timeout });
|
||||
const requiredExecutive = [
|
||||
"Связанная картина риска",
|
||||
"Главный вывод",
|
||||
"Сотрудников в работе",
|
||||
"Средний индекс активности",
|
||||
"Главная причина риска",
|
||||
"Подтверждающие слои",
|
||||
"Достоверность данных агента",
|
||||
"Качество данных",
|
||||
"Стабильность агента за 7 дней",
|
||||
"Качество данных по рабочим местам",
|
||||
"Покрытие агентов",
|
||||
"Карта рисков подразделений",
|
||||
"Корреляция Workforce и Security",
|
||||
"Полнота данных",
|
||||
"Карта рисков",
|
||||
"Связь рисков и активности",
|
||||
"Риски подразделений",
|
||||
"Динамика рисков",
|
||||
"Кандидаты в инциденты",
|
||||
"Требует проверки",
|
||||
"ТОП-5 лучших подразделений",
|
||||
"ТОП-5 проблемных подразделений",
|
||||
"Требует внимания",
|
||||
@@ -161,11 +161,14 @@ async function main() {
|
||||
ok: requiredExecutive.every((marker) => containsText(readyBodyText, marker)),
|
||||
required: requiredExecutive,
|
||||
});
|
||||
const riskNarrativeIndex = readyBodyText.indexOf("Связанная картина риска");
|
||||
const executiveIndex = readyBodyText.indexOf("Сводка руководителя");
|
||||
const trustIndex = readyBodyText.indexOf("Достоверность данных агента");
|
||||
const businessRiskIndex = readyBodyText.indexOf("Риски подразделений");
|
||||
const candidatesIndex = readyBodyText.indexOf("Кандидаты в инциденты");
|
||||
const cardHeadings = await page.$$eval("#content section.card h3, #content h3.section-title", (nodes) =>
|
||||
nodes.map((node) => node.textContent.trim()),
|
||||
);
|
||||
const riskNarrativeIndex = cardHeadings.indexOf("Главный вывод");
|
||||
const executiveIndex = cardHeadings.indexOf("Сводка руководителя");
|
||||
const trustIndex = cardHeadings.indexOf("Качество данных");
|
||||
const businessRiskIndex = cardHeadings.indexOf("Риски подразделений");
|
||||
const candidatesIndex = cardHeadings.indexOf("Требует проверки");
|
||||
checks.push({
|
||||
name: "management_block_order",
|
||||
ok:
|
||||
@@ -175,11 +178,11 @@ async function main() {
|
||||
businessRiskIndex > trustIndex &&
|
||||
candidatesIndex > businessRiskIndex,
|
||||
order: [
|
||||
"Связанная картина риска",
|
||||
"Главный вывод",
|
||||
"Сводка руководителя",
|
||||
"Достоверность данных агента",
|
||||
"Качество данных",
|
||||
"Риски подразделений",
|
||||
"Кандидаты в инциденты",
|
||||
"Требует проверки",
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -219,9 +222,9 @@ async function main() {
|
||||
name: "risk_open_investigation_readonly_navigation",
|
||||
ok: activeTab.trim() === "Расследования"
|
||||
&& containsText(incidentText, "Расследование сформировано автоматически")
|
||||
&& containsText(incidentText, "incident_id")
|
||||
&& containsText(incidentText, "risk_id")
|
||||
&& containsText(incidentText, "evidence"),
|
||||
&& containsText(incidentText, "Номер")
|
||||
&& containsText(incidentText, "Риск")
|
||||
&& containsText(incidentText, "Материалы расследования"),
|
||||
buttons: investigationButtons,
|
||||
});
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user