Files
tf-iot-dashboard/server.js
T

158 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// =============================================================================
// 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 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");
// -----------------------------------------------------------------------
// 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 в поля объекта
}));
res.json(items);
} catch (e) {
// При ошибке Redis — возвращаем ошибку в JSON (не 500,
// чтобы фронтенд не упал)
res.json({ error: e.message });
}
});
// -----------------------------------------------------------------------
// 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");
res.json(data);
} catch (e) {
res.json({ error: e.message });
}
});
// -----------------------------------------------------------------------
// 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 });
}
});
// -----------------------------------------------------------------------
// GET / — заглушка (статику отдаёт express.static выше)
// -----------------------------------------------------------------------
app.get("/", (req, res) => {
res.send("IoT Dashboard OK");
});
// Запуск HTTP-сервера
app.listen(PORT, () => console.log(`Dashboard on port ${PORT}`));
}
// Стартуем приложение
start();