diff --git a/cmd/server/web/dashboard.html b/cmd/server/web/dashboard.html
index 1cc9c5e..659600c 100644
--- a/cmd/server/web/dashboard.html
+++ b/cmd/server/web/dashboard.html
@@ -53,16 +53,17 @@
+
+
ГРАФИК КАНАЛА 1
+
+
+
-
-
ГРАФИК СИГНАЛА
-
-
diff --git a/cmd/server/web/js/app.js b/cmd/server/web/js/app.js
index 5849b15..f3fa7ec 100644
--- a/cmd/server/web/js/app.js
+++ b/cmd/server/web/js/app.js
@@ -1,6 +1,6 @@
// app.js (оркестратор)
import { fetchHealth, fetchHistory } from "./data.js";
-import { state, smooth, addSignal } from "./state.js";
+import { state, smooth, addSignals, resetBufferTracking } from "./state.js";
import {
initDOM,
setStatus,
@@ -15,9 +15,9 @@ import { drawChart } from "./chart.js";
let lastChart = 0;
let connectionLost = false;
-let consecutiveErrors = 0; // счётчик ошибок подряд
-const MAX_ERRORS = 3; // после скольки ошибок замедляем опрос
-let pollTimer = null; // храним ID таймера для возможности сброса
+let consecutiveErrors = 0;
+const MAX_ERRORS = 3;
+let pollTimer = null;
// ================= INIT =================
window.addEventListener("DOMContentLoaded", () => {
@@ -47,20 +47,27 @@ async function update() {
connectionLost = false;
} else {
setStatus("Нет связи", true);
+
+ if (!connectionLost) {
+ resetBufferTracking();
+ }
connectionLost = true;
}
const arr = Array.isArray(hist.bytes) ? hist.bytes : [];
const raw = arr[arr.length - 1] ?? 0;
+ // ===== ДОБАВЛЯЕМ ТОЛЬКО НОВЫЕ БАЙТЫ =====
+ if (arr.length > 0 && isOnline) {
+ const bufferSize = h.stats?.filled ?? 0;
+ addSignals(arr, bufferSize);
+ }
+
// ===== DECODE =====
const value = raw & 0x3F;
const level = (raw >> 6) & 0x3;
const percent = (value / 63) * 100;
- // ===== STATE =====
- addSignal(value);
-
// ===== UI =====
if (isOnline) {
setBars(smooth(percent));
@@ -77,12 +84,10 @@ async function update() {
// ===== META =====
const uptime = Math.round(h.uptime_sec || 0);
const filled = h.stats?.filled ?? 0;
- const bps = Math.round(h.stats?.bits_per_sec ?? 0);
const Bps = Math.round(h.stats?.bytes_per_sec ?? 0);
- const idle = Math.round((h.last_data_ms || 0) / 1000);
setMeta(
- `работа: ${uptime}s | буфер: ${filled} | ${bps} bit/s (${Bps} B/s) | idle: ${idle}s`
+ `Работа: ${uptime}s | Буфер: ${filled} | Приём: ${Bps} B/s`
);
// ===== HISTORY =====
@@ -112,6 +117,7 @@ async function update() {
setStatus("СЕРВЕР НЕДОСТУПЕН", true);
setBars(0, true);
setSignal(0, true);
+ resetBufferTracking();
if (DOM.lastByte) {
DOM.lastByte.textContent = "--";
@@ -129,7 +135,6 @@ async function update() {
function startPolling() {
if (pollTimer) clearInterval(pollTimer);
- // Если были ошибки — сразу пробуем переподключиться
if (consecutiveErrors > 0) {
console.log("[app] Попытка переподключения...");
update();
@@ -138,7 +143,6 @@ function startPolling() {
pollTimer = setInterval(update, 500);
}
-// Сброс при фокусе окна (пользователь вернулся — пробуем быстро)
window.addEventListener("focus", () => {
if (consecutiveErrors >= MAX_ERRORS) {
console.log("[app] Окно в фокусе, ускоряем опрос");
diff --git a/cmd/server/web/js/chart.js b/cmd/server/web/js/chart.js
index c803a7b..895bcde 100644
--- a/cmd/server/web/js/chart.js
+++ b/cmd/server/web/js/chart.js
@@ -3,7 +3,7 @@ export function drawChart(canvas, history) {
if (!canvas || !history?.length) return;
const rect = canvas.getBoundingClientRect();
- const displayHeight = rect.height || 200; // увеличил для подписей
+ const displayHeight = rect.height || 200;
const displayWidth = rect.width || 800;
canvas.width = displayWidth;
@@ -13,22 +13,22 @@ export function drawChart(canvas, history) {
const w = canvas.width;
const h = canvas.height;
- // ===== ОТСТУПЫ ДЛЯ ПОДПИСЕЙ =====
- const padLeft = 50; // место под значения Y слева
- const padRight = 20; // отступ справа
- const padTop = 15; // отступ сверху
- const padBottom = 30; // отступ снизу для подписей X
+ // ===== ОТСТУПЫ =====
+ const padLeft = 45;
+ const padRight = 15;
+ const padTop = 10;
+ const padBottom = 25;
const plotW = w - padLeft - padRight;
const plotH = h - padTop - padBottom;
- // ===== ФОН ГРАФИКА =====
+ // ===== ФОН =====
ctx.fillStyle = "#0a0f0a";
ctx.fillRect(0, 0, w, h);
// ===== СЕТКА =====
const maxVal = 63;
- const gridLines = 4; // 4 линии + 0 = 5 уровней: 0, 16, 32, 48, 64
+ const gridLines = 4;
ctx.strokeStyle = "#0d2a0d";
ctx.lineWidth = 0.5;
@@ -38,56 +38,35 @@ export function drawChart(canvas, history) {
const val = (maxVal / gridLines) * i;
const y = padTop + plotH - (val / maxVal) * plotH;
- // Линия сетки
ctx.beginPath();
ctx.moveTo(padLeft, y);
ctx.lineTo(w - padRight, y);
ctx.stroke();
- // Подпись значения слева
ctx.fillStyle = "#00ff88";
ctx.font = "11px monospace";
ctx.textAlign = "right";
ctx.fillText(Math.round(val), padLeft - 8, y + 4);
}
- // Вертикальные линии (временные метки)
- const timeMarks = 5;
- ctx.setLineDash([4, 4]);
- for (let i = 0; i <= timeMarks; i++) {
- const x = padLeft + (plotW / timeMarks) * i;
- ctx.beginPath();
- ctx.moveTo(x, padTop);
- ctx.lineTo(x, padTop + plotH);
- ctx.stroke();
+ ctx.setLineDash([]);
- // Подпись снизу
- const pct = Math.round((i / timeMarks) * 100);
- ctx.fillStyle = "#008844";
- ctx.font = "9px monospace";
- ctx.textAlign = "center";
- ctx.fillText(`${100 - pct}%`, x, padTop + plotH + 16);
- }
-
- ctx.setLineDash([]); // сброс пунктира
-
- // ===== ПОДПИСИ ОСЕЙ =====
+ // ===== ПОДПИСЬ ОСИ Y =====
ctx.fillStyle = "#00ff88";
- ctx.font = "bold 12px monospace";
+ ctx.font = "bold 11px monospace";
- // Y
ctx.save();
ctx.translate(12, padTop + plotH / 2);
ctx.rotate(-Math.PI / 2);
ctx.textAlign = "center";
- ctx.fillText("ЗНАЧЕНИЕ (0–63)", 0, 0);
+ ctx.fillText("ЗНАЧЕНИЕ", 0, 0);
ctx.restore();
- // X
+ // ===== ПОДПИСЬ ОСИ X =====
ctx.textAlign = "center";
- ctx.fillText("ВРЕМЯ ← новые", w / 2, h - 4);
+ ctx.fillText("БУФЕР ПРИНЯТЫХ ДАННЫХ", padLeft + plotW / 2, h - 4);
- // ===== РИСУЕМ СИГНАЛ =====
+ // ===== СИГНАЛ =====
const hasSignal = history.some(v => v > 0);
if (!hasSignal) {
@@ -98,7 +77,17 @@ export function drawChart(canvas, history) {
return;
}
- // Линия графика
+ // Прореживание если точек больше чем пикселей
+ let plotData = history;
+ if (history.length > plotW) {
+ const step = history.length / plotW;
+ plotData = [];
+ for (let i = 0; i < plotW; i++) {
+ const idx = Math.floor(i * step);
+ plotData.push(history[idx]);
+ }
+ }
+
ctx.strokeStyle = "#e6852d";
ctx.lineWidth = 1.5;
ctx.shadowColor = "#e6852d";
@@ -106,11 +95,11 @@ export function drawChart(canvas, history) {
ctx.beginPath();
- const stepX = plotW / Math.max(history.length - 1, 1);
+ const stepX = plotW / Math.max(plotData.length - 1, 1);
- for (let i = 0; i < history.length; i++) {
+ for (let i = 0; i < plotData.length; i++) {
const x = padLeft + i * stepX;
- const y = padTop + plotH - (history[i] / maxVal) * plotH;
+ const y = padTop + plotH - (plotData[i] / maxVal) * plotH;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
@@ -119,39 +108,44 @@ export function drawChart(canvas, history) {
ctx.stroke();
ctx.shadowBlur = 0;
- // ===== ПОСЛЕДНЯЯ ТОЧКА =====
+ // ===== ПОСЛЕДНЯЯ ТОЧКА + ЗНАЧЕНИЕ =====
if (history.length > 0) {
- const lastIdx = history.length - 1;
- const lastX = padLeft + lastIdx * stepX;
- const lastY = padTop + plotH - (history[lastIdx] / maxVal) * plotH;
- const lastVal = history[lastIdx];
+ const lastVal = history[history.length - 1];
+ const lastX = padLeft + (plotData.length - 1) * stepX;
+ const lastY = padTop + plotH - (lastVal / maxVal) * plotH;
- // Кружок на последней точке
+ // Кружок
ctx.fillStyle = "#c46b1f";
ctx.beginPath();
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
ctx.fill();
- // Подпись значения над точкой
+ // Значение последней точки
+ const labelText = `${lastVal}`;
+ const labelWidth = ctx.measureText(labelText).width;
+
+ // Фон для читаемости
+ ctx.fillStyle = "rgba(0,0,0,0.8)";
+ ctx.fillRect(lastX - labelWidth / 2 - 4, lastY - 22, labelWidth + 8, 18);
+
+ // Текст по центру над точкой
ctx.fillStyle = "#00ff88";
ctx.font = "bold 12px monospace";
- ctx.textAlign = "left";
- ctx.fillText(`${lastVal}`, lastX + 8, lastY - 8);
+ ctx.textAlign = "center";
+ ctx.fillText(labelText, lastX, lastY - 8);
- // Карточка статистики в правом верхнем углу
- const avg = Math.round(history.reduce((a, b) => a + b, 0) / history.length);
+ // Статистика в углу
const peak = Math.max(...history);
const min = Math.min(...history);
ctx.fillStyle = "rgba(0,0,0,0.7)";
- ctx.fillRect(w - 140, padTop, 130, 55);
+ ctx.fillRect(w - 120, padTop, 110, 42);
ctx.fillStyle = "#00ff88";
ctx.font = "10px monospace";
ctx.textAlign = "left";
- ctx.fillText(`Пик: ${peak}`, w - 130, padTop + 14);
- ctx.fillText(`Сред: ${avg}`, w - 130, padTop + 28);
- ctx.fillText(`Мин: ${min}`, w - 130, padTop + 42);
- ctx.fillText(`Всего: ${history.length}`, w - 130, padTop + 56);
+ ctx.fillText(`Пик: ${peak}`, w - 110, padTop + 14);
+ ctx.fillText(`Мин: ${min}`, w - 110, padTop + 28);
+ ctx.fillText(`Точек: ${history.length}`, w - 110, padTop + 42);
}
}
\ No newline at end of file
diff --git a/cmd/server/web/js/state.js b/cmd/server/web/js/state.js
index 80bd150..32a7432 100644
--- a/cmd/server/web/js/state.js
+++ b/cmd/server/web/js/state.js
@@ -3,19 +3,40 @@ export const state = {
maxPoints: 1000,
signalHistory: [],
smoothValue: 0,
+ lastBufferSize: 0,
};
-// сглаживание
export function smooth(target) {
state.smoothValue += 0.2 * (target - state.smoothValue);
return state.smoothValue;
}
-// добавление в историю
export function addSignal(value) {
state.signalHistory.push(value);
if (state.signalHistory.length > state.maxPoints) {
- state.signalHistory.shift();
+ state.signalHistory.splice(0, state.signalHistory.length - state.maxPoints);
}
+}
+
+export function addSignals(values, bufferSize) {
+ if (!Array.isArray(values) || values.length === 0) return;
+
+ const newBytes = bufferSize - state.lastBufferSize;
+
+ if (newBytes > 0 && newBytes <= values.length) {
+ const fresh = values.slice(-newBytes);
+ const decoded = fresh.map(b => b & 0x3F);
+ state.signalHistory.push(...decoded);
+ }
+
+ state.lastBufferSize = bufferSize;
+
+ if (state.signalHistory.length > state.maxPoints) {
+ state.signalHistory.splice(0, state.signalHistory.length - state.maxPoints);
+ }
+}
+
+export function resetBufferTracking() {
+ state.lastBufferSize = 0;
}
\ No newline at end of file