feat(agent): expose data quality in portal

This commit is contained in:
igor04091968
2026-06-04 11:49:14 +03:00
parent d7616fd9a9
commit 95e7c0f314
7 changed files with 362 additions and 5 deletions
@@ -9,6 +9,8 @@
--ok-bg: #dcfce7;
--warn: #f97316;
--warn-bg: #ffedd5;
--degraded: #ea580c;
--degraded-bg: #fed7aa;
--fail: #dc2626;
--fail-bg: #fee2e2;
--unknown: #64748b;
@@ -262,6 +264,7 @@ h1 {
.status-ok { background: var(--ok-bg); color: var(--ok); }
.status-warn { background: var(--warn-bg); color: var(--warn); }
.status-degraded { background: var(--degraded-bg); color: var(--degraded); }
.status-fail { background: var(--fail-bg); color: var(--fail); }
.status-unknown { background: var(--unknown-bg); color: var(--unknown); }
.text-ok { color: var(--ok); }
@@ -504,6 +507,49 @@ h1 {
line-height: 1.35;
}
.agent-quality-card {
margin: 12px 0 16px;
}
.quality-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin-top: 10px;
}
.quality-grid > div {
display: grid;
gap: 4px;
min-width: 0;
padding: 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--soft);
}
.quality-grid strong {
overflow-wrap: anywhere;
}
.quality-warning {
margin-top: 10px;
padding: 10px 12px;
border-left: 4px solid var(--warn);
border-radius: 8px;
background: var(--warn-bg);
color: #9a3412;
font-weight: 700;
line-height: 1.35;
}
.quality-error {
margin: 10px 0 0;
color: var(--fail);
font-weight: 700;
overflow-wrap: anywhere;
}
.heatmap-table td {
min-width: 120px;
}
@@ -24,8 +24,9 @@ async function postJson(path, payload) {
function statusClass(status) {
const s = String(status || "UNKNOWN").toLowerCase();
if (s === "ok" || s === "true") return "status-ok";
if (s === "warn" || s === "warning") return "status-warn";
if (s === "fail" || s === "false") return "status-fail";
if (s === "warn" || s === "warning" || s === "fallback") return "status-warn";
if (s === "degraded") return "status-degraded";
if (s === "fail" || s === "false" || s === "error") return "status-fail";
return "status-unknown";
}
@@ -805,6 +806,7 @@ function renderOperator(data, report) {
</div>
${renderPeriodBanner(report)}
${renderExecutiveMetrics(report, incidents)}
${renderAgentQuality(report?.agent_quality)}
${renderOverviewAnalytics(report)}
<section class="dashboard-band">
<div class="band-head"><h3>Рабочая активность сотрудников</h3><span class="muted">загрузка, простои, перегруз и дисциплина процессов</span></div>
@@ -1334,6 +1336,32 @@ function renderUebaRisk(risk) {
`;
}
function renderAgentQuality(quality) {
const q = quality || {};
const status = q.quality_status || "unknown";
const source = q.collector_source || "unknown";
const warn = ["fallback", "degraded", "error"].includes(String(status).toLowerCase());
return `
<section class="card agent-quality-card">
<div class="section-head">
<div>
<h3>Качество данных агента</h3>
<p class="muted">Доверие к источнику, который подтверждает активность и RDP-сессии.</p>
</div>
<span class="badge ${statusClass(status)}">${ui(status)}</span>
</div>
${warn ? `<div class="quality-warning">Внимание. Данные активности собраны не основным способом. Точность определения активности и RDP-сессий может быть снижена.</div>` : ""}
<div class="quality-grid">
<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>
${q.collector_error ? `<p class="quality-error">Ошибка коллектора: ${ui(q.collector_error)}</p>` : ""}
</section>
`;
}
function renderReports(data) {
data = periodReport(data);
return `
@@ -1368,6 +1396,7 @@ function renderReports(data) {
</div>
<h3 class="section-title">Ключевые показатели</h3>
${renderKpiCards(data.kpis)}
${renderAgentQuality(data.agent_quality)}
${renderUebaRisk(data.ueba_risk)}
${renderWorkforceIndexExplanation(data.workforce_policy)}
<h3 class="section-title">Срезы отчета</h3>