// ============================================================================= // 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();