Network panel: core on/off (TUN) toggle without a password, Amnezia conflict guard, install hint for the netd service
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,9 @@ final class NetCore: ObservableObject {
|
|||||||
@Published var autoPick = "" // what "auto" chose
|
@Published var autoPick = "" // what "auto" chose
|
||||||
@Published var clients: [NetClient] = []
|
@Published var clients: [NetClient] = []
|
||||||
@Published var busy = false
|
@Published var busy = false
|
||||||
|
/// TUN on = the core carries the Mac's traffic; off = idle (e.g. back on AmneziaVPN).
|
||||||
|
@Published var tunOn = false
|
||||||
|
@Published var lastError: String?
|
||||||
|
|
||||||
/// Delays measured by the group test (covers the subscription nodes too).
|
/// Delays measured by the group test (covers the subscription nodes too).
|
||||||
private var measured: [String: Int] = [:]
|
private var measured: [String: Int] = [:]
|
||||||
@@ -80,6 +83,9 @@ final class NetCore: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
running = true
|
running = true
|
||||||
|
if let cfg = await request("/configs") as? [String: Any], let tun = cfg["tun"] as? [String: Any] {
|
||||||
|
tunOn = tun["enable"] as? Bool ?? false
|
||||||
|
}
|
||||||
// Subscription (VLESS) nodes keep their history in the provider, not in /proxies.
|
// Subscription (VLESS) nodes keep their history in the provider, not in /proxies.
|
||||||
var providerDelay: [String: Int] = [:]
|
var providerDelay: [String: Int] = [:]
|
||||||
if let p = await request("/providers/proxies/vless-cluster") as? [String: Any],
|
if let p = await request("/providers/proxies/vless-cluster") as? [String: Any],
|
||||||
@@ -141,6 +147,26 @@ final class NetCore: ObservableObject {
|
|||||||
await refresh()
|
await refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var amneziaRunning: Bool {
|
||||||
|
NSWorkspace.shared.runningApplications.contains {
|
||||||
|
($0.bundleIdentifier ?? "").lowercased().contains("amnezia") || ($0.localizedName ?? "").contains("Amnezia")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns traffic capture on/off without a password (the core keeps running).
|
||||||
|
func setTun(_ on: Bool) async {
|
||||||
|
if on && Self.amneziaRunning {
|
||||||
|
lastError = "Сначала выключи AmneziaVPN — иначе она и ядро подерутся за маршрут."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastError = nil
|
||||||
|
tunOn = on
|
||||||
|
_ = await request("/configs", method: "PATCH", body: ["tun": ["enable": on]], timeout: 8)
|
||||||
|
SoundEngine.shared.play(on ? "pop" : "close")
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
await refresh()
|
||||||
|
}
|
||||||
|
|
||||||
static func pretty(_ name: String) -> (String, String) {
|
static func pretty(_ name: String) -> (String, String) {
|
||||||
switch name {
|
switch name {
|
||||||
case "auto": return ("Авто", "первый живой")
|
case "auto": return ("Авто", "первый живой")
|
||||||
@@ -214,13 +240,26 @@ struct NetPanel: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let err = net.lastError {
|
||||||
|
Text(err).font(.system(size: 11.5)).foregroundColor(Color(hex: "#FB923C"))
|
||||||
|
.fixedSize(horizontal: false, vertical: true).frame(width: 444, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
if !net.running {
|
if !net.running {
|
||||||
Text("Запусти ядро: в Терминале `~/Documents/brov-secrets/start.sh` (AmneziaVPN при этом выключи). Скоро это будет делать сам BroV.")
|
Text("Ядро не отвечает. Если служба ещё не установлена — выключи AmneziaVPN и один раз выполни в Терминале:\nsudo sh ~/Documents/brov-secrets/install-netd.sh\nДальше ядро будет запускаться само при включении Мака.")
|
||||||
.font(.system(size: 11.5))
|
.font(.system(size: 11.5))
|
||||||
.foregroundColor(Color(hex: "#B0B5BE"))
|
.foregroundColor(Color(hex: "#B0B5BE"))
|
||||||
|
.textSelection(.enabled)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.frame(width: 440, alignment: .leading)
|
.frame(width: 444, alignment: .leading)
|
||||||
} else {
|
} else {
|
||||||
|
NetRow(title: "Ядро перехватывает трафик",
|
||||||
|
subtitle: net.tunOn ? "весь трафик Мака идёт через BroV" : "выключено — Мак ходит сам (можно включить AmneziaVPN)",
|
||||||
|
delay: nil, isOn: net.tunOn, accent: "#A78BFA") {
|
||||||
|
Task { await net.setTun(!net.tunOn) }
|
||||||
|
}
|
||||||
|
.frame(width: 444)
|
||||||
|
Rectangle().fill(Color.white.opacity(0.08)).frame(height: 1)
|
||||||
HStack(alignment: .top, spacing: 14) {
|
HStack(alignment: .top, spacing: 14) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("ВЫХОД В ИНТЕРНЕТ").font(.system(size: 9.5, weight: .bold)).foregroundColor(Color(hex: "#6B7079"))
|
Text("ВЫХОД В ИНТЕРНЕТ").font(.system(size: 9.5, weight: .bold)).foregroundColor(Color(hex: "#6B7079"))
|
||||||
|
|||||||
Reference in New Issue
Block a user