Files
RouterOS/RouterOSAssistant/Features/Wizard/Steps/Connect/ConnectView.swift
T
KayandClaude Sonnet 5 9d1aecc452 Tooltips für alle Konfigurationsfelder + wählbarer Backup-Ordner
.help(...)-Tooltips auf jedem Eingabefeld/Picker/Toggle in Connect-
sowie allen Einrichten-Schritten (WAN/LAN/VLAN/WLAN/Firewall) --
kurze Erklärung was der Wert bedeutet und welche Auswirkung er hat,
passend zum Laien-Anspruch der App.

Sicherungen-Tab: Speicherort jetzt änderbar (Ordner wählen über
nativen macOS-Dialog, Zurücksetzen auf Standard). BackupService hält
den gewählten Pfad in UserDefaults (BackupService.customDirectoryURL),
fällt ohne Auswahl weiter auf ~/Library/Application Support/... zurück.

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

131 lines
4.9 KiB
Swift

import SwiftUI
struct ConnectView: View {
@ObservedObject private var connectionService: ConnectionService
@StateObject private var viewModel: ConnectViewModel
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() }
.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.")
}
}
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)
}
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())
}