9 Commits

Author SHA1 Message Date
librelad
5655835398 ui(devmode): persistent banner under topbar + shorter auto-enable toast
Two small dev-mode UX changes.

1. Banner. When CFG_DEV_MODE is on, a 36px amber-tinted strip sits flush
   under the topbar with "You are currently running in Developer mode"
   and a dismiss X. Dismissal is remembered in localStorage and cleared
   whenever dev mode is toggled back on, so re-enabling the mode brings
   the banner back. Body picks up `.has-dev-banner` while visible to
   bump padding-top by the strip's height (also adjusts the mobile
   drawer's top/height).

2. Toast. The auto-enable message dropped the trailing
   "Click the LibrePortal logo 10× to disable." — too noisy on every
   git/local page load; the easter egg is still discoverable. New
   message is just "Developer mode auto-enabled — you're on a <mode>
   install."

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-27 14:13:56 +01:00
librelad
cfdd39386c feat(admin): move Peers into Admin/Tools; lift System next to Overview
Two related UI tidies — both removing surface area from the topbar / Tools
group rather than adding new pages.

Peers → /admin/tools/peers
  Was a top-level /peers route with its own topbar nav item, which doubled
  the navigation surface for what's really an admin tool (same shape as
  SSH Access). Now lives under the Admin sidebar's Tools group alongside
  SSH Access. /peers is kept as a legacy redirect → /admin/tools/peers.

  Plumbing:
  - config-sidebar.js gains a Peers entry under the Tools label.
  - config-manager.js gains a 'peers' branch that fetches
    peers-content.html into config-section, then inits PeersPage.
  - window.adminPath() learns 'peers' → /admin/tools/peers.
  - spa.js handlePeers() is now a redirect (mirrors handleSsh).
  - topbar.html drops the Peers nav item.
  - peers-content.html slimmed to a config-section template (no
    standalone page wrapper) so it embeds cleanly under the admin shell.
  - PeersPage gains a rootId constructor arg for symmetry with SshPage
    (queries still work globally — IDs are unique).

