improve ui on keydetailview

add set(keypair, onHost) to set a keypair on host
remove getkeys it always returns []
add delete button to
remove rsa from keytype
rename symbolpreview ->hostsymbolpreview
This commit is contained in:
neon443
2025-07-02 21:55:50 +01:00
parent 6ad8ed22a0
commit c9d7b06305
10 changed files with 110 additions and 88 deletions

View File

@@ -169,12 +169,10 @@ class HostsManager: ObservableObject, @unchecked Sendable {
}
}
func getKeys() -> [Keypair] {
var result: [Keypair] = []
for host in hosts {
guard let keyID = host.privateKeyID else { continue }
}
return result
func set(keypair: Keypair, onHost: Host) {
guard let index = hosts.firstIndex(where: { $0.id == onHost.id }) else { return }
hosts[index].privateKeyID = keypair.id
saveHosts()
}
func getHostsUsingKeys(_ keys: [Keypair]) -> [Host] {

View File

@@ -140,7 +140,8 @@ class KeyManager: ObservableObject {
func importKey(type: KeyType, priv: String, name: String) {
if type == .ed25519 {
saveToKeychain(KeyManager.importSSHPrivkey(priv: priv))
guard let importedKeypair = KeyManager.importSSHPrivkey(priv: priv) else { return }
saveToKeychain(importedKeypair)
saveKeypairs()
} else { fatalError() }
}
@@ -156,8 +157,6 @@ class KeyManager: ObservableObject {
)
saveToKeychain(keypair)
saveKeypairs()
case .rsa:
fatalError("unimplemented")
}
loadKeypairs()
}
@@ -187,7 +186,8 @@ class KeyManager: ObservableObject {
return Data(pubkeyline.utf8)
}
static func importSSHPrivkey(priv: String) -> Keypair {
static func importSSHPrivkey(priv: String) -> Keypair? {
guard !priv.isEmpty else { return nil }
var split = priv.replacingOccurrences(of: "-----BEGIN OPENSSH PRIVATE KEY-----\n", with: "")
split = split.replacingOccurrences(of: "-----BEGIN OPENSSH PRIVATE KEY-----", with: "")
split = split.replacingOccurrences(of: "\n-----END OPENSSH PRIVATE KEY-----\n", with: "")

View File

@@ -9,14 +9,11 @@ import Foundation
enum KeyType: Codable, Equatable, Hashable, CustomStringConvertible, CaseIterable {
case ed25519
case rsa
var description: String {
switch self {
case .ed25519:
return "Ed25519"
case .rsa:
return "RSA"
}
}
}

View File

@@ -35,7 +35,7 @@ struct ConnectionView: View {
RoundedRectangle(cornerRadius: 10)
.fill(.gray.opacity(0.5))
}
SymbolPreview(symbol: symbol, label: handler.host.label)
HostSymbolPreview(symbol: symbol, label: handler.host.label)
.padding(5)
}
.frame(width: 60, height: 60)
@@ -47,7 +47,7 @@ struct ConnectionView: View {
}
HStack {
SymbolPreview(symbol: handler.host.symbol, label: handler.host.label)
HostSymbolPreview(symbol: handler.host.symbol, label: handler.host.label)
.id(handler.host)
.frame(width: 60, height: 60)

View File

@@ -26,7 +26,7 @@ struct HostsView: View {
keyManager: keyManager
)
} label: {
SymbolPreview(symbol: host.symbol, label: host.label)
HostSymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(host.description)
}

View File

@@ -22,79 +22,102 @@ struct KeyDetailView: View {
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
.ignoresSafeArea(.all)
List {
TextBox(label: "Name", text: $keyname, prompt: "A name for your key")
.onAppear {
keyname = keypair.name
}
.onChange(of: keyname) { _ in
keyManager.renameKey(keypair: keypair, newName: keyname)
}
VStack(alignment: .leading) {
Text("Used on")
.bold()
ForEach(hostsManager.getHostsUsingKeys([keypair])) { host in
HStack {
SymbolPreview(symbol: host.symbol, label: host.label)
HostSymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(host.description)
}
}
}
VStack(alignment: .leading) {
Text("Public key")
.bold()
Text(keypair.openSshPubkey)
}
VStack(alignment: .leading) {
Text("Private key")
.bold()
.frame(maxWidth: .infinity)
ZStack(alignment: .center) {
Text(keypair.openSshPrivkey)
.blur(radius: reveal ? 0 : 5)
VStack {
Image(systemName: "eye.slash.fill")
.resizable().scaledToFit()
.frame(width: 50)
Text("Tap to reveal")
}
.opacity(reveal ? 0 : 1)
}
.frame(maxWidth: .infinity)
.onTapGesture {
Task {
if !reveal {
guard await authWithBiometrics() else { return }
Menu("Add") {
let hostsNotUsingKey = hostsManager.hosts.filter(
{
hostsManager.getHostsUsingKeys([keypair]).contains($0)
})
ForEach(hostsNotUsingKey) { host in
Button() {
hostsManager.set(keypair: keypair, onHost: host)
} label: {
Image(systemName: "plus")
.resizable().scaledToFit()
.foregroundStyle(.blue)
.frame(width: 30, height: 30)
Text("Add")
.foregroundStyle(.blue)
}
withAnimation(.spring) { reveal.toggle() }
}
}
}
Button {
UIPasteboard.general.string = keypair.openSshPubkey
} label: {
CenteredLabel(title: "Copy public key", systemName: "document.on.document")
}
.listRowSeparator(.hidden)
Button {
Task {
guard await authWithBiometrics() else { return }
UIPasteboard.general.string = String(data: KeyManager.makeSSHPrivkey(keypair), encoding: .utf8) ?? ""
Section() {
TextBox(label: "Name", text: $keyname, prompt: "A name for your key")
.onAppear {
keyname = keypair.name
}
.onChange(of: keyname) { _ in
keyManager.renameKey(keypair: keypair, newName: keyname)
}
Button {
UIPasteboard.general.string = keypair.openSshPubkey
} label: {
CenteredLabel(title: "Copy public key", systemName: "document.on.document")
}
} label: {
CenteredLabel(title: "Copy private key", systemName: "document.on.document")
}
.listRowSeparator(.hidden)
CenteredLabel(title: "Delete", systemName: "trash")
.foregroundStyle(.red)
.onTapGesture {
keyManager.deleteKey(keypair)
dismiss()
.listRowSeparator(.hidden)
Button {
Task {
guard await authWithBiometrics() else { return }
UIPasteboard.general.string = String(data: KeyManager.makeSSHPrivkey(keypair), encoding: .utf8) ?? ""
}
} label: {
CenteredLabel(title: "Copy private key", systemName: "document.on.document")
}
.listRowSeparator(.hidden)
CenteredLabel(title: "Delete", systemName: "trash")
.foregroundStyle(.red)
.onTapGesture {
keyManager.deleteKey(keypair)
dismiss()
}
}
Section("Key") {
VStack(alignment: .leading) {
Text("Public key")
.bold()
Text(keypair.openSshPubkey.dropLast(2))
}
VStack(alignment: .leading) {
Text("Private key")
.bold()
.frame(maxWidth: .infinity)
ZStack(alignment: .center) {
Text(keypair.openSshPrivkey.dropLast(2))
.blur(radius: reveal ? 0 : 5)
VStack {
Image(systemName: "eye.slash.fill")
.resizable().scaledToFit()
.frame(width: 50)
Text("Tap to reveal")
}
.opacity(reveal ? 0 : 1)
}
.frame(maxWidth: .infinity)
.onTapGesture {
Task {
if !reveal {
guard await authWithBiometrics() else { return }
}
withAnimation(.spring) { reveal.toggle() }
}
}
}
}
}
.scrollContentBackground(.hidden)
}

View File

@@ -46,14 +46,18 @@ struct KeyImporterView: View {
.foregroundStyle(.gray)
}
Button() {
keyManager.importKey(type: keyType, priv: privkeyStr, name: keyName)
dismiss()
} label: {
Label("Import", systemImage: "key.horizontal")
}
.buttonStyle(.borderedProminent)
}
Button() {
keyManager.importKey(type: keyType, priv: privkeyStr, name: keyName)
dismiss()
} label: {
Text("Import")
}
.onTapGesture {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
.buttonStyle(.borderedProminent)
}
}

View File

@@ -1,5 +1,5 @@
//
// SymbolPreview.swift
// HostSymbolPreview.swift
// ShhShell
//
// Created by neon443 on 26/06/2025.
@@ -7,7 +7,7 @@
import SwiftUI
struct SymbolPreview: View {
struct HostSymbolPreview: View {
@State var symbol: HostSymbol
@State var label: String
@@ -30,5 +30,5 @@ struct SymbolPreview: View {
}
#Preview {
SymbolPreview(symbol: HostSymbol.desktopcomputer, label: "lo0")
HostSymbolPreview(symbol: HostSymbol.desktopcomputer, label: "lo0")
}

View File

@@ -28,7 +28,7 @@ struct SessionView: View {
.resizable().scaledToFit()
.frame(width: 40, height: 40)
.foregroundStyle(.terminalGreen)
SymbolPreview(symbol: host.symbol, label: host.label)
HostSymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(host.description)
}