import { useState } from "react" import { Search } from "lucide-react" import { cn } from "cn" import { Bestaetigen } from "@/components/cockpit/Bestaetigen" import { Chip } from "@/components/cockpit/Chip" import { Geraetesymbol } from "@/components/cockpit/Geraetesymbol" import { Button } from "@/components/ui/button" import { geraetebild } from "@/lib/geraetebild" import { appBaustein, artText, listenAlter, rueckwegText, systemBaustein, updateRang, versionText, } from "@/lib/homelab" import type { BausteinStand, ZielAktion, ZielStand } from "@/lib/typen" type Frage = { ziel: ZielStand; aktion: ZielAktion } | null // Feste Breite für die Knöpfe — sonst verschiebt eine Zeile ohne Knopf alle Spalten. const SPALTEN = "md:grid-cols-[minmax(0,1.1fr)_minmax(0,1.3fr)_minmax(0,1fr)_minmax(0,0.9fr)_11.5rem]" /** Die App eines Geräts: was läuft, was neu ist — und warum es keinen Knopf gibt, wenn es keinen gibt. */ function AppZelle({ ziel, b }: { ziel: ZielStand; b: BausteinStand | null }) { if (!b) { const pve = ziel.art === "proxmox-host" ? systemBaustein(ziel)?.installiert : null return pve ? PVE {pve} : – } const laeuft = b.installiert ? versionText(b.installiert) : "–" return ( {laeuft} {b.zustand === "neu" && b.verfuegbar && → {b.verfuegbar}} {b.zustand === "aktuell" && aktuell} {b.zustand === "unbekannt" && unklar} {b.zustand === "neu" && !b.aktion && b.grund && {b.grund}} ) } /** Pakete (bzw. Docker-Images): Zahl offener Pakete, „aktuell“ oder „unklar“ mit dem Alter der Listen. */ function PaketZelle({ b, onSuchen, gesperrt }: { b: BausteinStand | null; onSuchen: (a: ZielAktion) => void; gesperrt: boolean }) { if (!b) return – const docker = b.id === "docker" let text if (b.zustand === "neu") text = {b.kurz || "Update bereit"} else if (b.zustand === "aktuell") text = {docker ? "Images aktuell" : "aktuell"} else { const datum = listenAlter(b.grund) text = ( {docker ? "Images unklar" : "unklar"} {datum && · Listen vom {datum}} ) } const suchen = b.zustand === "unbekannt" ? b.aktion : null return ( {text} {suchen && ( )} ) } const KNOPF_TEXT: Record = { app: "App updaten", os: "Pakete einspielen", pakete: "Pakete einspielen" } function Zeile({ ziel, laeuft, gesperrt, onFrage }: { ziel: ZielStand laeuft: string | null gesperrt: boolean onFrage: (f: Frage) => void }) { const app = appBaustein(ziel) const sys = systemBaustein(ziel) const knoepfe = ziel.bausteine.filter((b) => b.zustand === "neu" && b.aktion) const ruhig = updateRang(ziel) >= 2 return (
{ziel.name} {artText(ziel) !== ziel.name && {artText(ziel)}}
App Pakete onFrage({ ziel, aktion })} /> {knoepfe.length ? rueckwegText(ziel) : ""} {laeuft ? ( Läuft: {laeuft} ) : ( knoepfe.map((b) => ( )) )}
) } /** * Die Update-Tabelle des Homelabs: eine Zeile je Gerät mit App-Version (läuft → neu), Paketstand, Rückweg und * den Knöpfen. Oben, was sich jetzt einspielen lässt; darunter, was wartet; zuletzt, was aktuell ist. */ export function UpdateTabelle({ ziele, laufend, gesperrt, onAktion }: { ziele: ZielStand[] laufend: Map gesperrt: boolean onAktion: (a: ZielAktion) => void }) { const [frage, setFrage] = useState(null) const sortiert = [...ziele].sort((a, b) => updateRang(a) - updateRang(b)) const aktuell = ziele.filter((z) => updateRang(z) === 3).length return ( <>
{sortiert.map((z) => ( ))}
{aktuell > 0 && aktuell === ziele.length && Alles aktuell} { if (frage) onAktion(frage.aktion) setFrage(null) }} onNein={() => setFrage(null)} /> ) }