Running a tool from the Tools tab raised two full-size toasts around a few
seconds of work — "task started!" as the run began and "task completed!" as
it ended — and then opened the result modal that actually carried the answer.
The started one was stale by the time it was read and the finished one said
what the modal was already showing.
Tool tasks now go through LP_BACKGROUND_TASKS with a new `silent` flag (no
started toast, no finish line), and tools-manager raises a compact
"Running <tool>…" spinner toast for the duration instead, dismissed the
moment the result modal or the user list opens.
A failed list_users now falls through to the result modal too — it has no
account list to open, and the completion toast that used to report the
failure is gone.
Running any tool jumped to the Tasks tab and left the user stranded there. That
is right for an install — long, log-heavy, worth watching — and wrong for a
tool, which is a short admin action whose answer is one line. Worse, half of
these are only meaningful back on Tools: List Users opens a modal over that tab,
and Create User Account returns a generated password that was being buried in a
log the user then had to go read.
Tools now stay put. On completion the tool's own outcome lines — the
isSuccessful/isError/isNotice output, ANSI stripped and framework boilerplate
filtered — are shown in a small result modal, with a View log button for
anything needing the full detail. list_users is left alone because the existing
account-list modal is already a better result view.
Also stops generate_arrays.sh walking scripts/dev. That directory is
`export-ignore`d, so it exists in a working clone but never in a shipped
install; generating a files_dev.sh entry from it wrote a reference into
files_source.sh that no install could satisfy, and the loader treats a missing
array file as a broken installation — every libreportal command stopped with
"files_dev.sh is missing from your LibrePortal Installation". Excluded alongside
unused/, system/ and release/. Regenerating also picked up scripts/validation,
which had never had an array file.
And Matrix's account listing prints its aligned line from python rather than
re-splitting the marker line in bash: TAB is IFS whitespace, so an empty display
name collapsed into the previous delimiter and shifted every later column.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both _titleBlock implementations title-cased the slug themselves instead of
calling getAppDisplayName, so the Tools tab read "Run app-specific actions for
Rocketchat" and Services read "the docker compose services that make up
Speedtest".
getAppDisplayName already resolves a slug to the app's declared title through
window.apps. Using it fixes four apps beyond Rocket.Chat:
rocketchat Rocketchat -> Rocket.Chat
speedtest Speedtest -> LibreSpeed
ipinfo Ipinfo -> IPinfo
libreportal_catalog Libreportal Catalog -> LibrePortal Catalog
The slug casing is kept as the fallback for the window.apps-not-loaded-yet case,
which is what the helper does internally anyway.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Tools tab has an interactive modal: when a list_users task completes it
parses the task log for EZ_USER lines and renders one row per account with
reset / promote / delete buttons. All four new apps failed its contract in every
respect, so running List Users produced log text and nothing else.
- The marker is EZ_USER, tab-separated as email, username, roles. Matrix and
Stoat emitted LP_USER in a different field order; Mattermost and Rocket.Chat
emitted no marker at all.
- Matrix and Stoat then consumed their own marker lines in the formatting loop
and printed only the pretty version, so nothing reached the log to parse.
- The row buttons look up tools by id: reset_password, set_admin, delete_user.
The deactivate tools were named deactivate_user / disable_user, so no delete
button rendered.
- Prefill only fills a field named email or username. Rocket.Chat's and Stoat's
identifier field was called user, so a row action would have opened with an
empty box.
- '-' placeholders are truthy, so the modal's `email || username` fallback
picked '-' over the real username for accounts without an email (rocket.cat).
The EZ_USER line now carries an empty string; '-' stays in the readable line.
Mattermost's listing is rebuilt on `mmctl --json`, which carries roles and
delete_at. The text listing has neither, and there is no --system-admin filter
on user list, so every account was reported as a plain user. Two parsing notes
that cost time: mmctl prints status lines both before and after the JSON, so it
needs raw_decode rather than json.loads; and --per-page above 200 makes it emit
a warning line ahead of the payload.
The modal's delete button also stops asserting "Delete user" over whatever the
tool actually does — it takes its label and icon from the tool, because most of
these deactivate and Matrix cannot delete at all.
Verified by replaying the modal's own parser over real tool output: 2 rows for
Matrix, 4 for Mattermost, 3 for Rocket.Chat, with admin and deactivated states
resolving correctly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De-clutter each component into sub-system folders (apps: core/ port-manager/
services/ tools/ routing/; admin: config/ overview/ system/ ssh/ peers/) with
the standard js/ css/ html/ icons/ layout inside; single-page components
(backup/dashboard/tasks/updater) get js/ css/ html/. Single-feature icon sets
moved into their sub-system (vpn -> apps/core/icons, config/cpu/os ->
admin/{config,system}/icons); shared app + category icons stay in core/icons.
feature.json + index.js stay at each component root (the scanned descriptor +
entry). Every controller/CSS/fragment/icon path reference rewritten; verified
no stale refs, all JS valid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>