初始化奇妙小屏幕控制器项目
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user