6 Commits

Author SHA1 Message Date
Aaron
28acb94a6f Limit inline settings messages to errors; restore status text defaults 2025-12-13 10:45:29 -05:00
Aaron
2c60ba981b Prevent release modal dismiss on backdrop click 2025-12-13 10:41:14 -05:00
Aaron
92e4ce88df Suppress inline error flash on release status fetch 2025-12-13 10:37:30 -05:00
Aaron
c1eb7d0765 Dedup updater log entries 2025-12-13 10:34:40 -05:00
Aaron
c66f7d78a0 Hide release status line for non-update states 2025-12-13 10:30:44 -05:00
Aaron
c20ea57da6 Throttle updater toasts and bump cache-bust 2025-12-13 10:27:35 -05:00
5 changed files with 46 additions and 41 deletions

View File

@@ -5,7 +5,7 @@ import { placeholderStatus, renderStats } from "./status.js";
import { initServiceControls, renderServices } from "./services.js"; import { initServiceControls, renderServices } from "./services.js";
import { initSettings } from "./settings.js"; import { initSettings } from "./settings.js";
import { initUpdateSettings, isUpdatesDirty } from "./update-settings.js"; import { initUpdateSettings, isUpdatesDirty } from "./update-settings.js";
import { initReleaseUI } from "./releases.js?v=20251213f"; import { initReleaseUI } from "./releases.js?v=20251213g";
const servicesGrid = document.getElementById("servicesGrid"); const servicesGrid = document.getElementById("servicesGrid");
const heroStats = document.getElementById("heroStats"); const heroStats = document.getElementById("heroStats");

View File

