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.
This commit is contained in:
rcourtman 2025-11-08 23:15:50 +00:00
parent 6bf32f98d6
commit 706822ed58

View file

@ -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 (
<UpdateProgressModal
isOpen={showProgressModal()}
onClose={() => 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() {
<SecurityWarning />
<DemoBanner />
<UpdateBanner />
<GlobalUpdateProgressWatcher />
<div class="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 font-sans py-4 sm:py-6">
<AppLayout
connected={connected}