diff --git a/config/plugins/commands.yaml b/config/plugins/commands.yaml index 2a936f4..9c9402e 100644 --- a/config/plugins/commands.yaml +++ b/config/plugins/commands.yaml @@ -4,10 +4,14 @@ commands: permissions: - framework.tui.control source: internal - chat_broadcast: &id001 - description: 向所有聊天客户端广播消息 + create-plugin: + description: 创建新插件脚手架 permissions: - - plugin.example.chat.broadcast + - framework.scaffold.plugin + source: internal + echo: &id001 + description: echo input + permissions: [] source: plugin.example_plugin help: description: 显示帮助信息 @@ -24,15 +28,15 @@ commands: permissions: - framework.network.diagnose source: internal - network_info: &id002 - description: 显示插件网络信息 - permissions: [] - source: plugin.example_plugin permissions: description: '权限管理: 显示权限状态' permissions: - framework.permission.read source: internal + plugin_status: &id002 + description: show plugin status + permissions: [] + source: plugin.example_plugin pm_plugin_status: description: '权限管理: 查看插件权限状态' permissions: @@ -88,9 +92,9 @@ commands: permissions: - framework.command.test source: internal -last_updated: 119133.773274654 +last_updated: 248209.227564533 plugin_commands: example_plugin: - chat_broadcast: *id001 - network_info: *id002 -total_commands: 18 + echo: *id001 + plugin_status: *id002 +total_commands: 19 diff --git a/config/services/network_routes.yaml b/config/services/network_routes.yaml index 8ea21b4..1f967fb 100644 --- a/config/services/network_routes.yaml +++ b/config/services/network_routes.yaml @@ -1,17 +1,9 @@ -http_port: 8000 -last_updated: 119133.817076582 +http_port: 4200 +last_updated: 248209.231599689 plugin_routes: example_plugin: - methods: - GET - path: /plugin/example_plugin/api/info + path: /example_plugin/api/example/info require_auth: false - - methods: - - POST - path: /plugin/example_plugin/api/echo - require_auth: true - - methods: - - WEBSOCKET - path: /plugin/example_plugin/ws/chat - require_auth: true -websocket_port: 8765 +websocket_port: 4240 diff --git a/main.py b/main.py index f5c3721..b717eb8 100644 --- a/main.py +++ b/main.py @@ -23,9 +23,7 @@ from services.plugin_service import PluginService from services.permission_service import PermissionService from services.api_service import APIService from services.shutdown_service import ShutdownService -from services.web_panel.manager import WebPanelManager -from services.project_service import ProjectService -from services.sensu_db import SenSuDB +from services.web_panel.manager import WebPanelManager from bridges.core_bridge import CoreBridge from bridges.plugin_bridge import PluginBridge @@ -33,11 +31,12 @@ from service_manager import ServiceManager logger = logging.getLogger(__name__) -class CatFramework: +class SenSuFramework: """框架主类""" - def __init__(self): + def __init__(self, headless=False): self.service_manager = ServiceManager() + self.headless = headless self.is_running = False self.headless = headless logger.debug("🐱 DreamSu 框架初始化开始") @@ -85,7 +84,7 @@ class CatFramework: # 8. TUI服务 if self.headless: - logger.info("Headless 模式,跳过 TUI") + logger.info("Headless: skip TUI") self.service_manager.register_service("tui", self._create_fallback_tui()) else: try: @@ -94,10 +93,8 @@ class CatFramework: self.service_manager.register_service("tui", tui_service) logger.info("TUI服务启动成功") except Exception as e: - logger.warning(f"TUI服务启动失败,使用命令行模式: {str(e)}") + logger.warning(f"TUI服务启动失败: {e}") self.service_manager.register_service("tui", self._create_fallback_tui()) - - # 9. 权限服务 logger.info("> 初始化 权限服务 中...") permission_service = PermissionService(base_config, self.service_manager.get_service("tui"), core_bridge) permission_started = await permission_service.start() @@ -124,8 +121,6 @@ class CatFramework: # 确保 internet_service 已经实例化(在第10步) if internet_service: from services.web_panel.manager import WebPanelManager -from services.project_service import ProjectService -from services.sensu_db import SenSuDB # 初始化面板管理器 web_panel = WebPanelManager(base_config, self.service_manager) @@ -134,17 +129,6 @@ from services.sensu_db import SenSuDB if await web_panel.start(): self.service_manager.register_service("web_panel", web_panel) logger.info("✅ Web 面板挂载完成") - # 11.6 项目注册表 + 数据库 - logger.info("> 初始化 项目注册表 中...") - try: - db = SenSuDB("data/sensu.db") - project_service = ProjectService(self.service_manager, db) - await project_service.start() - self.service_manager.register_service("project", project_service) - logger.info("✅ 项目注册表就绪") - except Exception as e: - logger.warning(f"项目注册表初始化跳过: {e}") - else: logger.warning("Web 面板初始化未完成") except Exception as e: @@ -238,7 +222,7 @@ from services.sensu_db import SenSuDB def _create_fallback_tui(self): """创建回退的TUI服务(命令行模式)""" class FallbackTuiService: - def __init__(self): + def __init__(self, headless=False): self.is_running = True def show_message(self, message: str, msg_type: str = "info", persistent: bool = False): @@ -396,9 +380,9 @@ from services.sensu_db import SenSuDB logger.error(f"关闭框架时出错: {str(e)}", exc_info=True) await self._safe_shutdown() -async def main(): +async def main(headless=False): """主函数""" - framework = CatFramework() + framework = SenSuFramework(headless=headless) try: # 初始化框架 @@ -419,6 +403,7 @@ if __name__ == "__main__": try: # 设置更详细的异常处理 import signal + def signal_handler(signum, frame): """信号处理""" @@ -430,8 +415,8 @@ if __name__ == "__main__": signal.signal(signal.SIGTERM, signal_handler) print("\n🐱 主程序启动...") - parser = argparse.ArgumentParser(description="SenSu 插件化项目管理框架") - parser.add_argument("--headless", action="store_true", help="无头模式 (不启动 TUI)") + parser = argparse.ArgumentParser(description="SenSu") + parser.add_argument("--headless", action="store_true") args = parser.parse_args() asyncio.run(main(headless=args.headless)) diff --git a/main.py.clean b/main.py.clean new file mode 100644 index 0000000..9ebf8b8 --- /dev/null +++ b/main.py.clean @@ -0,0 +1,433 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import logging +import asyncio +import sys +import signal +import os +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) + +from services.init_service import InitService +from services.log_service import LogService +from services.tui_service import TuiService +from services.command_service import CommandService +from services.auth_service import AuthService +from services.internet_service import InternetService +from services.plugin_service import PluginService +from services.permission_service import PermissionService +from services.api_service import APIService +from services.shutdown_service import ShutdownService +from services.web_panel.manager import WebPanelManager + +from bridges.core_bridge import CoreBridge +from bridges.plugin_bridge import PluginBridge +from service_manager import ServiceManager + +logger = logging.getLogger(__name__) + +class CatFramework: + """框架主类""" + + def __init__(self): + self.service_manager = ServiceManager() + self.is_running = False + logger.debug("🐱 DreamSu 框架初始化开始") + + async def initialize(self): + """初始化框架""" + try: + logger.info("🐱 SenSu 启动中...") + + # 1. 初始化服务 + init_service = InitService() + configs = await init_service.initialize_framework() + base_config = configs['base'] + self.service_manager.register_service("init", init_service) + + # 2. 日志服务 + log_service = LogService(base_config) + self.service_manager.register_service("log", log_service) + + # 3. 核心桥接服务 + core_bridge = CoreBridge() + await core_bridge.start() + self.service_manager.register_service("core_bridge", core_bridge) + + # 4. 指令服务 + command_service = CommandService(self.service_manager) + command_service.register_builtin_commands() + self.service_manager.register_service("command", command_service) + + # 5. 认证服务 + logger.info("> 初始化 访问认证 ...") + auth_service = AuthService(base_config) + self.service_manager.register_service("auth", auth_service) + + # 6. 插件桥接服务 + logger.info("> 初始化 插件桥 ...") + plugin_bridge = PluginBridge(core_bridge) + await plugin_bridge.start() + self.service_manager.register_service("plugin_bridge", plugin_bridge) + + # 7. 关闭服务 + logger.info("> 初始化 关闭服务 中...") + shutdown_service = ShutdownService(self.service_manager) + self.service_manager.register_service("shutdown", shutdown_service) + + # 8. TUI服务 + try: + tui_service = TuiService(base_config, log_service, command_service) + await tui_service.start() + self.service_manager.register_service("tui", tui_service) + logger.info("TUI服务启动成功") + except Exception as e: + logger.warning(f"TUI服务启动失败,使用命令行模式: {str(e)}") + self.service_manager.register_service("tui", self._create_fallback_tui()) + + # 9. 权限服务 + logger.info("> 初始化 权限服务 中...") + permission_service = PermissionService(base_config, self.service_manager.get_service("tui"), core_bridge) + permission_started = await permission_service.start() + if permission_started: + self.service_manager.register_service("permission", permission_service) + else: + logger.warning("权限服务启动失败,跳过注册") + + # 10. 互联网服务(提前创建但不启动) + logger.info("> 初始化 网络服务 中...") + internet_service = InternetService(base_config, self.service_manager) + self.service_manager.register_service("internet", internet_service) + + # 11. 插件服务(在网络服务启动前注册路由) + logger.info("> 初始化 插件管理器 中...") + plugin_service = PluginService(base_config, permission_service, plugin_bridge, self.service_manager) + await plugin_service.start() + self.service_manager.register_service("plugin", plugin_service) + + # === 🟢 新增: 11.5 Web管理面板初始化 (必须在网络服务启动前!) === + # 原因: aiohttp 启动后会“冻结”路由器,之后再挂载子应用会报错 + logger.info("> 初始化 Web 管理面板 中...") + try: + # 确保 internet_service 已经实例化(在第10步) + if internet_service: + from services.web_panel.manager import WebPanelManager + + # 初始化面板管理器 + web_panel = WebPanelManager(base_config, self.service_manager) + + # 这里的 start() 会把面板路由挂载到 http_app 上 (此时还未冻结) + if await web_panel.start(): + self.service_manager.register_service("web_panel", web_panel) + logger.info("✅ Web 面板挂载完成") + else: + logger.warning("Web 面板初始化未完成") + except Exception as e: + logger.error(f"❌ Web 管理面板初始化失败: {str(e)}", exc_info=True) + + # 12. 启动网络服务(在所有插件路由注册后) + logger.info("> 启动网络服务 中...") + try: + # 先检查依赖和端口 + health_info = await internet_service.check_service_health() + logger.debug(f"网络服务预检查: {health_info}") + + if not health_info["dependencies_available"]: + logger.error("❌ 缺少必要的依赖包,网络服务无法启动") + logger.info("💡 请运行: pip install aiohttp yaml") + internet_started = False + else: + # 现在启动网络服务(路由器会在启动时冻结) + internet_started = await internet_service.start() + + # 再次检查服务状态 + if internet_started: + post_health = await internet_service.check_service_health() + logger.debug(f"网络服务启动后检查: {post_health}") + + if post_health["http_active"]: + logger.info("✅ 互联网服务启动成功") + + # 显示服务信息 + logger.info(f"🌐 服务地址: http://{internet_service.http_host}:{internet_service.http_port}") + logger.info(f"🔍 健康检查: http://{internet_service.http_host}:{internet_service.http_port}/health") + else: + logger.warning("⚠️ 网络服务已启动但端口未响应") + else: + logger.warning("❌ 互联网服务启动失败") + + except Exception as e: + logger.error(f"❌ 互联网服务启动异常: {str(e)}", exc_info=True) + logger.warning("互联网服务启动失败") + + # 13. API服务 + logger.info("> 初始化 API 服务 中...") + try: + api_service = APIService(internet_service, auth_service, permission_service) + self.service_manager.register_service("api", api_service) + logger.info("✅ API 服务启动成功") + + except Exception as e: + logger.error(f"❌ API 服务初始化失败: {str(e)}") + logger.warning("API 服务启动失败,跳过注册") + + + # 注册框架关闭处理器 + shutdown_service.register_shutdown_handler(self._framework_shutdown_handler) + logger.info("🎉 DreamSu 初始化完成!") + self.is_running = True + + # 显示欢迎日志 + version = base_config.get('framework', {}).get('version', 'Unknown') + logLevel = base_config.get('logging', {}).get('level', 'Unknown') + + logger.info("\n🐱 SenSu 已就绪!") + logger.info(f"当前 SenSu 版本号 {version}") + logger.info(f"当前日志级别 {logLevel}") + + panel_path = base_config.get('panel', {}).get('entrance', {}).get('path', 'panel') + + # 显示欢迎消息 + tui_service = self.service_manager.get_service("tui") + if hasattr(tui_service, 'show_message'): + tui_service.show_message("🐱 DreamSu 框架 已就绪!\n", "info") + tui_service.show_message("====================================\n", "info") + tui_service.show_message("🐱 SenSu 已就绪!", "info") + tui_service.show_message(f"当前 SenSu 版本号 {version}", "info") + tui_service.show_message(f"当前日志级别 {logLevel}\n", "info") + tui_service.show_message("====================================\n", "info") + tui_service.show_message(f"当前API配置地址", "debug") + tui_service.show_message(f"地址:http://{internet_service.http_host}:{internet_service.http_port}", "debug") + tui_service.show_message("====================================\n", "debug") + tui_service.show_message(f"🌐 Web 面板配置地址: http://{internet_service.http_host}:{internet_service.http_port}{panel_path}", "info") + + else: + print("🐱 SenSu 已就绪!输入 'help' 查看可用命令") + + except Exception as e: + logger.error(f"框架初始化失败: {str(e)}", exc_info=True) + await self._safe_shutdown() + raise + + + def _create_fallback_tui(self): + """创建回退的TUI服务(命令行模式)""" + class FallbackTuiService: + def __init__(self): + self.is_running = True + + def show_message(self, message: str, msg_type: str = "info", persistent: bool = False): + """显示消息到控制台""" + prefix = { + "info": "ℹ️", + "warning": "⚠️", + "error": "❌", + "success": "✅" + }.get(msg_type, "📝") + print(f"{prefix} {message}") + + async def start(self): + """启动回退TUI""" + print("🐱 使用命令行模式...") + return True + + def shutdown(self): + """关闭回退TUI""" + self.is_running = False + + return FallbackTuiService() + + async def _safe_shutdown(self): + """安全关闭,即使服务未完全初始化""" + try: + logger.info("执行安全关闭") + self.is_running = False + + # 尝试获取关闭服务 + try: + shutdown_service = self.service_manager.get_service("shutdown") + if shutdown_service: + await shutdown_service.initiate_shutdown("安全关闭") + return + except: + pass + + # 如果关闭服务不可用,手动关闭其他服务 + services_to_shutdown = ['plugin', 'core_bridge', 'plugin_bridge', 'tui', 'log'] + for service_name in services_to_shutdown: + try: + service = self.service_manager.get_service(service_name) + if service and hasattr(service, 'shutdown'): + await service.shutdown() if asyncio.iscoroutinefunction(service.shutdown) else service.shutdown() + except: + pass + + logger.debug("安全关闭完成") + + except Exception as e: + print(f"❌ 安全关闭时出错: {e}") + + async def _framework_shutdown_handler(self): + """框架关闭处理器""" + try: + logger.info("执行框架关闭处理") + self.is_running = False + + # 关闭插件服务 + plugin_service = self.service_manager.get_service("plugin") + if plugin_service: + # 卸载所有插件 + for plugin_name in list(plugin_service.plugins.keys()): + await plugin_service.unload_plugin(plugin_name) + + # 关闭桥接服务 + core_bridge = self.service_manager.get_service("core_bridge") + if core_bridge: + await core_bridge.shutdown() + + plugin_bridge = self.service_manager.get_service("plugin_bridge") + if plugin_bridge: + await plugin_bridge.shutdown() + + logger.debug("框架关闭处理完成") + + except Exception as e: + logger.error(f"框架关闭处理时出错: {str(e)}", exc_info=True) + + async def run(self): + """运行框架主循环""" + try: + logger.info("进入框架主循环") + + # 特殊终端,添加命令行输入处理 + if not hasattr(self.service_manager.get_service("tui"), 'tui_app'): + await self._run_cli_mode() + else: + # 原有的TUI模式 + while self.is_running: + try: + await asyncio.sleep(1) + except asyncio.CancelledError: + logger.info("主循环被取消") + break + except Exception as e: + logger.error(f"主循环运行时出错: {str(e)}", exc_info=True) + await asyncio.sleep(5) + + logger.info("框架主循环结束") + + except Exception as e: + logger.error(f"运行框架主循环时出错: {str(e)}", exc_info=True) + await self._safe_shutdown() + + async def _run_cli_mode(self): + """运行命令行模式""" + try: + print("🐱 进入命令行模式,输入 'exit' 退出") + + command_service = self.service_manager.get_service("command") + + while self.is_running: + try: + # 读取用户输入 + user_input = await asyncio.get_event_loop().run_in_executor( + None, input, "🐱 > " + ) + + if user_input.strip().lower() in ('exit', 'quit', 'q'): + await self.shutdown() + break + + # 处理命令 + if user_input.strip(): + result = await command_service.process_command(user_input, "cli") + print(f"📝 {result}") + + except (KeyboardInterrupt, EOFError): + print("\n🐱 接收到退出信号") + await self.shutdown() + break + except Exception as e: + print(f"❌ 命令处理错误: {str(e)}") + + except Exception as e: + logger.error(f"命令行模式运行时出错: {str(e)}", exc_info=True) + await self._safe_shutdown() + + async def shutdown(self): + """关闭框架""" + try: + logger.info("开始关闭框架") + self.is_running = False + + # 通过关闭服务发起优雅关闭 + shutdown_service = self.service_manager.get_service("shutdown") + if shutdown_service: + await shutdown_service.initiate_shutdown("手动关闭") + else: + await self._safe_shutdown() + + except Exception as e: + logger.error(f"关闭框架时出错: {str(e)}", exc_info=True) + await self._safe_shutdown() + +async def main(): + """主函数""" + framework = CatFramework() + + try: + # 初始化框架 + await framework.initialize() + + # 运行主循环 + await framework.run() + + except KeyboardInterrupt: + print("\n🐱 接收到键盘中断") + await framework.shutdown() + except Exception as e: + print(f"🐱 框架运行出错: {str(e)}") + await framework._safe_shutdown() + sys.exit(1) + +if __name__ == "__main__": + try: + # 设置更详细的异常处理 + import signal + + def signal_handler(signum, frame): + """信号处理""" + print(f"\n🐱 接收到信号 {signum},正在关闭...") + sys.exit(0) + + # 注册信号处理 + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + print("\n🐱 主程序启动...") + # 运行主程序 + asyncio.run(main()) + + except KeyboardInterrupt: + print("\n🐱 接收到键盘中断,关闭...") + print("🐱 再见喵~") + sys.exit(0) + except SystemExit as e: + # 优雅处理SystemExit + exit_code = e.code if e.code is not None else 0 + if exit_code == 0: + print("🐱 框架已关闭") + else: + print(f"🐱 框架退出,代码: {exit_code}") + sys.exit(exit_code) + except Exception as e: + print(f"🐱 框架运行异常: {str(e)}") + import traceback + traceback.print_exc() + sys.exit(1) + finally: + print("🐱 框架进程结束") diff --git a/plugins/example_plugin/__init__.py b/plugins/example_plugin/__init__.py index f8a1dba..9068fec 100644 --- a/plugins/example_plugin/__init__.py +++ b/plugins/example_plugin/__init__.py @@ -14,8 +14,8 @@ except ImportError: logger=logging.getLogger(__name__) class Plugin: - def __init__(self,n,c,bridge): - self.plugin_name=n;self.config=c;self.bridge=bridge + def __init__(self, plugin_name=None, config=None, bridge=None, n=None, c=None): + self.plugin_name=plugin_name or n;self.config=config or c;self.bridge=bridge self.network_bridge=None;self.is_running=False async def initialize(self): diff --git a/services/plugin_service.py b/services/plugin_service.py index 4e12b90..e6145d5 100644 --- a/services/plugin_service.py +++ b/services/plugin_service.py @@ -12,8 +12,6 @@ from dataclasses import dataclass import yaml import traceback from sdk.plugin_command_decorator import plugin_command, command -from sdk.plugin_status import PluginStatus -from sdk.plugin_error import PluginConfigError, PluginLoadError logger = logging.getLogger(__name__) @@ -41,7 +39,6 @@ class PluginService: self.service_manager = service_manager # 新增服务管理器 self.bridge_service.service_manager = self.service_manager self.plugins: Dict[str, Any] = {} - self.plugin_status: Dict[str, Any] = {} self.plugin_info: Dict[str, PluginInfo] = {} self.plugins_dir = Path("plugins") self.is_running = False @@ -211,8 +208,7 @@ class PluginService: commands=plugin_commands ) - self.plugin_status[plugin_name] = PluginStatus.RUNNING - logger.info(f"插件加载成功: {plugin_name} v{plugin_config['version']}, 注册了 {len(plugin_commands)} 个命令") + logger.info(f"插件加载成功: {plugin_name} v{plugin_config['version']}, 注册了 {len(plugin_commands)} 个命令") return True except Exception as e: diff --git a/services/sysmon_widget.py b/services/sysmon_widget.py index d970c2c..c77f36f 100644 --- a/services/sysmon_widget.py +++ b/services/sysmon_widget.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 """SenSu TUI 系统监控组件 — CPU/内存/磁盘/插件状态""" -import psutil, time, asyncio, logging +try: + import psutil +except ImportError: + psutil = None +import time, asyncio, logging from textual.widgets import Static from textual.reactive import reactive diff --git a/services/tui_service.py b/services/tui_service.py index 2691dad..b755c70 100644 --- a/services/tui_service.py +++ b/services/tui_service.py @@ -9,7 +9,6 @@ from textual.app import App from textual.containers import Container, ScrollableContainer from textual.widgets import Static, Input, Header, Footer from textual.reactive import reactive -from services.sysmon_widget import SysMonWidget from typing import List, Dict import asyncio from datetime import datetime @@ -659,42 +658,6 @@ class TUIFramework(App): except Exception as e: print(f"❌ 关闭TUI时出错: {str(e)}") - - def _start_plugin_status_refresh(self): - async def refresh(): - while True: - try: - ps = self.service_manager.get_service("plugin") - if ps and hasattr(ps, "plugin_status"): - lines = [] - for name, status in ps.plugin_status.items(): - icon = {"running":"🟢","error":"🔴","loading":"🟡","unloaded":"⚫"}.get(str(status),"⚪") - lines.append(f"{icon} {name}: {status}") - widget = self.tui_app.query_one(SysMonWidget) - widget.update_plugins("\n".join(lines) if lines else "无已加载插件") - except: pass - await asyncio.sleep(3) - import asyncio - asyncio.create_task(refresh()) - - - def _get_suggester(self): - from textual.suggester import Suggester - class CmdSuggester(Suggester): - async def get_suggestion(self, value): - try: - cs = self.app.query_one("#input-area").app - names = ["help","status","history","testlog","netdiag", - "permissions","pmpending","pmhelp","echo","plugin_status", - "scroll","autoscroll","create-plugin","exit","quit"] - if not value: return None - for n in names: - if n.startswith(value) and n != value: - return n - except: pass - return None - return CmdSuggester() - class TuiService: """TUI服务"""