From 9a74b841d9db8a8cff2c5b7b7fb234c1995bfd98 Mon Sep 17 00:00:00 2001 From: librelad Date: Thu, 20 Aug 2026 03:27:27 +0100 Subject: [PATCH] Give every dispatched command a task name; finish instance removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task titles come from one declarative table in formatCommandForUser, whose final fallback is the raw command string. Five of the sixteen command shapes the WebUI dispatches had no row and hit that fallback, so they showed as "libreportal instance remove bookstack_work" where a backup shows as "Bookstack - Create Backup". Cross-checked every shape in task-commands.js against the real formatter rather than fixing only the reported one: instance create -> Bookstack - New Instance (work) instance remove -> Bookstack · work - Remove Instance system status -> LibrePortal - System Status system update -> LibrePortal - System Update system reset -> LibrePortal - Reset System Instance create is named off the TYPE rather than the new slug: at create time the instance is not in window.apps yet, so displayName() would fall back to capitalising the slug and render "Bookstack_work". Four more were not raw but read badly, because the generic " Application" fallback has no entry for the compose verbs: up and down rendered as "Up Application" and "Down Application". Now Start/Stop Containers, Reload Application and Check Status. And per-service restart collapsed to "Restart Application", losing the only thing that distinguished it — it now names the service. All seventeen shapes verified through the real formatter: zero fall through. Instance removal also finishes properly now. It skipped the button lockout a normal uninstall applies, so the instance's Config/Tools/Backups tabs stayed live against an app being torn down underneath them; it now makes the same disableAppButtons call executeUninstall does. And it bounced to the TYPE's Config page, hiding the very task the click had just started — it now lands on the removed instance's own Tasks tab. Verified by driving the WebUI: creating an instance files a task titled "Bookstack - New Instance (uitest)", removing it lands on /app/bookstack_uitest/tasks, and disableAppButtons flips all five app tabs from enabled to disabled. Test instance removed afterwards. Co-Authored-By: Claude Opus 5 --- .../apps/core/js/instance-manager.js | 14 +++++++++- .../components/tasks/js/tasks-format.js | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/containers/libreportal/frontend/components/apps/core/js/instance-manager.js b/containers/libreportal/frontend/components/apps/core/js/instance-manager.js index 6410cc7..eab49e3 100644 --- a/containers/libreportal/frontend/components/apps/core/js/instance-manager.js +++ b/containers/libreportal/frontend/components/apps/core/js/instance-manager.js @@ -269,10 +269,22 @@ class InstanceManager { return; } try { + // Same lockout a normal uninstall applies (apps-manager executeUninstall): + // the instance's controls have to stop accepting input the moment removal + // starts, or its Config/Tools/Backups tabs stay live against an app that is + // being torn down underneath them. + if (window.appTabbedManager && typeof window.appTabbedManager.disableAppButtons === 'function') { + window.appTabbedManager.disableAppButtons(slug, 'uninstall'); + } + await window.tasksManager.router.routeAction('instance_remove', { appName: slug }); this.close(); notify(`Removing instance ${slug} — track progress in Tasks.`, 'success'); - const path = (typeof window.appPath === 'function') ? window.appPath(typeSlug, 'config') : `/app/${typeSlug}/config`; + // The INSTANCE's own Tasks tab, not the type's Config. The removal log is + // the only thing worth looking at at this point, and it is filed under the + // instance being removed — bouncing to the parent app hid the very task + // this click just started. + const path = (typeof window.appPath === 'function') ? window.appPath(slug, 'tasks') : `/app/${slug}/tasks`; if (window.librePortalSPA && window.librePortalSPA.navigateTo) { window.librePortalSPA.navigateTo(path); } else if (window.navigateToRoute) { diff --git a/containers/libreportal/frontend/components/tasks/js/tasks-format.js b/containers/libreportal/frontend/components/tasks/js/tasks-format.js index 04996a8..40a866e 100644 --- a/containers/libreportal/frontend/components/tasks/js/tasks-format.js +++ b/containers/libreportal/frontend/components/tasks/js/tasks-format.js @@ -37,6 +37,20 @@ Object.assign(TasksManager.prototype, { { match: /^libreportal peer remove\b/, title: 'LibrePortal - Remove Peer' }, { match: /^libreportal peer pair\b/, title: 'LibrePortal - Pair with Peer' }, + // -- Per-service restart ----------------------------------------------- + // Must precede nothing in particular (the generic `libreportal app + // ` handler lives below the table), but it needs a row at all: without + // one it collapsed to "Restart Application", losing the only detail that + // distinguishes it from restarting the whole app. + { match: /^libreportal app restart (\S+) (\S+)/, title: (m) => `${displayName(m[1])} - Restart ${m[2]}` }, + + // -- Instances --------------------------------------------------------- + // Named off the TYPE, not the new slug: at create time the instance does + // not exist in window.apps yet, so displayName() would fall back to + // capitalising the raw slug and render "Bookstack_work". + { match: /^libreportal instance create (\S+) (\S+)/, title: (m) => `${displayName(m[1])} - New Instance (${m[2]})` }, + { match: /^libreportal instance remove (\S+)/, title: (m) => `${displayName(m[1])} - Remove Instance` }, + // -- Regen ------------------------------------------------------------- { match: /^libreportal regen\b/, title: 'LibrePortal - Regenerate WebUI Data' }, @@ -48,6 +62,11 @@ Object.assign(TasksManager.prototype, { { match: /^libreportal system health check\b/, title: 'LibrePortal - System Health Check' }, { match: /^libreportal system network heal\b/, title: 'LibrePortal - Heal Network' }, { match: /^libreportal system network check\b/, title: 'LibrePortal - Check Network' }, + // These three are dispatched by the WebUI (see task-commands.js) and had no + // row, so they fell all the way through to the raw-command fallback. + { match: /^libreportal system status\b/, title: 'LibrePortal - System Status' }, + { match: /^libreportal system update\b/, title: 'LibrePortal - System Update' }, + { match: /^libreportal system reset\b/, title: 'LibrePortal - Reset System' }, // -- Backup: per-app (these capture the app slug) ---------------------- { match: /^libreportal backup app create (\w+)/, title: (m) => `${displayName(m[1])} - Create Backup` }, @@ -115,6 +134,13 @@ Object.assign(TasksManager.prototype, { 'rebuild': 'Rebuild Application', 'delete': 'Delete Backup', 'backup': 'Backup Application', + // Compose verbs the WebUI dispatches. Without these the generic + // " Application" fallback rendered "Up Application" and + // "Down Application", which read as broken English rather than a task. + 'up': 'Start Containers', + 'down': 'Stop Containers', + 'reload': 'Reload Application', + 'status': 'Check Status', }; const formattedAction = actionMap[action] || `${action.charAt(0).toUpperCase() + action.slice(1)} Application`; return `${displayName(appName)} - ${formattedAction}`;