diff --git a/apps/web/src/App.css b/apps/web/src/App.css
deleted file mode 100644
index cfceaad..0000000
--- a/apps/web/src/App.css
+++ /dev/null
@@ -1,1117 +0,0 @@
-/* Grundlayout der Anwendung. */
-
-.app-shell {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
-}
-
-.app-shell__header {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- gap: var(--space-3);
- padding: var(--space-4) var(--space-6);
- border-bottom: var(--border-width) solid var(--color-border);
- background-color: var(--color-surface-raised);
-}
-
-.app-shell__brand {
- font-size: var(--text-lg);
- font-weight: 600;
- letter-spacing: -0.01em;
-}
-
-.app-shell__phase {
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
-}
-
-.app-shell__main {
- /* Die Breite ist begrenzt, damit Tabellen auf grossen Bildschirmen lesbar bleiben. */
- width: 100%;
- max-width: 60rem;
- margin: 0 auto;
- padding: var(--space-8) var(--space-6);
-}
-
-/* Auf schmalen Bildschirmen faellt der Innenabstand kleiner aus. */
-@media (max-width: 40rem) {
- .app-shell__main {
- padding: var(--space-6) var(--space-4);
- }
-}
-
-/* Kontobereich in der Kopfzeile. */
-
-.app-shell__account {
- /* Der Bereich rueckt an das rechte Ende der Kopfzeile. */
- margin-left: auto;
- display: flex;
- align-items: center;
- gap: var(--space-3);
-}
-
-.app-shell__username {
- font-size: var(--text-sm);
- font-weight: 500;
-}
-
-.app-shell__warning {
- font-size: var(--text-sm);
- color: var(--color-status-warning);
- border: var(--border-width) solid var(--color-status-warning);
- border-radius: var(--radius);
- padding: 0 var(--space-2);
-}
-
-.app-shell__logout {
- font: inherit;
- font-size: var(--text-sm);
- padding: var(--space-1) var(--space-3);
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius);
- background-color: var(--color-surface-page);
- color: var(--color-text-primary);
- cursor: pointer;
-}
-
-.app-shell__logout:hover {
- border-color: var(--color-text-secondary);
-}
-
-/* ---------------------------------------------------------------------------
- Grundlayout mit Seitenleiste
- --------------------------------------------------------------------------- */
-
-.app-shell__body {
- display: flex;
- flex: 1;
- align-items: flex-start;
-}
-
-/* Die Hauptflaeche ueberschreibt die schmale Breite aus dem Grundlayout:
- Tabellen mit sieben Spalten brauchen den Platz. */
-.app-shell__body .app-shell__main {
- max-width: none;
- flex: 1;
- min-width: 0;
-}
-
-/* ---------------------------------------------------------------------------
- Navigation
- --------------------------------------------------------------------------- */
-
-.navigation {
- flex: 0 0 15rem;
- padding: var(--space-6) var(--space-4);
- border-right: var(--border-width) solid var(--color-border);
- position: sticky;
- top: 0;
- align-self: stretch;
-}
-
-.navigation__section + .navigation__section {
- margin-top: var(--space-6);
-}
-
-.navigation__section-title {
- font-size: var(--text-xs);
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.06em;
- color: var(--color-text-secondary);
- margin: 0 0 var(--space-2) var(--space-2);
-}
-
-.navigation__list {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-.navigation__entry {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 2px;
- width: 100%;
- padding: var(--space-2) var(--space-3);
- border: none;
- border-radius: var(--radius-sm);
- background: none;
- color: var(--color-text-primary);
- font: inherit;
- text-align: left;
- cursor: pointer;
-}
-
-.navigation__entry:hover {
- background-color: var(--color-surface-raised);
-}
-
-.navigation__entry--current {
- background-color: var(--color-surface-raised);
- font-weight: 600;
-}
-
-/* Ein nicht verfuegbarer Bereich bleibt anklickbar: Der Klick fuehrt zur
- Erklaerung, was fehlt. Ein gesperrter Knopf sagt nur „nein". */
-.navigation__entry--unavailable .navigation__entry-label {
- color: var(--color-text-secondary);
-}
-
-.navigation__entry-badge {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
-}
-
-/* ---------------------------------------------------------------------------
- Seiten
- --------------------------------------------------------------------------- */
-
-.page + .page {
- margin-top: var(--space-8);
-}
-
-.page__header {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- gap: var(--space-3);
- margin-bottom: var(--space-5);
-}
-
-.page__title {
- font-size: var(--text-xl);
- margin: 0;
-}
-
-.page__meta,
-.page__badge {
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
-}
-
-.page__hint {
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
- max-width: 44rem;
-}
-
-.page__state {
- color: var(--color-text-secondary);
- padding: var(--space-6) 0;
-}
-
-.page__state--empty {
- padding: var(--space-8) 0;
- text-align: center;
-}
-
-/* ---------------------------------------------------------------------------
- Hinweise
- --------------------------------------------------------------------------- */
-
-.notice {
- border: var(--border-width) solid var(--color-border);
- border-left-width: 3px;
- border-radius: var(--radius-sm);
- padding: var(--space-4);
- margin-bottom: var(--space-5);
- max-width: 48rem;
-}
-
-.notice--information { border-left-color: var(--color-status-info); }
-.notice--warning { border-left-color: var(--color-status-warning); }
-.notice--critical { border-left-color: var(--color-status-critical); }
-
-.notice__text {
- margin: 0;
-}
-
-.notice__meta {
- margin: var(--space-2) 0 0;
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
- font-family: var(--font-mono, monospace);
-}
-
-/* ---------------------------------------------------------------------------
- Kennzahlen der Uebersicht
- --------------------------------------------------------------------------- */
-
-.widget-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
- gap: var(--space-4);
-}
-
-.widget {
- border: var(--border-width) solid var(--color-border);
- border-top-width: 3px;
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
- padding: var(--space-4);
-}
-
-/* Die Statusfarbe steht am oberen Rand — sie ist der schnellste Weg, eine
- Kachel mit Befund zu finden. */
-.widget--healthy { border-top-color: var(--color-status-healthy); }
-.widget--warning { border-top-color: var(--color-status-warning); }
-.widget--high { border-top-color: var(--color-status-high); }
-.widget--critical { border-top-color: var(--color-status-critical); }
-.widget--information { border-top-color: var(--color-status-info); }
-.widget--unavailable { border-top-color: var(--color-border); }
-
-.widget__title {
- font-size: var(--text-sm);
- font-weight: 600;
- color: var(--color-text-secondary);
- margin: 0 0 var(--space-2);
-}
-
-.widget__value {
- font-size: 1.75rem;
- font-weight: 600;
- margin: 0 0 var(--space-2);
- line-height: 1.1;
-}
-
-.widget__unit {
- font-size: var(--text-sm);
- font-weight: 400;
- color: var(--color-text-secondary);
-}
-
-.widget__value-missing,
-.widget__unavailable {
- font-size: var(--text-sm);
- font-weight: 500;
- color: var(--color-text-secondary);
-}
-
-.widget__detail {
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
- margin: 0;
-}
-
-.widget__breakdown {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-3);
- margin: var(--space-3) 0 0;
- padding-top: var(--space-3);
- border-top: var(--border-width) solid var(--color-border);
- font-size: var(--text-xs);
-}
-
-.widget__breakdown-item dt {
- color: var(--color-text-secondary);
-}
-
-.widget__breakdown-item dd {
- margin: 0;
- font-weight: 600;
-}
-
-/* ---------------------------------------------------------------------------
- Tabellen
- --------------------------------------------------------------------------- */
-
-/* Breite Tabellen scrollen in ihrem eigenen Rahmen; die Seite selbst nie. */
-.table-wrapper {
- overflow-x: auto;
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
-}
-
-.data-table {
- width: 100%;
- border-collapse: collapse;
- font-size: var(--text-sm);
-}
-
-.data-table th,
-.data-table td {
- padding: var(--space-3) var(--space-4);
- text-align: left;
- vertical-align: top;
- border-bottom: var(--border-width) solid var(--color-border);
-}
-
-.data-table th {
- font-weight: 600;
- color: var(--color-text-secondary);
- white-space: nowrap;
-}
-
-.data-table tbody tr:last-child td {
- border-bottom: none;
-}
-
-.data-table__number {
- text-align: right;
- font-variant-numeric: tabular-nums;
-}
-
-.data-table__code {
- font-family: var(--font-mono, monospace);
- font-size: var(--text-xs);
-}
-
-.data-table__secondary {
- display: block;
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
- word-break: break-all;
-}
-
-.data-table__secondary--warning {
- color: var(--color-status-warning);
-}
-
-.data-table__unknown {
- color: var(--color-text-secondary);
- font-style: italic;
-}
-
-/* Ein geloeschter Eintrag bleibt lesbar, aber erkennbar vergangen. */
-.data-table__row--deleted td {
- opacity: 0.6;
-}
-
-/* ---------------------------------------------------------------------------
- Statusanzeigen
- --------------------------------------------------------------------------- */
-
-.badge {
- display: inline-block;
- padding: 2px var(--space-2);
- border-radius: var(--radius-sm);
- font-size: var(--text-xs);
- font-weight: 600;
- white-space: nowrap;
- border: var(--border-width) solid currentColor;
-}
-
-.badge--healthy { color: var(--color-status-healthy); }
-.badge--warning { color: var(--color-status-warning); }
-.badge--high { color: var(--color-status-high); }
-.badge--critical { color: var(--color-status-critical); }
-.badge--info { color: var(--color-status-info); }
-.badge--neutral { color: var(--color-status-neutral); }
-
-/* ---------------------------------------------------------------------------
- Filterleiste
- --------------------------------------------------------------------------- */
-
-.filter-bar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--space-4);
- margin-bottom: var(--space-4);
-}
-
-.filter-bar__field {
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-
-.filter-bar__label {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
-}
-
-.filter-bar__select {
- padding: var(--space-2) var(--space-3);
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
- color: var(--color-text-primary);
- font: inherit;
-}
-
-.filter-bar__checkbox {
- display: flex;
- align-items: center;
- gap: var(--space-2);
- font-size: var(--text-sm);
-}
-
-/* ---------------------------------------------------------------------------
- Karten und Berechtigungen
- --------------------------------------------------------------------------- */
-
-.card-list {
- list-style: none;
- margin: 0;
- padding: 0;
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
- gap: var(--space-4);
-}
-
-.card {
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
- padding: var(--space-4);
-}
-
-.card__title {
- font-size: var(--text-base);
- margin: 0 0 var(--space-2);
-}
-
-.card__text {
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
- margin: 0 0 var(--space-3);
-}
-
-.permission-list {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-2);
-}
-
-.permission-list__item {
- font-family: var(--font-mono, monospace);
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- padding: 1px var(--space-2);
-}
-
-.button {
- padding: var(--space-2) var(--space-4);
- border-radius: var(--radius-sm);
- border: var(--border-width) solid var(--color-border);
- background-color: var(--color-surface-raised);
- color: var(--color-text-primary);
- font: inherit;
- cursor: pointer;
-}
-
-.button--secondary:hover {
- border-color: var(--color-text-secondary);
-}
-
-/* ---------------------------------------------------------------------------
- Schmale Bildschirme
- --------------------------------------------------------------------------- */
-
-/* Unter 55rem wandert die Navigation ueber den Inhalt: Eine Seitenleiste von
- 15rem laesst auf einem Tablet quer zu wenig fuer eine Tabelle uebrig. */
-@media (max-width: 55rem) {
- .app-shell__body {
- flex-direction: column;
- }
-
- .navigation {
- flex: none;
- width: 100%;
- position: static;
- border-right: none;
- border-bottom: var(--border-width) solid var(--color-border);
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-4);
- padding: var(--space-3) var(--space-4);
- }
-
- .navigation__section + .navigation__section {
- margin-top: 0;
- }
-
- .navigation__list {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-1, 4px);
- }
-}
-
-/* ---------------------------------------------------------------------------
- Diagramme
- --------------------------------------------------------------------------- */
-
-.chart-list {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(26rem, 1fr));
- gap: var(--space-4);
-}
-
-.chart-card {
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
- padding: var(--space-4);
-}
-
-.chart-card--unavailable {
- opacity: 0.75;
-}
-
-.chart-card__title {
- font-size: var(--text-base);
- margin: 0 0 var(--space-1, 4px);
-}
-
-.chart-card__description,
-.chart-card__note,
-.chart-card__badge {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
- margin: 0 0 var(--space-3);
-}
-
-.chart-card__badge {
- font-weight: 600;
-}
-
-/* Der Hinweis zur Aussagekraft steht unter der Kurve und ist abgesetzt: Er
- gehoert zur Lesart des Diagramms, nicht zu seiner Beschreibung. */
-.chart-card__note {
- margin: var(--space-2) 0 0;
- padding-top: var(--space-2);
- border-top: var(--border-width) solid var(--color-border);
-}
-
-.chart {
- margin: 0;
-}
-
-.chart__canvas {
- width: 100%;
- height: 13rem;
- display: block;
-}
-
-.chart__grid line {
- stroke: var(--color-border);
- stroke-width: 1;
- vector-effect: non-scaling-stroke;
-}
-
-.chart__grid text {
- fill: var(--color-text-secondary);
- font-size: 11px;
-}
-
-/* Die Linie behaelt ihre Staerke, obwohl das SVG in der Breite gestreckt wird.
- Ohne diese Angabe waeren waagerechte und senkrechte Linien verschieden dick. */
-.chart__line {
- stroke-width: 2;
- stroke-linejoin: round;
- stroke-linecap: round;
- vector-effect: non-scaling-stroke;
-}
-
-.chart__empty {
- font-size: var(--text-sm);
- color: var(--color-text-secondary);
- padding: var(--space-6) 0;
- margin: 0;
-}
-
-.chart__legend {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-3);
- margin-top: var(--space-2);
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
-}
-
-.chart__legend-item {
- display: flex;
- align-items: center;
- gap: var(--space-2);
-}
-
-.chart__legend-swatch {
- width: 10px;
- height: 3px;
- border-radius: 2px;
-}
-
-/* ---------------------------------------------------------------------------
- Meldungen
- --------------------------------------------------------------------------- */
-
-.summary-bar {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-5);
- padding: var(--space-4);
- margin-bottom: var(--space-4);
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
-}
-
-.summary-figure {
- display: flex;
- flex-direction: column;
-}
-
-.summary-figure__value {
- font-size: 1.5rem;
- font-weight: 600;
- line-height: 1.1;
-}
-
-.summary-figure__label {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
-}
-
-/* Farbe nur, wenn die Zahl ein Zustand ist: Null kritische Meldungen sind
- keine gute Nachricht in Rot. */
-.summary-figure--critical .summary-figure__value { color: var(--color-status-critical); }
-.summary-figure--high .summary-figure__value { color: var(--color-status-high); }
-
-.alert-list {
- list-style: none;
- margin: 0 0 var(--space-8);
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: var(--space-3);
-}
-
-.alert-card {
- border: var(--border-width) solid var(--color-border);
- border-left-width: 3px;
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
- padding: var(--space-4);
-}
-
-.alert-card--critical { border-left-color: var(--color-status-critical); }
-.alert-card--high { border-left-color: var(--color-status-high); }
-.alert-card--warning { border-left-color: var(--color-status-warning); }
-.alert-card--information { border-left-color: var(--color-status-info); }
-
-/* Eine erledigte Meldung bleibt lesbar, aber erkennbar vergangen. */
-.alert-card--resolved {
- opacity: 0.6;
- border-left-color: var(--color-border);
-}
-
-.alert-card__head {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--space-3);
- margin-bottom: var(--space-2);
-}
-
-.alert-card__title {
- font-size: var(--text-base);
- margin: 0;
- flex: 1;
- min-width: 12rem;
-}
-
-.alert-card__count {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
-}
-
-.alert-card__message {
- margin: 0 0 var(--space-2);
- max-width: 60rem;
-}
-
-.alert-card__meta {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
- margin: 0;
-}
-
-.alert-card__actions {
- display: flex;
- gap: var(--space-2);
- margin-top: var(--space-3);
-}
-
-/* ---------------------------------------------------------------------------
- Regelwerk
- --------------------------------------------------------------------------- */
-
-.rule-list__title {
- font-size: var(--text-base);
- margin: 0 0 var(--space-3);
-}
-
-.rule-list__items {
- list-style: none;
- margin: 0;
- padding: 0;
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
- gap: var(--space-3);
-}
-
-.rule-list__item {
- border: var(--border-width) solid var(--color-border);
- border-radius: var(--radius-sm);
- padding: var(--space-3);
-}
-
-.rule-list__item--unavailable {
- opacity: 0.7;
-}
-
-.rule-list__head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: var(--space-2);
- margin-bottom: var(--space-2);
-}
-
-.rule-list__name {
- font-weight: 600;
- font-size: var(--text-sm);
-}
-
-.rule-list__description {
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
- margin: 0;
-}
-
-/* ---------------------------------------------------------------------------
- Security Center
- --------------------------------------------------------------------------- */
-
-.score-card {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--space-6);
- padding: var(--space-5);
- margin-bottom: var(--space-5);
- border: var(--border-width) solid var(--color-border);
- border-left-width: 4px;
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
-}
-
-.score-card--healthy { border-left-color: var(--color-status-healthy); }
-.score-card--warning { border-left-color: var(--color-status-warning); }
-.score-card--high { border-left-color: var(--color-status-high); }
-.score-card--critical { border-left-color: var(--color-status-critical); }
-
-.score-card__figure {
- display: flex;
- align-items: baseline;
- gap: 2px;
-}
-
-.score-card__value {
- font-size: 3rem;
- font-weight: 600;
- line-height: 1;
-}
-
-.score-card__unit {
- font-size: var(--text-lg);
- color: var(--color-text-secondary);
-}
-
-.score-card__text {
- flex: 1;
- min-width: 16rem;
-}
-
-.score-card__grade {
- font-weight: 600;
- margin: 0 0 var(--space-1, 4px);
- text-transform: capitalize;
-}
-
-.score-card__summary {
- margin: 0;
- color: var(--color-text-secondary);
-}
-
-.score-card__unchecked {
- margin: var(--space-2) 0 0;
- font-size: var(--text-xs);
- color: var(--color-text-secondary);
-}
-
-.finding-list {
- list-style: none;
- margin: 0 0 var(--space-8);
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: var(--space-3);
-}
-
-.finding {
- border: var(--border-width) solid var(--color-border);
- border-left-width: 3px;
- border-radius: var(--radius-sm);
- background-color: var(--color-surface-raised);
- padding: var(--space-4);
-}
-
-.finding--critical { border-left-color: var(--color-status-critical); }
-.finding--high { border-left-color: var(--color-status-high); }
-.finding--warning { border-left-color: var(--color-status-warning); }
-.finding--information { border-left-color: var(--color-status-info); }
-
-.finding__head {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--space-3);
- margin-bottom: var(--space-2);
-}
-
-.finding__title {
- font-size: var(--text-base);
- margin: 0;
-}
-
-.finding__explanation {
- margin: 0 0 var(--space-3);
- max-width: 60rem;
-}
-
-.finding__details {
- margin: 0;
- display: flex;
- flex-direction: column;
- gap: var(--space-2);
-}
-
-.finding__detail {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-2);
- font-size: var(--text-sm);
-}
-
-.finding__detail dt {
- color: var(--color-text-secondary);
- min-width: 8rem;
-}
-
-.finding__detail dd {
- margin: 0;
- flex: 1;
- min-width: 16rem;
-}
-
-/* Die Empfehlung ist der Grund, warum der Befund angezeigt wird — sie steht
- hervorgehoben und nicht als Fussnote. */
-.finding__detail--recommendation dd {
- font-weight: 500;
-}
-
-.area-overview__title {
- font-size: var(--text-base);
- margin: 0 0 var(--space-3);
-}
-
-/* --- Berichte (Phase 17) --- */
-
-.reports__catalog {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
- gap: var(--space-3);
- margin-bottom: var(--space-5);
-}
-
-.reports__entry {
- display: flex;
- flex-direction: column;
- gap: var(--space-2);
- padding: var(--space-4);
- text-align: left;
- background: var(--color-surface-raised);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-md);
- cursor: pointer;
- font: inherit;
- color: inherit;
-}
-
-.reports__entry:hover {
- border-color: var(--color-text-secondary);
-}
-
-/* Die Auswahl wird ueber die Randstaerke gezeigt, nicht ueber eine Farbe:
- Farbe ist den Zustaenden vorbehalten (UI-Konvention). */
-.reports__entry--selected {
- border-width: 2px;
- border-color: var(--color-text-primary);
- padding: calc(var(--space-4) - 1px);
-}
-
-.reports__entry-title {
- font-weight: 600;
-}
-
-.reports__entry-description,
-.reports__entry-period {
- font-size: 0.85rem;
- color: var(--color-text-secondary);
-}
-
-.reports__panel {
- padding: var(--space-4);
- background: var(--color-surface-raised);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-md);
-}
-
-.reports__panel-header {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-3);
- align-items: center;
- justify-content: space-between;
- margin-bottom: var(--space-4);
-}
-
-.reports__panel-title {
- margin: 0;
- font-size: 1.25rem;
-}
-
-.reports__actions {
- display: flex;
- gap: var(--space-2);
-}
-
-.reports__error {
- padding: var(--space-3);
- margin-bottom: var(--space-4);
- border-left: 3px solid var(--color-status-critical);
- background: var(--color-surface-page);
-}
-
-.reports__request-id {
- display: block;
- margin-top: var(--space-1);
- font-size: 0.8rem;
- color: var(--color-text-secondary);
-}
-
-.reports__meta {
- margin: 0 0 var(--space-4);
- font-size: 0.85rem;
- color: var(--color-text-secondary);
-}
-
-.reports__section {
- margin-bottom: var(--space-5);
-}
-
-.reports__section-title {
- margin: 0 0 var(--space-2);
- font-size: 1.05rem;
-}
-
-.reports__section-description {
- margin: 0 0 var(--space-3);
- font-size: 0.9rem;
- color: var(--color-text-secondary);
-}
-
-.reports__metrics {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
- gap: var(--space-2) var(--space-5);
- margin: 0 0 var(--space-4);
-}
-
-.reports__metric {
- display: flex;
- gap: var(--space-3);
- justify-content: space-between;
- align-items: baseline;
- padding-bottom: var(--space-1);
- border-bottom: 1px solid var(--color-border);
-}
-
-.reports__metric-label {
- font-size: 0.9rem;
-}
-
-.reports__metric-value {
- margin: 0;
- font-weight: 600;
- text-align: right;
-}
-
-/* Eine ungemessene Kennzahl wird zurueckgenommen dargestellt und nennt ihren
- Grund. Sie als Null zu zeigen waere der eine Fehler, den ein Bericht nicht
- machen darf. */
-.reports__metric-value--unknown {
- font-weight: 400;
- font-style: italic;
- color: var(--color-text-secondary);
-}
-
-.reports__metric-reason {
- display: block;
- margin-top: var(--space-1);
- font-size: 0.78rem;
- font-style: normal;
- line-height: 1.4;
-}
-
-.reports__table {
- margin-bottom: var(--space-4);
-}
-
-.reports__table-title {
- margin: 0 0 var(--space-2);
- font-size: 0.95rem;
-}
-
-/* Breite Tabellen scrollen in sich selbst; die Seite bleibt ruhig. */
-.reports__table-scroll {
- overflow-x: auto;
-}
-
-.reports__empty,
-.reports__truncation {
- margin: var(--space-2) 0 0;
- font-size: 0.85rem;
- color: var(--color-text-secondary);
-}
-
-.reports__notes {
- padding-top: var(--space-3);
- border-top: 1px solid var(--color-border);
-}
-
-.reports__note-list {
- margin: 0;
- padding-left: var(--space-4);
- font-size: 0.85rem;
- line-height: 1.5;
- color: var(--color-text-secondary);
-}
-
-.reports__note-list li {
- margin-bottom: var(--space-2);
-}
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 1cf1c07..d6ae84c 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -17,6 +17,7 @@ import { getAccessToken, logout } from './features/auth/authApi';
import { LoginPage } from './features/auth/LoginPage';
import { AlertsPage } from './features/alerts/AlertsPage';
import { DashboardPage } from './features/dashboard/DashboardPage';
+import { SystemHealthPanel } from './features/health/SystemHealthPanel';
import { RecoveryPointsPage } from './features/dashboard/RecoveryPointsPage';
import { RolesPage } from './features/administration/RolesPage';
import { SettingsPage } from './features/administration/SettingsPage';
@@ -40,12 +41,6 @@ import { DEFAULT_PAGE_ID, findPage, mayViewPage } from './navigation/pages';
import { useCurrentPage } from './navigation/useCurrentPage';
import type { CurrentUser } from './types/auth';
import './styles/theme.css';
-// Die noch nicht umgebauten Seiten nutzen weiterhin das alte Stylesheet. Es
-// hier stehen zu lassen ist unschoen und die einzige Alternative zu einer
-// Oberflaeche, die auf halbem Weg unformatiert erscheint. Es faellt weg,
-// sobald die letzte Seite umgebaut ist.
-import './styles/tokens.css';
-import './App.css';
// Der API-Client erhaelt seinen Tokenzugriff einmalig beim Laden des Moduls.
setAccessTokenProvider(getAccessToken);
@@ -142,7 +137,14 @@ function CurrentPageContent({
switch (pageDefinition.id) {
case 'dashboard':
- return
- {what} werden geladen… -
- ); -} - -/** Eigenschaften der Fehleranzeige. */ -interface ErrorStateProperties { - /** Der aufgetretene Fehler. */ - readonly error: ApiError; - /** Laedt erneut. */ - readonly onRetry: () => void; -} - -/** Zeigt einen Fehler samt Request-ID. */ -export function ErrorState({ error, onRetry }: ErrorStateProperties): React.JSX.Element { - return ( -{error.message}
- - {/* Die Request-ID gehoert sichtbar in die Oberflaeche: Mit ihr laesst sich - ein Vorfall im Serverlog eindeutig wiederfinden (PROMPT.md §50). */} - {error.requestId !== '' && ( -- Fehlercode {error.code} · Vorgang {error.requestId} -
- )} - - -{message}
; -} diff --git a/apps/web/src/components/StatusIndicator.css b/apps/web/src/components/StatusIndicator.css deleted file mode 100644 index 7a511d8..0000000 --- a/apps/web/src/components/StatusIndicator.css +++ /dev/null @@ -1,20 +0,0 @@ -/* Darstellung der Statusanzeige. */ - -.status-indicator { - display: inline-flex; - align-items: center; - gap: var(--space-2); -} - -.status-indicator__dot { - /* Feste Groesse, damit der Punkt in Tabellen nicht springt. */ - width: 0.625rem; - height: 0.625rem; - border-radius: 50%; - flex-shrink: 0; -} - -.status-indicator__label { - font-size: var(--text-sm); - color: var(--color-text-primary); -} diff --git a/apps/web/src/components/StatusIndicator.tsx b/apps/web/src/components/StatusIndicator.tsx deleted file mode 100644 index 8d68346..0000000 --- a/apps/web/src/components/StatusIndicator.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Statusanzeige des Syncova-Designsystems (PROMPT.md §105/§106). - * - * Der Zustand wird nicht allein ueber Farbe vermittelt, sondern immer zusaetzlich - * ueber Text. Farbe allein waere fuer farbfehlsichtige Anwender unzugaenglich - * (PROMPT.md §107). - */ - -import type { HealthStatus } from '../types/api'; -import './StatusIndicator.css'; - -/** Zuordnung von Zustand zu Anzeigetext und Farbvariable. */ -const STATUS_PRESENTATION: Record