Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/cli/tui/app.py
T

1071 lines
30 KiB
Python

import asyncio
from collections import OrderedDict
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import datetime
from time import monotonic
from textual.app import App
from textual.css.query import NoMatches
from cli.tui.adapters import (
TUI_SNAPSHOT_COLLECTOR_REGISTRY,
BrowserPageSnapshot,
DashboardSnapshot,
DetailPageSnapshot,
DetailSectionSnapshot,
)
from cli.tui.copy import TUI_COPY
from cli.tui.keymaps import TUI_KEYMAP_REGISTRY
from cli.tui.queries import TUI_PAGE_TIMEOUT_SECONDS
from cli.tui.screens import BrowserScreen, DashboardScreen, DetailScreen
from cli.tui.widgets import NavigationItem, WorkspaceSidebar
PageSnapshot = BrowserPageSnapshot | DashboardSnapshot | DetailPageSnapshot
SnapshotCollector = Callable[['RuoyiTuiApp'], Awaitable[PageSnapshot]]
TUI_NAVIGATION_DEBOUNCE_SECONDS = 0.075
TUI_SNAPSHOT_CACHE_TTL_SECONDS = 15.0
TUI_SNAPSHOT_CACHE_MAX_ENTRIES = 18
@dataclass(frozen=True)
class TuiViewSpec:
"""
TUI 视图规格定义。
:param view_key: 视图标识
:param include_query: 是否注入搜索词
:param include_filter: 是否注入筛选键
"""
view_key: str
include_query: bool = False
include_filter: bool = False
@dataclass(frozen=True)
class TuiViewDefinition:
"""
TUI 视图定义。
:param view_key: 视图标识
:param collector: 页面快照采集器
"""
view_key: str
collector: SnapshotCollector
@dataclass(frozen=True)
class TuiViewRegistry:
"""
TUI 视图注册表。
该对象集中维护视图打开、刷新与顺序导航所需的元数据,
避免 `RuoyiTuiApp` 内继续手工维护多份视图映射。
:param definitions: 视图定义映射
:param navigation_items: 导航项列表
"""
definitions: dict[str, TuiViewDefinition]
navigation_items: list[NavigationItem]
@staticmethod
def normalize_view_key(view_key: str) -> str:
"""
规范化视图标识。
:param view_key: 原始视图标识
:return: 规范化后的视图标识
"""
return str(view_key).strip().lower()
def resolve_view_key(self, view_key: str) -> str:
"""
解析可用视图标识,不存在时回退到 dashboard。
:param view_key: 原始视图标识
:return: 可用视图标识
"""
normalized_view_key = self.normalize_view_key(view_key)
if normalized_view_key in self.definitions:
return normalized_view_key
return 'dashboard'
async def collect_snapshot(self, app: 'RuoyiTuiApp', view_key: str) -> PageSnapshot:
"""
采集指定视图的页面快照。
:param app: 当前 TUI 应用实例
:param view_key: 目标视图标识
:return: 页面快照
"""
resolved_view_key = self.resolve_view_key(view_key)
return await self.definitions[resolved_view_key].collector(app)
def get_navigation_index(self, view_key: str) -> int:
"""
获取视图在导航列表中的索引。
:param view_key: 目标视图标识
:return: 导航索引
"""
resolved_view_key = self.resolve_view_key(view_key)
return next(
(index for index, item in enumerate(self.navigation_items) if item.view_key == resolved_view_key),
0,
)
def get_relative_view_key(self, view_key: str, offset: int) -> str:
"""
获取相对当前视图偏移后的目标视图标识。
:param view_key: 当前视图标识
:param offset: 偏移量
:return: 目标视图标识
"""
current_index = self.get_navigation_index(view_key)
target_index = (current_index + offset) % len(self.navigation_items)
return self.navigation_items[target_index].view_key
_TUI_VIEW_SPECS: tuple[TuiViewSpec, ...] = (
TuiViewSpec('dashboard'),
TuiViewSpec('app', include_query=True),
TuiViewSpec('ops', include_query=True),
TuiViewSpec('database', include_query=True),
TuiViewSpec('cache', include_query=True),
TuiViewSpec('jobs', include_query=True, include_filter=True),
TuiViewSpec('gen', include_query=True),
TuiViewSpec('configs', include_query=True, include_filter=True),
TuiViewSpec('crypto', include_query=True),
)
_TUI_APP_CSS = """
Screen {
layout: vertical;
background: #04080f;
color: #d8f7ff;
scrollbar-size-horizontal: 1;
scrollbar-size-vertical: 1;
scrollbar-background: #06111b;
scrollbar-background-hover: #0a1723;
scrollbar-background-active: #0d1f2d;
scrollbar-color: #1f6f8d;
scrollbar-color-hover: #38d8ff;
scrollbar-color-active: #6df7ff;
}
Widget {
scrollbar-size-horizontal: 1;
scrollbar-size-vertical: 1;
scrollbar-background: #06111b;
scrollbar-background-hover: #0a1723;
scrollbar-background-active: #0d1f2d;
scrollbar-color: #1f6f8d;
scrollbar-color-hover: #38d8ff;
scrollbar-color-active: #6df7ff;
}
#workspace-shell {
height: 1fr;
background: #050c15;
}
#workspace-main {
padding: 1 2 1 1;
background: #050c15;
border: round #18425d;
margin: 1 1 1 0;
overflow-x: auto;
overflow-y: auto;
scrollbar-size-horizontal: 1;
scrollbar-size-vertical: 1;
scrollbar-background: #06111b;
scrollbar-background-hover: #0a1723;
scrollbar-background-active: #0d1f2d;
scrollbar-color: #1f6f8d;
scrollbar-color-hover: #38d8ff;
scrollbar-color-active: #6df7ff;
}
#workspace-main:focus {
border: heavy #6df7ff;
background: #071320;
}
#detail-body {
height: auto;
margin-top: 1;
border: double #18425d;
background: #06111b;
padding: 1;
overflow-x: auto;
overflow-y: auto;
}
#browser-body {
height: auto;
margin-top: 1;
border: double #18425d;
background: #06111b;
padding: 1;
overflow-x: auto;
overflow-y: auto;
}
#browser-filter-bar {
border: round #163c57;
padding: 1 2;
margin-bottom: 1;
background: #07131f;
color: #d9fbff;
}
#browser-detail-pane {
width: 1fr;
min-width: 48;
border: round #163c57;
padding: 1;
background: #07131f;
overflow-x: auto;
overflow-y: auto;
}
#browser-detail-pane.is-ok {
border: round #22c983;
}
#browser-detail-pane.is-fail {
border: round #ff6b7a;
background: #190d12;
}
#browser-detail-pane.is-warn {
border: round #f2b84b;
background: #171108;
}
#browser-detail-pane.is-info {
border: round #38d8ff;
}
#browser-section-body {
height: auto;
border-top: solid #163c57;
padding-top: 1;
background: #06111b;
overflow-x: auto;
overflow-y: auto;
}
#dashboard-metrics {
grid-size: 4;
grid-gutter: 1 2;
grid-columns: 1fr 1fr 1fr 1fr;
height: auto;
margin-top: 1;
margin-bottom: 1;
padding: 1 0;
}
#dashboard-grid, #detail-grid {
grid-size: 2;
grid-gutter: 1 2;
grid-columns: 1fr 1fr;
height: auto;
margin-top: 1;
}
#detail-grid {
margin-bottom: 1;
}
Footer {
background: #060d16;
color: #b6efff;
border-top: heavy #163c57;
}
FooterKey {
background: #0b2130;
color: #ecfcff;
}
"""
@dataclass(frozen=True)
class TuiBindingSpec:
"""
TUI 快捷键绑定定义。
:param key: 快捷键
:param action: 动作名称
:param label: 展示标签
"""
key: str
action: str
label: str
class TuiBindingBuilder:
"""
TUI 快捷键绑定构建器。
该对象负责统一装配应用级固定绑定和视图跳转绑定,避免 `RuoyiTuiApp`
继续手工维护重复的绑定元组列表。
"""
def build_base_bindings(self) -> list[TuiBindingSpec]:
"""
构建应用级固定绑定。
:return: 绑定定义列表
"""
return [
TuiBindingSpec('q', 'quit', TUI_COPY.build_app_binding_label('quit')),
TuiBindingSpec('s', 'focus_sidebar', TUI_COPY.build_app_binding_label('sidebar')),
TuiBindingSpec('[', 'show_previous_view', TUI_COPY.build_app_binding_label('previous')),
TuiBindingSpec(']', 'show_next_view', TUI_COPY.build_app_binding_label('next')),
TuiBindingSpec('r', 'refresh_current_view', TUI_COPY.build_app_binding_label('refresh')),
]
def build_navigation_bindings(self) -> list[TuiBindingSpec]:
"""
构建页面导航绑定。
:return: 绑定定义列表
"""
return [
TuiBindingSpec(
TUI_KEYMAP_REGISTRY.get_navigation_shortcut(spec.view_key),
f'show_{spec.view_key}',
TUI_COPY.render_view_label(spec.view_key),
)
for spec in _TUI_VIEW_SPECS
]
def build(self) -> list[tuple[str, str, str]]:
"""
构建 Textual 所需的绑定元组列表。
:return: 绑定元组列表
"""
return [
(binding.key, binding.action, binding.label)
for binding in [*self.build_base_bindings(), *self.build_navigation_bindings()]
]
TUI_APP_BINDINGS = TuiBindingBuilder().build()
@dataclass(frozen=True)
class TuiViewRegistryBuilder:
"""
TUI 视图注册表构建器。
该对象负责根据统一规格构建导航项和快照采集定义,避免模块内
继续维护重复的 `lambda collect(...)` 配置块。
"""
def build_navigation_items(self) -> list[NavigationItem]:
"""
构建工作台导航项列表。
:return: 导航项列表
"""
return [
NavigationItem(
view_key,
TUI_COPY.render_view_label(view_key),
shortcut,
TUI_COPY.render_navigation_description(view_key),
)
for view_key, shortcut in TUI_KEYMAP_REGISTRY.navigation_shortcuts.items()
]
def build_collector(self, spec: TuiViewSpec) -> SnapshotCollector:
"""
根据视图规格构建页面快照采集器。
:param spec: 视图规格
:return: 页面快照采集器
"""
async def collect(app: 'RuoyiTuiApp') -> PageSnapshot:
collect_kwargs: dict[str, str] = {}
if spec.include_filter:
collect_kwargs['filter_key'] = app.get_browser_filter(spec.view_key)
if spec.include_query:
collect_kwargs['query'] = app.get_browser_query(spec.view_key)
return await TUI_SNAPSHOT_COLLECTOR_REGISTRY.collect(spec.view_key, app.env, **collect_kwargs)
return collect
def build_definitions(self) -> dict[str, TuiViewDefinition]:
"""
构建视图定义映射。
:return: 视图定义映射
"""
return {spec.view_key: TuiViewDefinition(spec.view_key, self.build_collector(spec)) for spec in _TUI_VIEW_SPECS}
def build(self) -> TuiViewRegistry:
"""
构建 TUI 视图注册表。
:return: 视图注册表
"""
return TuiViewRegistry(
definitions=self.build_definitions(),
navigation_items=self.build_navigation_items(),
)
@dataclass
class TuiViewStateStore:
"""
TUI 视图状态存储。
该对象集中维护页面级动作反馈、浏览页筛选键和搜索词,避免
`RuoyiTuiApp` 本体继续直接操作多份同类字典状态。
:param action_feedback_by_view: 动作反馈映射
:param browser_filter_by_view: 浏览页筛选映射
:param browser_query_by_view: 浏览页搜索词映射
"""
action_feedback_by_view: dict[str, list[str]]
browser_filter_by_view: dict[str, str]
browser_query_by_view: dict[str, str]
@classmethod
def create_default(cls) -> 'TuiViewStateStore':
"""
创建默认视图状态存储。
:return: 视图状态存储
"""
return cls(
action_feedback_by_view={},
browser_filter_by_view={'jobs': 'all', 'configs': 'all'},
browser_query_by_view={},
)
def remember_action_feedback(self, view_key: str, lines: list[str]) -> None:
"""
记录指定页面最近一次动作反馈。
:param view_key: 页面视图标识
:param lines: 反馈文本行
:return: None
"""
self.action_feedback_by_view[view_key] = list(lines)
def get_action_feedback_lines(self, view_key: str) -> list[str]:
"""
读取指定页面最近一次动作反馈。
:param view_key: 页面视图标识
:return: 反馈文本行
"""
return list(self.action_feedback_by_view.get(view_key, []))
def remember_browser_filter(self, view_key: str, filter_key: str) -> None:
"""
记录指定浏览页当前筛选键。
:param view_key: 页面视图标识
:param filter_key: 当前筛选键
:return: None
"""
self.browser_filter_by_view[view_key] = filter_key
def get_browser_filter(self, view_key: str, default: str = 'all') -> str:
"""
读取指定浏览页当前筛选键。
:param view_key: 页面视图标识
:param default: 默认筛选键
:return: 当前筛选键
"""
return str(self.browser_filter_by_view.get(view_key, default) or default)
def remember_browser_query(self, view_key: str, query: str) -> None:
"""
记录指定浏览页当前搜索词。
:param view_key: 页面视图标识
:param query: 当前搜索词
:return: None
"""
normalized_query = str(query or '').strip()
if normalized_query:
self.browser_query_by_view[view_key] = normalized_query
return
self.browser_query_by_view.pop(view_key, None)
def get_browser_query(self, view_key: str, default: str = '') -> str:
"""
读取指定浏览页当前搜索词。
:param view_key: 页面视图标识
:param default: 默认搜索词
:return: 当前搜索词
"""
return str(self.browser_query_by_view.get(view_key, default) or default)
TUI_VIEW_REGISTRY = TuiViewRegistryBuilder().build()
NAVIGATION_ITEMS = TUI_VIEW_REGISTRY.navigation_items
@dataclass(frozen=True)
class TuiScreenFactory:
"""
TUI 工作台页面工厂。
该对象负责把页面快照转换为具体 screen 实例,避免 `RuoyiTuiApp`
同时承担状态协调和 screen 构建职责。
"""
@staticmethod
def build_refresh_timestamp() -> str:
"""
构建当前页面刷新时间文本。
:return: 格式化后的刷新时间
"""
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
def build(
self,
*,
snapshot: BrowserPageSnapshot | DashboardSnapshot | DetailPageSnapshot,
env: str,
active_view: str,
navigation_items: list[NavigationItem],
action_feedback_lines: list[str],
) -> BrowserScreen | DashboardScreen | DetailScreen:
"""
根据快照类型构建对应的工作台页面。
:param snapshot: 页面快照
:param env: 当前运行环境
:param active_view: 当前激活视图
:param navigation_items: 导航项列表
:param action_feedback_lines: 当前视图动作反馈
:return: 对应的页面对象
"""
refreshed_at = self.build_refresh_timestamp()
if isinstance(snapshot, DashboardSnapshot):
return DashboardScreen(
snapshot,
env=env,
active_view=active_view,
navigation_items=navigation_items,
refreshed_at=refreshed_at,
)
if isinstance(snapshot, BrowserPageSnapshot):
return BrowserScreen(
snapshot,
env=env,
active_view=active_view,
navigation_items=navigation_items,
refreshed_at=refreshed_at,
action_feedback_lines=action_feedback_lines,
)
return DetailScreen(
snapshot,
env=env,
active_view=active_view,
navigation_items=navigation_items,
refreshed_at=refreshed_at,
)
@dataclass
class TuiScreenNavigator:
"""
TUI 工作台页面导航器。
该对象负责处理首屏展示与后续切屏差异,让应用壳更专注于状态切换。
:param app: 当前 TUI 应用实例
:param initialized: 工作台屏幕是否已初始化
"""
app: 'RuoyiTuiApp'
initialized: bool = False
def show(self, screen: BrowserScreen | DashboardScreen | DetailScreen) -> None:
"""
显示工作台页面,并兼容首屏初始化与后续切屏。
Textual 在默认根屏阶段直接调用 `switch_screen()` 会触发内部回调栈异常,
因此首次展示页面需要使用 `push_screen()` 建立活动屏,后续再改用
`switch_screen()` 做同层页面替换。
:param screen: 待显示的页面对象
:return: None
"""
if not self.initialized:
self.app.push_screen(screen)
self.initialized = True
return
self.app.switch_screen(screen)
@dataclass
class TuiSnapshotCache:
"""
TUI 页面快照短期缓存。
缓存按视图、筛选与搜索条件隔离,并通过容量和 TTL 双重限制避免
工作台长时间运行后无界增长。
"""
ttl_seconds: float = TUI_SNAPSHOT_CACHE_TTL_SECONDS
max_entries: int = TUI_SNAPSHOT_CACHE_MAX_ENTRIES
def __post_init__(self) -> None:
"""初始化有序缓存容器。"""
self._entries: OrderedDict[str, tuple[float, PageSnapshot]] = OrderedDict()
def get(self, key: str) -> PageSnapshot | None:
"""
读取仍在有效期内的页面快照。
:param key: 缓存键
:return: 页面快照或 None
"""
cached = self._entries.get(key)
if cached is None:
return None
created_at, snapshot = cached
if monotonic() - created_at > self.ttl_seconds:
self._entries.pop(key, None)
return None
self._entries.move_to_end(key)
return snapshot
def put(self, key: str, snapshot: PageSnapshot) -> None:
"""
写入页面快照并执行容量淘汰。
:param key: 缓存键
:param snapshot: 页面快照
:return: None
"""
self._entries[key] = (monotonic(), snapshot)
self._entries.move_to_end(key)
while len(self._entries) > self.max_entries:
self._entries.popitem(last=False)
def invalidate(self, key: str) -> None:
"""
删除指定缓存项。
:param key: 缓存键
:return: None
"""
self._entries.pop(key, None)
@dataclass(frozen=True)
class TuiViewOpeningCoordinator:
"""
TUI 视图打开协调器。
该对象负责串联视图解析、快照采集、screen 构建与切屏展示,
让 `RuoyiTuiApp` 更专注于状态暴露与 Textual action 桥接。
:param view_registry: 视图注册表
:param screen_factory: 工作台页面工厂
:param navigation_items: 导航项列表
"""
view_registry: TuiViewRegistry
screen_factory: TuiScreenFactory
navigation_items: list[NavigationItem]
snapshot_cache: TuiSnapshotCache
@staticmethod
def build_failure_snapshot(view_key: str, error: Exception) -> DetailPageSnapshot:
"""
构建页面加载失败快照。
:param view_key: 视图标识
:param error: 页面加载异常
:return: 失败页面快照
"""
view_label = TUI_COPY.render_view_label(view_key)
return DetailPageSnapshot(
title=view_label,
subtitle=f'{view_label}加载失败,工作台仍可继续切换或重试',
sections=[
DetailSectionSnapshot(
title='加载失败',
status='fail',
lines=[
'## 错误信息',
str(error) or error.__class__.__name__,
'',
'## 建议操作',
'按 R 重试,或切换到其他页面继续操作。',
],
)
],
)
async def show(
self,
app: 'RuoyiTuiApp',
view_key: str,
*,
request_id: int,
force_refresh: bool = False,
) -> None:
"""
切换并展示指定视图。
:param app: 当前 TUI 应用实例
:param view_key: 目标视图标识
:return: None
"""
resolved_view_key = self.view_registry.resolve_view_key(view_key)
if app.screen_navigator.initialized:
await asyncio.sleep(TUI_NAVIGATION_DEBOUNCE_SECONDS)
if not app.is_current_view_request(request_id, resolved_view_key):
return
cache_key = app.build_snapshot_cache_key(resolved_view_key)
snapshot = None if force_refresh else self.snapshot_cache.get(cache_key)
if snapshot is None:
if force_refresh:
self.snapshot_cache.invalidate(cache_key)
try:
snapshot = await asyncio.wait_for(
self.view_registry.collect_snapshot(app, resolved_view_key),
timeout=TUI_PAGE_TIMEOUT_SECONDS,
)
except asyncio.CancelledError:
raise
except Exception as error:
snapshot = self.build_failure_snapshot(resolved_view_key, error)
else:
self.snapshot_cache.put(cache_key, snapshot)
if not app.is_current_view_request(request_id, resolved_view_key):
return
app.current_view = resolved_view_key
screen = self.screen_factory.build(
snapshot=snapshot,
env=app.env,
active_view=app.current_view,
navigation_items=self.navigation_items,
action_feedback_lines=app.get_action_feedback_lines(app.current_view),
)
app.screen_navigator.show(screen)
class RuoyiTuiApp(App[None]):
"""
RuoYi CLI Textual 应用。
:param env: 当前运行环境
"""
CSS = _TUI_APP_CSS
BINDINGS = TUI_APP_BINDINGS
def __init__(self, env: str) -> None:
"""
初始化 TUI 应用实例。
:param env: 当前运行环境
:return: None
"""
self.env = env
self.current_view = 'dashboard'
self.requested_view = 'dashboard'
self._view_request_id = 0
self._view_task: asyncio.Task[None] | None = None
self.view_state_store = TuiViewStateStore.create_default()
self.screen_factory = TuiScreenFactory()
self.screen_navigator = TuiScreenNavigator(self)
self.view_opening_coordinator = TuiViewOpeningCoordinator(
view_registry=TUI_VIEW_REGISTRY,
screen_factory=self.screen_factory,
navigation_items=NAVIGATION_ITEMS,
snapshot_cache=TuiSnapshotCache(),
)
super().__init__()
def build_snapshot_cache_key(self, view_key: str) -> str:
"""
构建包含筛选与搜索状态的页面缓存键。
:param view_key: 视图标识
:return: 页面缓存键
"""
return '\x1f'.join(
(
view_key,
self.get_browser_filter(view_key, ''),
self.get_browser_query(view_key),
)
)
def is_current_view_request(self, request_id: int, view_key: str) -> bool:
"""
判断异步页面加载是否仍为最新请求。
:param request_id: 请求编号
:param view_key: 视图标识
:return: 是否仍为最新请求
"""
return request_id == self._view_request_id and view_key == self.requested_view
def show_view(self, view_key: str, *, force_refresh: bool = False) -> None:
"""
切换并展示指定视图。
:param view_key: 目标视图标识
:return: None
"""
resolved_view_key = TUI_VIEW_REGISTRY.resolve_view_key(view_key)
self.requested_view = resolved_view_key
self._view_request_id += 1
if self._view_task is not None and not self._view_task.done():
self._view_task.cancel()
self._view_task = asyncio.create_task(
self.view_opening_coordinator.show(
self,
resolved_view_key,
request_id=self._view_request_id,
force_refresh=force_refresh,
)
)
def remember_action_feedback(self, view_key: str, lines: list[str]) -> None:
"""
记录指定页面最近一次动作反馈。
:param view_key: 页面视图标识
:param lines: 反馈文本行
:return: None
"""
self.view_state_store.remember_action_feedback(view_key, lines)
def get_action_feedback_lines(self, view_key: str) -> list[str]:
"""
读取指定页面最近一次动作反馈。
:param view_key: 页面视图标识
:return: 反馈文本行
"""
return self.view_state_store.get_action_feedback_lines(view_key)
def remember_browser_filter(self, view_key: str, filter_key: str) -> None:
"""
记录指定浏览页当前筛选键。
:param view_key: 页面视图标识
:param filter_key: 当前筛选键
:return: None
"""
self.view_state_store.remember_browser_filter(view_key, filter_key)
def get_browser_filter(self, view_key: str, default: str = 'all') -> str:
"""
读取指定浏览页当前筛选键。
:param view_key: 页面视图标识
:param default: 默认筛选键
:return: 当前筛选键
"""
return self.view_state_store.get_browser_filter(view_key, default)
def remember_browser_query(self, view_key: str, query: str) -> None:
"""
记录指定浏览页当前搜索词。
:param view_key: 页面视图标识
:param query: 当前搜索词
:return: None
"""
self.view_state_store.remember_browser_query(view_key, query)
def get_browser_query(self, view_key: str, default: str = '') -> str:
"""
读取指定浏览页当前搜索词。
:param view_key: 页面视图标识
:param default: 默认搜索词
:return: 当前搜索词
"""
return self.view_state_store.get_browser_query(view_key, default)
def on_mount(self) -> None:
"""
在应用启动时载入首页。
:return: None
"""
self.show_view('dashboard')
def on_unmount(self) -> None:
"""
应用卸载时取消页面加载任务。
:return: None
"""
if self._view_task is not None and not self._view_task.done():
self._view_task.cancel()
def open_view(self, view_key: str) -> None:
"""
按视图标识打开对应页面。
:param view_key: 视图标识
:return: None
"""
self.show_view(view_key)
def action_focus_sidebar(self) -> None:
"""
将焦点切换到左侧导航栏。
:return: None
"""
try:
self.screen.query_one(WorkspaceSidebar).focus()
except NoMatches:
return
def action_show_previous_view(self) -> None:
"""
切换到前一个工作台视图。
:return: None
"""
self.open_view(TUI_VIEW_REGISTRY.get_relative_view_key(self.requested_view, -1))
def action_show_next_view(self) -> None:
"""
切换到后一个工作台视图。
:return: None
"""
self.open_view(TUI_VIEW_REGISTRY.get_relative_view_key(self.requested_view, 1))
def action_refresh_current_view(self) -> None:
"""
刷新当前视图数据。
:return: None
"""
self.show_view(self.current_view, force_refresh=True)
def action_show_dashboard(self) -> None:
"""
切换到首页视图。
:return: None
"""
self.open_view('dashboard')
def action_show_app(self) -> None:
"""
切换到应用详情视图。
:return: None
"""
self.open_view('app')
def action_show_ops(self) -> None:
"""
切换到运维详情视图。
:return: None
"""
self.open_view('ops')
def action_show_database(self) -> None:
"""
切换到数据库详情视图。
:return: None
"""
self.open_view('database')
def action_show_cache(self) -> None:
"""
切换到缓存浏览视图。
:return: None
"""
self.open_view('cache')
def action_show_jobs(self) -> None:
"""
切换到任务浏览视图。
:return: None
"""
self.open_view('jobs')
def action_show_gen(self) -> None:
"""
切换到代码生成浏览视图。
:return: None
"""
self.open_view('gen')
def action_show_configs(self) -> None:
"""
切换到配置浏览视图。
:return: None
"""
self.open_view('configs')
def action_show_crypto(self) -> None:
"""
切换到传输加密详情视图。
:return: None
"""
self.open_view('crypto')
@dataclass(frozen=True)
class TuiAppRunner:
"""
TUI 应用启动器。
该对象负责按环境创建并运行 Textual 应用实例,使命令入口不再依赖
模块级启动函数。
:param app_factory: TUI 应用工厂
"""
app_factory: Callable[[str], RuoyiTuiApp]
def run(self, env: str) -> None:
"""
启动 TUI 应用。
:param env: 当前运行环境
:return: None
"""
self.app_factory(env).run()
TUI_APP_RUNNER = TuiAppRunner(RuoyiTuiApp)