README, комментарии в коде и HTML
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user