README, комментарии в коде и HTML

This commit is contained in:
2026-07-22 10:25:52 +04:00
parent bc6f43d561
commit 1bc5bffc38
3 changed files with 443 additions and 61 deletions
+103 -11
View File
@@ -1,40 +1,115 @@
const express = require("express");
const { createClient } = require("redis");
// =============================================================================
// IoT Dashboard — Веб-интерфейс для визуализации IoT-данных
// =============================================================================
// Читает данные ТОЛЬКО из Redis (не трогает MongoDB — это зона Consumer'а).
// Отдаёт статический HTML (public/index.html) и JSON API для графиков.
//
// Redis-ключи (заполняются Consumer'ом):
// iot:latest (hash) — последние значения всех датчиков
// iot:counters (hash) — счётчики событий по типам устройств
// iot:recent (zset) — лента последних событий (сортирована по времени)
//
// API-эндпоинты:
// GET /api/latest — последние значения датчиков [{sensor_id, value, unit, ...}]
// GET /api/counters — счётчики по типам {temperature: N, humidity: N, ...}
// GET /api/recent — последние 50 событий (в обратном хронологическом порядке)
// GET / — HTML-дашборд (static из public/)
//
// Переменные окружения (задаются Terraform'ом через json_env):
// REDIS_HOST — хост Redis (K8s service.cluster.local)
// REDIS_PORT — порт Redis (6379)
// REDIS_PASS — пароль Redis
// PORT — порт HTTP-сервера (3000)
// =============================================================================
const PORT = process.env.PORT || 3000;
const REDIS_HOST = process.env.REDIS_HOST || "127.0.0.1";
const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379");
const REDIS_PASS = process.env.REDIS_PASS || undefined;
// ---------------------------------------------------------------------------
// Зависимости
// ---------------------------------------------------------------------------
const express = require("express"); // HTTP-фреймворк
const { createClient } = require("redis"); // Redis-клиент (v4+, Promise API)
// ---------------------------------------------------------------------------
// Конфигурация из переменных окружения
// ---------------------------------------------------------------------------
const PORT = process.env.PORT || 3000; // HTTP-порт
const REDIS_HOST = process.env.REDIS_HOST || "127.0.0.1"; // Redis хост
const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379"); // Redis порт
const REDIS_PASS = process.env.REDIS_PASS || undefined; // пароль
// ---------------------------------------------------------------------------
// Express-приложение
// ---------------------------------------------------------------------------
const app = express();
// Раздаём статику из папки public/ (index.html, Chart.js подгружается из CDN)
app.use(express.static("public"));
// ---------------------------------------------------------------------------
// Redis-клиент (один на всё приложение)
// ---------------------------------------------------------------------------
const redis = createClient({
socket: { host: REDIS_HOST, port: REDIS_PORT },
password: REDIS_PASS,
});
// Логируем ошибки Redis, но не крашим сервер
redis.on("error", (err) => console.error("Redis error:", err.message));
// ---------------------------------------------------------------------------
// start() — подключение к Redis и регистрация API-маршрутов
// ---------------------------------------------------------------------------
async function start() {
await redis.connect();
console.log("Redis connected");
// API: latest sensor values
// -----------------------------------------------------------------------
// GET /api/latest — последние значения всех датчиков
// -----------------------------------------------------------------------
// Читает Redis-хэш iot:latest (sensor_id → JSON),
// десериализует JSON и возвращает массив объектов.
//
// Redis-структура:
// iot:latest = {
// "temp_living": '{"value":26.95,"unit":"°C","location":"living_room",...}',
// "humidity_bed": '{"value":52.1,"unit":"%","location":"bedroom",...}',
// ...
// }
//
// Ответ:
// [{sensor_id: "temp_living", value: 26.95, unit: "°C", location: "living_room", ...}, ...]
// -----------------------------------------------------------------------
app.get("/api/latest", async (req, res) => {
try {
// hGetAll возвращает объект {sensor_id: json_string, ...}
const data = await redis.hGetAll("iot:latest");
// Преобразуем: {key: json} → [{sensor_id: key, ...parsedJson}]
const items = Object.entries(data).map(([id, json]) => ({
sensor_id: id,
...JSON.parse(json),
...JSON.parse(json), // разворачиваем JSON в поля объекта
}));
res.json(items);
} catch (e) {
// При ошибке Redis — возвращаем ошибку в JSON (не 500,
// чтобы фронтенд не упал)
res.json({ error: e.message });
}
});
// API: counters by device type
// -----------------------------------------------------------------------
// GET /api/counters — счётчики событий по типам устройств
// -----------------------------------------------------------------------
// Redis-структура (заполняется HINCRBY в consumer):
// iot:counters = {
// "temperature": "3847",
// "humidity": "2563",
// "power_meter": "3851"
// }
//
// Ответ: {temperature: "3847", humidity: "2563", power_meter: "3851"}
// Значения — строки (Redis hash), фронтенд преобразует в Number.
// -----------------------------------------------------------------------
app.get("/api/counters", async (req, res) => {
try {
const data = await redis.hGetAll("iot:counters");
@@ -44,22 +119,39 @@ async function start() {
}
});
// API: recent events
// -----------------------------------------------------------------------
// GET /api/recent — последние 50 событий (лента)
// -----------------------------------------------------------------------
// Redis-структура (заполняется ZADD score=timestamp в consumer):
// iot:recent (sorted set, до 1000 элементов)
//
// ZRANGE 0 49 REV — последние 50 элементов (REV = обратный порядок)
// Каждый элемент — JSON-строка события, десериализуем.
//
// Ответ: массив до 50 объектов событий в обратном хронологическом порядке
// -----------------------------------------------------------------------
app.get("/api/recent", async (req, res) => {
try {
// ZRANGE с REV: true — от большего score к меньшему (сначала новые)
const items = await redis.zRange("iot:recent", 0, 49, { REV: true });
// Каждый элемент в zset — JSON-строка, парсим
res.json(items.map(JSON.parse));
} catch (e) {
res.json({ error: e.message });
}
});
// Health check
// -----------------------------------------------------------------------
// GET / — заглушка (статику отдаёт express.static выше)
// -----------------------------------------------------------------------
app.get("/", (req, res) => {
res.send("IoT Dashboard OK");
});
// Запуск HTTP-сервера
app.listen(PORT, () => console.log(`Dashboard on port ${PORT}`));
}
// Стартуем приложение
start();