fix(webui): refresh the Instances bar when an instance task completes
The post-task handler only repainted the app detail page when the finished task's app WAS the app on screen, and only ever re-rendered that same slug. An instance removal is neither: it is dispatched for the instance, while the list that changed — the Instances bar — is drawn on the parent and on every sibling. Removing an instance from a family page left its pill sitting there. Now the family (INSTANCE_OF) is read before the apps.json reload — a removed instance is gone from it afterwards — and the page ON SCREEN is repainted whenever it belongs to the same family as the completed task, so a pill appears on create and disappears on remove without a manual refresh. Also handles the page whose app no longer exists: an instance removal deletes the app outright (a plain uninstall only flips it to not-installed), and the removal flow parks the user on the instance's own Tasks tab, so re-rendering printed "App not found". Land on the type it was an instance of instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
66cdb5be0d
commit
0e6eb841bd
@ -155,6 +155,22 @@ class AppsManager {
|
||||
} catch (e) { console.error('post-uninstall task cleanup failed:', e); }
|
||||
}
|
||||
|
||||
// Instance family membership, read BEFORE the reload. `libreportal
|
||||
// instance remove` deletes the instance's definition outright, so once
|
||||
// apps.json is refreshed there is nothing left to say which type it
|
||||
// belonged to — and the page on screen may be a SIBLING whose Instances
|
||||
// bar lists the app that just came or went.
|
||||
const _currentAppPreReload = decodeURIComponent((window.location.pathname.match(/^\/app\/([^/?]+)/) || [])[1] || '')
|
||||
|| new URL(window.location.href).searchParams.get('app') || '';
|
||||
const familyOf = (slug) => {
|
||||
if (!slug) return '';
|
||||
const a = (window.apps || []).find(x => (x.command || '').split(' ').pop() === slug);
|
||||
const cfg = (a && a.config) || {};
|
||||
return cfg[`CFG_${String(slug).toUpperCase()}_INSTANCE_OF`] || slug;
|
||||
};
|
||||
const completedFamilyPre = familyOf(appName);
|
||||
const currentFamily = familyOf(_currentAppPreReload);
|
||||
|
||||
this.clearCache();
|
||||
await this.reloadAppsData();
|
||||
if (window.serviceButtons) {
|
||||
@ -166,11 +182,36 @@ class AppsManager {
|
||||
const pathname = window.location.pathname;
|
||||
const isAppsPage = pathname === '/apps' || pathname.startsWith('/apps/');
|
||||
const isAppDetailPage = pathname === '/app' || pathname.startsWith('/app/');
|
||||
const stillListed = (slug) => !!slug && (window.apps || []).some(a => (a.command || '').split(' ').pop() === slug);
|
||||
// A CREATED instance only gains its INSTANCE_OF after the reload, a
|
||||
// REMOVED one only had it before — take whichever read still has data.
|
||||
const completedFamily = stillListed(appName) ? familyOf(appName) : completedFamilyPre;
|
||||
|
||||
// The app this page is showing can be GONE: an instance removal deletes
|
||||
// it, where a plain uninstall only flips it to not-installed. Rendering
|
||||
// it would print "App not found", so land on the type it was an instance
|
||||
// of — its Instances bar is where the removal reads as a result.
|
||||
if (isAppDetailPage && currentAppFromUrl === appName && !stillListed(appName)) {
|
||||
const dest = (completedFamily !== appName && stillListed(completedFamily))
|
||||
? (typeof window.appPath === 'function' ? window.appPath(completedFamily, 'config') : `/app/${completedFamily}/config`)
|
||||
: '/apps';
|
||||
if (window.librePortalSPA?.navigateTo) window.librePortalSPA.navigateTo(dest);
|
||||
else if (window.navigateToRoute) window.navigateToRoute(dest.replace(/^\//, ''));
|
||||
else window.location.href = dest;
|
||||
if (typeof window.renderInstalledApps === 'function') window.renderInstalledApps();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAppsPage && !isAppDetailPage) {
|
||||
const category = window.appsCategory || 'all';
|
||||
this.renderApps(category);
|
||||
} else if (isAppDetailPage && currentAppFromUrl === appName) {
|
||||
} else if (isAppDetailPage && currentAppFromUrl
|
||||
&& (currentAppFromUrl === appName
|
||||
|| (currentFamily && currentFamily === completedFamily))) {
|
||||
// Repaint the app ON SCREEN, which is not always the one the task ran
|
||||
// for: a sibling instance created or removed changes this page's
|
||||
// Instances bar and nothing else would redraw it.
|
||||
//
|
||||
// Defer + isolate the heavy re-render so a throw inside
|
||||
// displayConfigForm / port-manager init can't lock up the
|
||||
// post-task UI cleanup. Fires on the next tick — gives the
|
||||
@ -178,7 +219,7 @@ class AppsManager {
|
||||
setTimeout(() => {
|
||||
// _skipReload flag tells renderAppDetail not to re-fetch
|
||||
// apps.json again (we already just did, line above).
|
||||
this.renderAppDetail(appName, null, true, { skipReload: true })
|
||||
this.renderAppDetail(currentAppFromUrl, null, true, { skipReload: true })
|
||||
.catch(err => console.error('renderAppDetail failed:', err));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user