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) } }