Serverpuls: Admin-Seite mit Verlauf und Warnungen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-30 12:43:13 +00:00
1 parent ae905a5d9b
commit ec740ca970
12 files changed
+749 -3

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
import { useEffect, useRef } from 'react'
import uPlot from 'uplot'
import 'uplot/dist/uPlot.min.css'
/**
* Duenner uPlot-Wrapper.
* data: [xs (Sekunden), ys...]; series: [{label, color, fmt}] fuer die y-Reihen.
*/
export default function Chart({ data, series, height = 280, yFmt, yRange }) {
const host = useRef(null)
const plot = useRef(null)
const cfg = useRef({ series, yFmt, yRange })
cfg.current = { series, yFmt, yRange }
useEffect(() => {
const el = host.current
const { series: s, yFmt: fy, yRange: yr } = cfg.current
const fmtVal = (v) => (v === null || v === undefined ? '–' : (fy ? fy(v) : String(v)))
const opts = {
width: Math.max(el.clientWidth, 200),
height,
cursor: { drag: { x: false, y: false } },
legend: { live: true },
scales: { y: { range: yr || ((u, min, max) => [Math.min(0, min), max === min ? min + 1 : max + (max - min) * 0.08]) } },
axes: [
{ stroke: '#9aa7b4', grid: { stroke: 'rgba(255,255,255,0.06)' }, ticks: { stroke: 'rgba(255,255,255,0.1)' } },
{
stroke: '#9aa7b4',
grid: { stroke: 'rgba(255,255,255,0.06)' },
ticks: { stroke: 'rgba(255,255,255,0.1)' },
size: 70,
values: (u, vals) => vals.map(fmtVal),
},
],
series: [
{},
...s.map((x) => ({
label: x.label,
stroke: x.color || '#10b981',
width: 2,
spanGaps: false,
points: { show: false },
fill: x.fill,
dash: x.dash,
value: (u, v) => fmtVal(v),
})),
],
}
plot.current = new uPlot(opts, data, el)
const ro = new ResizeObserver(() => {
if (plot.current) plot.current.setSize({ width: Math.max(el.clientWidth, 200), height })
})
ro.observe(el)
return () => {
ro.disconnect()
plot.current?.destroy()
plot.current = null
}
// Neu aufbauen nur wenn sich Reihenanzahl/Beschriftung aendert
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [series.map((x) => x.label).join('|'), height])
useEffect(() => {
plot.current?.setData(data)
}, [data])
return <div className="sp-chart" data-testid="sp-chart" ref={host} />
}