import { announce, apiJson } from "../core.js"; import { setControlBusy, setControlReady, setControlUnavailable } from "../interaction-feedback.js"; import { createLatchedDraftState } from "../wifi-draft-state.js"; const ssidInput = document.getElementById("wifi-ssid"); const passwordInput = document.getElementById("wifi-password"); const dhcpToggle = document.getElementById("wifi-dhcp-enabled"); const dhcpState = document.getElementById("wifi-dhcp-state"); const dhcpRow = dhcpToggle.closest(".wifi-dhcp-row"); const manualFields = document.getElementById("wifi-manual-fields"); const addressInput = document.getElementById("wifi-address"); const gatewayInput = document.getElementById("wifi-gateway"); const prefixInput = document.getElementById("wifi-prefix"); const dnsInput = document.getElementById("wifi-dns"); const immediateButton = document.getElementById("wifi-apply-immediate"); const nextBootButton = document.getElementById("wifi-apply-next-boot"); const activeStatus = document.getElementById("wifi-active-status"); const feedback = document.getElementById("wifi-feedback"); const delayInput = document.getElementById("wifi-prompt-delay"); const delaySaveButton = document.getElementById("wifi-prompt-delay-save"); const delayFeedback = document.getElementById("wifi-prompt-feedback"); const networkFields = new Map([ ["ssid", { control: ssidInput, marker: document.getElementById("wifi-ssid-modified"), highlight: ssidInput }], ["password", { control: passwordInput, marker: document.getElementById("wifi-password-modified"), highlight: passwordInput }], ["dhcp", { control: dhcpToggle, marker: document.getElementById("wifi-dhcp-modified"), highlight: dhcpRow }], ["address", { control: addressInput, marker: document.getElementById("wifi-address-modified"), highlight: addressInput }], ["gateway", { control: gatewayInput, marker: document.getElementById("wifi-gateway-modified"), highlight: gatewayInput }], ["prefix", { control: prefixInput, marker: document.getElementById("wifi-prefix-modified"), highlight: prefixInput }], ["dns", { control: dnsInput, marker: document.getElementById("wifi-dns-modified"), highlight: dnsInput }], ]); const networkDraft = createLatchedDraftState(networkFields.keys()); const delayDraft = createLatchedDraftState(["prompt_delay_seconds"]); let wifiPending = false; let delayPending = false; let settingsLoaded = false; let loadGeneration = 0; let baseline = null; function setFeedback(message, state = "idle") { feedback.textContent = message; feedback.dataset.state = state; feedback.hidden = !message; } function setDelayFeedback(message, state = "idle") { delayFeedback.textContent = message; delayFeedback.dataset.state = state; delayFeedback.hidden = !message; } function showDhcpMode() { const enabled = dhcpToggle.checked; dhcpState.textContent = enabled ? "开启" : "关闭"; manualFields.hidden = enabled; addressInput.required = !enabled; gatewayInput.required = !enabled; } function splitDns(value) { return value.split(/[,,\s]+/u).map((item) => item.trim()).filter(Boolean); } function showStatus(data) { const active = data.active || {}; if (active.connected) { activeStatus.textContent = `当前已连接:${active.ssid || "未知 WiFi"} · ${active.ipv4_address || "等待 IPv4"}`; } else if (data.available === false) { activeStatus.textContent = `网络状态不可用:${data.error || "NetworkManager 无响应"}`; } else { activeStatus.textContent = "当前未连接 WiFi"; } } function formStateFromResponse(data) { const saved = data.saved || {}; return { ssid: saved.ssid || "", password: saved.password || "", passwordConfigured: Boolean(saved.password_configured), dhcp: (saved.ipv4_mode || "dhcp") === "dhcp", address: saved.address || "", gateway: saved.gateway || "", prefix: saved.prefix ?? "", dns: (saved.dns_servers || []).join(", "), promptDelay: data.prompt_delay_seconds ?? 30, }; } function applyFormState(state) { if (!state) return; ssidInput.value = state.ssid; passwordInput.value = state.password; passwordInput.placeholder = state.passwordConfigured ? "" : "请输入 8..63 字节密码"; dhcpToggle.checked = state.dhcp; addressInput.value = state.address; gatewayInput.value = state.gateway; prefixInput.value = state.prefix; dnsInput.value = state.dns; delayInput.value = String(state.promptDelay); showDhcpMode(); } function setNetworkMarker(key, modified) { const field = networkFields.get(key); field.marker.hidden = !modified; field.highlight.classList.toggle("wifi-field-modified", modified); } function clearNetworkDraft() { networkDraft.clear(); networkFields.forEach((_field, key) => setNetworkMarker(key, false)); updateNetworkButtons(); } function clearDelayDraft() { delayDraft.clear(); updateDelayButton(); } function updateNetworkButtons() { if (wifiPending) return; const ready = settingsLoaded && networkDraft.dirty; immediateButton.classList.toggle("wifi-action-ready", ready); nextBootButton.classList.toggle("wifi-action-ready", ready); if (ready) { setControlReady(immediateButton); setControlReady(nextBootButton); return; } const reason = settingsLoaded ? "请先修改 WiFi 或 DHCP 设置,再选择生效方式。" : "请等待当前 WiFi 设置读取完成。"; setControlUnavailable(immediateButton, reason); setControlUnavailable(nextBootButton, reason); } function updateDelayButton() { if (delayPending) return; const ready = settingsLoaded && delayDraft.dirty; delaySaveButton.classList.toggle("wifi-action-ready", ready); if (ready) { setControlReady(delaySaveButton); return; } setControlUnavailable( delaySaveButton, settingsLoaded ? "请先修改网络提示等待时间。" : "请等待当前网络提示设置读取完成。", ); } function markNetworkField(key) { if (!settingsLoaded) return; networkDraft.mark(key); setNetworkMarker(key, true); updateNetworkButtons(); } function markDelayField() { if (!settingsLoaded) return; delayDraft.mark("prompt_delay_seconds"); updateDelayButton(); } function syncForm(data) { baseline = formStateFromResponse(data); applyFormState(baseline); settingsLoaded = true; clearNetworkDraft(); clearDelayDraft(); showStatus(data); } export async function loadWifiSettings() { const generation = ++loadGeneration; settingsLoaded = false; clearNetworkDraft(); clearDelayDraft(); try { const data = await apiJson("/api/network/wifi"); if (generation !== loadGeneration) return; syncForm(data); setFeedback(""); setDelayFeedback(""); } catch (error) { if (generation !== loadGeneration) return; setFeedback(`WiFi 设置读取失败:${error.message}`, "error"); announce(`WiFi 设置读取失败:${error.message}`, true); updateNetworkButtons(); updateDelayButton(); } } function buildRequest(activation) { const ssid = ssidInput.value.trim(); if (!ssid) throw new Error("请输入 WiFi 名称(SSID)"); const password = passwordInput.value; if (ssid !== baseline.ssid && !password) throw new Error("修改 WiFi 名称时必须填写新密码"); if (!password && !baseline.passwordConfigured) throw new Error("当前没有已保存密码,请填写 WiFi 密码"); const manual = !dhcpToggle.checked; if (manual && !addressInput.value.trim()) throw new Error("关闭 DHCP 后必须填写静态 IPv4 地址"); if (manual && !gatewayInput.value.trim()) throw new Error("关闭 DHCP 后必须填写网关"); const rawPrefix = prefixInput.value.trim(); const prefix = rawPrefix ? Number(rawPrefix) : null; if (rawPrefix && (!Number.isInteger(prefix) || prefix < 1 || prefix > 32)) { throw new Error("子网前缀必须是 1..32 的整数"); } return { ssid, password: password || null, ipv4_mode: manual ? "manual" : "dhcp", address: manual ? addressInput.value.trim() : null, gateway: manual ? gatewayInput.value.trim() : null, prefix, dns_servers: manual ? splitDns(dnsInput.value) : [], activation, }; } function captureNetworkBaseline(request) { baseline = { ...baseline, ssid: request.ssid, password: request.password || baseline.password, passwordConfigured: Boolean(request.password || baseline.passwordConfigured), dhcp: request.ipv4_mode === "dhcp", address: request.address || "", gateway: request.gateway || "", prefix: request.prefix ?? "", dns: (request.dns_servers || []).join(", "), }; } async function applyWifi(activation) { if (wifiPending || !networkDraft.dirty) return; let request; try { request = buildRequest(activation); } catch (error) { setFeedback(error.message, "error"); announce(error.message, true); return; } wifiPending = true; setControlBusy(immediateButton, { label: activation === "immediate" ? "正在保存…" : undefined, message: "正在安全保存 WiFi 与 IPv4 配置…", }); setControlBusy(nextBootButton, { label: activation === "next_boot" ? "正在保存…" : undefined, }); setFeedback( activation === "immediate" ? "正在保存;设备随后会切换网络,当前页面可能断开。" : "正在保存下次断电开机使用的网络配置…", "pending", ); try { const result = await apiJson("/api/network/wifi", { method: "PUT", body: JSON.stringify(request), }); captureNetworkBaseline(request); clearNetworkDraft(); setFeedback(result.message, "success"); announce(result.message); if (result.network) showStatus(result.network); } catch (error) { setFeedback(`WiFi 设置保存失败:${error.message}`, "error"); announce(`WiFi 设置保存失败:${error.message}`, true); } finally { wifiPending = false; updateNetworkButtons(); } } function validatedPromptDelay() { const delay = Number(delayInput.value); if (!Number.isInteger(delay) || delay < 1 || delay > 3600) { throw new Error("网络提示等待时间必须是 1..3600 秒的整数"); } return delay; } async function savePromptDelay() { if (delayPending || !delayDraft.dirty) return; let delay; try { delay = validatedPromptDelay(); } catch (error) { setDelayFeedback(error.message, "error"); announce(error.message, true); return; } delayPending = true; setControlBusy(delaySaveButton, { label: "正在保存…", message: "正在保存网络提示等待时间…", }); setDelayFeedback("正在保存网络提示等待时间…", "pending"); try { const result = await apiJson("/api/network/wifi/prompt-delay", { method: "PUT", body: JSON.stringify({ prompt_delay_seconds: delay }), }); baseline = { ...baseline, promptDelay: result.prompt_delay_seconds }; delayInput.value = String(result.prompt_delay_seconds); clearDelayDraft(); setDelayFeedback(result.message, "success"); announce(result.message); } catch (error) { setDelayFeedback(`网络提示等待时间保存失败:${error.message}`, "error"); announce(`网络提示等待时间保存失败:${error.message}`, true); } finally { delayPending = false; updateDelayButton(); } } export function discardWifiSettingsDrafts() { loadGeneration += 1; applyFormState(baseline); settingsLoaded = false; clearNetworkDraft(); clearDelayDraft(); setFeedback(""); setDelayFeedback(""); } for (const [key, field] of networkFields) { if (key === "dhcp") continue; field.control.addEventListener("input", () => markNetworkField(key)); } dhcpToggle.addEventListener("change", () => { showDhcpMode(); markNetworkField("dhcp"); }); delayInput.addEventListener("input", markDelayField); immediateButton.addEventListener("click", () => applyWifi("immediate")); nextBootButton.addEventListener("click", () => applyWifi("next_boot")); delaySaveButton.addEventListener("click", savePromptDelay); updateNetworkButtons(); updateDelayButton();