zustaende: Dienste und Einstellungen sagen, was los ist
- Dienste: Lade-, Fehler- und Leerzustand; das Protokoll erscheint direkt unter dem angetippten Dienst (am Handy stand es vorher unter der ganzen Liste ausser Sicht) und meldet Lesefehler; "Wirklich neu starten?" hat ein "Nein", waehrend eines Neustarts sind die Knoepfe gesperrt, Meldungen nennen den Dienst beim Namen statt der Unit. - Einstellungen: Solange der Stand laedt, steht "Wird gelesen ..." statt eines falschen "Fehlt."; Lesefehler und eine nicht beschreibbare Ablage werden gesagt; Speichern sperrt gegen Doppelklick, "Zugang loeschen" fragt vorher nach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a091ccb3b8
commit
bd12667231
@@ -1,11 +1,28 @@
|
||||
import { useState } from "react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { cn } from "cn"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
||||
import { useDienste, useProtokoll } from "@/lib/abfragen"
|
||||
import { type Dienst, useDienste, useProtokoll } from "@/lib/abfragen"
|
||||
import { fehlerText } from "@/lib/anzeige"
|
||||
import { post } from "@/lib/api"
|
||||
import { melden } from "@/lib/meldungen"
|
||||
import { cn } from "cn"
|
||||
import type { AktionsErgebnis } from "@/lib/typen"
|
||||
|
||||
/** Das Protokoll eines Dienstes, direkt unter seiner Zeile — am Handy sonst weit unten außer Sicht. */
|
||||
function Protokoll({ unit }: { unit: string }) {
|
||||
const protokoll = useProtokoll(unit)
|
||||
let text: string
|
||||
if (protokoll.isPending) text = "Wird gelesen …"
|
||||
else if (protokoll.isError) text = `Das Protokoll lässt sich gerade nicht lesen: ${protokoll.error.message}`
|
||||
else if (protokoll.data.ok === false) text = protokoll.data.err || protokoll.data.text || "Das Protokoll lässt sich nicht lesen."
|
||||
else text = protokoll.data.text?.trim() || "Noch keine Einträge."
|
||||
return (
|
||||
<pre className="ziffern m-0 max-h-[50vh] w-full basis-full overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap break-all text-text-2">
|
||||
{text}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
/** Dienste der Box mit Protokoll und Neustart — die „Log-Ansicht der Dienste“. */
|
||||
export function Dienste({ offen, onSchliessen }: { offen: boolean; onSchliessen: () => void }) {
|
||||
@@ -13,20 +30,79 @@ export function Dienste({ offen, onSchliessen }: { offen: boolean; onSchliessen:
|
||||
const dienste = useDienste()
|
||||
const [gewaehlt, setGewaehlt] = useState<string | null>(null)
|
||||
const [bestaetigen, setBestaetigen] = useState<string | null>(null)
|
||||
const protokoll = useProtokoll(gewaehlt)
|
||||
const [startet, setStartet] = useState<string | null>(null)
|
||||
|
||||
async function neustarten(unit: string) {
|
||||
async function neustarten(d: Dienst) {
|
||||
if (startet) return
|
||||
setBestaetigen(null)
|
||||
setStartet(d.unit)
|
||||
try {
|
||||
const e = await post<{ ok: boolean; err?: string }>("/api/maintenance/restart", { service: unit })
|
||||
if (e.ok) melden("erfolg", `${unit} startet neu.`)
|
||||
else melden("fehler", e.err || `${unit} ließ sich nicht neu starten.`)
|
||||
const e = await post<AktionsErgebnis>("/api/maintenance/restart", { service: d.unit })
|
||||
if (e.ok) melden("erfolg", `${d.name} ${d.schlaeft ? "wird geweckt" : "startet neu"}.`)
|
||||
else melden("fehler", `${d.name} ließ sich nicht starten: ${fehlerText(e)}`)
|
||||
qc.invalidateQueries({ queryKey: ["dienste"] })
|
||||
} catch (f) {
|
||||
melden("fehler", (f as Error).message)
|
||||
} finally {
|
||||
setStartet(null)
|
||||
}
|
||||
}
|
||||
|
||||
let inhalt
|
||||
if (dienste.isPending) {
|
||||
inhalt = <p role="status" className="px-4 text-[15px] text-text-2">Dienste werden gelesen …</p>
|
||||
} else if (dienste.isError && !dienste.data) {
|
||||
inhalt = <p role="alert" className="px-4 text-[15px] text-rot-text">Die Dienste lassen sich gerade nicht lesen: {dienste.error.message}</p>
|
||||
} else if (!dienste.data?.services.length) {
|
||||
inhalt = <p className="px-4 text-[15px] text-text-2">Keine Dienste gefunden.</p>
|
||||
} else {
|
||||
inhalt = (
|
||||
<ul className="m-0 flex list-none flex-col px-4 pb-4">
|
||||
{dienste.data.services.map((d) => (
|
||||
<li key={d.unit} className="flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5">
|
||||
<span className="flex min-w-0 items-center gap-2.5">
|
||||
<span
|
||||
className={cn("size-2.5 shrink-0 rounded-full", d.ok ? "bg-gruen" : d.schlaeft ? "bg-text-3" : "bg-rot")}
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[15px]">{d.name}</span>
|
||||
<span className="ziffern text-xs text-text-3">
|
||||
{d.unit} · {d.ok ? "läuft" : d.schlaeft ? "schläft (abgeschaltet)" : "antwortet nicht"}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant={gewaehlt === d.unit ? "info" : "ghost"}
|
||||
size="sm"
|
||||
aria-expanded={gewaehlt === d.unit}
|
||||
onClick={() => setGewaehlt(gewaehlt === d.unit ? null : d.unit)}
|
||||
>
|
||||
Protokoll
|
||||
</Button>
|
||||
{bestaetigen === d.unit ? (
|
||||
<>
|
||||
<Button variant="gefahr" size="sm" onClick={() => neustarten(d)}>
|
||||
{d.schlaeft ? "Wirklich wecken?" : "Wirklich neu starten?"}
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => setBestaetigen(null)}>
|
||||
Nein
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button variant="outline" size="sm" disabled={startet !== null} onClick={() => setBestaetigen(d.unit)}>
|
||||
{startet === d.unit ? "Startet …" : d.schlaeft ? "Wecken" : "Neu starten"}
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
{gewaehlt === d.unit && <Protokoll unit={d.unit} />}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
||||
<SheetContent side="right" className="gap-3 overflow-y-auto border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl">
|
||||
@@ -34,43 +110,7 @@ export function Dienste({ offen, onSchliessen }: { offen: boolean; onSchliessen:
|
||||
<SheetTitle className="schild text-lg">Dienste und Protokolle</SheetTitle>
|
||||
<SheetDescription className="text-text-2">Welcher Dienst läuft, was er zuletzt geschrieben hat.</SheetDescription>
|
||||
</SheetHeader>
|
||||
<ul className="m-0 flex list-none flex-col px-4 pb-2">
|
||||
{(dienste.data?.services ?? []).map((d) => (
|
||||
<li key={d.unit} className="flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5">
|
||||
<span className="flex min-w-0 items-center gap-2.5">
|
||||
<span
|
||||
className={cn("size-2.5 shrink-0 rounded-full", d.ok ? "bg-gruen" : d.schlaeft ? "bg-text-3" : "bg-rot")}
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[15px]">{d.name}</span>
|
||||
<span className="ziffern text-xs text-text-3">
|
||||
{d.unit} · {d.ok ? "läuft" : d.schlaeft ? "schläft (abgeschaltet)" : "antwortet nicht"}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex gap-2">
|
||||
<Button variant={gewaehlt === d.unit ? "info" : "ghost"} size="sm" onClick={() => setGewaehlt(d.unit)}>
|
||||
Protokoll
|
||||
</Button>
|
||||
{bestaetigen === d.unit ? (
|
||||
<Button variant="gefahr" size="sm" onClick={() => neustarten(d.unit)}>
|
||||
{d.schlaeft ? "Wirklich wecken?" : "Wirklich neu starten?"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" size="sm" onClick={() => setBestaetigen(d.unit)}>
|
||||
{d.schlaeft ? "Wecken" : "Neu starten"}
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{gewaehlt && (
|
||||
<pre className="ziffern mx-4 mb-4 max-h-[50vh] overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap text-text-2">
|
||||
{protokoll.isFetching ? "Wird gelesen …" : protokoll.data?.text || protokoll.data?.err || "Kein Protokoll."}
|
||||
</pre>
|
||||
)}
|
||||
{inhalt}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user