Files
ovpn/Sources/CampusNASMonitor/LinkState.swift
T
romanvandClaude Opus 5 cd53b6a1a1 Add CampusNAS menu bar agent
Native macOS agent that keeps the office VPN and the NAS shares working
without the shell wrapper it replaces.

- VPN: full port of the `ovpn` script — host-route pinning, gateway
  discovery via networksetup, tunnel liveness by address plus pushed
  route, startup grace, restart of a wedged daemon. Root steps are
  batched into one system authorization prompt.
- SMB: mounts via NetFS, discovers shares from the server, detects
  mounts event-driven through NSWorkspace, matches the host by resolved
  alias so externally created mounts are recognised.
- Credentials never pass through the process: the VPN auth file is
  written by `security` straight to disk, and the password is stored via
  SecItemAdd rather than a command line visible in ps.
- UI: status window, settings editor, ⌘+/⌘− zoom that re-lays out rather
  than scaling a bitmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:19:31 +06:00

51 lines
1.5 KiB
Swift

import Foundation
enum LinkState: String {
case unknown
case ok
case nasDown
case vpnDown
var title: String {
switch self {
case .unknown: return "Проверяю…"
case .ok: return "NAS доступен"
case .nasDown: return "VPN жив, NAS не отвечает"
case .vpnDown: return "Офисный VPN не поднят"
}
}
var notificationBody: String {
switch self {
case .unknown: return ""
case .ok: return "Офисный NAS снова доступен"
case .nasDown: return "Туннель поднят, но NAS не отвечает на 445"
case .vpnDown: return "Офисный VPN отвалился"
}
}
/// Shown next to the menu bar icon. Empty when healthy — a permanent label
/// would be ignored within a day, which is the whole reason it is only
/// drawn when something is actually wrong.
var badge: String {
switch self {
case .unknown, .ok: return ""
case .nasDown: return "NAS"
case .vpnDown: return "VPN"
}
}
var symbolName: String {
switch self {
case .unknown: return "externaldrive.badge.questionmark"
case .ok: return "externaldrive.connected.to.line.below"
case .nasDown: return "externaldrive.badge.exclamationmark"
case .vpnDown: return "externaldrive.badge.xmark"
}
}
var isTunnelUp: Bool {
self == .ok || self == .nasDown
}
}