mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-15 18:59:52 +00:00
Issue #47: application-layer ProxyControlWorkflow wrapping the existing ProxyRuntimeController with a whole-published ProxyControlSnapshot (typed runtime facts, current mode, available modes, active-target facts with safe path summary, skipped-invalid-server count, HTTP export capability) and typed async commands (enable/disable/mode) that republish the full snapshot on completion. Production runtime adapter delegates to the controller without duplicating generation, health gating or system-proxy ownership; catalog target facts arrive through a narrow seam (CatalogTreeSnapshot.pathSummary) that never exposes the tree. Status menu now reads runtime state and issues proxy commands only via the workflow snapshot; catalog tree, activation and subscription actions stay on CatalogWorkflow. MainApp wires the production adapters once as composition root. No change to persistence order, failure taxonomy, mode-availability policy (#46), catalog commit sync (#40), settings (#44) or diagnostics (#43) seams. Tests: fake-adapter snapshot/command/boundary contract tests plus real-controller integration matrix (enable failures, approval, system proxy, mode persistence failure and no-op) and an integration-level status-menu model test proving the menu derives everything from the workflow. Unavailable-mode no-op and runtime-stop-failure items of the spec matrix are vacuous after ADR-0007 (no constructible unavailable ProxyMode; controller stop path has no distinct failure category).
51 lines
2.0 KiB
Swift
51 lines
2.0 KiB
Swift
import Foundation
|
||
|
||
/// 状态菜单八项白名单的呈现模型(spec #21 D11,issue #31/#47):从代理控制
|
||
/// 工作流的整体 snapshot 派生菜单数据。不持有 AppKit 类型,纯逻辑可测;
|
||
/// 视觉与交互细节(勾选标记、禁用态)在 ProxyStatusMenu 呈现,不建自动测试。
|
||
enum StatusMenuModel {
|
||
/// 头部状态摘要:运行状态、当前模式、活动目标(含点名失败细节)。
|
||
struct Summary: Equatable {
|
||
/// 代理开关的当前意图:true 为已开(菜单项呈现「停止代理」)。
|
||
let isOn: Bool
|
||
let status: String
|
||
/// 启动/激活/系统代理失败的点名原因,随状态摘要一并呈现。
|
||
let detail: String?
|
||
let modeLabel: String
|
||
/// 活动目标从根到节点的显示名路径;未激活为 nil。
|
||
let targetPath: String?
|
||
}
|
||
|
||
/// 运行状态 → 摘要映射(与主窗口诊断区口径一致):输入是代理控制工作流
|
||
/// 的整体 snapshot(issue #47),菜单不再拆装控制器或运行时字段。
|
||
static func summary(from snapshot: ProxyControlSnapshot) -> Summary {
|
||
let status: String
|
||
switch snapshot.runtime.status {
|
||
case .off:
|
||
status = "代理未运行"
|
||
case .starting:
|
||
status = "正在启动代理…"
|
||
case .running:
|
||
status = "代理运行中"
|
||
case .firewallBlocked:
|
||
status = "代理运行中(局域网受阻)"
|
||
case .launchFailed:
|
||
status = "启动失败"
|
||
case .activationFailed:
|
||
status = "无法启动"
|
||
case .requiresApproval:
|
||
status = "等待允许后台代理"
|
||
case .serviceFailed:
|
||
status = "服务管理失败"
|
||
case .systemProxyFailed:
|
||
status = "系统代理未应用"
|
||
}
|
||
return Summary(
|
||
isOn: snapshot.runtime.isOn,
|
||
status: status,
|
||
detail: snapshot.runtime.failure.map { AppPresentation.message(for: $0) },
|
||
modeLabel: snapshot.proxyMode.label,
|
||
targetPath: snapshot.activeTarget?.pathSummary)
|
||
}
|
||
}
|