Compare commits
2 Commits
dea4764bb6
...
3134afecd2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3134afecd2 | ||
|
|
1de4d5d970 |
@ -591,17 +591,20 @@ class AppTabbedManager {
|
|||||||
const app = (this.appUpdater.apps || []).find((x) => x.name === this.currentApp);
|
const app = (this.appUpdater.apps || []).find((x) => x.name === this.currentApp);
|
||||||
// Always lead with the title block + recessed dark container, so the tab
|
// Always lead with the title block + recessed dark container, so the tab
|
||||||
// matches the Config / Backups / Tasks tabs whether or not there's data.
|
// matches the Config / Backups / Tasks tabs whether or not there's data.
|
||||||
|
const autoLine = this.appUpdater.renderAutoCheckLine();
|
||||||
const body = app
|
const body = app
|
||||||
? this.appUpdater.renderAppDetail(app, { includeVersion: true })
|
? 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) +
|
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
|
// 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
|
// left, the Update action on the right. Mirrors .backup-title (.config-title
|
||||||
// family) so every app-detail tab shares one header idiom. `a` may be null
|
// family) so every app-detail tab shares one header idiom. There's no manual
|
||||||
// (no data yet); the Update button only appears when an update is available.
|
// "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) {
|
renderAppUpdaterHead(a) {
|
||||||
const esc = (s) => (this.appUpdater ? this.appUpdater.escape(s) : String(s == null ? '' : s));
|
const esc = (s) => (this.appUpdater ? this.appUpdater.escape(s) : String(s == null ? '' : s));
|
||||||
const name = this.currentApp
|
const name = this.currentApp
|
||||||
@ -616,7 +619,6 @@ class AppTabbedManager {
|
|||||||
<p>Version, security and recovery for ${esc(name)}.</p>
|
<p>Version, security and recovery for ${esc(name)}.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="updater-title-actions">
|
<div class="updater-title-actions">
|
||||||
<button class="updater-btn" data-updater-action="check">↻ Check</button>
|
|
||||||
${updBtn}
|
${updBtn}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
@ -360,6 +360,32 @@
|
|||||||
.updater-detail-container .updater-detail-section { padding: 16px 0; }
|
.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:first-child { padding-top: 0; }
|
||||||
.updater-detail-container .updater-detail-section:last-child { padding-bottom: 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 {
|
.updater-detail-container .updater-detail-section + .updater-detail-section {
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -545,6 +545,38 @@ class UpdaterPage {
|
|||||||
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
||||||
return `${Math.floor(s / 86400)}d 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;
|
window.UpdaterPage = UpdaterPage;
|
||||||
|
|||||||
@ -225,12 +225,17 @@ webuiUpdaterScan() {
|
|||||||
done
|
done
|
||||||
[ "$do_registry" = "1" ] && touch "$reg_stamp" 2>/dev/null || true
|
[ "$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)"
|
local tmp; tmp="$(mktemp)"
|
||||||
if [ "$have_jq" = "1" ]; then
|
if [ "$have_jq" = "1" ]; then
|
||||||
jq -s --arg now "$now" '{generated_at:$now, apps:.}' "$objs" > "$tmp" 2>/dev/null \
|
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", "apps": [] }\n' "$now" > "$tmp"
|
|| printf '{ "generated_at": "%s", "scan_interval_minutes": %s, "apps": [] }\n' "$now" "$scan_interval" > "$tmp"
|
||||||
else
|
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"
|
paste -sd, "$objs"; printf '] }\n'; } > "$tmp"
|
||||||
fi
|
fi
|
||||||
rm -f "$objs"
|
rm -f "$objs"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user