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 && (
)}
{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 && (
)}
))}
);
}