Files
ShadowsocksX-NG/ShadowsocksX-NG2/App/StatusMenuModel.swift
T
Qiu Yuzhou 7951d5aa65 feat(ng2): add proxy control workflow as unified UI seam
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).
2026-09-23 20:54:08 +08:00

51 lines
2.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}