You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.2 KiB
JavaScript
141 lines
4.2 KiB
JavaScript
const state = {
|
|
result: null,
|
|
frequencies: null,
|
|
health: null,
|
|
};
|
|
|
|
function byId(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
function fmt(value, digits = 6) {
|
|
if (value === null || value === undefined) return "-";
|
|
if (typeof value !== "number") return String(value);
|
|
return Number.isFinite(value) ? value.toFixed(digits) : String(value);
|
|
}
|
|
|
|
async function getJson(url) {
|
|
const res = await fetch(url);
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
throw new Error(data.error || data.status || `HTTP ${res.status}`);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
async function postJson(url, payload) {
|
|
const res = await fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
throw new Error(data.error || data.status || `HTTP ${res.status}`);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function render() {
|
|
const data = state.result?.data;
|
|
const delivery = state.result?.output_delivery || state.frequencies?.output_delivery;
|
|
byId("updated-at").textContent = `updated: ${state.result?.updated_at_utc || "n/a"}`;
|
|
byId("health-status").textContent = `health: ${state.health?.status || "n/a"}`;
|
|
byId("delivery-status").textContent = `delivery: ${delivery?.status || "n/a"}`;
|
|
|
|
if (!data) {
|
|
byId("selected-freq").textContent = "-";
|
|
byId("pos-x").textContent = "-";
|
|
byId("pos-y").textContent = "-";
|
|
byId("pos-z").textContent = "-";
|
|
byId("rmse").textContent = "-";
|
|
byId("receivers-list").textContent = "Нет данных";
|
|
byId("delivery-details").textContent = JSON.stringify(delivery || {}, null, 2);
|
|
byId("freq-table").querySelector("tbody").innerHTML = "";
|
|
return;
|
|
}
|
|
|
|
byId("selected-freq").textContent = fmt(data.selected_frequency_hz, 1);
|
|
byId("pos-x").textContent = fmt(data.position?.x);
|
|
byId("pos-y").textContent = fmt(data.position?.y);
|
|
byId("pos-z").textContent = fmt(data.position?.z);
|
|
byId("rmse").textContent = fmt(data.rmse_m);
|
|
|
|
const receivers = data.receivers || [];
|
|
byId("receivers-list").textContent = JSON.stringify(receivers, null, 2);
|
|
byId("delivery-details").textContent = JSON.stringify(delivery || {}, null, 2);
|
|
|
|
const rows = data.frequency_table || [];
|
|
const tbody = byId("freq-table").querySelector("tbody");
|
|
tbody.innerHTML = rows
|
|
.map(
|
|
(row) => `
|
|
<tr>
|
|
<td>${fmt(row.frequency_hz, 1)}</td>
|
|
<td>${fmt(row.position?.x)}</td>
|
|
<td>${fmt(row.position?.y)}</td>
|
|
<td>${fmt(row.position?.z)}</td>
|
|
<td>${fmt(row.rmse_m)}</td>
|
|
<td>${row.exact ? "yes" : "no"}</td>
|
|
</tr>`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
async function loadAll() {
|
|
const [healthRes, resultRes, freqRes] = await Promise.allSettled([
|
|
getJson("/health"),
|
|
getJson("/result"),
|
|
getJson("/frequencies"),
|
|
]);
|
|
state.health = healthRes.status === "fulfilled" ? healthRes.value : { status: "error" };
|
|
state.result = resultRes.status === "fulfilled" ? resultRes.value : null;
|
|
state.frequencies = freqRes.status === "fulfilled" ? freqRes.value : null;
|
|
render();
|
|
}
|
|
|
|
async function refreshNow() {
|
|
await postJson("/refresh", {});
|
|
await loadAll();
|
|
}
|
|
|
|
async function loadConfig() {
|
|
try {
|
|
const config = await getJson("/config");
|
|
byId("config-editor").value = JSON.stringify(config.config, null, 2);
|
|
byId("config-state").textContent = "config: loaded";
|
|
} catch (err) {
|
|
byId("config-state").textContent = `config: ${err.message}`;
|
|
}
|
|
}
|
|
|
|
async function saveConfig() {
|
|
const raw = byId("config-editor").value.trim();
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
const result = await postJson("/config", parsed);
|
|
byId("config-state").textContent = result.restart_required
|
|
? "config: saved, restart required"
|
|
: "config: saved";
|
|
} catch (err) {
|
|
byId("config-state").textContent = `config: ${err.message}`;
|
|
}
|
|
}
|
|
|
|
function bindUi() {
|
|
byId("refresh-now").addEventListener("click", refreshNow);
|
|
byId("load-config").addEventListener("click", loadConfig);
|
|
byId("save-config").addEventListener("click", saveConfig);
|
|
}
|
|
|
|
async function boot() {
|
|
bindUi();
|
|
await loadConfig();
|
|
await loadAll();
|
|
setInterval(loadAll, 2000);
|
|
}
|
|
|
|
boot().catch((err) => {
|
|
byId("health-status").textContent = `health: ${err.message}`;
|
|
});
|