forked from kay/RouterOS
Letzter offener Punkt aus HANDOFF.md: gespeicherte .rsc-Backups lassen sich jetzt wieder einspielen. Weg: Backup-Datei per SFTP auf den Router hochladen (Citadel, die bereits eingebundene SSH-Bibliothek, hat einen SFTP-Client), dann RouterOS' offiziell dokumentierter Restore-Weg in einem Rutsch: /system reset-configuration no-defaults=yes run-after-reset=<datei> (kompletter Wipe + sofortiges Wiederanwenden, sicherer als ein Re-Import auf eine bestehende, andere Config). Vor dem Bestätigungsdialog wird das Routermodell abgeglichen (/system routerboard prints "model"-Feld gegen die "# model = ..."- Kopfzeile der Sicherung) und bei Mismatch komplett blockiert, um ein Brick-Risiko durch falsches Modell zu vermeiden - der Dialog selbst warnt zusaetzlich prominent davor. Ein ernster Bug live gefunden und gefixt: der erste echte Restore-Test sperrte den Router komplett aus (RouterOS exportiert nie Passwoerter, no-defaults=yes loescht zusaetzlich den Werks-Admin-Account), nur per Hardware-Reset behebbar. Fix: das aktuell verwendete App-Login wird jetzt vorne ins Restore-Skript eingefuegt, noch vor dem eigentlichen Sicherungsinhalt, da RouterOS den Import beim ersten Fehler irgendwo im Skript komplett abbricht. Zwei Tests fuer die Escaping-Logik ergaenzt. Nebenbei: BackupListView mit den neuen Restore-Dialogen liess sich nicht mehr kompilieren (SwiftUI-Typpruefung timeoutete bei der langen Modifier-Kette) - Restore-Dialoge in eine eigene @ViewBuilder-Property ausgelagert. HANDOFF.md/CHATLOG.md aktualisiert: M13, Bug 18+19, "Backup- Wiederherstellung fehlt" aus den offenen Punkten entfernt, Hinweis auf den zweiten (ungewollten) Werksreset waehrend der Session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
89 lines
3.1 KiB
Swift
89 lines
3.1 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
final class BackupViewModel: ObservableObject {
|
|
@Published private(set) var backups: [BackupRecord] = []
|
|
@Published private(set) var isCreatingBackup = false
|
|
@Published var errorMessage: String?
|
|
@Published private(set) var backupDirectoryPath: String = BackupService.customDirectoryURL?.path
|
|
?? BackupService.defaultDirectoryURL.path
|
|
|
|
@Published private(set) var isResettingToFactoryDefaults = false
|
|
@Published var resetError: String?
|
|
@Published private(set) var didSendFactoryReset = false
|
|
|
|
@Published private(set) var isRestoring = false
|
|
@Published var restoreError: String?
|
|
@Published private(set) var didSendRestore = false
|
|
|
|
private let backupService = BackupService()
|
|
private let factoryResetService = FactoryResetService()
|
|
|
|
func load() {
|
|
backups = backupService.listBackups()
|
|
}
|
|
|
|
func setBackupDirectory(_ url: URL) {
|
|
BackupService.customDirectoryURL = url
|
|
backupDirectoryPath = url.path
|
|
load()
|
|
}
|
|
|
|
func resetBackupDirectoryToDefault() {
|
|
BackupService.customDirectoryURL = nil
|
|
backupDirectoryPath = BackupService.defaultDirectoryURL.path
|
|
load()
|
|
}
|
|
|
|
func createBackup(for credentials: RouterOSCredentials) {
|
|
isCreatingBackup = true
|
|
errorMessage = nil
|
|
Task {
|
|
do {
|
|
_ = try await backupService.createBackup(for: credentials)
|
|
load()
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
isCreatingBackup = false
|
|
}
|
|
}
|
|
|
|
/// Backs up first (best-effort — a failed backup shouldn't block the reset itself, since
|
|
/// RouterOS makes its own backup as part of the reset anyway), then restores the router's
|
|
/// vendor-default configuration and reboots it.
|
|
func resetToFactoryDefaults(for credentials: RouterOSCredentials) {
|
|
isResettingToFactoryDefaults = true
|
|
resetError = nil
|
|
didSendFactoryReset = false
|
|
Task {
|
|
do {
|
|
_ = try? await backupService.createBackup(for: credentials)
|
|
try await factoryResetService.resetToVendorDefaults(for: credentials)
|
|
didSendFactoryReset = true
|
|
} catch {
|
|
resetError = error.localizedDescription
|
|
}
|
|
isResettingToFactoryDefaults = false
|
|
}
|
|
}
|
|
|
|
/// Wipes the router's config and replaces it with `record`'s. Model-compatibility checking
|
|
/// happens in the View before this is ever called (needs `ConnectionService.deviceInfo`,
|
|
/// which this view model doesn't hold) — this only performs the already-confirmed restore.
|
|
func restore(_ record: BackupRecord, for credentials: RouterOSCredentials) {
|
|
isRestoring = true
|
|
restoreError = nil
|
|
didSendRestore = false
|
|
Task {
|
|
do {
|
|
try await backupService.restoreBackup(record, for: credentials)
|
|
didSendRestore = true
|
|
} catch {
|
|
restoreError = error.localizedDescription
|
|
}
|
|
isRestoring = false
|
|
}
|
|
}
|
|
}
|