Fix SpoolBuddy update UI: loading states, page reload, stale data

Check button had no visual feedback because isFetching doesn't trigger
  reliably with cached data — replaced with manual loading state. Removed
  kiosk restart via getty; the frontend now detects daemon re-registration
  via WebSocket and calls window.location.reload(), keeping the user on
  the same page and fetching all fresh data.
This commit is contained in:
maziggy
2026-03-23 14:59:12 +01:00
parent 1f24bd264a
commit 559d41cc5e
5 changed files with 34 additions and 20 deletions
@@ -506,10 +506,23 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
const [error, setError] = useState<string | null>(null);
const [sshExpanded, setSSHExpanded] = useState(false);
const [copied, setCopied] = useState(false);
const [manualChecking, setManualChecking] = useState(false);
const wasUpdatingRef = useRef(false);
const isUpdating = device.update_status === 'pending' || device.update_status === 'updating';
const { data: updateResult, isFetching: checking, refetch } = useQuery({
// Track when an update was in progress so we can reload when device comes back
useEffect(() => {
if (isUpdating) {
wasUpdatingRef.current = true;
} else if (wasUpdatingRef.current && device.update_status == null) {
// Status cleared = daemon re-registered after update. Reload for fresh state.
wasUpdatingRef.current = false;
window.location.reload();
}
}, [isUpdating, device.update_status]);
const { data: updateResult, refetch } = useQuery({
queryKey: ['spoolbuddy-update-check', device.device_id],
queryFn: () => spoolbuddyApi.checkDaemonUpdate(device.device_id),
staleTime: 30 * 1000,
@@ -522,6 +535,15 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
staleTime: Infinity,
});
const checkForUpdates = async () => {
setManualChecking(true);
try {
await refetch();
} finally {
setManualChecking(false);
}
};
const applyUpdate = async () => {
setApplying(true);
setError(null);
@@ -606,11 +628,11 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
/* Up to date → Check + Force buttons side by side */
<div className="flex gap-2">
<button
onClick={() => refetch()}
disabled={checking || applying}
onClick={checkForUpdates}
disabled={manualChecking || applying}
className="flex-1 px-3 py-2 rounded-lg text-xs font-medium bg-zinc-700 text-zinc-300 hover:bg-zinc-600 disabled:opacity-40 transition-colors flex items-center justify-center gap-1"
>
{checking && (
{manualChecking && (
<svg className="w-3 h-3 animate-spin" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
@@ -635,11 +657,11 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
) : !isUpdating ? (
/* No result yet → Check button */
<button
onClick={() => refetch()}
disabled={checking}
onClick={checkForUpdates}
disabled={manualChecking}
className="w-full px-3 py-2 rounded-lg text-sm font-medium bg-zinc-700 text-zinc-200 hover:bg-zinc-600 disabled:opacity-40 transition-colors flex items-center justify-center gap-2"
>
{checking && (
{manualChecking && (
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />