Files
RouterOS/RouterOSAssistant/Core/Models/PortConflict.swift
T
KayandClaude Sonnet 5 31607b7270 M17-M19: Bekannte Router, Live-Traffic-Anzeige, Übersicht-Animation+Drag
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
2026-09-15 21:40:46 +02:00

66 lines
3.2 KiB
Swift

import Foundation
/// Pre-existing configuration on a physical port that the Setup wizard's LAN step would silently
/// override if the user picks that port for a new LAN/DHCP network — surfaced instead so the user
/// can either choose a different, actually-free port or explicitly (twice-confirmed) opt in to
/// clearing it. `DhcpServerCommandBuilder` already unconditionally detaches a port from any bridge
/// as a safety net (see its own doc comment) — this check runs *before* that, to make the same
/// fact visible up front instead of only implicitly, and additionally covers cases that builder
/// doesn't touch at all (an existing IP address, or the port already being used as a WAN dial-up).
struct PortConflict: Equatable {
enum Reason: Equatable {
case bridgeMember(bridgeName: String)
case hasAddresses([String])
case dhcpClient
case pppoeClient
var description: String {
switch self {
case .bridgeMember(let bridgeName):
return "Ist Mitglied der Bridge \"\(bridgeName)\""
case .hasAddresses(let addresses):
return "Trägt bereits die IP-Adresse\(addresses.count == 1 ? "" : "n") \(addresses.joined(separator: ", "))"
case .dhcpClient:
return "Ist als WAN-DHCP-Client konfiguriert (bezieht selbst eine Adresse aus dem Internet)"
case .pppoeClient:
return "Wird von einer PPPoE-Einwahl verwendet"
}
}
}
let interfaceName: String
let reasons: [Reason]
/// Commands that clear each found conflict so the port is actually free before
/// `DhcpServerCommandBuilder`'s own commands run. Bridge membership is deliberately excluded
/// here — `DhcpServerCommandBuilder` already removes it unconditionally regardless of whether
/// this check ran or was acknowledged, so repeating it would just be a harmless duplicate
/// `.remove` at best; excluding it keeps this list to exactly what wouldn't otherwise happen.
func resolutionCommands() -> [RouterOSCommand] {
reasons.flatMap { reason -> [RouterOSCommand] in
switch reason {
case .bridgeMember:
return []
case .hasAddresses:
return [RouterOSCommand.remove(
menuPath: "/ip address", restPath: "ip/address",
matchField: "interface", matchValue: interfaceName,
summary: "Bestehende IP-Adresse(n) auf \(interfaceName) entfernen"
)]
case .dhcpClient:
return [RouterOSCommand.remove(
menuPath: "/ip dhcp-client", restPath: "ip/dhcp-client",
matchField: "interface", matchValue: interfaceName,
summary: "WAN-DHCP-Client auf \(interfaceName) entfernen"
)]
case .pppoeClient:
return [RouterOSCommand.remove(
menuPath: "/interface pppoe-client", restPath: "interface/pppoe-client",
matchField: "interface", matchValue: interfaceName,
summary: "PPPoE-Einwahl auf \(interfaceName) entfernen"
)]
}
}
}
}