Files
ShadowsocksX-NG/ShadowsocksX-NG2/App/StatusMenuModel.swift
T
Qiu Yuzhou 398e8d84b6 feat(ng2): split agent and system proxy switches
实现 #60(ACL 1/8):agent 意图与系统代理意图拆为两个独立开关,
首次运行默认 agent 开、系统代理关;显式关闭 agent 持久化并在 GUI
重启后生效;关闭系统代理只恢复 NG2 持有的系统设置,本地 SOCKS/HTTP
继续监听;关闭 agent 先恢复系统设置再停止监听。无活动目标时 agent
以空服务器列表契约提供本地监听(isWellFormed 放行空 servers,上游
sslocal v1.25.0 接受);需要代理出口的模式在活动目标缺失或端点不健
康时系统代理保持待应用,条件恢复后随下次收敛自动应用。首页、状态菜
单与侧栏状态卡经同一代理控制快照展示/操作 agent 意图/运行状态与系
统代理意图/实际应用及 typed 错误。测试覆盖启动顺序、持久化、健康门
禁、ownership 冲突、无效活动目标与 GUI 重同步(含真实 sslocal 空监
听回环证据);替身测试不表述为真实系统写入验证。
2026-09-26 00:38:28 +08:00

81 lines
3.3 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/#60):从代理控制工作流
/// 的整体 snapshot 派生菜单数据。agent 与系统代理是两个独立状态面;不持有
/// AppKit 类型,纯逻辑可测;视觉与交互细节(勾选标记、禁用态)在
/// ProxyStatusMenu 呈现,不建自动测试。
enum StatusMenuModel {
/// 头部状态摘要:agent 运行状态、当前模式、活动目标(含点名失败细节)与
/// 系统代理实际应用状态。
struct Summary: Equatable {
/// Agent 开关意图(开关勾选态的绑定来源)。
let agentIntentEnabled: Bool
/// Agent 运行状态的「在跑」投影。
let isOn: Bool
/// Agent 运行状态文本。
let status: String
/// Agent 侧点名原因:运行失败或激活拒绝/目标清除(独立于运行状态)。
let detail: String?
/// 系统代理开关意图(开关勾选态的绑定来源)。
let systemProxyIntentEnabled: Bool
/// 系统代理实际应用状态文本(与 agent 运行状态分开呈现)。
let systemProxyStatus: String
/// 系统代理写入/恢复失败的点名原因;非失败态为 nil。
let systemProxyDetail: String?
let modeLabel: String
/// 活动目标从根到节点的显示名路径;未激活为 nil。
let targetPath: String?
}
/// 运行状态 → 摘要映射(与主窗口诊断区口径一致):输入是代理控制工作流
/// 的整体 snapshot(issue #47/#60),菜单不再拆装控制器或运行时字段。
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 .requiresApproval:
status = "等待允许后台代理"
case .serviceFailed:
status = "服务管理失败"
}
let agentDetail =
snapshot.runtime.failure.map { AppPresentation.message(for: $0) }
?? snapshot.activationFailure.map { AppPresentation.message(for: $0) }
let systemProxyStatus: String
let systemProxyDetail: String?
switch snapshot.systemProxyApplication {
case .idle:
systemProxyStatus = "系统代理:未接管"
systemProxyDetail = nil
case .pending:
systemProxyStatus = "系统代理:待应用"
systemProxyDetail = nil
case .applied:
systemProxyStatus = "系统代理:已应用"
systemProxyDetail = nil
case .failed(let facts):
systemProxyStatus = "系统代理:应用失败"
systemProxyDetail = AppPresentation.message(for: RuntimeFailureFacts.systemProxy(facts))
}
return Summary(
agentIntentEnabled: snapshot.agentIntentEnabled,
isOn: snapshot.runtime.isOn,
status: status,
detail: agentDetail,
systemProxyIntentEnabled: snapshot.systemProxyIntentEnabled,
systemProxyStatus: systemProxyStatus,
systemProxyDetail: systemProxyDetail,
modeLabel: snapshot.proxyMode.label,
targetPath: snapshot.activeTarget?.pathSummary)
}
}