mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
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:
@@ -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 */,
|
||||
|
||||
@@ -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] {
|
||||
|
||||
@@ -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: "")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
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 {
|
||||
Task {
|
||||
guard await authWithBiometrics() else { return }
|
||||
UIPasteboard.general.string = String(data: KeyManager.makeSSHPrivkey(keypair), encoding: .utf8) ?? ""
|
||||
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)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
CenteredLabel(title: "Delete", systemName: "trash")
|
||||
.foregroundStyle(.red)
|
||||
.onTapGesture {
|
||||
keyManager.deleteKey(keypair)
|
||||
dismiss()
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user