diff --git a/app.py b/app.py index 0673829..65b7633 100644 --- a/app.py +++ b/app.py @@ -1040,7 +1040,7 @@ HTML_TEMPLATE = ''' - 项目服务管理面板 v2.8 + 项目服务管理面板 v2.9 @@ -1078,6 +1078,8 @@ HTML_TEMPLATE = ''' .threshold-section { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 16px; margin-bottom: 16px; } .threshold-item { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid #334155; } .threshold-item:last-child { border-bottom: none; } + .notification-toggle { display: flex; align-items: center; gap: 8px; padding: 12px; background: #334155; border-radius: 8px; margin-top: 12px; } + .notification-toggle label { cursor: pointer; } .threshold-label { flex: 1; display: flex; align-items: center; gap: 8px; } .threshold-input { width: 60px; background: #334155; border: 1px solid #475569; border-radius: 6px; padding: 4px 8px; color: #f1f5f9; text-align: center; } .threshold-input:focus { outline: none; border-color: #3b82f6; } @@ -1357,6 +1359,14 @@ HTML_TEMPLATE = ''' + +
+ + + +
@@ -1658,6 +1668,7 @@ HTML_TEMPLATE = ''' } if (tab === 'system') { loadThresholds(); + loadDesktopNotifySetting(); loadSystemStats(); } } @@ -1689,6 +1700,86 @@ HTML_TEMPLATE = ''' localStorage.setItem('systemThresholds', JSON.stringify(thresholds)); } + // 桌面通知开关 + let desktopNotifyEnabled = false; + + function toggleDesktopNotify() { + const checked = document.getElementById('enableDesktopNotify').checked; + const status = document.getElementById('notifyStatus'); + + if (checked) { + // 检查浏览器支持 + if (!('Notification' in window)) { + status.textContent = '不支持'; + status.className = 'text-xs text-red-500 ml-1'; + document.getElementById('enableDesktopNotify').checked = false; + return; + } + + // 检查权限状态 + if (Notification.permission === 'granted') { + desktopNotifyEnabled = true; + status.textContent = '已启用'; + status.className = 'text-xs text-green-400 ml-1'; + localStorage.setItem('desktopNotify', 'true'); + } else if (Notification.permission === 'denied') { + status.textContent = '已拒绝'; + status.className = 'text-xs text-red-500 ml-1'; + document.getElementById('enableDesktopNotify').checked = false; + } else { + // 请求权限 + Notification.requestPermission().then(permission => { + if (permission === 'granted') { + desktopNotifyEnabled = true; + status.textContent = '已启用'; + status.className = 'text-xs text-green-400 ml-1'; + localStorage.setItem('desktopNotify', 'true'); + } else { + status.textContent = '已拒绝'; + status.className = 'text-xs text-red-500 ml-1'; + document.getElementById('enableDesktopNotify').checked = false; + } + }); + } + } else { + desktopNotifyEnabled = false; + status.textContent = ''; + localStorage.setItem('desktopNotify', 'false'); + } + } + + // 加载桌面通知设置 + function loadDesktopNotifySetting() { + const saved = localStorage.getItem('desktopNotify'); + if (saved === 'true' && Notification.permission === 'granted') { + desktopNotifyEnabled = true; + document.getElementById('enableDesktopNotify').checked = true; + document.getElementById('notifyStatus').textContent = '已启用'; + document.getElementById('notifyStatus').className = 'text-xs text-green-400 ml-1'; + } + } + + // 发送桌面通知 + function sendDesktopNotification(warnings) { + if (!desktopNotifyEnabled || Notification.permission !== 'granted') return; + + const notification = new Notification('⚠️ 系统资源告警', { + body: warnings.join('\n'), + icon: 'data:image/svg+xml,📊', + tag: 'system-alert', + requireInteraction: false + }); + + // 点击后聚焦窗口 + notification.onclick = () => { + window.focus(); + notification.close(); + }; + + // 5秒后自动关闭 + setTimeout(() => notification.close(), 5000); + } + // 检查阈值并弹窗警告 function checkThresholds(data) { const now = Date.now(); @@ -1715,8 +1806,11 @@ HTML_TEMPLATE = ''' } } - // 显示警告弹窗 + // 显示警告弹窗(同时发送桌面通知) function showAlertPopup(warnings) { + // 发送桌面通知 + sendDesktopNotification(warnings); + // 移除已有弹窗 const existing = document.querySelector('.alert-popup'); if (existing) existing.remove(); diff --git a/logs/app.log b/logs/app.log index d4168f5..8cfedc5 100644 --- a/logs/app.log +++ b/logs/app.log @@ -1,8 +1,8 @@ -[2026-04-23 17:47:44] ================================================== -[2026-04-23 17:47:44] 项目服务管理面板 v2.0.0 启动 -[2026-04-23 17:47:44] 访问地址: http://localhost:19013 -[2026-04-23 17:47:44] 进程PID: 1138715 -[2026-04-23 17:47:44] ================================================== +[2026-04-23 17:53:14] ================================================== +[2026-04-23 17:53:14] 项目服务管理面板 v2.0.0 启动 +[2026-04-23 17:53:14] 访问地址: http://localhost:19013 +[2026-04-23 17:53:14] 进程PID: 1140936 +[2026-04-23 17:53:14] ================================================== * Serving Flask app 'app' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. @@ -10,24 +10,25 @@ WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:19013 * Running on http://192.168.2.17:19013 Press CTRL+C to quit -127.0.0.1 - - [23/Apr/2026 17:47:47] "GET / HTTP/1.1" 200 - -192.168.2.14 - - [23/Apr/2026 17:47:48] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:47:49] "GET / HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:47:55] "GET / HTTP/1.1" 200 - -192.168.2.8 - - [23/Apr/2026 17:47:56] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:47:56] "GET / HTTP/1.1" 200 - -192.168.2.14 - - [23/Apr/2026 17:47:58] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:05] "GET / HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:06] "GET / HTTP/1.1" 200 - -192.168.2.8 - - [23/Apr/2026 17:48:06] "GET /api/projects HTTP/1.1" 200 - -192.168.2.14 - - [23/Apr/2026 17:48:08] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:13] "GET / HTTP/1.1" 200 - -192.168.2.8 - - [23/Apr/2026 17:48:14] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:15] "GET / HTTP/1.1" 200 - -192.168.2.8 - - [23/Apr/2026 17:48:16] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:17] "GET / HTTP/1.1" 200 - -192.168.2.14 - - [23/Apr/2026 17:48:18] "GET /api/projects HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:25] "GET / HTTP/1.1" 200 - -127.0.0.1 - - [23/Apr/2026 17:48:26] "GET / HTTP/1.1" 200 - -192.168.2.8 - - [23/Apr/2026 17:48:27] "GET /api/projects HTTP/1.1" 200 - -192.168.2.14 - - [23/Apr/2026 17:48:28] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:19] "GET / HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:23] "GET / HTTP/1.1" 200 - +192.168.2.14 - - [23/Apr/2026 17:53:25] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:26] "GET / HTTP/1.1" 200 - +192.168.2.8 - - [23/Apr/2026 17:53:27] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:34] "GET / HTTP/1.1" 200 - +192.168.2.14 - - [23/Apr/2026 17:53:36] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:36] "GET / HTTP/1.1" 200 - +192.168.2.8 - - [23/Apr/2026 17:53:37] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:44] "GET / HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:44] "GET / HTTP/1.1" 200 - +192.168.2.8 - - [23/Apr/2026 17:53:46] "GET /api/projects HTTP/1.1" 200 - +192.168.2.14 - - [23/Apr/2026 17:53:46] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:46] "GET / HTTP/1.1" 200 - +192.168.2.14 - - [23/Apr/2026 17:53:46] "GET /api/system/stats HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:47] "GET / HTTP/1.1" 200 - +192.168.2.8 - - [23/Apr/2026 17:53:47] "GET /api/projects HTTP/1.1" 200 - +192.168.2.14 - - [23/Apr/2026 17:53:49] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:54] "GET / HTTP/1.1" 200 - +192.168.2.14 - - [23/Apr/2026 17:53:56] "GET /api/projects HTTP/1.1" 200 - +127.0.0.1 - - [23/Apr/2026 17:53:56] "GET / HTTP/1.1" 200 - +192.168.2.8 - - [23/Apr/2026 17:53:58] "GET /api/projects HTTP/1.1" 200 -