Отлаженная версия в кабинете от 06.05.2026

This commit is contained in:
Maxim
2026-05-06 11:41:48 +03:00
parent f9b5ab27d5
commit 80aeb1a5eb
12 changed files with 305 additions and 240 deletions

View File

@@ -0,0 +1,198 @@
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: monospace;
background: #050805;
color: #00ff88;
padding: 12px;
}
h1 {
text-align: center;
font-size: 22px;
margin: 0 0 15px;
}
/* ===== GRID ===== */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
/* Desktop */
@media (min-width: 900px) {
.grid {
grid-template-columns: 1fr 1fr;
}
.wide {
grid-column: span 2;
}
}
/* ===== CARD ===== */
.card {
border: 1px solid #00ff88;
border-radius: 10px;
padding: 14px;
background: rgba(0,255,136,0.05);
overflow: hidden;
}
.card h3 {
margin: 0 0 10px;
font-size: 18px;
}
/* ===== STATUS ===== */
#status {
font-size: 28px;
font-weight: bold;
margin-bottom: 8px;
}
#meta {
font-size: 14px;
opacity: 0.8;
word-break: break-word;
}
/* ===== LATEST ===== */
#latest {
font-size: 36px;
font-weight: bold;
margin-bottom: 12px;
}
/* ===== BITS ===== */
#bits {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
.bit {
height: 52px;
border: 1px solid #00ff88;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
font-size: 22px;
font-weight: bold;
}
.bit.on {
background: #00ff88;
color: #000;
}
/* Tablet/Desktop */
@media (min-width: 700px) {
#bits {
grid-template-columns: repeat(8, 1fr);
}
}
/* ===== HISTORY ===== */
#history {
font-size: 13px;
line-height: 1.35;
max-height: 420px;
overflow-y: auto;
white-space: nowrap;
padding-right: 4px;
}
/* Desktop */
@media (min-width: 900px) {
#history {
font-size: 12px;
max-height: 520px;
}
}
/* ===== PHONE ===== */
@media (max-width: 500px) {
h1 {
font-size: 18px;
}
#status {
font-size: 22px;
}
#latest {
font-size: 30px;
}
.bit {
height: 44px;
font-size: 18px;
}
#history {
font-size: 16px;
line-height: 1.5;
max-height: 55vh;
}
}
/* ===== PROGRESS BARS ===== */
.progress-row {
margin-bottom: 16px;
}
.progress-label {
font-size: 16px;
margin-bottom: 6px;
color: #ffffff;
opacity: 0.9;
}
.progress {
width: 100%;
height: 34px;
padding: 6px;
border: 3px solid #8a8a8a;
background: #222;
box-shadow:
inset 0 0 6px rgba(255,255,255,0.15),
0 0 4px rgba(0,0,0,0.8);
}
.fill {
height: 100%;
width: 0%;
background: #e6852d;
box-shadow:
inset 0 0 4px rgba(255,255,255,0.2),
0 0 6px rgba(230,133,45,0.5);
transition: width 0.25s linear;
}
/* mobile */
@media (max-width: 500px) {
.progress {
height: 30px;
padding: 5px;
}
.progress-label {
font-size: 15px;
}
}

View File

@@ -0,0 +1,65 @@
<!-- web/dashboard.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>GPIO Панель</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>GPIO МОНИТОРИНГ В РЕАЛЬНОМ ВРЕМЕНИ</h1>
<div class="grid">
<div class="card">
<h3>СОСТОЯНИЕ</h3>
<div id="status">...</div>
<div id="meta"></div>
</div>
<div class="card">
<h3>ПОСЛЕДНИЙ СИГНАЛ</h3>
<div id="latest">--</div>
<div id="bits"></div>
</div>
<div class="card wide">
<h3>УРОВНИ КАНАЛОВ</h3>
<div class="progress-row">
<div class="progress-label">Канал 1</div>
<div class="progress">
<div class="fill" id="bar1"></div>
</div>
</div>
<div class="progress-row">
<div class="progress-label">Звук</div>
<div class="progress">
<div class="fill" id="bar2"></div>
</div>
</div>
<div class="progress-row">
<div class="progress-label">Высота</div>
<div class="progress">
<div class="fill" id="bar3"></div>
</div>
</div>
</div>
<div class="card wide">
<h3>ПОТОК ДАННЫХ (последние 32)</h3>
<div id="history"></div>
</div>
</div>
<script src="/js/app.js"></script>
</body>
</html>

BIN
cmd/server/web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=/dashboard.html">
</head>
<body>
</body>
</html>

101
cmd/server/web/js/app.js Normal file
View File

@@ -0,0 +1,101 @@
// ===== BIT RENDER =====
function renderBits(byte) {
const bits = document.getElementById("bits");
bits.innerHTML = "";
byte = Number(byte || 0);
for (let i = 7; i >= 0; i--) {
const v = (byte >> i) & 1;
const d = document.createElement("div");
d.className = "bit" + (v ? " on" : "");
d.textContent = v;
bits.appendChild(d);
}
}
// ===== PROGRESS BAR =====
function setBar(id, value) {
value = Math.max(0, Math.min(100, value));
document.getElementById(id).style.width = value + "%";
}
// ===== 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;
// ===== LATEST =====
const latest = Number(h.latest || 0);
document.getElementById("latest").textContent = latest;
renderBits(latest);
// ===== 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);
} catch (e) {
console.error("UPDATE ERROR:", e);
document.getElementById("status").textContent = "СЕРВЕР НЕДОСТУПЕН";
}
}
// быстрее и плавнее
setInterval(update, 500);
update();