const form = document.querySelector("[data-config-form]"); const statusGrid = document.querySelector("[data-status-grid]"); const message = document.querySelector("[data-message]"); const refreshButton = document.querySelector("[data-refresh]"); function getPath(object, path) { return path.split(".").reduce((current, key) => current?.[key], object); } function setPath(object, path, value) { const parts = path.split("."); let cursor = object; for (const part of parts.slice(0, -1)) { cursor[part] ??= {}; cursor = cursor[part]; } cursor[parts.at(-1)] = value; } function coerceInputValue(input) { if (input.type === "checkbox") return input.checked; if (input.type === "number") return Number(input.value); return input.value.trim(); } function renderStatus(config, bridge, kasm, camera) { const items = [ ["Agent", config?.server?.publicBaseUrl ?? "online"], ["Workspace", config?.workspace?.root ?? "-"], ["ROS", `${config?.ros?.stack ?? "-"} · ${bridge?.stack?.stack ?? bridge?.health?.stack ?? "offline"}`], ["KasmVNC", kasm?.running ? `running · ${kasm.url}` : kasm?.installed ? "stopped" : "not installed"], ["Camera", camera?.enabled ? `${camera.videoDevice} · enabled` : "disabled"], ]; statusGrid.innerHTML = items.map(([label, value]) => `
${label} ${value}
`).join(""); } function fillForm(config) { for (const input of form.elements) { if (!input.name) continue; const value = getPath(config, input.name); if (input.type === "checkbox") { input.checked = Boolean(value); } else if (value !== undefined) { input.value = String(value); } } } function readFormPatch() { const patch = {}; for (const input of form.elements) { if (!input.name) continue; setPath(patch, input.name, coerceInputValue(input)); } return patch; } async function json(path, init) { const response = await fetch(path, init); const data = await response.json().catch(() => ({})); if (!response.ok) throw new Error(data.message || data.detail || `HTTP ${response.status}`); return data; } async function refresh() { message.textContent = "Loading..."; const [config, bridge, kasm, camera] = await Promise.all([ json("/api/config"), json("/api/bridge/status").catch((error) => ({ ok: false, health: { error: error.message } })), json("/api/kasmvnc/status").catch((error) => ({ ok: false, message: error.message })), json("/api/camera/status").catch((error) => ({ enabled: false, error: error.message })), ]); fillForm(config); renderStatus(config, bridge, kasm, camera); message.textContent = ""; } form?.addEventListener("submit", async (event) => { event.preventDefault(); message.textContent = "Saving..."; try { const config = await json("/api/config", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(readFormPatch()), }); fillForm(config); await refresh(); message.textContent = "Saved."; } catch (error) { message.textContent = error instanceof Error ? error.message : "Save failed."; } }); refreshButton?.addEventListener("click", () => refresh().catch((error) => { message.textContent = error instanceof Error ? error.message : "Refresh failed."; })); refresh().catch((error) => { message.textContent = error instanceof Error ? error.message : "Load failed."; });