From 706822ed58086b4688f3edc40877e9c732e94d4f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 8 Nov 2025 23:15:50 +0000 Subject: [PATCH] Fix updater auto-refresh for all open tabs (related to #671) Problem: When an update was triggered, only the tab that clicked "Apply Update" would show the progress modal and auto-refresh after completion. Other open tabs would remain on the old version indefinitely. Root cause: The UpdateProgressModal was only shown when explicitly opened via the UpdateBanner component. WebSocket already broadcasts update:progress events, but no global listener existed to show the modal in all tabs. Solution: Added GlobalUpdateProgressWatcher component in App.tsx that: - Listens to WebSocket updateProgress events globally (in all tabs) - Filters to only real update-in-progress states (downloading, verifying, extracting, installing, restarting) to avoid false positives from routine update checks - Auto-opens the progress modal when an update starts - Allows manual dismissal after update completes - Works independently of UpdateBanner visibility (e.g., when banner is dismissed) The modal's existing health-check and auto-reload logic handles the page refresh once the backend is healthy again. --- frontend-modern/src/App.tsx | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/frontend-modern/src/App.tsx b/frontend-modern/src/App.tsx index c23ba42..520ce36 100644 --- a/frontend-modern/src/App.tsx +++ b/frontend-modern/src/App.tsx @@ -40,6 +40,8 @@ import BellIcon from 'lucide-solid/icons/bell'; import SettingsIcon from 'lucide-solid/icons/settings'; import { TokenRevealDialog } from './components/TokenRevealDialog'; import { useAlertsActivation } from './stores/alertsActivation'; +import { UpdateProgressModal } from './components/UpdateProgressModal'; +import type { UpdateStatus } from './api/updates'; const Dashboard = lazy(() => import('./components/Dashboard/Dashboard').then((module) => ({ default: module.Dashboard })), @@ -106,6 +108,60 @@ function HostsRoute() { ); } +// Helper to detect if an update is actively in progress (not just checking for updates) +function isUpdateInProgress(status: string | undefined): boolean { + if (!status) return false; + const inProgressStates = ['downloading', 'verifying', 'extracting', 'installing', 'restarting']; + return inProgressStates.includes(status); +} + +// Global update progress watcher - shows modal in ALL tabs when an update is running +function GlobalUpdateProgressWatcher() { + const wsContext = useContext(WebSocketContext); + const navigate = useNavigate(); + const [showProgressModal, setShowProgressModal] = createSignal(false); + const [hasAutoOpened, setHasAutoOpened] = createSignal(false); + + // Watch for update progress events from WebSocket + createEffect(() => { + const progress = wsContext?.updateProgress?.() as UpdateStatus | null; + + if (!progress) { + // Reset when no progress data + setHasAutoOpened(false); + return; + } + + const inProgress = isUpdateInProgress(progress.status); + + if (inProgress && !showProgressModal() && !hasAutoOpened()) { + // Update is starting - auto-open the modal in this tab + logger.info('Update in progress detected via WebSocket, showing progress modal', { + status: progress.status, + message: progress.message, + }); + setShowProgressModal(true); + setHasAutoOpened(true); + } else if (!inProgress && hasAutoOpened()) { + // Update finished - allow the modal to be dismissed + setHasAutoOpened(false); + } + }); + + return ( + setShowProgressModal(false)} + onViewHistory={() => { + setShowProgressModal(false); + navigate('/settings/updates'); + }} + connected={wsContext?.connected} + reconnecting={wsContext?.reconnecting} + /> + ); +} + function App() { const TooltipRoot = createTooltipSystem(); const owner = getOwner(); @@ -644,6 +700,7 @@ function App() { +