Merge claude/2
This commit is contained in:
commit
3134afecd2
@ -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 })
|
||||
: `<div class="updater-empty">No update data for this app yet. <button class="updater-btn updater-btn-primary" data-updater-action="check">Check now</button></div>`;
|
||||
: `<div class="updater-empty">No scan data for this app yet — the first automatic check will fill this in shortly.</div>`;
|
||||
section.innerHTML = this.renderAppUpdaterHead(app) +
|
||||
`<div class="updater-detail-container">${body}</div>`;
|
||||
`<div class="updater-detail-container">${autoLine}${body}</div>`;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
<p>Version, security and recovery for ${esc(name)}.</p>
|
||||
</div>
|
||||
<div class="updater-title-actions">
|
||||
<button class="updater-btn" data-updater-action="check">↻ Check</button>
|
||||
${updBtn}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 `<div class="updater-autocheck${off}"><span class="updater-autocheck-dot"></span>` +
|
||||
`<span>Checked automatically · last checked <strong>${last}</strong>${nextBit}</span></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.UpdaterPage = UpdaterPage;
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user