import os
import sys
import threading
import time
from pathlib import Path
from oss.logger.logger import Log
from oss.plugin.types import Plugin, Response, register_plugin_type
from oss.config import get_config
from oss.tui.converter import TUIManager, TUIRenderer, HTMLToTUIConverter
class TUIPlugin(Plugin):
self.webui = webui
def set_http_api(self, http_api):
Log.info("tui", "TUI 插件初始化中...")
config = get_config()
width = config.get("TUI_WIDTH", 80)
height = config.get("TUI_HEIGHT", 24)
self.tui_manager = TUIManager.get_instance(width, height)
if self.http_api and self.http_api.router:
self.http_api.router.get("/tui/index.html", self._handle_tui_index)
self.http_api.router.get("/tui/page", self._handle_tui_page)
self.http_api.router.get("/tui/css", self._handle_tui_css)
self.http_api.router.get("/tui/js", self._handle_tui_js)
self.http_api.router.post("/tui/interact", self._handle_tui_interact)
self.http_api.router.get("/tui/pages", self._handle_tui_pages)
Log.ok("tui", "已注册 TUI API 路由 (/tui/*)")
else:
Log.warn("tui", "警告:未找到 http-api 依赖")
self._load_default_pages()
Log.ok("tui", "TUI 插件初始化完成 - 强大的转换层已就绪")
def _load_default_pages(self):
此方法模拟访问 WebUI 页面并获取 HTML,然后由 TUI 转换层解析。
WebUI 开放的 /tui 接口会返回带有特殊标记的 HTML,不含用户可见内容,
但包含 data-tui-* 属性和 script[type='application/x-tui-*'] 配置。
if not self.webui or not hasattr(self.webui, 'server'):
return ""
try:
from oss.plugin.types import Request
request = Request(method="GET", path=path, headers={}, body="")
router = self.webui.server.router
if hasattr(router, 'routes'):
for route_path, handler in router.routes.items():
if route_path == path or (route_path.endswith('*') and path.startswith(route_path[:-1])):
response = handler(request)
if response and hasattr(response, 'body'):
return response.body.decode('utf-8') if isinstance(response.body, bytes) else response.body
except Exception as e:
Log.debug("tui", f"获取 WebUI 页面失败:{e}")
return ""
def start(self):
try:
self._show_welcome()
self._event_loop()
except Exception as e:
Log.error("tui", f"TUI 循环异常:{e}")
finally:
self.running = False
def _show_welcome(self):
NebulaShell TUI
- [1] 首页
- [2] 仪表盘
- [3] 日志
- [4] 终端
- [5] 插件管理
- [q] 退出 TUI
- [r] 刷新
self.tui_manager.load_page("/welcome", welcome_html)
self._render_current("/welcome")
def _render_current(self, path: str = None):
import sys
import tty
import termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
while self.running:
char = sys.stdin.read(1)
if char == '\x03': break
elif char == '\x04': break
elif char == 'q':
Log.info("tui", "用户退出 TUI")
break
elif char == '1':
self._render_current("/")
elif char == '2':
self._render_current("/dashboard")
elif char == '3':
self._render_current("/logs")
elif char == '4':
self._render_current("/terminal")
elif char == '5':
self._render_current("/plugins")
elif char == 'r':
self._load_default_pages()
self._render_current()
elif char == '\n' or char == '\r':
self._render_current()
except Exception as e:
Log.error("tui", f"事件循环错误:{e}")
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
def _handle_tui_index(self, request):
html =
return Response(
status=200,
headers={"Content-Type": "text/html; charset=utf-8"},
body=html
)
def _handle_tui_page(self, request):
from urllib.parse import parse_qs, urlparse
parsed = urlparse(request.path)
params = parse_qs(parsed.query)
page_path = params.get('path', ['/'])[0]
html = self._fetch_webui_page(page_path)
if html:
html = html.replace('', '')
return Response(
status=200,
headers={"Content-Type": "text/html; charset=utf-8"},
body=html
)
else:
error_html =
return Response(
status=404,
headers={"Content-Type": "text/html; charset=utf-8"},
body=error_html
)
def _handle_tui_css(self, request):
css = // TUI JS 模拟配置
// 仅支持基础交互功能
const TUI = {
// 鼠标支持
mouse: {
enabled: true,
getPosition: () => ({ x: 0, y: 0 }),
onClick: (handler) => {},
},
// 键盘支持
keyboard: {
enabled: true,
onKeyPress: (handler) => {},
bindings: {},
},
// DOM 操作(简化版)
querySelector: (selector) => null,
querySelectorAll: (selector) => [],
// 事件系统
addEventListener: (event, handler) => {},
removeEventListener: (event, handler) => {},
};
// 导出配置
export default TUI;
return Response(
status=200,
headers={"Content-Type": "application/javascript"},
body=js_config
)
def _handle_tui_interact(self, request):
import json
pages = []
if self.webui and hasattr(self.webui, 'server'):
router = self.webui.server.router
if hasattr(router, 'routes'):
pages = list(router.routes.keys())
return Response(
status=200,
headers={"Content-Type": "application/json"},
body=json.dumps({
'success': True,
'pages': pages,
'current': self.tui_manager.current_page if self.tui_manager else None
})
)
def wait_for_exit(self):
Log.info("tui", "TUI 停止中...")
self.running = False
if self.tui_thread:
self.tui_thread.join(timeout=2)
Log.ok("tui", "TUI 已停止")
register_plugin_type("TUIPlugin", TUIPlugin)
def New():
return TUIPlugin()