docs+portal: add pilot v1 demo pack

This commit is contained in:
igor04091968
2026-06-06 21:33:31 +03:00
parent b062237168
commit 9c72539401
15 changed files with 929 additions and 14 deletions
+23
View File
@@ -3441,6 +3441,7 @@ fn role_filtered_report(report: Value, role: PortalRole) -> Value {
"business_risk",
"business_risk_history_summary",
"risk_heatmap",
"security_events_summary",
"workforce",
"markdown",
] {
@@ -10037,6 +10038,27 @@ mod tests {
}
}
#[test]
fn portal_pilot_demo_navigation_is_present() {
for marker in [
"Pilot v1 demo",
"Executive demo",
"Manager demo",
"Security demo",
"Forensics demo",
"Admin demo",
"data-demo-view-mode=\"executive\"",
"data-demo-view-mode=\"security\"",
"data-demo-view-mode=\"forensics\"",
"data-demo-view-mode=\"admin\"",
] {
assert!(
INDEX_HTML.contains(marker),
"demo navigation missing {marker}"
);
}
}
#[test]
fn api_contract_artifacts_are_valid_and_future_ui_ready() {
let openapi: Value =
@@ -10134,6 +10156,7 @@ mod tests {
let executive = role_filtered_report(report.clone(), PortalRole::Executive);
assert!(executive.get("workforce").is_some());
assert!(executive.get("executive_dashboard").is_some());
assert!(executive.get("security_events_summary").is_some());
assert!(executive.get("risk_incident_candidates").is_none());
assert!(executive.get("security_correlation").is_none());
@@ -82,13 +82,17 @@ body.security-mode .small-button,
body.security-mode a.button,
body.security-mode .tab,
body.security-mode .nav-link,
body.security-mode .view-button,
body.security-mode .demo-button,
body.security-mode .control-strip select {
background: #111827;
color: var(--ink);
}
body.security-mode .tab.is-active,
body.security-mode .nav-link.is-active {
body.security-mode .nav-link.is-active,
body.security-mode .view-button.is-active,
body.security-mode .demo-button.is-active {
background: #0f2a3a;
color: var(--link);
}
@@ -145,14 +149,16 @@ h1 {
.control-strip,
.header-actions,
.view-switch {
.view-switch,
.demo-switch {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.view-switch {
.view-switch,
.demo-switch {
align-items: center;
padding: 4px;
border: 1px solid var(--line);
@@ -160,14 +166,16 @@ h1 {
background: var(--panel);
}
.view-switch > span {
.view-switch > span,
.demo-switch > span {
padding: 0 6px;
color: var(--muted);
font-size: 12px;
font-weight: 700;
}
.view-button {
.view-button,
.demo-button {
min-height: 30px;
border: 1px solid transparent;
border-radius: 6px;
@@ -180,18 +188,21 @@ h1 {
cursor: pointer;
}
.view-button.is-active {
.view-button.is-active,
.demo-button.is-active {
border-color: #1d4ed8;
background: #2563eb;
color: #ffffff;
}
.view-button:hover {
.view-button:hover,
.demo-button:hover {
color: var(--ink);
background: rgba(37, 99, 235, 0.08);
}
.view-button.is-active:hover {
.view-button.is-active:hover,
.demo-button.is-active:hover {
color: #ffffff;
background: #1d4ed8;
}
@@ -1210,10 +1221,13 @@ pre {
.topbar-side { justify-items: stretch; min-width: 0; width: 100%; }
.control-strip,
.header-actions,
.view-switch { justify-content: flex-start; }
.view-switch,
.demo-switch { justify-content: flex-start; }
.control-strip label { min-width: 100%; }
.view-switch > span { flex-basis: 100%; }
.view-button { flex: 1 1 120px; }
.view-switch > span,
.demo-switch > span { flex-basis: 100%; }
.view-button,
.demo-button { flex: 1 1 120px; }
h1 { font-size: 24px; }
.page-head { flex-direction: column; }
.period-banner { flex-direction: column; align-items: flex-start; }
@@ -248,6 +248,9 @@ function updateViewModeButtons() {
document.querySelectorAll("[data-view-mode]").forEach(button => {
button.classList.toggle("is-active", button.dataset.viewMode === currentViewMode());
});
document.querySelectorAll("[data-demo-view-mode]").forEach(button => {
button.classList.toggle("is-active", button.dataset.demoViewMode === currentViewMode());
});
}
function setViewMode(mode) {
@@ -2298,9 +2301,7 @@ function renderSecurityEventsSummary(summary, options = {}) {
const title = options.compact
? "События безопасности"
: "События безопасности за 24 часа";
const compactStateText = disabled && options.compact
? "Детализация скрыта для роли"
: stateText;
const compactStateText = stateText;
const compactModeText = disabled && options.compact
? "Без ИБ-детализации"
: disabled
@@ -3052,6 +3053,10 @@ document.querySelectorAll("[data-view-mode]").forEach(btn => {
btn.addEventListener("click", () => setViewMode(btn.dataset.viewMode));
});
document.querySelectorAll("[data-demo-view-mode]").forEach(btn => {
btn.addEventListener("click", () => setViewMode(btn.dataset.demoViewMode));
});
document.getElementById("periodFilter")?.addEventListener("change", event => {
state.period = periodFromSelectValue(event.target.value);
refresh({ stage: "Получение данных за выбранный период" });
@@ -58,6 +58,14 @@
<button class="view-button" data-view-mode="forensics" type="button">Расследования</button>
<button class="view-button" data-view-mode="admin" type="button">Админ</button>
</div>
<div class="demo-switch" aria-label="Pilot v1 demo navigation">
<span>Pilot v1 demo</span>
<button class="demo-button is-active" data-demo-view-mode="executive" type="button">Executive demo</button>
<button class="demo-button" data-demo-view-mode="manager" type="button">Manager demo</button>
<button class="demo-button" data-demo-view-mode="security" type="button">Security demo</button>
<button class="demo-button" data-demo-view-mode="forensics" type="button">Forensics demo</button>
<button class="demo-button" data-demo-view-mode="admin" type="button">Admin demo</button>
</div>
<div class="header-actions">
<button class="small-button primary" data-open-reports="true">Сформировать отчет</button>
<button class="small-button" data-export-markdown="true">Текстовый отчет</button>