Files
tf-iot-dashboard/public/index.html
T

146 lines
6.4 KiB
HTML
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.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IoT Dashboard — Умный дом</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; }
.header { background: #1e293b; padding: 20px 30px; border-bottom: 1px solid #334155; }
.header h1 { font-size: 24px; color: #38bdf8; }
.header span { color: #94a3b8; font-size: 14px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; padding: 20px; }
.card { background: #1e293b; border-radius: 12px; padding: 20px; border: 1px solid #334155; }
.card h2 { font-size: 16px; color: #94a3b8; margin-bottom: 15px; }
.chart-wrap { position: relative; height: 250px; }
.table-wrap { max-height: 300px; overflow-y: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #334155; }
th { color: #64748b; font-weight: 600; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; }
.badge-temp { background: #f97316; color: #fff; }
.badge-hum { background: #06b6d4; color: #fff; }
.badge-power { background: #eab308; color: #000; }
.refresh { text-align: right; font-size: 12px; color: #64748b; margin-bottom: 10px; }
</style>
</head>
<body>
<div class="header">
<h1>🏠 IoT Dashboard — Умный дом</h1>
<span>RabbitMQ → Redis → MongoDB | Автообновление каждые 3 сек</span>
</div>
<div class="grid">
<div class="card">
<h2>📊 Последние значения датчиков</h2>
<div class="chart-wrap"><canvas id="latestChart"></canvas></div>
</div>
<div class="card">
<h2>📈 Счётчики по типам устройств</h2>
<div class="chart-wrap"><canvas id="countersChart"></canvas></div>
</div>
<div class="card">
<h2>📋 Последние события</h2>
<div class="table-wrap">
<table id="recentTable">
<thead><tr><th>Время</th><th>Датчик</th><th>Значение</th><th>Локация</th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<div class="card" style="margin: 0 20px 20px 20px; line-height: 1.8;">
<h2>О демонстрации</h2>
<p>
<b>Эмуляция:</b> IoT-датчики «Умного дома» — температура, влажность, энергопотребление (8 датчиков, 3 типа).<br>
<b>Пайплайн данных:</b><br>
1. <b>Producer</b> (Node.js) — генерирует события → отправляет в RabbitMQ<br>
2. <b>RabbitMQ</b> — очередь сообщений (iot-events)<br>
3. <b>Consumer</b> (Node.js) — читает очередь → сохраняет в Redis + MongoDB<br>
4. <b>Redis</b> — кэш: последние значения, счётчики, лента событий<br>
5. <b>MongoDB</b> — постоянное хранение всех событий<br>
6. <b>Dashboard</b> (Node.js + Chart.js) — читает Redis → отображает графики<br>
<b>Сервисы Nubes Cloud:</b> RabbitMQ, Redis, MongoDB, Node.js ×3<br>
<b>Развёрнуто через Terraform</b> на тестовом стенде (k8s-4-sandbox-nubes-ru)
</p>
</div>
<script>
let latestChart, countersChart;
function getBadge(type) {
if (type === 'temperature') return 'badge-temp';
if (type === 'humidity') return 'badge-hum';
return 'badge-power';
}
async function refresh() {
try {
// Latest values
const latest = await fetch('/api/latest').then(r => r.json());
const labels = latest.map(d => d.sensor_id);
const values = latest.map(d => d.value);
const colors = latest.map(d => {
if (d.unit === '°C') return '#f97316';
if (d.unit === '%') return '#06b6d4';
return '#eab308';
});
if (!latestChart) {
latestChart = new Chart(document.getElementById('latestChart'), {
type: 'bar',
data: { labels, datasets: [{ label: 'Значение', data: values, backgroundColor: colors }] },
options: { responsive: true, maintainAspectRatio: false,
scales: { y: { ticks: { color: '#94a3b8' } }, x: { ticks: { color: '#94a3b8' } } },
plugins: { legend: { display: false } }
}
});
} else {
latestChart.data.labels = labels;
latestChart.data.datasets[0].data = values;
latestChart.data.datasets[0].backgroundColor = colors;
latestChart.update();
}
// Counters
const counters = await fetch('/api/counters').then(r => r.json());
const cLabels = Object.keys(counters);
const cValues = Object.values(counters).map(Number);
if (!countersChart) {
countersChart = new Chart(document.getElementById('countersChart'), {
type: 'doughnut',
data: { labels: cLabels, datasets: [{ data: cValues, backgroundColor: ['#f97316','#06b6d4','#eab308'] }] },
options: { responsive: true, maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#94a3b8' } } }
}
});
} else {
countersChart.data.labels = cLabels;
countersChart.data.datasets[0].data = cValues;
countersChart.update();
}
// Recent events
const recent = await fetch('/api/recent').then(r => r.json());
const tbody = document.querySelector('#recentTable tbody');
tbody.innerHTML = recent.slice(0, 15).map(e => `
<tr>
<td>${new Date(e.timestamp).toLocaleTimeString()}</td>
<td>${e.sensor_id}</td>
<td><span class="badge ${getBadge(e.device_type)}">${e.value} ${e.unit}</span></td>
<td>${e.location}</td>
</tr>
`).join('');
} catch (e) {
console.error(e);
}
}
refresh();
setInterval(refresh, 3000);
</script>
</body>
</html>