forked from kay/RouterOS
Neuer optionaler Schritt im Einrichten-Wizard, standardmäßig übersprungen (Toggle): legt ein VLAN-Interface auf einem gewählten Basis-Anschluss an, mit eigenem IP-Bereich und eigenem DHCP-Server — z.B. für Gäste/IoT. Bewusst ohne Port-Zuweisung (Access/Trunk): das bräuchte RouterOS Bridge-VLAN-Filtering mit "set"-Operationen auf bestehende Einträge, die unser bisheriges reines "add"-Befehlsmodell (identisch für REST+SSH) nicht abdeckt. Wer ein VLAN auf einem bestimmten Switch-Port braucht, muss den Trunk weiterhin manuell einrichten. Entscheidung mit Nutzer abgestimmt. DHCP-Befehlsbau (Adresse+Pool+Server+Netzwerk) aus dem LAN-Schritt in DhcpServerCommandBuilder extrahiert, da jetzt zweimal identisch gebraucht (LAN direkt, VLAN pro Eintrag). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
112 lines
3.7 KiB
Swift
112 lines
3.7 KiB
Swift
import Foundation
|
|
|
|
enum SetupStep: Int, CaseIterable {
|
|
case wan
|
|
case lan
|
|
case vlan
|
|
case review
|
|
}
|
|
|
|
@MainActor
|
|
final class SetupViewModel: ObservableObject {
|
|
@Published var step: SetupStep = .wan
|
|
@Published var wanConfig = WanConfig(interfaceName: "ether1")
|
|
@Published var lanConfig = LanDhcpConfig()
|
|
@Published private(set) var vlanSectionEnabled = false
|
|
@Published var vlans: [VlanEntry] = []
|
|
|
|
@Published private(set) var isApplying = false
|
|
@Published private(set) var applyLog: [String] = []
|
|
@Published private(set) var applyError: String?
|
|
@Published private(set) var didApplySuccessfully = false
|
|
|
|
private let connectionService: ConnectionService
|
|
private let backupService: BackupService
|
|
|
|
init(connectionService: ConnectionService, backupService: BackupService = BackupService()) {
|
|
self.connectionService = connectionService
|
|
self.backupService = backupService
|
|
prepareDefaults(from: connectionService.interfaces)
|
|
}
|
|
|
|
var plannedCommands: [RouterOSCommand] {
|
|
wanConfig.buildCommands() + lanConfig.buildCommands() + vlans.flatMap { $0.buildCommands() }
|
|
}
|
|
|
|
func setVlanSectionEnabled(_ enabled: Bool) {
|
|
vlanSectionEnabled = enabled
|
|
if !enabled { vlans = [] }
|
|
}
|
|
|
|
func addVlan() {
|
|
let usedIDs = Set(vlans.map(\.vlanID))
|
|
var nextID = 20
|
|
while usedIDs.contains(nextID) { nextID += 1 }
|
|
vlans.append(VlanEntry(vlanID: nextID, parentInterface: lanConfig.interfaceName))
|
|
}
|
|
|
|
func removeVlan(_ id: VlanEntry.ID) {
|
|
vlans.removeAll { $0.id == id }
|
|
}
|
|
|
|
/// Replaces the placeholder WAN/LAN interface names ("ether1"/"bridge") with real ones
|
|
/// from the connected device, whenever the current value isn't actually one of its
|
|
/// interfaces — otherwise the Picker selections don't match any of their tags.
|
|
func prepareDefaults(from interfaces: [NetworkInterface]) {
|
|
guard !interfaces.isEmpty else { return }
|
|
let names = Set(interfaces.map(\.name))
|
|
|
|
if !names.contains(wanConfig.interfaceName) {
|
|
if let firstEthernet = interfaces.first(where: { $0.type.lowercased().contains("ether") }) {
|
|
wanConfig.interfaceName = firstEthernet.name
|
|
} else {
|
|
wanConfig.interfaceName = interfaces[0].name
|
|
}
|
|
}
|
|
|
|
if !names.contains(lanConfig.interfaceName) {
|
|
if let bridge = interfaces.first(where: { $0.type.lowercased().contains("bridge") }) {
|
|
lanConfig.interfaceName = bridge.name
|
|
} else if let fallback = interfaces.first(where: { $0.name != wanConfig.interfaceName }) {
|
|
lanConfig.interfaceName = fallback.name
|
|
} else {
|
|
lanConfig.interfaceName = interfaces[0].name
|
|
}
|
|
}
|
|
}
|
|
|
|
func goNext() {
|
|
guard let next = SetupStep(rawValue: step.rawValue + 1) else { return }
|
|
step = next
|
|
}
|
|
|
|
func goBack() {
|
|
guard let previous = SetupStep(rawValue: step.rawValue - 1) else { return }
|
|
step = previous
|
|
}
|
|
|
|
func apply(credentials: RouterOSCredentials) {
|
|
isApplying = true
|
|
applyError = nil
|
|
applyLog = []
|
|
didApplySuccessfully = false
|
|
|
|
Task {
|
|
do {
|
|
applyLog.append("Sichere aktuelle Konfiguration…")
|
|
_ = try await backupService.createBackup(for: credentials)
|
|
|
|
for command in plannedCommands {
|
|
applyLog.append(command.summary)
|
|
try await connectionService.apply(command)
|
|
}
|
|
|
|
didApplySuccessfully = true
|
|
} catch {
|
|
applyError = error.localizedDescription
|
|
}
|
|
isApplying = false
|
|
}
|
|
}
|
|
}
|