From b949d846c13717f475ab936e4b5bce64c7f3b008 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 30 Aug 2025 18:56:52 -0700 Subject: [PATCH] WIP --- Sources/Packages/Localizable.xcstrings | 43 ++- Sources/Secretive.xcodeproj/project.pbxproj | 8 +- Sources/Secretive/App.swift | 2 +- .../Controllers/AgentStatusChecker.swift | 15 +- .../Controllers/LaunchAgentController.swift | 14 +- .../ShellConfigurationController.swift | 63 ---- .../PreviewAgentStatusChecker.swift | 5 +- .../Secretive/Views/ActionButtonStyle.swift | 27 ++ Sources/Secretive/Views/AgentStatusView.swift | 153 ++++++++ Sources/Secretive/Views/ContentView.swift | 54 +-- Sources/Secretive/Views/SetupView.swift | 354 ++++-------------- 11 files changed, 339 insertions(+), 399 deletions(-) delete mode 100644 Sources/Secretive/Controllers/ShellConfigurationController.swift create mode 100644 Sources/Secretive/Views/AgentStatusView.swift diff --git a/Sources/Packages/Localizable.xcstrings b/Sources/Packages/Localizable.xcstrings index 403b721..a3e0581 100644 --- a/Sources/Packages/Localizable.xcstrings +++ b/Sources/Packages/Localizable.xcstrings @@ -1,11 +1,19 @@ { "sourceLanguage" : "en", "strings" : { - ".zshrc" : { - - }, "Add Automatically" : { + }, + "agent_not_running_notice_detail_description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**Secretive will not be able to function properly unless the agent is installed and running.**" + } + } + } }, "agent_not_running_notice_title" : { "extractionState" : "manual", @@ -1169,6 +1177,9 @@ }, "Configure" : { + }, + "Copy" : { + }, "copyable_click_to_copy_button" : { "extractionState" : "manual", @@ -2489,6 +2500,9 @@ } } } + }, + "Disable Agent" : { + }, "Done" : { @@ -3406,6 +3420,18 @@ } } } + }, + "Restart Agent" : { + + }, + "Reveal in Finder" : { + + }, + "Running Since" : { + + }, + "Secret Agent Location" : { + }, "secret_detail_md5_fingerprint_label" : { "extractionState" : "manual", @@ -4335,9 +4361,6 @@ } } } - }, - "setup_ssh_add_to_config_button_%@" : { - }, "setup_ssh_added_manually_button" : { "extractionState" : "manual", @@ -5205,7 +5228,10 @@ } } }, - "TfileDialogMessageest" : { + "Socket Path" : { + + }, + "Start Agent" : { }, "unnamed_secret" : { @@ -6153,6 +6179,9 @@ } } } + }, + "Version" : { + } }, "version" : "1.0" diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index bf9352a..b09a304 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ 5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; }; 5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; }; 5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; }; - 5066A6F7251829B1004B5A36 /* ShellConfigurationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6F6251829B1004B5A36 /* ShellConfigurationController.swift */; }; 506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; }; 506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; }; 5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */; }; @@ -52,6 +51,7 @@ 50AE97002E5C1A420018C710 /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* ConfigurationView.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 */; }; 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 */ @@ -121,7 +121,6 @@ 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = ""; }; 5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; 5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = ""; }; - 5066A6F6251829B1004B5A36 /* ShellConfigurationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellConfigurationController.swift; sourceTree = ""; }; 506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = ""; }; 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreListView.swift; sourceTree = ""; }; @@ -142,6 +141,7 @@ 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.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 = ""; }; 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 */ @@ -256,6 +256,7 @@ 506772C82425BB8500034DED /* NoStoresView.swift */, 50153E1F250AFCB200525160 /* UpdateView.swift */, 5066A6C12516F303004B5A36 /* SetupView.swift */, + 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */, 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, ); @@ -269,7 +270,6 @@ 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */, 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */, 50571E0424393D1500F76F6C /* LaunchAgentController.swift */, - 5066A6F6251829B1004B5A36 /* ShellConfigurationController.swift */, ); path = Controllers; sourceTree = ""; @@ -445,8 +445,8 @@ 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */, 5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */, 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */, - 5066A6F7251829B1004B5A36 /* ShellConfigurationController.swift in Sources */, 50033AC327813F1700253856 /* BundleIDs.swift in Sources */, + 50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */, 508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */, 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */, 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 27ffeba..2e5653f 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -87,7 +87,7 @@ extension Secretive { private func reinstallAgent() { justUpdatedChecker.check() Task { - await LaunchAgentController().install() + _ = await LaunchAgentController().install() try? await Task.sleep(for: .seconds(1)) agentStatusChecker.check() if !agentStatusChecker.running { diff --git a/Sources/Secretive/Controllers/AgentStatusChecker.swift b/Sources/Secretive/Controllers/AgentStatusChecker.swift index 646cb4c..b7327a6 100644 --- a/Sources/Secretive/Controllers/AgentStatusChecker.swift +++ b/Sources/Secretive/Controllers/AgentStatusChecker.swift @@ -6,12 +6,14 @@ import Observation @MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable { var running: Bool { get } var developmentBuild: Bool { get } + var process: NSRunningApplication? { get } func check() } @Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol { var running: Bool = false + var process: NSRunningApplication? = nil nonisolated init() { Task { @MainActor in @@ -20,7 +22,8 @@ import Observation } func check() { - running = instanceSecretAgentProcess != nil + process = instanceSecretAgentProcess + running = process != nil } // All processes, including ones from older versions, etc @@ -34,7 +37,7 @@ import Observation let agents = allSecretAgentProcesses for agent in agents { guard let url = agent.bundleURL else { continue } - if url.absoluteString.hasPrefix(Bundle.main.bundleURL.absoluteString) { + if url.absoluteString.hasPrefix(Bundle.main.bundleURL.absoluteString) || (url.isXcodeURL && developmentBuild) { return agent } } @@ -43,9 +46,15 @@ import Observation // Whether Secretive is being run in an Xcode environment. var developmentBuild: Bool { - Bundle.main.bundleURL.absoluteString.contains("/Library/Developer/Xcode") + Bundle.main.bundleURL.isXcodeURL } } +extension URL { + var isXcodeURL: Bool { + absoluteString.contains("/Library/Developer/Xcode") + } + +} diff --git a/Sources/Secretive/Controllers/LaunchAgentController.swift b/Sources/Secretive/Controllers/LaunchAgentController.swift index ab0a912..c863d92 100644 --- a/Sources/Secretive/Controllers/LaunchAgentController.swift +++ b/Sources/Secretive/Controllers/LaunchAgentController.swift @@ -8,15 +8,23 @@ struct LaunchAgentController { private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController") - func install() async { + func install() async -> Bool { logger.debug("Installing agent") _ = setEnabled(false) // This is definitely a bit of a "seems to work better" thing but: // Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old // and start new? try? await Task.sleep(for: .seconds(1)) - await MainActor.run { - _ = setEnabled(true) + return await MainActor.run { + setEnabled(true) + } + } + + func uninstall() async -> Bool { + logger.debug("Uninstalling agent") + try? await Task.sleep(for: .seconds(1)) + return await MainActor.run { + setEnabled(false) } } diff --git a/Sources/Secretive/Controllers/ShellConfigurationController.swift b/Sources/Secretive/Controllers/ShellConfigurationController.swift deleted file mode 100644 index 2044160..0000000 --- a/Sources/Secretive/Controllers/ShellConfigurationController.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Foundation -import Cocoa -import SecretKit - -struct ShellConfigurationController { - - let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String - - 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)"), - ] - - } - - - @MainActor func addToShell(shellInstructions: ShellConfigInstruction) -> Bool { - let openPanel = NSOpenPanel() - // This is sync, so no need to strongly retain - let delegate = Delegate(name: shellInstructions.shellConfigFilename) - openPanel.delegate = delegate - openPanel.message = "Select \(shellInstructions.shellConfigFilename) to let Secretive configure your shell automatically." - openPanel.prompt = "Add to \(shellInstructions.shellConfigFilename)" - openPanel.canChooseFiles = true - openPanel.canChooseDirectories = false - openPanel.showsHiddenFiles = true - openPanel.directoryURL = URL(fileURLWithPath: shellInstructions.shellConfigDirectory) - openPanel.nameFieldStringValue = shellInstructions.shellConfigFilename - openPanel.allowedContentTypes = [.symbolicLink, .data, .plainText] - openPanel.runModal() - guard let fileURL = openPanel.urls.first else { return false } - let handle: FileHandle - do { - handle = try FileHandle(forUpdating: fileURL) - guard let existing = try handle.readToEnd(), - let existingString = String(data: existing, encoding: .utf8) else { return false } - guard !existingString.contains(shellInstructions.text) else { - return true - } - try handle.seekToEnd() - } catch { - return false - } - handle.write(Data("\n# Secretive Config\n\(shellInstructions.text)\n".utf8)) - return true - } - -} diff --git a/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift b/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift index 51a5c09..e9799e9 100644 --- a/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift +++ b/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift @@ -1,12 +1,15 @@ import Foundation +import AppKit class PreviewAgentStatusChecker: AgentStatusCheckerProtocol { let running: Bool + let process: NSRunningApplication? let developmentBuild = false - init(running: Bool = true) { + init(running: Bool = true, process: NSRunningApplication? = nil) { self.running = running + self.process = process } func check() { diff --git a/Sources/Secretive/Views/ActionButtonStyle.swift b/Sources/Secretive/Views/ActionButtonStyle.swift index 4d7455f..cc0b4d6 100644 --- a/Sources/Secretive/Views/ActionButtonStyle.swift +++ b/Sources/Secretive/Views/ActionButtonStyle.swift @@ -22,3 +22,30 @@ extension View { } } + +struct DangerButtonModifier: ViewModifier { + + @Environment(\.colorScheme) var colorScheme + + func body(content: Content) -> some View { + // Tinted glass prominent is really hard to read on 26.0. + if #available(macOS 26.0, *), colorScheme == .dark { + content.buttonStyle(.glassProminent) + .tint(.red) + .foregroundStyle(.white) + } else { + content.buttonStyle(.borderedProminent) + .tint(.red) + .foregroundStyle(.white) + } + } + +} + +extension View { + + func danger() -> some View { + modifier(DangerButtonModifier()) + } + +} diff --git a/Sources/Secretive/Views/AgentStatusView.swift b/Sources/Secretive/Views/AgentStatusView.swift new file mode 100644 index 0000000..b829a15 --- /dev/null +++ b/Sources/Secretive/Views/AgentStatusView.swift @@ -0,0 +1,153 @@ +import SwiftUI + +struct AgentStatusView: View { + + @Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol + private let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String + + var body: some View { + if agentStatusChecker.running { + Form { + Section { + if let process = agentStatusChecker.process { + AgentInformationView( + title: "Secret Agent Location", + value: process.bundleURL!.path(), + actions: [.revealInFinder], + ) + AgentInformationView( + title: "Socket Path", + value: socketPath, + actions: [.copy], + ) + AgentInformationView( + title: "Version", + value: Bundle(url: process.bundleURL!)!.infoDictionary!["CFBundleShortVersionString"] as! String + ) + if let launchDate = process.launchDate { + AgentInformationView( + title: "Running Since", + value: launchDate.formatted() + ) + } + } + } header: { + Text(.agentRunningNoticeDetailTitle) + .font(.headline) + .padding(.top) + } footer: { + VStack(alignment: .leading) { + Text(.agentRunningNoticeDetailDescription) + HStack { + Spacer() + Menu("Restart Agent") { + Button("Disable Agent") { + Task { + await LaunchAgentController() + .uninstall() + } + } + } primaryAction: { + Task { + let controller = LaunchAgentController() + let installed = await controller.install() + if !installed { + _ = await controller.forceLaunch() + } + agentStatusChecker.check() + } + } + } + } + .padding(.vertical) + } + + } + .formStyle(.grouped) + .frame(width: 400) + } else { + Form { + Section { + } header: { + Text(.agentNotRunningNoticeTitle) + .font(.headline) + .padding(.top) + } footer: { + Text(.agentNotRunningNoticeDetailDescription) + Spacer() + HStack { + Spacer() + Button("Start Agent") { + Task { + let controller = LaunchAgentController() + let installed = await controller.install() + if !installed { + _ = await controller.forceLaunch() + } + agentStatusChecker.check() + } + } + .primary() + } + .padding(.vertical) + } + } + .formStyle(.grouped) + .frame(width: 400) + } + } + +} + +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)) +} +#Preview { + AgentStatusView() + .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current)) +} diff --git a/Sources/Secretive/Views/ContentView.swift b/Sources/Secretive/Views/ContentView.swift index 899d12b..34e0405 100644 --- a/Sources/Secretive/Views/ContentView.swift +++ b/Sources/Secretive/Views/ContentView.swift @@ -56,7 +56,7 @@ extension ContentView { } var needsSetup: Bool { - (runningSetup || !hasRunSetup || !agentStatusChecker.running) && !agentStatusChecker.developmentBuild + runningSetup || !hasRunSetup } /// Item either showing a "everything's good, here's more info" or "something's wrong, re-run setup" message @@ -66,7 +66,7 @@ extension ContentView { if needsSetup { setupNoticeView } else { - runningNoticeView + agentStatusToolbarView } } @@ -125,43 +125,44 @@ extension ContentView { Button(action: { runningSetup = true }, label: { - Group { - if hasRunSetup && !agentStatusChecker.running { - Text(.agentNotRunningNoticeTitle) - } else { - Text(.agentSetupNoticeTitle) - } + if !hasRunSetup { + Text(.agentSetupNoticeTitle) + .font(.headline) } - .font(.headline) - }) .buttonStyle(ToolbarButtonStyle(color: .orange)) } @ViewBuilder - var runningNoticeView: some View { + var agentStatusToolbarView: some View { Button(action: { showingAgentInfo = true }, label: { HStack { - Text(.agentRunningNoticeTitle) - .font(.headline) - .foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white) - Circle() - .frame(width: 10, height: 10) - .foregroundColor(Color.green) + if agentStatusChecker.running { + Text(.agentRunningNoticeTitle) + .font(.headline) + .foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white) + Circle() + .frame(width: 10, height: 10) + .foregroundColor(Color.green) + } else { + Text(.agentNotRunningNoticeTitle) + .font(.headline) + Circle() + .frame(width: 10, height: 10) + .foregroundColor(Color.red) + } } }) - .buttonStyle(ToolbarButtonStyle(lightColor: .black.opacity(0.05), darkColor: .white.opacity(0.05))) + .buttonStyle( + ToolbarButtonStyle( + lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75), + darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5), + ) + ) .popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) { - VStack { - Text(.agentRunningNoticeDetailTitle) - .font(.title) - .padding(5) - Text(.agentRunningNoticeDetailDescription) - .frame(width: 300) - } - .padding() + AgentStatusView() } } @@ -193,7 +194,6 @@ extension ContentView { } var attachmentAnchor: PopoverAttachmentAnchor { - // Ideally .point(.bottom), but broken on Sonoma (FB12726503) .rect(.bounds) } diff --git a/Sources/Secretive/Views/SetupView.swift b/Sources/Secretive/Views/SetupView.swift index 030caf3..5c452a0 100644 --- a/Sources/Secretive/Views/SetupView.swift +++ b/Sources/Secretive/Views/SetupView.swift @@ -19,8 +19,7 @@ struct SetupView: View { ) { OnboardingButton("setup_agent_install_button", installed) { Task { - await LaunchAgentController().install() - installed = true + installed = await LaunchAgentController().install() } } } @@ -30,7 +29,7 @@ struct SetupView: View { description: "setup_updates_description", systemImage: "network.badge.shield.half.filled", ) { - OnboardingButton("setup_updates_ok", false) { + OnboardingButton("setup_updates_ok", updates) { Task { updates = true } @@ -43,27 +42,9 @@ struct SetupView: View { systemImage: "network.badge.shield.half.filled", ) { HStack { - OnboardingButton("setup_ssh_added_manually_button", false) { - sshConfig = true + OnboardingButton("Configure", false) { +// sshConfig = true } - OnboardingButton("Add Automatically", false) { - // let controller = ShellConfigurationController() - // if controller.addToShell(shellInstructions: selectedShellInstruction) { - // } - sshConfig = true - } - .fileImporter(isPresented: $sshConfig, allowedContentTypes: [.utf8PlainText, .symbolicLink, .data]) { result in - print(result) - } - // FIXME: - .fileDialogDefaultDirectory(URL(fileURLWithPath: "/Users/max/")) - .fileDialogBrowserOptions([.displayFileExtensions, .includeHiddenFiles]) - .fileExporterFilenameLabel(Text(".zshrc")) - .fileDialogMessage("TfileDialogMessageest") - .fileDialogConfirmationLabel("Configure") - .fileDialogURLEnabled(#Predicate { - return $0.lastPathComponent == ".zshrc" || $0.hasDirectoryPath - }) } } } @@ -152,232 +133,61 @@ struct NewStepView: View { } -struct OldSetupView: View { - - @State var stepIndex = 0 - @Binding var visible: Bool - @Binding var setupComplete: Bool - - var body: some View { - GeometryReader { proxy in - VStack { - StepView(numberOfSteps: 3, currentStep: stepIndex, width: proxy.size.width) - GeometryReader { _ in - HStack(spacing: 0) { - SecretAgentSetupView(buttonAction: advance) - .frame(width: proxy.size.width) - SSHAgentSetupView(buttonAction: advance) - .frame(width: proxy.size.width) - UpdaterExplainerView { - visible = false - setupComplete = true - } - .frame(width: proxy.size.width) - } - .offset(x: -proxy.size.width * Double(stepIndex), y: 0) - } - } - } - .frame(minWidth: 500, idealWidth: 500, minHeight: 500, idealHeight: 500) - } - - - func advance() { - withAnimation(.spring()) { - stepIndex += 1 - } - } - -} - -struct StepView: View { - - let numberOfSteps: Int - let currentStep: Int - - // Ideally we'd have a geometry reader inside this view doing this for us, but that crashes on 11.0b7 - let width: Double - - var body: some View { - ZStack(alignment: .leading) { - Rectangle() - .foregroundColor(.blue) - .frame(height: 5) - Rectangle() - .foregroundColor(.green) - .frame(width: max(0, ((width - (Constants.padding * 2)) / Double(numberOfSteps - 1)) * Double(currentStep) - (Constants.circleWidth / 2)), height: 5) - HStack { - ForEach(Array(0.. index { - Circle() - .foregroundColor(.green) - .frame(width: Constants.circleWidth, height: Constants.circleWidth) - Text("setup_step_complete_symbol") - .foregroundColor(.white) - .bold() - } else { - Circle() - .foregroundColor(.blue) - .frame(width: Constants.circleWidth, height: Constants.circleWidth) - if currentStep == index { - Circle() - .strokeBorder(Color.white, lineWidth: 3) - .frame(width: Constants.circleWidth, height: Constants.circleWidth) - } - Text(String(describing: index + 1)) - .foregroundColor(.white) - .bold() - } - } - if index < numberOfSteps - 1 { - Spacer(minLength: 30) - } - } - } - }.padding(Constants.padding) - } - -} - -extension StepView { - - enum Constants { - - static let padding: Double = 15 - static let circleWidth: Double = 30 - - } - -} - -struct SetupStepView : View where Content : View { - - let title: LocalizedStringKey - let image: Image - let bodyText: LocalizedStringKey - let buttonTitle: LocalizedStringKey - let buttonAction: () -> Void - let content: Content - - init(title: LocalizedStringKey, image: Image, bodyText: LocalizedStringKey, buttonTitle: LocalizedStringKey, buttonAction: @escaping () -> Void = {}, @ViewBuilder content: () -> Content) { - self.title = title - self.image = image - self.bodyText = bodyText - self.buttonTitle = buttonTitle - self.buttonAction = buttonAction - self.content = content() - } - - var body: some View { - VStack { - Text(title) - .font(.title) - Spacer() - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 64) - Spacer() - Text(bodyText) - .multilineTextAlignment(.center) - Spacer() - content - Spacer() - Button(buttonTitle) { - buttonAction() - } - }.padding() - } - -} - -struct SecretAgentSetupView: View { - - let buttonAction: () -> Void - - var body: some View { - SetupStepView(title: "setup_agent_title", - image: Image(nsImage: NSApplication.shared.applicationIconImage), - bodyText: "setup_agent_description", - buttonTitle: "setup_agent_install_button", - buttonAction: install) { - Text("setup_agent_activity_monitor_description") - .multilineTextAlignment(.center) - } - } - - func install() { - Task { - await LaunchAgentController().install() - buttonAction() - } - } - -} - -struct SSHAgentSetupView: View { - - let buttonAction: () -> Void - - private static let controller = ShellConfigurationController() - @State private var selectedShellInstruction: ShellConfigInstruction = controller.shellInstructions.first! - - 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() - } - } - } - } - -} - -class Delegate: NSObject, NSOpenSavePanelDelegate { - - private let name: String - - init(name: String) { - self.name = name - } - - func panel(_ sender: Any, shouldEnable url: URL) -> Bool { - return url.lastPathComponent == name - } - -} - -struct UpdaterExplainerView: View { - - let buttonAction: () -> Void - - var body: some View { - SetupStepView(title: "setup_updates_title", - image: Image(systemName: "dot.radiowaves.left.and.right"), - bodyText: "setup_updates_description", - buttonTitle: "setup_updates_ok", - buttonAction: buttonAction) { - Link("setup_updates_readmore", destination: SetupView.Constants.updaterFAQURL) - } - } - -} +//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 { @@ -404,46 +214,10 @@ struct ShellConfigInstruction: Identifiable, Hashable { } -#if DEBUG - -struct SetupView_Previews: PreviewProvider { - - static var previews: some View { - Group { - SetupView(visible: .constant(true), setupComplete: .constant(false)) - } - } - +#Preview { + SetupView(visible: .constant(true), setupComplete: .constant(false)) } -struct SecretAgentSetupView_Previews: PreviewProvider { - - static var previews: some View { - Group { - SecretAgentSetupView(buttonAction: {}) - } - } - -} - -struct SSHAgentSetupView_Previews: PreviewProvider { - - static var previews: some View { - Group { - SSHAgentSetupView(buttonAction: {}) - } - } - -} - -struct UpdaterExplainerView_Previews: PreviewProvider { - - static var previews: some View { - Group { - UpdaterExplainerView(buttonAction: {}) - } - } - -} - -#endif +//#Preview { +// SSHAgentSetupView(buttonAction: {}) +//}