This commit is contained in:
Max Goedjen 2025-09-02 23:58:21 -07:00
parent c42bfe38a3
commit b21abbb49f
No known key found for this signature in database
3 changed files with 122 additions and 13 deletions

View File

@ -3182,6 +3182,50 @@
} }
} }
}, },
"integrations_configure_using_secret_empty_create" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "You'll need to create a Secret before configuring this action."
}
}
}
},
"integrations_configure_using_secret_header" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configure Using Secret"
}
}
}
},
"integrations_configure_using_secret_no_secret" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No Secret"
}
}
}
},
"integrations_configure_using_secret_secret_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Secret"
}
}
}
},
"integrations_getting_started_multiple_config" : { "integrations_getting_started_multiple_config" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "localizations" : {

View File

@ -47,10 +47,6 @@ public final class PublicKeyFileStoreController: Sendable {
return directory.appending(component: "\(minimalHex).pub").path() return directory.appending(component: "\(minimalHex).pub").path()
} }
public func publicKeyPath(for name: String) -> String {
return directory.appending(component: "\(name).pub").path()
}
/// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory. /// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory.
public var hasAnyCertificates: Bool { public var hasAnyCertificates: Bool {
do { do {

View File

@ -51,7 +51,7 @@ struct FauxToolbarModifier<ToolbarContent: View>: ViewModifier {
var toolbarContent: ToolbarContent var toolbarContent: ToolbarContent
func body(content: Content) -> some View { func body(content: Content) -> some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
content content
Divider() Divider()
HStack { HStack {
@ -69,6 +69,10 @@ struct FauxToolbarModifier<ToolbarContent: View>: ViewModifier {
struct IntegrationsDetailView: View { struct IntegrationsDetailView: View {
@Environment(\.secretStoreList) private var secretStoreList
@State var creating = false
@State var selectedSecret: AnySecret?
@Binding private var selectedInstruction: ConfigurationFileInstructions? @Binding private var selectedInstruction: ConfigurationFileInstructions?
private let instructions = Instructions() private let instructions = Instructions()
@ -115,13 +119,51 @@ struct IntegrationsDetailView: View {
.formStyle(.grouped) .formStyle(.grouped)
case .tool: case .tool:
Form { Form {
if selectedInstruction.requiresSecret {
if secretStoreList.allSecrets.isEmpty {
Section {
Text(.integrationsConfigureUsingSecretEmptyCreate)
if let store = secretStoreList.modifiableStore {
HStack {
Spacer()
Button(.createSecretTitle) {
creating = true
}
.sheet(isPresented: $creating) {
CreateSecretView(store: store) { created in
selectedSecret = created
}
}
}
}
}
} else {
Section {
Picker(.integrationsConfigureUsingSecretSecretTitle, selection: $selectedSecret) {
if selectedSecret == nil {
Text(.integrationsConfigureUsingSecretNoSecret)
.tag(nil as (AnySecret?))
}
ForEach(secretStoreList.allSecrets) { secret in
Text(secret.name)
.tag(secret)
}
}
} header: {
Text(.integrationsConfigureUsingSecretHeader)
}
.onAppear {
selectedSecret = secretStoreList.allSecrets.first
}
}
}
ForEach(selectedInstruction.steps) { stepGroup in ForEach(selectedInstruction.steps) { stepGroup in
Section { Section {
ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path)) ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path))
ForEach(stepGroup.steps, id: \.self.key) { step in ForEach(stepGroup.steps, id: \.self.key) { step in
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) { ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) {
HStack { HStack {
Text(step) Text(placeholdersReplaced(text: String(localized: step)))
.padding(8) .padding(8)
.font(.system(.subheadline, design: .monospaced)) .font(.system(.subheadline, design: .monospaced))
Spacer() Spacer()
@ -180,11 +222,23 @@ struct IntegrationsDetailView: View {
} }
} }
func placeholdersReplaced(text: String) -> String {
guard let selectedSecret else { return text }
let writer = OpenSSHPublicKeyWriter()
let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
return text
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret))
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret))
}
} }
private struct Instructions { private struct Instructions {
private let publicKeyPath = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL).publicKeyPath(for: String(localized: .integrationsPublicKeyPathPlaceholder)) enum Constants {
static let publicKeyPathPlaceholder = "_PUBLIC_KEY_PATH_PLACEHOLDER_"
static let publicKeyPlaceholder = "_PUBLIC_KEY_PLACEHOLDER_"
}
var defaultShell: ConfigurationFileInstructions { var defaultShell: ConfigurationFileInstructions {
zsh zsh
@ -207,14 +261,14 @@ private struct Instructions {
tool: .integrationsToolNameGitSigning, tool: .integrationsToolNameGitSigning,
steps: [ steps: [
.init(path: "~/.gitconfig", steps: [ .init(path: "~/.gitconfig", steps: [
.integrationsGitStepGitconfigDescription(publicKeyPathPlaceholder: publicKeyPath) .integrationsGitStepGitconfigDescription(publicKeyPathPlaceholder: Constants.publicKeyPathPlaceholder)
], ],
note: .integrationsGitStepGitconfigSectionNote note: .integrationsGitStepGitconfigSectionNote
), ),
.init( .init(
path: "~/.gitallowedsigners", path: "~/.gitallowedsigners",
steps: [ steps: [
.integrationsPublicKeyPlaceholder LocalizedStringResource(stringLiteral: Constants.publicKeyPlaceholder)
], ],
note: .integrationsGitStepGitallowedsignersDescription note: .integrationsGitStepGitallowedsignersDescription
), ),
@ -293,26 +347,42 @@ struct ConfigurationFileInstructions: Hashable, Identifiable {
var id: ID var id: ID
var tool: LocalizedStringResource var tool: LocalizedStringResource
var steps: [StepGroup] var steps: [StepGroup]
var requiresSecret: Bool
var website: URL? var website: URL?
init(tool: LocalizedStringResource, configPath: String, configText: LocalizedStringResource, website: URL? = nil, note: LocalizedStringResource? = nil) { init(
tool: LocalizedStringResource,
configPath: String,
configText: LocalizedStringResource,
requiresSecret: Bool = false,
website: URL? = nil,
note: LocalizedStringResource? = nil
) {
self.id = .tool(String(localized: tool)) self.id = .tool(String(localized: tool))
self.tool = tool self.tool = tool
self.steps = [StepGroup(path: configPath, steps: [configText], note: note)] self.steps = [StepGroup(path: configPath, steps: [configText], note: note)]
self.requiresSecret = requiresSecret
self.website = website self.website = website
} }
init(tool: LocalizedStringResource, steps: [StepGroup], website: URL? = nil) { init(
tool: LocalizedStringResource,
steps: [StepGroup],
requiresSecret: Bool = false,
website: URL? = nil
) {
self.id = .tool(String(localized: tool)) self.id = .tool(String(localized: tool))
self.tool = tool self.tool = tool
self.steps = steps self.steps = steps
self.requiresSecret = true
self.website = website self.website = website
} }
init(_ name: LocalizedStringResource, id: ID) { init(_ name: LocalizedStringResource, id: ID) {
self.id = id self.id = id
tool = name tool = name
self.steps = [] steps = []
requiresSecret = false
} }
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
@ -341,7 +411,6 @@ struct ConfigurationFileInstructions: Hashable, Identifiable {
} }
#Preview { #Preview {
IntegrationsView() IntegrationsView()
.frame(height: 500) .frame(height: 500)