初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -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();