mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
rewrote authWithPubkey() to not write files and take data direcytly remove read loop (comment adding it) readfromchannel returns string using swifterm to do terminal emulation
201 lines
5.3 KiB
Swift
201 lines
5.3 KiB
Swift
//
|
|
// ConnectionView.swift
|
|
// ShhShell
|
|
//
|
|
// Created by neon443 on 20/06/2025.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct ConnectionView: View {
|
|
@StateObject var handler: SSHHandler
|
|
@StateObject var keyManager: KeyManager
|
|
@StateObject var hostsManager: HostsManager
|
|
|
|
@State var passphrase: String = ""
|
|
|
|
@State var pubkeyStr: String = ""
|
|
@State var privkeyStr: String = ""
|
|
|
|
@State var pubkey: Data?
|
|
@State var privkey: Data?
|
|
|
|
@State var privPickerPresented: Bool = false
|
|
@State var pubPickerPresented: Bool = false
|
|
|
|
@State var hostKeyChangedAlert: Bool = false
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
List {
|
|
Section {
|
|
HStack {
|
|
Text(handler.connected ? "connected" : "not connected")
|
|
.modifier(foregroundColorStyle(handler.connected ? .green : .red))
|
|
|
|
Text(handler.authorized ? "authorized" : "unauthorized")
|
|
.modifier(foregroundColorStyle(handler.authorized ? .green : .red))
|
|
}
|
|
TextField("address", text: $handler.host.address)
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
TextField(
|
|
"port",
|
|
text: Binding(
|
|
get: { String(handler.host.port) },
|
|
set: {
|
|
if let input = Int($0) {
|
|
handler.host.port = input
|
|
}
|
|
}
|
|
)
|
|
)
|
|
.keyboardType(.numberPad)
|
|
.textFieldStyle(.roundedBorder)
|
|
}
|
|
|
|
Section {
|
|
TextField("Username", text: $handler.host.username)
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
SecureField("Password", text: $handler.host.password)
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
HStack {
|
|
TextField("", text: $pubkeyStr, prompt: Text("Public Key"))
|
|
.onSubmit {
|
|
pubkey = Data(pubkeyStr.utf8)
|
|
}
|
|
Button() {
|
|
pubPickerPresented.toggle()
|
|
} label: {
|
|
Image(systemName: "folder")
|
|
}
|
|
.buttonStyle(.plain)
|
|
.fileImporter(isPresented: $pubPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
|
|
do {
|
|
let fileURL = try Result.get()
|
|
pubkey = try? Data(contentsOf: fileURL)
|
|
print(fileURL)
|
|
} catch {
|
|
print(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
HStack {
|
|
TextField("", text: $privkeyStr, prompt: Text("Private Key"))
|
|
.onSubmit {
|
|
privkey = Data(privkeyStr.utf8)
|
|
}
|
|
Button() {
|
|
privPickerPresented.toggle()
|
|
} label: {
|
|
Image(systemName: "folder")
|
|
}
|
|
.buttonStyle(.plain)
|
|
.fileImporter(isPresented: $privPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
|
|
do {
|
|
let fileURL = try Result.get()
|
|
privkey = try? Data(contentsOf: fileURL)
|
|
print(privkey)
|
|
print(fileURL)
|
|
} catch {
|
|
print(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
TextField("", text: $passphrase, prompt: Text("Passphrase (Optional)"))
|
|
}
|
|
|
|
if handler.host.key != nil {
|
|
Text("Hostkey: \(handler.host.key!.base64EncodedString())")
|
|
.onChange(of: handler.host.key) { _ in
|
|
guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return }
|
|
guard handler.host.key == previousKnownHost.key else {
|
|
hostKeyChangedAlert = true
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
NavigationLink() {
|
|
Button("Reload") {
|
|
handler.readFromChannel()
|
|
}
|
|
TerminalController(handler: handler)
|
|
} label: {
|
|
Label("Open Terminal", systemImage: "apple.terminal")
|
|
}
|
|
.disabled(!(handler.connected && handler.authorized))
|
|
|
|
Button() {
|
|
withAnimation { handler.testExec() }
|
|
} label: {
|
|
if let testResult = handler.testSuceeded {
|
|
Image(systemName: testResult ? "checkmark.circle" : "xmark.circle")
|
|
.modifier(foregroundColorStyle(testResult ? .green : .red))
|
|
} else {
|
|
Label("Test Connection", systemImage: "checkmark")
|
|
}
|
|
}
|
|
}
|
|
.alert("Hostkey changed", isPresented: $hostKeyChangedAlert) {
|
|
Button("Accept New Hostkey", role: .destructive) {
|
|
hostsManager.updateHost(handler.host)
|
|
}
|
|
|
|
Button("Disconnect", role: .cancel) {
|
|
handler.disconnect()
|
|
handler.host.key = hostsManager.getHostMatching(handler.host)?.key
|
|
}
|
|
} message: {
|
|
Text("Expected \(hostsManager.getHostMatching(handler.host)!.key?.base64EncodedString() ?? "null")\nbut recieved \(handler.host.key?.base64EncodedString() ?? "null" ) from the server")
|
|
}
|
|
.transition(.opacity)
|
|
.toolbar {
|
|
ToolbarItem() {
|
|
if handler.connected {
|
|
Button() {
|
|
handler.disconnect()
|
|
} label: {
|
|
Label("Disconnect", systemImage: "xmark.app.fill")
|
|
}
|
|
} else {
|
|
Button() {
|
|
handler.connect()
|
|
if pubkey != nil && privkey != nil {
|
|
handler.authWithPubkey(pub: pubkey!, priv: privkey!, pass: passphrase)
|
|
} else {
|
|
let _ = handler.authWithPw()
|
|
}
|
|
handler.openShell()
|
|
} label: {
|
|
Label("Connect", systemImage: "powerplug.portrait")
|
|
}
|
|
.disabled(
|
|
pubkey == nil && privkey == nil &&
|
|
handler.host.username.isEmpty && handler.host.password.isEmpty
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onDisappear {
|
|
guard hostsManager.getHostMatching(handler.host) == handler.host else {
|
|
hostsManager.updateHost(handler.host)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#Preview {
|
|
ConnectionView(
|
|
handler: SSHHandler(host: Host.debug),
|
|
keyManager: KeyManager(),
|
|
hostsManager: HostsManager()
|
|
)
|
|
}
|