mirror of
https://github.com/dredx/prole.git
synced 2026-09-27 14:14:31 +00:00
90 lines
3.9 KiB
Swift
90 lines
3.9 KiB
Swift
import Foundation
|
|
import Network
|
|
|
|
final class ServiceChecker {
|
|
static let statusDidChangeNotification = Notification.Name("ServiceChecker.statusDidChange")
|
|
static let forceRefreshNotification = Notification.Name("ServiceChecker.forceRefresh")
|
|
|
|
private let queue = DispatchQueue(label: "prole.status.checker")
|
|
private var timer: DispatchSourceTimer?
|
|
|
|
// Public reachability flags
|
|
private(set) var svcReachable = false
|
|
private(set) var raspberryReachable = false
|
|
private(set) var piReachable = false
|
|
private(set) var localReachable = false
|
|
|
|
// Latency measurements (ms)
|
|
private(set) var svcLatency: Int = -1
|
|
private(set) var raspberryLatency: Int = -1
|
|
private(set) var piLatency: Int = -1
|
|
private(set) var localLatency: Int = -1
|
|
|
|
init() {
|
|
NotificationCenter.default.addObserver(self, selector: #selector(forceRefresh), name: Self.forceRefreshNotification, object: nil)
|
|
}
|
|
|
|
func start(interval: TimeInterval = 15) {
|
|
timer?.cancel()
|
|
let t = DispatchSource.makeTimerSource(queue: queue)
|
|
t.schedule(deadline: .now(), repeating: interval)
|
|
t.setEventHandler { [weak self] in self?.refreshAll() }
|
|
t.resume()
|
|
timer = t
|
|
}
|
|
|
|
@objc private func forceRefresh() { queue.async { self.refreshAll() } }
|
|
|
|
private func refreshAll() {
|
|
let group = DispatchGroup()
|
|
|
|
group.enter(); tcpPing(host: "svc.prole.org", port: 443) { [weak self] ok, ms in self?.svcReachable = ok; self?.svcLatency = ms; group.leave() }
|
|
group.enter(); tcpPing(host: "raspberry.prole.org", port: 6443) { [weak self] ok, ms in self?.raspberryReachable = ok; self?.raspberryLatency = ms; group.leave() }
|
|
group.enter(); tcpPing(host: "pi.prole.org", port: 6443) { [weak self] ok, ms in self?.piReachable = ok; self?.piLatency = ms; group.leave() }
|
|
group.enter(); tcpPing(host: "localhost", port: 6443) { [weak self] ok, ms in self?.localReachable = ok; self?.localLatency = ms; group.leave() }
|
|
|
|
group.notify(queue: .main) {
|
|
NotificationCenter.default.post(name: Self.statusDidChangeNotification, object: self)
|
|
}
|
|
}
|
|
|
|
private func tcpPing(host: String, port: UInt16, timeout: TimeInterval = 2.0, completion: @escaping (Bool, Int) -> Void) {
|
|
let start = DispatchTime.now()
|
|
let params = NWParameters.tcp
|
|
params.allowLocalEndpointReuse = true
|
|
let endpoint = NWEndpoint.hostPort(host: .name(host, nil), port: .init(integerLiteral: port))
|
|
let conn = NWConnection(to: endpoint, using: params)
|
|
conn.stateUpdateHandler = { state in
|
|
switch state {
|
|
case .ready:
|
|
let elapsed = DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds
|
|
completion(true, Int(Double(elapsed) / 1_000_000.0))
|
|
conn.cancel()
|
|
case .failed, .cancelled:
|
|
completion(false, -1)
|
|
default: break
|
|
}
|
|
}
|
|
conn.start(queue: queue)
|
|
// Timeout
|
|
queue.asyncAfter(deadline: .now() + timeout) {
|
|
conn.cancel()
|
|
}
|
|
}
|
|
|
|
// Tooltips
|
|
func tooltipForSvc() -> String { "svc.prole.org:443 — " + (svcReachable ? "reachable (\(svcLatency) ms)" : "unreachable") }
|
|
func tooltipForAggregateK3s() -> String {
|
|
let r = raspberryReachable ? "raspberry ✓ (\(raspberryLatency) ms)" : "raspberry ✗"
|
|
let p = piReachable ? "pi ✓ (\(piLatency) ms)" : "pi ✗"
|
|
let overall: String
|
|
switch (raspberryReachable, piReachable) {
|
|
case (true, true): overall = "overall: green"
|
|
case (true, false), (false, true): overall = "overall: yellow"
|
|
default: overall = "overall: red"
|
|
}
|
|
return "k3s aggregate — \(overall)\n\(r)\n\(p)"
|
|
}
|
|
func tooltipForLocal() -> String { "k3d localhost:6443 — " + (localReachable ? "reachable (\(localLatency) ms)" : "unreachable") }
|
|
}
|