Welle 1 · Vorhersage „voll in etwa N Tagen“: Pool, NAS, Systemplatte, Container, VMs und die Platte der KI-Box
Aus dem Verlauf der Woche (Tagesmittel, Schutz gegen einmalige Sprünge, schneller werdendes Volllaufen zählt sofort): gelb unter 14, rot unter 3 Tagen. Der Wächter meldet es, auch wenn der Füllstand selbst noch unauffällig ist; Speicherkarte, Geräte-Tabelle, Instrumente der Übersicht und die KI-Box-Seite zeigen es an. Jeder Bericht des Ausführers schreibt dafür die Belegungen in die Messreihe „speicher“. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
34360c9644
commit
f7a9b8ad50
@@ -15,6 +15,7 @@ export function Instrument({
|
||||
unter,
|
||||
beschriftung,
|
||||
ariaText,
|
||||
hinweis,
|
||||
}: {
|
||||
anteil: number | null
|
||||
farbe?: string
|
||||
@@ -23,6 +24,8 @@ export function Instrument({
|
||||
unter?: string
|
||||
beschriftung: string
|
||||
ariaText: string
|
||||
/** Eine Zeile unter der Beschriftung, etwa „voll in etwa 9 Tagen“ (seit 25.09.2026); className setzt die Farbe. */
|
||||
hinweis?: { text: string; className: string } | null
|
||||
}) {
|
||||
const zeigen = anteil != null && anteil > 0.004
|
||||
return (
|
||||
@@ -46,6 +49,7 @@ export function Instrument({
|
||||
)}
|
||||
</svg>
|
||||
<figcaption className="etikett text-[11.5px] tracking-[0.14em] text-text-3">{beschriftung}</figcaption>
|
||||
{hinweis && <span className={`text-center text-xs ${hinweis.className}`}>{hinweis.text}</span>}
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { CSSProperties } from "react"
|
||||
import { cn } from "cn"
|
||||
import type { Bereich } from "@/lib/navigation"
|
||||
import { type Einheit, wertText } from "@/lib/messung"
|
||||
import { STUFE_FARBE, type VorhersageStufe } from "@/lib/prognose"
|
||||
import type { Messpunkt } from "@/lib/typen"
|
||||
import { Sparkline } from "./Sparkline"
|
||||
|
||||
@@ -26,7 +27,9 @@ function randpunkt(anteil: number, r: number) {
|
||||
return { x: MITTE + r * Math.cos(winkel), y: MITTE + r * Math.sin(winkel) }
|
||||
}
|
||||
|
||||
export function Messring({ label, wert, einheit, punkte, zustand = "ruhig", bereich, grenzen, max = 100, zeitraumText, i = 0 }: {
|
||||
export function Messring({
|
||||
label, wert, einheit, punkte, zustand = "ruhig", bereich, grenzen, max = 100, zeitraumText, i = 0, hinweis,
|
||||
}: {
|
||||
label: string
|
||||
wert: number | null | undefined
|
||||
einheit: Einheit
|
||||
@@ -39,6 +42,8 @@ export function Messring({ label, wert, einheit, punkte, zustand = "ruhig", bere
|
||||
zeitraumText: string
|
||||
/** Reihenfolge für das Auftauchen. */
|
||||
i?: number
|
||||
/** Eine Zeile unter der Linie, etwa „voll in etwa 9 Tagen“ (seit 25.09.2026). */
|
||||
hinweis?: { text: string; stufe: VorhersageStufe } | null
|
||||
}) {
|
||||
const z = ZUSTAND[zustand]
|
||||
const farbe = zustand === "rot" ? "var(--rot)" : zustand === "gelb" ? "var(--bernstein)" : bereich === "box" ? "var(--box)" : "var(--lab)"
|
||||
@@ -77,6 +82,7 @@ export function Messring({ label, wert, einheit, punkte, zustand = "ruhig", bere
|
||||
? <Sparkline punkte={punkte} max={max} farbe={farbe} hoehe={20} beschriftung={`${label}, ${zeitraumText}`} />
|
||||
: <span className="block h-5" aria-hidden />}
|
||||
</div>
|
||||
{hinweis && <span className={cn("text-center text-[11px] leading-tight", STUFE_FARBE[hinweis.stufe])}>{hinweis.text}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Geraetesymbol } from "@/components/cockpit/Geraetesymbol"
|
||||
import { geraetebild } from "@/lib/geraetebild"
|
||||
import { artText, plattenAnteil } from "@/lib/homelab"
|
||||
import { laufzeit } from "@/lib/messung"
|
||||
import { knappeVorhersage, STUFE_FARBE } from "@/lib/prognose"
|
||||
import type { GeraetMesswerte, ZielStand } from "@/lib/typen"
|
||||
|
||||
const SPALTEN = "md:grid-cols-[minmax(0,1.35fr)_110px_minmax(0,1fr)_minmax(0,1.2fr)_minmax(0,1fr)_90px]"
|
||||
@@ -11,8 +12,14 @@ const SPALTEN = "md:grid-cols-[minmax(0,1.35fr)_110px_minmax(0,1fr)_minmax(0,1.2
|
||||
const gb = (b: number) => (b / 1e9).toFixed(1).replace(".", ",")
|
||||
|
||||
/** Balken mit Wert (v5): ruhig in der Farbe des Homelabs, ab gelb bernstein, ab rot rot — der Text sagt es zusätzlich
|
||||
* in Zahlen. */
|
||||
function Balken({ wert, gelb, rot, zusatz }: { wert: number | null | undefined; gelb: number; rot: number; zusatz?: string }) {
|
||||
* in Zahlen. Darunter auf Wunsch eine Zeile (bei der Platte: „voll in etwa 9 Tagen“, seit 25.09.2026). */
|
||||
function Balken({ wert, gelb, rot, zusatz, unter }: {
|
||||
wert: number | null | undefined
|
||||
gelb: number
|
||||
rot: number
|
||||
zusatz?: string
|
||||
unter?: { text: string; stufe: keyof typeof STUFE_FARBE } | null
|
||||
}) {
|
||||
if (wert === null || wert === undefined) return <span className="text-sm text-text-3">–</span>
|
||||
const farbe = wert >= rot ? "var(--rot)" : wert >= gelb ? "var(--bernstein)" : "color-mix(in srgb, var(--lab) 80%, transparent)"
|
||||
const text = wert >= rot ? "text-rot-text" : wert >= gelb ? "text-bernstein" : "text-text-2"
|
||||
@@ -24,6 +31,7 @@ function Balken({ wert, gelb, rot, zusatz }: { wert: number | null | undefined;
|
||||
<span className="h-1.5 w-full max-w-40 overflow-hidden rounded-full bg-white/[0.07]" aria-hidden>
|
||||
<span className="block h-full rounded-full" style={{ width: `${Math.min(100, Math.max(2, wert))}%`, background: farbe }} />
|
||||
</span>
|
||||
{unter && <span className={cn("text-xs", STUFE_FARBE[unter.stufe])}>{unter.text}</span>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -43,6 +51,7 @@ function rang(z: ZielStand): number {
|
||||
*/
|
||||
export function Gesundheit({ ziele, messwerte }: { ziele: ZielStand[]; messwerte: GeraetMesswerte[] | undefined }) {
|
||||
const mess = new Map((messwerte ?? []).map((g) => [g.id, g.aktuell]))
|
||||
const prognose = new Map((messwerte ?? []).map((g) => [g.id, g.prognose?.platte]))
|
||||
const sortiert = [...ziele].sort((a, b) => rang(a) - rang(b))
|
||||
return (
|
||||
<div role="table" aria-label="Zustand der Geräte" className="flex flex-col">
|
||||
@@ -77,7 +86,7 @@ export function Gesundheit({ ziele, messwerte }: { ziele: ZielStand[]; messwerte
|
||||
</span>
|
||||
<span role="cell"><span className="text-xs text-text-3 md:hidden">CPU </span><Balken wert={a?.cpu} gelb={85} rot={95} /></span>
|
||||
<span role="cell"><span className="text-xs text-text-3 md:hidden">Speicher </span><Balken wert={a?.ram} gelb={85} rot={95} zusatz={ram} /></span>
|
||||
<span role="cell"><span className="text-xs text-text-3 md:hidden">Platte </span><Balken wert={platte} gelb={80} rot={90} /></span>
|
||||
<span role="cell"><span className="text-xs text-text-3 md:hidden">Platte </span><Balken wert={platte} gelb={80} rot={90} unter={knappeVorhersage(prognose.get(z.id))} /></span>
|
||||
<span role="cell" className="ziffern text-sm text-text-2"><span className="font-sans text-xs text-text-3 md:hidden">Läuft seit </span>{laufzeit(a?.uptime_s)}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -35,6 +35,21 @@ describe("Speicher", () => {
|
||||
expect(screen.getByText(/Alle 8 Geräte gesichert/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("sagt, wann es bei diesem Tempo voll ist (seit 25.09.2026)", () => {
|
||||
render(<Speicher daten={{
|
||||
...daten,
|
||||
pools: [{ ...daten.pools[0], vorhersage: { tage: 9.4, rate: 5.4, anteil: 49, grund: null } }],
|
||||
systemplatte: { ...daten.systemplatte!, vorhersage: { tage: null, rate: 0, anteil: 50, grund: "gleich" } },
|
||||
nas: [{ ...daten.nas[0], vorhersage: { tage: null, rate: null, anteil: null, grund: "verlauf" } }],
|
||||
sicherungen: { ...daten.sicherungen, ziele: [{ speicher: "pbs-qnap", art: "pbs", aktiv: true, belegt: 1_517_685_571_584,
|
||||
gesamt: 3_847_503_609_856, vorhersage: { tage: 200, rate: 0.3, anteil: 39, grund: null } }] },
|
||||
}} />)
|
||||
const pool = screen.getByText("+5,4 % am Tag · voll in etwa 9 Tagen")
|
||||
expect(pool).toHaveClass("text-bernstein")
|
||||
expect(screen.getByText("Belegung gleichbleibend")).toBeInTheDocument()
|
||||
expect(screen.getByText("Ziel pbs-qnap: 39 % belegt · voll in etwa 7 Monaten")).toHaveClass("text-text-3")
|
||||
})
|
||||
|
||||
it("klappt die Vorschläge erst auf, wenn der Pool knapp wird", () => {
|
||||
const { container, rerender } = render(<Speicher daten={daten} />)
|
||||
const klappe = container.querySelector("details")!
|
||||
|
||||
@@ -4,9 +4,11 @@ import { cn } from "cn"
|
||||
import { Bestaetigen } from "@/components/cockpit/Bestaetigen"
|
||||
import { Chip, type ChipArt } from "@/components/cockpit/Chip"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { STUFE_FARBE, tempoText, vollText, vorhersageStufe } from "@/lib/prognose"
|
||||
import { auftraegeText, groesse, STUFE_TEXT } from "@/lib/speicher"
|
||||
import type {
|
||||
HomelabSpeicher, NasStand, SicherungsLage, SpeicherEmpfehlung, SpeicherPool, SpeicherStufe, Systemplatte, ZielAktion,
|
||||
HomelabSpeicher, NasStand, SicherungsLage, SpeicherEmpfehlung, SpeicherPool, SpeicherStufe, Systemplatte, Vorhersage,
|
||||
ZielAktion,
|
||||
} from "@/lib/typen"
|
||||
import { flugplanZeit } from "@/lib/zeit"
|
||||
|
||||
@@ -35,6 +37,15 @@ function Fuellstand({ anteil, stufe, name }: { anteil: number; stufe: SpeicherSt
|
||||
)
|
||||
}
|
||||
|
||||
/** Das Tempo der Belegung (seit 25.09.2026): „+1,2 % am Tag · voll in etwa 9 Tagen“, unter 14 Tagen bernstein, unter
|
||||
* 3 Tagen rot. Ohne zwei Tage Verlauf steht nichts da. */
|
||||
function Tempo({ v }: { v?: Vorhersage | null }) {
|
||||
if (v?.grund === "gleich") return <p className="text-xs text-text-3">Belegung gleichbleibend</p>
|
||||
const voll = vollText(v)
|
||||
if (!voll) return null
|
||||
return <p className={cn("ziffern text-xs", STUFE_FARBE[vorhersageStufe(v)])}>{tempoText(v)} · {voll}</p>
|
||||
}
|
||||
|
||||
function Kachel({ titel, unter, chip, chipText, children }: {
|
||||
titel: string
|
||||
unter?: string
|
||||
@@ -67,6 +78,7 @@ function PoolKachel({ p }: { p: SpeicherPool }) {
|
||||
{p.meta_anteil !== null && (
|
||||
<p className="text-xs text-text-3">Metadaten {Math.round(p.meta_anteil * 100)} % belegt</p>
|
||||
)}
|
||||
<Tempo v={p.vorhersage} />
|
||||
</Kachel>
|
||||
)
|
||||
}
|
||||
@@ -78,6 +90,7 @@ function SystemplatteKachel({ s }: { s: Systemplatte }) {
|
||||
<p className="ziffern text-sm text-text-2">
|
||||
{Math.round(s.anteil * 100)} % · {groesse(s.belegt)} von {groesse(s.gesamt)} · {groesse(s.frei)} frei
|
||||
</p>
|
||||
<Tempo v={s.vorhersage} />
|
||||
</Kachel>
|
||||
)
|
||||
}
|
||||
@@ -100,6 +113,7 @@ function NasKachel({ n }: { n: NasStand }) {
|
||||
<p className="ziffern text-sm text-text-2">
|
||||
{Math.round(n.anteil * 100)} % · {groesse(n.belegt)} von {groesse(n.gesamt)} · {groesse((n.gesamt ?? 0) - (n.belegt ?? 0))} frei
|
||||
</p>
|
||||
<Tempo v={n.vorhersage} />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
@@ -131,6 +145,15 @@ function SicherungsKachel({ s }: { s: SicherungsLage }) {
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{s.ziele.filter((z) => z.gesamt).map((z) => {
|
||||
const voll = vollText(z.vorhersage)
|
||||
return (
|
||||
<p key={z.speicher} className={cn("ziffern text-xs", voll ? STUFE_FARBE[vorhersageStufe(z.vorhersage)] : "text-text-3")}>
|
||||
Ziel {z.speicher}: {Math.round(((z.belegt ?? 0) / (z.gesamt ?? 1)) * 100)} % belegt
|
||||
{voll ? ` · ${voll}` : z.vorhersage?.grund === "gleich" ? " · gleichbleibend" : ""}
|
||||
</p>
|
||||
)
|
||||
})}
|
||||
</Kachel>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { inTagen, knappeVorhersage, tempoText, vollText, vorhersageStufe } from "@/lib/prognose"
|
||||
import type { Vorhersage } from "@/lib/typen"
|
||||
|
||||
const v = (tage: number | null, rate: number | null = 1.25): Vorhersage => ({ tage, rate, anteil: 70, grund: tage == null ? "gleich" : null })
|
||||
|
||||
it("nennt die Zeit bis voll wie das Backend", () => {
|
||||
expect([0.4, 1.2, 9.4, 35, 200, 400].map(inTagen)).toEqual([
|
||||
"in weniger als einem Tag", "in etwa einem Tag", "in etwa 9 Tagen", "in etwa 5 Wochen", "in etwa 7 Monaten",
|
||||
"frühestens in einem Jahr",
|
||||
])
|
||||
})
|
||||
|
||||
it("stuft wie der Wächter: gelb unter 14, rot unter 3 Tagen", () => {
|
||||
expect([v(2.9), v(3), v(13.9), v(14), v(null), null].map(vorhersageStufe)).toEqual(["rot", "gelb", "gelb", "ok", "ok", "ok"])
|
||||
})
|
||||
|
||||
it("sagt nur etwas, wenn es etwas zu sagen gibt", () => {
|
||||
expect(vollText(v(9.4))).toBe("voll in etwa 9 Tagen")
|
||||
expect(vollText(v(null))).toBeNull()
|
||||
expect(vollText(undefined)).toBeNull()
|
||||
expect(tempoText(v(9.4))).toBe("+1,3 % am Tag")
|
||||
expect(knappeVorhersage(v(9.4))).toEqual({ text: "voll in etwa 9 Tagen", stufe: "gelb" })
|
||||
expect(knappeVorhersage(v(75))).toBeNull()
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { Vorhersage } from "@/lib/typen"
|
||||
|
||||
// „Voll in etwa N Tagen“ (Ausbauplan Welle 1, seit 25.09.2026): Das Backend rechnet aus dem Verlauf der letzten Woche
|
||||
// (kern/prognose.py), hier stehen nur die Worte. Dieselben Stufen wie beim Wächter: gelb unter 14, rot unter 3 Tagen.
|
||||
|
||||
export type VorhersageStufe = "ok" | "gelb" | "rot"
|
||||
|
||||
export const GELB_TAGE = 14
|
||||
export const ROT_TAGE = 3
|
||||
/** Knappe Stellen (Ringe, Geräte-Tabelle) nennen nur, was näher liegt — weiter weg wäre es Rauschen. */
|
||||
export const KNAPP_TAGE = 60
|
||||
|
||||
/** „in weniger als einem Tag“, „in etwa 9 Tagen“, „in etwa 5 Wochen“ … — wie im Backend (prognose.in_tagen). */
|
||||
export function inTagen(tage: number): string {
|
||||
if (tage < 1) return "in weniger als einem Tag"
|
||||
if (tage < 1.5) return "in etwa einem Tag"
|
||||
if (tage < 21) return `in etwa ${Math.round(tage)} Tagen`
|
||||
if (tage < 90) return `in etwa ${Math.round(tage / 7)} Wochen`
|
||||
return tage < 365 ? `in etwa ${Math.round(tage / 30)} Monaten` : "frühestens in einem Jahr"
|
||||
}
|
||||
|
||||
export function vorhersageStufe(v: Vorhersage | null | undefined): VorhersageStufe {
|
||||
if (v?.tage == null) return "ok"
|
||||
return v.tage < ROT_TAGE ? "rot" : v.tage < GELB_TAGE ? "gelb" : "ok"
|
||||
}
|
||||
|
||||
/** „voll in etwa 9 Tagen“ — oder null, wenn es nichts zu sagen gibt (zu wenig Verlauf, gleichbleibend, weiter weg
|
||||
* als bis Tage). */
|
||||
export function vollText(v: Vorhersage | null | undefined, bis = Infinity): string | null {
|
||||
if (v?.tage == null || v.tage >= bis) return null
|
||||
return `voll ${inTagen(v.tage)}`
|
||||
}
|
||||
|
||||
/** „+1,2 % am Tag“ */
|
||||
export function tempoText(v: Vorhersage | null | undefined): string | null {
|
||||
if (v?.rate == null) return null
|
||||
return `+${v.rate.toFixed(1).replace(".", ",")} % am Tag`
|
||||
}
|
||||
|
||||
/** Die Textfarbe zur Stufe: ruhig grau, dann bernstein, dann rot. */
|
||||
export const STUFE_FARBE: Record<VorhersageStufe, string> = {
|
||||
ok: "text-text-3",
|
||||
gelb: "text-bernstein",
|
||||
rot: "text-rot-text",
|
||||
}
|
||||
|
||||
/** Für die knappen Stellen: {text: „voll in etwa 9 Tagen“, stufe} oder null (weiter weg als KNAPP_TAGE). */
|
||||
export function knappeVorhersage(v: Vorhersage | null | undefined): { text: string; stufe: VorhersageStufe } | null {
|
||||
const text = vollText(v, KNAPP_TAGE)
|
||||
return text ? { text, stufe: vorhersageStufe(v) } : null
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export interface Start {
|
||||
ram_total: number | null
|
||||
temp_cpu: number | null
|
||||
temp_gpu: number | null
|
||||
platte: { used: number; total: number; percent: number } | null
|
||||
platte: { used: number; total: number; percent: number; vorhersage?: Vorhersage | null } | null
|
||||
uptime_s: number | null
|
||||
}
|
||||
updates: {
|
||||
@@ -427,12 +427,25 @@ export type Zeitraum = "1h" | "24h" | "7d"
|
||||
/** Ein Punkt einer Messreihe: [Unix-Sekunden des Schrittbeginns, Wert oder null bei einer Lücke]. */
|
||||
export type Messpunkt = [number, number | null]
|
||||
|
||||
/** Vorhersage „voll in etwa N Tagen“ aus dem Verlauf der letzten Woche (kern/prognose.py, seit 25.09.2026). */
|
||||
export interface Vorhersage {
|
||||
/** Tage bis voll; null ohne Vorhersage (grund sagt, warum). */
|
||||
tage: number | null
|
||||
/** Zuwachs in Prozentpunkten am Tag. */
|
||||
rate: number | null
|
||||
/** Jüngstes Mittel in %. */
|
||||
anteil: number | null
|
||||
/** verlauf = weniger als zwei Tage Verlauf, gleich = wächst nicht. */
|
||||
grund: "verlauf" | "gleich" | null
|
||||
}
|
||||
|
||||
/** KI-Box: cpu, ram, gpu, platte in %, temp_cpu/temp_gpu in °C, netz_rx/netz_tx in Bytes/s, tokens je Minute. */
|
||||
export interface BoxMesswerte {
|
||||
zeitraum: Zeitraum
|
||||
schritt_s: number
|
||||
reihen: Partial<Record<"cpu" | "ram" | "gpu" | "temp_cpu" | "temp_gpu" | "platte" | "netz_rx" | "netz_tx" | "tokens", Messpunkt[]>>
|
||||
aktuell: Partial<Record<string, number | null>>
|
||||
prognose?: { platte?: Vorhersage | null }
|
||||
}
|
||||
|
||||
export interface GeraetMesswerte {
|
||||
@@ -452,6 +465,7 @@ export interface GeraetMesswerte {
|
||||
load1?: number | null
|
||||
uptime_s?: number | null
|
||||
}
|
||||
prognose?: { platte?: Vorhersage | null }
|
||||
}
|
||||
|
||||
export interface HomelabMesswerte {
|
||||
@@ -517,6 +531,8 @@ export interface SpeicherPool {
|
||||
meta_anteil: number | null
|
||||
aktiv: boolean
|
||||
stufe: SpeicherStufe
|
||||
/** Daten oder Metadaten, was früher voll ist. */
|
||||
vorhersage?: Vorhersage | null
|
||||
}
|
||||
|
||||
export interface NasStand {
|
||||
@@ -532,11 +548,19 @@ export interface NasStand {
|
||||
anteil: number | null
|
||||
stufe: SpeicherStufe
|
||||
problem: string | null
|
||||
vorhersage?: Vorhersage | null
|
||||
}
|
||||
|
||||
export interface SicherungsLage {
|
||||
jobs: { zeitplan: string | null; speicher: string | null; alle: boolean }[]
|
||||
ziele: { speicher: string; art: string | null; aktiv: boolean | null; belegt: number | null; gesamt: number | null }[]
|
||||
ziele: {
|
||||
speicher: string
|
||||
art: string | null
|
||||
aktiv: boolean | null
|
||||
belegt: number | null
|
||||
gesamt: number | null
|
||||
vorhersage?: Vorhersage | null
|
||||
}[]
|
||||
erfasst: number
|
||||
fehlend: { ziel: string; name: string; zuletzt: number | null }[]
|
||||
zuletzt: number | null
|
||||
@@ -562,6 +586,7 @@ export interface Systemplatte {
|
||||
frei: number
|
||||
anteil: number
|
||||
stufe: SpeicherStufe
|
||||
vorhersage?: Vorhersage | null
|
||||
}
|
||||
|
||||
export interface HomelabSpeicher {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Zaehlwerk } from "@/components/cockpit/Zaehlwerk"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRadar, useStart } from "@/lib/abfragen"
|
||||
import { RADAR_STATUS_TEXT, temperaturFarbe } from "@/lib/anzeige"
|
||||
import { knappeVorhersage, STUFE_FARBE } from "@/lib/prognose"
|
||||
import { useLetzteMetrik } from "@/lib/strom"
|
||||
import type { Radar, Start } from "@/lib/typen"
|
||||
import { gb, gbText } from "@/lib/zeit"
|
||||
@@ -22,6 +23,7 @@ function Instrumente({ box }: { box: Start["box"] }) {
|
||||
const temp = m?.temp_cpu ?? box.temp_cpu
|
||||
const tempGpu = m?.temp_gpu ?? box.temp_gpu
|
||||
const platte = box.platte
|
||||
const vorhersage = knappeVorhersage(platte?.vorhersage)
|
||||
const uptime = m?.uptime_s ?? box.uptime_s
|
||||
const ramAnteil = ramBelegt != null && ramGesamt ? ramBelegt / ramGesamt : null
|
||||
|
||||
@@ -53,6 +55,7 @@ function Instrumente({ box }: { box: Start["box"] }) {
|
||||
mitte={platte ? `${Math.round(platte.percent)}%` : "–"}
|
||||
unter={platte ? `${gbText(platte.used)} / ${gbText(platte.total)}` : undefined}
|
||||
ariaText={platte ? `Platte zu ${Math.round(platte.percent)} Prozent belegt` : "Platte unbekannt"}
|
||||
hinweis={vorhersage ? { text: vorhersage.text, className: STUFE_FARBE[vorhersage.stufe] } : null}
|
||||
/>
|
||||
<Zaehlwerk sekunden={uptime} />
|
||||
</section>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { boxbild, type Geraetebild, geraetebild, PUNKT, type Punktzustand } from
|
||||
import { artText, kurzname } from "@/lib/homelab"
|
||||
import { laufzeit, zustandBei } from "@/lib/messung"
|
||||
import { type Bereich, useBereichStand, type Verbindung } from "@/lib/navigation"
|
||||
import { knappeVorhersage, type VorhersageStufe } from "@/lib/prognose"
|
||||
import { istSnapshotLauf } from "@/lib/snapshots"
|
||||
import { groesse } from "@/lib/speicher"
|
||||
import { useLetzteMetrik } from "@/lib/strom"
|
||||
@@ -115,6 +116,7 @@ interface Messwert {
|
||||
punkte?: Messpunkt[]
|
||||
gelb: number
|
||||
rot: number
|
||||
hinweis?: { text: string; stufe: VorhersageStufe } | null
|
||||
}
|
||||
|
||||
/** Vier Messringe eines Rechners unter seinem Namen. */
|
||||
@@ -135,7 +137,8 @@ function Instrumentgruppe({ bereich, titel, unterzeile, werte, basis }: {
|
||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
||||
{werte.map((w, n) => (
|
||||
<Messring key={w.label} label={w.label} wert={w.wert} einheit={w.einheit} punkte={w.punkte} bereich={bereich}
|
||||
grenzen={[w.gelb, w.rot]} zustand={zustandBei(w.wert ?? null, w.gelb, w.rot)} zeitraumText="letzte Stunde" i={basis + n} />
|
||||
grenzen={[w.gelb, w.rot]} zustand={zustandBei(w.wert ?? null, w.gelb, w.rot)} zeitraumText="letzte Stunde" i={basis + n}
|
||||
hinweis={w.hinweis} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,7 +162,8 @@ function Instrumente({ homelabAktiv }: { homelabAktiv: boolean }) {
|
||||
{ label: "CPU", wert: live?.cpu ?? mess.data?.aktuell.cpu, einheit: "prozent", punkte: r?.cpu, gelb: 85, rot: 95 },
|
||||
{ label: "Speicher", wert: ramProzent, einheit: "prozent", punkte: r?.ram, gelb: 85, rot: 95 },
|
||||
{ label: "Temp.", wert: live?.temp_cpu ?? b?.temp_cpu, einheit: "grad", punkte: r?.temp_cpu, gelb: 80, rot: 90 },
|
||||
{ label: "Platte", wert: live?.disk ?? b?.platte?.percent, einheit: "prozent", punkte: r?.platte, gelb: 80, rot: 90 },
|
||||
{ label: "Platte", wert: live?.disk ?? b?.platte?.percent, einheit: "prozent", punkte: r?.platte, gelb: 80, rot: 90,
|
||||
hinweis: knappeVorhersage(mess.data?.prognose?.platte ?? b?.platte?.vorhersage) },
|
||||
]} />
|
||||
{homelabAktiv ? (
|
||||
<Instrumentgruppe bereich="homelab" titel="Proxmox-Host" basis={4}
|
||||
@@ -168,7 +172,8 @@ function Instrumente({ homelabAktiv }: { homelabAktiv: boolean }) {
|
||||
{ label: "CPU", wert: host?.aktuell.cpu, einheit: "prozent", punkte: host?.reihen.cpu, gelb: 85, rot: 95 },
|
||||
{ label: "Speicher", wert: host?.aktuell.ram, einheit: "prozent", punkte: host?.reihen.ram, gelb: 90, rot: 95 },
|
||||
{ label: "Temp.", wert: host?.aktuell.temp_cpu, einheit: "grad", punkte: host?.reihen.temp_cpu, gelb: 80, rot: 90 },
|
||||
{ label: "Platte", wert: host?.aktuell.platte, einheit: "prozent", punkte: host?.reihen.platte, gelb: 80, rot: 90 },
|
||||
{ label: "Platte", wert: host?.aktuell.platte, einheit: "prozent", punkte: host?.reihen.platte, gelb: 80, rot: 90,
|
||||
hinweis: knappeVorhersage(host?.prognose?.platte) },
|
||||
]} />
|
||||
) : (
|
||||
<p className="self-center text-sm text-text-2">Der Homelab-Teil antwortet gerade nicht; hier steht nichts Veraltetes.</p>
|
||||
|
||||
Reference in New Issue
Block a user