diff --git a/pikit-web/assets/main.js b/pikit-web/assets/main.js index 6697150..393de59 100644 --- a/pikit-web/assets/main.js +++ b/pikit-web/assets/main.js @@ -110,6 +110,13 @@ const firstbootErrorClose = document.getElementById("firstbootErrorClose"); const firstbootCopyError = document.getElementById("firstbootCopyError"); const firstbootShowRecovery = document.getElementById("firstbootShowRecovery"); const firstbootRecovery = document.getElementById("firstbootRecovery"); +const networkModal = document.getElementById("networkModal"); +const networkClose = document.getElementById("networkClose"); +const networkReserveBtn = document.getElementById("networkReserveBtn"); +const networkStaticBtn = document.getElementById("networkStaticBtn"); +const networkLaterBtn = document.getElementById("networkLaterBtn"); +const networkHelpBtn = document.getElementById("networkHelpBtn"); +const networkProfileHint = document.getElementById("networkProfileHint"); const confirmModal = document.getElementById("confirmModal"); const confirmTitle = document.getElementById("confirmTitle"); const confirmBody = document.getElementById("confirmBody"); @@ -172,6 +179,58 @@ const firstbootUI = createFirstbootUI({ showToast, }); +const networkState = { + shown: false, + profile: null, +}; + +function networkKey(profile) { + const id = profile?.id || profile?.name || "profile"; + return `pikit:network-setup:${id}`; +} + +function markNetworkHandled(profile, message) { + if (!profile) return; + try { + localStorage.setItem(networkKey(profile), "done"); + } catch (err) { + // ignore storage failures + } + if (networkModal) networkModal.classList.add("hidden"); + networkState.shown = false; + if (message) showToast?.(message, "success"); +} + +function openNetworkModal(profile, force = false) { + if (!networkModal) return; + if (!profile?.requires_stable_ip && !force) return; + if (networkProfileHint) { + const name = profile?.name ? `${profile.name} profile` : "This profile"; + networkProfileHint.textContent = `${name} needs a stable IP address so your devices can always reach DNS.`; + } + networkModal.classList.remove("hidden"); + networkState.shown = true; + networkState.profile = profile; +} + +function shouldShowNetworkPrompt(firstbootData) { + if (!firstbootData || firstbootData.state !== "done") return false; + const profile = firstbootData.profile || null; + if (!profile?.requires_stable_ip) return false; + if (networkState.shown) return false; + try { + return localStorage.getItem(networkKey(profile)) !== "done"; + } catch (err) { + return true; + } +} + +function handleFirstbootStatus(firstbootData) { + if (shouldShowNetworkPrompt(firstbootData)) { + openNetworkModal(firstbootData.profile); + } +} + const statusController = createStatusController({ heroStats, servicesGrid, @@ -191,6 +250,7 @@ const statusController = createStatusController({ getError: getFirstbootError, ui: firstbootUI, }, + onFirstbootStatus: handleFirstbootStatus, }); const { loadStatus } = statusController; @@ -220,6 +280,30 @@ function wireDialogs() { addServiceModal?.addEventListener("click", (e) => { if (e.target === addServiceModal) addServiceModal.classList.add("hidden"); }); + networkModal?.addEventListener("click", (e) => { + if (e.target === networkModal) { + markNetworkHandled(networkState.profile, "Network setup saved"); + } + }); +} + +function wireNetworkSetup() { + networkClose?.addEventListener("click", () => { + markNetworkHandled(networkState.profile, "Network setup saved"); + }); + networkReserveBtn?.addEventListener("click", () => { + markNetworkHandled(networkState.profile, "Router reservation saved"); + }); + networkStaticBtn?.addEventListener("click", () => { + markNetworkHandled(networkState.profile, "Static IP reminder saved"); + }); + networkLaterBtn?.addEventListener("click", () => { + markNetworkHandled(networkState.profile, "Network setup saved"); + }); + networkHelpBtn?.addEventListener("click", () => { + if (helpModal) helpModal.classList.add("hidden"); + openNetworkModal(networkState.profile || { requires_stable_ip: true }, true); + }); } // Testing hook @@ -348,6 +432,7 @@ function main() { window.__pikitTest.exposeServiceForm?.(); } wireDialogs(); + wireNetworkSetup(); wireResetAndUpdates(); wireAccordions({ forceOpen: typeof window !== "undefined" && window.__pikitTest?.forceAccordionsOpen, diff --git a/pikit-web/assets/status-controller.js b/pikit-web/assets/status-controller.js index dfd1c8f..ccd8790 100644 --- a/pikit-web/assets/status-controller.js +++ b/pikit-web/assets/status-controller.js @@ -18,6 +18,7 @@ export function createStatusController({ setUpdatesUI = null, updatesFlagEl = null, firstboot = null, + onFirstbootStatus = null, }) { let lastStatusData = null; let lastFirstbootState = null; @@ -88,6 +89,7 @@ export function createStatusController({ firstbootData = await firstboot.getStatus(); lastFirstbootState = firstbootData?.state || lastFirstbootState; firstboot.ui.update(firstbootData); + onFirstbootStatus?.(firstbootData); if (firstbootData?.state === "error" && firstboot.getError) { const err = await firstboot.getError(); if (err?.present) { diff --git a/pikit-web/index.html b/pikit-web/index.html index 16602d5..a9ad5ac 100644 --- a/pikit-web/index.html +++ b/pikit-web/index.html @@ -132,6 +132,42 @@ + +