From d5fe1bc56b3d15653dafee8d8d995080b3d778c2 Mon Sep 17 00:00:00 2001 From: librelad Date: Thu, 21 May 2026 23:33:43 +0100 Subject: [PATCH] feat(webui): out-of-date detection + one-click update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface when LibrePortal is behind upstream and let users update from the WebUI, reusing the proven git-update path instead of reinventing it. Detection (host): webuiSystemUpdateCheck writes frontend/data/system/update_status.json from a throttled git fetch + behind-count + VERSION compare, off the existing per-minute `webui generate system` cron. A new /VERSION file is the canonical version. Display (frontend): update-notifier.js/.css render a global topbar badge (every page) and a dashboard banner (prominent when behind, subtle "up to date" with a manual check otherwise), plus a details panel. Actions go through the task pipeline: - `libreportal update apply` -> webuiRunUpdate (non-interactive: guards, forced check, gitPerformUpdate, then dockerInstallApp libreportal) - `libreportal update check` -> forced recheck gitFolderResetAndBackup's body is extracted into gitPerformUpdate (no exit) so the WebUI path can reuse it; the interactive CLI flow is unchanged. Detection JSON verified against the repo (up-to-date and behind cases). webuiRunUpdate's re-clone + redeploy still needs validation on a live host. The latest-version source is git for now and is the single swap point for get.libreportal.org later — the JSON contract and frontend stay unchanged. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- VERSION | 1 + .../frontend/css/update-notifier.css | 237 ++++++++++++ containers/libreportal/frontend/index.html | 1 + .../js/components/tasks/tasks-manager.js | 6 +- .../frontend/js/components/topbar.js | 2 + .../frontend/js/components/update-notifier.js | 351 ++++++++++++++++++ .../frontend/js/system/system-loader.js | 1 + .../frontend/js/utils/data-loader.js | 5 +- .../commands/update/cli_update_commands.sh | 28 +- .../cli/commands/update/cli_update_header.sh | 9 +- scripts/update/backup/reset_git_backup.sh | 21 +- scripts/update/check_update.sh | 81 ++++ .../generators/system/webui_system_update.sh | 163 ++++++++ 13 files changed, 891 insertions(+), 15 deletions(-) create mode 100644 VERSION create mode 100644 containers/libreportal/frontend/css/update-notifier.css create mode 100644 containers/libreportal/frontend/js/components/update-notifier.js diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 diff --git a/containers/libreportal/frontend/css/update-notifier.css b/containers/libreportal/frontend/css/update-notifier.css new file mode 100644 index 0000000..c05446f --- /dev/null +++ b/containers/libreportal/frontend/css/update-notifier.css @@ -0,0 +1,237 @@ +/* Update Notifier — topbar badge, dashboard banner, and details panel. + Driven by js/components/update-notifier.js. Colours come from the active + theme tokens (with safe fallbacks) so it tracks every palette. */ + +/* ---- Topbar badge -------------------------------------------------------- */ + +.update-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 7px 12px; + border: 1px solid var(--status-warning, #e0a106); + border-radius: 6px; + background: rgba(var(--status-warning-rgb, 224, 161, 6), 0.14); + color: var(--status-warning, #e0a106); + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: background 0.2s, transform 0.1s; + white-space: nowrap; +} + +.update-badge:hover { + background: rgba(var(--status-warning-rgb, 224, 161, 6), 0.24); +} + +.update-badge:active { transform: scale(0.97); } + +.update-badge-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--status-warning, #e0a106); + box-shadow: 0 0 0 0 rgba(var(--status-warning-rgb, 224, 161, 6), 0.6); + animation: update-badge-pulse 2s infinite; +} + +@keyframes update-badge-pulse { + 0% { box-shadow: 0 0 0 0 rgba(var(--status-warning-rgb, 224, 161, 6), 0.6); } + 70% { box-shadow: 0 0 0 7px rgba(var(--status-warning-rgb, 224, 161, 6), 0); } + 100% { box-shadow: 0 0 0 0 rgba(var(--status-warning-rgb, 224, 161, 6), 0); } +} + +@media (prefers-reduced-motion: reduce) { + .update-badge-dot { animation: none; } +} + +/* ---- Dashboard banner ---------------------------------------------------- */ + +.update-banner { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 20px; + padding: 16px 20px; + border: 1px solid var(--status-warning, #e0a106); + border-left-width: 4px; + border-radius: 10px; + background: rgba(var(--status-warning-rgb, 224, 161, 6), 0.1); + color: var(--text-primary, #fff); +} + +/* Subtle "up to date" / local-install variant of the banner. */ +.update-banner.update-banner-ok { + border-color: var(--border-color, rgba(255, 255, 255, 0.12)); + border-left-color: var(--status-success, #2ea043); + background: var(--surface-bg, rgba(255, 255, 255, 0.03)); +} + +.update-banner.update-banner-ok .update-banner-icon { color: var(--status-success, #2ea043); } + +.update-banner.update-banner-ok .update-banner-title { font-weight: 600; } + +.update-banner-icon { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + color: var(--status-warning, #e0a106); +} + +.update-banner-text { flex: 1 1 auto; min-width: 0; } + +.update-banner-title { font-weight: 700; font-size: 1rem; } + +.update-banner-sub { + margin-top: 2px; + font-size: 0.85rem; + color: var(--text-muted, #9aa); +} + +.update-banner-actions { + display: flex; + gap: 8px; + flex: 0 0 auto; +} + +/* ---- Shared action buttons ----------------------------------------------- */ + +.update-btn-primary, +.update-btn-secondary { + padding: 8px 16px; + border-radius: 6px; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; + border: 1px solid transparent; + white-space: nowrap; +} + +.update-btn-primary { + background: var(--primary-color, #4f7cff); + color: var(--text-on-accent, #fff); +} + +.update-btn-primary:hover { background: var(--primary-hover, var(--accent-hover, #3a63d8)); } + +.update-btn-secondary { + background: transparent; + border-color: var(--border-color, rgba(255, 255, 255, 0.2)); + color: var(--text-primary, #fff); +} + +.update-btn-secondary:hover { background: var(--surface-hover, rgba(255, 255, 255, 0.08)); } + +/* ---- Details panel (modal) ----------------------------------------------- */ + +.update-panel-overlay { + position: fixed; + inset: 0; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + background: rgba(0, 0, 0, 0.55); + backdrop-filter: blur(2px); +} + +.update-panel { + width: 100%; + max-width: 440px; + border: 1px solid var(--card-border, var(--border-color, rgba(255, 255, 255, 0.15))); + border-radius: 12px; + background: var(--card-bg, var(--surface-bg-solid, #1b1f2a)); + box-shadow: var(--card-shadow, 0 20px 60px rgba(0, 0, 0, 0.45)); + color: var(--text-primary, #fff); + overflow: hidden; +} + +.update-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.1)); +} + +.update-panel-header h3 { margin: 0; font-size: 1.05rem; } + +.update-panel-close { + border: none; + background: transparent; + color: var(--text-muted, #9aa); + font-size: 1.5rem; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.update-panel-close:hover { color: var(--text-primary, #fff); } + +.update-panel-status { + padding: 14px 20px 0; + font-size: 0.9rem; + color: var(--text-secondary, var(--text-muted, #9aa)); +} + +.update-panel-status.is-outdated { + color: var(--status-warning, #e0a106); + font-weight: 600; +} + +.update-panel-error { + margin: 12px 20px 0; + padding: 8px 12px; + border-radius: 6px; + font-size: 0.82rem; + background: rgba(var(--status-danger-rgb, 220, 53, 69), 0.12); + color: var(--status-danger, #dc3545); +} + +.update-panel-rows { + margin: 14px 0 0; + padding: 0 20px; +} + +.update-panel-row { + display: flex; + justify-content: space-between; + gap: 16px; + padding: 8px 0; + border-bottom: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.06)); + font-size: 0.88rem; +} + +.update-panel-row:last-child { border-bottom: none; } + +.update-panel-row dt { color: var(--text-muted, #9aa); margin: 0; } + +.update-panel-row dd { + margin: 0; + font-weight: 600; + text-align: right; + word-break: break-word; +} + +.update-panel-note { + margin: 14px 20px 0; + font-size: 0.8rem; + color: var(--text-muted, #9aa); + line-height: 1.45; +} + +.update-panel-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 18px 20px 20px; +} + +@media (max-width: 600px) { + .update-banner { flex-wrap: wrap; } + .update-banner-actions { width: 100%; } + .update-banner-actions button { flex: 1 1 auto; } +} diff --git a/containers/libreportal/frontend/index.html b/containers/libreportal/frontend/index.html index f09389e..6113cf0 100755 --- a/containers/libreportal/frontend/index.html +++ b/containers/libreportal/frontend/index.html @@ -32,6 +32,7 @@ +