feat: 系统桌面通知功能

This commit is contained in:
2026-04-23 17:54:02 +08:00
parent 55cc408881
commit eb5bd4ae9d
2 changed files with 123 additions and 28 deletions

98
app.py
View File

@@ -1040,7 +1040,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目服务管理面板 v2.8</title>
<title>项目服务管理面板 v2.9</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📊</text></svg>">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
@@ -1078,6 +1078,8 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
.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 = '''<!DOCTYPE html>
<span class="text-gray-500 text-xs">秒</span>
</div>
</div>
<!-- 桌面通知开关 -->
<div class="notification-toggle">
<input type="checkbox" id="enableDesktopNotify" onchange="toggleDesktopNotify()" class="w-4 h-4 cursor-pointer">
<label for="enableDesktopNotify" class="text-gray-300 text-sm cursor-pointer">
<i class="ri-notification-line text-yellow-400"></i> 系统桌面通知
</label>
<span id="notifyStatus" class="text-xs text-gray-500 ml-1"></span>
</div>
</div>
<!-- 控制按钮 -->
@@ -1658,6 +1668,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
}
if (tab === 'system') {
loadThresholds();
loadDesktopNotifySetting();
loadSystemStats();
}
}
@@ -1689,6 +1700,86 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📊</text></svg>',
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 = '''<!DOCTYPE html>
}
}
// 显示警告弹窗
// 显示警告弹窗(同时发送桌面通知)
function showAlertPopup(warnings) {
// 发送桌面通知
sendDesktopNotification(warnings);
// 移除已有弹窗
const existing = document.querySelector('.alert-popup');
if (existing) existing.remove();

View File

@@ -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 -