Files
RouterOS/RouterOSAssistant/Features/Wizard/Steps/Connect/ConnectView.swift
T
KayandClaude Sonnet 5 9993f84311 Verbinden-Tab: Sicherung direkt nach dem Verbinden erstellbar
"Jetzt sichern"-Button erscheint sobald verbunden, noch bevor man in
den Einrichten-Tab wechselt -- Nutzer-Wunsch, um vor Änderungen ohne
Tab-Wechsel sichern zu können. Zeigt zusätzlich "Zuletzt gesichert: ..."
für den aktuell verbundenen Host, falls schon eine Sicherung existiert
(gefiltert auf den aktuellen Host, damit es bei mehreren Routern nicht
verwirrt). Nutzt dieselbe BackupViewModel/BackupService-Logik wie der
Sicherungen-Tab, eigene Instanz.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
2026-09-12 20:44:47 +02:00

173 lines
6.7 KiB
Swift

import SwiftUI
struct ConnectView: View {
@ObservedObject private var connectionService: ConnectionService
@StateObject private var viewModel: ConnectViewModel
@StateObject private var backupViewModel = BackupViewModel()
init(connectionService: ConnectionService) {
self.connectionService = connectionService
_viewModel = StateObject(wrappedValue: ConnectViewModel(connectionService: connectionService))
}
var body: some View {
NavigationSplitView {
Form {
Section("Verbindung") {
TextField("IP-Adresse oder Hostname", text: $viewModel.host)
.help("Die Adresse deines Routers im Netzwerk. Werkseinstellung bei Mikrotik ist meist 192.168.88.1.")
TextField("Benutzername", text: $viewModel.username)
.help("Der Admin-Benutzername deines Routers. Werkseinstellung ist meist \"admin\".")
SecureField("Passwort", text: $viewModel.password)
.help("Das Passwort für diesen Benutzer. Bei unverändertem Werkszustand oft leer.")
Toggle("Passwort merken", isOn: $viewModel.rememberPassword)
.help("Speichert das Passwort verschlüsselt in der macOS-Schlüsselbundverwaltung, damit du es nicht jedes Mal neu eingeben musst.")
}
Section {
Button("Verbinden") {
viewModel.connect()
}
.disabled(viewModel.host.isEmpty || viewModel.username.isEmpty)
}
statusSection
}
.formStyle(.grouped)
.frame(minWidth: 320)
} detail: {
deviceDetail
}
.onAppear {
viewModel.onAppear()
backupViewModel.load()
}
.alert(
"Unbekanntes Zertifikat",
isPresented: certificateAlertBinding,
presenting: certificateFingerprint
) { fingerprint in
Button("Vertrauen und verbinden") {
viewModel.trustAndRetry(fingerprint: fingerprint)
}
Button("Abbrechen", role: .cancel) {}
} message: { fingerprint in
Text("Der Router hat sich mit einem unbekannten Zertifikat gemeldet.\nFingerabdruck: \(fingerprint)\n\nNur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist.")
}
.alert(
"Sicherung fehlgeschlagen",
isPresented: Binding(
get: { backupViewModel.errorMessage != nil },
set: { _ in backupViewModel.errorMessage = nil }
),
presenting: backupViewModel.errorMessage
) { _ in
Button("OK") {}
} message: { message in
Text(message)
}
}
private var certificateFingerprint: String? {
if case .needsCertificateConfirmation(let fingerprint) = connectionService.state {
return fingerprint
}
return nil
}
private var certificateAlertBinding: Binding<Bool> {
Binding(
get: { certificateFingerprint != nil },
set: { _ in }
)
}
@ViewBuilder
private var statusSection: some View {
switch connectionService.state {
case .idle, .needsCertificateConfirmation:
EmptyView()
case .connecting:
Section {
HStack {
ProgressView()
Text("Verbinde…")
}
}
case .connected(let kind):
Section {
Label("Verbunden (\(kind == .rest ? "REST-API" : "SSH"))", systemImage: "checkmark.circle.fill")
.foregroundStyle(.green)
Button {
if let credentials = connectionService.credentials {
backupViewModel.createBackup(for: credentials)
}
} label: {
if backupViewModel.isCreatingBackup {
HStack {
ProgressView()
Text("Sichere…")
}
} else {
Label("Jetzt sichern", systemImage: "square.and.arrow.down")
}
}
.disabled(backupViewModel.isCreatingBackup)
.help("Sichert die aktuelle Router-Konfiguration — sinnvoll direkt nach dem Verbinden, bevor du im Einrichten-Tab etwas änderst.")
if let lastBackup = backupViewModel.backups.first(where: { $0.host == connectionService.credentials?.host }) {
Label(
"Zuletzt gesichert: \(lastBackup.createdAt.formatted(date: .abbreviated, time: .standard))",
systemImage: "checkmark"
)
.font(.caption)
.foregroundStyle(.secondary)
}
}
case .failed(let message):
Section {
Label(message, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
}
}
}
@ViewBuilder
private var deviceDetail: some View {
if let info = connectionService.deviceInfo {
List {
Section("Gerät") {
LabeledContent("Modell", value: info.boardName)
LabeledContent("RouterOS-Version", value: info.routerOSVersion)
LabeledContent("Architektur", value: info.architecture)
LabeledContent("Laufzeit", value: info.uptime)
}
Section("Interfaces") {
ForEach(connectionService.interfaces) { interface in
HStack {
Image(systemName: interface.running ? "circle.fill" : "circle")
.foregroundStyle(interface.running ? .green : .secondary)
.font(.caption)
VStack(alignment: .leading) {
Text(interface.name).bold()
Text(interface.type).font(.caption).foregroundStyle(.secondary)
}
}
}
}
}
} else {
ContentUnavailableView(
"Nicht verbunden",
systemImage: "network.slash",
description: Text("Verbinde dich mit deinem Router, um Geräteinformationen zu sehen.")
)
}
}
}
#Preview {
ConnectView(connectionService: ConnectionService())
}