调整部分前端布局和后端增删改逻辑
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
from cli.tui.actions.bootstrap import TUI_ACTION_EXECUTION_SERVICE, TUI_ACTION_PRESENTATION_SERVICE, TUI_ACTION_REGISTRY
|
||||
from cli.tui.actions.models import TuiActionResult, TuiActionSpec
|
||||
|
||||
__all__ = [
|
||||
'TUI_ACTION_EXECUTION_SERVICE',
|
||||
'TUI_ACTION_PRESENTATION_SERVICE',
|
||||
'TUI_ACTION_REGISTRY',
|
||||
'TuiActionResult',
|
||||
'TuiActionSpec',
|
||||
]
|
||||
@@ -0,0 +1,103 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.builders import TuiActionSpecFactory
|
||||
from cli.tui.actions.factories.cache import CacheActionTemplateFactory
|
||||
from cli.tui.actions.factories.gen import GenActionTemplateFactory
|
||||
from cli.tui.actions.factories.jobs import JobActionTemplateFactory
|
||||
from cli.tui.actions.factories.static import StaticActionTemplateFactory
|
||||
from cli.tui.actions.registry import TuiActionRegistry, TuiActionSlotResolver
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionRegistryBuilder:
|
||||
"""
|
||||
TUI 动作注册表构建器。
|
||||
|
||||
该对象负责装配各领域动作模板工厂,最终输出浏览页和详情页共用的
|
||||
动作注册表,避免模板常量继续散落在模块底部。
|
||||
|
||||
:param jobs: 任务页动作模板工厂
|
||||
:param cache: 缓存页动作模板工厂
|
||||
:param gen: 代码生成页动作模板工厂
|
||||
:param static: 静态页面动作模板工厂
|
||||
:param spec_factory: 动作规格构建器
|
||||
"""
|
||||
|
||||
jobs: JobActionTemplateFactory
|
||||
cache: CacheActionTemplateFactory
|
||||
gen: GenActionTemplateFactory
|
||||
static: StaticActionTemplateFactory
|
||||
spec_factory: TuiActionSpecFactory
|
||||
|
||||
def build(self) -> TuiActionRegistry:
|
||||
"""
|
||||
构建 TUI 动作注册表。
|
||||
|
||||
:return: 动作注册表
|
||||
"""
|
||||
return TuiActionRegistry(
|
||||
browser_resolvers={
|
||||
'jobs': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'primary': self.jobs.create_run_once_template(),
|
||||
'secondary': self.jobs.create_toggle_template(),
|
||||
'global': self.jobs.create_sync_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
'configs': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'global': self.static.create_config_sync_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
'cache': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'global': self.cache.create_clear_wizard_template(),
|
||||
'utility': self.cache.create_warmup_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
'gen': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'primary': self.gen.create_export_wizard_template(),
|
||||
'secondary': self.gen.create_import_wizard_template(),
|
||||
'global': self.gen.create_export_dry_run_template(),
|
||||
'utility': self.gen.create_sync_db_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
},
|
||||
detail_resolvers={
|
||||
'app': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'primary': self.static.create_app_run_template(),
|
||||
'global': self.static.create_app_run_wizard_template(),
|
||||
'utility': self.static.create_completion_install_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
'database': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'global': self.static.create_db_upgrade_wizard_template(),
|
||||
'utility': self.static.create_db_init_dry_run_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
'ops': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'primary': self.static.create_ops_ping_db_template(),
|
||||
'secondary': self.static.create_ops_ping_redis_template(),
|
||||
'global': self.static.create_prod_check_wizard_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
'crypto': TuiActionSlotResolver(
|
||||
slot_templates={
|
||||
'primary': self.static.create_crypto_keygen_template(),
|
||||
'global': self.static.create_crypto_rotate_dry_run_template(),
|
||||
},
|
||||
spec_factory=self.spec_factory,
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
from cli.tui.actions.assembly import TuiActionRegistryBuilder
|
||||
from cli.tui.actions.builders import TuiActionSpecFactory, TuiActionTemplateSupport
|
||||
from cli.tui.actions.execution import TuiActionExecutionService
|
||||
from cli.tui.actions.factories.cache import CacheActionTemplateFactory
|
||||
from cli.tui.actions.factories.gen import GenActionTemplateFactory
|
||||
from cli.tui.actions.factories.jobs import JobActionTemplateFactory
|
||||
from cli.tui.actions.factories.static import StaticActionTemplateFactory
|
||||
from cli.tui.actions.presentation import TuiActionPresentationService
|
||||
from cli.tui.capabilities import TUI_CAPABILITY_REGISTRY
|
||||
|
||||
_ACTION_SPEC_FACTORY = TuiActionSpecFactory()
|
||||
TUI_ACTION_EXECUTION_SERVICE = TuiActionExecutionService()
|
||||
|
||||
_ACTION_TEMPLATE_SUPPORT = TuiActionTemplateSupport(spec_factory=_ACTION_SPEC_FACTORY)
|
||||
_JOB_ACTION_TEMPLATE_FACTORY = JobActionTemplateFactory(support=_ACTION_TEMPLATE_SUPPORT)
|
||||
_CACHE_ACTION_TEMPLATE_FACTORY = CacheActionTemplateFactory(support=_ACTION_TEMPLATE_SUPPORT)
|
||||
_GEN_ACTION_TEMPLATE_FACTORY = GenActionTemplateFactory(support=_ACTION_TEMPLATE_SUPPORT)
|
||||
_STATIC_ACTION_TEMPLATE_FACTORY = StaticActionTemplateFactory(support=_ACTION_TEMPLATE_SUPPORT)
|
||||
_ACTION_REGISTRY_BUILDER = TuiActionRegistryBuilder(
|
||||
jobs=_JOB_ACTION_TEMPLATE_FACTORY,
|
||||
cache=_CACHE_ACTION_TEMPLATE_FACTORY,
|
||||
gen=_GEN_ACTION_TEMPLATE_FACTORY,
|
||||
static=_STATIC_ACTION_TEMPLATE_FACTORY,
|
||||
spec_factory=_ACTION_SPEC_FACTORY,
|
||||
)
|
||||
TUI_ACTION_REGISTRY = _ACTION_REGISTRY_BUILDER.build()
|
||||
|
||||
TUI_ACTION_PRESENTATION_SERVICE = TuiActionPresentationService(
|
||||
capability_registry=TUI_CAPABILITY_REGISTRY,
|
||||
action_registry=TUI_ACTION_REGISTRY,
|
||||
)
|
||||
@@ -0,0 +1,329 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cli.tui.actions.models import (
|
||||
ActionCommandBuilder,
|
||||
ActionExecutionMode,
|
||||
ActionSummaryBuilder,
|
||||
ActionTextBuilder,
|
||||
TuiActionSpec,
|
||||
)
|
||||
from cli.tui.copy import TUI_COPY
|
||||
from cli.utils import NESTED_CLI_SUPPORT, SHELL_TEXT_FORMATTER
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionSpecFactory:
|
||||
"""
|
||||
TUI 动作规格构建器。
|
||||
|
||||
该对象负责统一构建动作预览文本与 `TuiActionSpec`,
|
||||
避免各动作函数继续手工拼接重复的预览和规格对象。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def parse_record_suffix(record: BrowserRecordSnapshot, prefix: str) -> str:
|
||||
"""
|
||||
从记录键中提取指定前缀后的标识。
|
||||
|
||||
:param record: 浏览记录
|
||||
:param prefix: 键名前缀
|
||||
:return: 后缀标识
|
||||
"""
|
||||
raw_key = str(record.key).strip()
|
||||
token_prefix = f'{prefix}:'
|
||||
if not raw_key.startswith(token_prefix):
|
||||
return ''
|
||||
return raw_key[len(token_prefix) :].strip()
|
||||
|
||||
def build_command_preview_lines(
|
||||
self,
|
||||
*,
|
||||
env: str,
|
||||
summary_lines: list[str],
|
||||
command_args: tuple[str, ...],
|
||||
append_yes: bool = True,
|
||||
consequence_text: str = TUI_COPY.build_action_consequence_text('preview'),
|
||||
) -> list[str]:
|
||||
"""
|
||||
构建统一的动作预览文本。
|
||||
|
||||
:param env: 当前运行环境
|
||||
:param summary_lines: 业务摘要文本
|
||||
:param command_args: 对应 CLI 命令参数
|
||||
:param consequence_text: 结果影响说明
|
||||
:return: 预览文本
|
||||
"""
|
||||
nested_cli_arguments = [
|
||||
*command_args,
|
||||
f'--env={env}',
|
||||
'--output=json',
|
||||
]
|
||||
if append_yes:
|
||||
nested_cli_arguments.append('--yes')
|
||||
command = SHELL_TEXT_FORMATTER.format_shell_command(
|
||||
NESTED_CLI_SUPPORT.build_nested_cli_command(*nested_cli_arguments)
|
||||
)
|
||||
return [
|
||||
TUI_COPY.build_action_preview_title('summary'),
|
||||
*summary_lines,
|
||||
'',
|
||||
TUI_COPY.build_action_preview_title('env'),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('target_env'), env),
|
||||
'',
|
||||
TUI_COPY.build_action_preview_title('command'),
|
||||
command,
|
||||
'',
|
||||
TUI_COPY.build_action_preview_title('consequence'),
|
||||
consequence_text,
|
||||
]
|
||||
|
||||
def build_external_command_preview_lines(
|
||||
self,
|
||||
*,
|
||||
env: str,
|
||||
summary_lines: list[str],
|
||||
command_args: tuple[str, ...],
|
||||
append_yes: bool = True,
|
||||
consequence_text: str = TUI_COPY.build_action_consequence_text('wizard'),
|
||||
) -> list[str]:
|
||||
"""
|
||||
构建外部交互命令的预览文本。
|
||||
|
||||
:param env: 当前运行环境
|
||||
:param summary_lines: 业务摘要文本
|
||||
:param command_args: 对应 CLI 命令参数
|
||||
:param consequence_text: 结果影响说明
|
||||
:return: 预览文本
|
||||
"""
|
||||
return self.build_command_preview_lines(
|
||||
env=env,
|
||||
summary_lines=summary_lines,
|
||||
command_args=command_args,
|
||||
append_yes=append_yes,
|
||||
consequence_text=consequence_text,
|
||||
)
|
||||
|
||||
def build_nested_json_action(
|
||||
self,
|
||||
*,
|
||||
action_id: str,
|
||||
label: str,
|
||||
command_args: tuple[str, ...],
|
||||
env: str,
|
||||
summary_lines: list[str],
|
||||
preview_title: str | None = None,
|
||||
consequence_text: str = TUI_COPY.build_action_consequence_text('preview'),
|
||||
append_yes: bool = True,
|
||||
refresh_view: bool = True,
|
||||
) -> TuiActionSpec:
|
||||
"""
|
||||
构建标准 nested JSON 动作。
|
||||
|
||||
:param action_id: 动作唯一标识
|
||||
:param label: 动作显示名称
|
||||
:param command_args: 对应 CLI 命令参数
|
||||
:param env: 当前运行环境
|
||||
:param summary_lines: 业务摘要文本
|
||||
:param preview_title: 预览标题
|
||||
:param consequence_text: 结果影响说明
|
||||
:param refresh_view: 执行后是否刷新视图
|
||||
:return: 动作定义
|
||||
"""
|
||||
return TuiActionSpec(
|
||||
action_id=action_id,
|
||||
label=label,
|
||||
command_args=command_args,
|
||||
preview_title=preview_title or label,
|
||||
preview_lines=self.build_command_preview_lines(
|
||||
env=env,
|
||||
summary_lines=summary_lines,
|
||||
command_args=command_args,
|
||||
append_yes=append_yes,
|
||||
consequence_text=consequence_text,
|
||||
),
|
||||
append_yes=append_yes,
|
||||
refresh_view=refresh_view,
|
||||
)
|
||||
|
||||
def build_external_action(
|
||||
self,
|
||||
*,
|
||||
action_id: str,
|
||||
label: str,
|
||||
command_args: tuple[str, ...],
|
||||
env: str,
|
||||
summary_lines: list[str],
|
||||
preview_title: str | None = None,
|
||||
consequence_text: str = TUI_COPY.build_action_consequence_text('wizard'),
|
||||
append_yes: bool = True,
|
||||
refresh_view: bool = True,
|
||||
) -> TuiActionSpec:
|
||||
"""
|
||||
构建标准外部交互动作。
|
||||
|
||||
:param action_id: 动作唯一标识
|
||||
:param label: 动作显示名称
|
||||
:param command_args: 对应 CLI 命令参数
|
||||
:param env: 当前运行环境
|
||||
:param summary_lines: 业务摘要文本
|
||||
:param preview_title: 预览标题
|
||||
:param consequence_text: 结果影响说明
|
||||
:param refresh_view: 执行后是否刷新视图
|
||||
:return: 动作定义
|
||||
"""
|
||||
return TuiActionSpec(
|
||||
action_id=action_id,
|
||||
label=label,
|
||||
command_args=command_args,
|
||||
preview_title=preview_title or label,
|
||||
preview_lines=self.build_external_command_preview_lines(
|
||||
env=env,
|
||||
summary_lines=summary_lines,
|
||||
command_args=command_args,
|
||||
append_yes=append_yes,
|
||||
consequence_text=consequence_text,
|
||||
),
|
||||
execution_mode='external',
|
||||
append_yes=append_yes,
|
||||
refresh_view=refresh_view,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionTemplate:
|
||||
"""
|
||||
TUI 动作模板。
|
||||
|
||||
该模板将命令参数构建、摘要构建与执行模式收敛为统一对象,
|
||||
使动作注册层可以直接声明“某个槽位对应哪类动作”。
|
||||
|
||||
:param action_id: 动作唯一标识
|
||||
:param label: 动作显示名称
|
||||
:param command_builder: 命令参数构建器
|
||||
:param summary_builder: 预览摘要构建器
|
||||
:param action_id_builder: 动态动作标识构建器
|
||||
:param label_builder: 动态动作标题构建器
|
||||
:param preview_title: 预览标题
|
||||
:param consequence_text: 结果影响说明
|
||||
:param execution_mode: 执行模式
|
||||
:param append_yes: 是否在命令执行与预览中自动追加 `--yes`
|
||||
:param refresh_view: 执行后是否刷新视图
|
||||
:param preview_env_override: 预览中展示的环境名覆盖值
|
||||
"""
|
||||
|
||||
action_id: str
|
||||
label: str
|
||||
command_builder: ActionCommandBuilder
|
||||
summary_builder: ActionSummaryBuilder
|
||||
action_id_builder: ActionTextBuilder | None = None
|
||||
label_builder: ActionTextBuilder | None = None
|
||||
preview_title: str | None = None
|
||||
consequence_text: str = TUI_COPY.build_action_consequence_text('preview')
|
||||
execution_mode: ActionExecutionMode = 'nested_json'
|
||||
append_yes: bool = True
|
||||
refresh_view: bool = True
|
||||
preview_env_override: str | None = None
|
||||
|
||||
def build(
|
||||
self,
|
||||
*,
|
||||
record: BrowserRecordSnapshot | None,
|
||||
env: str,
|
||||
spec_factory: TuiActionSpecFactory,
|
||||
) -> TuiActionSpec | None:
|
||||
"""
|
||||
根据当前页面上下文构建动作定义。
|
||||
|
||||
:param record: 当前选中记录
|
||||
:param env: 当前运行环境
|
||||
:param spec_factory: 动作规格构建器
|
||||
:return: 动作定义
|
||||
"""
|
||||
command_args = self.command_builder(record, env)
|
||||
if command_args is None:
|
||||
return None
|
||||
preview_env = self.preview_env_override or env
|
||||
summary_lines = self.summary_builder(record, env)
|
||||
action_id = self.action_id_builder(record, env) if self.action_id_builder is not None else self.action_id
|
||||
label = self.label_builder(record, env) if self.label_builder is not None else self.label
|
||||
if self.execution_mode == 'external':
|
||||
return spec_factory.build_external_action(
|
||||
action_id=action_id,
|
||||
label=label,
|
||||
command_args=command_args,
|
||||
env=preview_env,
|
||||
summary_lines=summary_lines,
|
||||
preview_title=self.preview_title,
|
||||
consequence_text=self.consequence_text,
|
||||
append_yes=self.append_yes,
|
||||
refresh_view=self.refresh_view,
|
||||
)
|
||||
return spec_factory.build_nested_json_action(
|
||||
action_id=action_id,
|
||||
label=label,
|
||||
command_args=command_args,
|
||||
env=preview_env,
|
||||
summary_lines=summary_lines,
|
||||
preview_title=self.preview_title,
|
||||
consequence_text=self.consequence_text,
|
||||
append_yes=self.append_yes,
|
||||
refresh_view=self.refresh_view,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionTemplateSupport:
|
||||
"""
|
||||
TUI 动作模板共享构建支持。
|
||||
|
||||
该对象集中封装多个动作模板都会复用的记录解析与摘要拼装逻辑,
|
||||
避免这些基础能力继续散落在模块级函数中。
|
||||
|
||||
:param spec_factory: 动作规格构建器
|
||||
"""
|
||||
|
||||
spec_factory: TuiActionSpecFactory
|
||||
|
||||
def build_scope_purpose_summary(self, scope: str, purpose: str) -> list[str]:
|
||||
"""
|
||||
构建通用的作用范围与目的摘要。
|
||||
|
||||
:param scope: 作用范围说明
|
||||
:param purpose: 目的说明
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('scope'), scope),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('purpose'), purpose),
|
||||
]
|
||||
|
||||
def extract_job_id(self, record: BrowserRecordSnapshot | None) -> str | None:
|
||||
"""
|
||||
从任务记录中提取任务 ID。
|
||||
|
||||
:param record: 当前记录
|
||||
:return: 任务 ID
|
||||
"""
|
||||
if record is None:
|
||||
return None
|
||||
job_id = self.spec_factory.parse_record_suffix(record, 'job')
|
||||
return job_id if job_id.isdigit() else None
|
||||
|
||||
@staticmethod
|
||||
def require_record_title(record: BrowserRecordSnapshot | None) -> str | None:
|
||||
"""
|
||||
提取记录标题作为命令目标。
|
||||
|
||||
:param record: 当前记录
|
||||
:return: 标题文本
|
||||
"""
|
||||
if record is None:
|
||||
return None
|
||||
title = str(record.title).strip()
|
||||
return title or None
|
||||
@@ -0,0 +1,121 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.models import TuiActionResult, TuiActionSpec
|
||||
from cli.tui.copy import TUI_COPY
|
||||
from cli.utils import NESTED_CLI_SUPPORT, SHELL_TEXT_FORMATTER
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionExecutionService:
|
||||
"""
|
||||
TUI 动作执行服务。
|
||||
|
||||
该对象负责统一封装 nested JSON 动作、外部交互动作
|
||||
与结果文本行的收口逻辑。
|
||||
"""
|
||||
|
||||
def execute(self, spec: TuiActionSpec, env: str) -> TuiActionResult:
|
||||
"""
|
||||
执行指定 TUI 动作。
|
||||
|
||||
:param spec: 动作定义
|
||||
:param env: 当前运行环境
|
||||
:return: 动作执行结果
|
||||
"""
|
||||
command_arguments = [
|
||||
*spec.command_args,
|
||||
f'--env={env}',
|
||||
'--output=json',
|
||||
]
|
||||
if spec.append_yes:
|
||||
command_arguments.append('--yes')
|
||||
payload = NESTED_CLI_SUPPORT.run(*command_arguments, parse_json=True).payload
|
||||
return TuiActionResult(spec=spec, payload=payload)
|
||||
|
||||
def execute_external(self, spec: TuiActionSpec) -> TuiActionResult:
|
||||
"""
|
||||
在当前终端中执行交互式 TUI 动作。
|
||||
|
||||
:param spec: 动作定义
|
||||
:return: 动作执行结果
|
||||
"""
|
||||
completed = NESTED_CLI_SUPPORT.run_live(*spec.command_args)
|
||||
if completed.returncode == 0:
|
||||
message = '外部交互命令已执行完成'
|
||||
else:
|
||||
message = f'外部交互命令执行失败,退出码 {completed.returncode}'
|
||||
return TuiActionResult(
|
||||
spec=spec,
|
||||
external_exit_code=completed.returncode,
|
||||
external_message=message,
|
||||
)
|
||||
|
||||
def build_result_lines(self, result: TuiActionResult) -> list[str]:
|
||||
"""
|
||||
构建动作结果详情文本。
|
||||
|
||||
:param result: 动作执行结果
|
||||
:return: 结果文本行
|
||||
"""
|
||||
payload = result.payload if isinstance(result.payload, dict) else {}
|
||||
lines = [
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('name'),
|
||||
result.spec.label,
|
||||
),
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('outcome'),
|
||||
TUI_COPY.build_action_result_field_label('success')
|
||||
if result.ok
|
||||
else TUI_COPY.build_action_result_field_label('fail'),
|
||||
),
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('summary'),
|
||||
SHELL_TEXT_FORMATTER.truncate_text(result.message, 88),
|
||||
),
|
||||
]
|
||||
service_message = str(payload.get('serviceMessage', '') or '').strip()
|
||||
if service_message:
|
||||
lines.append(
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('service'),
|
||||
SHELL_TEXT_FORMATTER.truncate_text(service_message, 88),
|
||||
)
|
||||
)
|
||||
if payload.get('hint'):
|
||||
lines.append(
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('hint'),
|
||||
SHELL_TEXT_FORMATTER.truncate_text(str(payload.get('hint', '') or ''), 88),
|
||||
)
|
||||
)
|
||||
if payload.get('count') is not None:
|
||||
lines.append(
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('count'),
|
||||
str(payload.get('count')),
|
||||
)
|
||||
)
|
||||
if payload.get('jobId') is not None:
|
||||
lines.append(
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('job_id'),
|
||||
str(payload.get('jobId')),
|
||||
)
|
||||
)
|
||||
if result.external_exit_code is not None:
|
||||
lines.append(
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('exit_code'),
|
||||
str(result.external_exit_code),
|
||||
)
|
||||
)
|
||||
operation_label = str(payload.get('operationLabel', '') or '').strip()
|
||||
if operation_label and operation_label != result.spec.label:
|
||||
lines.append(
|
||||
TUI_COPY.build_action_result_message_line(
|
||||
TUI_COPY.build_action_result_field_label('operation'),
|
||||
SHELL_TEXT_FORMATTER.truncate_text(operation_label, 64),
|
||||
)
|
||||
)
|
||||
return lines
|
||||
@@ -0,0 +1,83 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.builders import TuiActionTemplate, TuiActionTemplateSupport
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
from cli.tui.copy import TUI_COPY
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CacheActionTemplateFactory:
|
||||
"""
|
||||
缓存页动作模板工厂。
|
||||
|
||||
该对象负责生成缓存浏览页相关动作模板,统一缓存清理向导和预热动作
|
||||
的命令与预览摘要构建。
|
||||
|
||||
:param support: 动作模板共享构建支持
|
||||
"""
|
||||
|
||||
support: TuiActionTemplateSupport
|
||||
|
||||
def build_clear_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建缓存清理向导命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
cache_name = record.title if record is not None else ''
|
||||
return (
|
||||
'wizard',
|
||||
'cache-clear',
|
||||
'--output=text',
|
||||
f'--default-env={env}',
|
||||
'--default-mode=cache-name',
|
||||
f'--default-cache-name={cache_name}',
|
||||
'--default-dry-run',
|
||||
)
|
||||
|
||||
def build_clear_summary(self, record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建缓存清理向导摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del record, env
|
||||
return self.support.build_scope_purpose_summary(
|
||||
'当前环境缓存清理流程',
|
||||
'进入向导后确认缓存名、键前缀和 dry-run 范围',
|
||||
)
|
||||
|
||||
def create_warmup_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建缓存预热动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='cache-warmup',
|
||||
label=TUI_COPY.build_action_label('cache_warmup'),
|
||||
command_builder=lambda record, env: ('cache', 'warmup'),
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('cache_warmup'),
|
||||
TUI_COPY.build_action_purpose_label('cache_warmup'),
|
||||
),
|
||||
)
|
||||
|
||||
def create_clear_wizard_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建缓存清理向导动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='wizard-cache-clear',
|
||||
label=TUI_COPY.build_action_label('cache_clear_wizard'),
|
||||
command_builder=self.build_clear_command,
|
||||
summary_builder=self.build_clear_summary,
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
)
|
||||
@@ -0,0 +1,254 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.builders import TuiActionTemplate, TuiActionTemplateSupport
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
from cli.tui.copy import TUI_COPY
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GenActionTemplateFactory:
|
||||
"""
|
||||
代码生成页动作模板工厂。
|
||||
|
||||
该对象负责生成代码生成浏览页相关动作模板,统一记录标题提取、
|
||||
导出导入向导和表结构同步动作的构建逻辑。
|
||||
|
||||
:param support: 动作模板共享构建支持
|
||||
"""
|
||||
|
||||
support: TuiActionTemplateSupport
|
||||
|
||||
def build_export_wizard_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...] | None:
|
||||
"""
|
||||
构建代码生成导出向导命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
table_name = self.support.require_record_title(record)
|
||||
if table_name is None:
|
||||
return None
|
||||
return (
|
||||
'wizard',
|
||||
'gen-export',
|
||||
'--output=text',
|
||||
f'--default-env={env}',
|
||||
f'--default-table-names={table_name}',
|
||||
'--default-mode=zip',
|
||||
'--default-dry-run',
|
||||
)
|
||||
|
||||
def build_import_wizard_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...] | None:
|
||||
"""
|
||||
构建代码生成导入向导命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
table_name = self.support.require_record_title(record)
|
||||
if table_name is None:
|
||||
return None
|
||||
return (
|
||||
'wizard',
|
||||
'gen-import',
|
||||
'--output=text',
|
||||
f'--default-env={env}',
|
||||
f'--default-table-names={table_name}',
|
||||
'--default-dry-run',
|
||||
)
|
||||
|
||||
def build_export_dry_run_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...] | None:
|
||||
"""
|
||||
构建代码生成导出 dry-run 命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del env
|
||||
table_name = self.support.require_record_title(record)
|
||||
if table_name is None:
|
||||
return None
|
||||
return ('gen', 'export', table_name, '--dry-run', '--mode=zip')
|
||||
|
||||
def build_sync_db_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...] | None:
|
||||
"""
|
||||
构建代码生成表结构同步命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del env
|
||||
table_name = self.support.require_record_title(record)
|
||||
if table_name is None:
|
||||
return None
|
||||
return ('gen', 'sync-db', table_name)
|
||||
|
||||
@staticmethod
|
||||
def build_export_wizard_summary(record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建代码生成导出向导摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return []
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('scope'), f'当前业务表 {record.title}'
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('current_status'),
|
||||
record.summary,
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('purpose'),
|
||||
'进入向导后选择业务表、输出目录和覆盖策略',
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_import_wizard_summary(record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建代码生成导入向导摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return []
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('scope'), f'当前物理表 {record.title}'
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('current_status'),
|
||||
record.summary,
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('purpose'),
|
||||
'进入向导后确认物理表列表,并先执行 dry-run 导入预演',
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_export_dry_run_summary(record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建代码生成导出预演摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return []
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('scope'),
|
||||
TUI_COPY.build_action_scope_label('gen_export_dry_run'),
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('job'), record.title),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('current_status'),
|
||||
record.summary,
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('purpose'),
|
||||
TUI_COPY.build_action_purpose_label('gen_export_dry_run'),
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_sync_db_summary(record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建代码生成表结构同步摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return []
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('scope'),
|
||||
TUI_COPY.build_action_scope_label('gen_sync_db'),
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('job'), record.title),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('current_status'),
|
||||
record.summary,
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('purpose'),
|
||||
TUI_COPY.build_action_purpose_label('gen_sync_db'),
|
||||
),
|
||||
]
|
||||
|
||||
def create_export_wizard_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建代码生成导出向导动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='wizard-gen-export',
|
||||
label=TUI_COPY.build_action_label('gen_export_wizard'),
|
||||
command_builder=self.build_export_wizard_command,
|
||||
summary_builder=self.build_export_wizard_summary,
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
refresh_view=False,
|
||||
)
|
||||
|
||||
def create_import_wizard_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建代码生成导入向导动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='wizard-gen-import',
|
||||
label=TUI_COPY.build_action_label('gen_import_wizard'),
|
||||
command_builder=self.build_import_wizard_command,
|
||||
summary_builder=self.build_import_wizard_summary,
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
refresh_view=False,
|
||||
)
|
||||
|
||||
def create_export_dry_run_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建代码生成导出预演动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='gen-export-dry-run',
|
||||
label=TUI_COPY.build_action_label('gen_export_dry_run'),
|
||||
command_builder=self.build_export_dry_run_command,
|
||||
summary_builder=self.build_export_dry_run_summary,
|
||||
)
|
||||
|
||||
def create_sync_db_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建代码生成表结构同步动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='gen-sync-db',
|
||||
label=TUI_COPY.build_action_label('gen_sync_db'),
|
||||
command_builder=self.build_sync_db_command,
|
||||
summary_builder=self.build_sync_db_summary,
|
||||
)
|
||||
@@ -0,0 +1,171 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.builders import TuiActionTemplate, TuiActionTemplateSupport
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
from cli.tui.copy import TUI_COPY
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JobActionTemplateFactory:
|
||||
"""
|
||||
任务页动作模板工厂。
|
||||
|
||||
该对象负责生成任务浏览页相关动作模板,收口任务 ID 解析、
|
||||
暂停/恢复切换和摘要拼装逻辑。
|
||||
|
||||
:param support: 动作模板共享构建支持
|
||||
"""
|
||||
|
||||
support: TuiActionTemplateSupport
|
||||
|
||||
def build_run_once_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...] | None:
|
||||
"""
|
||||
构建任务执行一次命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del env
|
||||
job_id = self.support.extract_job_id(record)
|
||||
if job_id is None:
|
||||
return None
|
||||
return ('job', 'run-once', job_id)
|
||||
|
||||
def build_run_once_summary(self, record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建任务执行一次动作摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return []
|
||||
job_id = self.support.extract_job_id(record) or ''
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('job'), record.title),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('job_id'), job_id),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('current_status'),
|
||||
record.summary,
|
||||
),
|
||||
]
|
||||
|
||||
def build_toggle_command(self, record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...] | None:
|
||||
"""
|
||||
构建任务暂停或恢复命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return None
|
||||
job_id = self.support.extract_job_id(record)
|
||||
if job_id is None:
|
||||
return None
|
||||
if record.status == 'warn':
|
||||
return ('job', 'resume', job_id)
|
||||
return ('job', 'pause', job_id)
|
||||
|
||||
def build_toggle_summary(self, record: BrowserRecordSnapshot | None, env: str) -> list[str]:
|
||||
"""
|
||||
构建任务暂停或恢复动作摘要。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 预览摘要
|
||||
"""
|
||||
del env
|
||||
if record is None:
|
||||
return []
|
||||
job_id = self.support.extract_job_id(record) or ''
|
||||
label = (
|
||||
TUI_COPY.build_action_label('job_resume')
|
||||
if record.status == 'warn'
|
||||
else TUI_COPY.build_action_label('job_pause')
|
||||
)
|
||||
return [
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('job'), record.title),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('job_id'), job_id),
|
||||
TUI_COPY.build_labeled_value_line(
|
||||
TUI_COPY.build_action_preview_field_label('current_status'),
|
||||
record.summary,
|
||||
),
|
||||
TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('target_action'), label),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_toggle_action_id(record: BrowserRecordSnapshot | None, env: str) -> str:
|
||||
"""
|
||||
构建任务暂停或恢复动作标识。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 动作标识
|
||||
"""
|
||||
del env
|
||||
if record is not None and record.status == 'warn':
|
||||
return 'job-resume'
|
||||
return 'job-pause'
|
||||
|
||||
@staticmethod
|
||||
def build_toggle_label(record: BrowserRecordSnapshot | None, env: str) -> str:
|
||||
"""
|
||||
构建任务暂停或恢复动作标题。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 动作标题
|
||||
"""
|
||||
del env
|
||||
if record is not None and record.status == 'warn':
|
||||
return TUI_COPY.build_action_label('job_resume')
|
||||
return TUI_COPY.build_action_label('job_pause')
|
||||
|
||||
def create_run_once_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建任务执行一次动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='job-run-once',
|
||||
label=TUI_COPY.build_action_label('job_run_once'),
|
||||
command_builder=self.build_run_once_command,
|
||||
summary_builder=self.build_run_once_summary,
|
||||
)
|
||||
|
||||
def create_toggle_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建任务暂停或恢复动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='job-toggle',
|
||||
label=TUI_COPY.build_action_label('job_pause'),
|
||||
command_builder=self.build_toggle_command,
|
||||
summary_builder=self.build_toggle_summary,
|
||||
action_id_builder=self.build_toggle_action_id,
|
||||
label_builder=self.build_toggle_label,
|
||||
)
|
||||
|
||||
def create_sync_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建任务同步动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='job-sync',
|
||||
label=TUI_COPY.build_action_label('job_sync'),
|
||||
command_builder=lambda record, env: ('job', 'sync'),
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('job_sync'),
|
||||
TUI_COPY.build_action_purpose_label('job_sync'),
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,385 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.builders import TuiActionTemplate, TuiActionTemplateSupport
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
from cli.tui.copy import TUI_COPY
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StaticActionTemplateFactory:
|
||||
"""
|
||||
静态页面动作模板工厂。
|
||||
|
||||
该对象负责生成不依赖当前记录内容的页面动作模板,统一详情页和
|
||||
部分浏览页的固定动作定义。
|
||||
|
||||
:param support: 动作模板共享构建支持
|
||||
"""
|
||||
|
||||
support: TuiActionTemplateSupport
|
||||
|
||||
@staticmethod
|
||||
def build_job_sync_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建任务同步命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('job', 'sync')
|
||||
|
||||
@staticmethod
|
||||
def build_config_sync_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建配置同步命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('config', 'sync-cache')
|
||||
|
||||
@staticmethod
|
||||
def build_cache_warmup_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建缓存预热命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('cache', 'warmup')
|
||||
|
||||
@staticmethod
|
||||
def build_db_upgrade_wizard_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建数据库升级向导命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record
|
||||
return (
|
||||
'wizard',
|
||||
'db-upgrade',
|
||||
'--output=text',
|
||||
f'--default-env={env}',
|
||||
'--default-revision=head',
|
||||
'--default-dry-run',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_db_init_dry_run_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建数据库初始化预演命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('db', 'init', '--dry-run')
|
||||
|
||||
@staticmethod
|
||||
def build_app_run_wizard_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建应用启动向导命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('wizard', 'app-run')
|
||||
|
||||
@staticmethod
|
||||
def build_completion_install_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建补全安装命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('completion', 'install', '--activate')
|
||||
|
||||
@staticmethod
|
||||
def build_prod_check_wizard_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建生产巡检向导命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record
|
||||
return (
|
||||
'wizard',
|
||||
'prod-check',
|
||||
'--output=text',
|
||||
f'--default-env={env}',
|
||||
'--default-include-config',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_crypto_rotate_dry_run_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建加密轮换预演命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('crypto', 'rotate', '--dry-run')
|
||||
|
||||
@staticmethod
|
||||
def build_app_run_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建应用直接启动命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record
|
||||
return ('app', 'run', f'--env={env}')
|
||||
|
||||
@staticmethod
|
||||
def build_crypto_keygen_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建加密密钥生成命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record
|
||||
return ('crypto', 'keygen', f'--env={env}', '--output=text')
|
||||
|
||||
@staticmethod
|
||||
def build_ops_ping_db_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建数据库探活命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('ops', 'ping-db')
|
||||
|
||||
@staticmethod
|
||||
def build_ops_ping_redis_command(record: BrowserRecordSnapshot | None, env: str) -> tuple[str, ...]:
|
||||
"""
|
||||
构建 Redis 探活命令参数。
|
||||
|
||||
:param record: 当前记录
|
||||
:param env: 当前运行环境
|
||||
:return: 命令参数
|
||||
"""
|
||||
del record, env
|
||||
return ('ops', 'ping-redis')
|
||||
|
||||
def create_config_sync_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建配置同步动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='config-sync-cache',
|
||||
label=TUI_COPY.build_action_label('config_sync'),
|
||||
command_builder=self.build_config_sync_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('config_sync'),
|
||||
TUI_COPY.build_action_purpose_label('config_sync'),
|
||||
),
|
||||
)
|
||||
|
||||
def create_db_upgrade_wizard_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建数据库升级向导动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='wizard-db-upgrade',
|
||||
label=TUI_COPY.build_action_label('db_upgrade_wizard'),
|
||||
command_builder=self.build_db_upgrade_wizard_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
'当前环境数据库升级流程',
|
||||
'进入向导后确认目标 revision、环境和 dry-run 预演',
|
||||
),
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
)
|
||||
|
||||
def create_db_init_dry_run_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建数据库初始化预演动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='db-init-dry-run',
|
||||
label=TUI_COPY.build_action_label('db_init_dry_run'),
|
||||
command_builder=self.build_db_init_dry_run_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('db_init_dry_run'),
|
||||
TUI_COPY.build_action_purpose_label('db_init_dry_run'),
|
||||
),
|
||||
append_yes=False,
|
||||
)
|
||||
|
||||
def create_app_run_wizard_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建应用启动向导动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='wizard-app-run',
|
||||
label=TUI_COPY.build_action_label('app_run_wizard'),
|
||||
command_builder=self.build_app_run_wizard_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
'当前环境应用启动流程',
|
||||
'进入向导后确认环境,并决定是否先执行启动前检查',
|
||||
),
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
refresh_view=False,
|
||||
)
|
||||
|
||||
def create_completion_install_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建补全安装动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='completion-install',
|
||||
label=TUI_COPY.build_action_label('completion_install'),
|
||||
command_builder=self.build_completion_install_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('completion_install'),
|
||||
TUI_COPY.build_action_purpose_label('completion_install'),
|
||||
),
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('external'),
|
||||
refresh_view=False,
|
||||
preview_env_override='-',
|
||||
)
|
||||
|
||||
def create_prod_check_wizard_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建生产巡检向导动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='wizard-prod-check',
|
||||
label=TUI_COPY.build_action_label('prod_check_wizard'),
|
||||
command_builder=self.build_prod_check_wizard_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
'当前环境生产巡检流程',
|
||||
'进入向导后统一检查数据库、缓存和运行配置',
|
||||
),
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
)
|
||||
|
||||
def create_crypto_rotate_dry_run_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建加密轮换预演动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='crypto-rotate-dry-run',
|
||||
label=TUI_COPY.build_action_label('crypto_rotate_dry_run'),
|
||||
command_builder=self.build_crypto_rotate_dry_run_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
'当前环境传输加密轮换预演',
|
||||
'仅生成轮换辅助结果,不直接写入新密钥配置',
|
||||
),
|
||||
append_yes=False,
|
||||
)
|
||||
|
||||
def create_app_run_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建应用直接启动动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='app-run',
|
||||
label=TUI_COPY.build_action_label('app_run'),
|
||||
command_builder=self.build_app_run_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
'当前环境应用直接启动流程',
|
||||
'确认后会在当前终端直接启动当前 FastAPI 应用,并持续占用终端会话',
|
||||
),
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
refresh_view=False,
|
||||
)
|
||||
|
||||
def create_crypto_keygen_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建密钥生成动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='crypto-keygen',
|
||||
label=TUI_COPY.build_action_label('crypto_keygen'),
|
||||
command_builder=self.build_crypto_keygen_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
'当前环境新密钥生成流程',
|
||||
'在终端中直接输出新密钥材料和环境变量补丁建议',
|
||||
),
|
||||
execution_mode='external',
|
||||
consequence_text=TUI_COPY.build_action_consequence_text('wizard'),
|
||||
refresh_view=False,
|
||||
)
|
||||
|
||||
def create_ops_ping_db_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建数据库探活动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='ops-ping-db',
|
||||
label=TUI_COPY.build_action_label('ops_ping_db'),
|
||||
command_builder=self.build_ops_ping_db_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('ops_ping_db'),
|
||||
TUI_COPY.build_action_purpose_label('ops_ping_db'),
|
||||
),
|
||||
append_yes=False,
|
||||
)
|
||||
|
||||
def create_ops_ping_redis_template(self) -> TuiActionTemplate:
|
||||
"""
|
||||
创建 Redis 探活动作模板。
|
||||
|
||||
:return: 动作模板
|
||||
"""
|
||||
return TuiActionTemplate(
|
||||
action_id='ops-ping-redis',
|
||||
label=TUI_COPY.build_action_label('ops_ping_redis'),
|
||||
command_builder=self.build_ops_ping_redis_command,
|
||||
summary_builder=lambda record, env: self.support.build_scope_purpose_summary(
|
||||
TUI_COPY.build_action_scope_label('ops_ping_redis'),
|
||||
TUI_COPY.build_action_purpose_label('ops_ping_redis'),
|
||||
),
|
||||
append_yes=False,
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from cli.tui.adapters.models import TUI_ADAPTER_MODEL_RENDERER, BrowserRecordSnapshot
|
||||
|
||||
ActionSlot = Literal['primary', 'secondary', 'global', 'utility']
|
||||
ActionExecutionMode = Literal['nested_json', 'external']
|
||||
ActionCommandBuilder = Callable[[BrowserRecordSnapshot | None, str], tuple[str, ...] | None]
|
||||
ActionSummaryBuilder = Callable[[BrowserRecordSnapshot | None, str], list[str]]
|
||||
ActionTextBuilder = Callable[[BrowserRecordSnapshot | None, str], str]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionSpec:
|
||||
"""
|
||||
TUI 低风险动作定义。
|
||||
|
||||
:param action_id: 动作唯一标识
|
||||
:param label: 动作显示名称
|
||||
:param command_args: 对应 CLI 命令参数
|
||||
:param preview_title: 预览弹窗标题
|
||||
:param preview_lines: 预览摘要文本
|
||||
:param append_yes: 执行 nested JSON 动作时是否自动追加 `--yes`
|
||||
:param refresh_view: 执行完成后是否刷新当前页面
|
||||
"""
|
||||
|
||||
action_id: str
|
||||
label: str
|
||||
command_args: tuple[str, ...]
|
||||
preview_title: str
|
||||
preview_lines: list[str]
|
||||
execution_mode: ActionExecutionMode = 'nested_json'
|
||||
append_yes: bool = True
|
||||
refresh_view: bool = True
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionResult:
|
||||
"""
|
||||
TUI 动作执行结果。
|
||||
|
||||
:param spec: 动作定义
|
||||
:param payload: CLI JSON 负载
|
||||
"""
|
||||
|
||||
spec: TuiActionSpec
|
||||
payload: dict[str, object] | None = None
|
||||
external_exit_code: int | None = None
|
||||
external_message: str | None = None
|
||||
|
||||
@property
|
||||
def ok(self) -> bool:
|
||||
"""
|
||||
判断动作是否执行成功。
|
||||
|
||||
:return: 是否成功
|
||||
"""
|
||||
if self.external_exit_code is not None:
|
||||
return self.external_exit_code == 0
|
||||
return bool(isinstance(self.payload, dict) and self.payload.get('ok', False))
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
获取动作结果摘要。
|
||||
|
||||
:return: 结果摘要
|
||||
"""
|
||||
if self.external_message is not None:
|
||||
return self.external_message
|
||||
return TUI_ADAPTER_MODEL_RENDERER.extract_payload_message(self.payload)
|
||||
@@ -0,0 +1,110 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.tui.actions.models import TuiActionResult
|
||||
from cli.tui.actions.registry import TuiActionRegistry
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
from cli.tui.capabilities import TuiCapabilityRegistry
|
||||
from cli.tui.copy import TUI_COPY
|
||||
from cli.tui.keymaps import TUI_KEYMAP_REGISTRY
|
||||
from cli.utils import SHELL_TEXT_FORMATTER
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionPresentationService:
|
||||
"""
|
||||
TUI 动作展示服务。
|
||||
|
||||
该对象负责动作提示、动作列表和动作结果通知等展示层文本构建,
|
||||
避免继续保留模块级桥接函数。
|
||||
"""
|
||||
|
||||
capability_registry: TuiCapabilityRegistry
|
||||
action_registry: TuiActionRegistry
|
||||
|
||||
def build_browser_action_hint(self, view_key: str) -> str:
|
||||
"""
|
||||
构建浏览页动作提示文本。
|
||||
|
||||
:param view_key: 当前页面视图标识
|
||||
:return: 动作提示文本
|
||||
"""
|
||||
capability_labels = [
|
||||
f'[{capability.key}] {capability.hint_label}'
|
||||
for capability in self.capability_registry.get_browser_capabilities(view_key)
|
||||
]
|
||||
interaction_text = TUI_COPY.build_browser_action_hint_text(
|
||||
view_key, TUI_KEYMAP_REGISTRY.browser_interaction_hint
|
||||
)
|
||||
return TUI_COPY.build_capability_hint_text(
|
||||
capability_labels,
|
||||
interaction_text,
|
||||
fallback='{interaction_hint}',
|
||||
)
|
||||
|
||||
def build_detail_action_hint(self, view_key: str) -> str:
|
||||
"""
|
||||
构建详情页动作提示文本。
|
||||
|
||||
:param view_key: 当前页面视图标识
|
||||
:return: 动作提示文本
|
||||
"""
|
||||
capability_labels = [
|
||||
f'[{capability.key}] {capability.hint_label}'
|
||||
for capability in self.capability_registry.get_detail_capabilities(view_key)
|
||||
]
|
||||
interaction_text = TUI_COPY.build_detail_action_hint_text(
|
||||
view_key, TUI_KEYMAP_REGISTRY.browser_interaction_hint
|
||||
)
|
||||
return TUI_COPY.build_capability_hint_text(
|
||||
capability_labels,
|
||||
interaction_text,
|
||||
fallback='{interaction_hint}',
|
||||
)
|
||||
|
||||
def build_browser_action_lines(
|
||||
self,
|
||||
*,
|
||||
view_key: str,
|
||||
record: BrowserRecordSnapshot | None,
|
||||
env: str,
|
||||
) -> list[str]:
|
||||
"""
|
||||
构建当前页面可执行动作列表。
|
||||
|
||||
:param view_key: 当前页面视图标识
|
||||
:param record: 当前选中记录
|
||||
:param env: 当前运行环境
|
||||
:return: 动作文本行
|
||||
"""
|
||||
lines: list[str] = []
|
||||
for capability in self.capability_registry.get_browser_capabilities(view_key):
|
||||
slot = capability.slot # type: ignore[assignment]
|
||||
action = self.action_registry.resolve_browser_action(
|
||||
view_key=view_key,
|
||||
slot=slot,
|
||||
record=record,
|
||||
env=env,
|
||||
)
|
||||
if action is None:
|
||||
continue
|
||||
lines.append(TUI_COPY.build_action_line(capability.key, action.label))
|
||||
return lines or [TUI_COPY.build_action_empty_line()]
|
||||
|
||||
@staticmethod
|
||||
def build_action_result_message(result: TuiActionResult) -> str:
|
||||
"""
|
||||
构建动作执行结果通知文本。
|
||||
|
||||
:param result: 动作执行结果
|
||||
:return: 通知文本
|
||||
"""
|
||||
outcome = (
|
||||
TUI_COPY.build_action_result_field_label('success')
|
||||
if result.ok
|
||||
else TUI_COPY.build_action_result_field_label('fail')
|
||||
)
|
||||
return TUI_COPY.build_action_result_toast(
|
||||
result.spec.label,
|
||||
outcome,
|
||||
SHELL_TEXT_FORMATTER.truncate_text(result.message, 72),
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cli.tui.actions.builders import TuiActionSpecFactory, TuiActionTemplate
|
||||
from cli.tui.actions.models import ActionSlot, TuiActionSpec
|
||||
from cli.tui.adapters.models import BrowserRecordSnapshot
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionRegistry:
|
||||
"""
|
||||
TUI 动作注册表。
|
||||
|
||||
该注册表负责集中维护浏览页和详情页的动作解析器,从而将
|
||||
原本散落的条件分发收敛为可声明的映射关系。
|
||||
|
||||
:param browser_resolvers: 浏览页动作槽位解析器映射
|
||||
:param detail_resolvers: 详情页动作槽位解析器映射
|
||||
"""
|
||||
|
||||
browser_resolvers: dict[str, TuiActionSlotResolver]
|
||||
detail_resolvers: dict[str, TuiActionSlotResolver]
|
||||
|
||||
def resolve_browser_action(
|
||||
self,
|
||||
*,
|
||||
view_key: str,
|
||||
slot: ActionSlot,
|
||||
record: BrowserRecordSnapshot | None,
|
||||
env: str,
|
||||
) -> TuiActionSpec | None:
|
||||
"""
|
||||
解析浏览页动作。
|
||||
|
||||
:param view_key: 当前页面视图标识
|
||||
:param slot: 动作槽位
|
||||
:param record: 当前选中记录
|
||||
:param env: 当前运行环境
|
||||
:return: 动作定义
|
||||
"""
|
||||
resolver = self.browser_resolvers.get(view_key)
|
||||
if resolver is None:
|
||||
return None
|
||||
return resolver.resolve(slot=slot, record=record, env=env)
|
||||
|
||||
def resolve_detail_action(
|
||||
self,
|
||||
*,
|
||||
view_key: str,
|
||||
slot: ActionSlot,
|
||||
env: str,
|
||||
) -> TuiActionSpec | None:
|
||||
"""
|
||||
解析详情页动作。
|
||||
|
||||
:param view_key: 当前页面视图标识
|
||||
:param slot: 动作槽位
|
||||
:param env: 当前运行环境
|
||||
:return: 动作定义
|
||||
"""
|
||||
resolver = self.detail_resolvers.get(view_key)
|
||||
if resolver is None:
|
||||
return None
|
||||
return resolver.resolve(slot=slot, record=None, env=env)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TuiActionSlotResolver:
|
||||
"""
|
||||
TUI 动作槽位解析器。
|
||||
|
||||
该对象将一个页面支持的槽位动作集中定义为映射关系,避免继续在
|
||||
`resolve_*_action` 中堆叠条件分支。
|
||||
|
||||
:param slot_templates: 槽位到动作模板的映射
|
||||
:param spec_factory: 动作规格构建器
|
||||
"""
|
||||
|
||||
slot_templates: dict[ActionSlot, TuiActionTemplate]
|
||||
spec_factory: TuiActionSpecFactory
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
*,
|
||||
slot: ActionSlot,
|
||||
record: BrowserRecordSnapshot | None,
|
||||
env: str,
|
||||
) -> TuiActionSpec | None:
|
||||
"""
|
||||
解析指定槽位的动作定义。
|
||||
|
||||
:param slot: 动作槽位
|
||||
:param record: 当前选中记录
|
||||
:param env: 当前运行环境
|
||||
:return: 动作定义
|
||||
"""
|
||||
template = self.slot_templates.get(slot)
|
||||
if template is None:
|
||||
return None
|
||||
return template.build(record=record, env=env, spec_factory=self.spec_factory)
|
||||
Reference in New Issue
Block a user