| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- {% extends "base.html" %}
- {% block title %}系统设备自检{% endblock %}
- {% block content %}
- <div class="module-header">
- <h2>🛡️ 系统设备自检</h2>
- </div>
- <div class="sub-section">
- <div style="background: white; padding: 25px; border-radius: 10px; border: 1px solid #eee; text-align: center;">
- <p style="color: #666; margin-bottom: 20px; font-size: 1.1em;">
- 点击下方按钮开始对所有连接的设备进行状态检查。<br>
- 检查项目包括:电视(Kodi)连通性、门禁控制器、展品LED控制器、以及Home Assistant设备状态。
- </p>
- <button id="btnStartCheck" class="btn btn-primary" style="font-size: 1.2em; padding: 15px 40px; border-radius: 50px; box-shadow: 0 4px 15px rgba(78, 205, 196, 0.4);" onclick="startFullSelfCheck()">
- 🚀 开始全面自检
- </button>
- <div id="progressArea" style="display: none; margin-top: 20px;">
- <div style="color: #2c3e50; font-weight: bold; margin-bottom: 10px;">正在检测中...</div>
- <div style="width: 100%; height: 10px; background: #eee; border-radius: 5px; overflow: hidden; max-width: 500px; margin: 0 auto;">
- <div id="progressBar" style="width: 0%; height: 100%; background: var(--primary-color); transition: width 0.3s;"></div>
- </div>
- </div>
- </div>
- </div>
- <div id="reportArea" style="display: none;">
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
- <h3 style="color: #2c3e50; margin: 0;">📋 自检报告</h3>
- <span id="checkTime" style="color: #999; font-size: 0.9em;"></span>
- </div>
- <!-- 概览卡片 -->
- <div style="display: flex; gap: 20px; margin-bottom: 30px; flex-wrap: wrap;">
- <div class="summary-card" id="summaryTotal">
- <div class="label">总设备数</div>
- <div class="value">0</div>
- </div>
- <div class="summary-card" id="summaryOnline">
- <div class="label">在线</div>
- <div class="value" style="color: #2ecc71;">0</div>
- </div>
- <div class="summary-card" id="summaryOffline">
- <div class="label">离线/异常</div>
- <div class="value" style="color: #e74c3c;">0</div>
- </div>
- </div>
- <!-- 邮件发送区域 -->
- <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 25px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 15px;">
- <div style="flex: 1; min-width: 250px;">
- <label for="reportEmail" style="font-weight: bold; color: #555; margin-right: 10px;">📧 发送报告到邮箱:</label>
- <input type="email" id="reportEmail" placeholder="留空则发送给默认接收者" style="padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 250px;">
- </div>
- <button class="btn btn-info" onclick="sendReport()">📤 发送报告</button>
- </div>
- <!-- 详细结果 -->
- <div id="detailResults">
- <!-- 动态生成 -->
- </div>
- </div>
- {% endblock %}
- {% block extra_styles %}
- .summary-card {
- flex: 1;
- min-width: 150px;
- background: white;
- padding: 20px;
- border-radius: 10px;
- box-shadow: 0 4px 6px rgba(0,0,0,0.05);
- text-align: center;
- border: 1px solid #eee;
- }
- .summary-card .label { color: #888; margin-bottom: 5px; font-size: 0.9em; }
- .summary-card .value { font-size: 2em; font-weight: bold; color: #333; }
-
- .module-report {
- background: white;
- border-radius: 10px;
- box-shadow: 0 4px 6px rgba(0,0,0,0.05);
- margin-bottom: 25px;
- overflow: hidden;
- }
- .module-report-header {
- padding: 15px 20px;
- background: #f8f9fa;
- border-bottom: 1px solid #eee;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .module-title { font-weight: bold; font-size: 1.1em; color: #2c3e50; }
- .module-stats { font-size: 0.9em; color: #666; }
-
- .device-list { padding: 0; }
- .device-item {
- padding: 12px 20px;
- border-bottom: 1px solid #f0f0f0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .device-item:last-child { border-bottom: none; }
- .device-info { flex: 1; }
- .device-name { font-weight: 600; color: #444; }
- .device-meta { font-size: 0.85em; color: #999; margin-top: 2px; }
- .device-status { font-weight: bold; display: flex; align-items: center; gap: 6px; font-size: 0.9em; }
-
- .status-online { color: #2ecc71; }
- .status-offline { color: #e74c3c; }
- {% endblock %}
- {% block scripts %}
- <script>
- async function startFullSelfCheck() {
- const btn = document.getElementById('btnStartCheck');
- const progressArea = document.getElementById('progressArea');
- const progressBar = document.getElementById('progressBar');
- const reportArea = document.getElementById('reportArea');
-
- btn.disabled = true;
- btn.style.opacity = '0.7';
- btn.innerHTML = '⏳ 检测进行中...';
- progressArea.style.display = 'block';
- reportArea.style.display = 'none';
- progressBar.style.width = '10%';
- // 定义检测任务
- const tasks = [
- { id: 'kodi', name: '电视系统 (Kodi)', url: '/api/mitv/self_check' },
- { id: 'door', name: '门禁系统', url: '/api/door/self_check' },
- { id: 'led', name: '展品灯座', url: '/api/led/self_check' },
- { id: 'ha', name: '展厅灯光 (HA)', url: '/api/ha/self_check' },
- { id: 'pc', name: '展厅PC', url: '/api/pc/self_check' }
- ];
- let results = {};
- let completed = 0;
- for (let i = 0; i < tasks.length; i++) {
- const task = tasks[i];
- try {
- const response = await fetch(task.url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const json = await response.json();
- results[task.id] = {
- success: json.success,
- data: json.data || [],
- error: json.success ? null : json.message
- };
- } catch (e) {
- results[task.id] = {
- success: false,
- data: [],
- error: e.message
- };
- }
-
- completed++;
- progressBar.style.width = `${10 + (completed / tasks.length) * 90}%`;
- }
- // 渲染报告
- renderReport(results, tasks);
-
- btn.disabled = false;
- btn.style.opacity = '1';
- btn.innerHTML = '🔄 重新开始自检';
- progressArea.style.display = 'none';
- reportArea.style.display = 'block';
-
- // 滚动到报告区域
- reportArea.scrollIntoView({ behavior: 'smooth' });
- }
- function renderReport(results, tasks) {
- const detailContainer = document.getElementById('detailResults');
- detailContainer.innerHTML = '';
-
- let totalDevices = 0;
- let totalOnline = 0;
- let totalOffline = 0;
-
- document.getElementById('checkTime').textContent = '检测时间: ' + new Date().toLocaleString();
- tasks.forEach(task => {
- const result = results[task.id];
- const deviceList = result.data;
-
- let moduleOnline = 0;
- let moduleTotal = deviceList.length;
- let contentHtml = '';
- if (!result.success) {
- contentHtml = `<div style="padding: 20px; color: #e74c3c; text-align: center;">检测失败: ${result.error || '未知错误'}</div>`;
- totalOffline++; // 整个模块失败算一个异常
- } else if (moduleTotal === 0) {
- contentHtml = `<div style="padding: 20px; color: #999; text-align: center;">未配置设备</div>`;
- } else {
- contentHtml = '<div class="device-list">';
- deviceList.forEach(device => {
- const isOnline = device.is_online;
- if (isOnline) {
- moduleOnline++;
- totalOnline++;
- } else {
- totalOffline++;
- }
- totalDevices++;
- let extraInfo = [];
- if (device.ip) extraInfo.push(`IP: ${device.ip}`);
- if (device.entity_id) extraInfo.push(`Entity: ${device.entity_id}`);
- if (device.state && device.state !== 'unknown') extraInfo.push(`State: ${device.state}`);
- if (device.error) extraInfo.push(`<span style="color: #e74c3c;">Err: ${device.error}</span>`);
- contentHtml += `
- <div class="device-item">
- <div class="device-info">
- <div class="device-name">${device.name || 'Unknown Device'}</div>
- <div class="device-meta">ID: ${device.id} ${extraInfo.length ? '| ' + extraInfo.join(' | ') : ''}</div>
- </div>
- <div class="device-status ${isOnline ? 'status-online' : 'status-offline'}">
- ${isOnline ? '✅ 在线' : '❌ 离线'}
- </div>
- </div>
- `;
- });
- contentHtml += '</div>';
- }
- const moduleHtml = `
- <div class="module-report">
- <div class="module-report-header">
- <div class="module-title">${task.name}</div>
- <div class="module-stats">
- ${result.success ? `在线: <span style="color: ${moduleOnline === moduleTotal && moduleTotal > 0 ? '#2ecc71' : '#666'}">${moduleOnline}</span> / ${moduleTotal}` : '<span style="color: #e74c3c;">检测失败</span>'}
- </div>
- </div>
- ${contentHtml}
- </div>
- `;
- detailContainer.innerHTML += moduleHtml;
- });
- // 更新概览
- document.getElementById('summaryTotal').querySelector('.value').textContent = totalDevices;
- document.getElementById('summaryOnline').querySelector('.value').textContent = totalOnline;
- document.getElementById('summaryOffline').querySelector('.value').textContent = totalOffline;
- }
- async function sendReport() {
- const email = document.getElementById('reportEmail').value.trim();
- const btn = document.querySelector('button[onclick="sendReport()"]');
-
- try {
- btn.disabled = true;
- btn.textContent = '发送中...';
-
- const response = await fetch('/api/send_report', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ email: email })
- });
-
- const result = await response.json();
-
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- btn.disabled = false;
- btn.textContent = '📤 发送报告';
- }
- }
- </script>
- {% endblock %}
|