Give every dispatched command a task name; finish instance removal

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
"<Action> 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 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-20 03:27:27 +01:00
parent db4ef19698
commit 9a74b841d9
2 changed files with 39 additions and 1 deletions

View File

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

View File

@ -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 <verb>
// <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
// "<Action> 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}`;