diff --git a/src/pages/ServerpulsPage.jsx b/src/pages/ServerpulsPage.jsx index edd5e31..8a76c51 100644 --- a/src/pages/ServerpulsPage.jsx +++ b/src/pages/ServerpulsPage.jsx @@ -1,10 +1,11 @@ import { useCallback, useEffect, useRef, useMemo, useState } from 'react' import { Navigate } from 'react-router-dom' import { useAuth } from '../context/AuthContext' -import { PageHeader } from '../components/ui' +import { PageHeader, Badge, Stat } from '../components/ui' +import { badgeToneClass } from '../components/ui/tones' import { serverpulsApi } from '../lib/serverpulsApi' import { - ageLabel, formatBytes, formatNumber, formatPct, formatSince, usedPct, + ageLabel, deltaDir, deltaSign, formatBytes, formatNumber, formatPct, formatSince, levelTone, usedPct, } from '../lib/serverpulsFormat' import Chart from '../components/serverpuls/Chart' import { CHART } from '../components/serverpuls/chartColors' @@ -81,20 +82,17 @@ function Tile({ id, label, model }) { let deltaEl = null if (model && model.delta !== null && model.delta !== undefined) { const d = model.delta - const dir = Math.abs(d) < 1e-9 ? 'flat' : d > 0 ? 'up' : 'down' - const sign = dir === 'up' ? '▲ +' : dir === 'down' ? '▼ −' : '' + const dir = deltaDir(d) deltaEl = (
- {sign}{model.fmtDelta(d)} ggü. gestern + {deltaSign(dir)}{model.fmtDelta(d)} ggü. gestern
) } return (
-
{label}
-
{model ? model.main : '–'}
-
{model?.sub || ' '}
- + + {deltaEl}
) @@ -102,40 +100,44 @@ function Tile({ id, label, model }) { function Pills({ items, value, onChange, testid }) { return ( -
- {items.map((it) => ( - - ))} +
+ {items.map((it) => { + const active = value === it.id + return ( + + ) + })}
) } function Cleanup({ data }) { - if (!data || !data.day) return
Noch keine Messung.
+ if (!data || !data.day) return
Noch keine Messung.
const stopped = Array.isArray(data.stopped) ? data.stopped : [] return ( -
-
-
Ungenutzte Images{formatBytes(data.imagesBytes)}
-
Build-Cache{formatBytes(data.buildCacheBytes)}
-
Gestoppte Container{formatBytes(data.stoppedBytes)}
+ <> +
+ + +
{stopped.length > 0 && (
    {stopped.map((s) => ( -
  • {s.name}{formatBytes(s.bytes)}
  • +
  • {s.name}{formatBytes(s.bytes)}
  • ))}
)} -
Stand {data.day} · nur Anzeige, hier wird nichts gelöscht.
-
+

Stand {data.day} · nur Anzeige, hier wird nichts gelöscht.

+ ) } @@ -245,7 +247,6 @@ export default function ServerpulsPage() { const level = overview?.level || 'ok' const alerts = overview?.alerts || [] const age = ageLabel(overview?.lastSample ?? null, now) - const dot = level === 'critical' ? '🔴' : level === 'medium' ? '🟠' : '🟢' const headline = level === 'ok' ? 'Alles ok' : level === 'critical' @@ -256,14 +257,20 @@ export default function ServerpulsPage() {
- {error &&
{error}
} + {error &&
{error}
} -
+
- {dot} + + {overview ? (LEVEL_TEXT[level] || level) : '–'} + {overview ? headline : (error ? 'Keine Daten' : 'Lädt …')} {overview && ( - + Letzte Messung {age.text}{age.stale ? ' – Daten veraltet' : ''} )} @@ -271,46 +278,46 @@ export default function ServerpulsPage() { {alerts.length > 0 && (
    {alerts.map((a) => ( -
  • - {LEVEL_LABEL[a.level] || a.level} - {a.title} - {a.value} - seit {formatSince(a.since)} +
  • +
    + {LEVEL_LABEL[a.level] || a.level} + {a.title} + {a.value} +
    + seit {formatSince(a.since)}
  • ))}
)}
-
+
{METRICS.map((x) => ( ))}
-
-

Verlauf

-
- - -
+

Verlauf

+
+ +
{chartData && chartData.data[0].length > 0 ? - :
{chart ? 'Für diesen Zeitraum liegen keine Messwerte vor.' : 'Lädt …'}
} + :
{chart ? 'Für diesen Zeitraum liegen keine Messwerte vor.' : 'Lädt …'}
}
-
-

Projekte

- {appsData?.sizesDay && Größen von {appsData.sizesDay}} -
+

+ Projekte + {appsData?.sizesDay && Größen von {appsData.sizesDay}} +

{selected && (
-
-

{detail?.name || selected}

+

{detail?.name || selected}

+
{detailData ? ( @@ -319,13 +326,13 @@ export default function ServerpulsPage() {
RAM
CPU
formatPct(v)} height={200} />
- ) :
Lädt …
} + ) :
Lädt …
}
)}
-

Aufräumbar

+

Aufräumbar

diff --git a/src/styles/serverpuls.css b/src/styles/serverpuls.css index e662155..4cda93c 100644 --- a/src/styles/serverpuls.css +++ b/src/styles/serverpuls.css @@ -1,49 +1,30 @@ -/* Serverpuls (Admin) */ -.sp-page { display: flex; flex-direction: column; gap: var(--sp-4); } -.sp-error { background: var(--red-bg); border: 1px solid var(--danger-ring); color: var(--red-fg); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md); } -.sp-muted { color: var(--text-muted); } -.sp-small { font-size: 12px; } +/* Serverpuls (P9): Kopf, Warnungen, Kacheln, Umschalter, Panels. Die Tabellenregeln weiter unten gehoeren Task 5. */ +.sp-page .msg { margin: 0 0 16px; } -.sp-head { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-4); } -.sp-level-medium { border-color: var(--orange-fg); } -.sp-level-critical { border-color: var(--danger); } -.sp-head-top { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; } -.sp-headline { font-size: 18px; } -.sp-age { margin-left: auto; font-size: 13px; color: var(--text-muted); } -.sp-age-stale { color: var(--orange-fg); background: var(--orange-bg); padding: 2px 10px; border-radius: var(--r-pill); } -.sp-alerts { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; } -.sp-alert { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: baseline; padding: 8px 12px; border-radius: var(--r-sm); background: var(--surface-2); border-left: 3px solid var(--text-faint); } -.sp-alert-critical { border-left-color: var(--danger); } -.sp-alert-medium { border-left-color: var(--warn); } -.sp-alert-level { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); min-width: 56px; } -.sp-alert-title { font-weight: 600; } -.sp-alert-value { color: var(--text-muted); } +.sp-head-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } +.sp-headline { font-size: 16px; line-height: 24px; font-weight: 600; } +.sp-age { margin-left: auto; font-size: 13px; color: var(--text-2); } +.sp-age-stale { color: var(--orange-fg); font-weight: 500; } +.sp-alerts { list-style: none; margin: 12px 0 0; padding: 0; } +.sp-alert-main { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; min-width: 0; } +.sp-alert-title { font-weight: 500; overflow-wrap: anywhere; } +.sp-alert-value { color: var(--text-2); } -.sp-tiles { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--sp-3); } -@media (max-width: 1100px) { .sp-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } } -@media (max-width: 700px) { .sp-tiles { grid-template-columns: 1fr; } .sp-age { margin-left: 0; } } -.sp-tile { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-3) var(--sp-4); display: flex; flex-direction: column; gap: 2px; min-width: 0; } -.sp-tile-label { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); } -.sp-tile-value { font-size: 26px; font-weight: 800; } -.sp-tile-sub { font-size: 12px; color: var(--text-muted); min-height: 16px; } -.sp-tile-delta { font-size: 12px; margin-top: 2px; } +.stats.sp-tiles { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 24px 32px; } +.sp-tile { min-width: 0; } +.sp-tile .sub { min-height: 16px; } .sp-spark { display: block; max-width: 100%; margin-top: 6px; } +.sp-tile-delta { font-size: 12px; line-height: 16px; margin-top: 4px; } +.sp-delta-up { color: var(--red-fg); } +.sp-delta-down { color: var(--green-fg); } +.sp-delta-flat { color: var(--text-2); } -.sp-delta-up { color: var(--danger); } -.sp-delta-down { color: var(--ok); } -.sp-delta-flat { color: var(--text-muted); } - -.sp-panel { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-4); min-width: 0; } -.sp-panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); } -.sp-panel-head h3 { margin: 0; font-size: 16px; } -.sp-controls { display: flex; gap: var(--sp-3); flex-wrap: wrap; } -.sp-pills { display: flex; gap: 4px; flex-wrap: wrap; } -.sp-pill { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-muted); border-radius: var(--r-pill); padding: 4px 12px; font: inherit; font-size: 13px; cursor: pointer; } -.sp-pill:hover { color: var(--text); } -.sp-pill.active { background: var(--accent-soft); border-color: var(--border-accent); color: var(--accent); font-weight: 600; } +.sp-panel { min-width: 0; } +.sp-controls { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 12px; } +.sp-controls .seg { flex-wrap: wrap; } .sp-chart { width: 100%; min-height: 200px; } -.sp-chart .u-legend { color: var(--text-muted); font-size: 12px; } -.sp-nodata { padding: var(--sp-6, 32px) 0; text-align: center; } +.sp-chart .u-legend { font-size: 13px; color: var(--text-2); } +.sp-nodata { padding: 40px 0; text-align: center; color: var(--text-2); } .sp-search { width: 100%; max-width: 320px; margin-bottom: var(--sp-3); background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: var(--r-sm); padding: 8px 12px; font: inherit; } .sp-table-wrap { overflow-x: auto; } @@ -57,12 +38,19 @@ .sp-empty { text-align: center; color: var(--text-muted); } .sp-delta { font-variant-numeric: tabular-nums; } -.sp-detail { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--border); } -.sp-detail-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); gap: var(--sp-4); } -.sp-chart-title { font-size: 13px; color: var(--text-muted); margin-bottom: 4px; } +.sp-detail { margin-top: 28px; } +.sp-detail-title { margin: 0 0 8px; font-size: 15px; line-height: 22px; font-weight: 600; overflow-wrap: anywhere; } +.sp-detail-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); gap: 24px; } +.sp-chart-title { font-size: 13px; color: var(--text-2); margin-bottom: 4px; } -.sp-cleanup-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-3); } -.sp-cleanup-grid > div { display: flex; flex-direction: column; gap: 2px; } -.sp-cleanup-grid strong { font-size: 20px; } -.sp-cleanup-list { list-style: none; margin: var(--sp-3) 0; padding: 0; } -.sp-cleanup-list li { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--border); font-size: 14px; } +.sp-cleanup-list { list-style: none; margin: 12px 0; padding: 0; } + +@media (max-width: 1100px) { + .stats.sp-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } +} + +@media (max-width: 860px) { + .stats.sp-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 24px; } + .sp-age { margin-left: 0; } + .sp-page .sp-cleanup-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } +}