diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index e296eb4..4ed62f0 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -3652,6 +3652,7 @@ Verifiziert 2026-09-09 (Phase 7): Bereichswechsel beschleunigt und Performance-V Verifiziert 2026-09-08 (Phase 7/13B): GPO-Quellenkorrektur fuer Work und Client Info. Aktuelle Evaluationen aus dem vorhandenen autoritativen Readmodell statt ergebnisabhaengiger Backupgruppierung; Import- und Auswertungszeit getrennt, Source-Scan-Provenienz auch auf GPO-only-Blaettern. 1.014 Tests bestanden, acht optionale PostgreSQL-Tests uebersprungen; Local-only-/Designpruefung und beide Builds erfolgreich. Dummy lesend bestaetigt: eine statt drei GPO-Quellenoptionen, 3.805 aktuelle Zeilen, elf historische Evaluationen unveraendert. Keine Persistenzmutation oder Neuanalyse. | Datum | Änderung | +| 2026-10-02 | Phase 7: Defender-Teil von `3f6259d` (Stile und Dashboardtest) nachgezogen. Kennzahlbeschriftungen und Tabellenkoepfe des Defender-Panels nutzen die 14px-Tabellenrolle statt der 12px-Labelrolle; die Kennzahlen stehen als kompakte Kacheln (`--tile-compact-width`) in einem Segment statt in sechs festen Spalten; der Zeilenmarker nutzt `--marker-width`. Beibehalten gegenueber dem Original: Kachelkontur `--tile-border`, gemeinsame Textrollen und die blaue Tabellenzeilen-Markierung. Nur CSS und Testassertions; nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 6/7/13A, Produktversion 2.12.0: `fdd46e8` (Profil Microsoft Defender for IoT Alerts) angepasst uebernommen. Profil `defender-for-iot-alerts` 1.6.0 mit Domain-Modell, Source Extractor, Manifestzeilen und deklarierten Scoring-/Prioritaets-/Timeslot-Vertraegen (kein nativer Gesamtscore, native Alarm-Severity, kein Timeslot-Vergleich). Alarme werden je Alarmtyp gruppiert; unterschiedliche Beschreibungen eines Typs erscheinen als gemeinsame Woerter mit `…`, die zugehoerige Nutzerregel steht mit ausdruecklicher Governance-Freigabe in `AGENTS.md`. Werkzeuguebergreifend: Unterstriche innerhalb eines Wortes sind in `markdown.ts` keine Auszeichnung mehr; ein Profil kann per `selectExtractedTables` die Tabellen behalten, aus denen sein Extractor Findings gebildet hat; ein Statusmapping kann sein Outcome deklarieren; beide Repositories setzen eine bereits durch einen ersetzten Lauf belegte Scan-Sequenz hinter alle gespeicherten Laeufe. Die Ausblendung von `Source Observations` liegt auf dieser Basis in `FindingReader.tsx`. Zwei Korrekturen aus spaeteren Commits waren fuer Defender auf dieser Basis noetig und sind vorgezogen: aus `210bbc0` der Titel-Rueckfall in `recordTitleFor` (eine leere gewaehlte Titelspalte faellt wieder auf den Datensatztitel zurueck; mit `7d3ef5f` war dieser Rueckfall fuer alle Scanner verloren gegangen) samt Regression fuer die GPO-Zeile, und aus `21a6266` die Ausnahme in `withSourceRuleId`, die eine native ID-Spalte nicht in `Source Rule ID` umbenennt, wenn sie zugleich der Finding-Titel ist (Defender `CVE`, Alert `Title`); die Regel-ID wird dann zusaetzlich angefuegt. Nicht uebernommen: die Versionsangabe 2.10.8 des Originals. Produktversion: Minor-Erhoehung von 2.11.2 auf 2.12.0 fuer die Defender-Serie (zwei neue Scannerprofile, geraetebezogene Risikoakzeptanz); `package.json`, Root-Eintraege in `package-lock.json` und die Versionsvertragstests sind synchron. Die `securityToolId`-Zeile aus `c056c21` folgt mit dem Result-DB-Werkzeugeintrag aus `21a6266`. Neue Profile, daher kein Reimport bestehender Importe; keine SQL-Migration. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 7/13A: `d9ff6a8` (geraetebezogene Risikoakzeptanz fuer Defender) angepasst uebernommen. Neben der bestehenden Akzeptanz des ganzen Findings gibt es die Ziele `device-finding` (ein Device mal eine CVE) und `device-snapshot` (alle aktuellen CVEs eines Device) mit unveraenderlichem Zielabbild und append-only Historie. Migration `028_scoped_risk_acceptances.sql` legt die projektgebundene Tabelle `assessor_scoped_risk_acceptances` an (Cascade bei Projektloeschung); beide Repositories, `GET`/`POST /api/v1/projects/:id/risk-acceptances/scoped`, App-State, Defender-Panel (Abdeckungszeile, deckende Entscheidungen, `Accept all current findings`), Finding Reader/Dialog und das Accepted-Risks-Register sind erweitert. Nutzerentscheidung 2026-10-02, abweichend vom Original: (1) Die Zaehlung `current` im Register bleibt bei der Basisregel `Accepted`, `Scheduled`, `Review due`, `Review required`. (2) Geraetebezogene Entscheidungen folgen der Regel fuer ganze Findings: sie decken nur die erfassten Occurrences; nach einem Reimport oder entfernten Scan-Lauf sowie bei einer nicht erfassten Occurrence desselben Device und derselben CVE bleibt die Entscheidung mit vollstaendiger Historie erhalten, zeigt `Review required` und deckt nichts. Der Marker `capturedScopeReplaced` wird bei jedem Lesen aus den aktuellen Scan-Laeufen abgeleitet und nie gespeichert. Offen: Ersetzte geraetebezogene Entscheidungen fliessen nicht in die Reimport-Transferzaehlung je Datei und damit nicht in den Data-Hub-Verarbeitungsstatus ein; das Defender-Panel listet nur deckende Entscheidungen. Neue Regressionen: Domain (erfasste gegen ersetzte Occurrence, Marker, Widerruf, keine Persistenz des Markers), lokales Repository (Reimport desselben Berichts mit geaenderter Device-Identitaet) und Register. Der opt-in PostgreSQL-Integrationstest lief nicht; der PostgreSQL-Lesepfad des Markers ist damit nur typgeprueft. Vor der Migration wurde das verifizierte Backup `backups/assessor-pre-028-20261002-132810.dump` erstellt. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.908 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 6/7: Defender-Teile von `b438f25` uebernommen (nach `c7c716c`, weil der Commit zunaechst nur als NVD-Korrektur eingeordnet war). CVE-, Device- und Site-Tabelle des Defender-Panels klappen ihre Details direkt unter der angeklickten Zeile auf und wieder zu (Dreieck-Schalter, `aria-expanded`/`aria-controls`); die CVE-Tabelle fuehrt die Beschreibung nur noch im Detail. Der Extractor gibt die Tabelle `Affected Devices` als `Name`, `IP Address`, `Scanner`, `Site` aus. Abweichend vom Original nutzt die aufgeklappte Zeile nach Nutzerentscheidung die blaue Tabellenzeilen-Markierung (`--table-row-marking`, `--table-row-marking-surface`) statt Gelb, und der Drilldown bleibt auf den gemeinsamen Kachelrollen. Nicht uebernommen: die `baseSeverity`-Korrektur im NVD-Client samt Test (CVE-Anreicherung ausgelassen) und der `.gitignore`-Eintrag. Kein Profilversionssprung wie im Original; die Tabellenspalten liegen bereits unter dem danach uebernommenen Profil 1.2.0, die lokale Datenbank enthaelt keine Defender-Importe. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.896 bestandenen und 17 uebersprungenen Tests (ein lastbedingter Einzelausfall in `node-import-engine.test.ts` im ersten Lauf, allein und im Wiederholungslauf bestanden), Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | diff --git a/src/styles/defender-for-iot.css b/src/styles/defender-for-iot.css index e8b1640..0ac3e13 100644 --- a/src/styles/defender-for-iot.css +++ b/src/styles/defender-for-iot.css @@ -2,9 +2,9 @@ .defender-header { order: 0; } .defender-source strong { color: var(--heading); font-weight: var(--fw-regular); } .defender-context { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin: 0; color: var(--muted); font-size: var(--type-meta-size); } -.defender-metrics { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); justify-content: start; gap: var(--space-2); min-width: 0; } +.defender-metrics { display: grid; grid-template-columns: repeat(auto-fill, var(--tile-compact-width)); justify-content: start; gap: var(--segment-gap); min-width: 0; padding: var(--segment-inset); border: var(--bw-hairline) solid var(--segment-border); border-radius: var(--segment-radius); background: var(--segment-surface); } .defender-metrics article { display: grid; align-content: center; gap: var(--space-2); min-width: 0; min-height: 88px; padding: var(--tile-inset); border: var(--border-hair) solid var(--tile-border); border-radius: var(--tile-radius); background: var(--tile-surface); box-shadow: none; } -.defender-metrics span { color: var(--muted); font-size: var(--type-label-size); font-weight: var(--fw-semibold); line-height: 1.3; } +.defender-metrics span { color: var(--muted); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); line-height: 1.3; } .defender-metrics strong { color: var(--heading); font-size: var(--type-h2-size); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; line-height: 1; } .defender-tabs, .client-info-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); } .defender-tabs button, .client-info-tabs button { min-height: 36px; padding: var(--space-2) var(--space-4); border: var(--border-hair) solid transparent; border-radius: var(--btn-radius); color: var(--text); background: transparent; font-size: var(--ui-control-size); font-weight: var(--fw-semibold); cursor: pointer; } @@ -17,7 +17,7 @@ .defender-view > header span { color: var(--muted); font-size: var(--type-meta-size); } .defender-table-wrap { min-width: 0; overflow-x: auto; background: var(--tile-surface); } .defender-table { width: 100%; border-collapse: collapse; table-layout: auto; color: var(--text); background: var(--tile-surface); font-size: var(--type-table-body-size); } -.defender-table th { padding: var(--space-2) var(--space-3); border-bottom: var(--border-hair) solid var(--line-strong); color: var(--heading); font-size: var(--type-label-size); font-weight: var(--fw-semibold); text-align: left; text-transform: uppercase; vertical-align: bottom; } +.defender-table th { padding: var(--space-2) var(--space-3); border-bottom: var(--border-hair) solid var(--line-strong); color: var(--heading); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); text-align: left; text-transform: uppercase; vertical-align: bottom; } .defender-table td { max-width: 440px; padding: var(--space-3); border-bottom: var(--border-hair) solid var(--line); line-height: 1.45; overflow-wrap: normal; word-break: normal; vertical-align: top; } .defender-table td > button { padding: 0; border: 0; color: var(--heading); background: transparent; font: inherit; font-weight: var(--fw-semibold); text-align: left; cursor: pointer; } .defender-table .defender-row-toggle { display: inline-flex; align-items: center; gap: var(--space-2); } @@ -25,7 +25,7 @@ .defender-table td > button:hover { background: var(--tile-surface-hover); } .defender-table td > button:focus-visible { outline: var(--bw-emphasis) solid var(--focus-ring); outline-offset: 2px; } .defender-selected-row > td { background: var(--table-row-marking-surface); } -.defender-selected-row > td:first-child { box-shadow: inset 4px 0 var(--table-row-marking); } +.defender-selected-row > td:first-child { box-shadow: inset var(--marker-width) 0 var(--table-row-marking); } .defender-detail-row > td { max-width: none; padding: 0 0 var(--space-3); border-bottom-color: var(--table-row-marking); } .defender-table small { display: block; margin-top: var(--space-1); color: var(--muted); font-size: var(--type-meta-size); } .defender-severity { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; } @@ -44,10 +44,8 @@ .defender-drilldown details > .defender-table, .defender-drilldown details > .defender-drilldown { margin-top: var(--space-3); } .defender-aggregate-summary, .defender-severity-strip { display: grid; grid-template-columns: repeat(auto-fill, var(--tile-compact-width)); justify-content: start; gap: var(--segment-gap); } .defender-aggregate-summary > span, .defender-severity-strip article { display: grid; gap: var(--space-1); min-width: var(--tile-compact-width); padding: var(--space-3); background: var(--surface-inset); } -.defender-aggregate-summary strong { color: var(--heading); font-size: var(--type-label-size); font-weight: var(--fw-semibold); } +.defender-aggregate-summary strong { color: var(--heading); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); } .defender-aggregate-summary span, .defender-severity-strip strong { font-size: var(--type-table-body-size); } .defender-severity-strip article { grid-template-columns: minmax(0, 1fr) auto; align-items: center; } .defender-severity-strip strong { color: var(--heading); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; } -@container (max-width: 1100px) { .defender-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); } } -@container (max-width: 760px) { .defender-view > header { display: grid; } .defender-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } } -@container (max-width: 480px) { .defender-metrics { grid-template-columns: 1fr; } } +@container (max-width: 760px) { .defender-view > header { display: grid; } } diff --git a/tests/features/defender-dashboard.test.ts b/tests/features/defender-dashboard.test.ts index 1543cee..2f59a86 100644 --- a/tests/features/defender-dashboard.test.ts +++ b/tests/features/defender-dashboard.test.ts @@ -57,9 +57,14 @@ describe('Defender vulnerability dashboard projection', () => { expect(dashboard).toContain('scopedRiskAcceptances={app.scopedRiskAcceptances} activeScanRunIds={app.activeScanRunIds}'); expect(dashboard).toContain('onScopedRiskAcceptance={app.backendStatus === \'connected\' ? write => app.recordScopedRiskAcceptance(write) : undefined}'); expect(styles).toContain('padding: var(--panel-inset)'); - expect(styles).toContain('grid-template-columns: repeat(6, minmax(0, 1fr))'); + expect(styles).toContain('grid-template-columns: repeat(auto-fill, var(--tile-compact-width))'); expect(styles).toContain('background: var(--tile-surface)'); + expect(styles).toMatch(/\.defender-metrics article \{[^}]*border-radius: var\(--tile-radius\)/); + expect(styles).toMatch(/\.defender-metrics span \{[^}]*font-size: var\(--type-table-body-size\)/); + expect(styles).toMatch(/\.defender-table th \{[^}]*font-size: var\(--type-table-body-size\)/); + expect(styles).toContain('box-shadow: inset var(--marker-width) 0 var(--table-row-marking)'); expect(styles).toMatch(/\.defender-drilldown \{[^}]*background: var\(--tile-surface\)/); + expect(styles).toMatch(/\.defender-drilldown \{[^}]*border-radius: var\(--tile-radius\)/); expect(styles).not.toMatch(/\.defender-drilldown \{[^}]*background: var\(--client-info-surface\)/); expect(styles).toContain('outline: var(--bw-emphasis) solid var(--marking-color)'); expect(styles).toContain('outline: var(--bw-emphasis) solid var(--focus-ring)');