From 4affc532d97a2eee0ffbc50871c6c03b2a015d20 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Sat, 28 Jun 2025 15:37:50 +0100 Subject: [PATCH] fix adding new hosts, it will navigate u to a new connection view with a host.blank, and add the host if it doesnt exist added addhostifneeded extracted hostskeys stuff to hostkeysview added nav titles to everything removed tabs, now just a list renamed savedHosts -> hosts --- ShhShell.xcodeproj/project.pbxproj | 4 + ShhShell/Host/HostsManager.swift | 48 +++--- ShhShell/ShhShellApp.swift | 2 +- ShhShell/Views/ContentView.swift | 39 +++-- ShhShell/Views/Hosts/ConnectionView.swift | 3 + ShhShell/Views/Hosts/HostsView.swift | 145 +++++++++---------- ShhShell/Views/Keys/HostkeysView.swift | 58 ++++++++ ShhShell/Views/Keys/KeyManagerView.swift | 41 +----- ShhShell/Views/Themes/ThemeManagerView.swift | 1 + 9 files changed, 185 insertions(+), 156 deletions(-) create mode 100644 ShhShell/Views/Keys/HostkeysView.swift diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index b064134..afa87e8 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; }; A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */; }; A9D8192F2E0F1BEE00442D38 /* ThemePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */; }; + A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819302E102D8700442D38 /* HostkeysView.swift */; }; A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97702E0D30ED00142DDC /* HostSymbol.swift */; }; A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* SymbolPreview.swift */; }; /* End PBXBuildFile section */ @@ -118,6 +119,7 @@ A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = ""; }; A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreview.swift; sourceTree = ""; }; + A9D819302E102D8700442D38 /* HostkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostkeysView.swift; sourceTree = ""; }; A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = ""; }; A9DA97722E0D40C100142DDC /* SymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolPreview.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -282,6 +284,7 @@ children = ( A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */, A98554542E05535F009051BD /* KeyManagerView.swift */, + A9D819302E102D8700442D38 /* HostkeysView.swift */, ); path = Keys; sourceTree = ""; @@ -497,6 +500,7 @@ A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */, A98554592E0553AA009051BD /* KeyManager.swift in Sources */, A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */, + A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */, A98554552E05535F009051BD /* KeyManagerView.swift in Sources */, A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */, A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */, diff --git a/ShhShell/Host/HostsManager.swift b/ShhShell/Host/HostsManager.swift index 2b84207..682fd9d 100644 --- a/ShhShell/Host/HostsManager.swift +++ b/ShhShell/Host/HostsManager.swift @@ -12,11 +12,11 @@ import SwiftUI class HostsManager: ObservableObject, @unchecked Sendable { private let userDefaults = NSUbiquitousKeyValueStore.default - @Published var savedHosts: [Host] = [] + @Published var hosts: [Host] = [] @Published var themes: [Theme] = [] init() { - loadSavedHosts() + loadHosts() loadThemes() } @@ -84,7 +84,7 @@ class HostsManager: ObservableObject, @unchecked Sendable { } func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? { - if let index = savedHosts.firstIndex(where: { $0.id == hostSearchingFor.id }) { + if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) { return index } else { return nil @@ -93,28 +93,28 @@ class HostsManager: ObservableObject, @unchecked Sendable { func getHostMatching(_ HostSearchingFor: Host) -> Host? { guard let index = getHostIndexMatching(HostSearchingFor) else { return nil } - return savedHosts[index] + return hosts[index] } func updateHost(_ updatedHost: Host) { let oldID = updatedHost.id - if let index = savedHosts.firstIndex(where: { $0.id == updatedHost.id }) { + if let index = hosts.firstIndex(where: { $0.id == updatedHost.id }) { var updateHostWithNewID = updatedHost updateHostWithNewID.id = UUID() - withAnimation { savedHosts[index] = updateHostWithNewID } + withAnimation { hosts[index] = updateHostWithNewID } updateHostWithNewID.id = oldID - withAnimation { savedHosts[index] = updateHostWithNewID } - saveSavedHosts() + withAnimation { hosts[index] = updateHostWithNewID } + saveHosts() } } func duplicateHost(_ hostToDup: Host) { var hostNewID = hostToDup hostNewID.id = UUID() - if let index = savedHosts.firstIndex(where: { $0 == hostToDup }) { - savedHosts.insert(hostNewID, at: index+1) + if let index = hosts.firstIndex(where: { $0 == hostToDup }) { + hosts.insert(hostNewID, at: index+1) } } @@ -131,38 +131,44 @@ class HostsManager: ObservableObject, @unchecked Sendable { } func moveHost(from: IndexSet, to: Int) { - savedHosts.move(fromOffsets: from, toOffset: to) - saveSavedHosts() + hosts.move(fromOffsets: from, toOffset: to) + saveHosts() } - func loadSavedHosts() { + func loadHosts() { userDefaults.synchronize() let decoder = JSONDecoder() guard let data = userDefaults.data(forKey: "savedHosts") else { return } if let decoded = try? decoder.decode([Host].self, from: data) { - self.savedHosts = decoded + self.hosts = decoded } } - func saveSavedHosts() { + func saveHosts() { let encoder = JSONEncoder() - if let encoded = try? encoder.encode(savedHosts) { + if let encoded = try? encoder.encode(hosts) { userDefaults.set(encoded, forKey: "savedHosts") userDefaults.synchronize() } } + func addHostIfNeeded(_ hostToAdd: Host) { + if !hosts.contains(hostToAdd) { + hosts.append(hostToAdd) + } + } + func removeHost(_ host: Host) { - if let index = savedHosts.firstIndex(where: { $0.id == host.id }) { - let _ = withAnimation { savedHosts.remove(at: index) } - saveSavedHosts() + if let index = hosts.firstIndex(where: { $0.id == host.id }) { + let _ = withAnimation { hosts.remove(at: index) } + saveHosts() } } func getKeys() -> [Keypair] { var result: [Keypair] = [] - for host in savedHosts { + for host in hosts { let keypair = Keypair(publicKey: host.publicKey, privateKey: host.privateKey) if !result.contains(keypair) { result.append(keypair) @@ -174,7 +180,7 @@ class HostsManager: ObservableObject, @unchecked Sendable { func getHostsKeysUsedOn(_ keys: [Keypair]) -> [Host] { var result: [Host] = [] for key in keys { - let hosts = savedHosts.filter({ + let hosts = hosts.filter({ $0.publicKey == key.publicKey && $0.privateKey == key.privateKey }) diff --git a/ShhShell/ShhShellApp.swift b/ShhShell/ShhShellApp.swift index d4a41ee..bd7f567 100644 --- a/ShhShell/ShhShellApp.swift +++ b/ShhShell/ShhShellApp.swift @@ -17,7 +17,7 @@ struct ShhShellApp: App { WindowGroup { ContentView( handler: sshHandler, - hostsManger: hostsManager, + hostsManager: hostsManager, keyManager: keyManager ) .colorScheme(.dark) diff --git a/ShhShell/Views/ContentView.swift b/ShhShell/Views/ContentView.swift index 64de759..812aa12 100644 --- a/ShhShell/Views/ContentView.swift +++ b/ShhShell/Views/ContentView.swift @@ -9,31 +9,38 @@ import SwiftUI struct ContentView: View { @ObservedObject var handler: SSHHandler - @ObservedObject var hostsManger: HostsManager + @ObservedObject var hostsManager: HostsManager @ObservedObject var keyManager: KeyManager - var body: some View { - TabView { - HostsView( - handler: handler, - hostsManager: hostsManger, - keyManager: keyManager - ) - .tabItem { - Label("Hosts", systemImage: "server.rack") - } - KeyManagerView(hostsManager: hostsManger, keyManager: keyManager) - .tabItem { - Label("Keys", systemImage: "key.2.on.ring") + var body: some View { + NavigationStack { + List { + HostsView( + handler: handler, + hostsManager: hostsManager, + keyManager: keyManager + ) + + NavigationLink { + KeyManagerView(hostsManager: hostsManager, keyManager: keyManager) + } label: { + Label("Keys", systemImage: "key.fill") } + + NavigationLink { + HostkeysView(hostsManager: hostsManager) + } label: { + Label("Hostkey Fingerprints", systemImage: "lock.display") + } + } } - } + } } #Preview { ContentView( handler: SSHHandler(host: Host.debug), - hostsManger: HostsManager(), + hostsManager: HostsManager(), keyManager: KeyManager() ) } diff --git a/ShhShell/Views/Hosts/ConnectionView.swift b/ShhShell/Views/Hosts/ConnectionView.swift index ab251a9..ed3fe36 100644 --- a/ShhShell/Views/Hosts/ConnectionView.swift +++ b/ShhShell/Views/Hosts/ConnectionView.swift @@ -188,6 +188,9 @@ struct ConnectionView: View { shellView = ShellView(handler: handler) } } + .onAppear { + hostsManager.addHostIfNeeded(handler.host) + } } } diff --git a/ShhShell/Views/Hosts/HostsView.swift b/ShhShell/Views/Hosts/HostsView.swift index d0163ce..f1e79dd 100644 --- a/ShhShell/Views/Hosts/HostsView.swift +++ b/ShhShell/Views/Hosts/HostsView.swift @@ -12,89 +12,76 @@ struct HostsView: View { @ObservedObject var hostsManager: HostsManager @ObservedObject var keyManager: KeyManager - var body: some View { - NavigationStack { - List { - if hostsManager.savedHosts.isEmpty { - Text("Add your first Host!") - Button() { - withAnimation { hostsManager.savedHosts.append(Host.blank) } - } label: { - Text("Create") - } - .buttonStyle(.borderedProminent) - } - - //proves that u can connect to multiple at the same time - NavigationLink() { - ForEach(hostsManager.savedHosts) { host in - let miniHandler = SSHHandler(host: host) - TerminalController(handler: miniHandler) - .onAppear { miniHandler.go() } - } - } label: { - Label("multiview", systemImage: "square.split.2x2") - } - - ForEach(hostsManager.savedHosts) { host in - NavigationLink() { - ConnectionView( - handler: SSHHandler(host: host), - hostsManager: hostsManager, - keyManager: keyManager - ) - } label: { - SymbolPreview(symbol: host.symbol, label: host.label) - .frame(width: 40, height: 40) - Text(hostsManager.makeLabel(forHost: host)) - } - .id(host) - .animation(.default, value: host) - .swipeActions(edge: .trailing) { - Button(role: .destructive) { - hostsManager.removeHost(host) - } label: { - Label("Delete", systemImage: "trash") - } - Button() { - hostsManager.duplicateHost(host) - } label: { - Label("Duplicate", systemImage: "square.filled.on.square") - } - } - } - .onMove(perform: { - hostsManager.moveHost(from: $0, to: $1) - }) - - Section() { - NavigationLink { - ThemeManagerView(hostsManager: hostsManager) - } label: { - Label("Themes", systemImage: "swatchpalette") - } - } + var body: some View { + if hostsManager.hosts.isEmpty { + Text("Add your first Host!") + } + + //proves that u can connect to multiple at the same time + NavigationLink() { + ForEach(hostsManager.hosts) { host in + let miniHandler = SSHHandler(host: host) + TerminalController(handler: miniHandler) + .onAppear { miniHandler.go() } } - .transition(.opacity) - .toolbar { - ToolbarItem(placement: .confirmationAction) { - let host = Host.blank - NavigationLink { - ConnectionView( - handler: SSHHandler(host: host), - hostsManager: hostsManager, - keyManager: keyManager - ) - .onAppear { - withAnimation { hostsManager.savedHosts.append(host) } - } - } label: { - Label("Add", systemImage: "plus") - } + } label: { + Label("multiview", systemImage: "square.split.2x2") + } + + ForEach(hostsManager.hosts) { host in + NavigationLink() { + ConnectionView( + handler: SSHHandler(host: host), + hostsManager: hostsManager, + keyManager: keyManager + ) + } label: { + SymbolPreview(symbol: host.symbol, label: host.label) + .frame(width: 40, height: 40) + Text(hostsManager.makeLabel(forHost: host)) + } + .id(host) + .animation(.default, value: host) + .swipeActions(edge: .trailing) { + Button(role: .destructive) { + hostsManager.removeHost(host) + } label: { + Label("Delete", systemImage: "trash") + } + Button() { + hostsManager.duplicateHost(host) + } label: { + Label("Duplicate", systemImage: "square.filled.on.square") } } } - } + .onMove(perform: { + hostsManager.moveHost(from: $0, to: $1) + }) + + Section() { + NavigationLink { + ThemeManagerView(hostsManager: hostsManager) + } label: { + Label("Themes", systemImage: "swatchpalette") + } + } + .transition(.opacity) + .navigationTitle("ShhShell") + .toolbar { + ToolbarItem(placement: .confirmationAction) { + NavigationLink { + ConnectionView( + handler: SSHHandler(host: Host.blank), + hostsManager: hostsManager, + keyManager: keyManager + ) + } label: { + Label("Add", systemImage: "plus") + } + } + } + } } #Preview { diff --git a/ShhShell/Views/Keys/HostkeysView.swift b/ShhShell/Views/Keys/HostkeysView.swift new file mode 100644 index 0000000..006d037 --- /dev/null +++ b/ShhShell/Views/Keys/HostkeysView.swift @@ -0,0 +1,58 @@ +// +// HostkeysView.swift +// ShhShell +// +// Created by neon443 on 28/06/2025. +// + +import SwiftUI + +struct HostkeysView: View { + @ObservedObject var hostsManager: HostsManager + + var body: some View { + NavigationStack { + List { + if hostsManager.hosts.isEmpty { + VStack(alignment: .leading) { + Text("Looking empty 'round here...") + .font(.title3) + .bold() + .padding(.bottom) + VStack(alignment: .leading) { + Text("Connect to some hosts to collect more hostkeys!") + .padding(.bottom) + Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.") + .font(.subheadline) + Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.") + .font(.subheadline) + } + } + } + + ForEach(hostsManager.hosts) { host in + VStack(alignment: .leading) { + if !host.name.isEmpty { + Text("name") + .foregroundStyle(.gray) + .font(.caption) + Text(host.name) + .bold() + } + Text("address") + .foregroundStyle(.gray) + .font(.caption) + Text(host.address) + .bold() + Text(host.key ?? "nil") + } + } + } + .navigationTitle("Hostkeys") + } + } +} + +#Preview { + HostkeysView(hostsManager: HostsManager()) +} diff --git a/ShhShell/Views/Keys/KeyManagerView.swift b/ShhShell/Views/Keys/KeyManagerView.swift index 12ca671..5c664ea 100644 --- a/ShhShell/Views/Keys/KeyManagerView.swift +++ b/ShhShell/Views/Keys/KeyManagerView.swift @@ -25,45 +25,7 @@ struct KeyManagerView: View { } } } - Section { - NavigationLink { - List { - if hostsManager.savedHosts.isEmpty { - VStack(alignment: .leading) { - Text("Looking empty 'round here...") - .font(.title3) - .bold() - .padding(.bottom) - VStack(alignment: .leading) { - Text("Connect to some hosts to collect more hostkeys!") - .padding(.bottom) - Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.") - .font(.subheadline) - Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.") - .font(.subheadline) - } - } - } - ForEach(hostsManager.savedHosts) { host in - VStack(alignment: .leading) { - if !host.name.isEmpty { - Text(host.name) - .bold() - } - Text(host.address) - .bold() - Text(host.key ?? "nil") - } - } - } - } label: { - HStack { - Image(systemName: "server.rack") - Image(systemName: "key.fill") - Text("Hostkey fingerprints") - } - } - } + Button("ed25519") { keyManager.generateEd25519() } @@ -75,6 +37,7 @@ struct KeyManagerView: View { } } } + .navigationTitle("Keys") } } } diff --git a/ShhShell/Views/Themes/ThemeManagerView.swift b/ShhShell/Views/Themes/ThemeManagerView.swift index 58feb94..58bd9c4 100644 --- a/ShhShell/Views/Themes/ThemeManagerView.swift +++ b/ShhShell/Views/Themes/ThemeManagerView.swift @@ -59,6 +59,7 @@ struct ThemeManagerView: View { } .fixedSize(horizontal: false, vertical: true) .scrollIndicators(.hidden) + .navigationTitle("Themes") .alert("Enter URL", isPresented: $showAlert) { TextField("", text: $importURL, prompt: Text("URL")) Button() {