diff --git a/containers/libreportal/frontend/components/apps/core/js/app-tabbed-manager.js b/containers/libreportal/frontend/components/apps/core/js/app-tabbed-manager.js index c223b02..11a2171 100755 --- a/containers/libreportal/frontend/components/apps/core/js/app-tabbed-manager.js +++ b/containers/libreportal/frontend/components/apps/core/js/app-tabbed-manager.js @@ -591,17 +591,20 @@ class AppTabbedManager { const app = (this.appUpdater.apps || []).find((x) => x.name === this.currentApp); // Always lead with the title block + recessed dark container, so the tab // matches the Config / Backups / Tasks tabs whether or not there's data. + const autoLine = this.appUpdater.renderAutoCheckLine(); const body = app ? this.appUpdater.renderAppDetail(app, { includeVersion: true }) - : `
No update data for this app yet.
`; + : `
No scan data for this app yet — the first automatic check will fill this in shortly.
`; section.innerHTML = this.renderAppUpdaterHead(app) + - `
${body}
`; + `
${autoLine}${body}
`; } // Title block for the per-app Updates tab — emoji + title + description on the - // left, Check/Update actions on the right. Mirrors .backup-title (.config-title - // family) so every app-detail tab shares one header idiom. `a` may be null - // (no data yet); the Update button only appears when an update is available. + // left, the Update action on the right. Mirrors .backup-title (.config-title + // family) so every app-detail tab shares one header idiom. There's no manual + // "Check" button: scans run automatically (the panel's auto-check line reports + // when). `a` may be null (no data yet); the Update button only appears when an + // update is genuinely available. renderAppUpdaterHead(a) { const esc = (s) => (this.appUpdater ? this.appUpdater.escape(s) : String(s == null ? '' : s)); const name = this.currentApp @@ -616,7 +619,6 @@ class AppTabbedManager {

Version, security and recovery for ${esc(name)}.

- ${updBtn}
`; diff --git a/containers/libreportal/frontend/components/apps/overview/css/overview.css b/containers/libreportal/frontend/components/apps/overview/css/overview.css index 3882d7b..9f4805f 100644 --- a/containers/libreportal/frontend/components/apps/overview/css/overview.css +++ b/containers/libreportal/frontend/components/apps/overview/css/overview.css @@ -360,6 +360,32 @@ .updater-detail-container .updater-detail-section { padding: 16px 0; } .updater-detail-container .updater-detail-section:first-child { padding-top: 0; } .updater-detail-container .updater-detail-section:last-child { padding-bottom: 0; } + +/* "Checked automatically · last checked X · next check ~Y" — a calm status line + at the top of the panel that replaces the old manual "Check" button, since + scans run on a schedule. Green dot = automation live; muted when it's off. */ +.updater-autocheck { + display: flex; + align-items: center; + gap: 9px; + margin: 0 0 14px; + padding: 9px 12px; + border-radius: 8px; + background: rgba(var(--page-updater-rgb), .08); + border: 1px solid rgba(var(--page-updater-rgb), .18); + font-size: .82rem; + color: rgba(var(--text-rgb), .72); +} +.updater-autocheck strong { color: var(--text-primary, #fff); font-weight: 600; } +.updater-autocheck-dot { + width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; + background: #36d399; box-shadow: 0 0 8px rgba(54, 211, 153, .6); +} +.updater-autocheck.off { + background: rgba(var(--text-rgb), .04); + border-color: rgba(var(--text-rgb), .1); +} +.updater-autocheck.off .updater-autocheck-dot { background: rgba(var(--text-rgb), .3); box-shadow: none; } .updater-detail-container .updater-detail-section + .updater-detail-section { border-top: 1px solid var(--border-color); } diff --git a/containers/libreportal/frontend/components/updater/js/updater-page.js b/containers/libreportal/frontend/components/updater/js/updater-page.js index e7bd2af..0f03fcc 100644 --- a/containers/libreportal/frontend/components/updater/js/updater-page.js +++ b/containers/libreportal/frontend/components/updater/js/updater-page.js @@ -545,6 +545,38 @@ class UpdaterPage { if (s < 86400) return `${Math.floor(s / 3600)}h ago`; return `${Math.floor(s / 86400)}d ago`; } + + // Approximate "when will the next automatic check run" — the auto-scan fires on + // the task processor's idle poll once updates.json is older than the interval, + // so this is an earliest-estimate, hence the "~". + fmtRelFuture(t) { + const s = (t - Date.now()) / 1000; + if (!t || isNaN(t) || s <= 0) return 'due now'; + if (s < 90) return 'in ~1m'; + if (s < 3600) return `in ~${Math.round(s / 60)}m`; + if (s < 86400) return `in ~${Math.round(s / 3600)}h`; + return `in ~${Math.round(s / 86400)}d`; + } + + // "Checked automatically · last checked X · next check ~Y" — LibrePortal scans + // for updates on a schedule (CFG_UPDATER_SCAN_INTERVAL, stamped into + // updates.json), so a manual "Check" button is redundant; this line says when + // the last scan ran and when the next is due instead. interval 0 = auto off. + renderAutoCheckLine() { + const gen = this.updates && this.updates.generated_at; + const iv = this.updates ? Number(this.updates.scan_interval_minutes) : NaN; + if (!gen && !(iv >= 0)) return ''; + const last = gen ? this.fmtRel(gen) : 'not yet'; + let nextBit = '', off = ''; + if (iv === 0) { + off = ' off'; + nextBit = ' · automatic checks are off'; + } else if (gen && iv > 0) { + nextBit = ` · next check ${this.fmtRelFuture(Date.parse(gen) + iv * 60000)}`; + } + return `
` + + `Checked automatically · last checked ${last}${nextBit}
`; + } } window.UpdaterPage = UpdaterPage; diff --git a/scripts/webui/data/generators/updater/webui_updater_scan.sh b/scripts/webui/data/generators/updater/webui_updater_scan.sh index 37efbbc..776bd42 100644 --- a/scripts/webui/data/generators/updater/webui_updater_scan.sh +++ b/scripts/webui/data/generators/updater/webui_updater_scan.sh @@ -225,12 +225,17 @@ webuiUpdaterScan() { done [ "$do_registry" = "1" ] && touch "$reg_stamp" 2>/dev/null || true + # The WebUI shows "checked automatically · next check ~N" from this stamp + + # the auto-scan cadence, so the display needs no separate config fetch. + local scan_interval="${CFG_UPDATER_SCAN_INTERVAL:-30}" + [[ "$scan_interval" =~ ^[0-9]+$ ]] || scan_interval=30 + local tmp; tmp="$(mktemp)" if [ "$have_jq" = "1" ]; then - jq -s --arg now "$now" '{generated_at:$now, apps:.}' "$objs" > "$tmp" 2>/dev/null \ - || printf '{ "generated_at": "%s", "apps": [] }\n' "$now" > "$tmp" + jq -s --arg now "$now" --argjson iv "$scan_interval" '{generated_at:$now, scan_interval_minutes:$iv, apps:.}' "$objs" > "$tmp" 2>/dev/null \ + || printf '{ "generated_at": "%s", "scan_interval_minutes": %s, "apps": [] }\n' "$now" "$scan_interval" > "$tmp" else - { printf '{ "generated_at": "%s", "apps": [' "$now" + { printf '{ "generated_at": "%s", "scan_interval_minutes": %s, "apps": [' "$now" "$scan_interval" paste -sd, "$objs"; printf '] }\n'; } > "$tmp" fi rm -f "$objs"