added a prompt to TextBox

fix right alignment in textbox
added quick connect
fix blank keys being returned from getKeys()
safely unwrap keys in keydetailview
swap buttons on shellview's titlbar
This commit is contained in:
neon443
2025-06-30 14:34:57 +01:00
parent 24bf52ff16
commit 94ad2fa661
6 changed files with 58 additions and 30 deletions

View File

@@ -185,6 +185,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
func getKeys() -> [Keypair] { func getKeys() -> [Keypair] {
var result: [Keypair] = [] var result: [Keypair] = []
for host in hosts { for host in hosts {
guard host.privateKey != nil && host.publicKey != nil else { continue }
let keypair = Keypair(publicKey: host.publicKey, privateKey: host.privateKey) let keypair = Keypair(publicKey: host.publicKey, privateKey: host.privateKey)
if !result.contains(keypair) { if !result.contains(keypair) {
result.append(keypair) result.append(keypair)

View File

@@ -51,34 +51,35 @@ struct ConnectionView: View {
.id(handler.host) .id(handler.host)
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
TextBox(label: "Icon Text", text: $handler.host.label) TextBox(label: "Icon Text", text: $handler.host.label, prompt: "a few letters in the icon")
} }
} }
Section { Section {
Text("\(handler.state)") Text("\(handler.state)")
.foregroundStyle(handler.state.color) .foregroundStyle(handler.state.color)
TextBox(label: "Name", text: $handler.host.name) TextBox(label: "Name", text: $handler.host.name, prompt: "defaults to host address")
TextBox(label: "Address", text: $handler.host.address) TextBox(label: "Address", text: $handler.host.address, prompt: "required")
TextBox(label: "Port", text: Binding( TextBox(label: "Port", text: Binding(
get: { String(handler.host.port) }, get: { String(handler.host.port) },
set: { set: {
if let input = Int($0) { if let input = Int($0) {
handler.host.port = input handler.host.port = input
} }
}), }),
prompt: "most likely 22",
keyboardType: .numberPad keyboardType: .numberPad
) )
} }
Section { Section {
TextBox(label: "Username", text: $handler.host.username) TextBox(label: "Username", text: $handler.host.username, prompt: "required")
TextBox(label: "Password", text: $handler.host.password, secure: true) TextBox(label: "Password", text: $handler.host.password, prompt: "not required if using publickeys", secure: true)
TextBox(label: "Publickey", text: $pubkeyStr) TextBox(label: "Publickey", text: $pubkeyStr, prompt: "in openssh format")
.onChange(of: pubkeyStr) { _ in .onChange(of: pubkeyStr) { _ in
let newStr = pubkeyStr.replacingOccurrences(of: "\r\n", with: "") let newStr = pubkeyStr.replacingOccurrences(of: "\r\n", with: "")
handler.host.publicKey = Data(newStr.utf8) handler.host.publicKey = Data(newStr.utf8)
@@ -88,7 +89,7 @@ struct ConnectionView: View {
handler.host.publicKey = Data(newStr.utf8) handler.host.publicKey = Data(newStr.utf8)
} }
TextBox(label: "Privatekey", text: $privkeyStr, secure: true) TextBox(label: "Privatekey", text: $privkeyStr, prompt: "required if using publickeys", secure: true)
.onSubmit { .onSubmit {
let newStr = privkeyStr.replacingOccurrences(of: "\r\n", with: "") let newStr = privkeyStr.replacingOccurrences(of: "\r\n", with: "")
handler.host.privateKey = Data(newStr.utf8) handler.host.privateKey = Data(newStr.utf8)
@@ -98,7 +99,7 @@ struct ConnectionView: View {
handler.host.privateKey = Data(newStr.utf8) handler.host.privateKey = Data(newStr.utf8)
} }
TextBox(label: "Passphrase", text: $handler.host.passphrase) TextBox(label: "Passphrase", text: $handler.host.passphrase, prompt: "optional")
} }
Button() { Button() {

View File

@@ -32,6 +32,24 @@ struct HostsView: View {
} }
.id(host) .id(host)
.animation(.default, value: host) .animation(.default, value: host)
.fullScreenCover(
isPresented: Binding(
get: { checkShell(handler.state) },
set: { newValue in
handler.go()
}
)
) {
ShellView(handler: handler, hostsManager: hostsManager)
}
.swipeActions(edge: .leading) {
Button() {
handler.go()
} label: {
Label("Quick Connect", systemImage: "power")
}
.foregroundStyle(.green)
}
.swipeActions(edge: .trailing) { .swipeActions(edge: .trailing) {
Button(role: .destructive) { Button(role: .destructive) {
hostsManager.removeHost(host) hostsManager.removeHost(host)
@@ -43,6 +61,7 @@ struct HostsView: View {
} label: { } label: {
Label("Duplicate", systemImage: "square.filled.on.square") Label("Duplicate", systemImage: "square.filled.on.square")
} }
.foregroundStyle(.blue)
} }
} }
.onMove(perform: { .onMove(perform: {

View File

@@ -12,6 +12,13 @@ struct KeyDetailView: View {
@State var keypair: Keypair @State var keypair: Keypair
@State private var reveal: Bool = false @State private var reveal: Bool = false
var publicKey: Data {
return keypair.publicKey ?? "".data(using: .utf8)!
}
var privateKey: Data {
return keypair.privateKey ?? "".data(using: .utf8)!
}
var body: some View { var body: some View {
ZStack { ZStack {
hostsManager.selectedTheme.background.suiColor.opacity(0.7) hostsManager.selectedTheme.background.suiColor.opacity(0.7)
@@ -31,14 +38,14 @@ struct KeyDetailView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Public key") Text("Public key")
.bold() .bold()
Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil") Text(String(data: publicKey, encoding: .utf8) ?? "nil")
} }
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Private key") Text("Private key")
.bold() .bold()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
ZStack(alignment: .center) { ZStack(alignment: .center) {
Text(String(data: keypair.privateKey!, encoding: .utf8) ?? "nil") Text(String(data: privateKey, encoding: .utf8) ?? "nil")
.blur(radius: reveal ? 0 : 5) .blur(radius: reveal ? 0 : 5)
VStack { VStack {
Image(systemName: "eye.slash.fill") Image(systemName: "eye.slash.fill")
@@ -62,9 +69,7 @@ struct KeyDetailView: View {
Button { Button {
Task { Task {
guard await hostsManager.authWithBiometrics() else { return } guard await hostsManager.authWithBiometrics() else { return }
if let privateKey = keypair.privateKey { UIPasteboard.general.string = String(data: privateKey, encoding: .utf8)
UIPasteboard.general.string = String(data: privateKey, encoding: .utf8)
}
} }
} label: { } label: {
CenteredLabel(title: "Copy private key", systemName: "document.on.document") CenteredLabel(title: "Copy private key", systemName: "document.on.document")

View File

@@ -10,22 +10,24 @@ import SwiftUI
struct TextBox: View { struct TextBox: View {
@State var label: String @State var label: String
@Binding var text: String @Binding var text: String
@State var prompt: String
@State var secure: Bool = false @State var secure: Bool = false
@State var keyboardType: UIKeyboardType = .default @State var keyboardType: UIKeyboardType = .default
var body: some View { var body: some View {
HStack { HStack {
Text(label) Text(label)
Spacer()
if secure { if secure {
SecureField("", text: $text) SecureField("", text: $text, prompt: Text(prompt))
.multilineTextAlignment(.trailing)
} else { } else {
TextField("", text: $text) TextField("", text: $text, prompt: Text(prompt))
.multilineTextAlignment(.trailing)
} }
} }
} }
} }
#Preview { #Preview {
TextBox(label: "Label", text: .constant("asdflkajsdl")) TextBox(label: "Label", text: .constant("asdflkajsdl"), prompt: "")
} }

View File

@@ -47,13 +47,6 @@ struct ShellView: View {
.navigationTitle(handler.title) .navigationTitle(handler.title)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) {
Button() {
dismiss()
} label: {
Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
}
}
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
Button() { Button() {
handler.disconnect() handler.disconnect()
@@ -62,6 +55,13 @@ struct ShellView: View {
Label("Disconnect", systemImage: "xmark.app.fill") Label("Disconnect", systemImage: "xmark.app.fill")
} }
} }
ToolbarItem(placement: .cancellationAction) {
Button() {
dismiss()
} label: {
Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
}
}
} }
} }
} }