System lifted out of the Tools group
  User feedback: 'overview/system are kinda like, the same thing'. Moved
  System to sit right under Overview at the top of the sidebar, before
  the 'Config' label. Both surfaces are admin-landing pages (Overview =
  ops/health summary, System = live host + per-app stats) — distinct from
  config form pages or the Tools utilities.

  config-sidebar.js: System block moved to the top section (right after
  Overview's click handler). Original Tools-group instance removed.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 20:16:45 +01:00
librelad
1014dd6e42 feat(peers): introduce 'Peer' as a first-class concept (Phase 2)
A peer is a named reference to another LibrePortal instance. Phase 2 only
implements kind=backup-channel (friendly label over a hostname that shows
up in a shared backup repo); direct-ssh-direct and direct-ssh-via-relay
(Connect's blind-relay) are reserved enum values for Phase 3.

DB schema (db_create_tables.sh):
  CREATE TABLE peers (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    name         TEXT UNIQUE NOT NULL,
    kind         TEXT NOT NULL DEFAULT 'backup-channel',
    config_json  TEXT NOT NULL DEFAULT '{}',
    status       TEXT DEFAULT 'unknown',
    last_seen    TEXT,
    created_at   TEXT DEFAULT CURRENT_TIMESTAMP
  );
  + indexes on name and kind.

  config_json is kind-specific so new transports don't need a schema
  migration. For backup-channel it carries {"hostname":"","loc_idx":N}.

Bash module (scripts/peer/):
  peer_helpers.sh   _peerDb, peerSqlEscape, peerValidateName/Kind.
  peer_add.sh       peerAdd <name> <kind> [k=v ...] → INSERT, refresh
                    generator. Rejects unimplemented kinds early so users
                    don't create dead-end peer records.
  peer_remove.sh    peerRemove <name> → DELETE.
  peer_list.sh      peerList → JSON array; peerGet, peerNameForHostname
                    (reverse-lookup for the migrate-tab overlay).
  peer_check.sh     peerCheckReachable, peerCheckAll. For backup-channel
                    'reachable' = at least one snapshot from that hostname
                    visible in (preferred|any enabled) location. Updates
                    status + last_seen so UI dots render without re-probing.

CLI (scripts/cli/commands/peer/):
  libreportal peer list
  libreportal peer get <name>
  libreportal peer add <name> backup-channel hostname=<host> [loc_idx=<n>]
  libreportal peer remove <name>
  libreportal peer check [name]

  Auto-routed by cli_initialize.sh's category-discovery.

WebUI data generator (scripts/webui/data/generators/peers/webui_peers.sh):
  Emits data/peers/generated/peers.json with the peerList output and a
  generated_at envelope. Hooked into webuiLibrePortalUpdate alongside the
  backup generators.

Frontend:
  - New top-level /peers route in spa.js (PeersPage class, peers-content.html).
  - 'Peers' nav item in the topbar between Backups and the right-side controls.
  - Add-peer modal with friendly-name + kind + hostname + preferred-location
    selector (populated from the existing backup-locations data).
  - Per-peer card with status dot, last-checked time, Check + Remove buttons.
  - Phase 3 kinds appear in the kind dropdown as disabled options so users
    can see what's coming.

Source-array wiring:
  - generate_arrays.sh auto-created files_peer.sh from the new peer/ dir.
  - cli_files.sh + app_files.sh include ${peer_scripts[@]} alphabetically.
  - files_webui.sh auto-picked-up the new peers/ generator subfolder.

The migrate-tab friendly-name overlay (use peer names in /backup/migrate
when a peer record exists for a hostname) is intentionally deferred — it's
a 5-line frontend lookup once peers.json is loaded; cleaner to add after
Phase 3 ships its peer-detail view.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 17:43:56 +01:00
librelad
152d9c5d28 fix(webui): make all icon and data asset URLs absolute under path routing
Same class of bug as the topbar partial: icon and data-file references were
relative (icons/apps/x.svg, data/apps/...), so on deep path routes (/app/<name>,
/admin/config/x) the browser resolved them against the route dir and the SPA
catch-all served index.html with HTTP 200 instead of 404 — broken images and
silently-wrong JSON.

Make every reference absolute (anchored on the quote/backtick so already-absolute
/icons paths are untouched):
- JS: all icons/ and data/ literals + templates across components/utils/system
- html/topbar.html: logo <img>
- generators: webui_config.sh and webui_create_app_categories.sh now emit
  /icons/... into apps.json / apps-categories.json (regenerated on install)
- updated the two icon-path comments to match

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 23:20:42 +01:00
librelad
447f57ac63 fix(webui): use absolute URLs for topbar partial + nav under path routing
Path-based routes (e.g. /app/<name>) made the relative fetch('html/topbar.html')
resolve to /app/html/topbar.html. The SPA catch-all returns index.html with HTTP
200 instead of 404, so response.ok passed and index.html got injected as the
topbar, leaving #nav-app-center absent -> 'Nav element not found' in setActiveNav.

Make the topbar fetch and the loadConfig fetch absolute, and switch the remaining
relative topbar nav hrefs (index/dashboard/tasks .html) to absolute paths so the
SPA click interceptor routes them instead of doing a real browser navigation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 23:15:46 +01:00
librelad
fab6997cd7 refactor(webui): path-based Admin routing (/admin/config/<x>, /admin/tools/ssh-access)
Replace the Admin area's ?= query URLs with clean, hierarchical paths that
mirror the breadcrumb:
  /admin                  -> Overview
  /admin/config/<category>-> Config / <category>
  /admin/tools/ssh-access -> Tools / SSH Access

New /admin (+ /admin*) SPA route -> handleAdmin, which parses the path via the
shared window.adminPath / window.adminCategoryFromPath helpers and renders
through the existing ConfigManager. Legacy /config, /config?=<x> and /ssh now
redirect into the matching /admin path, so old links/bookmarks keep working
(server already serves index.html for any depth). Sidebar, Admin Overview,
dashboard link and top-nav now build /admin paths; active-nav + config data
loading recognise /admin across spa.js, topbar.js, router.js, data-loader.js.

Scope: Admin area only — /app, /apps, /tasks, /backup keep their existing ?=
URLs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 18:36:06 +01:00
librelad
4fd043a852 refactor(webui): fold SSH Access into an Admin area
Rename the Config top-nav to 'Admin' and move SSH Access into its sidebar
under a 'Tools' group, instead of a separate top-level nav item. SSH Access is
rendered by SshPage into the config main pane via a renderConfig('ssh-access')
special case; the sidebar item (config-sidebar.js) routes there. SshPage now
mounts into any container (defaults to #config-section). /ssh redirects to
/config?=ssh-access for old links; the standalone ssh-content.html is removed.

Declutters the top bar and gives system/admin features one home that scales
(updates, users, Connect settings can become sidebar entries later).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 17:31:26 +01:00
librelad
e75f10618d feat(ssh): WebUI SSH Access page
New /ssh page (topbar nav + SPA route + SshPage controller + ssh-content.html
+ ssh.css). Reads data/ssh/access.json and lets the admin: paste a public key
to authorize a machine, remove keys, and toggle key-only login — all via
'libreportal ssh ...' tasks through the backend's lockout guards. Reuses the
backup key-card styles for a consistent look. This is the inbound counterpart
to the backup location key card (outbound): same paste-a-key model, opposite
direction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 16:52:47 +01:00
librelad
875a60f90f LibrePortal v0.1.0 — initial release
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.

Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-21 20:37:54 +01:00