import type { ReportDomainSummary, ReportProjection } from './report-projection';
import { ASSESSOR_PRODUCT_VERSION } from '../domain/assessor-release';
import { escapeHtml } from '../shared/html';
import { statementText } from './report-statement-text';
import { buildContextManagementHtml } from './context-management-report';
/** Offline management report: a factual view of the shared projection. */
export const MANAGEMENT_OVERVIEW_RENDERER_VERSION = '3.3.0';
const DASH = '—';
const count = (n: number): string => Number.isFinite(n) ? n.toLocaleString('de-DE') : DASH;
function date(iso: string): string {
const parsed = Date.parse(iso);
return Number.isFinite(parsed)
? new Date(parsed).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
: DASH;
}
function tile(item: ReportDomainSummary): string {
const assessed = item.assessed > 0 && Number.isFinite(item.assessed) && Number.isFinite(item.deviations);
const share = assessed ? Math.round(100 * item.deviations / item.assessed) : undefined;
const level = share === undefined ? 'unknown' : `h${share < 25 ? 0 : share < 50 ? 1 : share < 75 ? 2 : 3}`;
return `
${escapeHtml(item.domainLabel)}
${share === undefined ? DASH : `${count(share)} %`}
${assessed ? `${count(item.deviations)} von ${count(item.assessed)} bewertet` : 'Keine bewerteten Ergebnisse'} `;
}
function comparisonRow(item: ReportDomainSummary): string {
const assessed = Number.isFinite(item.assessed) && item.assessed > 0;
const passed = assessed && Number.isFinite(item.passed) ? Math.max(0, Math.min(item.passed, item.assessed)) : 0;
const deviations = assessed && Number.isFinite(item.deviations) ? Math.max(0, Math.min(item.deviations, item.assessed - passed)) : 0;
const other = assessed ? Math.max(0, item.assessed - passed - deviations) : 0;
const passPercent = assessed ? Math.round(100 * passed / item.assessed) : undefined;
return `${escapeHtml(item.domainLabel)}
${other ? ` ` : ''}
${passPercent === undefined ? DASH : `${count(passPercent)} %`} · ${assessed ? `${count(item.deviations)} von ${count(item.assessed)}` : DASH} `;
}
function keyStatements(projection: ReportProjection): string {
const statements = projection.statements ?? [];
if (!statements.length) return '';
return `Kernaussagen ${statements.map(statement => {
const { text, note } = statementText(statement);
return `${escapeHtml(text)}
${note ? `${escapeHtml(note)}
` : ''} `;
}).join('')} `;
}
export function buildManagementOverviewHtml(projection: ReportProjection): string {
// Retain the historical renderer for archived projections; new management runs require Context.
if (projection.context) return buildContextManagementHtml(projection);
const domain = projection.environment.find(fact => fact.key === 'Domain')?.value;
const dc = projection.environment.find(fact => fact.key === 'Domain Controller')?.value;
const gpo = projection.gpo;
const gpoNote = gpo && projection.domains.some(item => item.domainId === 'gpo')
? `Die GPO-Kachel zählt zugeordnete Ergebniszeilen. Die gesonderte GPO-Evaluation zählt ${count(gpo.counts.Fail)} abweichende Vergleiche zu ${count(gpo.affectedSettings)} Einstellungen. Diese Zähleinheiten sind nicht gleichzusetzen.${gpo.scopeLimited ? ' Die Evaluation bezieht sich auf die importierten GPO-Backups.' : ''}
`
: '';
return `
ASSESSOR · Management Report · ${escapeHtml(projection.project.name)}
ASSESSOR
Management Report
Gespeicherter Assessment-Stand
Technische Ergebnisse im Überblick
${escapeHtml(projection.project.name)}
${domain ? `Domain: ${escapeHtml(domain)} ` : ''}Berichtsdatum: ${date(projection.generatedAt)}
${dc ? escapeHtml(dc) : DASH} Domain Controller erfasst
${count(projection.coverage.assessed)} Ergebnisse bewertet
${count(projection.coverage.deviations)} Abweichende Ergebnisse
${keyStatements(projection)}
Abweichungsanteil je Security Domain Abweichende von bewerteten Ergebnissen innerhalb des jeweiligen Bereichs.
${projection.domains.length ? projection.domains.map(tile).join('') : 'Keine Bereiche verfügbar — Keine bewerteten Ergebnisse '}
Farbstufe: 0–24 % 25–49 % 50–74 % 75–100 %
Bestandene und abweichende Ergebnisse
${projection.domains.map(comparisonRow).join('')}
Bestanden Abweichung
Pass Rate je Security Domain; rechts: Anzahl der Abweichungen von den bewerteten Ergebnissen.
Lesart Die Farbe zeigt die Häufigkeit abweichender Ergebnisse innerhalb eines Bereichs. Sie zeigt weder Schweregrad noch Geschäftsrisiko. Bereiche mit wenigen Prüfungen können hohe Prozentwerte haben.
${gpoNote}Die Ergebnisse können aus unterschiedlichen Erhebungszeitpunkten stammen. Betroffene Geschäftsdienste, Ausnutzung und aktueller Behebungsstand sind aus dieser Darstellung nicht ableitbar.
`;
}