diff --git a/Sources/Packages/Localizable.xcstrings b/Sources/Packages/Localizable.xcstrings index a3e0581..4e0650f 100644 --- a/Sources/Packages/Localizable.xcstrings +++ b/Sources/Packages/Localizable.xcstrings @@ -1,7 +1,10 @@ { "sourceLanguage" : "en", "strings" : { - "Add Automatically" : { + "" : { + + }, + "Add This:" : { }, "agent_not_running_notice_detail_description" : { @@ -1174,11 +1177,14 @@ } } } + }, + "Configuration File" : { + }, "Configure" : { }, - "Copy" : { + "Configuring" : { }, "copyable_click_to_copy_button" : { @@ -3858,6 +3864,9 @@ } } } + }, + "Secretive was unable to get SecretAgent to launch. Please try restarting your Mac, and if that doesn't work, file an issue on GitHub." : { + }, "secure_enclave" : { "extractionState" : "manual", @@ -5233,6 +5242,9 @@ }, "Start Agent" : { + }, + "Starting Agent" : { + }, "unnamed_secret" : { "extractionState" : "manual", diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 8dd813b..65828b2 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; }; 50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; }; 50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */; }; + 50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; }; 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; 50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; }; /* End PBXBuildFile section */ @@ -144,6 +145,7 @@ 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = ""; }; 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentStatusView.swift; sourceTree = ""; }; 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorStyle.swift; sourceTree = ""; }; + 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = ""; }; 50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = ""; }; 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -262,6 +264,7 @@ 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */, 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, + 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */, ); path = Views; sourceTree = ""; @@ -460,6 +463,7 @@ 5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */, 50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */, 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */, + 50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */, 50617D8323FCE48E0099B055 /* App.swift in Sources */, 506772C92425BB8500034DED /* NoStoresView.swift in Sources */, 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */, diff --git a/Sources/Secretive/Views/ActionButtonStyle.swift b/Sources/Secretive/Views/ActionButtonStyle.swift index cc0b4d6..1f6afdc 100644 --- a/Sources/Secretive/Views/ActionButtonStyle.swift +++ b/Sources/Secretive/Views/ActionButtonStyle.swift @@ -23,6 +23,28 @@ extension View { } +struct NormalButtonModifier: ViewModifier { + + func body(content: Content) -> some View { + if #available(macOS 26.0, *) { + content + .glassEffect(.regular.tint(.white.opacity(0.1)), in: .circle) + } else { + content + .buttonStyle(.borderless) + } + } + +} + +extension View { + + func normal() -> some View { + modifier(NormalButtonModifier()) + } + +} + struct DangerButtonModifier: ViewModifier { @Environment(\.colorScheme) var colorScheme diff --git a/Sources/Secretive/Views/AgentStatusView.swift b/Sources/Secretive/Views/AgentStatusView.swift index eab0ea0..6af8df5 100644 --- a/Sources/Secretive/Views/AgentStatusView.swift +++ b/Sources/Secretive/Views/AgentStatusView.swift @@ -21,22 +21,22 @@ struct AgentRunningView: View { Form { Section { if let process = agentStatusChecker.process { - AgentInformationView( + ConfigurationItemView( title: "Secret Agent Location", value: process.bundleURL!.path(), - actions: [.revealInFinder], + action: .revealInFinder(process.bundleURL!.path()), ) - AgentInformationView( + ConfigurationItemView( title: "Socket Path", value: socketPath, - actions: [.copy], + action: .copy(socketPath), ) - AgentInformationView( + ConfigurationItemView( title: "Version", value: Bundle(url: process.bundleURL!)!.infoDictionary!["CFBundleShortVersionString"] as! String ) if let launchDate = process.launchDate { - AgentInformationView( + ConfigurationItemView( title: "Running Since", value: launchDate.formatted() ) @@ -144,50 +144,6 @@ struct AgentNotRunningView: View { } -struct AgentInformationView: View { - - enum Action { - case copy - case revealInFinder - } - - let title: LocalizedStringResource - let value: String - let actions: Set - @State var tapping = false - - init(title: LocalizedStringResource, value: String, actions: Set = []) { - self.title = title - self.value = value - self.actions = actions - } - - var body: some View { - VStack(alignment: .leading) { - HStack { - Text(title) - Spacer() - if actions.contains(.revealInFinder) { - Button("Reveal in Finder", systemImage: "folder") { - NSWorkspace.shared.selectFile(value, inFileViewerRootedAtPath: value) - } - .labelStyle(.iconOnly) - .buttonStyle(.borderless) - } - if actions.contains(.copy) { - Button("Copy", systemImage: "document.on.document") { - } - .labelStyle(.iconOnly) - .buttonStyle(.borderless) - } - } - Text(value) - .font(.subheadline) - .foregroundStyle(.secondary) - } - } -} - #Preview { AgentStatusView() .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false)) diff --git a/Sources/Secretive/Views/ConfigurationItemView.swift b/Sources/Secretive/Views/ConfigurationItemView.swift new file mode 100644 index 0000000..7f320e5 --- /dev/null +++ b/Sources/Secretive/Views/ConfigurationItemView.swift @@ -0,0 +1,54 @@ +import SwiftUI + +struct ConfigurationItemView: View { + + enum Action: Hashable { + case copy(String) + case revealInFinder(String) + } + + let title: LocalizedStringResource + let content: Content + let action: Action? + + init(title: LocalizedStringResource, value: String, action: Action? = nil) where Content == Text { + self.title = title + self.content = Text(value) + .font(.subheadline) + .foregroundStyle(.secondary) + self.action = action + } + + init(title: LocalizedStringResource, action: Action? = nil, content: () -> Content) { + self.title = title + self.content = content() + self.action = action + } + + var body: some View { + VStack(alignment: .leading) { + HStack { + Text(title) + Spacer() + switch action { + case .copy(let string): + Button("Reveal in Finder", systemImage: "folder") { + NSWorkspace.shared.selectFile(string, inFileViewerRootedAtPath: string) + } + .labelStyle(.iconOnly) + .buttonStyle(.borderless) + case .revealInFinder(let string): + Button("Reveal in Finder", systemImage: "folder") { + NSWorkspace.shared.selectFile(string, inFileViewerRootedAtPath: string) + } + .labelStyle(.iconOnly) + .buttonStyle(.borderless) + case nil: + EmptyView() + } + } + content + } + } +} + diff --git a/Sources/Secretive/Views/ConfigurationView.swift b/Sources/Secretive/Views/ConfigurationView.swift index 6d9eef7..3db61e9 100644 --- a/Sources/Secretive/Views/ConfigurationView.swift +++ b/Sources/Secretive/Views/ConfigurationView.swift @@ -4,52 +4,79 @@ struct ConfigurationView: View { @Binding var visible: Bool - @State var running = true - @State var sshConfig = false - @Environment(\.agentStatusChecker) var agentStatusChecker + 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 { - VStack(spacing: 0) { - NewStepView( - title: "setup_agent_title", - description: "setup_agent_description", - systemImage: "network.badge.shield.half.filled", - ) { - OnboardingButton("setup_agent_install_button", running) { - Task { - _ = await LaunchAgentController().forceLaunch() - agentStatusChecker.check() - running = agentStatusChecker.running + Form { + Section { + Picker("Configuring", selection: $selectedShellInstruction) { + ForEach(shellInstructions) { instruction in + Text(instruction.shell) + .tag(instruction) + .padding() } } - } - Divider() - Divider() - NewStepView( - title: "setup_ssh_title", - description: "setup_ssh_description", - systemImage: "network.badge.shield.half.filled", - ) { - HStack { - OnboardingButton("setup_ssh_added_manually_button", false) { - sshConfig = true + 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) + } + } - OnboardingButton("Add Automatically", false) { -// let controller = ShellConfigurationController() -// if controller.addToShell(shellInstructions: selectedShellInstruction) { -// } - sshConfig = true + 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")!) } } - .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10)) - .frame(minWidth: 500, idealWidth: 500, minHeight: 500, idealHeight: 500) - .padding() - .task { - running = agentStatusChecker.running + .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 29d8a35..2e20ad5 100644 --- a/Sources/Secretive/Views/ContentView.swift +++ b/Sources/Secretive/Views/ContentView.swift @@ -103,11 +103,10 @@ extension ContentView { @ViewBuilder var newItemView: some View { if storeList.modifiableStore?.isAvailable ?? false { - Button(action: { + Button(.appMenuNewSecretButton, systemImage: "plus") { showingCreation = true - }, label: { - Image(systemName: "plus") - }) + } + .normal() .sheet(isPresented: $showingCreation) { if let modifiable = storeList.modifiableStore { CreateSecretView(store: modifiable, showing: $showingCreation) { created in diff --git a/Sources/Secretive/Views/SecretDetailView.swift b/Sources/Secretive/Views/SecretDetailView.swift index 140ee97..c727bc2 100644 --- a/Sources/Secretive/Views/SecretDetailView.swift +++ b/Sources/Secretive/Views/SecretDetailView.swift @@ -23,6 +23,12 @@ 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() @@ -45,12 +51,6 @@ extension URL { } -#if DEBUG - -struct SecretDetailView_Previews: PreviewProvider { - static var previews: some View { - SecretDetailView(secret: Preview.Store(numberOfRandomSecrets: 1).secrets[0]) - } +#Preview { + SecretDetailView(secret: Preview.Secret(name: "Demonstration Secret")) } - -#endif diff --git a/Sources/Secretive/Views/SetupView.swift b/Sources/Secretive/Views/SetupView.swift index 5c452a0..81d4ad2 100644 --- a/Sources/Secretive/Views/SetupView.swift +++ b/Sources/Secretive/Views/SetupView.swift @@ -133,62 +133,6 @@ struct NewStepView: View { } -//struct SSHAgentSetupView: View { -// -// 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: "global", -// 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 { -// SetupStepView(title: "setup_ssh_title", -// image: Image(systemName: "terminal"), -// bodyText: "setup_ssh_description", -// buttonTitle: "setup_ssh_added_manually_button", -// buttonAction: buttonAction) { -// Link("setup_third_party_faq_link", destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!) -// Picker(selection: $selectedShellInstruction, label: EmptyView()) { -// ForEach(SSHAgentSetupView.controller.shellInstructions) { instruction in -// Text(instruction.shell) -// .tag(instruction) -// .padding() -// } -// }.pickerStyle(SegmentedPickerStyle()) -// CopyableView(title: "setup_ssh_add_to_config_button_\(selectedShellInstruction.shellConfigPath)", image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text) -// Button("setup_ssh_add_for_me_button") { -// let controller = ShellConfigurationController() -// if controller.addToShell(shellInstructions: selectedShellInstruction) { -// buttonAction() -// } -// } -// } -// } -// -//} - extension SetupView { enum Constants {