324 lines
8.7 KiB
Python
324 lines
8.7 KiB
Python
from textual.widgets import Static
|
|
|
|
from cli.tui.copy import TUI_COPY
|
|
|
|
|
|
class StatusPanelRenderingSupport:
|
|
"""
|
|
状态面板渲染支持对象。
|
|
|
|
该对象负责状态类名解析、结构化正文渲染和信号带脉冲文本构建,
|
|
供状态面板、指标卡与信号带组件复用。
|
|
"""
|
|
|
|
PULSE_FRAMES = ['·', '•', '◉', '•']
|
|
|
|
@staticmethod
|
|
def resolve_status_class(status: str) -> str:
|
|
"""
|
|
根据状态值解析样式类。
|
|
|
|
:param status: 面板状态
|
|
:return: 状态样式类名
|
|
"""
|
|
normalized_status = str(status).strip().lower()
|
|
if normalized_status in {'ok', 'success', 'healthy'}:
|
|
return 'is-ok'
|
|
if normalized_status in {'fail', 'error', 'down'}:
|
|
return 'is-fail'
|
|
if normalized_status in {'warn', 'warning', 'degraded'}:
|
|
return 'is-warn'
|
|
return 'is-info'
|
|
|
|
def render_structured_body(self, body: str) -> str:
|
|
"""
|
|
将结构化正文文本渲染为更有层次的终端块内容。
|
|
|
|
支持以下约定:
|
|
- `## ` 开头:分组标题
|
|
- `> ` 开头:说明或子项
|
|
- 空字符串:保留空行
|
|
|
|
:param body: 原始正文文本
|
|
:return: 渲染后的正文文本
|
|
"""
|
|
rendered_lines: list[str] = []
|
|
for raw_line in body.splitlines():
|
|
stripped = raw_line.strip()
|
|
if not stripped:
|
|
if rendered_lines and rendered_lines[-1] != '':
|
|
rendered_lines.append('')
|
|
continue
|
|
if stripped.startswith('## '):
|
|
title = stripped[3:].strip()
|
|
if rendered_lines and rendered_lines[-1] != '':
|
|
rendered_lines.append('')
|
|
rendered_lines.append(f'【{title}】')
|
|
rendered_lines.append('─' * max(10, min(len(title) * 2, 28)))
|
|
continue
|
|
if stripped.startswith('> '):
|
|
rendered_lines.append(f'│ {stripped[2:].strip()}')
|
|
continue
|
|
rendered_lines.append(f'• {stripped}')
|
|
|
|
while rendered_lines and rendered_lines[0] == '':
|
|
rendered_lines.pop(0)
|
|
while rendered_lines and rendered_lines[-1] == '':
|
|
rendered_lines.pop()
|
|
return '\n'.join(rendered_lines) if rendered_lines else f'- {TUI_COPY.build_status_panel_empty_text()}'
|
|
|
|
def build_signal_rail_text(self, lines: list[str], pulse_index: int) -> str:
|
|
"""
|
|
构建信号带渲染文本。
|
|
|
|
:param lines: 信号文本行
|
|
:param pulse_index: 当前脉冲帧索引
|
|
:return: 渲染文本
|
|
"""
|
|
pulse = self.PULSE_FRAMES[pulse_index % len(self.PULSE_FRAMES)]
|
|
body = '\n'.join(line for line in lines if line.strip())
|
|
return TUI_COPY.build_signal_rail_text(pulse, body or TUI_COPY.build_signal_rail_empty_text())
|
|
|
|
|
|
STATUS_PANEL_RENDERING = StatusPanelRenderingSupport()
|
|
|
|
|
|
class StatusPanel(Static):
|
|
"""
|
|
TUI 状态面板组件。
|
|
|
|
:param title: 面板标题
|
|
:param status: 面板状态
|
|
:param body: 面板正文
|
|
"""
|
|
|
|
DEFAULT_CSS = """
|
|
StatusPanel {
|
|
border: double #245874;
|
|
padding: 1 2;
|
|
margin: 0;
|
|
height: 17;
|
|
min-height: 17;
|
|
background: #08131f;
|
|
color: #def8ff;
|
|
}
|
|
|
|
StatusPanel.is-ok {
|
|
border: double #22c983;
|
|
background: #081b17;
|
|
}
|
|
|
|
StatusPanel.is-fail {
|
|
border: double #ff6b7a;
|
|
background: #241118;
|
|
}
|
|
|
|
StatusPanel.is-warn {
|
|
border: double #f2b84b;
|
|
background: #261d0f;
|
|
}
|
|
|
|
StatusPanel.is-info {
|
|
border: double #38d8ff;
|
|
background: #081825;
|
|
}
|
|
|
|
StatusPanel:focus {
|
|
border: heavy #6df7ff;
|
|
background: #10364b;
|
|
}
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
title: str,
|
|
status: str,
|
|
body: str,
|
|
rendering: StatusPanelRenderingSupport | None = None,
|
|
) -> None:
|
|
"""
|
|
初始化状态面板。
|
|
|
|
:param title: 面板标题
|
|
:param status: 面板状态
|
|
:param body: 面板正文
|
|
:param rendering: 状态面板渲染支持对象
|
|
:return: None
|
|
"""
|
|
self.title = title
|
|
self.status = status
|
|
self.body = body
|
|
self.rendering = rendering or STATUS_PANEL_RENDERING
|
|
super().__init__(self._build_render_text(), markup=False)
|
|
self.can_focus = True
|
|
self.add_class(self.rendering.resolve_status_class(self.status))
|
|
|
|
def _build_render_text(self) -> str:
|
|
"""
|
|
构建面板渲染文本。
|
|
|
|
:return: 渲染文本
|
|
"""
|
|
rendered_body = self.rendering.render_structured_body(self.body)
|
|
normalized_status = TUI_COPY.render_status_label(self.status)
|
|
return TUI_COPY.build_status_panel_text(
|
|
self.title,
|
|
TUI_COPY.render_status_code(self.status),
|
|
normalized_status,
|
|
rendered_body,
|
|
)
|
|
|
|
|
|
class MetricPanel(Static):
|
|
"""
|
|
TUI 首页驾驶舱指标卡组件。
|
|
|
|
:param title: 指标标题
|
|
:param value: 指标主值
|
|
:param status: 指标状态
|
|
:param hint: 指标补充说明
|
|
"""
|
|
|
|
DEFAULT_CSS = """
|
|
MetricPanel {
|
|
border: double #245874;
|
|
padding: 1 2;
|
|
height: 12;
|
|
min-height: 12;
|
|
background: #08131f;
|
|
color: #effcff;
|
|
}
|
|
|
|
MetricPanel.is-primary {
|
|
border: heavy #6df7ff;
|
|
height: 12;
|
|
min-height: 12;
|
|
background: #0a1725;
|
|
text-style: bold;
|
|
}
|
|
|
|
MetricPanel.is-ok {
|
|
border: double #22c983;
|
|
background: #081b17;
|
|
}
|
|
|
|
MetricPanel.is-fail {
|
|
border: double #ff6b7a;
|
|
background: #241118;
|
|
}
|
|
|
|
MetricPanel.is-warn {
|
|
border: double #f2b84b;
|
|
background: #261d0f;
|
|
}
|
|
|
|
MetricPanel.is-info {
|
|
border: double #38d8ff;
|
|
background: #081825;
|
|
}
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
title: str,
|
|
value: str,
|
|
status: str,
|
|
hint: str,
|
|
*,
|
|
accent: bool = False,
|
|
rendering: StatusPanelRenderingSupport | None = None,
|
|
) -> None:
|
|
"""
|
|
初始化指标卡。
|
|
|
|
:param title: 指标标题
|
|
:param value: 指标主值
|
|
:param status: 指标状态
|
|
:param hint: 指标补充说明
|
|
:param accent: 是否作为主指标高亮展示
|
|
:param rendering: 状态面板渲染支持对象
|
|
:return: None
|
|
"""
|
|
self.title = title
|
|
self.value = value
|
|
self.status = status
|
|
self.hint = hint
|
|
self.rendering = rendering or STATUS_PANEL_RENDERING
|
|
super().__init__(self._build_render_text(), markup=False)
|
|
self.add_class(self.rendering.resolve_status_class(self.status))
|
|
if accent:
|
|
self.add_class('is-primary')
|
|
|
|
def _build_render_text(self) -> str:
|
|
"""
|
|
构建指标卡渲染文本。
|
|
|
|
:return: 渲染文本
|
|
"""
|
|
normalized_status = TUI_COPY.render_status_label(self.status)
|
|
return TUI_COPY.build_metric_panel_text(
|
|
self.title,
|
|
self.value,
|
|
TUI_COPY.render_status_code(self.status),
|
|
normalized_status,
|
|
self.hint,
|
|
)
|
|
|
|
|
|
class SignalRail(Static):
|
|
"""
|
|
首页信号带组件,用于展示简洁的系统态势条目。
|
|
|
|
:param lines: 信号文本行
|
|
"""
|
|
|
|
DEFAULT_CSS = """
|
|
SignalRail {
|
|
border: double #1db9d1;
|
|
padding: 1 2;
|
|
margin-top: 1;
|
|
margin-bottom: 1;
|
|
min-height: 6;
|
|
background: #06111b;
|
|
color: #e9fdff;
|
|
}
|
|
"""
|
|
|
|
def __init__(self, lines: list[str], rendering: StatusPanelRenderingSupport | None = None) -> None:
|
|
"""
|
|
初始化首页信号带。
|
|
|
|
:param lines: 信号文本行
|
|
:param rendering: 状态面板渲染支持对象
|
|
:return: None
|
|
"""
|
|
self.lines = lines
|
|
self._pulse_index = 0
|
|
self.rendering = rendering or STATUS_PANEL_RENDERING
|
|
super().__init__(self._build_render_text(), markup=False)
|
|
|
|
def on_mount(self) -> None:
|
|
"""
|
|
挂载后启动轻量脉冲刷新。
|
|
|
|
:return: None
|
|
"""
|
|
self.set_interval(0.8, self._advance_pulse, name='signal rail pulse')
|
|
|
|
def _advance_pulse(self) -> None:
|
|
"""
|
|
推进信号带脉冲状态。
|
|
|
|
:return: None
|
|
"""
|
|
self._pulse_index = (self._pulse_index + 1) % 4
|
|
self.update(self._build_render_text())
|
|
|
|
def _build_render_text(self) -> str:
|
|
"""
|
|
构建信号带渲染文本。
|
|
|
|
:return: 渲染文本
|
|
"""
|
|
return self.rendering.build_signal_rail_text(self.lines, self._pulse_index)
|