README-Milestone-Nachcheck (Port-Konflikt-Prüfung/"Fertig"-Button) deckte drei eigenständige Bugs derselben Klasse auf: Cancel/OK-Buttons bei ReviewApplyViews Apply-Fehler-Alert, ConnectViews Zertifikat-Alert und ConnectViews SSH-Hostkey-Alert taten nichts oder zu wenig - der jeweilige Verbindungs-/Fehlerzustand blieb hängen, der Dialog konnte nicht sauber verlassen werden. Neue ConnectionService.cancelPendingTrustConfirmation() und SetupViewModel.dismissApplyError(), alle drei Alerts korrekt verdrahtet (Button-Action + Bindings-Setter fuer Tap-Outside/Esc). Totes dismissPendingSSHTrust() entfernt. 1 neuer Regressionstest, alle 102 Unit-Tests gruen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
91 lines
3.9 KiB
Swift
91 lines
3.9 KiB
Swift
import SwiftUI
|
|
|
|
struct ReviewApplyView: View {
|
|
@ObservedObject var viewModel: SetupViewModel
|
|
let credentials: RouterOSCredentials?
|
|
|
|
@State private var showCliDetails = false
|
|
@AppStorage("appLanguage") private var appLanguage: String = "de"
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section(L10n.t("Geplante Änderungen", appLanguage)) {
|
|
ForEach(Array(viewModel.plannedCommands.enumerated()), id: \.element.id) { index, command in
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(command.summary)
|
|
if showCliDetails {
|
|
Text(command.cliLine)
|
|
.appFont(.caption, design: .monospaced)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.listRowBackground(TableZebra.color(for: index))
|
|
}
|
|
Toggle(L10n.t("Details anzeigen (RouterOS-Befehle)", appLanguage), isOn: $showCliDetails)
|
|
}
|
|
|
|
Section {
|
|
Text(L10n.t("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.", appLanguage))
|
|
.appFont(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
if !viewModel.applyLog.isEmpty {
|
|
Section(L10n.t("Ablauf", appLanguage)) {
|
|
ForEach(Array(viewModel.applyLog.enumerated()), id: \.offset) { index, line in
|
|
Text(line).appFont(.caption)
|
|
.listRowBackground(TableZebra.color(for: index))
|
|
}
|
|
}
|
|
}
|
|
|
|
if viewModel.didApplySuccessfully {
|
|
Section {
|
|
Label(L10n.t("Änderungen erfolgreich angewendet", appLanguage), systemImage: "checkmark.circle.fill")
|
|
.foregroundStyle(.green)
|
|
}
|
|
}
|
|
|
|
Section {
|
|
HStack {
|
|
Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() }
|
|
.disabled(viewModel.isApplying)
|
|
Spacer()
|
|
if viewModel.didApplySuccessfully {
|
|
Button(L10n.t("Fertig", appLanguage)) { viewModel.finish() }
|
|
.keyboardShortcut(.defaultAction)
|
|
} else {
|
|
Button {
|
|
if let credentials {
|
|
viewModel.apply(credentials: credentials)
|
|
}
|
|
} label: {
|
|
if viewModel.isApplying {
|
|
ProgressView()
|
|
} else {
|
|
Text(L10n.t("Jetzt anwenden", appLanguage))
|
|
}
|
|
}
|
|
.disabled(credentials == nil || viewModel.isApplying)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.formStyle(.grouped)
|
|
.scrollContentBackground(.hidden)
|
|
.navigationTitle(LocalizedStringKey(L10n.t("Übersicht & Anwenden", appLanguage)))
|
|
.toolbar { ToolbarItem { ManualHelpButton(anchor: ManualAnchor.stepReview) } }
|
|
.alert(
|
|
L10n.t("Anwenden fehlgeschlagen", appLanguage),
|
|
isPresented: Binding(
|
|
get: { viewModel.applyError != nil },
|
|
set: { isPresented in if !isPresented { viewModel.dismissApplyError() } }
|
|
)
|
|
) {
|
|
Button(L10n.t("OK", appLanguage)) { viewModel.dismissApplyError() }
|
|
} message: {
|
|
Text(viewModel.applyError ?? "")
|
|
}
|
|
}
|
|
}
|