forked from kay/RouterOS
XcodeGen-basiertes SwiftUI-Projekt für den RouterOS-Interview-Assistenten. Erster Wizard-Schritt: Verbindung zu Mikrotik-Geräten per REST-API (RouterOS >=7.1) mit SSH-CLI-Fallback für ältere Firmware, Zertifikats- TOFU-Bestätigung, Zugangsdaten im Keychain. Unit-Tests für CLI-Parser und Fallback-Logik. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
121 lines
4.2 KiB
Swift
121 lines
4.2 KiB
Swift
import SwiftUI
|
|
|
|
struct ConnectView: View {
|
|
@StateObject private var viewModel = ConnectViewModel()
|
|
|
|
var body: some View {
|
|
NavigationSplitView {
|
|
Form {
|
|
Section("Verbindung") {
|
|
TextField("IP-Adresse oder Hostname", text: $viewModel.host)
|
|
TextField("Benutzername", text: $viewModel.username)
|
|
SecureField("Passwort", text: $viewModel.password)
|
|
Toggle("Passwort merken", isOn: $viewModel.rememberPassword)
|
|
}
|
|
|
|
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) = viewModel.connectionService.state {
|
|
return fingerprint
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private var certificateAlertBinding: Binding<Bool> {
|
|
Binding(
|
|
get: { certificateFingerprint != nil },
|
|
set: { _ in }
|
|
)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var statusSection: some View {
|
|
switch viewModel.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 = viewModel.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(viewModel.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()
|
|
}
|