diff options
Diffstat (limited to 'templates')
| -rw-r--r-- | templates/sensors.html | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/templates/sensors.html b/templates/sensors.html new file mode 100644 index 0000000..3bc21a0 --- /dev/null +++ b/templates/sensors.html @@ -0,0 +1,474 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Данные датчиков</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: #1a1a2e; + padding: 20px; + min-height: 100vh; + } + .container { + max-width: 1200px; + margin: 0 auto; + background: #16213e; + border-radius: 15px; + padding: 30px; + box-shadow: 0 10px 30px rgba(0,0,0,0.5); + border: 1px solid #0f3460; + } + .header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 25px; + padding-bottom: 15px; + border-bottom: 2px solid #0f3460; + flex-wrap: wrap; + gap: 10px; + } + h1 { + color: #e94560; + font-size: 28px; + font-weight: 700; + display: flex; + align-items: center; + gap: 10px; + } + h1 span { + color: #fff; + font-weight: 300; + } + .stats { + color: #aaa; + font-size: 14px; + background: #0f3460; + padding: 8px 16px; + border-radius: 20px; + } + .stats strong { + color: #e94560; + } + .table-wrapper { + overflow-x: auto; + background: #1a1a2e; + border-radius: 10px; + border: 1px solid #0f3460; + } + table { + width: 100%; + border-collapse: collapse; + min-width: 600px; + } + thead { + background: #0f3460; + } + th { + color: #fff; + padding: 15px 20px; + text-align: left; + font-weight: 600; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 1px; + border-bottom: 2px solid #e94560; + white-space: nowrap; + } + td { + padding: 12px 20px; + color: #ddd; + border-bottom: 1px solid #0f3460; + font-size: 15px; + } + tbody tr { + transition: all 0.3s ease; + } + tbody tr:hover { + background: #0f3460; + transform: scale(1.01); + } + tbody tr:last-child td { + border-bottom: none; + } + .sensor-key { + font-weight: 600; + color: #e94560; + font-size: 16px; + } + .sensor-value { + color: #4fc3f7; + font-family: 'Courier New', monospace; + font-size: 16px; + font-weight: 500; + } + .sensor-unit { + color: #888; + font-size: 12px; + margin-left: 5px; + } + .sensor-status { + display: inline-block; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 600; + } + .status-active { + background: #00e67620; + color: #00e676; + border: 1px solid #00e67640; + } + .status-inactive { + background: #ff174420; + color: #ff1744; + border: 1px solid #ff174440; + } + .status-warning { + background: #ffea0020; + color: #ffea00; + border: 1px solid #ffea0040; + } + .no-data { + text-align: center; + padding: 60px 20px; + color: #888; + } + .no-data .icon { + font-size: 48px; + margin-bottom: 15px; + display: block; + } + .no-data p { + font-size: 18px; + } + .no-data .sub { + font-size: 14px; + color: #666; + margin-top: 10px; + } + .footer { + margin-top: 20px; + display: flex; + justify-content: space-between; + align-items: center; + color: #666; + font-size: 13px; + padding-top: 15px; + border-top: 1px solid #0f3460; + flex-wrap: wrap; + gap: 10px; + } + .footer .badge { + display: inline-block; + background: #00e67620; + color: #00e676; + padding: 2px 12px; + border-radius: 20px; + font-size: 12px; + } + .refresh-btn { + background: #e94560; + color: white; + border: none; + padding: 8px 20px; + border-radius: 20px; + cursor: pointer; + font-weight: 600; + transition: all 0.3s ease; + } + .refresh-btn:hover { + background: #c62840; + transform: scale(1.05); + } + .auto-refresh { + display: flex; + align-items: center; + gap: 10px; + } + .auto-refresh label { + color: #aaa; + font-size: 13px; + } + .auto-refresh input[type="checkbox"] { + accent-color: #e94560; + width: 18px; + height: 18px; + cursor: pointer; + } + .json-link { + color: #4fc3f7; + text-decoration: none; + font-size: 14px; + } + .json-link:hover { + text-decoration: underline; + } + @media (max-width: 768px) { + .container { + padding: 15px; + } + .header { + flex-direction: column; + align-items: flex-start; + } + h1 { + font-size: 22px; + } + th, td { + padding: 10px 15px; + font-size: 13px; + } + } + </style> +</head> +<body> + <div class="container"> + <div class="header"> + <h1>📊 <span>Данные датчиков</span></h1> + <div class="stats"> + Всего датчиков: <strong id="sensorCount">0</strong> + <span style="margin:0 10px;">|</span> + <span id="updateTimeDisplay">Обновлено: --</span> + </div> + </div> + + <div style="margin-bottom: 15px; text-align: right;"> + <a href="/sensors?format=json" class="json-link">📄 Показать как JSON</a> + </div> + + <div class="table-wrapper"> + <table> + <thead> + <tr> + <th style="width: 30%;">📌 Датчик</th> + <th style="width: 40%;">📈 Значение</th> + <th style="width: 30%;">⚡ Статус</th> + </tr> + </thead> + <tbody id="sensorsTableBody"> + <tr> + <td colspan="3"> + <div class="no-data"> + <span class="icon">🔄</span> + <p>Загрузка данных...</p> + <div class="sub">Ожидайте поступления данных от датчиков</div> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div class="footer"> + <div> + <span class="badge">● LIVE</span> + <span style="margin-left: 15px;">Данные обновляются автоматически</span> + </div> + <div class="auto-refresh"> + <label for="autoRefresh">Автообновление</label> + <input type="checkbox" id="autoRefresh" checked> + <button class="refresh-btn" onclick="loadData()">🔄 Обновить</button> + </div> + </div> + </div> + + <script> + let autoRefreshInterval = null; + const AUTO_REFRESH_TIME = 3000; + + function getSensorStatus(value) { + if (value === null || value === undefined || value === '') { + return { status: 'inactive', text: 'Нет данных' }; + } + + let cleanValue = value; + if (typeof value === 'string' && value.includes('_')) { + cleanValue = value.split('_')[0]; + } + + if (!isNaN(cleanValue) && typeof cleanValue !== 'boolean') { + const numValue = parseFloat(cleanValue); + if (numValue > 100) { + return { status: 'warning', text: '⚠ Высокое' }; + } else if (numValue < 0) { + return { status: 'warning', text: '⚠ Низкое' }; + } else { + return { status: 'active', text: '✅ Норма' }; + } + } + + if (typeof value === 'string') { + if (value.toLowerCase().includes('error') || value.toLowerCase().includes('fail')) { + return { status: 'inactive', text: '❌ Ошибка' }; + } + if (value.toLowerCase().includes('warn')) { + return { status: 'warning', text: '⚠ Предупреждение' }; + } + return { status: 'active', text: '✅ OK' }; + } + + return { status: 'active', text: '✅ Активен' }; + } + + function getUnit(key) { + const units = { + 'Temp': '°C', + 'Temperature': '°C', + 'Hum': '%', + 'Humidity': '%', + 'Pres': 'мм рт. ст.', + 'Pressure': 'мм рт. ст.', + 'Batt': 'V', + 'Battery': 'V', + 'Volt': 'V', + 'Voltage': 'V', + 'Amp': 'A', + 'Current': 'A', + 'Power': 'W', + 'Energy': 'кВт·ч', + 'Freq': 'Гц', + 'Frequency': 'Гц' + }; + return units[key] || ''; + } + + function formatValue(value) { + if (value === null || value === undefined) { + return '--'; + } + + if (typeof value === 'string' && value.includes('_')) { + const parts = value.split('_'); + value = parts[0]; + } + + if (!isNaN(value) && typeof value !== 'boolean') { + const num = parseFloat(value); + if (Number.isInteger(num)) { + return num.toString(); + } else { + return num.toFixed(2); + } + } + + return value; + } + + function loadData() { + fetch('/sensors?format=json') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + updateTable(data); + }) + .catch(error => { + console.error('Error fetching data:', error); + document.getElementById('sensorsTableBody').innerHTML = ` + <tr> + <td colspan="3"> + <div class="no-data"> + <span class="icon">⚠️</span> + <p>Ошибка загрузки данных</p> + <div class="sub">Проверьте подключение к серверу</div> + </div> + </td> + </tr> + `; + }); + } + + function updateTable(data) { + const tbody = document.getElementById('sensorsTableBody'); + const sensorCount = document.getElementById('sensorCount'); + + const now = new Date(); + document.getElementById('updateTimeDisplay').textContent = + `Обновлено: ${now.toLocaleTimeString('ru-RU')}`; + + if (!data || typeof data !== 'object' || Object.keys(data).length === 0) { + tbody.innerHTML = ` + <tr> + <td colspan="3"> + <div class="no-data"> + <span class="icon">📡</span> + <p>Нет данных от датчиков</p> + <div class="sub">Ожидайте поступления данных...</div> + </div> + </td> + </tr> + `; + sensorCount.textContent = '0'; + return; + } + + const keys = Object.keys(data); + sensorCount.textContent = keys.length; + keys.sort(); + + let rows = ''; + keys.forEach(key => { + const rawValue = data[key]; + const value = formatValue(rawValue); + const unit = getUnit(key); + const status = getSensorStatus(rawValue); + + let statusClass = 'status-active'; + if (status.status === 'inactive') statusClass = 'status-inactive'; + if (status.status === 'warning') statusClass = 'status-warning'; + + rows += ` + <tr> + <td class="sensor-key">${key}</td> + <td class="sensor-value"> + ${value} + ${unit ? `<span class="sensor-unit">${unit}</span>` : ''} + </td> + <td> + <span class="sensor-status ${statusClass}">${status.text}</span> + </td> + </tr> + `; + }); + + tbody.innerHTML = rows; + } + + function toggleAutoRefresh() { + const isChecked = document.getElementById('autoRefresh').checked; + + if (autoRefreshInterval) { + clearInterval(autoRefreshInterval); + autoRefreshInterval = null; + } + + if (isChecked) { + autoRefreshInterval = setInterval(loadData, AUTO_REFRESH_TIME); + loadData(); + } + } + + document.addEventListener('DOMContentLoaded', function() { + loadData(); + + const autoRefreshCheckbox = document.getElementById('autoRefresh'); + autoRefreshCheckbox.addEventListener('change', toggleAutoRefresh); + + if (autoRefreshCheckbox.checked) { + autoRefreshInterval = setInterval(loadData, AUTO_REFRESH_TIME); + } + }); + </script> +</body> +</html> |
