M25: Einrichten-Wizard-Politur + SSH-Trust-Persistenz endlich verifiziert
- WAN-Schritt: fehlenden "Zurück"-Button ergänzt (beide Modi) - "Abbrechen"/"Jetzt sichern" prominent gemacht (wie "Neu scannen") - LAN-/VLAN-Schritt: Adressfelder starten leer, nur Format-Beispiel im Feld sichtbar statt vorbelegter Werte. VlanStepView bekam dafür eine Validierungssperre auf "Weiter" (fehlte bisher, war ok solange Defaults immer gültig waren). Zwei Tests entsprechend angepasst. - SSH-Host-Key-Trust aus M24 hatte sich entgegen der Live-Bestätigung nie tatsächlich persistiert (defaults read zeigte leeren Schlüssel, Ursache ungeklärt) — betraf BackupServices dedizierte SSH-Verbindung beim ersten Experte-Tab-Schreibversuch pro Sitzung. Erneut über den bestehenden Trust-Dialog bestätigt, diesmal per defaults read verifiziert statt nur der UI-Bestätigung vertraut. Alles live bestätigt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,18 @@ import Foundation
|
||||
struct LanDhcpConfig: Identifiable, Equatable {
|
||||
var id = UUID()
|
||||
var interfaceName: String = "bridge"
|
||||
var networkAddress: String = "192.168.88.0/24"
|
||||
var routerAddress: String = "192.168.88.1/24"
|
||||
var poolRangeStart: String = "192.168.88.10"
|
||||
var poolRangeEnd: String = "192.168.88.254"
|
||||
/// Empty on purpose (Nutzerwunsch: "die auszufüllenden Felder leer lassen, nur Beispiele
|
||||
/// zeigen ... so sieht man das Schema dahinter") — each TextField's label (e.g.
|
||||
/// "Router-Adresse (z.B. 192.168.88.1/24)") doubles as its placeholder while empty, so a
|
||||
/// pre-filled value would hide the format example instead of showing it. `interfaceName`
|
||||
/// above and `leaseTimeHours` below stay as real defaults — a Picker selection and a
|
||||
/// Stepper, neither has a "type an example" UI to hide.
|
||||
var networkAddress: String = ""
|
||||
var routerAddress: String = ""
|
||||
var poolRangeStart: String = ""
|
||||
var poolRangeEnd: String = ""
|
||||
var leaseTimeHours: Int = 24
|
||||
var dnsServers: String = "192.168.88.1"
|
||||
var dnsServers: String = ""
|
||||
/// Blocks forward traffic to/from every other configured LAN/VLAN network — see
|
||||
/// `FirewallConfig.NetworkSegment`. Internet access (WAN NAT) is unaffected.
|
||||
var isolated: Bool = false
|
||||
|
||||
@@ -21,16 +21,21 @@ struct VlanEntry: Identifiable, Equatable {
|
||||
/// `FirewallConfig.NetworkSegment`. Internet access (WAN NAT) is unaffected.
|
||||
var isolated: Bool = false
|
||||
|
||||
/// Fields start empty on purpose (Nutzerwunsch: "die auszufüllenden Felder leer lassen,
|
||||
/// nur Beispiele zeigen ... so sieht man das Schema dahinter") — each TextField's label
|
||||
/// (e.g. "Router-Adresse (z.B. 192.168.20.1/24)") doubles as its placeholder while empty,
|
||||
/// so leaving these blank shows the format example instead of hiding it behind an
|
||||
/// auto-derived value the user might not even notice, let alone check. `VlanStepView`'s
|
||||
/// "Weiter" button is disabled until every VLAN's fields are actually filled in.
|
||||
init(name: String = "Gäste", vlanID: Int, parentInterface: String) {
|
||||
self.name = name
|
||||
self.vlanID = vlanID
|
||||
self.parentInterface = parentInterface
|
||||
let octet = vlanID % 256
|
||||
self.networkAddress = "192.168.\(octet).0/24"
|
||||
self.routerAddress = "192.168.\(octet).1/24"
|
||||
self.poolRangeStart = "192.168.\(octet).10"
|
||||
self.poolRangeEnd = "192.168.\(octet).254"
|
||||
self.dnsServers = "192.168.\(octet).1"
|
||||
self.networkAddress = ""
|
||||
self.routerAddress = ""
|
||||
self.poolRangeStart = ""
|
||||
self.poolRangeEnd = ""
|
||||
self.dnsServers = ""
|
||||
}
|
||||
|
||||
var interfaceName: String { "vlan\(vlanID)" }
|
||||
|
||||
@@ -223,6 +223,9 @@ struct BackupListView: View {
|
||||
Label(L10n.t("Jetzt sichern", appLanguage), systemImage: "square.and.arrow.down")
|
||||
}
|
||||
}
|
||||
// Same precedent as LAN-Scanner's "Neu scannen"/Einrichten's "Abbrechen" —
|
||||
// a plain text toolbar button is easy to miss, applied app-wide now.
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(connectionService.credentials == nil || viewModel.isCreatingBackup)
|
||||
.help(connectionService.credentials == nil ? L10n.t("Zuerst im Tab 'Verbinden' mit dem Router verbinden.", appLanguage) : L10n.t("Sicherung jetzt erstellen", appLanguage))
|
||||
}
|
||||
|
||||
@@ -45,6 +45,13 @@ struct SetupView: View {
|
||||
Button(L10n.t("Abbrechen", appLanguage)) {
|
||||
showCancelConfirmation = true
|
||||
}
|
||||
// Same reasoning/precedent as LAN-Scanner's "Neu scannen" button
|
||||
// (Bug/Nutzerwunsch: "wird übersehen") — a plain text toolbar button
|
||||
// blends into the window chrome; `.borderedProminent` gives it a
|
||||
// filled, colored background so it's actually noticeable. Applied
|
||||
// consistently to every standalone toolbar action button in the app
|
||||
// per explicit request ("für die gesamte App fixen").
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(viewModel.isApplying)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,24 @@ struct VlanStepView: View {
|
||||
Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() }
|
||||
Spacer()
|
||||
Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() }
|
||||
.disabled(!isStepValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle(LocalizedStringKey(L10n.t("Zusätzliche Netzwerke (VLAN)", appLanguage)))
|
||||
}
|
||||
|
||||
/// Only gates on filled-in fields while the VLAN section is actually enabled — with it off,
|
||||
/// `viewModel.vlans` is empty (see `setVlanSectionEnabled`) and there's nothing to validate.
|
||||
/// Needed as of the fields-start-empty change: previously every VLAN's address fields had a
|
||||
/// valid auto-derived default, so "Weiter" was always safe to press; now an incompletely
|
||||
/// filled-in VLAN would otherwise reach `buildCommands()` with blank arguments.
|
||||
private var isStepValid: Bool {
|
||||
guard viewModel.vlanSectionEnabled else { return true }
|
||||
return viewModel.vlans.allSatisfy { vlan in
|
||||
!vlan.name.isEmpty && !vlan.networkAddress.isEmpty && !vlan.routerAddress.isEmpty
|
||||
&& !vlan.poolRangeStart.isEmpty && !vlan.poolRangeEnd.isEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ struct WanStepView: View {
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() }
|
||||
Spacer()
|
||||
Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() }
|
||||
.disabled(!isStepValid)
|
||||
|
||||
Reference in New Issue
Block a user