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

@@ -63,7 +63,7 @@
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 */; };
A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */; };
A9FD37552E143D23005319A8 /* SecKeyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD37542E143D23005319A8 /* SecKeyConvertible.swift */; };
A9FD37572E143D5A005319A8 /* SecKeyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD37562E143D5A005319A8 /* SecKeyStore.swift */; };
A9FD37592E143D74005319A8 /* GenericPasswordConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD37582E143D74005319A8 /* GenericPasswordConvertible.swift */; };
@@ -165,7 +165,7 @@
A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreview.swift; sourceTree = "<group>"; };
A9D819302E102D8700442D38 /* HostkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostkeysView.swift; sourceTree = "<group>"; };
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = "<group>"; };
A9DA97722E0D40C100142DDC /* SymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolPreview.swift; sourceTree = "<group>"; };
A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbolPreview.swift; sourceTree = "<group>"; };
A9FD37542E143D23005319A8 /* SecKeyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecKeyConvertible.swift; sourceTree = "<group>"; };
A9FD37562E143D5A005319A8 /* SecKeyStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecKeyStore.swift; sourceTree = "<group>"; };
A9FD37582E143D74005319A8 /* GenericPasswordConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPasswordConvertible.swift; sourceTree = "<group>"; };
@@ -349,6 +349,7 @@
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
A9B15A992E0ABA0400F66E02 /* DialogView.swift */,
A96C90A02E12B87900724253 /* TextBox.swift */,
A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */,
);
path = Misc;
sourceTree = "<group>";
@@ -379,7 +380,6 @@
A93143BF2DF61B3200FCD5DB /* Host.swift */,
A98554602E058433009051BD /* HostsManager.swift */,
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */,
A9DA97722E0D40C100142DDC /* SymbolPreview.swift */,
);
path = Host;
sourceTree = "<group>";
@@ -601,7 +601,7 @@
A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */,
A98554632E0587DF009051BD /* HostsView.swift in Sources */,
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */,
A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */,
A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */,
A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */,
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */,

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,6 +22,37 @@ struct KeyDetailView: View {
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
.ignoresSafeArea(.all)
List {
VStack(alignment: .leading) {
Text("Used on")
.bold()
ForEach(hostsManager.getHostsUsingKeys([keypair])) { host in
HStack {
HostSymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(host.description)
}
}
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)
}
}
}
}
Section() {
TextBox(label: "Name", text: $keyname, prompt: "A name for your key")
.onAppear {
keyname = keypair.name
@@ -30,48 +61,6 @@ struct KeyDetailView: View {
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)
.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 }
}
withAnimation(.spring) { reveal.toggle() }
}
}
}
Button {
UIPasteboard.general.string = keypair.openSshPubkey
} label: {
@@ -96,6 +85,40 @@ struct KeyDetailView: View {
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,15 +46,19 @@ struct KeyImporterView: View {
.foregroundStyle(.gray)
}
}
Button() {
keyManager.importKey(type: keyType, priv: privkeyStr, name: keyName)
dismiss()
} label: {
Label("Import", systemImage: "key.horizontal")
Text("Import")
}
.onTapGesture {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
.buttonStyle(.borderedProminent)
}
}
}
#Preview {

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)
}