mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-01 00:05:18 +00:00
462 lines
18 KiB
Python
462 lines
18 KiB
Python
import asyncio
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from cli.tui.adapters.base import BaseDetailAdapter
|
|
from cli.tui.adapters.models import (
|
|
TUI_ADAPTER_MODEL_RENDERER,
|
|
DetailPageSnapshot,
|
|
DetailSectionSnapshot,
|
|
)
|
|
from cli.tui.copy import TUI_COPY
|
|
from cli.tui.diagnostics import TUI_DIAGNOSTIC_SERVICE
|
|
from cli.tui.queries import TUI_RUNTIME_QUERIES, TuiRuntimeQueryService
|
|
from cli.utils import SHELL_TEXT_FORMATTER
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DatabaseDetailSourcePayloads:
|
|
"""
|
|
数据库详情页原始数据源快照。
|
|
|
|
:param revision_payload: `db current` 结果
|
|
:param check_payload: `db check` 结果
|
|
:param heads_payload: `db heads` 结果
|
|
:param history_payload: `db history` 结果
|
|
:param config_payload: `app config` 结果
|
|
"""
|
|
|
|
revision_payload: dict[str, Any] | None
|
|
check_payload: dict[str, Any] | None
|
|
heads_payload: dict[str, Any] | None
|
|
history_payload: dict[str, Any] | None
|
|
config_payload: dict[str, Any] | None
|
|
|
|
|
|
class DatabaseDetailSnapshotCollector:
|
|
"""
|
|
数据库详情页数据采集器。
|
|
|
|
该对象负责拉取数据库详情页所需的多路 CLI 原始结果,
|
|
让 `DatabaseDetailAdapter` 保持详情页编排职责。
|
|
"""
|
|
|
|
def __init__(self, query_service: TuiRuntimeQueryService | None = None) -> None:
|
|
"""
|
|
初始化数据库详情页数据采集器。
|
|
|
|
:param query_service: TUI 进程内查询服务
|
|
:return: None
|
|
"""
|
|
self.query_service = query_service or TUI_RUNTIME_QUERIES
|
|
|
|
async def collect(self, env: str) -> DatabaseDetailSourcePayloads:
|
|
"""
|
|
采集数据库详情页所需原始结果。
|
|
|
|
:param env: 当前运行环境
|
|
:return: 数据库详情页原始数据源快照
|
|
"""
|
|
revision_payload, check_payload, heads_payload, history_payload, config_payload = await asyncio.gather(
|
|
self.query_service.get_database_current(),
|
|
self.query_service.get_database_ping(),
|
|
self.query_service.get_database_heads(),
|
|
self.query_service.get_database_history(limit=8),
|
|
self.query_service.get_app_config(env),
|
|
)
|
|
return DatabaseDetailSourcePayloads(
|
|
revision_payload=revision_payload,
|
|
check_payload=check_payload,
|
|
heads_payload=heads_payload,
|
|
history_payload=history_payload,
|
|
config_payload=config_payload,
|
|
)
|
|
|
|
|
|
class DatabaseSectionBuilder:
|
|
"""
|
|
数据库详情分区构建器。
|
|
|
|
该构建器负责将数据库相关 CLI 结果负载转换为 TUI 详情页分区,
|
|
使详情页适配器本体只保留采集与编排职责。
|
|
"""
|
|
|
|
@staticmethod
|
|
def extract_revision_items(payload: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
"""
|
|
从 Alembic 结果中提取修订版本列表。
|
|
|
|
:param payload: `db heads` 或 `db history` JSON 负载
|
|
:return: 修订版本列表
|
|
"""
|
|
items = payload.get('items') if isinstance(payload, dict) else None
|
|
if not isinstance(items, list):
|
|
return []
|
|
return [item for item in items if isinstance(item, dict)]
|
|
|
|
@staticmethod
|
|
def build_revision_item_lines(item: dict[str, Any], *, index: int, item_label: str) -> list[str]:
|
|
"""
|
|
构建单条 Alembic 修订版本详情文本。
|
|
|
|
:param item: 修订版本字典
|
|
:param index: 当前索引
|
|
:param item_label: 条目标识名称
|
|
:return: 文本行列表
|
|
"""
|
|
down_revisions = item.get('downRevisions')
|
|
branch_labels = item.get('branchLabels')
|
|
depends_on = item.get('dependsOn')
|
|
return [
|
|
f'## {item_label} {index:02d} · {item.get("revision", "-")}',
|
|
f'> 下游版本: {",".join(down_revisions) if isinstance(down_revisions, list) and down_revisions else "-"}',
|
|
f'> 分支标签: {",".join(branch_labels) if isinstance(branch_labels, list) and branch_labels else "-"}',
|
|
f'> 依赖版本: {",".join(depends_on) if isinstance(depends_on, list) and depends_on else "-"}',
|
|
f'> 说明: {SHELL_TEXT_FORMATTER.truncate_text(item.get("doc", "-"), 72)}',
|
|
f'> 文件: {SHELL_TEXT_FORMATTER.truncate_text(item.get("path", "-"), 72)}',
|
|
]
|
|
|
|
@staticmethod
|
|
def build_revision_section(payload: dict[str, Any] | None) -> DetailSectionSnapshot:
|
|
"""
|
|
构建数据库迁移状态分区。
|
|
|
|
:param payload: `db current` JSON 负载
|
|
:return: 分区快照
|
|
"""
|
|
current_revision = payload.get('currentRevision', '-') if isinstance(payload, dict) else '-'
|
|
if not isinstance(payload, dict) or not payload.get('ok', False):
|
|
return DetailSectionSnapshot(
|
|
title='迁移版本',
|
|
status='fail',
|
|
lines=TUI_ADAPTER_MODEL_RENDERER.build_failure_lines(
|
|
payload, empty_label='迁移版本', empty_value=str(current_revision or '-')
|
|
),
|
|
)
|
|
return DetailSectionSnapshot(
|
|
title='迁移版本',
|
|
status='ok',
|
|
lines=[
|
|
'## 当前版本',
|
|
f'迁移版本: {current_revision}',
|
|
'',
|
|
'## 状态说明',
|
|
f'结果消息: {TUI_ADAPTER_MODEL_RENDERER.extract_payload_message(payload)}',
|
|
],
|
|
)
|
|
|
|
def build_overview_section(
|
|
self,
|
|
revision_payload: dict[str, Any] | None,
|
|
check_payload: dict[str, Any] | None,
|
|
heads_payload: dict[str, Any] | None,
|
|
history_payload: dict[str, Any] | None,
|
|
) -> DetailSectionSnapshot:
|
|
"""
|
|
构建数据库页总览判断分区。
|
|
|
|
:param revision_payload: `db current` JSON 负载
|
|
:param check_payload: `db check` JSON 负载
|
|
:param heads_payload: `db heads` JSON 负载
|
|
:param history_payload: `db history` JSON 负载
|
|
:return: 分区快照
|
|
"""
|
|
revision_ok = bool(isinstance(revision_payload, dict) and revision_payload.get('ok', False))
|
|
check_ok = bool(isinstance(check_payload, dict) and check_payload.get('ok', False))
|
|
heads_ok = bool(isinstance(heads_payload, dict) and heads_payload.get('ok', False))
|
|
current_revision = revision_payload.get('currentRevision', '-') if isinstance(revision_payload, dict) else '-'
|
|
head_items = self.extract_revision_items(heads_payload)
|
|
history_items = self.extract_revision_items(history_payload)
|
|
|
|
status = 'ok'
|
|
conclusion = '数据库基线正常,可继续查看连接状态、Heads 与版本链路'
|
|
if not revision_ok or not check_ok:
|
|
status = 'fail'
|
|
conclusion = '数据库存在基础异常,优先确认迁移版本与连通性检查结果'
|
|
elif not heads_ok or len(head_items) != 1:
|
|
status = 'warn'
|
|
conclusion = '数据库存在迁移分叉风险,优先确认 Heads 与版本链路是否一致'
|
|
|
|
return DetailSectionSnapshot(
|
|
title='总览判断',
|
|
status=status,
|
|
lines=[
|
|
'## 当前结论',
|
|
conclusion,
|
|
'',
|
|
'## 核心指标',
|
|
f'当前 revision: {current_revision}',
|
|
f'连通性检查: {"正常" if check_ok else "异常"}',
|
|
f'Heads 数量: {len(head_items)}',
|
|
f'版本数量: {history_payload.get("totalCount", len(history_items)) if isinstance(history_payload, dict) else len(history_items)}',
|
|
'',
|
|
'## 建议入口',
|
|
'优先关注:迁移版本 / 连通性检查 / Heads 状态 / 升级入口',
|
|
],
|
|
)
|
|
|
|
@staticmethod
|
|
def build_profile_section(payload: dict[str, Any] | None) -> DetailSectionSnapshot:
|
|
"""
|
|
构建数据库连接配置分区。
|
|
|
|
:param payload: `app config` JSON 负载
|
|
:return: 分区快照
|
|
"""
|
|
config = payload.get('config') if isinstance(payload, dict) else None
|
|
if not isinstance(config, dict):
|
|
return DetailSectionSnapshot(
|
|
title='连接信息',
|
|
status='fail',
|
|
lines=TUI_ADAPTER_MODEL_RENDERER.build_failure_lines(
|
|
payload, empty_label='连接信息', empty_value='不可用'
|
|
),
|
|
)
|
|
return DetailSectionSnapshot(
|
|
title='连接信息',
|
|
status='ok',
|
|
lines=[
|
|
'## 数据库连接',
|
|
f'数据库类型: {config.get("dbType", "-")}',
|
|
f'连接地址: {config.get("dbHost", "-")}:{config.get("dbPort", "-")}',
|
|
f'数据库名: {config.get("dbDatabase", "-")}',
|
|
],
|
|
)
|
|
|
|
@staticmethod
|
|
def build_check_section(payload: dict[str, Any] | None) -> DetailSectionSnapshot:
|
|
"""
|
|
构建数据库连通性检查分区。
|
|
|
|
:param payload: `db check` JSON 负载
|
|
:return: 分区快照
|
|
"""
|
|
if not isinstance(payload, dict) or not payload.get('ok', False):
|
|
return DetailSectionSnapshot(
|
|
title='连通性检查',
|
|
status='fail',
|
|
lines=TUI_ADAPTER_MODEL_RENDERER.build_failure_lines(
|
|
payload, empty_label='数据库连接', empty_value='异常'
|
|
),
|
|
)
|
|
return DetailSectionSnapshot(
|
|
title='连通性检查',
|
|
status='ok',
|
|
lines=[
|
|
'## 当前状态',
|
|
'数据库连接: 正常',
|
|
'',
|
|
'## 结果摘要',
|
|
f'结果消息: {TUI_ADAPTER_MODEL_RENDERER.extract_payload_message(payload)}',
|
|
],
|
|
)
|
|
|
|
def build_heads_section(self, payload: dict[str, Any] | None) -> DetailSectionSnapshot:
|
|
"""
|
|
构建 Alembic heads 分区。
|
|
|
|
:param payload: `db heads` JSON 负载
|
|
:return: 分区快照
|
|
"""
|
|
if not isinstance(payload, dict) or not payload.get('ok', False):
|
|
return DetailSectionSnapshot(
|
|
title='Heads 状态',
|
|
status='fail',
|
|
lines=TUI_ADAPTER_MODEL_RENDERER.build_failure_lines(
|
|
payload, empty_label='Heads', empty_value='不可用'
|
|
),
|
|
)
|
|
items = self.extract_revision_items(payload)
|
|
status = 'ok' if len(items) == 1 else 'warn'
|
|
lines = [
|
|
'## Heads 概况',
|
|
f'Heads 数量: {len(items)}',
|
|
f'结果消息: {TUI_ADAPTER_MODEL_RENDERER.extract_payload_message(payload)}',
|
|
]
|
|
if not items:
|
|
lines.extend(['', '## Heads 列表', '> 当前仓库未返回可用 heads'])
|
|
else:
|
|
lines.extend(['', '## Heads 列表'])
|
|
for index, item in enumerate(items[:4], start=1):
|
|
lines.extend(self.build_revision_item_lines(item, index=index, item_label='Head'))
|
|
lines.append('')
|
|
if len(lines) > 1 and lines[-1] == '':
|
|
lines.pop()
|
|
return DetailSectionSnapshot(
|
|
title='Heads 状态',
|
|
status=status,
|
|
lines=lines,
|
|
)
|
|
|
|
def build_history_section(self, payload: dict[str, Any] | None) -> DetailSectionSnapshot:
|
|
"""
|
|
构建 Alembic 历史版本分区。
|
|
|
|
:param payload: `db history` JSON 负载
|
|
:return: 分区快照
|
|
"""
|
|
if not isinstance(payload, dict) or not payload.get('ok', False):
|
|
return DetailSectionSnapshot(
|
|
title='历史版本',
|
|
status='fail',
|
|
lines=TUI_ADAPTER_MODEL_RENDERER.build_failure_lines(
|
|
payload, empty_label='历史版本', empty_value='不可用'
|
|
),
|
|
)
|
|
items = self.extract_revision_items(payload)
|
|
lines = [
|
|
'## 历史概况',
|
|
f'返回数量: {payload.get("count", len(items))}',
|
|
f'总版本数: {payload.get("totalCount", len(items))}',
|
|
f'查询上限: {payload.get("limit", len(items))}',
|
|
'',
|
|
'## 历史列表',
|
|
]
|
|
if not items:
|
|
lines.append('> 当前仓库未返回可展示的历史版本')
|
|
else:
|
|
for index, item in enumerate(items[:6], start=1):
|
|
lines.extend(self.build_revision_item_lines(item, index=index, item_label='版本'))
|
|
lines.append('')
|
|
if lines[-1] == '':
|
|
lines.pop()
|
|
return DetailSectionSnapshot(
|
|
title='历史版本',
|
|
status='ok',
|
|
lines=lines,
|
|
)
|
|
|
|
@staticmethod
|
|
def build_upgrade_entry_section() -> DetailSectionSnapshot:
|
|
"""
|
|
构建数据库升级向导入口分区。
|
|
|
|
:return: 分区快照
|
|
"""
|
|
return DetailSectionSnapshot(
|
|
title='升级入口',
|
|
status='info',
|
|
lines=TUI_COPY.build_command_hint_lines(
|
|
scenario='准备执行数据库升级、校验迁移路径或在生产前做升级预演时,应先通过向导确认目标版本与 dry-run 结果。',
|
|
command=TUI_COPY.build_cli_command_hint('wizard', 'db-upgrade', '--output=text'),
|
|
guide='向导会继续询问目标环境和目标 revision,并在真正升级前先展示预览信息。',
|
|
),
|
|
)
|
|
|
|
@staticmethod
|
|
def build_init_entry_section() -> DetailSectionSnapshot:
|
|
"""
|
|
构建数据库初始化预演入口分区。
|
|
|
|
:return: 分区快照
|
|
"""
|
|
return DetailSectionSnapshot(
|
|
title='初始化预演入口',
|
|
status='info',
|
|
lines=TUI_COPY.build_command_hint_lines(
|
|
scenario='准备初始化新库、重建本地环境,或先确认初始化命令会执行到哪些迁移版本时,应先执行 dry-run 预演。',
|
|
command=TUI_COPY.build_cli_command_hint('db', 'init', '--dry-run', '--output=text'),
|
|
guide='预演只返回命令摘要与执行目录;确认无误后,再通过外部终端或向导执行真实初始化流程。',
|
|
),
|
|
)
|
|
|
|
def build_sections(
|
|
self,
|
|
*,
|
|
revision_payload: dict[str, Any] | None,
|
|
check_payload: dict[str, Any] | None,
|
|
heads_payload: dict[str, Any] | None,
|
|
history_payload: dict[str, Any] | None,
|
|
config_payload: dict[str, Any] | None,
|
|
) -> list[DetailSectionSnapshot]:
|
|
"""
|
|
构建数据库页全部详情分区。
|
|
|
|
:param revision_payload: `db current` 结果负载
|
|
:param check_payload: `db check` 结果负载
|
|
:param heads_payload: `db heads` 结果负载
|
|
:param history_payload: `db history` 结果负载
|
|
:param config_payload: `app config` 结果负载
|
|
:return: 详情分区列表
|
|
"""
|
|
return [
|
|
self.build_overview_section(revision_payload, check_payload, heads_payload, history_payload),
|
|
self.build_revision_section(revision_payload),
|
|
self.build_profile_section(config_payload),
|
|
self.build_check_section(check_payload),
|
|
self.build_heads_section(heads_payload),
|
|
self.build_history_section(history_payload),
|
|
self.build_init_entry_section(),
|
|
self.build_upgrade_entry_section(),
|
|
]
|
|
|
|
|
|
class DatabaseDetailAdapter(BaseDetailAdapter):
|
|
"""
|
|
数据库详情页适配器。
|
|
|
|
该适配器负责采集数据库相关 CLI 结果,并组装为 TUI 详情页快照。
|
|
页面私有的解析逻辑、分区构建逻辑和快照采集流程统一收口在该类中。
|
|
|
|
:param section_builder: 数据库详情分区构建器
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
section_builder: DatabaseSectionBuilder | None = None,
|
|
snapshot_collector: DatabaseDetailSnapshotCollector | None = None,
|
|
) -> None:
|
|
"""
|
|
初始化数据库详情页适配器。
|
|
|
|
:param section_builder: 数据库详情分区构建器
|
|
:param snapshot_collector: 数据库详情页数据采集器
|
|
:return: None
|
|
"""
|
|
super().__init__(
|
|
page_title='数据库',
|
|
search_view_key='database',
|
|
default_suggestions=[
|
|
'总览判断',
|
|
'迁移版本',
|
|
'连接信息',
|
|
'连通性检查',
|
|
'Heads 状态',
|
|
'历史版本',
|
|
'初始化预演入口',
|
|
'升级入口',
|
|
],
|
|
)
|
|
self.section_builder = section_builder or DatabaseSectionBuilder()
|
|
self.snapshot_collector = snapshot_collector or DatabaseDetailSnapshotCollector()
|
|
|
|
async def collect_snapshot(self, env: str, query: str = '') -> DetailPageSnapshot:
|
|
"""
|
|
采集数据库状态页只读快照。
|
|
|
|
:param env: 当前运行环境
|
|
:param query: 当前搜索词
|
|
:return: 页面快照
|
|
"""
|
|
source_payloads = await self.snapshot_collector.collect(env)
|
|
sections = self.section_builder.build_sections(
|
|
revision_payload=source_payloads.revision_payload,
|
|
check_payload=source_payloads.check_payload,
|
|
heads_payload=source_payloads.heads_payload,
|
|
history_payload=source_payloads.history_payload,
|
|
config_payload=source_payloads.config_payload,
|
|
)
|
|
return DetailPageSnapshot(
|
|
title='数据库',
|
|
subtitle=TUI_DIAGNOSTIC_SERVICE.build_database_diagnostic_subtitle(
|
|
source_payloads.revision_payload,
|
|
source_payloads.check_payload,
|
|
source_payloads.heads_payload,
|
|
),
|
|
sections=self.filter_sections(sections, query),
|
|
search=self.resolve_search_context(query),
|
|
)
|
|
|
|
|
|
DATABASE_DETAIL_ADAPTER = DatabaseDetailAdapter()
|