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)) {