116 lines
3.9 KiB
JavaScript
116 lines
3.9 KiB
JavaScript
// ===== ACCORDION =====
|
||
const historyHeader = document.getElementById("history-header");
|
||
const historyBody = document.getElementById("history-body");
|
||
let accordionOpen = false;
|
||
historyBody.classList.add("collapsed"); // изначально свёрнут
|
||
|
||
historyHeader.addEventListener("click", () => {
|
||
accordionOpen = !accordionOpen;
|
||
historyHeader.textContent = (accordionOpen ? "▼" : "▶") + " ПРИНЯТЫЕ ДАННЫЕ";
|
||
historyBody.classList.toggle("collapsed", !accordionOpen);
|
||
});
|
||
|
||
// ===== PROGRESS BAR =====
|
||
function setBar(id, value) {
|
||
value = Math.max(0, Math.min(100, value));
|
||
document.getElementById(id).style.width = value + "%";
|
||
}
|
||
|
||
// ===== SIGNAL ICON =====
|
||
function setSignal(percent) {
|
||
const bars = document.querySelectorAll(".sig-bar");
|
||
bars.forEach(b => {
|
||
b.classList.remove("active-1", "active-2", "active-3", "active-4");
|
||
b.classList.add("dim");
|
||
});
|
||
|
||
if (percent > 75) {
|
||
bars[0].classList.add("active-1"); bars[0].classList.remove("dim");
|
||
bars[1].classList.add("active-2"); bars[1].classList.remove("dim");
|
||
bars[2].classList.add("active-3"); bars[2].classList.remove("dim");
|
||
bars[3].classList.add("active-4"); bars[3].classList.remove("dim");
|
||
} else if (percent > 50) {
|
||
bars[0].classList.add("active-1"); bars[0].classList.remove("dim");
|
||
bars[1].classList.add("active-2"); bars[1].classList.remove("dim");
|
||
bars[2].classList.add("active-3"); bars[2].classList.remove("dim");
|
||
} else if (percent > 25) {
|
||
bars[0].classList.add("active-1"); bars[0].classList.remove("dim");
|
||
bars[1].classList.add("active-2"); bars[1].classList.remove("dim");
|
||
} else if (percent > 0) {
|
||
bars[0].classList.add("active-1"); bars[0].classList.remove("dim");
|
||
}
|
||
}
|
||
|
||
// ===== SMOOTHING =====
|
||
let lastValue = 0;
|
||
|
||
function smoothBar(target) {
|
||
const alpha = 0.2;
|
||
lastValue = lastValue + alpha * (target - lastValue);
|
||
return lastValue;
|
||
}
|
||
|
||
// ===== MEDIAN FILTER =====
|
||
function median(values) {
|
||
if (values.length === 0) return 0;
|
||
|
||
const sorted = [...values].sort((a, b) => a - b);
|
||
const mid = Math.floor(sorted.length / 2);
|
||
|
||
return sorted.length % 2 !== 0
|
||
? sorted[mid]
|
||
: (sorted[mid - 1] + sorted[mid]) / 2;
|
||
}
|
||
|
||
// ===== MAIN LOOP =====
|
||
async function update() {
|
||
try {
|
||
const r1 = await fetch("/api/health");
|
||
const r2 = await fetch("/api/history");
|
||
|
||
const h = await r1.json();
|
||
const hist = await r2.json();
|
||
|
||
// ===== STATUS =====
|
||
document.getElementById("status").textContent = h.status || "неизвестно";
|
||
|
||
const filled = h.stats?.filled ?? 0;
|
||
const uptime = Math.round(h.uptime_sec || 0);
|
||
|
||
document.getElementById("meta").textContent =
|
||
"аптайм: " + uptime + "s | буфер: " + filled;
|
||
|
||
// ===== HISTORY =====
|
||
const arr = Array.isArray(hist.bytes) ? hist.bytes : [];
|
||
|
||
document.getElementById("history").innerHTML =
|
||
arr.map((b, i) =>
|
||
"[" + i + "] " +
|
||
(Number(b) & 0xFF).toString(2).padStart(8, "0") +
|
||
" = значение: " + (Number(b) & 0xFF)
|
||
).join("<br>");
|
||
|
||
// ===== FILTER (последние 9 значений) =====
|
||
const last = arr.slice(-9).map(v => Number(v) & 0xFF);
|
||
|
||
const filtered = median(last);
|
||
|
||
// ===== NORMALIZE =====
|
||
const percent = (filtered / 255) * 100;
|
||
|
||
// ===== SMOOTH =====
|
||
const smooth = smoothBar(percent);
|
||
|
||
// ===== ONLY CHANNEL 1 =====
|
||
setBar("bar1", smooth);
|
||
document.getElementById("last-byte").textContent = filtered;
|
||
setSignal(smooth);
|
||
|
||
} catch (e) {
|
||
console.error("UPDATE ERROR:", e);
|
||
document.getElementById("status").textContent = "СЕРВЕР НЕДОСТУПЕН";
|
||
}
|
||
}
|
||
|
||
setInterval(update, 500);
|
||
update(); |