M17: "Bekannte Router" im Verbinden-Tab (SavedRouter/SavedRoutersStore), Standort-Freitextfeld, Scroll-Cap ab 4 Einträgen. Bugfix: Umbenennen- TextField steckte in einem sich selbst deaktivierenden Button. M18: Live-Traffic-Punkt an Interfaces (InterfaceTrafficMonitor, eigene SSH-Verbindung, monitor-traffic-Polling). Dabei zwei reale CLI-Parser-Bugs gefunden und gefixt: running/disabled-Flags werden als Buchstaben vor dem ersten Feld codiert, nicht als key=value; monitor-traffic liefert "50.7kbps" statt einer reinen Zahl. M19: Übersicht-Tab — animierte Flussrichtung auf allen Verbindungslinien (TimelineView+dashPhase), frei verschiebbare Knoten mit Live-folgenden Linien, Zurücksetzen-Button. Zusätzlich (noch nicht live getestet, nur Build+Unit-Tests grün): LAN-Port-Konflikt-Prüfung im Einrichten-Assistenten mit doppelter Sicherheitsbestätigung, "Fertig"-Button nach erfolgreichem Anwenden. 82 Tests grün. HANDOFF.md/README.md/Manual.md/CHATLOG.md aktualisiert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
49 lines
2.5 KiB
Swift
49 lines
2.5 KiB
Swift
import Foundation
|
|
|
|
/// A router this app has connected to successfully before, remembered in the Verbinden-Tab for
|
|
/// quick reconnect — the app's own "known devices" list. Login credentials stay exactly where
|
|
/// they already did (the macOS Keychain, keyed by "username@host" — see `KeychainService`); this
|
|
/// only remembers which host/username pairs exist, a user-editable display name, and when it was
|
|
/// last used.
|
|
struct SavedRouter: Identifiable, Codable, Equatable {
|
|
let id: UUID
|
|
var host: String
|
|
var username: String
|
|
/// Editable bookmark name. Defaults to the router's own hardware marketing name (e.g. "hEX")
|
|
/// the first time a connection to this host/username succeeds — RouterDeviceInfo.boardName,
|
|
/// the closest thing to a "werksmäßige Bezeichnung" (factory designation) this app already
|
|
/// reads — but is never overwritten afterwards, preserving whatever the user renames it to.
|
|
var name: String
|
|
/// Free-text location/purpose (e.g. "Keller, Serverschrank" or "1. OG, Gästezimmer") — added
|
|
/// per explicit request to tell multiple saved routers apart at a glance. Always
|
|
/// user-supplied, no default; empty means "not set", never shown then.
|
|
var location: String
|
|
var lastConnectedAt: Date
|
|
|
|
init(id: UUID = UUID(), host: String, username: String, name: String, location: String = "", lastConnectedAt: Date = Date()) {
|
|
self.id = id
|
|
self.host = host
|
|
self.username = username
|
|
self.name = name
|
|
self.location = location
|
|
self.lastConnectedAt = lastConnectedAt
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id, host, username, name, location, lastConnectedAt
|
|
}
|
|
|
|
/// Custom decoding so an already-saved list from before `location` existed keeps loading
|
|
/// (missing key -> "") instead of the whole list silently vanishing — `SavedRoutersStore.
|
|
/// load()` treats any decode failure as "no saved routers at all".
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try container.decode(UUID.self, forKey: .id)
|
|
host = try container.decode(String.self, forKey: .host)
|
|
username = try container.decode(String.self, forKey: .username)
|
|
name = try container.decode(String.self, forKey: .name)
|
|
location = try container.decodeIfPresent(String.self, forKey: .location) ?? ""
|
|
lastConnectedAt = try container.decode(Date.self, forKey: .lastConnectedAt)
|
|
}
|
|
}
|