From 7f034d3e02801cd7a0e66bd5268f09d6bfb9c68b Mon Sep 17 00:00:00 2001 From: librelad Date: Thu, 28 May 2026 21:52:29 +0100 Subject: [PATCH] fix(webui): reload app/service data after a config apply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config_update re-deploys apps (ports, subdomains, Open URLs, routing) but the WebUI only unlocked the nav on completion — leaving stale URLs/routing until a manual reload. apps-manager now refreshes apps + services and repaints the current view when a config_update task completes, via a reusable refreshAppsAndView() helper (also the basis for the upcoming task-refresh registry). Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- .../js/components/app/apps-manager.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/containers/libreportal/frontend/js/components/app/apps-manager.js b/containers/libreportal/frontend/js/components/app/apps-manager.js index 9493598..c9d904f 100755 --- a/containers/libreportal/frontend/js/components/app/apps-manager.js +++ b/containers/libreportal/frontend/js/components/app/apps-manager.js @@ -65,6 +65,14 @@ class AppsManager { return; } + // Config apply re-deploys apps (ports/subdomains/URLs/routing). Reload + // app + service data and repaint so the UI reflects the new config + // instead of showing stale URLs/routing until a manual refresh. + if (action === 'config_update' && status === 'completed') { + await this.refreshAppsAndView(); + return; + } + // First-install welcome modal — only on the very first successful install per app per browser. if (action === 'install' && status === 'completed' && appName) { const key = `libreportal.welcomeShown.${String(appName).toLowerCase()}`; @@ -181,6 +189,33 @@ class AppsManager { } } + // Reload apps + service data and repaint whatever app surface is on screen + // (grid or detail), with no manual page reload. Used after any task that + // changes app config/state — config_update, restore, start/stop/restart, + // update, rebuild — so the UI reflects the result. + async refreshAppsAndView(appName) { + this.clearCache(); + await this.reloadAppsData(); + if (window.serviceButtons) { + try { await window.serviceButtons.loadServices(); } catch (e) { console.error('loadServices failed:', e); } + } + const url = new URL(window.location.href); + const pathname = window.location.pathname; + const currentApp = decodeURIComponent((pathname.match(/^\/app\/([^/?]+)/) || [])[1] || '') + || url.searchParams.get('app') || url.searchParams.get('') || ''; + const isAppsPage = pathname === '/apps' || pathname.startsWith('/apps/'); + const isAppDetailPage = pathname === '/app' || pathname.startsWith('/app/'); + if (isAppsPage && !isAppDetailPage) { + this.renderApps(window.appsCategory || 'all'); + } else if (isAppDetailPage && currentApp && (!appName || currentApp === appName)) { + setTimeout(() => { + this.renderAppDetail(currentApp, null, true, { skipReload: true }) + .catch(err => console.error('renderAppDetail failed:', err)); + }, 0); + } + if (typeof window.renderInstalledApps === 'function') window.renderInstalledApps(); + } + async loadApps(category = 'all') { // Check cache first if (this.cache.has(category)) {