Add a "Check now" button to the Firmware update card
Build and push server image / build-and-push (push) Successful in 36s

GET /api/firmware/check's 15-minute throttle meant a genuinely new
Gitea release could sit invisible in the UI for up to that long even
though POST /api/firmware/apply-latest (unthrottled) would've picked
it up immediately. New ?force=true bypasses the throttle for an
explicit check; the button wires it up and surfaces errors instead of
failing silently like the passive poll.
This commit is contained in:
2026-07-21 22:29:05 -04:00
parent fdb5dc7ab3
commit 6a0072e383
3 changed files with 30 additions and 8 deletions
+8 -3
View File
@@ -529,7 +529,7 @@ def _apply_gitea_update(cfg: config.FrameConfig) -> str:
@app.get("/api/firmware/check", dependencies=[Depends(require_access_token)])
def api_firmware_check():
def api_firmware_check(force: bool = False):
"""Throttled check of the configured Gitea repo's latest release
(gitea_releases.UPDATE_CHECK_INTERVAL_S) -- cheap, since it only reads
the release's tag name, not its binaries. If firmware_auto_update is
@@ -538,13 +538,18 @@ def api_firmware_check():
Applying (auto or manual) needs to know the frame's board, which is
learned from the device's own X-Frame-Board header rather than
picked by the user -- update_available stays false until a device
has checked in at least once, regardless of what Gitea has."""
has checked in at least once, regardless of what Gitea has.
force=true (the "Check now" button) bypasses the throttle and always
hits Gitea -- otherwise a genuinely new release can sit invisible in
the UI for up to the full throttle interval even though it's already
live, since the passive poll won't look again until then."""
cfg = config.load()
if not cfg.firmware_update_repo_url:
return {"enabled": False}
now = time.time()
if now - cfg.firmware_update_checked_at >= gitea_releases.UPDATE_CHECK_INTERVAL_S:
if force or now - cfg.firmware_update_checked_at >= gitea_releases.UPDATE_CHECK_INTERVAL_S:
# Deliberately not updated on failure (see below) -- checked_at only
# advances on a successful reach, so a Gitea outage gets retried
# every poll instead of waiting out the full throttle interval.
+20 -4
View File
@@ -131,6 +131,7 @@
</div>
<button type="button" class="secondary" id="firmware-settings-save">Save</button>
<p class="sub" id="firmware-gitea-status" style="display: none; margin-top: 10px;"></p>
<button type="button" class="secondary" id="firmware-check-now">Check now</button>
<button type="button" id="firmware-update-btn" style="display: none;">Update frame</button>
</section>
</div>
@@ -541,13 +542,16 @@
}
});
async function loadFirmwareCheck() {
async function loadFirmwareCheck(force) {
const statusEl = document.getElementById('firmware-gitea-status');
const btn = document.getElementById('firmware-update-btn');
const boardEl = document.getElementById('firmware-board');
try {
const resp = await fetch('/api/firmware/check');
const resp = await fetch('/api/firmware/check' + (force ? '?force=true' : ''));
if (!resp.ok) {
if (force) {
showStatus(false, await resp.text());
}
return;
}
const data = await resp.json();
@@ -557,6 +561,9 @@
if (!data.enabled) {
statusEl.style.display = 'none';
btn.style.display = 'none';
if (force) {
showStatus(false, 'No Gitea repo URL configured.');
}
return;
}
statusEl.style.display = 'block';
@@ -573,12 +580,21 @@
statusEl.textContent = 'No releases found yet.';
btn.style.display = 'none';
}
if (force) {
showStatus(true, 'Checked.');
}
} catch (e) {
// A failed check is silent -- the manual upload path still works
// regardless, and this just retries on the next poll.
// A failed passive poll is silent -- the manual upload path still
// works regardless, and this just retries on the next poll. An
// explicit "Check now" click still surfaces the error.
if (force) {
showStatus(false, e.message);
}
}
}
document.getElementById('firmware-check-now').addEventListener('click', () => loadFirmwareCheck(true));
document.getElementById('firmware-update-btn').addEventListener('click', async () => {
const btn = document.getElementById('firmware-update-btn');
btn.disabled = true;