mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
fix adding new hosts, it will navigate u to a new connection view with a host.blank, and add the host if it doesnt exist
added addhostifneeded extracted hostskeys stuff to hostkeysview added nav titles to everything removed tabs, now just a list renamed savedHosts -> hosts
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
|
||||
A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */; };
|
||||
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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -118,6 +119,7 @@
|
||||
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -282,6 +284,7 @@
|
||||
children = (
|
||||
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */,
|
||||
A98554542E05535F009051BD /* KeyManagerView.swift */,
|
||||
A9D819302E102D8700442D38 /* HostkeysView.swift */,
|
||||
);
|
||||
path = Keys;
|
||||
sourceTree = "<group>";
|
||||
@@ -497,6 +500,7 @@
|
||||
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */,
|
||||
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */,
|
||||
|
||||
@@ -12,11 +12,11 @@ import SwiftUI
|
||||
class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
private let userDefaults = NSUbiquitousKeyValueStore.default
|
||||
|
||||
@Published var savedHosts: [Host] = []
|
||||
@Published var hosts: [Host] = []
|
||||
@Published var themes: [Theme] = []
|
||||
|
||||
init() {
|
||||
loadSavedHosts()
|
||||
loadHosts()
|
||||
loadThemes()
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
}
|
||||
|
||||
func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? {
|
||||
if let index = savedHosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
|
||||
if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
|
||||
return index
|
||||
} else {
|
||||
return nil
|
||||
@@ -93,28 +93,28 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
|
||||
func getHostMatching(_ HostSearchingFor: Host) -> Host? {
|
||||
guard let index = getHostIndexMatching(HostSearchingFor) else { return nil }
|
||||
return savedHosts[index]
|
||||
return hosts[index]
|
||||
}
|
||||
|
||||
func updateHost(_ updatedHost: Host) {
|
||||
let oldID = updatedHost.id
|
||||
|
||||
if let index = savedHosts.firstIndex(where: { $0.id == updatedHost.id }) {
|
||||
if let index = hosts.firstIndex(where: { $0.id == updatedHost.id }) {
|
||||
var updateHostWithNewID = updatedHost
|
||||
updateHostWithNewID.id = UUID()
|
||||
withAnimation { savedHosts[index] = updateHostWithNewID }
|
||||
withAnimation { hosts[index] = updateHostWithNewID }
|
||||
|
||||
updateHostWithNewID.id = oldID
|
||||
withAnimation { savedHosts[index] = updateHostWithNewID }
|
||||
saveSavedHosts()
|
||||
withAnimation { hosts[index] = updateHostWithNewID }
|
||||
saveHosts()
|
||||
}
|
||||
}
|
||||
|
||||
func duplicateHost(_ hostToDup: Host) {
|
||||
var hostNewID = hostToDup
|
||||
hostNewID.id = UUID()
|
||||
if let index = savedHosts.firstIndex(where: { $0 == hostToDup }) {
|
||||
savedHosts.insert(hostNewID, at: index+1)
|
||||
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
|
||||
hosts.insert(hostNewID, at: index+1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,38 +131,44 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
}
|
||||
|
||||
func moveHost(from: IndexSet, to: Int) {
|
||||
savedHosts.move(fromOffsets: from, toOffset: to)
|
||||
saveSavedHosts()
|
||||
hosts.move(fromOffsets: from, toOffset: to)
|
||||
saveHosts()
|
||||
}
|
||||
|
||||
func loadSavedHosts() {
|
||||
func loadHosts() {
|
||||
userDefaults.synchronize()
|
||||
let decoder = JSONDecoder()
|
||||
guard let data = userDefaults.data(forKey: "savedHosts") else { return }
|
||||
|
||||
if let decoded = try? decoder.decode([Host].self, from: data) {
|
||||
self.savedHosts = decoded
|
||||
self.hosts = decoded
|
||||
}
|
||||
}
|
||||
|
||||
func saveSavedHosts() {
|
||||
func saveHosts() {
|
||||
let encoder = JSONEncoder()
|
||||
if let encoded = try? encoder.encode(savedHosts) {
|
||||
if let encoded = try? encoder.encode(hosts) {
|
||||
userDefaults.set(encoded, forKey: "savedHosts")
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
func addHostIfNeeded(_ hostToAdd: Host) {
|
||||
if !hosts.contains(hostToAdd) {
|
||||
hosts.append(hostToAdd)
|
||||
}
|
||||
}
|
||||
|
||||
func removeHost(_ host: Host) {
|
||||
if let index = savedHosts.firstIndex(where: { $0.id == host.id }) {
|
||||
let _ = withAnimation { savedHosts.remove(at: index) }
|
||||
saveSavedHosts()
|
||||
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
|
||||
let _ = withAnimation { hosts.remove(at: index) }
|
||||
saveHosts()
|
||||
}
|
||||
}
|
||||
|
||||
func getKeys() -> [Keypair] {
|
||||
var result: [Keypair] = []
|
||||
for host in savedHosts {
|
||||
for host in hosts {
|
||||
let keypair = Keypair(publicKey: host.publicKey, privateKey: host.privateKey)
|
||||
if !result.contains(keypair) {
|
||||
result.append(keypair)
|
||||
@@ -174,7 +180,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
func getHostsKeysUsedOn(_ keys: [Keypair]) -> [Host] {
|
||||
var result: [Host] = []
|
||||
for key in keys {
|
||||
let hosts = savedHosts.filter({
|
||||
let hosts = hosts.filter({
|
||||
$0.publicKey == key.publicKey &&
|
||||
$0.privateKey == key.privateKey
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ShhShellApp: App {
|
||||
WindowGroup {
|
||||
ContentView(
|
||||
handler: sshHandler,
|
||||
hostsManger: hostsManager,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
.colorScheme(.dark)
|
||||
|
||||
@@ -9,22 +9,29 @@ import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@ObservedObject var handler: SSHHandler
|
||||
@ObservedObject var hostsManger: HostsManager
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
NavigationStack {
|
||||
List {
|
||||
HostsView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManger,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
.tabItem {
|
||||
Label("Hosts", systemImage: "server.rack")
|
||||
|
||||
NavigationLink {
|
||||
KeyManagerView(hostsManager: hostsManager, keyManager: keyManager)
|
||||
} label: {
|
||||
Label("Keys", systemImage: "key.fill")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
HostkeysView(hostsManager: hostsManager)
|
||||
} label: {
|
||||
Label("Hostkey Fingerprints", systemImage: "lock.display")
|
||||
}
|
||||
KeyManagerView(hostsManager: hostsManger, keyManager: keyManager)
|
||||
.tabItem {
|
||||
Label("Keys", systemImage: "key.2.on.ring")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +40,7 @@ struct ContentView: View {
|
||||
#Preview {
|
||||
ContentView(
|
||||
handler: SSHHandler(host: Host.debug),
|
||||
hostsManger: HostsManager(),
|
||||
hostsManager: HostsManager(),
|
||||
keyManager: KeyManager()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -188,6 +188,9 @@ struct ConnectionView: View {
|
||||
shellView = ShellView(handler: handler)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
hostsManager.addHostIfNeeded(handler.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,21 +13,13 @@ struct HostsView: View {
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
if hostsManager.savedHosts.isEmpty {
|
||||
if hostsManager.hosts.isEmpty {
|
||||
Text("Add your first Host!")
|
||||
Button() {
|
||||
withAnimation { hostsManager.savedHosts.append(Host.blank) }
|
||||
} label: {
|
||||
Text("Create")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
|
||||
//proves that u can connect to multiple at the same time
|
||||
NavigationLink() {
|
||||
ForEach(hostsManager.savedHosts) { host in
|
||||
ForEach(hostsManager.hosts) { host in
|
||||
let miniHandler = SSHHandler(host: host)
|
||||
TerminalController(handler: miniHandler)
|
||||
.onAppear { miniHandler.go() }
|
||||
@@ -36,7 +28,7 @@ struct HostsView: View {
|
||||
Label("multiview", systemImage: "square.split.2x2")
|
||||
}
|
||||
|
||||
ForEach(hostsManager.savedHosts) { host in
|
||||
ForEach(hostsManager.hosts) { host in
|
||||
NavigationLink() {
|
||||
ConnectionView(
|
||||
handler: SSHHandler(host: host),
|
||||
@@ -74,20 +66,16 @@ struct HostsView: View {
|
||||
Label("Themes", systemImage: "swatchpalette")
|
||||
}
|
||||
}
|
||||
}
|
||||
.transition(.opacity)
|
||||
.navigationTitle("ShhShell")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
let host = Host.blank
|
||||
NavigationLink {
|
||||
ConnectionView(
|
||||
handler: SSHHandler(host: host),
|
||||
handler: SSHHandler(host: Host.blank),
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
.onAppear {
|
||||
withAnimation { hostsManager.savedHosts.append(host) }
|
||||
}
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus")
|
||||
}
|
||||
@@ -95,7 +83,6 @@ struct HostsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HostsView(
|
||||
|
||||
58
ShhShell/Views/Keys/HostkeysView.swift
Normal file
58
ShhShell/Views/Keys/HostkeysView.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// HostkeysView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 28/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HostkeysView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
if hostsManager.hosts.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Looking empty 'round here...")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Connect to some hosts to collect more hostkeys!")
|
||||
.padding(.bottom)
|
||||
Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.")
|
||||
.font(.subheadline)
|
||||
Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(hostsManager.hosts) { host in
|
||||
VStack(alignment: .leading) {
|
||||
if !host.name.isEmpty {
|
||||
Text("name")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.name)
|
||||
.bold()
|
||||
}
|
||||
Text("address")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Hostkeys")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HostkeysView(hostsManager: HostsManager())
|
||||
}
|
||||
@@ -25,45 +25,7 @@ struct KeyManagerView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
Section {
|
||||
NavigationLink {
|
||||
List {
|
||||
if hostsManager.savedHosts.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Looking empty 'round here...")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Connect to some hosts to collect more hostkeys!")
|
||||
.padding(.bottom)
|
||||
Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.")
|
||||
.font(.subheadline)
|
||||
Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(hostsManager.savedHosts) { host in
|
||||
VStack(alignment: .leading) {
|
||||
if !host.name.isEmpty {
|
||||
Text(host.name)
|
||||
.bold()
|
||||
}
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "server.rack")
|
||||
Image(systemName: "key.fill")
|
||||
Text("Hostkey fingerprints")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button("ed25519") {
|
||||
keyManager.generateEd25519()
|
||||
}
|
||||
@@ -75,6 +37,7 @@ struct KeyManagerView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Keys")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ struct ThemeManagerView: View {
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.scrollIndicators(.hidden)
|
||||
.navigationTitle("Themes")
|
||||
.alert("Enter URL", isPresented: $showAlert) {
|
||||
TextField("", text: $importURL, prompt: Text("URL"))
|
||||
Button() {
|
||||
|
||||
Reference in New Issue
Block a user