fix duplicate ssh keys in keys, now shows the hosts that use that key if u tap on it

added theme deletion
added animations on change of themes list
getkeys doesnt return duplicates, thanks to:
wrote a custom == operator to check if keys are the same, ignoring uuid
added gethostskeysusedon to get the hosts that use a key
fix refreshing of hosts on modification
added cancel to import alert
This commit is contained in:
neon443
2025-06-28 14:59:59 +01:00
parent d0c84b19f4
commit 7ceef899df
5 changed files with 46 additions and 5 deletions

View File

@@ -50,6 +50,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
func renameTheme(_ theme: Theme?, to newName: String) { func renameTheme(_ theme: Theme?, to newName: String) {
guard let theme else { return } guard let theme else { return }
guard theme.name != newName else { return }
guard let index = themes.firstIndex(where: {$0.id == theme.id}) else { return } guard let index = themes.firstIndex(where: {$0.id == theme.id}) else { return }
var newTheme = themes[index] var newTheme = themes[index]
newTheme.name = newName newTheme.name = newName
@@ -58,6 +59,12 @@ class HostsManager: ObservableObject, @unchecked Sendable {
saveThemes() saveThemes()
} }
func deleteTheme(_ themeToDel: Theme) {
guard let index = themes.firstIndex(where: {$0 == themeToDel}) else { return }
themes.remove(at: index)
saveThemes()
}
@MainActor @MainActor
func importTheme(name: String, data: Data?) { func importTheme(name: String, data: Data?) {
guard let data else { return } guard let data else { return }
@@ -156,15 +163,26 @@ class HostsManager: ObservableObject, @unchecked Sendable {
func getKeys() -> [Keypair] { func getKeys() -> [Keypair] {
var result: [Keypair] = [] var result: [Keypair] = []
for host in savedHosts { for host in savedHosts {
if result.contains(where: { $0 == Keypair(publicKey: host.publicKey, privateKey: host.privateKey)}) { let keypair = Keypair(publicKey: host.publicKey, privateKey: host.privateKey)
if !result.contains(keypair) {
} else { result.append(keypair)
result.append(Keypair(publicKey: host.publicKey, privateKey: host.privateKey))
} }
} }
return result return result
} }
func getHostsKeysUsedOn(_ keys: [Keypair]) -> [Host] {
var result: [Host] = []
for key in keys {
let hosts = savedHosts.filter({
$0.publicKey == key.publicKey &&
$0.privateKey == key.privateKey
})
result += hosts
}
return result
}
func authWithBiometrics() async -> Bool { func authWithBiometrics() async -> Bool {
let context = LAContext() let context = LAContext()
var error: NSError? var error: NSError?

View File

@@ -24,4 +24,13 @@ struct Keypair: KeypairProtocol {
self.publicKey = publicKey self.publicKey = publicKey
self.privateKey = privateKey self.privateKey = privateKey
} }
static func ==(lhs: Keypair, rhs: Keypair) -> Bool {
if lhs.publicKey?.base64EncodedString() == rhs.publicKey?.base64EncodedString()
&& lhs.privateKey?.base64EncodedString() == rhs.privateKey?.base64EncodedString() {
return true
} else {
return false
}
}
} }

View File

@@ -48,6 +48,7 @@ struct HostsView: View {
.frame(width: 40, height: 40) .frame(width: 40, height: 40)
Text(hostsManager.makeLabel(forHost: host)) Text(hostsManager.makeLabel(forHost: host))
} }
.id(host)
.animation(.default, value: host) .animation(.default, value: host)
.swipeActions(edge: .trailing) { .swipeActions(edge: .trailing) {
Button(role: .destructive) { Button(role: .destructive) {

View File

@@ -14,6 +14,17 @@ struct KeyDetailView: View {
var body: some View { var body: some View {
List { List {
VStack(alignment: .leading) {
Text("Used on")
.bold()
ForEach(hostsManager.getHostsKeysUsedOn([keypair])) { host in
HStack {
SymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(hostsManager.makeLabel(forHost: host))
}
}
}
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Public key") Text("Public key")
.bold() .bold()

View File

@@ -40,13 +40,14 @@ struct ThemeManagerView: View {
Label("Rename", systemImage: "pencil") Label("Rename", systemImage: "pencil")
} }
Button(role: .destructive) { Button(role: .destructive) {
hostsManager.deleteTheme(theme)
} label: { } label: {
Label("Delete", systemImage: "trash") Label("Delete", systemImage: "trash")
} }
} }
} }
} }
.animation(.default, value: hostsManager.themes)
.alert("", isPresented: $showRenameAlert) { .alert("", isPresented: $showRenameAlert) {
TextField("", text: $rename) TextField("", text: $rename)
Button("OK") { Button("OK") {
@@ -66,6 +67,7 @@ struct ThemeManagerView: View {
} label: { } label: {
Label("Import", systemImage: "square.and.arrow.down") Label("Import", systemImage: "square.and.arrow.down")
} }
Button("Cancel") {}
} }
.toolbar { .toolbar {
ToolbarItem() { ToolbarItem() {