Files
Qiu Yuzhou f6abd4343e feat(rules)!: add isolated routing presets
Add Mainland China proxy routing with independent rule workspaces.
Preserve existing routing choices through equivalent preset mappings.

BREAKING CHANGE: existing custom rules and enablement settings are
not migrated. Recreate them in the appropriate preset.
2026-10-25 14:32:12 +08:00

138 lines
5.9 KiB
Swift

import Foundation
/// 状态菜单的呈现模型:从代理控制工作流
/// 的整体 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
/// 同上但不带「系统代理:」前缀(首页等已有上下文的呈现面用)。
let systemProxyStateLabel: String
/// 系统代理写入/清理失败的点名原因;非失败态为 nil。
let systemProxyDetail: String?
let modeLabel: String
/// 活动目标从根到节点的显示名路径;未激活为 nil。
let targetPath: String?
}
/// 运行状态 → 摘要映射(与主窗口诊断区口径一致):输入是代理控制工作流
/// 的整体 snapshot,菜单不再拆装控制器或运行时字段。
static func summary(from snapshot: ProxyControlSnapshot) -> Summary {
let agentDetail =
snapshot.runtime.failure.map { AppPresentation.message(for: $0) }
?? snapshot.activationFailure.map { AppPresentation.message(for: $0) }
let systemProxy = systemProxyPresentation(
for: snapshot.systemProxyApplication,
approvalRequired: snapshot.systemProxyApprovalRequired,
backgroundUnavailable: snapshot.systemProxyInspection.backgroundUnavailable)
return Summary(
agentIntentEnabled: snapshot.agentIntentEnabled,
isOn: snapshot.runtime.isOn,
status: statusText(for: snapshot.runtime.status),
detail: agentDetail,
systemProxyIntentEnabled: snapshot.systemProxyIntentEnabled,
systemProxyStatus: String(localized: "系统代理:\(systemProxy.label)"),
systemProxyStateLabel: systemProxy.label,
systemProxyDetail: systemProxy.detail,
modeLabel: modeLabel(for: snapshot),
targetPath: snapshot.activeTarget?.pathSummary)
}
/// 模式标签:规则模式带上子选项。
private static func modeLabel(for snapshot: ProxyControlSnapshot) -> String {
let mode = snapshot.proxyMode
if mode == .rule {
return "\(mode.label) · \(snapshot.rulePreset.label)"
}
return mode.label
}
private static func statusText(for status: ProxyRuntimeStatus) -> String {
switch status {
case .off:
return String(localized: "代理客户端未运行")
case .starting:
return String(localized: "正在启动代理客户端…")
case .running:
return String(localized: "代理客户端运行中")
case .firewallBlocked:
return String(localized: "代理客户端运行中(局域网受阻)")
case .launchFailed:
return String(localized: "启动失败")
case .requiresApproval:
return String(localized: "等待用户批准代理客户端后台运行")
case .serviceFailed:
return String(localized: "服务管理失败")
}
}
// 系统代理应用状态 → 菜单文本;批准阻塞独立于操作结果。
// Exhaustive presentation mapping for the closed state family.
// swiftlint:disable:next cyclomatic_complexity
private static func systemProxyPresentation(
for application: SystemProxyApplicationFacts,
approvalRequired: Bool,
backgroundUnavailable: Bool
) -> (label: String, detail: String?) {
switch application {
case .idle:
return (String(localized: "未应用"), nil)
case .pending:
return approvalRequired ? (String(localized: "待批准"), nil) : (String(localized: "待应用"), nil)
case .changed: return (String(localized: "配置已改变"), nil)
case .applying: return (String(localized: "应用中"), nil)
case .repairing: return (String(localized: "修复中"), nil)
case .paused:
return (String(localized: "已暂停"), AppPresentation.systemProxyPausedNotice)
case .unreadable(let facts):
return (
String(localized: "无法检查"),
AppPresentation.systemProxyUnreadablePrefix
+ AppPresentation.message(for: RuntimeFailureFacts.systemProxy(facts))
)
case .repairFailed(let facts):
return (
String(localized: "修复失败"),
failureDetail(
facts, prefix: String(localized: "无法修复系统代理配置:"), approvalRequired: approvalRequired)
)
case .clearFailed(let facts):
return (
String(localized: "清除失败"),
failureDetail(
facts,
prefix: backgroundUnavailable
? String(localized: "后台代理不可用,系统代理配置未能清除。\n") : String(localized: "系统代理配置未能清除:"),
approvalRequired: approvalRequired)
)
case .applied:
return (String(localized: "已应用"), nil)
case .failed(let facts):
return (
String(localized: "应用失败"),
failureDetail(facts, prefix: "", approvalRequired: approvalRequired)
)
}
}
private static func failureDetail(
_ facts: SystemProxyFailureFacts, prefix: String, approvalRequired: Bool
) -> String? {
if approvalRequired && facts == .operation(.helperUnavailable) { return nil }
return prefix + AppPresentation.message(for: RuntimeFailureFacts.systemProxy(facts))
}
}