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 — Умный дом
+
+
+
+
+
+
+
+
+
📊 Последние значения датчиков
-
+
+
+
+
+
+
📈 Счётчики по типам устройств
-
+
+
+
+
+
📋 Последние события
- | Время | Датчик | Значение | Локация |
-
+
+
+ | Время |
+ Датчик |
+ Значение |
+ Локация |
+
+
+
+
+
+
+
ℹ️ О демонстрации
@@ -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();