@ -1,520 +1,102 @@
import type {
ReportDomainPriority ,
ReportDomainSummary ,
ReportFindingRef ,
ReportGpoBlock ,
ReportPriorityBasis ,
ReportProjection ,
ReportScoreSource
} from '../domain/report-projection' ;
import type { ReportDomainSummary , ReportProjection } from '../domain/report-projection' ;
import { ASSESSOR_PRODUCT_VERSION } from '../domain/assessor-release' ;
import { escapeHtml } from '../shared/html' ;
/ * *
* Artifact A of docs / REPORT_STRATEGY.md : the Management Overview .
*
* A frozen snapshot for the customer ' s decision makers . It answers how the
* environment stands , what is most urgent , what the statement rests on and what
* is not known — and deliberately carries no registry keys , SIDs , evidence paths
* or parser versions , so the file can be forwarded as it is .
*
* German carries the prose ; established English technical terminology stays
* English . The file is self - contained : every style is inline , nothing is
* fetched when it is opened .
* /
export const MANAGEMENT_OVERVIEW_RENDERER_VERSION = '1.1.0' ;
/** Offline management report: a factual view of the shared projection. */
export const MANAGEMENT_OVERVIEW_RENDERER_VERSION = '2.0.0' ;
const DASH = '—' ;
const count = ( n : number ) : string = > Number . isFinite ( n ) ? n . toLocaleString ( 'de-DE' ) : DASH ;
/** Unknown stays unknown. An absent value never becomes a zero. */
function value ( input : string | number | undefined | null ) : string {
if ( input === undefined || input === null ) return DASH ;
if ( typeof input === 'number' && ! Number . isFinite ( input ) ) return DASH ;
const text = String ( input ) . trim ( ) ;
return text === '' ? DASH : escapeHtml ( text ) ;
}
function integer ( input : number | undefined ) : string {
return typeof input === 'number' && Number . isFinite ( input )
? escapeHtml ( input . toLocaleString ( 'de-DE' ) )
: DASH ;
}
function percent ( ratio : number | undefined , digits = 0 ) : string {
return typeof ratio === 'number' && Number . isFinite ( ratio )
? escapeHtml ( ( ratio * 100 ) . toLocaleString ( 'de-DE' , {
minimumFractionDigits : digits , maximumFractionDigits : digits
} ) )
: DASH ;
}
function germanDateTime ( iso : string ) : string {
function date ( iso : string ) : string {
const parsed = Date . parse ( iso ) ;
if ( ! Number . isFinite ( parsed ) ) return value ( iso ) ;
return escapeHtml ( new Date ( parsed ) . toLocaleString ( 'de-DE' , {
year : 'numeric' , month : '2-digit' , day : '2-digit' , hour : '2-digit' , minute : '2-digit'
} ) ) ;
}
function germanDate ( iso : string | undefined ) : string {
if ( ! iso ) return DASH ;
const parsed = Date . parse ( iso ) ;
if ( ! Number . isFinite ( parsed ) ) return value ( iso ) ;
return escapeHtml ( new Date ( parsed ) . toLocaleDateString ( 'de-DE' , {
year : 'numeric' , month : '2-digit' , day : '2-digit'
} ) ) ;
}
/ * *
* Binding wording from docs / SCORING_MODEL . md . A method is named , never implied ,
* and the GPO contribution is never called Baseline - Compliance .
* /
const SCORE_METHOD_LABEL : Record < ReportScoreSource [ ' method ' ] , string > = {
native : 'Nativer Score des Werkzeugs' ,
'inverted-native' : 'Nativer Risk, invertiert als 100 − Risk' ,
'pass-rate' : 'Pass Rate der bewerteten Checks' ,
unavailable : 'Kein verwertbarer Score'
} ;
/** Names why a source is ordered the way it is, or why it is not ordered. */
function basisLabel ( basis : ReportPriorityBasis ) : string {
if ( basis . kind === 'severity' ) return 'Sortiert nach nativer Severity' ;
if ( basis . kind === 'numeric' ) {
const direction = basis . direction === 'higher-is-worse' ? 'höher ist kritischer' : 'niedriger ist kritischer' ;
return ` Sortiert nach ${ escapeHtml ( basis . property ) } ( ${ escapeHtml ( basis . scale ) } , ${ direction } ) ` ;
}
if ( basis . kind === 'equal-weight' ) return ` Ohne Rangfolge: ${ escapeHtml ( basis . reason ) } ` ;
return 'Ohne Rangfolge: Die Quelle deklariert keinen belastbaren Maßstab.' ;
}
const SEVERITY_LABEL : Record < ReportFindingRef [ ' severity ' ] , string > = {
critical : 'Critical' , high : 'High' , medium : 'Medium' ,
low : 'Low' , info : 'Info' , unknown : 'Unknown'
} ;
/ * *
* Shared donut geometry : full 360 degrees , continuous neutral track , value arc
* clockwise from twelve o ' clock , flat caps . The number sits on the exact centre
* and its unit follows to the right without displacing it .
* /
function donut ( score : number | undefined , caption : string ) : string {
const radius = 54 ;
const circumference = 2 * Math . PI * radius ;
const ratio = typeof score === 'number' && Number . isFinite ( score )
? Math . min ( Math . max ( score , 0 ) , 100 ) / 100
: 0 ;
const dash = ` ${ ( circumference * ratio ) . toFixed ( 3 ) } ${ ( circumference * ( 1 - ratio ) ) . toFixed ( 3 ) } ` ;
const display = typeof score === 'number' && Number . isFinite ( score )
? score > 0 && score < 0.5 ? '<1' : Math . round ( score ) . toLocaleString ( 'de-DE' )
return Number . isFinite ( parsed )
? new Date ( parsed ) . toLocaleDateString ( 'de-DE' , { day : '2-digit' , month : '2-digit' , year : 'numeric' } )
: DASH ;
const hasScore = display !== DASH ;
return ` <figure class="donut">
< svg viewBox = "0 0 128 128" role = "img" aria - label = "Overall Score ${hasScore ? `${display} von 100` : 'nicht verfügbar'}" >
< circle class = "donut__track" cx = "64" cy = "64" r = "${radius}" > < / circle >
$ { hasScore ? ` <circle class="donut__value" cx="64" cy="64" r=" ${ radius } "
stroke - dasharray = "${dash}" transform = "rotate(-90 64 64)" > < / circle > $ { ratio > 0 && ratio < 0.02
? ` <rect class="donut__small-arc" x="112" y=" ${ ( 64 - Math . max ( 3 , circumference * ratio ) / 2 ) . toFixed ( 3 ) } " width="12" height=" ${ Math . max ( 3 , circumference * ratio ) . toFixed ( 3 ) } " transform="rotate(-90 64 64)"></rect> `
: '' } ` : ''}
< / svg >
< figcaption class = "donut__readout" >
< span class = "donut__number" > $ { escapeHtml ( display ) } $ { hasScore ? '<span class="donut__unit">/100</span>' : '' } < / span >
< span class = "donut__caption" > $ { escapeHtml ( caption ) } < / span >
< / figcaption >
< / figure > ` ;
}
/** Domain pass rates as a connected spine, not a conventional table. */
function domainSpine ( domains : ReportDomainSummary [ ] ) : string {
if ( ! domains . length ) return '<p class="empty">Keine Security Domain wurde bewertet.</p>' ;
return ` <ol class="spine"> ${ domains . map ( domain = > {
const known = typeof domain . passRate === 'number' ;
const width = known ? Math . round ( domain . passRate ! * 100 ) : 0 ;
return ` <li class="spine__item">
< span class = "spine__node" aria - hidden = "true" > < / span >
< span class = "spine__label" > $ { escapeHtml ( domain . domainLabel ) } < / span >
< span class = "spine__meter" > < span class = "spine__fill" style = "width:${width}%" > < / span > < / span >
< span class = "spine__figure" > $ { known ? ` ${ percent ( domain . passRate ) } %` : DASH } < / span >
< span class = "spine__counts" > $ { integer ( domain . deviations ) } Deviations von $ { integer ( domain . assessed ) } bewertet < / span >
< / li > ` ;
} ) . join ( '' ) } < / ol > ` ;
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 ` <li class=" ${ level } "><span class="area"> ${ escapeHtml ( item . domainLabel ) } </span>
< strong class = "value" > $ { share === undefined ? DASH : ` ${ count ( share ) } % ` } < / strong >
< span class = "count" > $ { assessed ? ` ${ count ( item . deviations ) } von ${ count ( item . assessed ) } bewertet ` : 'Keine bewerteten Ergebnisse' } < / span > < / li > ` ;
}
function findingRow ( finding : ReportFindingRef , ranked : boolean ) : string {
const severity = finding . severity ;
const showSeverity = severity !== 'unknown' ;
return ` <li class="finding">
< span class = "finding__accent finding__accent--${escapeHtml(severity)}" aria - hidden = "true" > < / span >
< span class = "finding__title" > $ { escapeHtml ( finding . title ) } < / span >
< span class = "finding__meta" >
$ { showSeverity ? ` <span class="badge badge-- ${ escapeHtml ( severity ) } "> ${ escapeHtml ( SEVERITY_LABEL [ severity ] ) } </span> ` : '' }
$ { ranked && finding . rankValue !== undefined && finding . rankBasis . kind === 'numeric'
? ` <span class="badge badge--neutral"> ${ escapeHtml ( finding . rankBasis . property ) } ${ integer ( finding . rankValue ) } </span> ` : '' }
< / span >
< / li > ` ;
}
function prioritySection ( priorities : ReportDomainPriority [ ] ) : string {
if ( ! priorities . length ) return '<p class="empty">Es wurde keine Deviation festgestellt.</p>' ;
return priorities . map ( domain = > ` <article class="tile domain">
< header class = "domain__head" >
< h3 > $ { escapeHtml ( domain . domainLabel ) } < / h3 >
< span class = "domain__count" > $ { integer ( domain . deviations ) } Deviations < / span >
< / header >
$ { domain . sources . map ( source = > ` <section class="source">
< header class = "source__head" >
< span class = "source__tool" > $ { escapeHtml ( source . toolId ) } < / span >
< span class = "source__basis" > $ { basisLabel ( source . basis ) } < / span >
< / header >
< ol class = "findings" > $ { source . shown . map ( item = > findingRow ( item , source . ranked ) ) . join ( '' ) } < / ol >
$ { source . shown . length < source . total
? ` <p class="source__more"> ${ integer ( source . total - source . shown . length ) } weitere Deviations dieser Quelle stehen im Technical Report.</p> `
: '' }
< / section > ` ).join('')}
< / article > ` ).join('');
}
/ * *
* GPO deviations carry no native criticality , so they form their own
* equal - weight block instead of being ranked against severity - bearing sources .
* /
function gpoSection ( gpo : ReportGpoBlock ) : string {
const rows : Array < [ string , number ] > = [
[ 'Fail' , gpo . counts . Fail ] ,
[ 'Review required' , gpo . counts [ 'Review required' ] ] ,
[ 'Pass' , gpo . counts . Pass ] ,
[ 'Not configured' , gpo . counts [ 'Not configured' ] ] ,
[ 'No baseline rule' , gpo . counts [ 'No baseline rule' ] ] ,
[ 'Not comparable' , gpo . counts [ 'Not comparable' ] ]
] ;
return ` <article class="tile domain">
< header class = "domain__head" >
< h3 > GPO & amp ; Baseline < / h3 >
< span class = "domain__count" > $ { integer ( gpo . counts . Fail ) } Deviations < / span >
< / header >
< p class = "note" > $ { escapeHtml ( gpo . basis . reason ) } Die Einträge stehen deshalb gleichgewichtig nebeneinander . < / p >
< dl class = "verdicts" > $ { rows . map ( ( [ label , count ] ) = > ` <div class="verdicts__row">
< dt > $ { escapeHtml ( label ) } < / dt > < dd > $ { integer ( count ) } < / dd >
< / div > ` ).join('')}</dl>
< p class = "note" >
Betroffene Policy Settings : < strong > $ { integer ( gpo . affectedSettings ) } < / strong > ·
betroffene GPO Backups : < strong > $ { integer ( gpo . affectedBackups ) } < / strong > ·
Conflicts : < strong > $ { integer ( gpo . conflicts ) } < / strong > ·
Duplicates : < strong > $ { integer ( gpo . duplicates ) } < / strong >
< / p >
< p class = "note" >
< code > Not configured < / code > , < code > No baseline rule < / code > und < code > Not comparable < / code >
sind score - neutral und begründen keine Maßnahme .
$ { gpo . scopeLimited
? 'Die Auswertung bezieht sich ausschließlich auf den importierten GPO-Backup-Satz und trifft keine Aussage über die gesamte Domäne.'
: '' }
< / p >
< / article > ` ;
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 ` <li class="comparison-row"><span class="comparison-label"> ${ escapeHtml ( item . domainLabel ) } </span>
< span class = "comparison-bar" aria - label = "${assessed ? `${count(passed)} bestanden, ${count(deviations)} abweichend` : 'Keine bewerteten Ergebnisse'}" >
< span class = "bar-pass" style = "width:${assessed ? 100 * passed / item.assessed : 0}%" > < / span >
< span class = "bar-fail" style = "width:${assessed ? 100 * deviations / item.assessed : 0}%" > < / span >
$ { other ? ` <span class="bar-other" style="width: ${ 100 * other / item . assessed } %"></span> ` : '' }
< / span >
< span class = "comparison-value" > $ { passPercent === undefined ? DASH : ` ${ count ( passPercent ) } % ` } < span > · $ { assessed ? ` ${ count ( item . deviations ) } von ${ count ( item . assessed ) } ` : DASH } < / span > < / span > < / li > ` ;
}
export function buildManagementOverviewHtml ( projection : ReportProjection ) : string {
const facts = projection . environment . length
? projection . environment
: [ { key : 'Umgebung' , value : undefined } ] ;
const remediation = projection . remediation ;
const coverage = projection . coverage ;
const head = ` <header class="doc__head">
< p class = "doc__kicker" > ASSESSOR · Management Overview < / p >
< h1 > $ { escapeHtml ( projection . project . name ) } < / h1 >
< dl class = "facts" >
$ { facts . map ( fact = > ` <div class="facts__item">
< dt > $ { escapeHtml ( fact . key ) } < / dt > < dd > $ { value ( fact . value ) } < / dd >
< / div > ` ).join('')}
< div class = "facts__item" > < dt > Assessment < / dt > < dd > $ { value ( projection . assessmentId ) } < / dd > < / div >
< div class = "facts__item" > < dt > Berichtsdatum < / dt > < dd > $ { germanDateTime ( projection . generatedAt ) } < / dd > < / div >
< / dl >
< / header > ` ;
const posture = ` <section class="segment" aria-labelledby="posture">
< h2 id = "posture" > Overall Posture < / h2 >
< div class = "posture" >
< div class = "tile posture__score" >
$ { donut ( projection . overallScore , 'Overall Score' ) }
< / div >
< div class = "tile posture__domains" >
< h3 > Security Domains < / h3 >
$ { domainSpine ( projection . domains ) }
< / div >
< / div >
< p class = "note" >
Der Overall Score ist ein Mixed - Method - Index ü ber die unten genannten Quellen .
Der GPO - Anteil bezeichnet die Konformität der konfigurierten , vergleichbaren
Einstellungen im Scope — er ist keine Aussage ü ber eine vollständig
ausgerollte Microsoft Baseline . Security - Domain - Werte sind Pass Rates und keine
Zerlegung des Index .
< / p >
< / section > ` ;
const priorities = ` <section class="segment" aria-labelledby="priorities">
< h2 id = "priorities" > Priority Findings < / h2 >
< p class = "note" >
Gruppiert nach Security Domain . Innerhalb einer Domain wird je Quelle nach deren
eigenem , deklariertem Maßstab geordnet ; eine quellenübergreifende Rangfolge
gibt es nicht , weil die Maßstäbe nicht vergleichbar sind .
< / p >
< div class = "segment__body" >
$ { prioritySection ( projection . priorities ) }
$ { projection . gpo ? gpoSection ( projection . gpo ) : '' }
< / div >
< / section > ` ;
const convergence = ` <section class="segment" aria-labelledby="convergence">
< h2 id = "convergence" > Konvergenz der Quellen < / h2 >
< p class = "note" > Mehrere Quellen in einer Security Domain belegen nicht automatisch denselben Finding . < / p >
< div class = "segment__body" > $ { projection . convergence . filter ( item = > item . sourceCount > 0 ) . map ( item = > ` <article class="tile domain">
< h3 > $ { value ( projection . domains . find ( domain = > domain . domainId === item . domainId ) ? . domainLabel ? ? item . domainId ) } < / h3 >
< p > $ { integer ( item . sourceCount ) } unabhängige Quellen mit Deviations : $ { item . sources . filter ( source = > source . deviations > 0 )
. map ( source = > escapeHtml ( source . toolId ) ) . join ( ', ' ) } . < / p >
< p class = "note" > $ { item . evidence === 'shared-framework-reference'
? 'Belegte Konvergenz: Mindestens zwei Quellen nennen dieselbe Framework-Referenz.'
: 'Nur thematische Nähe innerhalb der Domain; ein gemeinsamer Finding ist nicht belegt und bedarf Validierung.' } < / p >
< / article > ` ).join('') || '<p class="empty">Keine quellenübergreifende Konvergenz belegt.</p>'}</div>
< / section > ` ;
const bundles = ` <section class="segment" aria-labelledby="bundles">
< h2 id = "bundles" > Maßnahmenbündel < / h2 >
< p class = "note" > Gleiche Framework - Referenzen gruppieren Findings ohne Aussage zu Aufwand , Nutzen oder Priorität . < / p >
$ { projection . bundles . length ? ` <table class="table"><thead><tr><th>Framework-Referenz</th><th>Findings</th></tr></thead>
< tbody > $ { projection . bundles . map ( bundle = > ` <tr><td> ${ escapeHtml ( bundle . key ) } </td><td class="num"> ${ integer ( bundle . members . length ) } </td></tr> ` ) . join ( '' ) } < / tbody > < / table > `
: '<p class="empty">Keine mehrfach belegte Framework-Referenz.</p>' }
< p class = "note" > Einzelquellen - GPO - Maßnahmen ohne Conflict oder Duplicate : $ { integer ( projection . singleSourcePolicyActions . length ) } . < / p >
< / section > ` ;
const remediationSection = remediation ? ` <section class="segment" aria-labelledby="remediation">
< h2 id = "remediation" > Remediation - Status < / h2 >
< p class = "note" >
Selbstauskunft des Kunden aus der Arbeitsgrundlage . Diese Angaben sind
< strong > kein technischer Nachweis < / strong > : Eine Maßnahme gilt erst dann als
behoben , wenn ein Rescan das belegt .
< / p >
< div class = "segment__body" >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( remediation . items ) } < / span > < span class = "kpi__label" > Maßnahmen in Arbeit < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( remediation . withoutOwner ) } < / span > < span class = "kpi__label" > ohne Owner < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( remediation . withoutDue ) } < / span > < span class = "kpi__label" > ohne Termin < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( remediation . riskAccepted ) } < / span > < span class = "kpi__label" > Accepted Risk < / span > < / article >
< / div >
$ { Object . keys ( remediation . byStatus ) . length ? ` <dl class="verdicts"> ${ Object . entries ( remediation . byStatus )
. map ( ( [ status , count ] ) = > ` <div class="verdicts__row"><dt> ${ escapeHtml ( status ) } </dt><dd> ${ integer ( count ) } </dd></div> ` )
. join ( '' ) } < / dl > ` : ''}
< / section > ` : '';
const coverageSection = ` <section class="segment" aria-labelledby="coverage">
< h2 id = "coverage" > Abdeckung und Lücken < / h2 >
< div class = "segment__body" >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( coverage . assessed ) } < / span > < span class = "kpi__label" > bewertet < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( coverage . deviations ) } < / span > < span class = "kpi__label" > Deviations < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( coverage . notRun ) } < / span > < span class = "kpi__label" > not run < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( coverage . notApplicable ) } < / span > < span class = "kpi__label" > not applicable < / span > < / article >
< article class = "tile kpi" > < span class = "kpi__figure" > $ { integer ( coverage . executionErrors ) } < / span > < span class = "kpi__label" > execution errors < / span > < / article >
< / div >
< p class = "note" >
Von insgesamt $ { integer ( coverage . total ) } Ergebnissen konnten $ { integer ( coverage . assessed ) }
bewertet werden . Nicht ausgeführte , nicht anwendbare und fehlerhaft ausgeführte
Checks zählen weder als bestanden noch als Deviation .
< / p >
< table class = "table" >
< caption > Abdeckung je Quelle < / caption >
< thead > < tr > < th scope = "col" > Quelle < / th > < th scope = "col" > Ergebnisse < / th > < th scope = "col" > bewertet < / th > < th scope = "col" > not run < / th > < th scope = "col" > not run Anteil < / th > < / tr > < / thead >
< tbody > $ { coverage . perSource . map ( source = > ` <tr>
< td > $ { escapeHtml ( source . toolId ) } < / td >
< td class = "num" > $ { integer ( source . total ) } < / td >
< td class = "num" > $ { integer ( source . assessed ) } < / td >
< td class = "num" > $ { integer ( source . notRun ) } < / td >
< td class = "num" > $ { source . total ? ` ${ percent ( source . notRun / source . total ) } % ` : DASH } < / td >
< / tr > ` ).join('')}</tbody>
< / table >
< / section > ` ;
const sources = ` <section class="segment" aria-labelledby="sources">
< h2 id = "sources" > Source Overview < / h2 >
< table class = "table" >
< caption > Score - Beitrag je Quelle < / caption >
< thead > < tr >
< th scope = "col" > Quelle < / th > < th scope = "col" > Stand < / th > < th scope = "col" > Beitrag < / th >
< th scope = "col" > Methode < / th > < th scope = "col" > Pass < / th > < th scope = "col" > Deviations < / th >
< / tr > < / thead >
< tbody > $ { projection . scoreSources . length ? projection . scoreSources . map ( source = > ` <tr>
< td > $ { escapeHtml ( source . toolId ) } < / td >
< td > $ { germanDate ( source . observedAt ) } < / td >
< td class = "num" > $ { typeof source . score === 'number' ? integer ( Math . round ( source . score ) ) : DASH } < / td >
< td > $ { escapeHtml ( SCORE_METHOD_LABEL [ source . method ] ) } < / td >
< td class = "num" > $ { integer ( source . passed ) } < / td >
< td class = "num" > $ { integer ( source . deviations ) } < / td >
< / tr > ` ).join('') : ` < tr > < td colspan = "6" > $ { DASH } < / td > < / tr > ` }</tbody>
< / table >
$ { projection . scoreSources . some ( source = > source . method === 'inverted-native' )
? '<p class="note">Eine invertierte Quelle liefert nativ einen Risk-Wert, bei dem ein höherer Wert schlechter ist. Er geht als <code>100 − Risk</code> in den Index ein; der rohe Risk-Wert bleibt in der Quelle erhalten.</p>'
: '' }
< / section > ` ;
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' )
? ` <p>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.' : '' } </p> `
: '' ;
return ` <!doctype html>
< html lang = "de" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > Management Overview · $ { escapeHtml ( projection . project . name ) } < / title >
< html lang = "de" > < head > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > ASSESSOR · Management Report · $ { escapeHtml ( projection . project . name ) } < / title >
< style >
: root {
-- font - ui : "Inter Variable" , "Inter" , system - ui , - apple - system , "Segoe UI" , sans - serif ;
-- type - h1 - size :56px ; -- type - h2 - size :32px ; -- type - h3 - size :18px ; -- type - h4 - size :16px ;
-- type - body - size :16px ; -- type - table - body - size :14px ; -- type - label - size :12px ;
-- type - meta - size :12px ; -- type - badge - size :11px ; -- type - table - head - size :12px ;
-- fw - regular :400 ; -- fw - semibold :600 ; -- fw - bold :700 ;
-- lh - ui :1.35 ; -- lh - prose :1.55 ; -- ls - caps :0.06em ;
-- space - 1 :4px ; -- space - 2 :8px ; -- space - 3 :12px ; -- space - 4 :16px ; -- space - 5 :20px ;
-- space - 6 :24px ; -- space - 8 :32px ;
-- panel - content - inset :20px ; -- panel - inset :var ( -- panel - content - inset ) ;
-- segment - surface :transparent ; -- segment - border :transparent ; -- segment - radius :10px ;
-- segment - inset :var ( -- space - 3 ) ; -- segment - gap :var ( -- space - 3 ) ;
-- tile - radius :0px ; -- tile - inset :var ( -- space - 4 ) ; -- tile - border :transparent ;
-- radius - xs :0px ; -- radius - sm :0px ;
-- donut - ring - size :128px ; -- donut - ring - stroke :12px ;
-- donut - value - size :26px ; -- donut - percent - size :14px ;
/* Light is the document default: a report is read and printed on white. */
-- surface - app : # F1F1F4 ; -- surface - panel : # FFFFFF ; -- tile - surface : # FFFFFF ;
-- text - primary : # 17171 C ; -- text - secondary : # 55555 F ; -- text - muted : # 74747 F ;
-- border - subtle : # E2E2E8 ; -- border - strong : # A8A8B4 ;
-- donut - track : # B9BEC7 ; -- donut - value : # 20242 A ; -- donut - label : # 555 B65 ; -- donut - all : # 1 C70C9 ;
-- marking - color : # E9A63C ;
-- sev - critical : # E8214F ; -- sev - high : # E2680F ; -- sev - medium : # C99700 ;
-- sev - low : # 2 E9E6B ; -- sev - info : # 3 D8FD1 ; -- sev - unknown : # 6 E6E78 ; -- sev - on : # FFFFFF ;
}
@media ( prefers - color - scheme :dark ) {
: root {
-- surface - app : # 242426 ; -- surface - panel : # 242426 ; -- tile - surface : # 323238 ;
-- text - primary : # E2DFE6 ; -- text - secondary : # B7BAC0 ; -- text - muted : # B7BAC0 ;
-- border - subtle :rgba ( 255 , 255 , 255 , . 14 ) ; -- border - strong : # 4 E4E58 ;
-- donut - track : # 555 B65 ; -- donut - value : # E2DFE6 ; -- donut - label : # B7BAC0 ; -- donut - all : # 348 CF2 ;
}
}
* { box - sizing :border - box }
html { background :var ( -- surface - app ) }
body { margin :0 ; padding :var ( -- panel - inset ) ; background :var ( -- surface - app ) ; color :var ( -- text - primary ) ;
font :var ( -- fw - regular ) var ( -- type - body - size ) / var ( -- lh - prose ) var ( -- font - ui ) }
h1 , h2 , h3 { margin :0 ; line - height :var ( -- lh - ui ) ; font - weight :var ( -- fw - semibold ) }
h1 { font - size :var ( -- type - h1 - size ) ; letter - spacing : - . 02 em }
h2 { font - size :var ( -- type - h2 - size ) }
h3 { font - size :var ( -- type - h3 - size ) }
code { font - family :var ( -- font - ui ) ; font - size :var ( -- type - table - body - size ) }
strong { font - weight :var ( -- fw - semibold ) }
. doc__head { padding - bottom :var ( -- space - 6 ) ; border - bottom :1px solid var ( -- border - strong ) }
. doc__kicker { margin :0 0 var ( -- space - 2 ) ; font - size :var ( -- type - label - size ) ; font - weight :var ( -- fw - semibold ) ;
letter - spacing :var ( -- ls - caps ) ; text - transform :uppercase ; color :var ( -- text - secondary ) }
. facts { display :flex ; flex - wrap :wrap ; gap :var ( -- space - 6 ) ; margin :var ( -- space - 5 ) 0 0 }
. facts__item { margin :0 }
. facts dt { font - size :var ( -- type - label - size ) ; font - weight :var ( -- fw - semibold ) ; color :var ( -- text - secondary ) }
. facts dd { margin :var ( -- space - 1 ) 0 0 ; font - size :var ( -- type - table - body - size ) }
. segment { margin - top :var ( -- space - 8 ) ; padding :var ( -- segment - inset ) ; background :var ( -- segment - surface ) ;
border :1px solid var ( -- segment - border ) ; border - radius :var ( -- segment - radius ) }
. segment__body { display :flex ; flex - wrap :wrap ; gap :var ( -- segment - gap ) ; margin - top :var ( -- space - 4 ) ; align - items :flex - start }
. tile { padding :var ( -- tile - inset ) ; background :var ( -- tile - surface ) ; border :1px solid var ( -- tile - border ) ;
border - radius :var ( -- tile - radius ) }
. note { margin :var ( -- space - 4 ) 0 0 ; font - size :var ( -- type - table - body - size ) ; color :var ( -- text - secondary ) }
. empty { margin :var ( -- space - 4 ) 0 0 ; font - size :var ( -- type - table - body - size ) ; color :var ( -- text - muted ) }
. posture { display :flex ; flex - wrap :wrap ; gap :var ( -- segment - gap ) ; margin - top :var ( -- space - 4 ) ; align - items :stretch }
. posture__score { display :flex ; align - items :center ; justify - content :center ; min - width :260px }
. posture__domains { flex :1 1 420 px ; min - width :320px }
. donut { position :relative ; margin :0 ; width :var ( -- donut - ring - size ) ; height :var ( -- donut - ring - size ) }
. donut svg { width :100 % ; height :100 % ; display :block }
. donut circle { fill :none ; stroke - width :var ( -- donut - ring - stroke ) ; stroke - linecap :butt }
. donut__track { stroke :var ( -- donut - track ) }
. donut__value { stroke :var ( -- donut - all ) }
. donut__small - arc { fill :var ( -- donut - all ) }
. donut__readout { position :absolute ; inset :0 ; display :flex ; flex - direction :column ; align - items :center ;
justify - content :center ; gap :var ( -- space - 1 ) ; text - align :center }
. donut__number { font - size :var ( -- donut - value - size ) ; font - weight :var ( -- fw - semibold ) ; color :var ( -- donut - value ) ;
line - height :1 ; font - variant - numeric :tabular - nums }
. donut__unit { font - size :var ( -- donut - percent - size ) ; font - weight :var ( -- fw - regular ) ; color :var ( -- donut - label ) }
. donut__caption { font - size :var ( -- type - badge - size ) ; color :var ( -- donut - label ) }
. spine { list - style :none ; margin :var ( -- space - 4 ) 0 0 ; padding :0 0 0 var ( -- space - 4 ) ;
border - left :1px solid var ( -- border - subtle ) }
. spine__item { position :relative ; display :grid ; grid - template - columns :minmax ( 150 px , 1 fr ) minmax ( 80 px , 140 px ) 52 px ;
gap :var ( -- space - 2 ) var ( -- space - 3 ) ; align - items :center ; padding :var ( -- space - 2 ) 0 }
. spine__node { position :absolute ; left :calc ( var ( -- space - 4 ) * - 1 - 4 px ) ; width :7px ; height :7px ; border - radius :50 % ;
background :var ( -- donut - all ) }
. spine__label { font - size :var ( -- type - table - body - size ) ; font - weight :var ( -- fw - semibold ) }
. spine__meter { height :6px ; background :var ( -- donut - track ) }
. spine__fill { display :block ; height :100 % ; background :var ( -- donut - all ) }
. spine__figure { font - size :var ( -- type - table - body - size ) ; font - variant - numeric :tabular - nums ; text - align :right }
. spine__counts { grid - column :1 / - 1 ; font - size :var ( -- type - meta - size ) ; color :var ( -- text - secondary ) }
. domain { flex :1 1 460 px ; min - width :320px }
. domain__head { display :flex ; align - items :baseline ; justify - content :space - between ; gap :var ( -- space - 3 ) ;
padding - bottom :var ( -- space - 2 ) ; border - bottom :1px solid var ( -- border - subtle ) }
. domain__count { font - size :var ( -- type - meta - size ) ; color :var ( -- text - secondary ) ; font - variant - numeric :tabular - nums }
. source { margin - top :var ( -- space - 4 ) }
. source__head { display :flex ; flex - wrap :wrap ; align - items :baseline ; gap :var ( -- space - 2 ) }
. source__tool { font - size :var ( -- type - h4 - size ) ; font - weight :var ( -- fw - semibold ) }
. source__basis { font - size :var ( -- type - meta - size ) ; color :var ( -- text - secondary ) }
. source__more { margin :var ( -- space - 2 ) 0 0 ; font - size :var ( -- type - meta - size ) ; color :var ( -- text - muted ) }
. findings { list - style :none ; margin :var ( -- space - 2 ) 0 0 ; padding :0 }
. finding { position :relative ; display :flex ; flex - direction :column ; gap :var ( -- space - 1 ) ;
padding :var ( -- space - 2 ) 0 var ( -- space - 2 ) var ( -- space - 3 ) ; border - bottom :1px solid var ( -- border - subtle ) }
. finding__accent { position :absolute ; left :0 ; top :var ( -- space - 2 ) ; bottom :var ( -- space - 2 ) ; width :3px ;
background :var ( -- sev - unknown ) }
. finding__accent -- critical { background :var ( -- sev - critical ) }
. finding__accent -- high { background :var ( -- sev - high ) }
. finding__accent -- medium { background :var ( -- sev - medium ) }
. finding__accent -- low { background :var ( -- sev - low ) }
. finding__accent -- info { background :var ( -- sev - info ) }
. finding__title { font - size :var ( -- type - table - body - size ) ; font - weight :var ( -- fw - semibold ) ; line - height :var ( -- lh - ui ) }
. finding__meta { display :flex ; flex - wrap :wrap ; align - items :center ; gap :var ( -- space - 2 ) }
. finding__asset { font - size :var ( -- type - meta - size ) ; color :var ( -- text - secondary ) }
. badge { display :inline - block ; padding :1px var ( -- space - 2 ) ; border - radius :var ( -- radius - xs ) ;
font - size :var ( -- type - badge - size ) ; font - weight :var ( -- fw - semibold ) ; letter - spacing :var ( -- ls - caps ) ;
text - transform :uppercase ; color :var ( -- sev - on ) }
. badge -- critical { background :var ( -- sev - critical ) }
. badge -- high { background :var ( -- sev - high ) }
. badge -- medium { background :var ( -- sev - medium ) }
. badge -- low { background :var ( -- sev - low ) }
. badge -- info { background :var ( -- sev - info ) }
. badge -- neutral { background :transparent ; color :var ( -- text - secondary ) ; border :1px solid var ( -- border - strong ) ;
text - transform :none ; letter - spacing :0 ; font - variant - numeric :tabular - nums }
. kpi { flex :1 1 150 px ; min - width :140px ; display :flex ; flex - direction :column ; gap :var ( -- space - 1 ) }
. kpi__figure { font - size :var ( -- type - h2 - size ) ; font - weight :var ( -- fw - semibold ) ; line - height :1 ;
font - variant - numeric :tabular - nums }
. kpi__label { font - size :var ( -- type - meta - size ) ; color :var ( -- text - secondary ) }
. verdicts { display :flex ; flex - wrap :wrap ; gap :var ( -- space - 2 ) var ( -- space - 6 ) ; margin :var ( -- space - 4 ) 0 0 }
. verdicts__row { display :flex ; align - items :baseline ; gap :var ( -- space - 2 ) }
. verdicts dt { font - size :var ( -- type - meta - size ) ; color :var ( -- text - secondary ) }
. verdicts dd { margin :0 ; font - size :var ( -- type - table - body - size ) ; font - weight :var ( -- fw - semibold ) ;
font - variant - numeric :tabular - nums }
. table { width :100 % ; margin - top :var ( -- space - 4 ) ; border - collapse :collapse ;
font - size :var ( -- type - table - body - size ) }
. table caption { margin - bottom :var ( -- space - 2 ) ; text - align :left ; font - size :var ( -- type - label - size ) ;
font - weight :var ( -- fw - semibold ) ; color :var ( -- text - secondary ) }
. table th , . table td { padding :var ( -- space - 2 ) var ( -- space - 3 ) ; text - align :left ;
border - bottom :1px solid var ( -- border - subtle ) }
. table th { font - size :var ( -- type - table - head - size ) ; font - weight :var ( -- fw - semibold ) ;
letter - spacing :var ( -- ls - caps ) ; text - transform :uppercase ; color :var ( -- text - secondary ) }
. table . num { text - align :right ; font - variant - numeric :tabular - nums }
@media print {
: root { -- surface - app : # FFFFFF ; -- surface - panel : # FFFFFF ; -- tile - surface : # FFFFFF }
body { padding :0 }
. segment { break - inside :avoid ; margin - top :var ( -- space - 6 ) }
. tile { break - inside :avoid ; border :1px solid var ( -- border - subtle ) }
. finding { break - inside :avoid }
}
@media ( max - width :720px ) {
h1 { font - size :var ( -- type - h2 - size ) }
. spine__item { grid - template - columns :1fr 60 px }
. spine__meter { grid - column :1 / - 1 }
}
< / style >
< / head >
< body >
$ { head }
$ { posture }
$ { convergence }
$ { coverageSection }
$ { priorities }
$ { bundles }
$ { remediationSection }
$ { sources }
< / body >
< / html > ` ;
: root { -- ink : # 17171 c ; -- muted : # 5 c6069 ; -- navy : # 123047 ; -- line : # cfd1d7 ; -- paper : # fff ; -- surface : # f5f5f7 ; -- orange : # df752d ; -- heat - 0 : # e7e8ec ; -- heat - 1 : # f8dfcb ; -- heat - 2 : # edb68d ; -- heat - 3 : # e68f51 }
* { box - sizing :border - box } body { margin :0 ; background : # e7e7eb ; color :var ( -- ink ) ; font :400 14 px / 1.45 Inter , Arial , sans - serif }
. sheet { width :210mm ; min - height :297mm ; margin :16px auto ; padding :12mm 16 mm 11 mm ; background :var ( -- paper ) ; display :flex ; flex - direction :column }
. mast { display :flex ; align - items :center ; justify - content :space - between ; gap :16px ; padding - bottom :12px ; border - bottom :2px solid var ( -- navy ) }
. brand { color :var ( -- navy ) ; font - size :19px ; font - weight :700 ; letter - spacing :.06em } . mast - note { color :var ( -- navy ) ; text - align :right ; font - size :12px ; font - weight :600 }
. kicker { margin :20px 0 7 px ; color :var ( -- orange ) ; font - size :12px ; font - weight :600 ; letter - spacing :.08em ; text - transform :uppercase }
h1 { margin :0 ; color :var ( -- navy ) ; font - size :30px ; line - height :1.13 ; font - weight :600 } h2 { margin :0 ; color :var ( -- navy ) ; font - size :18px ; font - weight :600 }
p { margin :0 0 8 px } . subtitle { margin :8px 0 14 px ; color :var ( -- muted ) ; font - size :14px }
. meta { display :flex ; flex - wrap :wrap ; gap :6px 18 px ; padding :8px 0 ; border - top :1px solid var ( -- line ) ; border - bottom :1px solid var ( -- line ) ; color :var ( -- muted ) ; font - size :14px }
. meta strong { color :var ( -- ink ) ; font - weight :600 } . facts { display :grid ; grid - template - columns :repeat ( 3 , 1 fr ) ; margin :21px 0 26 px ; border - top :2px solid var ( -- navy ) ; border - bottom :1px solid var ( -- line ) }
. fact { padding :13px 15 px ; border - right :1px solid var ( -- line ) } . fact :last - child { border - right :0 } . fact strong { display :block ; color :var ( -- navy ) ; font - size :28px ; line - height :1.12 ; font - weight :600 ; font - variant - numeric :tabular - nums } . fact span { display :block ; margin - top :5px ; font - size :14px }
. section - head { display :flex ; align - items :end ; justify - content :space - between ; gap :15px ; margin - bottom :11px } . section - head p { max - width :260px ; margin :0 ; color :var ( -- muted ) ; font - size :14px ; text - align :right }
. heatmap { display :grid ; grid - template - columns :repeat ( 3 , 1 fr ) ; gap :7px ; margin :0 ; padding :0 ; list - style :none } . heatmap li { min - height :104px ; padding :11px 12 px 10 px ; display :flex ; flex - direction :column ; border :1px solid rgba ( 18 , 48 , 71 , . 17 ) ; break - inside :avoid }
. heatmap . h0 { background :var ( -- heat - 0 ) } . heatmap . h1 { background :var ( -- heat - 1 ) } . heatmap . h2 { background :var ( -- heat - 2 ) } . heatmap . h3 { background :var ( -- heat - 3 ) } . heatmap . unknown { background :var ( -- surface ) }
. heatmap . area { color :var ( -- navy ) ; font - size :14px ; line - height :1.25 ; font - weight :600 } . heatmap . value { display :block ; margin - top :auto ; color :var ( -- navy ) ; font - size :28px ; line - height :1.08 ; font - weight :600 ; font - variant - numeric :tabular - nums } . heatmap . count { display :block ; margin - top :3px ; color :var ( -- ink ) ; font - size :14px }
. legend { display :flex ; align - items :center ; flex - wrap :wrap ; gap :5px 12 px ; margin :12px 0 0 ; color :var ( -- muted ) ; font - size :14px } . legend . key { display :inline - flex ; align - items :center ; gap :5px ; white - space :nowrap } . legend i { display :inline - block ; width :14px ; height :14px ; border :1px solid rgba ( 18 , 48 , 71 , . 17 ) }
. legend . l0 { background :var ( -- heat - 0 ) } . legend . l1 { background :var ( -- heat - 1 ) } . legend . l2 { background :var ( -- heat - 2 ) } . legend . l3 { background :var ( -- heat - 3 ) }
. comparison { margin - top :26px ; padding :15px 17 px ; background :var ( -- surface ) ; border :1px solid var ( -- line ) ; break - inside :avoid } . comparison h2 { margin - bottom :13px }
. comparison - scale { display :grid ; grid - template - columns :240px minmax ( 180 px , 1 fr ) 135 px ; gap :12px ; margin - bottom :5px ; color :var ( -- muted ) ; font - size :12px }
. comparison - scale div { grid - column :2 ; display :flex ; justify - content :space - between }
. comparison - list { list - style :none ; margin :0 ; padding :0 } . comparison - row { display :grid ; grid - template - columns :240px minmax ( 180 px , 1 fr ) 135 px ; gap :12px ; align - items :center ; min - height :34px ; font - size :14px }
. comparison - bar { display :flex ; height :14px ; background : # e3e3e8 } . bar - pass { background : # 167048 } . bar - fail { background : # c60b41 } . bar - other { background : # 92969 d }
. comparison - value { text - align :right ; font - variant - numeric :tabular - nums ; font - weight :600 ; white - space :nowrap } . comparison - value span { color :var ( -- muted ) ; font - weight :400 }
. comparison - key { display :flex ; gap :24px ; margin :13px 0 0 252 px ; font - size :14px ; color :var ( -- muted ) } . comparison - key i { display :inline - block ; width :12px ; height :12px ; margin - right :5px ; vertical - align : - 1 px } . comparison - key . pass { background : # 167048 } . comparison - key . fail { background : # c60b41 }
. notes { margin - top :23px ; padding :13px 15 px ; background :var ( -- surface ) ; border - top :2px solid var ( -- navy ) } . notes h2 { font - size :16px ; margin - bottom :6px } . notes p { margin :0 0 5 px ; font - size :14px } . notes p :last - child { margin - bottom :0 }
. foot { margin - top :auto ; padding - top :10px ; border - top :1px solid var ( -- navy ) ; display :flex ; justify - content :space - between ; gap :12px ; color :var ( -- muted ) ; font - size :12px } . foot strong { color :var ( -- navy ) ; font - weight :600 }
@media ( max - width :800px ) { body { background : # fff } . sheet { width :auto ; min - height :0 ; margin :0 ; padding :18px 16 px 26 px } . heatmap { grid - template - columns :repeat ( 2 , 1 fr ) } . section - head { display :block } . section - head p { max - width :none ; margin - top :4px ; text - align :left } h1 { font - size :26px } }
@media ( max - width :480px ) { . heatmap , . facts { grid - template - columns :1fr } . fact { border - right :0 ; border - bottom :1px solid var ( -- line ) } . fact :last - child { border - bottom :0 } }
@media ( max - width :720px ) { . comparison - scale { display :none } . comparison - row { grid - template - columns :1fr auto ; gap :3px 10 px ; margin - bottom :12px } . comparison - label { grid - column :1 / - 1 } . comparison - bar { grid - column :1 } . comparison - value { grid - column :2 } . comparison - key { margin - left :0 } }
@page { size :A4 ; margin :0 } @media print { body { background : # fff ; print - color - adjust :exact ; - webkit - print - color - adjust :exact } . sheet { margin :0 } }
< / style > < / head > < body > < main class = "sheet" >
< header class = "mast" > < div class = "brand" > ASSESSOR < / div > < div class = "mast-note" > Management Report < / div > < / header >
< p class = "kicker" > Gespeicherter Assessment - Stand < / p > < h1 > Technische Ergebnisse im Ü berblick < / h1 >
< p class = "subtitle" > $ { escapeHtml ( projection . project . name ) } < / p >
< div class = "meta" > $ { domain ? ` <span><strong>Domain:</strong> ${ escapeHtml ( domain ) } </span> ` : '' } < span > < strong > Berichtsdatum : < / strong > $ { date ( projection . generatedAt ) } < / span > < / div >
< div class = "facts" aria - label = "Erfasste Kennzahlen" >
< div class = "fact" > < strong > $ { dc ? escapeHtml ( dc ) : DASH } < / strong > < span > Domain Controller erfasst < / span > < / div >
< div class = "fact" > < strong > $ { count ( projection . coverage . assessed ) } < / strong > < span > Ergebnisse bewertet < / span > < / div >
< div class = "fact" > < strong > $ { count ( projection . coverage . deviations ) } < / strong > < span > Abweichende Ergebnisse < / span > < / div > < / div >
< section aria - labelledby = "heatmap-title" > < div class = "section-head" > < h2 id = "heatmap-title" > Abweichungsanteil je Security Domain < / h2 > < p > Abweichende von bewerteten Ergebnissen innerhalb des jeweiligen Bereichs . < / p > < / div >
< ol class = "heatmap" aria - label = "Heatmap der bewerteten Ergebnisse" > $ { projection . domains . length ? projection . domains . map ( tile ) . join ( '' ) : '<li class="unknown"><span class="area">Keine Bereiche verfügbar</span><strong class="value">—</strong><span class="count">Keine bewerteten Ergebnisse</span></li>' } < / ol >
< div class = "legend" aria - label = "Farbstufen nach Abweichungsanteil" > < span > Farbstufe : < / span > < span class = "key" > < i class = "l0" > < / i > 0 – 24 % < / span > < span class = "key" > < i class = "l1" > < / i > 25 – 49 % < / span > < span class = "key" > < i class = "l2" > < / i > 50 – 74 % < / span > < span class = "key" > < i class = "l3" > < / i > 75 – 100 % < / span > < / div > < / section >
< section class = "comparison" aria - labelledby = "comparison-title" > < h2 id = "comparison-title" > Bestandene und abweichende Ergebnisse < / h2 >
< div class = "comparison-scale" aria - hidden = "true" > < div > < span > 0 % < / span > < span > 50 % < / span > < span > 100 % < / span > < / div > < / div >
< ol class = "comparison-list" > $ { projection . domains . map ( comparisonRow ) . join ( '' ) } < / ol >
< div class = "comparison-key" > < span > < i class = "pass" > < / i > Bestanden < / span > < span > < i class = "fail" > < / i > Abweichung < / span > < / div >
< p > Pass Rate je Security Domain ; rechts : Anzahl der Abweichungen von den bewerteten Ergebnissen . < / p > < / section >
< aside class = "notes" aria - labelledby = "notes-title" > < h2 id = "notes-title" > Lesart < / h2 > < p > 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 . < / p > $ { gpoNote } < p > Die Ergebnisse können aus unterschiedlichen Erhebungszeitpunkten stammen . Betroffene Geschäftsdienste , Ausnutzung und aktueller Behebungsstand sind aus dieser Darstellung nicht ableitbar . < / p > < / aside >
< footer class = "foot" > < span > < strong > ASSESSOR $ { escapeHtml ( ASSESSOR_PRODUCT_VERSION ) } < / strong > · $ { escapeHtml ( projection . project . name ) } < / span > < / footer >
< / main > < / body > < / html > ` ;
}