From d982d018b8c521dbb3e1f5de59090560a40d1404 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:03:30 +0100 Subject: [PATCH] functions to get hosts and indexes matching input, and to update a host added a hostkey checker - fatalerror rn reordered ui in connection view to make more sense added sections moved connect button to toolbar, disconect is there too! hostsview update --- ShhShell/Host/HostsManager.swift | 24 ++++ ShhShell/SSH/SSHHandler.swift | 5 +- ShhShell/Views/ConnectionView.swift | 212 +++++++++++++++------------- ShhShell/Views/HostsView.swift | 46 +++--- 4 files changed, 162 insertions(+), 125 deletions(-) diff --git a/ShhShell/Host/HostsManager.swift b/ShhShell/Host/HostsManager.swift index a43d2a8..2e3d395 100644 --- a/ShhShell/Host/HostsManager.swift +++ b/ShhShell/Host/HostsManager.swift @@ -16,6 +16,30 @@ class HostsManager: ObservableObject { loadSavedHosts() } + /// get the index of a matching host in saved hosts + /// - Parameter host: input a host + /// - Returns: if an item in savedHosts has a matching uuid to the parameter, it returns the index + /// else returns nil + func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? { + if let index = savedHosts.firstIndex(where: { $0.id == hostSearchingFor.id }) { + return index + } else { + return nil + } + } + + func getHostMatching(_ HostSearchingFor: Host) -> Host? { + guard let index = getHostIndexMatching(HostSearchingFor) else { return nil } + return savedHosts[index] + } + + func updateHost(_ updatedHost: Host) { + if let index = savedHosts.firstIndex(where: { $0.id == updatedHost.id }) { + savedHosts[index] = updatedHost + saveSavedHosts() + } + } + func loadSavedHosts() { let decoder = JSONDecoder() guard let data = userDefaults.data(forKey: "savedHosts") else { return } diff --git a/ShhShell/SSH/SSHHandler.swift b/ShhShell/SSH/SSHHandler.swift index c785bff..6eae2c5 100644 --- a/ShhShell/SSH/SSHHandler.swift +++ b/ShhShell/SSH/SSHHandler.swift @@ -17,7 +17,7 @@ class SSHHandler: ObservableObject { @Published var connected: Bool = false @Published var authorized: Bool = false - @Published var testSuceeded: Bool = false + @Published var testSuceeded: Bool? = nil @Published var host: Host @Published var terminal: String = "" @@ -86,8 +86,9 @@ class SSHHandler: ObservableObject { ssh_free(session) withAnimation { authorized = false } withAnimation { connected = false } + withAnimation { testSuceeded = nil } session = nil - host.key = nil +// host.key = nil } func testExec() { diff --git a/ShhShell/Views/ConnectionView.swift b/ShhShell/Views/ConnectionView.swift index 0e40959..9425c8a 100644 --- a/ShhShell/Views/ConnectionView.swift +++ b/ShhShell/Views/ConnectionView.swift @@ -26,103 +26,96 @@ struct ConnectionView: View { var body: some View { NavigationStack { List { - HStack { - TextField("", text: $pubkeyStr, prompt: Text("Public Key")) - .onSubmit { - pubkey = Data(pubkeyStr.utf8) - } - Button() { - pubPickerPresented.toggle() - } label: { - Image(systemName: "folder") + Section { + HStack { + Text(handler.connected ? "connected" : "not connected") + .modifier(foregroundColorStyle(handler.connected ? .green : .red)) + + Text(handler.authorized ? "authorized" : "unauthorized") + .modifier(foregroundColorStyle(handler.authorized ? .green : .red)) } - .buttonStyle(.plain) - .fileImporter(isPresented: $pubPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in - do { - let fileURL = try Result.get() - pubkey = try! Data(contentsOf: fileURL) - print(fileURL) - } catch { - print(error.localizedDescription) - } - } - } - - HStack { - TextField("", text: $privkeyStr, prompt: Text("Private Key")) - .onSubmit { - privkey = Data(privkeyStr.utf8) - } - Button() { - privPickerPresented.toggle() - } label: { - Image(systemName: "folder") - } - .buttonStyle(.plain) - .fileImporter(isPresented: $privPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in - do { - let fileURL = try Result.get() - privkey = try! Data(contentsOf: fileURL) - print(fileURL) - } catch { - print(error.localizedDescription) - } - } - } - - TextField("", text: $passphrase) - HStack { - Text(handler.connected ? "connected" : "not connected") - .modifier(foregroundColorStyle(handler.connected ? .green : .red)) + TextField("address", text: $handler.host.address) + .textFieldStyle(.roundedBorder) - Text(handler.authorized ? "authorized" : "unauthorized") - .modifier(foregroundColorStyle(handler.authorized ? .green : .red)) + TextField( + "port", + text: Binding( + get: { String(handler.host.port) }, + set: { + if let input = Int($0) { + handler.host.port = input + } + } + ) + ) + .keyboardType(.numberPad) + .textFieldStyle(.roundedBorder) + } + + Section { + TextField("Username", text: $handler.host.username) + .textFieldStyle(.roundedBorder) + + SecureField("Password", text: $handler.host.password) + .textFieldStyle(.roundedBorder) + + HStack { + TextField("", text: $pubkeyStr, prompt: Text("Public Key")) + .onSubmit { + pubkey = Data(pubkeyStr.utf8) + } + Button() { + pubPickerPresented.toggle() + } label: { + Image(systemName: "folder") + } + .buttonStyle(.plain) + .fileImporter(isPresented: $pubPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in + do { + let fileURL = try Result.get() + pubkey = try! Data(contentsOf: fileURL) + print(fileURL) + } catch { + print(error.localizedDescription) + } + } + } + + HStack { + TextField("", text: $privkeyStr, prompt: Text("Private Key")) + .onSubmit { + privkey = Data(privkeyStr.utf8) + } + Button() { + privPickerPresented.toggle() + } label: { + Image(systemName: "folder") + } + .buttonStyle(.plain) + .fileImporter(isPresented: $privPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in + do { + let fileURL = try Result.get() + privkey = try! Data(contentsOf: fileURL) + print(fileURL) + } catch { + print(error.localizedDescription) + } + } + } + TextField("", text: $passphrase, prompt: Text("Passphrase (Optional)")) } if handler.host.key != nil { Text("Hostkey: \(handler.host.key!.base64EncodedString())") - } - - TextField("address", text: $handler.host.address) - .textFieldStyle(.roundedBorder) - - TextField( - "port", - text: Binding( - get: { String(handler.host.port) }, - set: { handler.host.port = Int($0) ?? 22} ) - ) - .keyboardType(.numberPad) - .textFieldStyle(.roundedBorder) - - TextField("username", text: $handler.host.username) - .textFieldStyle(.roundedBorder) - - SecureField("password", text: $handler.host.password) - .textFieldStyle(.roundedBorder) - - if handler.connected { - Button() { - handler.disconnect() - } label: { - Label("Disconnect", systemImage: "xmark.app.fill") - } - } else { - Button() { - handler.connect() - if pubkey != nil && privkey != nil { - handler.authWithPubkey(pub: pubkey!, priv: privkey!, pass: passphrase) - } else { - let _ = handler.authWithPw() + .onChange(of: handler.host.key) { _ in + guard let matchedIndex = hostsManager.getHostIndexMatching(handler.host) else { return } + guard handler.host.key == hostsManager.savedHosts[matchedIndex].key else { + let hostkeyBefore = hostsManager.savedHosts[matchedIndex].key + let currentHostkey = handler.host.key + print("hiiii") + fatalError() + } } - handler.openShell() - } label: { - Label("Connect", systemImage: "powerplug.portrait") - } - .disabled( - pubkey == nil && privkey == nil && - handler.host.username.isEmpty && handler.host.password.isEmpty - ) } NavigationLink() { @@ -135,21 +128,46 @@ struct ConnectionView: View { Button() { withAnimation { handler.testExec() } } label: { - if handler.testSuceeded { - Image(systemName: handler.testSuceeded ? "checkmark.circle" : "xmark.circle") - .modifier(foregroundColorStyle(handler.testSuceeded ? .green : .red)) + if let testResult = handler.testSuceeded { + Image(systemName: testResult ? "checkmark.circle" : "xmark.circle") + .modifier(foregroundColorStyle(testResult ? .green : .red)) } else { Label("Test Connection", systemImage: "checkmark") } } -// .disabled(!(handler.connected && handler.authorized)) } .transition(.opacity) + .toolbar { + ToolbarItem() { + if handler.connected { + Button() { + handler.disconnect() + } label: { + Label("Disconnect", systemImage: "xmark.app.fill") + } + } else { + Button() { + handler.connect() + if pubkey != nil && privkey != nil { + handler.authWithPubkey(pub: pubkey!, priv: privkey!, pass: passphrase) + } else { + let _ = handler.authWithPw() + } + handler.openShell() + } label: { + Label("Connect", systemImage: "powerplug.portrait") + } + .disabled( + pubkey == nil && privkey == nil && + handler.host.username.isEmpty && handler.host.password.isEmpty + ) + } + } + } } .onDisappear { - if let index = hostsManager.savedHosts.firstIndex(where: { $0.id == handler.host.id }) { - hostsManager.savedHosts[index] = handler.host - hostsManager.saveSavedHosts() + if hostsManager.getHostMatching(handler.host) != handler.host { + hostsManager.updateHost(handler.host) } } } diff --git a/ShhShell/Views/HostsView.swift b/ShhShell/Views/HostsView.swift index f9d69ff..ca2bb9b 100644 --- a/ShhShell/Views/HostsView.swift +++ b/ShhShell/Views/HostsView.swift @@ -25,25 +25,20 @@ struct HostsView: View { .buttonStyle(.borderedProminent) } ForEach(hostsManager.savedHosts) { host in - HStack { + NavigationLink() { + ConnectionView( + handler: SSHHandler(host: host), + keyManager: keyManager, + hostsManager: hostsManager + ) + } label: { if host.address.isEmpty { Text(host.id.uuidString) } else { Text(host.address) } - NavigationLink() { - ConnectionView( - handler: SSHHandler(host: host), - keyManager: keyManager, - hostsManager: hostsManager - ) - } label: { - Image(systemName: "info.circle") - } - .onChange(of: host) { _ in - hostsManager.saveSavedHosts() - } } + .animation(.default, value: host) .swipeActions(edge: .trailing) { Button(role: .destructive) { if let index = hostsManager.savedHosts.firstIndex(where: { $0.id == host.id }) { @@ -56,22 +51,21 @@ struct HostsView: View { } } } - .transition(.scale) + .transition(.opacity) .toolbar { ToolbarItem(placement: .confirmationAction) { - if !hostsManager.savedHosts.isEmpty { - NavigationLink { - ConnectionView( - handler: SSHHandler(host: hostsManager.savedHosts.last!), - keyManager: keyManager, - hostsManager: hostsManager - ) - .onAppear() { - withAnimation { hostsManager.savedHosts.append(Host.blank) } - } - } label: { - Label("Add", systemImage: "plus") + let host = Host.blank + NavigationLink { + ConnectionView( + handler: SSHHandler(host: host), + keyManager: keyManager, + hostsManager: hostsManager + ) + .task(priority: .userInitiated) { + withAnimation { hostsManager.savedHosts.append(host) } } + } label: { + Label("Add", systemImage: "plus") } } }