67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { CheckCircle2, Loader2, XCircle } from 'lucide-react';
|
|
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
|
|
|
type Step = {
|
|
id: string;
|
|
label: string;
|
|
status: 'pending' | 'running' | 'done' | 'error';
|
|
detail?: string;
|
|
};
|
|
|
|
// ShutdownProgress is a full-screen overlay that appears while OpsLog is
|
|
// running its close-time tasks (backup, future LoTW upload, ...). It
|
|
// listens for `shutdown:start` / `shutdown:update` / `shutdown:done`
|
|
// events from the backend and renders a checklist that updates as each
|
|
// task completes. The backend triggers wruntime.Quit() once everything
|
|
// is finished, so this component never has to dismiss itself.
|
|
export function ShutdownProgress() {
|
|
const [steps, setSteps] = useState<Step[] | null>(null);
|
|
|
|
useEffect(() => {
|
|
const u1 = EventsOn('shutdown:start', (s: Step[]) => setSteps(s ?? []));
|
|
const u2 = EventsOn('shutdown:update', (s: Step[]) => setSteps(s ?? []));
|
|
const u3 = EventsOn('shutdown:done', (s: Step[]) => setSteps(s ?? []));
|
|
return () => { u1?.(); u2?.(); u3?.(); };
|
|
}, []);
|
|
|
|
if (!steps) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
|
<div className="bg-card border border-border rounded-lg shadow-xl p-6 min-w-[360px] max-w-[480px]">
|
|
<div className="text-sm font-semibold mb-3 text-foreground">Closing OpsLog…</div>
|
|
<div className="space-y-2">
|
|
{steps.length === 0 ? (
|
|
<div className="text-xs text-muted-foreground italic">Nothing to do, exiting.</div>
|
|
) : steps.map((s) => (
|
|
<div key={s.id} className="flex items-start gap-2 text-sm">
|
|
<div className="mt-0.5 w-4 flex items-center justify-center">
|
|
{s.status === 'done' && <CheckCircle2 className="size-4 text-success" />}
|
|
{s.status === 'running' && <Loader2 className="size-4 animate-spin text-primary" />}
|
|
{s.status === 'error' && <XCircle className="size-4 text-danger" />}
|
|
{s.status === 'pending' && <span className="size-2 rounded-full bg-muted-foreground/40" />}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className={
|
|
s.status === 'done' ? 'text-foreground'
|
|
: s.status === 'error' ? 'text-danger font-medium'
|
|
: s.status === 'pending' ? 'text-muted-foreground'
|
|
: 'text-foreground font-medium'
|
|
}>
|
|
{s.label}
|
|
</div>
|
|
{s.detail && (
|
|
<div className="text-[11px] text-muted-foreground mt-0.5 font-mono break-all">
|
|
{s.detail}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|