/
NickLasher
/
AutomatedTempScanner
Обзор
Документация
Войти
/
NickLasher
/
AutomatedTempScanner
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
dashboard.html
1 210 строк
44 KB
NickLasher
upload files
01 фев 2026, 22:40
Верифицирован
01 фев 2026, 22:40
0732dad
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Панель мониторинга температуры</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a5a3a 0%, #2d8a6e 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1600px; margin: 0 auto; } /* Навигация */ .nav-header { background: white; padding: 15px 25px; border-radius: 15px; margin-bottom: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; } .nav-header h1 { color: #1b5e20; font-size: 24px; display: flex; align-items: center; gap: 10px; } .back-button { display: inline-flex; align-items: center; gap: 8px; background: #2e7d32; color: white; padding: 10px 20px; border-radius: 8px; text-decoration: none; font-weight: 600; transition: all 0.3s; } .back-button:hover { background: #1b5e20; transform: translateY(-2px); } /* Вкладки */ .tabs { background: white; padding: 20px; border-radius: 15px; margin-bottom: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .tabs-nav { display: flex; gap: 10px; flex-wrap: wrap; } .tab-btn { padding: 12px 25px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 15px; transition: all 0.3s; background: #e8f5e9; color: #1b5e20; } .tab-btn:hover { background: #c8e6c9; } .tab-btn.active { background: linear-gradient(135deg, #1b5e20 0%, #2e7d32 100%); color: white; } /* Основной контент */ .dashboard-content { display: none; } .dashboard-content.active { display: block; } .content-section { background: white; padding: 25px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); margin-bottom: 25px; } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; padding-bottom: 20px; border-bottom: 2px solid #eaeaea; flex-wrap: wrap; gap: 15px; } .section-header h2 { color: #1b5e20; font-size: 22px; } /* Карточки устройств */ .devices-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; margin-bottom: 25px; } .device-card { background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); padding: 25px; border-radius: 15px; box-shadow: 0 5px 15px rgba(0,0,0,0.1); transition: all 0.3s; } .device-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0,0,0,0.15); } .device-card.offline { background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%); } .device-card.warning { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); } .device-card.critical { background: linear-gradient(135deg, #ffebee 0%, #ef9a9a 100%); animation: pulse-critical 2s infinite; } @keyframes pulse-critical { 0%, 100% { box-shadow: 0 5px 15px rgba(198, 40, 40, 0.3); } 50% { box-shadow: 0 5px 25px rgba(198, 40, 40, 0.5); } } .device-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .device-header h3 { color: #1b5e20; font-size: 18px; display: flex; align-items: center; gap: 10px; } .device-status { padding: 5px 15px; border-radius: 20px; font-size: 12px; font-weight: 600; } .status-active { background: #4caf50; color: white; } .status-offline { background: #9e9e9e; color: white; } .status-warning { background: #ff9800; color: white; } .status-critical { background: #f44336; color: white; } .device-temp { text-align: center; margin: 20px 0; } .temp-value { font-size: 48px; font-weight: bold; color: #1b5e20; } .device-card.critical .temp-value { color: #c62828; } .device-card.warning .temp-value { color: #e65100; } .temp-unit { font-size: 24px; color: #666; } .device-info { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(0,0,0,0.1); } .info-item { text-align: center; } .info-item label { display: block; font-size: 12px; color: #666; margin-bottom: 5px; } .info-item span { font-weight: 600; color: #1b5e20; } /* Статистика */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 25px; } .stat-card { background: linear-gradient(135deg, #1b5e20 0%, #2e7d32 100%); color: white; padding: 25px; border-radius: 12px; text-align: center; transition: transform 0.3s; } .stat-card:hover { transform: translateY(-5px); } .stat-card h3 { font-size: 14px; margin-bottom: 15px; opacity: 0.9; } .stat-value { font-size: 36px; font-weight: bold; } .stat-card.secondary { background: linear-gradient(135deg, #0277bd 0%, #0288d1 100%); } .stat-card.warning-stat { background: linear-gradient(135deg, #c62828 0%, #d32f2f 100%); } /* Диаграммы */ .charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 25px; margin-bottom: 25px; } .chart-container { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.08); height: 400px; } .chart-title { text-align: center; margin-bottom: 20px; color: #1b5e20; font-size: 18px; font-weight: 600; } /* Таблица */ .table-section { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.08); margin-bottom: 25px; } .table-section h2 { color: #1b5e20; margin-bottom: 20px; font-size: 22px; } .table-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px; } .search-box input { padding: 12px 20px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 15px; width: 300px; transition: border-color 0.3s; } .search-box input:focus { outline: none; border-color: #2e7d32; } .table-responsive { overflow-x: auto; } table { width: 100%; border-collapse: collapse; } th { background: #e8f5e9; padding: 15px; text-align: left; font-weight: 600; color: #1b5e20; border-bottom: 2px solid #c8e6c9; } td { padding: 15px; border-bottom: 1px solid #e0e0e0; } tr:hover { background: #f8f9fa; } .anomaly-badge { padding: 5px 10px; border-radius: 5px; font-size: 12px; font-weight: 600; } .anomaly-yes { background: #ffebee; color: #c62828; } .anomaly-no { background: #e8f5e9; color: #2e7d32; } /* Форма добавления устройства */ .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 8px; color: #333; font-weight: 500; } .form-group input, .form-group select { width: 100%; padding: 12px 15px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 15px; transition: border-color 0.3s; } .form-group input:focus, .form-group select:focus { outline: none; border-color: #2e7d32; } .form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; } .btn-primary { background: linear-gradient(135deg, #1b5e20 0%, #2e7d32 100%); color: white; padding: 12px 30px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 15px; transition: all 0.3s; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(27, 94, 32, 0.3); } .btn-secondary { background: #e8f5e9; color: #1b5e20; padding: 12px 30px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 15px; transition: all 0.3s; } .btn-secondary:hover { background: #c8e6c9; } /* Сообщения */ .message { padding: 15px 20px; border-radius: 8px; margin: 10px 0; font-size: 14px; display: none; } .success { background: #e8f5e9; color: #1b5e20; border: 1px solid #c8e6c9; } .error { background: #ffebee; color: #c62828; border: 1px solid #ffcdd2; } .loading { text-align: center; padding: 40px; font-size: 18px; color: #666; } /* Футер */ .footer { text-align: center; padding: 20px; color: white; font-size: 14px; opacity: 0.9; } /* Адаптивность */ @media (max-width: 768px) { .charts-grid { grid-template-columns: 1fr; } .chart-container { height: 350px; } .table-controls { flex-direction: column; align-items: stretch; } .search-box input { width: 100%; } .nav-header { flex-direction: column; text-align: center; } .devices-grid { grid-template-columns: 1fr; } .form-row { grid-template-columns: 1fr; } } @media (max-width: 480px) { .table-grid { grid-template-columns: 1fr; } .stats-grid { grid-template-columns: 1fr; } th, td { padding: 10px; font-size: 14px; } } </style> </head> <body> <div class="container"> <!-- Навигация --> <div class="nav-header"> <h1><i class="fas fa-thermometer-half"></i> Система мониторинга температуры</h1> <a href="index.html" class="back-button"> <i class="fas fa-home"></i> На главную </a> </div> <!-- Вкладки --> <div class="tabs"> <div class="tabs-nav"> <button class="tab-btn active" onclick="switchTab('monitoring')"> <i class="fas fa-thermometer-half"></i> Мониторинг </button> <button class="tab-btn" onclick="switchTab('history')"> <i class="fas fa-history"></i> История измерений </button> <button class="tab-btn" onclick="switchTab('devices')"> <i class="fas fa-microchip"></i> Устройства </button> <button class="tab-btn" onclick="switchTab('analysis')"> <i class="fas fa-chart-line"></i> Анализ </button> </div> </div> <!-- Мониторинг --> <div id="monitoring" class="dashboard-content active"> <!-- Статистика --> <div class="content-section"> <div class="section-header"> <h2><i class="fas fa-chart-bar"></i> Статистика системы</h2> </div> <div class="stats-grid"> <div class="stat-card"> <h3>Активные устройства</h3> <div class="stat-value" id="activeDevices">0</div> </div> <div class="stat-card secondary"> <h3>Всего измерений</h3> <div class="stat-value" id="totalMeasurements">0</div> </div> <div class="stat-card warning-stat"> <h3>Обнаружено аномалий</h3> <div class="stat-value" id="anomalyCount">0</div> </div> <div class="stat-card"> <h3>Средняя температура</h3> <div class="stat-value" id="avgTemp">--</div> </div> </div> </div> <!-- Текущие показания --> <div class="content-section"> <div class="section-header"> <h2><i class="fas fa-tachometer-alt"></i> Текущие показания устройств</h2> <button class="btn-primary" onclick="refreshDevices()"> <i class="fas fa-sync-alt"></i> Обновить </button> </div> <div class="devices-grid" id="devicesGrid"> <div class="loading">Загрузка данных об устройствах...</div> </div> </div> <!-- График --> <div class="content-section"> <div class="section-header"> <h2><i class="fas fa-chart-line"></i> График температуры</h2> </div> <div class="chart-container"> <canvas id="temperatureChart"></canvas> </div> </div> </div> <!-- История измерений --> <div id="history" class="dashboard-content"> <div class="content-section"> <div class="section-header"> <h2><i class="fas fa-history"></i> История измерений</h2> <div style="display: flex; gap: 10px;"> <select id="historyDeviceSelect" onchange="loadHistory()"> <option value="">Все устройства</option> </select> </div> </div> <div class="table-controls"> <div class="search-box"> <input type="text" id="historySearch" placeholder="Поиск по устройству..." oninput="filterHistory()"> </div> </div> <div class="table-responsive"> <table id="historyTable"> <thead> <tr> <th onclick="sortHistory('id')">ID</th> <th onclick="sortHistory('device_id')">Устройство</th> <th onclick="sortHistory('temperature')">Температура</th> <th onclick="sortHistory('humidity')">Влажность</th> <th onclick="sortHistory('timestamp')">Время</th> <th onclick="sortHistory('is_anomaly')">Аномалия</th> </tr> </thead> <tbody id="historyTableBody"> <tr><td colspan="6" class="loading">Загрузка истории...</td></tr> </tbody> </table> </div> </div> </div> <!-- Устройства --> <div id="devices" class="dashboard-content"> <div class="content-section"> <div class="section-header"> <h2><i class="fas fa-microchip"></i> Управление устройствами</h2> <button class="btn-primary" onclick="showAddDeviceForm()"> <i class="fas fa-plus"></i> Добавить устройство </button> </div> <!-- Форма добавления --> <div id="addDeviceForm" style="display: none; margin-bottom: 30px;"> <h3 style="color: #1b5e20; margin-bottom: 20px;">Добавление нового устройства</h3> <form onsubmit="addDevice(event)"> <div class="form-row"> <div class="form-group"> <label>Название устройства *</label> <input type="text" id="deviceName" required placeholder="Например: Датчик №1"> </div> <div class="form-group"> <label>Местоположение *</label> <input type="text" id="deviceLocation" required placeholder="Например: Серверная"> </div> </div> <div class="form-row"> <div class="form-group"> <label>Тип устройства *</label> <select id="deviceType" required> <option value="DS18B20">DS18B20</option> <option value="DHT22">DHT22</option> <option value="DHT11">DHT11</option> <option value="AM2302">AM2302</option> <option value="BME280">BME280</option> <option value="SHT31">SHT31</option> <option value="Другое">Другое</option> </select> </div> <div class="form-group"> <label>Минимальная температура (°C)</label> <input type="number" id="deviceMinTemp" step="0.1" placeholder="Например: 18"> </div> <div class="form-group"> <label>Максимальная температура (°C)</label> <input type="number" id="deviceMaxTemp" step="0.1" placeholder="Например: 25"> </div> </div> <div style="display: flex; gap: 10px;"> <button type="submit" class="btn-primary"> <i class="fas fa-save"></i> Сохранить </button> <button type="button" class="btn-secondary" onclick="hideAddDeviceForm()"> <i class="fas fa-times"></i> Отмена </button> </div> </form> </div> <div id="deviceMessage" class="message"></div> <div class="table-responsive"> <table id="devicesTable"> <thead> <tr> <th onclick="sortDevices('id')">ID</th> <th onclick="sortDevices('name')">Название</th> <th onclick="sortDevices('location')">Местоположение</th> <th onclick="sortDevices('device_type')">Тип</th> <th onclick="sortDevices('min_temp')">Мин. темп.</th> <th onclick="sortDevices('max_temp')">Макс. темп.</th> <th onclick="sortDevices('status')">Статус</th> <th>Действия</th> </tr> </thead> <tbody id="devicesTableBody"> <tr><td colspan="8" class="loading">Загрузка устройств...</td></tr> </tbody> </table> </div> </div> </div> <!-- Анализ --> <div id="analysis" class="dashboard-content"> <div class="content-section"> <div class="section-header"> <h2><i class="fas fa-chart-line"></i> Анализ температурных данных</h2> <div style="display: flex; gap: 10px;"> <select id="analysisDeviceSelect" onchange="runAnalysis()"> <option value="">Выберите устройство</option> </select> <select id="analysisPeriod" onchange="runAnalysis()"> <option value="24">За 24 часа</option> <option value="48">За 48 часов</option> <option value="168">За неделю</option> </select> </div> </div> <div id="analysisResults" style="display: none;"> <div class="stats-grid"> <div class="stat-card"> <h3>Средняя температура</h3> <div class="stat-value" id="analysisAvg">--</div> </div> <div class="stat-card secondary"> <h3>Минимальная температура</h3> <div class="stat-value" id="analysisMin">--</div> </div> <div class="stat-card warning-stat"> <h3>Максимальная температура</h3> <div class="stat-value" id="analysisMax">--</div> </div> <div class="stat-card"> <h3>Количество аномалий</h3> <div class="stat-value" id="analysisAnomalies">--</div> </div> </div> <div class="chart-container" style="height: 400px;"> <canvas id="analysisChart"></canvas> </div> </div> <div id="analysisPlaceholder" class="loading"> Выберите устройство для анализа данных </div> </div> </div> <!-- Футер --> <div class="footer"> <p>Система мониторинга температуры | Ульяновский государственный технический университет | 2025</p> </div> </div> <script> // API Configuration const API_BASE = 'http://localhost:8000'; let temperatureChart = null; let analysisChart = null; // Initialize document.addEventListener('DOMContentLoaded', function() { initCharts(); loadDevices(); loadStats(); loadDevicesForSelects(); }); // Switch tabs function switchTab(tabId) { document.querySelectorAll('.dashboard-content').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.getElementById(tabId).classList.add('active'); event.target.closest('.tab-btn').classList.add('active'); if (tabId === 'history') loadHistory(); if (tabId === 'analysis') loadDevicesForAnalysis(); } // Initialize charts function initCharts() { const ctx = document.getElementById('temperatureChart').getContext('2d'); temperatureChart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'Температура (°C)', data: [], borderColor: '#1b5e20', backgroundColor: 'rgba(27, 94, 32, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top' } }, scales: { y: { beginAtZero: false, title: { display: true, text: 'Температура (°C)' } }, x: { title: { display: true, text: 'Время' } } } } }); } // Load devices async function loadDevices() { try { const response = await fetch(`${API_BASE}/api/devices/`); if (!response.ok) throw new Error('Ошибка загрузки устройств'); const devices = await response.json(); const grid = document.getElementById('devicesGrid'); if (devices.length === 0) { grid.innerHTML = '<div class="loading">Устройства не найдены. Добавьте первое устройство!</div>'; return; } grid.innerHTML = devices.map(device => ` <div class="device-card" id="device-${device.id}"> <div class="device-header"> <h3><i class="fas fa-thermometer-half"></i> ${device.name}</h3> <span class="device-status status-${device.status}">${device.status}</span> </div> <div class="device-temp"> <span class="temp-value">${device.current_temp || '--'}</span> <span class="temp-unit">°C</span> </div> <div class="device-info"> <div class="info-item"> <label>Местоположение</label> <span>${device.location}</span> </div> <div class="info-item"> <label>Тип</label> <span>${device.device_type}</span> </div> <div class="info-item"> <label>Мин. темп.</label> <span>${device.min_temp || '--'}°C</span> </div> <div class="info-item"> <label>Макс. темп.</label> <span>${device.max_temp || '--'}°C</span> </div> </div> </div> `).join(''); } catch (error) { console.error('Ошибка:', error); document.getElementById('devicesGrid').innerHTML = '<div class="loading">Ошибка подключения к серверу. Проверьте, что API запущен.</div>'; } } // Load statistics async function loadStats() { // Mock data for demonstration document.getElementById('activeDevices').textContent = '3'; document.getElementById('totalMeasurements').textContent = '1,247'; document.getElementById('anomalyCount').textContent = '12'; document.getElementById('avgTemp').textContent = '22.5°C'; // Update chart with mock data updateTemperatureChart(); } // Update temperature chart function updateTemperatureChart() { const labels = []; const data = []; for (let i = 23; i >= 0; i--) { const time = new Date(); time.setHours(time.getHours() - i); labels.push(time.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })); data.push((20 + Math.random() * 5).toFixed(1)); } temperatureChart.data.labels = labels; temperatureChart.data.datasets[0].data = data; temperatureChart.update(); } // Refresh devices function refreshDevices() { loadDevices(); loadStats(); } // Load history async function loadHistory() { const tbody = document.getElementById('historyTableBody'); tbody.innerHTML = '<tr><td colspan="6" class="loading">Загрузка истории...</td></tr>'; // Mock data for demonstration const mockData = [ { id: 1, device_id: 1, device_name: 'Датчик №1', temperature: 22.5, humidity: 45, timestamp: '2025-02-01 14:30:00', is_anomaly: false }, { id: 2, device_id: 1, device_name: 'Датчик №1', temperature: 26.1, humidity: 42, timestamp: '2025-02-01 14:25:00', is_anomaly: true }, { id: 3, device_id: 2, device_name: 'Датчик №2', temperature: 21.3, humidity: 50, timestamp: '2025-02-01 14:20:00', is_anomaly: false }, { id: 4, device_id: 3, device_name: 'Датчик №3', temperature: 23.8, humidity: 48, timestamp: '2025-02-01 14:15:00', is_anomaly: false }, { id: 5, device_id: 1, device_name: 'Датчик №1', temperature: 19.2, humidity: 47, timestamp: '2025-02-01 14:10:00', is_anomaly: true }, ]; tbody.innerHTML = mockData.map(row => ` <tr> <td>${row.id}</td> <td>${row.device_name}</td> <td>${row.temperature}°C</td> <td>${row.humidity || '--'}%</td> <td>${row.timestamp}</td> <td><span class="anomaly-badge ${row.is_anomaly ? 'anomaly-yes' : 'anomaly-no'}">${row.is_anomaly ? 'Да' : 'Нет'}</span></td> </tr> `).join(''); } // Filter history function filterHistory() { loadHistory(); } // Sort history function sortHistory(field) { loadHistory(); } // Load devices for selects function loadDevicesForSelects() { const select = document.getElementById('historyDeviceSelect'); select.innerHTML = '<option value="">Все устройства</option>' + '<option value="1">Датчик №1</option>' + '<option value="2">Датчик №2</option>' + '<option value="3">Датчик №3</option>'; } // Show add device form function showAddDeviceForm() { document.getElementById('addDeviceForm').style.display = 'block'; } // Hide add device form function hideAddDeviceForm() { document.getElementById('addDeviceForm').style.display = 'none'; } // Add device async function addDevice(event) { event.preventDefault(); const deviceData = { name: document.getElementById('deviceName').value, location: document.getElementById('deviceLocation').value, device_type: document.getElementById('deviceType').value, min_temp: document.getElementById('deviceMinTemp').value ? parseFloat(document.getElementById('deviceMinTemp').value) : null, max_temp: document.getElementById('deviceMaxTemp').value ? parseFloat(document.getElementById('deviceMaxTemp').value) : null }; try { const response = await fetch(`${API_BASE}/api/devices/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(deviceData) }); if (!response.ok) throw new Error('Ошибка добавления устройства'); showMessage('deviceMessage', 'Устройство успешно добавлено!', 'success'); hideAddDeviceForm(); loadDevices(); event.target.reset(); } catch (error) { showMessage('deviceMessage', 'Ошибка: ' + error.message, 'error'); } } // Load devices table async function loadDevicesTable() { const tbody = document.getElementById('devicesTableBody'); tbody.innerHTML = '<tr><td colspan="8" class="loading">Загрузка устройств...</td></tr>'; // Mock data for demonstration const mockDevices = [ { id: 1, name: 'Датчик №1', location: 'Серверная', device_type: 'DS18B20', min_temp: 18, max_temp: 25, status: 'active' }, { id: 2, name: 'Датчик №2', location: 'Офис 101', device_type: 'DHT22', min_temp: 20, max_temp: 28, status: 'active' }, { id: 3, name: 'Датчик №3', location: 'Склад', device_type: 'DS18B20', min_temp: 15, max_temp: 22, status: 'offline' }, ]; tbody.innerHTML = mockDevices.map(device => ` <tr> <td>${device.id}</td> <td>${device.name}</td> <td>${device.location}</td> <td>${device.device_type}</td> <td>${device.min_temp || '--'}°C</td> <td>${device.max_temp || '--'}°C</td> <td><span class="anomaly-badge ${device.status === 'active' ? 'anomaly-no' : 'anomaly-yes'}">${device.status}</span></td> <td> <button class="btn-secondary" style="padding: 5px 10px; font-size: 12px;" onclick="editDevice(${device.id})"> <i class="fas fa-edit"></i> </button> <button class="btn-secondary" style="padding: 5px 10px; font-size: 12px; background: #ffebee; color: #c62828;" onclick="deleteDevice(${device.id})"> <i class="fas fa-trash"></i> </button> </td> </tr> `).join(''); } // Sort devices function sortDevices(field) { loadDevicesTable(); } // Edit device function editDevice(id) { alert('Редактирование устройства ID: ' + id); } // Delete device function deleteDevice(id) { if (confirm('Вы уверены, что хотите удалить это устройство?')) { showMessage('deviceMessage', 'Устройство удалено', 'success'); loadDevicesTable(); } } // Load devices for analysis function loadDevicesForAnalysis() { const select = document.getElementById('analysisDeviceSelect'); if (select.options.length <= 1) { select.innerHTML = '<option value="">Выберите устройство</option>' + '<option value="1">Датчик №1</option>' + '<option value="2">Датчик №2</option>' + '<option value="3">Датчик №3</option>'; } } // Run analysis async function runAnalysis() { const deviceId = document.getElementById('analysisDeviceSelect').value; if (!deviceId) { document.getElementById('analysisResults').style.display = 'none'; document.getElementById('analysisPlaceholder').style.display = 'block'; return; } document.getElementById('analysisResults').style.display = 'block'; document.getElementById('analysisPlaceholder').style.display = 'none'; // Mock analysis data document.getElementById('analysisAvg').textContent = '22.3°C'; document.getElementById('analysisMin').textContent = '18.5°C'; document.getElementById('analysisMax').textContent = '26.8°C'; document.getElementById('analysisAnomalies').textContent = '5'; // Update chart updateAnalysisChart(); } // Update analysis chart function updateAnalysisChart() { const ctx = document.getElementById('analysisChart').getContext('2d'); if (analysisChart) { analysisChart.destroy(); } const labels = []; const data = []; for (let i = 23; i >= 0; i--) { const time = new Date(); time.setHours(time.getHours() - i); labels.push(time.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })); data.push((20 + Math.random() * 5).toFixed(1)); } analysisChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Температура (°C)', data: data, borderColor: '#0277bd', backgroundColor: 'rgba(2, 119, 189, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top' } }, scales: { y: { beginAtZero: false, title: { display: true, text: 'Температура (°C)' } }, x: { title: { display: true, text: 'Время' } } } } }); } // Show message function showMessage(elementId, text, type) { const element = document.getElementById(elementId); element.textContent = text; element.className = 'message ' + type; element.style.display = 'block'; setTimeout(() => { element.style.display = 'none'; }, 5000); } </script> </body> </html>