From 9299bf343ffc29b5ca75efe30c788b9042d7123f Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Mon, 1 Sep 2025 14:52:17 -0700 Subject: [PATCH] WIP --- Sources/Packages/Localizable.xcstrings | 41 +++- Sources/Secretive.xcodeproj/project.pbxproj | 65 +++++- Sources/Secretive/App.swift | 15 +- .../Views/ConfigurationItemView.swift | 13 +- .../Secretive/Views/ConfigurationView.swift | 82 -------- Sources/Secretive/Views/ContentView.swift | 4 +- .../Secretive/Views/CreateSecretView.swift | 8 +- .../Secretive/Views/IntegrationsView.swift | 192 ++++++++++++++++++ .../Secretive/Views/SecretDetailView.swift | 6 - Sources/Secretive/Views/SetupView.swift | 32 +-- 10 files changed, 321 insertions(+), 137 deletions(-) delete mode 100644 Sources/Secretive/Views/ConfigurationView.swift create mode 100644 Sources/Secretive/Views/IntegrationsView.swift diff --git a/Sources/Packages/Localizable.xcstrings b/Sources/Packages/Localizable.xcstrings index 4e0650f..b7cb78c 100644 --- a/Sources/Packages/Localizable.xcstrings +++ b/Sources/Packages/Localizable.xcstrings @@ -787,6 +787,9 @@ } } } + }, + "Apps" : { + }, "auth_context_persist_for_duration" : { "comment" : "When the user clicks the notification to leave a secret unlocked, they are shown a prompt to approve the action. This is the description, showing which secret will used. The first placeholder is the name of the secret. The second placeholder is a localized description of the time period it will remain unlocked for (eg: \"five minutes\")", @@ -1184,7 +1187,7 @@ "Configure" : { }, - "Configuring" : { + "Copy" : { }, "copyable_click_to_copy_button" : { @@ -3039,6 +3042,15 @@ } } } + }, + "Getting Started" : { + + }, + "Integrations" : { + + }, + "Integrations..." : { + }, "no_secure_storage_description" : { "extractionState" : "manual", @@ -3270,6 +3282,12 @@ } } } + }, + "other" : { + + }, + "Other" : { + }, "persist_authentication_accept_button" : { "comment" : "When the user authorizes an action using a secret that requires unlock, they're shown a notification offering to leave the secret unlocked for a set period of time. This is the title for the notification.", @@ -5009,6 +5027,9 @@ } } } + }, + "Shell" : { + }, "signed_notification_description" : { "comment" : "When the user performs an action using a secret, they're shown a notification describing what happened. This is the description, showing which secret was used. The placeholder is the name of the secret.", @@ -5245,6 +5266,18 @@ }, "Starting Agent" : { + }, + "System" : { + + }, + "TBD" : { + + }, + "There's a community-maintained list of instructions for apps on GitHub. If the app you're looking for isn't supported, create an issue and the community may be able to help." : { + + }, + "There's a community-maintained list of shell instructions on GitHub. If the shell you're looking for isn't supported, create an issue and the community may be able to help." : { + }, "unnamed_secret" : { "extractionState" : "manual", @@ -6194,6 +6227,12 @@ }, "Version" : { + }, + "View Documentation on Web" : { + + }, + "View on GitHub" : { + } }, "version" : "1.0" diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 65828b2..029fe62 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -48,7 +48,7 @@ 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */; }; 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; }; 50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; }; - 50AE97002E5C1A420018C710 /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */; }; + 50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */; }; 50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; }; 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; }; 50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; }; @@ -140,7 +140,7 @@ 50A3B79624026B7600D209EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = ""; }; - 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = ""; }; + 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsView.swift; sourceTree = ""; }; 50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = ""; }; 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = ""; }; 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentStatusView.swift; sourceTree = ""; }; @@ -261,7 +261,7 @@ 50153E1F250AFCB200525160 /* UpdateView.swift */, 5066A6C12516F303004B5A36 /* SetupView.swift */, 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, - 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */, + 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */, 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */, @@ -457,7 +457,7 @@ 508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */, 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */, 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */, - 50AE97002E5C1A420018C710 /* ConfigurationView.swift in Sources */, + 50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */, 50153E20250AFCB200525160 /* UpdateView.swift in Sources */, 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */, 5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */, @@ -659,10 +659,18 @@ ENABLE_APP_SANDBOX = YES; ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readwrite; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = Secretive/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -691,10 +699,18 @@ ENABLE_APP_SANDBOX = YES; ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readwrite; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = Secretive/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -795,10 +811,18 @@ ENABLE_APP_SANDBOX = YES; ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = NO; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readwrite; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = Secretive/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -821,8 +845,17 @@ DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\""; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_PREVIEWS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = SecretAgent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -847,8 +880,17 @@ DEVELOPMENT_TEAM = Z72PRUAWF6; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_PREVIEWS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = SecretAgent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -874,8 +916,17 @@ DEVELOPMENT_TEAM = Z72PRUAWF6; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_PREVIEWS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = SecretAgent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 2e5653f..bbd25c4 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -37,6 +37,7 @@ struct Secretive: App { @Environment(\.agentStatusChecker) var agentStatusChecker @AppStorage("defaultsHasRunSetup") var hasRunSetup = false @State private var showingSetup = false + @State private var showingIntegrations = false @State private var showingCreation = false @SceneBuilder var body: some Scene { @@ -58,8 +59,17 @@ struct Secretive: App { forceLaunchAgent() } } + .sheet(isPresented: $showingIntegrations) { + IntegrationsView() + .frame(minHeight: 400) + } } .commands { + CommandGroup(before: CommandGroupPlacement.appSettings) { + Button("Integrations...", systemImage: "app.connected.to.app.below.fill") { + showingIntegrations = true + } + } CommandGroup(after: CommandGroupPlacement.newItem) { Button(.appMenuNewSecretButton) { showingCreation = true @@ -71,11 +81,6 @@ struct Secretive: App { NSWorkspace.shared.open(Constants.helpURL) } } - CommandGroup(before: .help) { - Button(.appMenuSetupButton) { - showingSetup = true - } - } SidebarCommands() } } diff --git a/Sources/Secretive/Views/ConfigurationItemView.swift b/Sources/Secretive/Views/ConfigurationItemView.swift index 7f320e5..7544454 100644 --- a/Sources/Secretive/Views/ConfigurationItemView.swift +++ b/Sources/Secretive/Views/ConfigurationItemView.swift @@ -32,14 +32,19 @@ struct ConfigurationItemView: View { Spacer() switch action { case .copy(let string): - Button("Reveal in Finder", systemImage: "folder") { - NSWorkspace.shared.selectFile(string, inFileViewerRootedAtPath: string) + Button("Copy", systemImage: "document.on.document") { + NSPasteboard.general.declareTypes([.string], owner: nil) + NSPasteboard.general.setString(string, forType: .string) } .labelStyle(.iconOnly) .buttonStyle(.borderless) - case .revealInFinder(let string): + case .revealInFinder(let rawPath): Button("Reveal in Finder", systemImage: "folder") { - NSWorkspace.shared.selectFile(string, inFileViewerRootedAtPath: string) + // All foundation-based normalization methods replace this with the container directly. + let processedPath = rawPath.replacingOccurrences(of: "~", with: "/Users/\(NSUserName())") + let url = URL(filePath: processedPath) + let folder = url.deletingLastPathComponent().path() + NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder) } .labelStyle(.iconOnly) .buttonStyle(.borderless) diff --git a/Sources/Secretive/Views/ConfigurationView.swift b/Sources/Secretive/Views/ConfigurationView.swift deleted file mode 100644 index 3db61e9..0000000 --- a/Sources/Secretive/Views/ConfigurationView.swift +++ /dev/null @@ -1,82 +0,0 @@ -import SwiftUI - -struct ConfigurationView: View { - - @Binding var visible: Bool - - - let buttonAction: () -> Void - - @State private var selectedShellInstruction: ShellConfigInstruction? - - private let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String - - private var shellInstructions: [ShellConfigInstruction] { - [ - ShellConfigInstruction(shell: "SSH", - shellConfigDirectory: "~/.ssh/", - shellConfigFilename: "config", - text: "Host *\n\tIdentityAgent \(socketPath)"), - ShellConfigInstruction(shell: "zsh", - shellConfigDirectory: "~/", - shellConfigFilename: ".zshrc", - text: "export SSH_AUTH_SOCK=\(socketPath)"), - ShellConfigInstruction(shell: "bash", - shellConfigDirectory: "~/", - shellConfigFilename: ".bashrc", - text: "export SSH_AUTH_SOCK=\(socketPath)"), - ShellConfigInstruction(shell: "fish", - shellConfigDirectory: "~/.config/fish", - shellConfigFilename: "config.fish", - text: "set -x SSH_AUTH_SOCK \(socketPath)"), - ] - - } - - var body: some View { - Form { - Section { - Picker("Configuring", selection: $selectedShellInstruction) { - ForEach(shellInstructions) { instruction in - Text(instruction.shell) - .tag(instruction) - .padding() - } - } - if let selectedShellInstruction { - ConfigurationItemView(title: "Configuration File", value: selectedShellInstruction.shellConfigPath, action: .revealInFinder(selectedShellInstruction.shellConfigPath)) - ConfigurationItemView(title: "Add This:", action: .copy(selectedShellInstruction.text)) { - HStack { - Text(selectedShellInstruction.text) - .padding(8) - .font(.system(.subheadline, design: .monospaced)) - Spacer() - } - .frame(maxWidth: .infinity) - .background { - RoundedRectangle(cornerRadius: 6) - .fill(.black.opacity(0.05)) - .stroke(.separator, lineWidth: 1) - } - - } - Button("setup_ssh_add_for_me_button") { - } - } - } footer: { - Link("setup_third_party_faq_link", destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!) - } - } - .formStyle(.grouped) - .onAppear { - selectedShellInstruction = shellInstructions.first - } -// } - } - -} - -#Preview { - ConfigurationView(visible: .constant(true)) {} - .frame(width: 400, height: 300) -} diff --git a/Sources/Secretive/Views/ContentView.swift b/Sources/Secretive/Views/ContentView.swift index 2e20ad5..8ef73a7 100644 --- a/Sources/Secretive/Views/ContentView.swift +++ b/Sources/Secretive/Views/ContentView.swift @@ -36,7 +36,7 @@ struct ContentView: View { toolbarItem(newItemView, id: "new") } .sheet(isPresented: $runningSetup) { - SetupView(visible: $runningSetup, setupComplete: $hasRunSetup) + SetupView(setupComplete: $hasRunSetup) } } @@ -109,7 +109,7 @@ extension ContentView { .normal() .sheet(isPresented: $showingCreation) { if let modifiable = storeList.modifiableStore { - CreateSecretView(store: modifiable, showing: $showingCreation) { created in + CreateSecretView(store: modifiable) { created in if let created { activeSecret = created } diff --git a/Sources/Secretive/Views/CreateSecretView.swift b/Sources/Secretive/Views/CreateSecretView.swift index 9a5557d..f3e8b03 100644 --- a/Sources/Secretive/Views/CreateSecretView.swift +++ b/Sources/Secretive/Views/CreateSecretView.swift @@ -4,7 +4,7 @@ import SecretKit struct CreateSecretView: View { @State var store: StoreType - @Binding var showing: Bool + @Environment(\.dismiss) private var dismiss var createdSecret: (AnySecret?) -> Void @State private var name = "" @@ -109,7 +109,7 @@ struct CreateSecretView: View { .toggleStyle(.button) Spacer() Button(.createSecretCancelButton, role: .cancel) { - showing = false + dismiss() } Button(.createSecretCreateButton, action: save) .keyboardShortcut(.return) @@ -137,7 +137,7 @@ struct CreateSecretView: View { ) ) createdSecret(AnySecret(new)) - showing = false + dismiss() } catch { errorText = error.localizedDescription } @@ -147,5 +147,5 @@ struct CreateSecretView: View { } #Preview { - CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true)) { _ in } + CreateSecretView(store: Preview.StoreModifiable()) { _ in } } diff --git a/Sources/Secretive/Views/IntegrationsView.swift b/Sources/Secretive/Views/IntegrationsView.swift new file mode 100644 index 0000000..7e31674 --- /dev/null +++ b/Sources/Secretive/Views/IntegrationsView.swift @@ -0,0 +1,192 @@ +import SwiftUI + +struct IntegrationsView: View { + + @Environment(\.dismiss) private var dismiss + + @State private var selectedInstruction: ConfigurationFileInstructions? + + private let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String + + private var instructions: [ConfigurationGroup] { + [ + ConfigurationGroup(name:"Integrations", instructions: [ + ConfigurationFileInstructions("Getting Started", id: .gettingStarted), + ]), + ConfigurationGroup(name: "System", instructions: [ + ConfigurationFileInstructions( + tool: "ssh", + configPath: "~/.ssh/config", + configText: "Host *\n\tIdentityAgent \(socketPath)", + website: URL(string: "https://man.openbsd.org/ssh_config.5")!, + ), + ConfigurationFileInstructions( + tool: "git", + configPath: "~/.gitconfig", + configText: "PLACEHOLDER", + website: URL(string: "https://git-scm.com/docs/git-config")! + ) + ]), + ConfigurationGroup(name: "Shell", instructions: [ + ConfigurationFileInstructions( + tool: "zsh", + configPath: "~/.zshrc", + configText: "export SSH_AUTH_SOCK=\(socketPath)" + ), + ConfigurationFileInstructions( + tool: "bash", + configPath: "~/.bashrc", + configText: "export SSH_AUTH_SOCK=\(socketPath)" + ), + ConfigurationFileInstructions( + tool: "fish", + configPath: "~/.config/fish/config.fish", + configText: "set -x SSH_AUTH_SOCK \(socketPath)" + ), + ConfigurationFileInstructions("other", id: .otherShell), + ]), + ConfigurationGroup(name:"Apps", instructions: [ + ConfigurationFileInstructions("Other", id: .otherApp), + ]), + ] + } + + var body: some View { + NavigationSplitView { + List(selection: $selectedInstruction) { + ForEach(instructions) { group in + Section(group.name) { + ForEach(group.instructions) { instruction in + Text(instruction.tool) + .padding(.vertical, 8) + .tag(instruction) + } + } + } + } + } detail: { + if let selectedInstruction { + Form { + switch selectedInstruction.id { + case .gettingStarted: + Text("TBD") + case .tool: + Section(selectedInstruction.tool) { + ConfigurationItemView(title: "Configuration File", value: selectedInstruction.configPath, action: .revealInFinder( selectedInstruction.configPath)) + ConfigurationItemView(title: "Add This:", action: .copy(selectedInstruction.configText)) { + HStack { + Text(selectedInstruction.configText) + .padding(8) + .font(.system(.subheadline, design: .monospaced)) + Spacer() + } + .frame(maxWidth: .infinity) + .background { + RoundedRectangle(cornerRadius: 6) + .fill(.black.opacity(0.05)) + .stroke(.separator, lineWidth: 1) + } + } + } + if let url = selectedInstruction.website { + Section { + Link(destination: url) { + VStack(alignment: .leading, spacing: 5) { + Text("View Documentation on Web") + .font(.headline) + Text(url.absoluteString) + .font(.caption2) + } + } + } + } + case .otherShell: + Section { + Link("View on GitHub", destination: URL(string: "https://github.com/maxgoedjen/secretive-config-instructions/tree/main/shells")!) + } header: { + Text("There's a community-maintained list of shell instructions on GitHub. If the shell you're looking for isn't supported, create an issue and the community may be able to help.") + .font(.body) + } + case .otherApp: + Section { + Link("View on GitHub", destination: URL(string: "https://github.com/maxgoedjen/secretive-config-instructions/tree/main/apps")!) + } header: { + Text("There's a community-maintained list of instructions for apps on GitHub. If the app you're looking for isn't supported, create an issue and the community may be able to help.") + .font(.body) + } + } + } + .formStyle(.grouped) + } + } + .onAppear { + selectedInstruction = instructions.first?.instructions.first + } + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("Done") { + dismiss() + } + .styled + } + } + } + +} + +struct ConfigurationGroup: Identifiable { + let id = UUID() + var name: LocalizedStringResource + var instructions: [ConfigurationFileInstructions] = [] +} + +struct ConfigurationFileInstructions: Identifiable, Hashable { + + var id: ID + var tool: String + var configPath: String + var configText: String + var website: URL? + + init(tool: String, configPath: String, configText: String, website: URL? = nil) { + self.id = .tool(tool) + self.tool = tool + self.configPath = configPath + self.configText = configText + self.website = website + } + + init(_ name: LocalizedStringResource, id: ID) { + self.id = id + tool = String(localized: name) + configPath = "" + configText = "" + } + + enum ID: Identifiable, Hashable { + case gettingStarted + case tool(String) + case otherShell + case otherApp + + var id: String { + switch self { + case .gettingStarted: + "getting_started" + case .tool(let name): + name + case .otherShell: + "other_shell" + case .otherApp: + "other_app" + } + } + } + +} + + +#Preview { + IntegrationsView() + .frame(height: 500) +} diff --git a/Sources/Secretive/Views/SecretDetailView.swift b/Sources/Secretive/Views/SecretDetailView.swift index c727bc2..1e616f1 100644 --- a/Sources/Secretive/Views/SecretDetailView.swift +++ b/Sources/Secretive/Views/SecretDetailView.swift @@ -23,12 +23,6 @@ struct SecretDetailView: View { .frame(height: 20) CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret)) Spacer() - } header: { - Text(verbatim: secret.name) - .font(.system(size: 16, weight: .bold, design: .default)) - .foregroundStyle(.secondary) - .padding(.leading) - .padding(.bottom) } } .padding() diff --git a/Sources/Secretive/Views/SetupView.swift b/Sources/Secretive/Views/SetupView.swift index 81d4ad2..6aefefc 100644 --- a/Sources/Secretive/Views/SetupView.swift +++ b/Sources/Secretive/Views/SetupView.swift @@ -2,7 +2,7 @@ import SwiftUI struct SetupView: View { - @Binding var visible: Bool + @Environment(\.dismiss) private var dismiss @Binding var setupComplete: Bool @State var installed = false @@ -12,7 +12,7 @@ struct SetupView: View { var body: some View { VStack { VStack(spacing: 0) { - NewStepView( + StepView( title: "setup_agent_title", description: "setup_agent_description", systemImage: "lock.laptopcomputer", @@ -24,7 +24,7 @@ struct SetupView: View { } } Divider() - NewStepView( + StepView( title: "setup_updates_title", description: "setup_updates_description", systemImage: "network.badge.shield.half.filled", @@ -36,7 +36,7 @@ struct SetupView: View { } } Divider() - NewStepView( + StepView( title: "setup_ssh_title", description: "setup_ssh_description", systemImage: "network.badge.shield.half.filled", @@ -101,7 +101,7 @@ extension View { } -struct NewStepView: View { +struct StepView: View { let title: LocalizedStringResource let icon: Image @@ -141,27 +141,7 @@ extension SetupView { } -struct ShellConfigInstruction: Identifiable, Hashable { - - var shell: String - var shellConfigDirectory: String - var shellConfigFilename: String - var text: String - - var id: String { - shell - } - - var shellConfigPath: String { - return (shellConfigDirectory as NSString).appendingPathComponent(shellConfigFilename) - } - -} #Preview { - SetupView(visible: .constant(true), setupComplete: .constant(false)) + SetupView(setupComplete: .constant(false)) } - -//#Preview { -// SSHAgentSetupView(buttonAction: {}) -//}