feat(portal): add executive action center
This commit is contained in:
@@ -988,6 +988,83 @@ h1 {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.action-center-card {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.security-actions-card {
|
||||
border-left: 4px solid #7c2d12;
|
||||
}
|
||||
|
||||
.action-center-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-height: 196px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--soft);
|
||||
}
|
||||
|
||||
.action-item-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-item-head strong,
|
||||
.action-item p,
|
||||
.action-item li {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.action-item p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.action-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.action-meta span {
|
||||
padding: 8px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.action-reasons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.action-reasons span {
|
||||
padding: 4px 7px;
|
||||
border-radius: 999px;
|
||||
background: var(--panel);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.action-item ul {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.risk-narrative-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -1303,6 +1380,8 @@ pre {
|
||||
.executive-grid { grid-template-columns: 1fr; }
|
||||
.risk-narrative-grid { grid-template-columns: 1fr; }
|
||||
.risk-narrative-columns { grid-template-columns: 1fr; }
|
||||
.action-center-list { grid-template-columns: 1fr; }
|
||||
.action-meta { grid-template-columns: 1fr; }
|
||||
.analytics-grid { grid-template-columns: 1fr; }
|
||||
.incident-row { grid-template-columns: 1fr; }
|
||||
.evidence-row { grid-template-columns: 1fr; }
|
||||
|
||||
@@ -738,6 +738,64 @@ function renderRiskNarrative(report) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderActionCenter(actions, options = {}) {
|
||||
const items = Array.isArray(actions) ? actions.slice(0, 6) : [];
|
||||
if (!items.length) {
|
||||
return `
|
||||
<section class="card action-center-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3>${ui(options.title || "Рекомендуемые действия")}</h3>
|
||||
<p class="muted">Критичных действий по текущему срезу не требуется.</p>
|
||||
</div>
|
||||
<span class="badge status-ok">low</span>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<section class="card action-center-card ${options.security ? "security-actions-card" : ""}">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h3 ${tooltip("Rule-based список действий: что сделать, кому адресовано, срок и почему система предлагает это действие.")}>${ui(options.title || "Рекомендуемые действия")}</h3>
|
||||
<p class="muted">Рекомендации сформированы по Workforce KPI, UEBA, покрытию данных, корреляции безопасности и кандидатам на проверку.</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass(items[0]?.priority)}">${ui(items[0]?.priority || "low")}</span>
|
||||
</div>
|
||||
<div class="action-center-list">
|
||||
${items.map(action => `
|
||||
<article class="action-item">
|
||||
<div class="action-item-head">
|
||||
<span class="badge ${statusClass(action.priority)}">${ui(action.priority || "low")}</span>
|
||||
<strong>${ui(action.title || "Действие не указано")}</strong>
|
||||
</div>
|
||||
<p>${ui(action.summary || "Описание действия не указано")}</p>
|
||||
<div class="action-meta">
|
||||
<span><strong>Срок</strong> ${ui(action.recommended_deadline || "72h")}</span>
|
||||
<span><strong>Адресат</strong> ${ui(actionOwnerLabel(action.owner_role))}</span>
|
||||
</div>
|
||||
<div class="action-reasons">
|
||||
${(Array.isArray(action.reason_codes) ? action.reason_codes : []).slice(0, 4).map(code => `<span>${ui(code)}</span>`).join("")}
|
||||
</div>
|
||||
<ul>${(Array.isArray(action.evidence) ? action.evidence : []).slice(0, 3).map(item => `<li>${ui(item)}</li>`).join("")}</ul>
|
||||
</article>
|
||||
`).join("")}
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function actionOwnerLabel(value) {
|
||||
const map = {
|
||||
executive: "Руководитель",
|
||||
manager: "Руководитель подразделения",
|
||||
security: "ИБ",
|
||||
forensics: "Расследования",
|
||||
admin: "Эксплуатация"
|
||||
};
|
||||
return map[value] || value || "Ответственный";
|
||||
}
|
||||
|
||||
function optionalPercent(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? `${Math.round(number)}%` : "нет данных";
|
||||
@@ -1289,6 +1347,7 @@ function renderOperatorRoleContent(mode, data, report, extras = {}) {
|
||||
function renderExecutiveView(report, incidents) {
|
||||
return `
|
||||
${renderRiskNarrative(report)}
|
||||
${renderActionCenter(report?.recommended_actions, { title: "Рекомендуемые действия" })}
|
||||
${renderExecutiveDashboard(report)}
|
||||
${renderKpiExplain(report?.workforce_kpi_explain)}
|
||||
${renderSecurityEventsSummary(report?.security_events_summary, { compact: true })}
|
||||
@@ -1301,6 +1360,7 @@ function renderExecutiveView(report, incidents) {
|
||||
function renderSecurityView(data, report, extras = {}) {
|
||||
const cases = Array.isArray(extras.cases?.cases) ? extras.cases.cases : [];
|
||||
return `
|
||||
${renderActionCenter(report?.recommended_actions, { title: "Рекомендуемые действия ИБ", security: true })}
|
||||
${renderSecurityEventsSummary(report?.security_events_summary)}
|
||||
${renderRiskIncidentCandidates(report?.risk_incident_candidates)}
|
||||
${renderSecurityCorrelation(report?.security_correlation)}
|
||||
@@ -2933,6 +2993,7 @@ function renderReports(data) {
|
||||
</div>
|
||||
${renderPeriodBanner(data)}
|
||||
${renderRiskNarrative(data)}
|
||||
${renderActionCenter(data.recommended_actions, { title: "Рекомендуемые действия" })}
|
||||
${renderExecutiveDashboard(data)}
|
||||
${renderReportTypeCards()}
|
||||
<div class="grid-2">
|
||||
|
||||
Reference in New Issue
Block a user