mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
ui background matches theme
ui bg is 70% opacity
This commit is contained in:
@@ -14,30 +14,35 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
SessionsListView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
|
||||
HostsView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
|
||||
NavigationLink {
|
||||
KeyManagerView(hostsManager: hostsManager, keyManager: keyManager)
|
||||
} label: {
|
||||
Label("Keys", systemImage: "key.fill")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
HostkeysView(hostsManager: hostsManager)
|
||||
} label: {
|
||||
Label("Hostkey Fingerprints", systemImage: "lock.display")
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
List {
|
||||
SessionsListView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
|
||||
HostsView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
|
||||
NavigationLink {
|
||||
KeyManagerView(hostsManager: hostsManager, keyManager: keyManager)
|
||||
} label: {
|
||||
Label("Keys", systemImage: "key.fill")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
HostkeysView(hostsManager: hostsManager)
|
||||
} label: {
|
||||
Label("Hostkey Fingerprints", systemImage: "lock.display")
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ struct ConnectionView: View {
|
||||
@State var hostKeyChangedAlert: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
List {
|
||||
Section {
|
||||
ScrollView(.horizontal) {
|
||||
@@ -128,6 +130,34 @@ struct ConnectionView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.transition(.opacity)
|
||||
.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
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
hostsManager.updateHost(handler.host)
|
||||
}
|
||||
.task {
|
||||
if let publicKeyData = handler.host.publicKey {
|
||||
pubkeyStr = String(data: publicKeyData, encoding: .utf8) ?? ""
|
||||
}
|
||||
if let privateKeyData = handler.host.privateKey {
|
||||
privkeyStr = String(data: privateKeyData, encoding: .utf8) ?? ""
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if shellView == nil {
|
||||
shellView = ShellView(handler: handler, hostsManager: hostsManager)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
hostsManager.addHostIfNeeded(handler.host)
|
||||
}
|
||||
.alert("Hostkey changed", isPresented: $hostKeyChangedAlert) {
|
||||
Button("Accept New Hostkey", role: .destructive) {
|
||||
hostsManager.updateHost(handler.host)
|
||||
@@ -141,7 +171,6 @@ struct ConnectionView: View {
|
||||
} message: {
|
||||
Text("Expected \(handler.host.key ?? "nil")\nbut recieved \(handler.getHostkey() ?? "nil") from the server")
|
||||
}
|
||||
.transition(.opacity)
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
@@ -156,40 +185,14 @@ struct ConnectionView: View {
|
||||
.disabled(handler.hostInvalid())
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showTerminal) {
|
||||
if let shellView {
|
||||
shellView
|
||||
} else {
|
||||
Text("no shellview")
|
||||
.fullScreenCover(isPresented: $showTerminal) {
|
||||
if let shellView {
|
||||
shellView
|
||||
} else {
|
||||
Text("no shellview")
|
||||
}
|
||||
}
|
||||
}
|
||||
.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
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
hostsManager.updateHost(handler.host)
|
||||
}
|
||||
.task {
|
||||
if let publicKeyData = handler.host.publicKey {
|
||||
pubkeyStr = String(data: publicKeyData, encoding: .utf8) ?? ""
|
||||
}
|
||||
if let privateKeyData = handler.host.privateKey {
|
||||
privkeyStr = String(data: privateKeyData, encoding: .utf8) ?? ""
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if shellView == nil {
|
||||
shellView = ShellView(handler: handler, hostsManager: hostsManager)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
hostsManager.addHostIfNeeded(handler.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,47 +10,52 @@ 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)
|
||||
var body: some View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
List {
|
||||
if hostsManager.hosts.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Connect to some hosts to collect more hostkeys!")
|
||||
Text("Looking empty 'round here...")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.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)
|
||||
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")
|
||||
|
||||
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.name)
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
Text("address")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Hostkeys")
|
||||
}
|
||||
.navigationTitle("Hostkeys")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@@ -12,63 +12,68 @@ struct KeyDetailView: View {
|
||||
@State var keypair: Keypair
|
||||
@State private var reveal: Bool = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Used on")
|
||||
.bold()
|
||||
ForEach(hostsManager.getHostsKeysUsedOn([keypair])) { host in
|
||||
HStack {
|
||||
SymbolPreview(symbol: host.symbol, label: host.label)
|
||||
.frame(width: 40, height: 40)
|
||||
Text(hostsManager.makeLabel(forHost: host))
|
||||
}
|
||||
}
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Public key")
|
||||
.bold()
|
||||
Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil")
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Private key")
|
||||
.bold()
|
||||
.frame(maxWidth: .infinity)
|
||||
ZStack(alignment: .center) {
|
||||
Text(String(data: keypair.privateKey!, encoding: .utf8) ?? "nil")
|
||||
.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 hostsManager.authWithBiometrics() else { return }
|
||||
var body: some View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
List {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Used on")
|
||||
.bold()
|
||||
ForEach(hostsManager.getHostsKeysUsedOn([keypair])) { host in
|
||||
HStack {
|
||||
SymbolPreview(symbol: host.symbol, label: host.label)
|
||||
.frame(width: 40, height: 40)
|
||||
Text(hostsManager.makeLabel(forHost: host))
|
||||
}
|
||||
withAnimation(.spring) { reveal.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Task {
|
||||
guard await hostsManager.authWithBiometrics() else { return }
|
||||
if let privateKey = keypair.privateKey {
|
||||
UIPasteboard.general.string = String(data: privateKey, encoding: .utf8)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Public key")
|
||||
.bold()
|
||||
Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil")
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Private key")
|
||||
.bold()
|
||||
.frame(maxWidth: .infinity)
|
||||
ZStack(alignment: .center) {
|
||||
Text(String(data: keypair.privateKey!, encoding: .utf8) ?? "nil")
|
||||
.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 hostsManager.authWithBiometrics() else { return }
|
||||
}
|
||||
withAnimation(.spring) { reveal.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
CenteredLabel(title: "Copy private key", systemName: "document.on.document")
|
||||
|
||||
Button {
|
||||
Task {
|
||||
guard await hostsManager.authWithBiometrics() else { return }
|
||||
if let privateKey = keypair.privateKey {
|
||||
UIPasteboard.general.string = String(data: privateKey, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
CenteredLabel(title: "Copy private key", systemName: "document.on.document")
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@@ -12,32 +12,37 @@ struct KeyManagerView: View {
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
ForEach(hostsManager.getKeys()) { keypair in
|
||||
NavigationLink {
|
||||
KeyDetailView(hostsManager: hostsManager, keypair: keypair)
|
||||
} label: {
|
||||
if let publicKey = keypair.publicKey {
|
||||
Text(String(data: publicKey, encoding: .utf8) ?? "nil")
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
ForEach(hostsManager.getKeys()) { keypair in
|
||||
NavigationLink {
|
||||
KeyDetailView(hostsManager: hostsManager, keypair: keypair)
|
||||
} label: {
|
||||
if let publicKey = keypair.publicKey {
|
||||
Text(String(data: publicKey, encoding: .utf8) ?? "nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button("ed25519") {
|
||||
keyManager.generateEd25519()
|
||||
}
|
||||
Button("rsa") {
|
||||
do {
|
||||
try keyManager.generateRSA()
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
|
||||
Button("ed25519") {
|
||||
keyManager.generateEd25519()
|
||||
}
|
||||
Button("rsa") {
|
||||
do {
|
||||
try keyManager.generateRSA()
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Keys")
|
||||
}
|
||||
.navigationTitle("Keys")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,75 +25,79 @@ struct ThemeManagerView: View {
|
||||
)
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
let columns: Int = Int(geo.size.width)/200
|
||||
let layout = Array(repeating: grid, count: columns)
|
||||
ScrollView {
|
||||
if hostsManager.themes.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Image(systemName: "paintpalette")
|
||||
.resizable().scaledToFit()
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 50)
|
||||
Text("No themes (yet)")
|
||||
.font(.title)
|
||||
.padding(.vertical, 10)
|
||||
.bold()
|
||||
Text("Tap the Safari icon at the top right to find themes!")
|
||||
Text("Once you find one that you like, copy it's link and enter it here using the link button.")
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
GeometryReader { geo in
|
||||
let columns: Int = Int(geo.size.width)/200
|
||||
let layout = Array(repeating: grid, count: columns)
|
||||
ScrollView {
|
||||
if hostsManager.themes.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Image(systemName: "paintpalette")
|
||||
.resizable().scaledToFit()
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 50)
|
||||
Text("No themes (yet)")
|
||||
.font(.title)
|
||||
.padding(.vertical, 10)
|
||||
.bold()
|
||||
Text("Tap the Safari icon at the top right to find themes!")
|
||||
Text("Once you find one that you like, copy it's link and enter it here using the link button.")
|
||||
}
|
||||
} else {
|
||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||
ForEach(hostsManager.themes) { theme in
|
||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.animation(.default, value: hostsManager.themes)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("Built-in Themes")
|
||||
.padding(.top)
|
||||
.padding(.horizontal)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
}
|
||||
} else {
|
||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||
ForEach(hostsManager.themes) { theme in
|
||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true)
|
||||
ForEach(Theme.builtinThemes) { theme in
|
||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.animation(.default, value: hostsManager.themes)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("Built-in Themes")
|
||||
.padding(.top)
|
||||
.padding(.horizontal)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
}
|
||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||
ForEach(Theme.builtinThemes) { theme in
|
||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.animation(.default, value: hostsManager.themes)
|
||||
}
|
||||
.navigationTitle("Themes")
|
||||
.alert("Enter URL", isPresented: $showAlert) {
|
||||
TextField("", text: $importURL, prompt: Text("URL"))
|
||||
Button("Cancel") {}
|
||||
Button() {
|
||||
hostsManager.downloadTheme(fromUrl: URL(string: importURL))
|
||||
importURL = ""
|
||||
} label: {
|
||||
Label("Import", systemImage: "square.and.arrow.down")
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
.navigationTitle("Themes")
|
||||
.alert("Enter URL", isPresented: $showAlert) {
|
||||
TextField("", text: $importURL, prompt: Text("URL"))
|
||||
Button("Cancel") {}
|
||||
Button() {
|
||||
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
||||
hostsManager.downloadTheme(fromUrl: URL(string: importURL))
|
||||
importURL = ""
|
||||
} label: {
|
||||
Label("Open themes site", systemImage: "safari")
|
||||
Label("Import", systemImage: "square.and.arrow.down")
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
showAlert.toggle()
|
||||
} label: {
|
||||
Label("From URL", systemImage: "link")
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
||||
} label: {
|
||||
Label("Open themes site", systemImage: "safari")
|
||||
}
|
||||
}
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
showAlert.toggle()
|
||||
} label: {
|
||||
Label("From URL", systemImage: "link")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user