feat(agent): expose data quality in portal
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user