diff --git a/README.md b/README.md new file mode 100644 index 0000000..e35e619 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# IoT Dashboard — веб-интерфейс «Умного дома» + +Читает данные из Redis (только чтение) и отображает графики через Chart.js в браузере. + +## Пайплайн + +``` +Producer → RabbitMQ → Consumer → Redis + MongoDB → Dashboard (этот сервис) +``` + +Dashboard **не пишет** в Redis — только читает. Данные туда кладёт Consumer. + +## API + +| Путь | Ответ | Данные из Redis | +|---|---|---| +| `GET /api/latest` | `[{sensor_id, value, unit, location, timestamp}]` | `iot:latest` (hash) | +| `GET /api/counters` | `{temperature: "N", humidity: "N", power_meter: "N"}` | `iot:counters` (hash) | +| `GET /api/recent` | `[{...50 последних событий}]` | `iot:recent` (zset) | +| `GET /` | HTML-дашборд | — | + +## Интерфейс + +- **Bar Chart** — последние значения датчиков (цвет зависит от типа) +- **Doughnut Chart** — распределение событий по типам устройств +- **Таблица** — последние 15 событий с временем, датчиком и значением +- **Автообновление** — каждые 3 секунды + +## Переменные окружения + +| Переменная | По умолчанию | Описание | +|---|---|---| +| `REDIS_HOST` | `127.0.0.1` | Хост Redis | +| `REDIS_PORT` | `6379` | Порт Redis | +| `REDIS_PASS` | — | Пароль Redis (необязательно) | +| `PORT` | `3000` | Порт HTTP | + +## Локальный запуск + +```bash +npm install +REDIS_HOST=localhost node server.js +# Открыть http://localhost:3000 +``` + +## Деплой на Nubes Cloud + +1. Запушьте этот репо в свой Gitea +2. В Terraform-конфиге укажите `git_path` на ваш репо и `git_revision` +3. Nubes автоматически запустит `npm start` + +## Примечания + +- **Chart.js загружается из CDN** (jsDelivr) — под должен иметь доступ в интернет +- **Тёмная тема** — slate-палитра, адаптивная сетка +- При недоступности Redis дашборд показывает ошибку в JSON (не 500) diff --git a/public/index.html b/public/index.html index 62908fe..180a526 100644 --- a/public/index.html +++ b/public/index.html @@ -1,55 +1,198 @@ + + IoT Dashboard — Умный дом + + + + +

🏠 IoT Dashboard — Умный дом

+ RabbitMQ → Redis → MongoDB | Автообновление каждые 3 сек
+ +
+ +

📊 Последние значения датчиков

-
+
+ + +
+ +

📈 Счётчики по типам устройств

-
+
+ +
+ +

📋 Последние события

- - + + + + + + + + + + +
ВремяДатчикЗначениеЛокация
ВремяДатчикЗначениеЛокация
+ +

ℹ️ О демонстрации

@@ -66,80 +209,171 @@

+ diff --git a/server.js b/server.js index 3c5eeff..366caf8 100644 --- a/server.js +++ b/server.js @@ -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();