import { useEffect, useState, useSyncExternalStore, type FormEvent, } from "react"; import { usePermission, useTranslation, type PluginHostRecord, } from "@termix/plugin-sdk/frontend"; import { Badge, Button, Input, Label } from "@termix/plugin-sdk/ui"; import type { MaintenanceStore } from "./maintenance-store"; import type { Recurrence } from "../maintenance"; export function MaintenanceBadge({ host, store, }: { host: PluginHostRecord; store: MaintenanceStore; }) { const allowed = usePermission("view"); const { t } = useTranslation(); return allowed ? ( ) : null; } function VisibleBadge({ host, store, title, }: { host: PluginHostRecord; store: MaintenanceStore; title: string; }) { const states = useSyncExternalStore(store.subscribe, store.snapshot); const active = states[Number(host.id)]?.active; if (!active) return null; return ( {title} ); } export function HostMaintenance({ host, store, visible, }: { host: PluginHostRecord; store: MaintenanceStore; visible: boolean; }) { const canView = usePermission("view"); const { t } = useTranslation(); if (!canView) return

{t("maintenance.denied")}

; if (!visible) return null; return ; } function MaintenanceEditor({ host, store, }: { host: PluginHostRecord; store: MaintenanceStore; }) { const { t } = useTranslation(); const canEditPermission = usePermission("edit"); const states = useSyncExternalStore(store.subscribe, store.snapshot); const state = states[Number(host.id)]; // A shared host shows its owner's maintenance, read only. const canEdit = canEditPermission && state?.owned !== false; const [mode, setMode] = useState<"start" | "schedule">("start"); const [reason, setReason] = useState(""); const [start, setStart] = useState(""); const [duration, setDuration] = useState(60); const [grace, setGrace] = useState(20); const [recurrence, setRecurrence] = useState("once"); const [notify, setNotify] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [ending, setEnding] = useState(false); useEffect(() => { void store.refresh(); }, [store]); async function mutate(input: Record) { setBusy(true); setError(""); try { await store.edit(Number(host.id), input); setEnding(false); } catch (error) { const body = (error as { response?: { data?: { error?: string } } }) .response?.data?.error; setError( body || (error instanceof Error ? error.message : t("maintenance.failed")), ); } finally { setBusy(false); } } function submit(event: FormEvent) { event.preventDefault(); void mutate({ action: mode, reason, start: start ? `${start}:00.000Z` : undefined, durationMinutes: duration, graceMinutes: grace, recurrence, notifyOverdue: notify, }); } const fieldClass = "h-9 border bg-background px-3 text-sm"; return (

{t("maintenance.title")}

{host.name || host.ip}

{t("maintenance.description")}

{store.error() && (

{t(store.error())}

)} {!store.loaded() &&

{t("maintenance.loading")}

} {error && (

{error}

)} {state?.active && (
{t("maintenance.active")}

{state.active.reasons.join("; ")}

{t("maintenance.estimated")}:{" "} {new Date(state.active.estimatedEnd).toLocaleString()}

{t("maintenance.noAutoEnd")}

{canEdit && (ending ? (

{t("maintenance.endConfirm")}

{" "}
) : ( ))}
)} {state?.owned === false && (

{t("maintenance.sharedReadOnly")}

)} {canEdit && (
setReason(event.target.value)} />
{mode === "schedule" && (
setStart(event.target.value)} />

{t("maintenance.utcHelp")}

)}
setDuration(Number(event.target.value))} />
setGrace(Number(event.target.value))} />
)}

{t("maintenance.schedules")}

{!state?.plans.length && (

{t("maintenance.noSchedules")}

)} {state?.plans.map((plan) => (

{plan.reason}

{t(`maintenance.${plan.recurrence}`)} {plan.nextStart ? `${plan.nextStart.replace("T", " ").slice(0, 16)} UTC` : t("maintenance.completed")} {t("maintenance.minutes", { count: plan.durationMinutes })}

{canEdit && ( )}
))}
); }