This commit is contained in:
Max Goedjen
2025-08-25 00:48:07 -07:00
parent f0a6f2e43b
commit cbf903deb7
10 changed files with 218 additions and 34 deletions

View File

@@ -71,7 +71,7 @@ struct Secretive: App {
NSWorkspace.shared.open(Constants.helpURL)
}
}
CommandGroup(after: .help) {
CommandGroup(before: .help) {
Button(.appMenuSetupButton) {
showingSetup = true
}

View File

@@ -24,13 +24,14 @@ import Observation
}
// All processes, including ones from older versions, etc
var secretAgentProcesses: [NSRunningApplication] {
NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.agentBundleID)
var allSecretAgentProcesses: [NSRunningApplication] {
NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.agentBundleID)
}
// The process corresponding to this instance of Secretive
var instanceSecretAgentProcess: NSRunningApplication? {
let agents = secretAgentProcesses
// FIXME: CHECK VERSION
let agents = allSecretAgentProcesses
for agent in agents {
guard let url = agent.bundleURL else { continue }
if url.absoluteString.hasPrefix(Bundle.main.bundleURL.absoluteString) {
@@ -40,7 +41,6 @@ import Observation
return nil
}
// Whether Secretive is being run in an Xcode environment.
var developmentBuild: Bool {
Bundle.main.bundleURL.absoluteString.contains("/Library/Developer/Xcode")

View File

@@ -36,7 +36,7 @@ struct LaunchAgentController {
}
private func setEnabled(_ enabled: Bool) -> Bool {
let service = SMAppService.loginItem(identifier: Bundle.main.agentBundleID)
let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
do {
if enabled {
try service.register()

View File

@@ -4,7 +4,7 @@ import SecretKit
struct ShellConfigurationController {
let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String
let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String
var shellInstructions: [ShellConfigInstruction] {
[

View File

@@ -1,7 +1,11 @@
import Foundation
extension Bundle {
public var agentBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "Host", with: "SecretAgent"))!}
public var hostBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "SecretAgent", with: "Host"))!}
public static var agentBundleID: String {
Bundle.main.bundleIdentifier!.replacingOccurrences(of: "Host", with: "SecretAgent")
}
public static var hostBundleID: String {
Bundle.main.bundleIdentifier!.replacingOccurrences(of: "SecretAgent", with: "Host")
}
}

View File

@@ -0,0 +1,47 @@
import SwiftUI
struct ConfigurationView: View {
@Binding var visible: Bool
@State var running = true
@State var sshConfig = false
@Environment(\.agentStatusChecker) var agentStatusChecker
var body: some View {
VStack(spacing: 0) {
NewStepView(title: "setup_agent_title", description: "setup_agent_description") {
OnboardingButton("setup_agent_install_button", running) {
Task {
_ = await LaunchAgentController().forceLaunch()
agentStatusChecker.check()
running = agentStatusChecker.running
}
}
}
Divider()
Divider()
NewStepView(title: "setup_ssh_title", description: "setup_ssh_description") {
HStack {
OnboardingButton("setup_ssh_added_manually_button", false) {
sshConfig = true
}
OnboardingButton("Add Automatically", false) {
// let controller = ShellConfigurationController()
// if controller.addToShell(shellInstructions: selectedShellInstruction) {
// }
sshConfig = true
}
}
}
}
.background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
.frame(minWidth: 500, idealWidth: 500, minHeight: 500, idealHeight: 500)
.padding()
.task {
running = agentStatusChecker.running
}
}
}

View File

@@ -6,8 +6,8 @@ struct SecretDetailView<SecretType: Secret>: View {
let secret: SecretType
private let keyWriter = OpenSSHPublicKeyWriter()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID))
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.agentHomePath)
var body: some View {
ScrollView {
Form {
@@ -37,6 +37,14 @@ struct SecretDetailView<SecretType: Secret>: View {
}
extension URL {
static var agentHomePath: String {
URL.homeDirectory.path().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID)
}
}
#if DEBUG
struct SecretDetailView_Previews: PreviewProvider {

View File

@@ -2,6 +2,124 @@ import SwiftUI
struct SetupView: View {
@Binding var visible: Bool
@Binding var setupComplete: Bool
@State var installed = false
@State var updates = false
@State var sshConfig = false
var body: some View {
VStack(spacing: 0) {
NewStepView(title: "setup_agent_title", description: "setup_agent_description") {
OnboardingButton("setup_agent_install_button", installed) {
Task {
await LaunchAgentController().install()
installed = true
}
}
}
Divider()
NewStepView(title: "setup_updates_title", description: "setup_updates_description") {
OnboardingButton("setup_updates_ok", false) {
Task {
updates = true
}
}
}
Divider()
NewStepView(title: "setup_ssh_title", description: "setup_ssh_description") {
HStack {
OnboardingButton("setup_ssh_added_manually_button", false) {
sshConfig = true
}
OnboardingButton("Add Automatically", false) {
// let controller = ShellConfigurationController()
// if controller.addToShell(shellInstructions: selectedShellInstruction) {
// }
sshConfig = true
}
}
}
}
.background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
.frame(minWidth: 500, idealWidth: 500, minHeight: 500, idealHeight: 500)
.padding()
}
}
struct OnboardingButton: View {
let label: LocalizedStringResource
let complete: Bool
let action: () -> Void
init(_ label: LocalizedStringResource, _ complete: Bool, action: @escaping () -> Void) {
self.label = label
self.complete = complete
self.action = action
}
var body: some View {
Button(action: action) {
HStack(spacing: 6) {
Text(label)
if complete {
Image(systemName: "checkmark.circle.fill")
}
}
.padding(.vertical, 2)
}
.disabled(complete)
.styled
}
}
extension View {
@ViewBuilder
var styled: some View {
if #available(macOS 26.0, *) {
buttonStyle(.glassProminent)
} else {
buttonStyle(.borderedProminent)
}
}
}
struct NewStepView<Content: View>: View {
let title: LocalizedStringResource
let description: LocalizedStringResource
let actions: Content
init(title: LocalizedStringResource, description: LocalizedStringResource, actions: () -> Content) {
self.title = title
self.description = description
self.actions = actions()
}
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 6) {
Text(title)
.bold()
Text(description)
}
Spacer(minLength: 20)
actions
}
.padding(20)
}
}
struct OldSetupView: View {
@State var stepIndex = 0
@Binding var visible: Bool
@Binding var setupComplete: Bool
@@ -61,7 +179,7 @@ struct StepView: View {
Circle()
.foregroundColor(.green)
.frame(width: Constants.circleWidth, height: Constants.circleWidth)
Text(.setupStepCompleteSymbol)
Text("setup_step_complete_symbol")
.foregroundColor(.white)
.bold()
} else {
@@ -101,14 +219,14 @@ extension StepView {
struct SetupStepView<Content> : View where Content : View {
let title: LocalizedStringResource
let title: LocalizedStringKey
let image: Image
let bodyText: LocalizedStringResource
let buttonTitle: LocalizedStringResource
let bodyText: LocalizedStringKey
let buttonTitle: LocalizedStringKey
let buttonAction: () -> Void
let content: Content
init(title: LocalizedStringResource, image: Image, bodyText: LocalizedStringResource, buttonTitle: LocalizedStringResource, buttonAction: @escaping () -> Void = {}, @ViewBuilder 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
@@ -145,12 +263,12 @@ struct SecretAgentSetupView: View {
let buttonAction: () -> Void
var body: some View {
SetupStepView(title: .setupAgentTitle,
SetupStepView(title: "setup_agent_title",
image: Image(nsImage: NSApplication.shared.applicationIconImage),
bodyText: .setupAgentDescription,
buttonTitle: .setupAgentInstallButton,
bodyText: "setup_agent_description",
buttonTitle: "setup_agent_install_button",
buttonAction: install) {
Text(.setupAgentActivityMonitorDescription)
Text("setup_agent_activity_monitor_description")
.multilineTextAlignment(.center)
}
}
@@ -172,12 +290,12 @@ struct SSHAgentSetupView: View {
@State private var selectedShellInstruction: ShellConfigInstruction = controller.shellInstructions.first!
var body: some View {
SetupStepView(title: .setupSshTitle,
SetupStepView(title: "setup_ssh_title",
image: Image(systemName: "terminal"),
bodyText: .setupSshDescription,
buttonTitle: .setupSshAddedManuallyButton,
bodyText: "setup_ssh_description",
buttonTitle: "setup_ssh_added_manually_button",
buttonAction: buttonAction) {
Link(.setupThirdPartyFaqLink, destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!)
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)
@@ -185,8 +303,8 @@ struct SSHAgentSetupView: View {
.padding()
}
}.pickerStyle(SegmentedPickerStyle())
CopyableView(title: .setupSshAddToConfigButton(configPath: selectedShellInstruction.shellConfigPath), image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text)
Button(.setupSshAddForMeButton) {
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()
@@ -216,12 +334,12 @@ struct UpdaterExplainerView: View {
let buttonAction: () -> Void
var body: some View {
SetupStepView(title: .setupUpdatesTitle,
SetupStepView(title: "setup_updates_title",
image: Image(systemName: "dot.radiowaves.left.and.right"),
bodyText: .setupUpdatesDescription,
buttonTitle: .setupUpdatesOk,
bodyText: "setup_updates_description",
buttonTitle: "setup_updates_ok",
buttonAction: buttonAction) {
Link(.setupUpdatesReadmore, destination: SetupView.Constants.updaterFAQURL)
Link("setup_updates_readmore", destination: SetupView.Constants.updaterFAQURL)
}
}