Files
SenSu/gui/api.py
T
AskaEth e6875f0b4b Initial commit: SenSu Alpha 0.2.0
- 13-service async plugin framework
- Textual TUI with CLI fallback
- Plugin hot-reload + permission system
- Web management panel (aiohttp)
- Bridge-based inter-module communication
- 10 regression tests

Fixes applied:
- PBKDF2-SHA256 auth (was plain SHA256)
- Auth bypass removed (was allow-all on fail)
- Bare excepts replaced with logged errors
- CatFramework/DreamSu -> SenSu naming unified
- ServiceManager: health checks + startup_order
- Env var credentials (SENSU_ADMIN_PASSWORD etc)
2026-06-10 12:28:05 +08:00

362 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import asyncio
from typing import Dict, List, Any, Optional
from aiohttp import web
import json
logger = logging.getLogger(__name__)
class GUIAPI:
"""GUI API接口 - 为未来的GUI界面提供操作接口"""
def __init__(self, service_manager):
self.service_manager = service_manager
self.app = web.Application()
self.setup_routes()
logger.debug("GUIAPI初始化开始")
def setup_routes(self):
"""设置API路由"""
try:
# 框架状态接口
self.app.router.add_get('/api/framework/status', self.handle_framework_status)
self.app.router.add_get('/api/framework/health', self.handle_framework_health)
# 日志接口
self.app.router.add_get('/api/logs/recent', self.handle_recent_logs)
self.app.router.add_get('/api/logs/stream', self.handle_log_stream)
# 插件接口
self.app.router.add_get('/api/plugins', self.handle_plugins_list)
self.app.router.add_post('/api/plugins/{plugin_name}/reload', self.handle_plugin_reload)
self.app.router.add_post('/api/plugins/{plugin_name}/unload', self.handle_plugin_unload)
# 命令接口
self.app.router.add_post('/api/command', self.handle_command_execute)
self.app.router.add_get('/api/command/history', self.handle_command_history)
# 服务接口
self.app.router.add_get('/api/services', self.handle_services_list)
self.app.router.add_get('/api/services/{service_name}/status', self.handle_service_status)
logger.debug("GUI API路由设置完成")
except Exception as e:
logger.error(f"设置GUI API路由时出错: {str(e)}", exc_info=True)
raise
async def handle_framework_status(self, request):
"""处理框架状态请求"""
try:
logger.debug("处理框架状态API请求")
status_info = {
"framework": {
"name": "CatFramework",
"version": "1.0.0",
"status": "running",
"uptime": "0s" # 实际应该计算运行时间
},
"services": {
"total": 0,
"running": 0
},
"plugins": {
"total": 0,
"loaded": 0
}
}
# 获取服务状态
try:
service_manager = self.service_manager.get_service("service_manager")
if service_manager:
status_info["services"]["total"] = len(service_manager.services)
status_info["services"]["running"] = len(service_manager.services)
except:
pass
# 获取插件状态
try:
plugin_service = self.service_manager.get_service("plugin")
if plugin_service:
status_info["plugins"]["total"] = len(plugin_service.plugin_info)
status_info["plugins"]["loaded"] = len(plugin_service.plugins)
except:
pass
return web.json_response(status_info)
except Exception as e:
logger.error(f"处理框架状态请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_framework_health(self, request):
"""处理框架健康检查"""
try:
logger.debug("处理框架健康检查API请求")
health_data = {
"status": "healthy",
"timestamp": asyncio.get_event_loop().time(),
"components": {
"core_services": "healthy",
"plugins": "healthy",
"network": "healthy"
}
}
return web.json_response(health_data)
except Exception as e:
logger.error(f"处理框架健康检查时出错: {str(e)}", exc_info=True)
return web.json_response({"status": "unhealthy", "error": str(e)}, status=503)
async def handle_recent_logs(self, request):
"""处理最近日志请求"""
try:
logger.debug("处理最近日志API请求")
count = int(request.query.get('count', 50))
level = request.query.get('level', '')
log_service = self.service_manager.get_service("log")
if not log_service:
return web.json_response({"error": "Log service not available"}, status=503)
logs = log_service.get_recent_logs(count)
# 按级别过滤
if level:
logs = [log for log in logs if log['level'].lower() == level.lower()]
return web.json_response({"logs": logs, "count": len(logs)})
except Exception as e:
logger.error(f"处理最近日志请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_log_stream(self, request):
"""处理日志流请求(SSE"""
try:
logger.debug("处理日志流API请求")
response = web.StreamResponse()
response.headers['Content-Type'] = 'text/event-stream'
response.headers['Cache-Control'] = 'no-cache'
response.headers['Connection'] = 'keep-alive'
await response.prepare(request)
# 这里应该实现真正的日志流
# 暂时发送测试数据
try:
while True:
test_log = {
"timestamp": asyncio.get_event_loop().time(),
"level": "INFO",
"message": "Log stream test message",
"source": "gui_api"
}
event_data = f"data: {json.dumps(test_log)}\n\n"
await response.write(event_data.encode('utf-8'))
await asyncio.sleep(5)
except asyncio.CancelledError:
logger.debug("日志流连接关闭")
finally:
await response.write_eof()
return response
except Exception as e:
logger.error(f"处理日志流请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_plugins_list(self, request):
"""处理插件列表请求"""
try:
logger.debug("处理插件列表API请求")
plugin_service = self.service_manager.get_service("plugin")
if not plugin_service:
return web.json_response({"error": "Plugin service not available"}, status=503)
plugins_info = []
for plugin_name, plugin_info in plugin_service.plugin_info.items():
plugins_info.append({
"name": plugin_info.name,
"version": plugin_info.version,
"description": plugin_info.description,
"author": plugin_info.author,
"enabled": plugin_info.enabled,
"loaded": plugin_info.loaded,
"error_count": plugin_info.error_count,
"permissions": plugin_info.permissions
})
return web.json_response({"plugins": plugins_info})
except Exception as e:
logger.error(f"处理插件列表请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_plugin_reload(self, request):
"""处理插件重载请求"""
try:
plugin_name = request.match_info['plugin_name']
logger.debug(f"处理插件重载API请求: {plugin_name}")
plugin_service = self.service_manager.get_service("plugin")
if not plugin_service:
return web.json_response({"error": "Plugin service not available"}, status=503)
# 先卸载再加载
unload_success = await plugin_service.unload_plugin(plugin_name)
if unload_success:
load_success = await plugin_service.load_plugin(plugin_name)
result = {"reloaded": load_success}
else:
result = {"reloaded": False, "error": "Unload failed"}
return web.json_response(result)
except Exception as e:
logger.error(f"处理插件重载请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_plugin_unload(self, request):
"""处理插件卸载请求"""
try:
plugin_name = request.match_info['plugin_name']
logger.debug(f"处理插件卸载API请求: {plugin_name}")
plugin_service = self.service_manager.get_service("plugin")
if not plugin_service:
return web.json_response({"error": "Plugin service not available"}, status=503)
success = await plugin_service.unload_plugin(plugin_name)
return web.json_response({"unloaded": success})
except Exception as e:
logger.error(f"处理插件卸载请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_command_execute(self, request):
"""处理命令执行请求"""
try:
data = await request.json()
command = data.get('command', '')
source = data.get('source', 'gui')
logger.debug(f"处理命令执行API请求: {command}")
command_service = self.service_manager.get_service("command")
if not command_service:
return web.json_response({"error": "Command service not available"}, status=503)
result = await command_service.process_command(command, source)
return web.json_response({
"command": command,
"result": str(result),
"success": True
})
except Exception as e:
logger.error(f"处理命令执行请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_command_history(self, request):
"""处理命令历史请求"""
try:
limit = int(request.query.get('limit', 10))
logger.debug(f"处理命令历史API请求,限制: {limit}")
command_service = self.service_manager.get_service("command")
if not command_service:
return web.json_response({"error": "Command service not available"}, status=503)
history = command_service.get_command_history(limit)
return web.json_response({"history": history})
except Exception as e:
logger.error(f"处理命令历史请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_services_list(self, request):
"""处理服务列表请求"""
try:
logger.debug("处理服务列表API请求")
service_manager = self.service_manager.get_service("service_manager")
if not service_manager:
return web.json_response({"error": "Service manager not available"}, status=503)
services_info = []
for name, service in service_manager.services.items():
services_info.append({
"name": name,
"type": type(service).__name__,
"status": "running" # 简化状态
})
return web.json_response({"services": services_info})
except Exception as e:
logger.error(f"处理服务列表请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def handle_service_status(self, request):
"""处理服务状态请求"""
try:
service_name = request.match_info['service_name']
logger.debug(f"处理服务状态API请求: {service_name}")
try:
service = self.service_manager.get_service(service_name)
status_info = {
"name": service_name,
"available": True,
"status": "running"
}
# 可以添加特定服务的状态检查
if hasattr(service, 'is_running'):
status_info["status"] = "running" if service.is_running else "stopped"
return web.json_response(status_info)
except ValueError:
return web.json_response({"error": f"Service {service_name} not found"}, status=404)
except Exception as e:
logger.error(f"处理服务状态请求时出错: {str(e)}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
async def start(self, host: str = "localhost", port: int = 8080):
"""启动GUI API服务器"""
try:
logger.info(f"启动GUI API服务器: {host}:{port}")
runner = web.AppRunner(self.app)
await runner.setup()
site = web.TCPSite(runner, host, port)
await site.start()
logger.debug("GUI API服务器启动完成")
return runner
except Exception as e:
logger.error(f"启动GUI API服务器时出错: {str(e)}", exc_info=True)
raise
async def shutdown(self):
"""关闭GUI API"""
try:
logger.info("关闭GUI API")
# 清理资源
logger.debug("GUI API关闭完成")
except Exception as e:
logger.error(f"关闭GUI API时出错: {str(e)}", exc_info=True)