diff --git a/HANDOFF.md b/HANDOFF.md index d7192f6..a6789b4 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1308,6 +1308,21 @@ später dazukommen könnte, da MikroTik-Router in ärmeren Ländern (Brasilien, spanischsprachige Länder) stark verbreitet sind — siehe Claude-Memory `future-language-support.md` für Details. +**"Abbrechen"-Button im Einrichten-Tab ergänzt** (2026-09-16, Nutzerwunsch: +"mir fehlt im einrichte-tab (einfach und experte) noch ein +\"Abbrechen\"-Button") — vorher gab es keinen Weg aus dem Wizard außer +sich Schritt für Schritt per "Zurück" bis zu Modus vorzuklicken. Zentral +in `SetupView.swift` als Toolbar-Button gelöst (nicht in jedem der sieben +Schritt-Views einzeln dupliziert), sichtbar auf jedem Schritt in beiden +Modi, mit `.confirmationDialog` ("Einrichten abbrechen?" / +"Weiter ausfüllen") gegen versehentlichen Datenverlust bereits +eingegebener Werte, deaktiviert während `isApplying` (ein laufendes +Apply soll nicht mitten im Ablauf abreißen). `SetupViewModel.finish()` +in eine private `resetToInitialState()` plus zwei benannte Wrapper +(`finish()`/neues `cancel()`) aufgeteilt — gleicher Reset, aber je nach +Aufrufkontext (erfolgreicher Apply vs. Nutzer bricht ab) unter dem +passenden Namen lesbar. Live bestätigt ("sieht gut aus"). + ## Nächste Schritte 1. ~~M16: restliche `RouterOSSchemaCatalog.swift`-Sektionen übersetzen~~, diff --git a/RouterOSAssistant/Core/Localization/L10n.swift b/RouterOSAssistant/Core/Localization/L10n.swift index 315e728..424e08f 100644 --- a/RouterOSAssistant/Core/Localization/L10n.swift +++ b/RouterOSAssistant/Core/Localization/L10n.swift @@ -764,6 +764,9 @@ enum L10n { "Details anzeigen (RouterOS-Befehle)": "Show Details (RouterOS Commands)", "Vor dem Anwenden wird automatisch eine Sicherung der aktuellen Konfiguration erstellt (Tab \"Sicherungen\"). Ein garantiertes automatisches Zurückrollen bei Verbindungsabbruch gibt es nicht — bei Problemen die Sicherung im Tab \"Sicherungen\" verwenden oder den Router lokal (Ethernet/Konsole) wiederherstellen.": "Before applying, a backup of the current configuration is automatically created (\"Backups\" tab). There's no guaranteed automatic rollback if the connection drops — if there are problems, use the backup in the \"Backups\" tab, or restore the router locally (Ethernet/console).", + "Einrichten abbrechen?": "Cancel Setup?", + "Weiter ausfüllen": "Keep Going", + "Alle in diesem Assistenten eingegebenen Werte gehen verloren.": "All values entered in this wizard will be lost.", "Ablauf": "Progress", "Änderungen erfolgreich angewendet": "Changes Applied Successfully", "Jetzt anwenden": "Apply Now", diff --git a/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupView.swift b/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupView.swift index 2151c3e..640e6bf 100644 --- a/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupView.swift +++ b/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupView.swift @@ -4,6 +4,7 @@ struct SetupView: View { @ObservedObject var connectionService: ConnectionService @StateObject private var viewModel: SetupViewModel @AppStorage("appLanguage") private var appLanguage: String = "de" + @State private var showCancelConfirmation = false init(connectionService: ConnectionService) { self.connectionService = connectionService @@ -38,6 +39,28 @@ struct SetupView: View { } } } + .toolbar { + if connectionService.credentials != nil { + ToolbarItem(placement: .cancellationAction) { + Button(L10n.t("Abbrechen", appLanguage)) { + showCancelConfirmation = true + } + .disabled(viewModel.isApplying) + } + } + } + .confirmationDialog( + L10n.t("Einrichten abbrechen?", appLanguage), + isPresented: $showCancelConfirmation, + titleVisibility: .visible + ) { + Button(L10n.t("Abbrechen", appLanguage), role: .destructive) { + viewModel.cancel() + } + Button(L10n.t("Weiter ausfüllen", appLanguage), role: .cancel) {} + } message: { + Text(L10n.t("Alle in diesem Assistenten eingegebenen Werte gehen verloren.", appLanguage)) + } } .onAppear { viewModel.prepareDefaults(from: configurableInterfaces) diff --git a/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupViewModel.swift b/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupViewModel.swift index 228ce08..8b4e4e5 100644 --- a/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupViewModel.swift +++ b/RouterOSAssistant/Features/Wizard/Steps/Setup/SetupViewModel.swift @@ -254,6 +254,19 @@ final class SetupViewModel: ObservableObject { /// LAN afterwards) starts from the router's actual current state rather than this session's /// now-outdated in-memory one. func finish() { + resetToInitialState() + } + + /// User-initiated abort from any step (per explicit request: no "Abbrechen" button existed + /// anywhere in the wizard before this — the only way out was clicking through every remaining + /// step to "Zurück" back to Mode, or force-quitting the app). Same reset as `finish()` — the + /// distinction is purely which caller/label makes sense in context (a successful apply vs. a + /// user backing out mid-way) — kept as separate names so each call site reads correctly. + func cancel() { + resetToInitialState() + } + + private func resetToInitialState() { step = .mode mode = .simple wanConfig = WanConfig(interfaceName: "ether1")