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