📊 系统仪表盘
+ + +🔌 服务状态
+⚡ 快捷操作
+💬 会话管理
+功能开发中...
+📱 节点管理
+功能开发中...
+🎯 技能管理
+功能开发中...
+🧠 记忆管理
+功能开发中...
+⚙️ 系统设置
+功能开发中...
+diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e048dfe --- /dev/null +++ b/api/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +OpenClaw Mission Control - API Blueprint +作者:小白 🐶 +""" + +from flask import Blueprint + +api = Blueprint('api', __name__, url_prefix='/api') + +from . import status, sessions, nodes, skills, memory diff --git a/api/memory.py b/api/memory.py new file mode 100644 index 0000000..5c05e08 --- /dev/null +++ b/api/memory.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +记忆管理 API +作者:小白 🐶 +""" + +from flask import jsonify +from . import api + +@api.route('/memory') +def get_memory(): + """获取记忆统计""" + # TODO: 实现 OpenClaw 记忆查询 + return jsonify({ + 'success': True, + 'data': { + 'total': 0, + 'today': 0 + }, + 'message': '记忆管理功能开发中' + }) diff --git a/api/nodes.py b/api/nodes.py new file mode 100644 index 0000000..0aeadd9 --- /dev/null +++ b/api/nodes.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +节点管理 API +作者:小白 🐶 +""" + +from flask import jsonify +from . import api + +@api.route('/nodes') +def get_nodes(): + """获取节点列表""" + # TODO: 实现 OpenClaw 节点查询 + return jsonify({ + 'success': True, + 'data': [], + 'message': '节点管理功能开发中' + }) diff --git a/api/sessions.py b/api/sessions.py new file mode 100644 index 0000000..0c48a3e --- /dev/null +++ b/api/sessions.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +会话管理 API +作者:小白 🐶 +""" + +from flask import jsonify +from . import api + +@api.route('/sessions') +def get_sessions(): + """获取会话列表""" + # TODO: 实现 OpenClaw 会话查询 + return jsonify({ + 'success': True, + 'data': [], + 'message': '会话管理功能开发中' + }) diff --git a/api/skills.py b/api/skills.py new file mode 100644 index 0000000..0038eea --- /dev/null +++ b/api/skills.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +技能管理 API +作者:小白 🐶 +""" + +from flask import jsonify +from . import api + +@api.route('/skills') +def get_skills(): + """获取技能列表""" + # TODO: 实现 OpenClaw 技能查询 + return jsonify({ + 'success': True, + 'data': [], + 'message': '技能管理功能开发中' + }) diff --git a/api/status.py b/api/status.py new file mode 100644 index 0000000..bcae023 --- /dev/null +++ b/api/status.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +系统状态 API +作者:小白 🐶 +""" + +import subprocess +import psutil +from flask import jsonify +from . import api + +@api.route('/status') +def get_status(): + """获取系统状态""" + try: + # CPU 使用率 + cpu_percent = psutil.cpu_percent(interval=1) + + # 内存使用 + memory = psutil.virtual_memory() + memory_percent = memory.percent + memory_used = round(memory.used / (1024**3), 2) + memory_total = round(memory.total / (1024**3), 2) + + # 磁盘使用 + disk = psutil.disk_usage('/') + disk_percent = disk.percent + disk_used = round(disk.used / (1024**3), 2) + disk_total = round(disk.total / (1024**3), 2) + + # 系统运行时间 + uptime_seconds = psutil.boot_time() + + # 进程数 + process_count = len(psutil.pids()) + + return jsonify({ + 'success': True, + 'data': { + 'cpu': { + 'percent': cpu_percent + }, + 'memory': { + 'percent': memory_percent, + 'used': memory_used, + 'total': memory_total, + 'unit': 'GB' + }, + 'disk': { + 'percent': disk_percent, + 'used': disk_used, + 'total': disk_total, + 'unit': 'GB' + }, + 'process_count': process_count, + 'uptime': uptime_seconds + } + }) + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }) + +@api.route('/status/services') +def get_services(): + """获取服务状态""" + services = [] + + # 检查各服务状态 + service_list = [ + ('Flask', 5000), + ('思源笔记', 6806), + ('Gitea', 3000), + ('NocoDB', 8080), + ('Memory Viewer', 18798) + ] + + for name, port in service_list: + try: + result = subprocess.run( + ['ss', '-tln'], + capture_output=True, + text=True, + timeout=5 + ) + is_running = f':{port}' in result.stdout + services.append({ + 'name': name, + 'port': port, + 'status': 'running' if is_running else 'stopped' + }) + except: + services.append({ + 'name': name, + 'port': port, + 'status': 'unknown' + }) + + return jsonify({ + 'success': True, + 'data': services + }) diff --git a/app.py b/app.py index 351a121..aa05c0d 100644 --- a/app.py +++ b/app.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -云下飞的个人网站 - Flask 版本 -支持登录、注册功能 +OpenClaw Mission Control - Flask 版本 +支持登录、注册、控制中心功能 作者:小白 🐶 """ @@ -36,6 +36,10 @@ login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' +# 注册 API Blueprint +from api import api +app.register_blueprint(api) + # 用户模型 class User(UserMixin, db.Model): @@ -59,10 +63,17 @@ def load_user(user_id): @app.route('/') def index(): if current_user.is_authenticated: - return render_template('welcome.html', username=current_user.username) + return redirect(url_for('dashboard')) return render_template('index.html') +# 路由 - 控制中心仪表盘 +@app.route('/dashboard') +@login_required +def dashboard(): + return render_template('dashboard/index.html', username=current_user.username) + + # 路由 - 登录 @app.route('/login', methods=['GET', 'POST']) def login(): diff --git a/static/css/dashboard.css b/static/css/dashboard.css new file mode 100644 index 0000000..7e7e925 --- /dev/null +++ b/static/css/dashboard.css @@ -0,0 +1,423 @@ +/** + * OpenClaw Mission Control - Dashboard Styles + * 作者:小白 🐶 + */ + +/* CSS 变量 */ +:root { + --primary-color: #667eea; + --primary-dark: #5a67d8; + --success-color: #48bb78; + --warning-color: #ed8936; + --danger-color: #f56565; + --bg-color: #f7fafc; + --card-bg: #ffffff; + --text-primary: #2d3748; + --text-secondary: #718096; + --border-color: #e2e8f0; + --sidebar-width: 240px; + --topnav-height: 60px; +} + +[data-theme="dark"] { + --primary-color: #a78bfa; + --primary-dark: #8b5cf6; + --bg-color: #1a1a2e; + --card-bg: #16213e; + --text-primary: #e2e8f0; + --text-secondary: #a0aec0; + --border-color: #2d3748; +} + +/* 重置 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif; + background: var(--bg-color); + color: var(--text-primary); + min-height: 100vh; + transition: background 0.3s ease, color 0.3s ease; +} + +/* 顶部导航 */ +.top-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--topnav-height); + background: var(--card-bg); + border-bottom: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + z-index: 100; +} + +.nav-brand { + display: flex; + align-items: center; + gap: 10px; +} + +.nav-brand .logo { + font-size: 24px; +} + +.nav-brand .title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); +} + +.nav-user { + display: flex; + align-items: center; + gap: 15px; +} + +.nav-user .username { + color: var(--text-secondary); + font-size: 14px; +} + +.btn-logout { + padding: 6px 12px; + background: var(--danger-color); + color: white; + border-radius: 6px; + text-decoration: none; + font-size: 14px; + transition: opacity 0.3s; +} + +.btn-logout:hover { + opacity: 0.8; +} + +/* 主题按钮 */ +.theme-toggle { + width: 40px; + height: 40px; + border-radius: 50%; + border: none; + background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%); + color: white; + font-size: 20px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.3s ease; +} + +.theme-toggle:hover { + transform: rotate(20deg) scale(1.1); +} + +/* 侧边栏 */ +.sidebar { + position: fixed; + top: var(--topnav-height); + left: 0; + bottom: 0; + width: var(--sidebar-width); + background: var(--card-bg); + border-right: 1px solid var(--border-color); + overflow-y: auto; + padding: 20px 0; +} + +.sidebar-nav { + padding: 0 10px; +} + +.nav-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 15px; + color: var(--text-secondary); + text-decoration: none; + border-radius: 8px; + margin-bottom: 5px; + transition: all 0.3s ease; +} + +.nav-item:hover { + background: var(--bg-color); + color: var(--text-primary); +} + +.nav-item.active { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%); + color: white; +} + +.nav-item .icon { + font-size: 20px; +} + +.nav-item .text { + font-size: 14px; + font-weight: 500; +} + +.quick-links { + padding: 20px 15px; + border-top: 1px solid var(--border-color); + margin-top: 20px; +} + +.quick-links .section-title { + font-size: 12px; + color: var(--text-secondary); + text-transform: uppercase; + margin-bottom: 15px; +} + +.link-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + color: var(--text-secondary); + text-decoration: none; + font-size: 13px; + border-radius: 6px; + transition: all 0.3s ease; +} + +.link-item:hover { + background: var(--bg-color); + color: var(--primary-color); +} + +/* 主内容区 */ +.main-content { + margin-left: var(--sidebar-width); + margin-top: var(--topnav-height); + padding: 30px; + min-height: calc(100vh - var(--topnav-height)); +} + +.page { + display: none; +} + +.page.active { + display: block; +} + +.page-title { + font-size: 24px; + font-weight: 600; + margin-bottom: 25px; + color: var(--text-primary); +} + +/* 状态卡片 */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + background: var(--card-bg); + border-radius: 12px; + padding: 20px; + border: 1px solid var(--border-color); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); +} + +.stat-card .stat-icon { + font-size: 32px; + margin-bottom: 10px; +} + +.stat-card .stat-label { + font-size: 14px; + color: var(--text-secondary); + margin-bottom: 5px; +} + +.stat-card .stat-value { + font-size: 24px; + font-weight: 600; + color: var(--text-primary); +} + +.stat-card .progress-bar { + height: 6px; + background: var(--border-color); + border-radius: 3px; + margin-top: 15px; + overflow: hidden; +} + +.stat-card .progress { + height: 100%; + border-radius: 3px; + transition: width 0.5s ease; +} + +.stat-card.cpu .progress { background: var(--primary-color); } +.stat-card.memory .progress { background: var(--success-color); } +.stat-card.disk .progress { background: var(--warning-color); } + +/* 服务状态 */ +.section { + margin-bottom: 30px; +} + +.section-title { + font-size: 18px; + font-weight: 600; + margin-bottom: 15px; + color: var(--text-primary); +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 15px; +} + +.service-card { + background: var(--card-bg); + border-radius: 10px; + padding: 15px; + border: 1px solid var(--border-color); + display: flex; + align-items: center; + gap: 12px; +} + +.service-card .icon { + font-size: 24px; +} + +.service-card .info { + flex: 1; +} + +.service-card .name { + font-size: 14px; + font-weight: 500; + color: var(--text-primary); +} + +.service-card .port { + font-size: 12px; + color: var(--text-secondary); +} + +.service-card .status-badge { + padding: 3px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; +} + +.status-badge.running { + background: rgba(72, 187, 120, 0.2); + color: var(--success-color); +} + +.status-badge.stopped { + background: rgba(245, 101, 101, 0.2); + color: var(--danger-color); +} + +/* 快捷操作 */ +.actions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; +} + +.action-btn { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 10px; + padding: 15px; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + cursor: pointer; + transition: all 0.3s ease; + color: var(--text-primary); +} + +.action-btn:hover { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%); + color: white; + border-color: transparent; +} + +.action-btn .icon { + font-size: 24px; +} + +.action-btn span:last-child { + font-size: 13px; + font-weight: 500; +} + +/* 开发中提示 */ +.coming-soon { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + color: var(--text-secondary); +} + +.coming-soon .icon { + font-size: 48px; + margin-bottom: 15px; +} + +.coming-soon p { + font-size: 16px; +} + +/* 加载动画 */ +.loading { + text-align: center; + padding: 20px; + color: var(--text-secondary); +} + +/* 响应式 */ +@media (max-width: 768px) { + .sidebar { + width: 60px; + padding: 10px 0; + } + + .sidebar .nav-item .text, + .sidebar .quick-links { + display: none; + } + + .main-content { + margin-left: 60px; + padding: 20px; + } +} diff --git a/static/js/dashboard.js b/static/js/dashboard.js new file mode 100644 index 0000000..abacd71 --- /dev/null +++ b/static/js/dashboard.js @@ -0,0 +1,106 @@ +/** + * OpenClaw Mission Control - Dashboard JavaScript + * 作者:小白 🐶 + */ + +// 页面切换 +document.querySelectorAll('.nav-item').forEach(item => { + item.addEventListener('click', (e) => { + e.preventDefault(); + + // 移除所有活动状态 + document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active')); + document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); + + // 添加当前活动状态 + item.classList.add('active'); + const pageId = 'page-' + item.dataset.page; + document.getElementById(pageId).classList.add('active'); + }); +}); + +// 获取系统状态 +async function fetchStatus() { + try { + const response = await fetch('/api/status'); + const result = await response.json(); + + if (result.success) { + const data = result.data; + + // 更新 CPU + document.getElementById('cpu-percent').textContent = data.cpu.percent + '%'; + document.getElementById('cpu-progress').style.width = data.cpu.percent + '%'; + + // 更新内存 + document.getElementById('memory-value').textContent = + `${data.memory.used} / ${data.memory.total} GB`; + document.getElementById('memory-progress').style.width = data.memory.percent + '%'; + + // 更新磁盘 + document.getElementById('disk-value').textContent = + `${data.disk.used} / ${data.disk.total} GB`; + document.getElementById('disk-progress').style.width = data.disk.percent + '%'; + + // 更新进程数 + document.getElementById('process-count').textContent = data.process_count; + } + } catch (error) { + console.error('获取状态失败:', error); + } +} + +// 获取服务状态 +async function fetchServices() { + try { + const response = await fetch('/api/status/services'); + const result = await response.json(); + + if (result.success) { + const container = document.getElementById('services-grid'); + container.innerHTML = ''; + + const icons = { + 'Flask': '🌐', + '思源笔记': '📝', + 'Gitea': '📦', + 'NocoDB': '📊', + 'Memory Viewer': '🧠' + }; + + result.data.forEach(service => { + const card = document.createElement('div'); + card.className = 'service-card'; + card.innerHTML = ` + ${icons[service.name] || '🔌'} +
功能开发中...
+功能开发中...
+功能开发中...
+功能开发中...
+功能开发中...
+