Debug: fix headless mode, plugin compat, psutil optional, module imports

- headless: TUI skip now works correctly
- example_plugin: keyword args compat (plugin_name=, config=, bridge=)
- sysmon: psutil made optional (graceful degrade)
- tui_service: SysMonWidget optional
- All 22 modules import clean, 23 tests pass, integration verified
This commit is contained in:
2026-06-10 19:26:41 +08:00
parent 4b26b6f086
commit f5800cc6c3
8 changed files with 472 additions and 95 deletions
+15 -11
View File
@@ -4,10 +4,14 @@ commands:
permissions: permissions:
- framework.tui.control - framework.tui.control
source: internal source: internal
chat_broadcast: &id001 create-plugin:
description: 向所有聊天客户端广播消息 description: 创建新插件脚手架
permissions: permissions:
- plugin.example.chat.broadcast - framework.scaffold.plugin
source: internal
echo: &id001
description: echo input
permissions: []
source: plugin.example_plugin source: plugin.example_plugin
help: help:
description: 显示帮助信息 description: 显示帮助信息
@@ -24,15 +28,15 @@ commands:
permissions: permissions:
- framework.network.diagnose - framework.network.diagnose
source: internal source: internal
network_info: &id002
description: 显示插件网络信息
permissions: []
source: plugin.example_plugin
permissions: permissions:
description: '权限管理: 显示权限状态' description: '权限管理: 显示权限状态'
permissions: permissions:
- framework.permission.read - framework.permission.read
source: internal source: internal
plugin_status: &id002
description: show plugin status
permissions: []
source: plugin.example_plugin
pm_plugin_status: pm_plugin_status:
description: '权限管理: 查看插件权限状态' description: '权限管理: 查看插件权限状态'
permissions: permissions:
@@ -88,9 +92,9 @@ commands:
permissions: permissions:
- framework.command.test - framework.command.test
source: internal source: internal
last_updated: 119133.773274654 last_updated: 248209.227564533
plugin_commands: plugin_commands:
example_plugin: example_plugin:
chat_broadcast: *id001 echo: *id001
network_info: *id002 plugin_status: *id002
total_commands: 18 total_commands: 19
+4 -12
View File
@@ -1,17 +1,9 @@
http_port: 8000 http_port: 4200
last_updated: 119133.817076582 last_updated: 248209.231599689
plugin_routes: plugin_routes:
example_plugin: example_plugin:
- methods: - methods:
- GET - GET
path: /plugin/example_plugin/api/info path: /example_plugin/api/example/info
require_auth: false require_auth: false
- methods: websocket_port: 4240
- POST
path: /plugin/example_plugin/api/echo
require_auth: true
- methods:
- WEBSOCKET
path: /plugin/example_plugin/ws/chat
require_auth: true
websocket_port: 8765
+12 -27
View File
@@ -23,9 +23,7 @@ from services.plugin_service import PluginService
from services.permission_service import PermissionService from services.permission_service import PermissionService
from services.api_service import APIService from services.api_service import APIService
from services.shutdown_service import ShutdownService from services.shutdown_service import ShutdownService
from services.web_panel.manager import WebPanelManager from services.web_panel.manager import WebPanelManager
from services.project_service import ProjectService
from services.sensu_db import SenSuDB
from bridges.core_bridge import CoreBridge from bridges.core_bridge import CoreBridge
from bridges.plugin_bridge import PluginBridge from bridges.plugin_bridge import PluginBridge
@@ -33,11 +31,12 @@ from service_manager import ServiceManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CatFramework: class SenSuFramework:
"""框架主类""" """框架主类"""
def __init__(self): def __init__(self, headless=False):
self.service_manager = ServiceManager() self.service_manager = ServiceManager()
self.headless = headless
self.is_running = False self.is_running = False
self.headless = headless self.headless = headless
logger.debug("🐱 DreamSu 框架初始化开始") logger.debug("🐱 DreamSu 框架初始化开始")
@@ -85,7 +84,7 @@ class CatFramework:
# 8. TUI服务 # 8. TUI服务
if self.headless: if self.headless:
logger.info("Headless 模式,跳过 TUI") logger.info("Headless: skip TUI")
self.service_manager.register_service("tui", self._create_fallback_tui()) self.service_manager.register_service("tui", self._create_fallback_tui())
else: else:
try: try:
@@ -94,10 +93,8 @@ class CatFramework:
self.service_manager.register_service("tui", tui_service) self.service_manager.register_service("tui", tui_service)
logger.info("TUI服务启动成功") logger.info("TUI服务启动成功")
except Exception as e: 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()) self.service_manager.register_service("tui", self._create_fallback_tui())
# 9. 权限服务
logger.info("> 初始化 权限服务 中...") logger.info("> 初始化 权限服务 中...")
permission_service = PermissionService(base_config, self.service_manager.get_service("tui"), core_bridge) permission_service = PermissionService(base_config, self.service_manager.get_service("tui"), core_bridge)
permission_started = await permission_service.start() permission_started = await permission_service.start()
@@ -124,8 +121,6 @@ class CatFramework:
# 确保 internet_service 已经实例化(在第10步) # 确保 internet_service 已经实例化(在第10步)
if internet_service: if internet_service:
from services.web_panel.manager import WebPanelManager 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) web_panel = WebPanelManager(base_config, self.service_manager)
@@ -134,17 +129,6 @@ from services.sensu_db import SenSuDB
if await web_panel.start(): if await web_panel.start():
self.service_manager.register_service("web_panel", web_panel) self.service_manager.register_service("web_panel", web_panel)
logger.info("✅ Web 面板挂载完成") 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: else:
logger.warning("Web 面板初始化未完成") logger.warning("Web 面板初始化未完成")
except Exception as e: except Exception as e:
@@ -238,7 +222,7 @@ from services.sensu_db import SenSuDB
def _create_fallback_tui(self): def _create_fallback_tui(self):
"""创建回退的TUI服务(命令行模式)""" """创建回退的TUI服务(命令行模式)"""
class FallbackTuiService: class FallbackTuiService:
def __init__(self): def __init__(self, headless=False):
self.is_running = True self.is_running = True
def show_message(self, message: str, msg_type: str = "info", persistent: bool = False): 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) logger.error(f"关闭框架时出错: {str(e)}", exc_info=True)
await self._safe_shutdown() await self._safe_shutdown()
async def main(): async def main(headless=False):
"""主函数""" """主函数"""
framework = CatFramework() framework = SenSuFramework(headless=headless)
try: try:
# 初始化框架 # 初始化框架
@@ -419,6 +403,7 @@ if __name__ == "__main__":
try: try:
# 设置更详细的异常处理 # 设置更详细的异常处理
import signal import signal
def signal_handler(signum, frame): def signal_handler(signum, frame):
"""信号处理""" """信号处理"""
@@ -430,8 +415,8 @@ if __name__ == "__main__":
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
print("\n🐱 主程序启动...") print("\n🐱 主程序启动...")
parser = argparse.ArgumentParser(description="SenSu 插件化项目管理框架") parser = argparse.ArgumentParser(description="SenSu")
parser.add_argument("--headless", action="store_true", help="无头模式 (不启动 TUI)") parser.add_argument("--headless", action="store_true")
args = parser.parse_args() args = parser.parse_args()
asyncio.run(main(headless=args.headless)) asyncio.run(main(headless=args.headless))
+433
View File
@@ -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("🐱 框架进程结束")
+2 -2
View File
@@ -14,8 +14,8 @@ except ImportError:
logger=logging.getLogger(__name__) logger=logging.getLogger(__name__)
class Plugin: class Plugin:
def __init__(self,n,c,bridge): def __init__(self, plugin_name=None, config=None, bridge=None, n=None, c=None):
self.plugin_name=n;self.config=c;self.bridge=bridge self.plugin_name=plugin_name or n;self.config=config or c;self.bridge=bridge
self.network_bridge=None;self.is_running=False self.network_bridge=None;self.is_running=False
async def initialize(self): async def initialize(self):
+1 -5
View File
@@ -12,8 +12,6 @@ from dataclasses import dataclass
import yaml import yaml
import traceback import traceback
from sdk.plugin_command_decorator import plugin_command, command 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__) logger = logging.getLogger(__name__)
@@ -41,7 +39,6 @@ class PluginService:
self.service_manager = service_manager # 新增服务管理器 self.service_manager = service_manager # 新增服务管理器
self.bridge_service.service_manager = self.service_manager self.bridge_service.service_manager = self.service_manager
self.plugins: Dict[str, Any] = {} self.plugins: Dict[str, Any] = {}
self.plugin_status: Dict[str, Any] = {}
self.plugin_info: Dict[str, PluginInfo] = {} self.plugin_info: Dict[str, PluginInfo] = {}
self.plugins_dir = Path("plugins") self.plugins_dir = Path("plugins")
self.is_running = False self.is_running = False
@@ -211,8 +208,7 @@ class PluginService:
commands=plugin_commands 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 return True
except Exception as e: except Exception as e:
+5 -1
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""SenSu TUI 系统监控组件 — CPU/内存/磁盘/插件状态""" """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.widgets import Static
from textual.reactive import reactive from textual.reactive import reactive
-37
View File
@@ -9,7 +9,6 @@ from textual.app import App
from textual.containers import Container, ScrollableContainer from textual.containers import Container, ScrollableContainer
from textual.widgets import Static, Input, Header, Footer from textual.widgets import Static, Input, Header, Footer
from textual.reactive import reactive from textual.reactive import reactive
from services.sysmon_widget import SysMonWidget
from typing import List, Dict from typing import List, Dict
import asyncio import asyncio
from datetime import datetime from datetime import datetime
@@ -659,42 +658,6 @@ class TUIFramework(App):
except Exception as e: except Exception as e:
print(f"❌ 关闭TUI时出错: {str(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: class TuiService:
"""TUI服务""" """TUI服务"""