Compare commits
2 Commits
306eda66c6
...
3db3a9bdcd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3db3a9bdcd | ||
|
|
8a9e0efed3 |
@@ -145,7 +145,7 @@ h1 {
|
||||
flex: 1;
|
||||
border-radius: 2px;
|
||||
min-width: 5px;
|
||||
border: 1.5px solid #00ff88;
|
||||
border: 1.5px solid #e6852d;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
||||
@@ -164,8 +164,8 @@ h1 {
|
||||
.sig-bar.active-1,
|
||||
.sig-bar.active-2,
|
||||
.sig-bar.active-3 {
|
||||
background-color: #00ff88;
|
||||
border-color: #00ff88;
|
||||
background-color: #e6852d;
|
||||
border-color: #c46b1f;
|
||||
}
|
||||
|
||||
/* Нет связи - сигнальные бары */
|
||||
@@ -186,7 +186,7 @@ h1 {
|
||||
#signalChart {
|
||||
width: 100%;
|
||||
background: #050805;
|
||||
border: 1px solid #00ff88;
|
||||
border: 1px solid #e6852d;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card wide">
|
||||
<h3>ГРАФИК СИГНАЛА (последние 1000)</h3>
|
||||
<h3>ГРАФИК СИГНАЛА</h3>
|
||||
<canvas id="signalChart" height="120"></canvas>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ import { drawChart } from "./chart.js";
|
||||
|
||||
let lastChart = 0;
|
||||
let connectionLost = false;
|
||||
let consecutiveErrors = 0; // счётчик ошибок подряд
|
||||
const MAX_ERRORS = 3; // после скольки ошибок замедляем опрос
|
||||
let pollTimer = null; // храним ID таймера для возможности сброса
|
||||
|
||||
// ================= INIT =================
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
@@ -30,6 +33,12 @@ async function update() {
|
||||
fetchHistory()
|
||||
]);
|
||||
|
||||
consecutiveErrors = 0;
|
||||
|
||||
if (connectionLost) {
|
||||
console.log("[app] Соединение восстановлено");
|
||||
}
|
||||
|
||||
// ===== STATUS =====
|
||||
const isOnline = h.pipe_alive && h.has_data;
|
||||
|
||||
@@ -37,7 +46,7 @@ async function update() {
|
||||
setStatus("На связи");
|
||||
connectionLost = false;
|
||||
} else {
|
||||
setStatus("Нет связи", true); // true = lost connection
|
||||
setStatus("Нет связи", true);
|
||||
connectionLost = true;
|
||||
}
|
||||
|
||||
@@ -57,9 +66,8 @@ async function update() {
|
||||
setBars(smooth(percent));
|
||||
setSignal(level);
|
||||
} else {
|
||||
// При потере связи - сбрасываем индикаторы
|
||||
setBars(0, true); // true = no connection
|
||||
setSignal(0, true); // true = no connection
|
||||
setBars(0, true);
|
||||
setSignal(0, true);
|
||||
}
|
||||
|
||||
if (DOM.lastByte) {
|
||||
@@ -95,7 +103,12 @@ async function update() {
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error("UPDATE ERROR:", e);
|
||||
consecutiveErrors++;
|
||||
|
||||
if (consecutiveErrors === 1) {
|
||||
console.warn("[app] Сервер недоступен, ждём...");
|
||||
}
|
||||
|
||||
setStatus("СЕРВЕР НЕДОСТУПЕН", true);
|
||||
setBars(0, true);
|
||||
setSignal(0, true);
|
||||
@@ -103,9 +116,37 @@ async function update() {
|
||||
if (DOM.lastByte) {
|
||||
DOM.lastByte.textContent = "--";
|
||||
}
|
||||
|
||||
if (consecutiveErrors >= MAX_ERRORS) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = setInterval(update, 2000);
|
||||
console.log("[app] Замедлили опрос до 2с");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================= LOOP =================
|
||||
setInterval(update, 500);
|
||||
// ================= RECONNECT LOGIC =================
|
||||
function startPolling() {
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
|
||||
// Если были ошибки — сразу пробуем переподключиться
|
||||
if (consecutiveErrors > 0) {
|
||||
console.log("[app] Попытка переподключения...");
|
||||
update();
|
||||
}
|
||||
|
||||
pollTimer = setInterval(update, 500);
|
||||
}
|
||||
|
||||
// Сброс при фокусе окна (пользователь вернулся — пробуем быстро)
|
||||
window.addEventListener("focus", () => {
|
||||
if (consecutiveErrors >= MAX_ERRORS) {
|
||||
console.log("[app] Окно в фокусе, ускоряем опрос");
|
||||
consecutiveErrors = 0;
|
||||
startPolling();
|
||||
}
|
||||
});
|
||||
|
||||
// ================= START =================
|
||||
startPolling();
|
||||
update();
|
||||
@@ -1,33 +1,157 @@
|
||||
// chart.js
|
||||
let ctx = null;
|
||||
|
||||
export function drawChart(canvas, history) {
|
||||
if (!canvas || !history?.length) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width;
|
||||
canvas.height = rect.height;
|
||||
const displayHeight = rect.height || 200; // увеличил для подписей
|
||||
const displayWidth = rect.width || 800;
|
||||
|
||||
canvas.width = displayWidth;
|
||||
canvas.height = displayHeight;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
// ===== ОТСТУПЫ ДЛЯ ПОДПИСЕЙ =====
|
||||
const padLeft = 50; // место под значения Y слева
|
||||
const padRight = 20; // отступ справа
|
||||
const padTop = 15; // отступ сверху
|
||||
const padBottom = 30; // отступ снизу для подписей X
|
||||
|
||||
const stepX = w / 1000;
|
||||
const scaleY = h / 63;
|
||||
const plotW = w - padLeft - padRight;
|
||||
const plotH = h - padTop - padBottom;
|
||||
|
||||
ctx.strokeStyle = "#00ff88";
|
||||
// ===== ФОН ГРАФИКА =====
|
||||
ctx.fillStyle = "#0a0f0a";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// ===== СЕТКА =====
|
||||
const maxVal = 63;
|
||||
const gridLines = 4; // 4 линии + 0 = 5 уровней: 0, 16, 32, 48, 64
|
||||
|
||||
ctx.strokeStyle = "#0d2a0d";
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 4]);
|
||||
|
||||
for (let i = 0; i <= gridLines; i++) {
|
||||
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();
|
||||
|
||||
// Подпись снизу
|
||||
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([]); // сброс пунктира
|
||||
|
||||
// ===== ПОДПИСИ ОСЕЙ =====
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "bold 12px 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.restore();
|
||||
|
||||
// X
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("ВРЕМЯ ← новые", w / 2, h - 4);
|
||||
|
||||
// ===== РИСУЕМ СИГНАЛ =====
|
||||
const hasSignal = history.some(v => v > 0);
|
||||
|
||||
if (!hasSignal) {
|
||||
ctx.fillStyle = "#ff4444";
|
||||
ctx.font = "14px monospace";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("Ожидание сигнала...", w / 2, h / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Линия графика
|
||||
ctx.strokeStyle = "#e6852d";
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.shadowColor = "#e6852d";
|
||||
ctx.shadowBlur = 3;
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
const stepX = plotW / Math.max(history.length - 1, 1);
|
||||
|
||||
for (let i = 0; i < history.length; i++) {
|
||||
const x = i * stepX;
|
||||
const y = h - history[i].value * scaleY;
|
||||
const x = padLeft + i * stepX;
|
||||
const y = padTop + plotH - (history[i] / maxVal) * plotH;
|
||||
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
// Кружок на последней точке
|
||||
ctx.fillStyle = "#c46b1f";
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Подпись значения над точкой
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "bold 12px monospace";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(`${lastVal}`, lastX + 8, 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.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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user