diff --git a/app.py b/app.py index a923af0..b5d80f5 100644 --- a/app.py +++ b/app.py @@ -20,6 +20,16 @@ from flask import Flask, render_template_string, jsonify, request, g import urllib.request import urllib.error +# 系统资源监控 +try: + import psutil + import time + HAS_PSUTIL = True + # 网速计算用的上次数据 + _last_net_stats = {'bytes_sent': 0, 'bytes_recv': 0, 'time': 0} +except ImportError: + HAS_PSUTIL = False + app = Flask(__name__) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -694,6 +704,101 @@ def api_cron_sync(): return jsonify({'message': f'同步完成,新增 {synced_count} 个任务'}) +# ==================== 系统资源监控 API ==================== + +@app.route('/api/system/stats') +def api_system_stats(): + """获取系统资源信息""" + if not HAS_PSUTIL: + return jsonify({'error': 'psutil未安装', 'available': False}) + + try: + # CPU + cpu_percent = psutil.cpu_percent(interval=0.5) + cpu_count = psutil.cpu_count(logical=False) or psutil.cpu_count() + cpu_count_logical = psutil.cpu_count(logical=True) + + # 内存 + mem = psutil.virtual_memory() + mem_total_gb = round(mem.total / (1024**3), 2) + mem_used_gb = round(mem.used / (1024**3), 2) + mem_available_gb = round(mem.available / (1024**3), 2) + mem_percent = mem.percent + + # 磁盘 + disk = psutil.disk_usage('/') + disk_total_gb = round(disk.total / (1024**3), 2) + disk_used_gb = round(disk.used / (1024**3), 2) + disk_free_gb = round(disk.free / (1024**3), 2) + disk_percent = disk.percent + + # 网络流量和网速计算 + net = psutil.net_io_counters() + current_time = time.time() + + # 计算网速 (bytes/s) + time_diff = current_time - _last_net_stats['time'] if _last_net_stats['time'] > 0 else 1 + sent_speed = (net.bytes_sent - _last_net_stats['bytes_sent']) / time_diff if time_diff > 0 else 0 + recv_speed = (net.bytes_recv - _last_net_stats['bytes_recv']) / time_diff if time_diff > 0 else 0 + + # 保存当前数据用于下次计算 + _last_net_stats['bytes_sent'] = net.bytes_sent + _last_net_stats['bytes_recv'] = net.bytes_recv + _last_net_stats['time'] = current_time + + # 转换为 KB/s 或 MB/s + sent_speed_kb = sent_speed / 1024 + recv_speed_kb = recv_speed / 1024 + sent_speed_display = f"{sent_speed_kb:.1f} KB/s" if sent_speed_kb < 1024 else f"{sent_speed_kb/1024:.2f} MB/s" + recv_speed_display = f"{recv_speed_kb:.1f} KB/s" if recv_speed_kb < 1024 else f"{recv_speed_kb/1024:.2f} MB/s" + + net_sent_mb = round(net.bytes_sent / (1024**2), 2) + net_recv_mb = round(net.bytes_recv / (1024**2), 2) + + # 系统启动时间 + boot_time = datetime.fromtimestamp(psutil.boot_time()).strftime('%Y-%m-%d %H:%M:%S') + uptime_seconds = int(datetime.now().timestamp() - psutil.boot_time()) + uptime_hours = uptime_seconds // 3600 + uptime_minutes = (uptime_seconds % 3600) // 60 + + # 进程数 + process_count = len(psutil.pids()) + + return jsonify({ + 'available': True, + 'cpu': { + 'percent': cpu_percent, + 'count': cpu_count, + 'logical': cpu_count_logical + }, + 'memory': { + 'total_gb': mem_total_gb, + 'used_gb': mem_used_gb, + 'available_gb': mem_available_gb, + 'percent': mem_percent + }, + 'disk': { + 'total_gb': disk_total_gb, + 'used_gb': disk_used_gb, + 'free_gb': disk_free_gb, + 'percent': disk_percent + }, + 'network': { + 'sent_mb': net_sent_mb, + 'recv_mb': net_recv_mb, + 'sent_speed': sent_speed_display, + 'recv_speed': recv_speed_display + }, + 'system': { + 'boot_time': boot_time, + 'uptime': f'{uptime_hours}小时{uptime_minutes}分钟', + 'process_count': process_count + } + }) + except Exception as e: + return jsonify({'error': str(e), 'available': False}) + + def guess_cron_name(command): """从命令推断任务名称""" keywords = { @@ -935,7 +1040,7 @@ HTML_TEMPLATE = ''' - 项目服务管理面板 v2.4 + 项目服务管理面板 v2.6 @@ -965,6 +1070,11 @@ HTML_TEMPLATE = ''' .filter-bar-btn.active { background: #3b82f6; border-color: #3b82f6; color: #fff; } .filter-bar-btn.add-btn { background: #22c55e; border-color: #22c55e; color: #fff; } .filter-bar-btn.add-btn:hover { background: #16a34a; } + .realtime-toggle { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 8px; background: #1e293b; border: 1px solid #334155; } + .realtime-toggle.active { background: #22c55e/20; border-color: #22c55e; } + .speed-badge { display: inline-flex; align-items: center; gap: 4px; padding: 4px 12px; border-radius: 6px; background: #334155; font-size: 13px; } + .speed-badge.upload { color: #f97316; } + .speed-badge.download { color: #3b82f6; } .tab-btn { border-bottom: 2px solid transparent; } .tab-btn.active { border-bottom-color: #3b82f6; } .cron-card { transition: all 0.2s; } @@ -1016,6 +1126,9 @@ HTML_TEMPLATE = ''' + @@ -1079,6 +1192,121 @@ HTML_TEMPLATE = ''' + + +