Initial commit
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
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]) => `
|
||||
<article>
|
||||
<span>${label}</span>
|
||||
<strong>${value}</strong>
|
||||
</article>
|
||||
`).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.";
|
||||
});
|
||||
Reference in New Issue
Block a user