@@ -45,15 +45,19 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
let releaseLogLines = []; let releaseLogLines = [];
let releaseLastFetched = 0; let releaseLastFetched = 0;
let lastReleaseLogKey = ""; let lastReleaseLogKey = "";
let lastReleaseToastKey = null;
let lastLogMessage = null;
let changelogCache = { version: null, text: "" }; let changelogCache = { version: null, text: "" };
let lastChangelogUrl = null; let lastChangelogUrl = null;
let releaseChannel = "dev"; let releaseChannel = "dev";
function logRelease(msg) { function logRelease(msg) {
if (!msg) return; if (!msg) return;
const plain = msg.trim();
if (plain === lastLogMessage) return;
lastLogMessage = plain;
const ts = new Date().toLocaleTimeString(); const ts = new Date().toLocaleTimeString();
const line = `${ts} ${msg}`; const line = `${ts} ${msg}`;
if (releaseLogLines[0] === line) return;
releaseLogLines.unshift(line); releaseLogLines.unshift(line);
releaseLogLines = releaseLogLines.slice(0, 120); releaseLogLines = releaseLogLines.slice(0, 120);
if (releaseLog) { if (releaseLog) {
@@ -84,12 +88,8 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
const msg = shorten(message, 80) || ""; const msg = shorten(message, 80) || "";
releaseFlagTop.title = msg || "Pi-Kit release status"; releaseFlagTop.title = msg || "Pi-Kit release status";
if (releaseStatusMsg) { if (releaseStatusMsg) {
releaseStatusMsg.textContent = releaseStatusMsg.textContent = status === "update_available" ? msg || "Update available" : "";
status === "update_available" releaseStatusMsg.classList.remove("error");
? msg || "Update available"
: status === "up_to_date"
? msg || "Up to date"
: msg || status;
} }
if (releaseLogStatus) { if (releaseLogStatus) {
releaseLogStatus.textContent = releaseLogStatus.textContent =
@@ -177,10 +177,9 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
} catch (e) { } catch (e) {
console.error("Failed to load release status", e); console.error("Failed to load release status", e);
setReleaseChip({ status: "error", message: "Failed to load" }); setReleaseChip({ status: "error", message: "Failed to load" });
if (releaseStatusMsg) { // surface via toast/log only; avoid inline red flashes
releaseStatusMsg.textContent = "Failed to load release status"; showToast("Failed to load release status", "error");
releaseStatusMsg.classList.add("error"); logRelease("Error: failed to load release status");
}
} }
} }
@@ -200,11 +199,20 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
releaseBusyActive = false; releaseBusyActive = false;
hideBusy(); hideBusy();
if (releaseProgress) releaseProgress.textContent = ""; if (releaseProgress) releaseProgress.textContent = "";
if (state.status === "up_to_date") { // Only toast once per apply/rollback cycle
if (state.status === "up_to_date" && releaseBusyActive === false) {
const key = `ok-${state.current_version || ""}-${state.latest_version || ""}`;
if (lastReleaseToastKey !== key) {
lastReleaseToastKey = key;
showToast(state.message || "Update complete", "success"); showToast(state.message || "Update complete", "success");
}
logRelease("Update complete"); logRelease("Update complete");
} else if (state.status === "error") { } else if (state.status === "error") {
const key = `err-${state.message || ""}`;
if (lastReleaseToastKey !== key) {
lastReleaseToastKey = key;
showToast(state.message || "Update failed", "error"); showToast(state.message || "Update failed", "error");
}
logRelease(`Error: ${state.message || "Update failed"}`); logRelease(`Error: ${state.message || "Update failed"}`);
} }
} }
@@ -218,8 +226,11 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
loadReleaseStatus(true); loadReleaseStatus(true);
}); });
releaseClose?.addEventListener("click", () => releaseModal?.classList.add("hidden")); releaseClose?.addEventListener("click", () => releaseModal?.classList.add("hidden"));
// Do not allow dismiss by clicking backdrop (consistency with other modals)
releaseModal?.addEventListener("click", (e) => { releaseModal?.addEventListener("click", (e) => {
if (e.target === releaseModal) releaseModal.classList.add("hidden"); if (e.target === releaseModal) {
e.stopPropagation();
}
}); });
releaseCheckBtn?.addEventListener("click", async () => { releaseCheckBtn?.addEventListener("click", async () => {
@@ -242,6 +253,7 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
releaseApplyBtn?.addEventListener("click", async () => { releaseApplyBtn?.addEventListener("click", async () => {
try { try {
lastReleaseToastKey = null;
const state = window.__lastReleaseState || {}; const state = window.__lastReleaseState || {};
const { current_version, latest_version } = state; const { current_version, latest_version } = state;
const sameVersion = const sameVersion =
@@ -274,6 +286,7 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction })
releaseRollbackBtn?.addEventListener("click", async () => { releaseRollbackBtn?.addEventListener("click", async () => {
try { try {
lastReleaseToastKey = null;
releaseBusyActive = true; releaseBusyActive = true;
showBusy("Rolling back…", "Restoring previous backup."); showBusy("Rolling back…", "Restoring previous backup.");
logRelease("Starting rollback…"); logRelease("Starting rollback…");

View File

@@ -425,15 +425,6 @@ body {
#releaseProgress { #releaseProgress {
display: none; display: none;
} }
.status-msg {
display: none;
}
.status-msg.error {
display: block;
}
#releaseStatusMsg {
display: block;
}
.updates-status { .updates-status {
display: none; display: none;
} }

View File

@@ -115,14 +115,15 @@ export function initUpdateSettings({
function showMessage(text, isError = false) { function showMessage(text, isError = false) {
if (!msgEl) return; if (!msgEl) return;
msgEl.textContent = text || ""; // Only surface inline text for errors; successes go to toast only.
msgEl.classList.toggle("error", isError); if (isError) {
msgEl.textContent = text || "Something went wrong";
if (text) { msgEl.classList.add("error");
if (toast && msgEl.id !== "updatesMsg") toast(text, isError ? "error" : "success"); } else {
setTimeout(() => (msgEl.textContent = ""), 2500); msgEl.textContent = "";
msgEl.classList.remove("error");
} }
if (toast) toast(text || (isError ? "Error" : ""), isError ? "error" : "success");
} }
function currentConfigFromForm() { function currentConfigFromForm() {

View File

@@ -726,7 +726,7 @@
</div> </div>
</div> </div>
<script type="module" src="assets/main.js?v=20251213f"></script> <script type="module" src="assets/main.js?v=20251213g"></script>
<div id="toastContainer" class="toast-container"></div> <div id="toastContainer" class="toast-container"></div>
</body> </body>
</html> </html>