mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-09-16 09:20:56 +00:00
WIP
This commit is contained in:
parent
19760f1e02
commit
b949d846c1
@ -1,11 +1,19 @@
|
|||||||
{
|
{
|
||||||
"sourceLanguage" : "en",
|
"sourceLanguage" : "en",
|
||||||
"strings" : {
|
"strings" : {
|
||||||
".zshrc" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"Add Automatically" : {
|
"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" : {
|
"agent_not_running_notice_title" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@ -1169,6 +1177,9 @@
|
|||||||
},
|
},
|
||||||
"Configure" : {
|
"Configure" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Copy" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"copyable_click_to_copy_button" : {
|
"copyable_click_to_copy_button" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@ -2489,6 +2500,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Disable Agent" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Done" : {
|
"Done" : {
|
||||||
|
|
||||||
@ -3406,6 +3420,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Restart Agent" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Reveal in Finder" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Running Since" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Secret Agent Location" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"secret_detail_md5_fingerprint_label" : {
|
"secret_detail_md5_fingerprint_label" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@ -4335,9 +4361,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"setup_ssh_add_to_config_button_%@" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"setup_ssh_added_manually_button" : {
|
"setup_ssh_added_manually_button" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@ -5205,7 +5228,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TfileDialogMessageest" : {
|
"Socket Path" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Start Agent" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"unnamed_secret" : {
|
"unnamed_secret" : {
|
||||||
@ -6153,6 +6179,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Version" : {
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
||||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.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 */; };
|
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
||||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
||||||
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079BA0E250F29BF00EA86F4 /* StoreListView.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 */; };
|
50AE97002E5C1A420018C710 /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */; };
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.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 */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@ -121,7 +121,6 @@
|
|||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
||||||
5066A6F6251829B1004B5A36 /* ShellConfigurationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellConfigurationController.swift; sourceTree = "<group>"; };
|
|
||||||
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
||||||
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreListView.swift; sourceTree = "<group>"; };
|
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreListView.swift; sourceTree = "<group>"; };
|
||||||
@ -142,6 +141,7 @@
|
|||||||
50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = "<group>"; };
|
50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = "<group>"; };
|
||||||
50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = "<group>"; };
|
50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = "<group>"; };
|
||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
||||||
|
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentStatusView.swift; sourceTree = "<group>"; };
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@ -256,6 +256,7 @@
|
|||||||
506772C82425BB8500034DED /* NoStoresView.swift */,
|
506772C82425BB8500034DED /* NoStoresView.swift */,
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
||||||
5066A6C12516F303004B5A36 /* SetupView.swift */,
|
5066A6C12516F303004B5A36 /* SetupView.swift */,
|
||||||
|
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
||||||
50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */,
|
50AE96FF2E5C1A420018C710 /* ConfigurationView.swift */,
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
||||||
);
|
);
|
||||||
@ -269,7 +270,6 @@
|
|||||||
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
|
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
||||||
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
|
||||||
5066A6F6251829B1004B5A36 /* ShellConfigurationController.swift */,
|
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -445,8 +445,8 @@
|
|||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
||||||
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
||||||
5066A6F7251829B1004B5A36 /* ShellConfigurationController.swift in Sources */,
|
|
||||||
50033AC327813F1700253856 /* BundleIDs.swift in Sources */,
|
50033AC327813F1700253856 /* BundleIDs.swift in Sources */,
|
||||||
|
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */,
|
||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
||||||
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
||||||
|
@ -87,7 +87,7 @@ extension Secretive {
|
|||||||
private func reinstallAgent() {
|
private func reinstallAgent() {
|
||||||
justUpdatedChecker.check()
|
justUpdatedChecker.check()
|
||||||
Task {
|
Task {
|
||||||
await LaunchAgentController().install()
|
_ = await LaunchAgentController().install()
|
||||||
try? await Task.sleep(for: .seconds(1))
|
try? await Task.sleep(for: .seconds(1))
|
||||||
agentStatusChecker.check()
|
agentStatusChecker.check()
|
||||||
if !agentStatusChecker.running {
|
if !agentStatusChecker.running {
|
||||||
|
@ -6,12 +6,14 @@ import Observation
|
|||||||
@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable {
|
@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable {
|
||||||
var running: Bool { get }
|
var running: Bool { get }
|
||||||
var developmentBuild: Bool { get }
|
var developmentBuild: Bool { get }
|
||||||
|
var process: NSRunningApplication? { get }
|
||||||
func check()
|
func check()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol {
|
@Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
var running: Bool = false
|
var running: Bool = false
|
||||||
|
var process: NSRunningApplication? = nil
|
||||||
|
|
||||||
nonisolated init() {
|
nonisolated init() {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
@ -20,7 +22,8 @@ import Observation
|
|||||||
}
|
}
|
||||||
|
|
||||||
func check() {
|
func check() {
|
||||||
running = instanceSecretAgentProcess != nil
|
process = instanceSecretAgentProcess
|
||||||
|
running = process != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// All processes, including ones from older versions, etc
|
// All processes, including ones from older versions, etc
|
||||||
@ -34,7 +37,7 @@ import Observation
|
|||||||
let agents = allSecretAgentProcesses
|
let agents = allSecretAgentProcesses
|
||||||
for agent in agents {
|
for agent in agents {
|
||||||
guard let url = agent.bundleURL else { continue }
|
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
|
return agent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,9 +46,15 @@ import Observation
|
|||||||
|
|
||||||
// Whether Secretive is being run in an Xcode environment.
|
// Whether Secretive is being run in an Xcode environment.
|
||||||
var developmentBuild: Bool {
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -8,15 +8,23 @@ struct LaunchAgentController {
|
|||||||
|
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
|
||||||
|
|
||||||
func install() async {
|
func install() async -> Bool {
|
||||||
logger.debug("Installing agent")
|
logger.debug("Installing agent")
|
||||||
_ = setEnabled(false)
|
_ = setEnabled(false)
|
||||||
// This is definitely a bit of a "seems to work better" thing but:
|
// 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
|
// Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old
|
||||||
// and start new?
|
// and start new?
|
||||||
try? await Task.sleep(for: .seconds(1))
|
try? await Task.sleep(for: .seconds(1))
|
||||||
await MainActor.run {
|
return await MainActor.run {
|
||||||
_ = setEnabled(true)
|
setEnabled(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstall() async -> Bool {
|
||||||
|
logger.debug("Uninstalling agent")
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return await MainActor.run {
|
||||||
|
setEnabled(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,12 +1,15 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
|
||||||
class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
|
class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
let running: Bool
|
let running: Bool
|
||||||
|
let process: NSRunningApplication?
|
||||||
let developmentBuild = false
|
let developmentBuild = false
|
||||||
|
|
||||||
init(running: Bool = true) {
|
init(running: Bool = true, process: NSRunningApplication? = nil) {
|
||||||
self.running = running
|
self.running = running
|
||||||
|
self.process = process
|
||||||
}
|
}
|
||||||
|
|
||||||
func check() {
|
func check() {
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
153
Sources/Secretive/Views/AgentStatusView.swift
Normal file
153
Sources/Secretive/Views/AgentStatusView.swift
Normal file
@ -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<Action>
|
||||||
|
@State var tapping = false
|
||||||
|
|
||||||
|
init(title: LocalizedStringResource, value: String, actions: Set<Action> = []) {
|
||||||
|
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))
|
||||||
|
}
|
@ -56,7 +56,7 @@ extension ContentView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var needsSetup: Bool {
|
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
|
/// 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 {
|
if needsSetup {
|
||||||
setupNoticeView
|
setupNoticeView
|
||||||
} else {
|
} else {
|
||||||
runningNoticeView
|
agentStatusToolbarView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,43 +125,44 @@ extension ContentView {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
runningSetup = true
|
runningSetup = true
|
||||||
}, label: {
|
}, label: {
|
||||||
Group {
|
if !hasRunSetup {
|
||||||
if hasRunSetup && !agentStatusChecker.running {
|
Text(.agentSetupNoticeTitle)
|
||||||
Text(.agentNotRunningNoticeTitle)
|
.font(.headline)
|
||||||
} else {
|
|
||||||
Text(.agentSetupNoticeTitle)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.font(.headline)
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.buttonStyle(ToolbarButtonStyle(color: .orange))
|
.buttonStyle(ToolbarButtonStyle(color: .orange))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var runningNoticeView: some View {
|
var agentStatusToolbarView: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
showingAgentInfo = true
|
showingAgentInfo = true
|
||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(.agentRunningNoticeTitle)
|
if agentStatusChecker.running {
|
||||||
.font(.headline)
|
Text(.agentRunningNoticeTitle)
|
||||||
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
.font(.headline)
|
||||||
Circle()
|
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
||||||
.frame(width: 10, height: 10)
|
Circle()
|
||||||
.foregroundColor(Color.green)
|
.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) {
|
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
||||||
VStack {
|
AgentStatusView()
|
||||||
Text(.agentRunningNoticeDetailTitle)
|
|
||||||
.font(.title)
|
|
||||||
.padding(5)
|
|
||||||
Text(.agentRunningNoticeDetailDescription)
|
|
||||||
.frame(width: 300)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +194,6 @@ extension ContentView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var attachmentAnchor: PopoverAttachmentAnchor {
|
var attachmentAnchor: PopoverAttachmentAnchor {
|
||||||
// Ideally .point(.bottom), but broken on Sonoma (FB12726503)
|
|
||||||
.rect(.bounds)
|
.rect(.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +19,7 @@ struct SetupView: View {
|
|||||||
) {
|
) {
|
||||||
OnboardingButton("setup_agent_install_button", installed) {
|
OnboardingButton("setup_agent_install_button", installed) {
|
||||||
Task {
|
Task {
|
||||||
await LaunchAgentController().install()
|
installed = await LaunchAgentController().install()
|
||||||
installed = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,7 +29,7 @@ struct SetupView: View {
|
|||||||
description: "setup_updates_description",
|
description: "setup_updates_description",
|
||||||
systemImage: "network.badge.shield.half.filled",
|
systemImage: "network.badge.shield.half.filled",
|
||||||
) {
|
) {
|
||||||
OnboardingButton("setup_updates_ok", false) {
|
OnboardingButton("setup_updates_ok", updates) {
|
||||||
Task {
|
Task {
|
||||||
updates = true
|
updates = true
|
||||||
}
|
}
|
||||||
@ -43,27 +42,9 @@ struct SetupView: View {
|
|||||||
systemImage: "network.badge.shield.half.filled",
|
systemImage: "network.badge.shield.half.filled",
|
||||||
) {
|
) {
|
||||||
HStack {
|
HStack {
|
||||||
OnboardingButton("setup_ssh_added_manually_button", false) {
|
OnboardingButton("Configure", false) {
|
||||||
sshConfig = true
|
// 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<Content: View>: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OldSetupView: View {
|
//struct SSHAgentSetupView: View {
|
||||||
|
//
|
||||||
@State var stepIndex = 0
|
// let buttonAction: () -> Void
|
||||||
@Binding var visible: Bool
|
//
|
||||||
@Binding var setupComplete: Bool
|
// @State private var selectedShellInstruction: ShellConfigInstruction?
|
||||||
|
//
|
||||||
var body: some View {
|
// private let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String
|
||||||
GeometryReader { proxy in
|
//
|
||||||
VStack {
|
// private var shellInstructions: [ShellConfigInstruction] {
|
||||||
StepView(numberOfSteps: 3, currentStep: stepIndex, width: proxy.size.width)
|
// [
|
||||||
GeometryReader { _ in
|
// ShellConfigInstruction(shell: "global",
|
||||||
HStack(spacing: 0) {
|
// shellConfigDirectory: "~/.ssh/",
|
||||||
SecretAgentSetupView(buttonAction: advance)
|
// shellConfigFilename: "config",
|
||||||
.frame(width: proxy.size.width)
|
// text: "Host *\n\tIdentityAgent \(socketPath)"),
|
||||||
SSHAgentSetupView(buttonAction: advance)
|
// ShellConfigInstruction(shell: "zsh",
|
||||||
.frame(width: proxy.size.width)
|
// shellConfigDirectory: "~/",
|
||||||
UpdaterExplainerView {
|
// shellConfigFilename: ".zshrc",
|
||||||
visible = false
|
// text: "export SSH_AUTH_SOCK=\(socketPath)"),
|
||||||
setupComplete = true
|
// ShellConfigInstruction(shell: "bash",
|
||||||
}
|
// shellConfigDirectory: "~/",
|
||||||
.frame(width: proxy.size.width)
|
// shellConfigFilename: ".bashrc",
|
||||||
}
|
// text: "export SSH_AUTH_SOCK=\(socketPath)"),
|
||||||
.offset(x: -proxy.size.width * Double(stepIndex), y: 0)
|
// ShellConfigInstruction(shell: "fish",
|
||||||
}
|
// shellConfigDirectory: "~/.config/fish",
|
||||||
}
|
// shellConfigFilename: "config.fish",
|
||||||
}
|
// text: "set -x SSH_AUTH_SOCK \(socketPath)"),
|
||||||
.frame(minWidth: 500, idealWidth: 500, minHeight: 500, idealHeight: 500)
|
// ]
|
||||||
}
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
func advance() {
|
// var body: some View {
|
||||||
withAnimation(.spring()) {
|
// SetupStepView(title: "setup_ssh_title",
|
||||||
stepIndex += 1
|
// 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()) {
|
||||||
struct StepView: View {
|
// ForEach(SSHAgentSetupView.controller.shellInstructions) { instruction in
|
||||||
|
// Text(instruction.shell)
|
||||||
let numberOfSteps: Int
|
// .tag(instruction)
|
||||||
let currentStep: Int
|
// .padding()
|
||||||
|
// }
|
||||||
// Ideally we'd have a geometry reader inside this view doing this for us, but that crashes on 11.0b7
|
// }.pickerStyle(SegmentedPickerStyle())
|
||||||
let width: Double
|
// 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") {
|
||||||
var body: some View {
|
// let controller = ShellConfigurationController()
|
||||||
ZStack(alignment: .leading) {
|
// if controller.addToShell(shellInstructions: selectedShellInstruction) {
|
||||||
Rectangle()
|
// buttonAction()
|
||||||
.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..<numberOfSteps), id: \.self) { index in
|
|
||||||
ZStack {
|
|
||||||
if currentStep > 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<Content> : 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SetupView {
|
extension SetupView {
|
||||||
|
|
||||||
@ -404,46 +214,10 @@ struct ShellConfigInstruction: Identifiable, Hashable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#Preview {
|
||||||
|
SetupView(visible: .constant(true), setupComplete: .constant(false))
|
||||||
struct SetupView_Previews: PreviewProvider {
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
Group {
|
|
||||||
SetupView(visible: .constant(true), setupComplete: .constant(false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SecretAgentSetupView_Previews: PreviewProvider {
|
//#Preview {
|
||||||
|
// SSHAgentSetupView(buttonAction: {})
|
||||||
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
|
|
||||||
|
Loading…
Reference in New Issue
Block a user