Files
secretive/Sources/Secretive/Views/Secrets/StoreListView.swift
Max Goedjen b337b24641 Certificate UI/Import (#798)
* Sketching out.

* WIP

* WIP

* Dump

* Apply stash

* Merge + WIP

* UI

* More WIP

* Agent config

* UI cleanup

* Restore dirty files

* XPC

* Edit/delete

* UI fixes

* Cleanup

* Change id for OpenSSHCertificate to hex of md5

* Fix runtime warning for confirmation dialog

* Mark strings as reviewed

* Cleanup

* Fix agent tests
2026-05-06 08:03:21 +00:00

98 lines
3.6 KiB
Swift

import SwiftUI
import SecretKit
import SSHProtocolKit
struct StoreListView: View {
enum StoreListSelection: Hashable {
case secret(AnySecret)
case certificate(OpenSSHCertificate)
}
@Binding var selection: StoreListSelection?
@Environment(\.secretStoreList) private var storeList
@Environment(\.certificateStore) private var certificateStore
private func secretDeleted(secret: AnySecret) {
selection = nextDefaultSecret.map(StoreListSelection.secret)
}
private func secretRenamed(secret: AnySecret) {
// Pull new version from store, so we get all updated attributes
selection = nil
selection = storeList.allSecrets.first(where: { $0.id == secret.id }).map(StoreListSelection.secret)
}
var body: some View {
NavigationSplitView {
List(selection: $selection) {
ForEach(storeList.stores) { store in
if store.isAvailable {
Section(header: Text(store.name)) {
ForEach(store.secrets) { secret in
SecretListItemView(
store: store,
secret: secret,
deletedSecret: secretDeleted,
renamedSecret: secretRenamed,
)
.tag(StoreListSelection.secret(secret))
}
}
}
}
if !certificateStore.certificates.isEmpty {
Section("Certificates") {
ForEach(certificateStore.certificates) { certificate in
CertificateListItemView(
certificate: certificate,
deletedCertificate: { _ in },
renamedCertificate: { _ in }
)
.tag(StoreListSelection.certificate(certificate))
}
}
}
}
} detail: {
switch selection {
case .secret(let secret):
SecretDetailView(secret: secret, certificates: certificateStore.certificates(for: secret)) {
selection = .certificate($0)
}
case .certificate(let certificate):
CertificateDetailView(certificate: certificate)
case nil:
if let nextDefaultSecret {
// This just means onAppear hasn't executed yet.
// Do this to avoid a blip.
SecretDetailView(secret: nextDefaultSecret, certificates: certificateStore.certificates(for: nextDefaultSecret)) {
selection = .certificate($0)
}
} else {
if let modifiable = storeList.modifiableStore, modifiable.isAvailable {
EmptyStoreView(store: modifiable)
} else {
EmptyStoreView(store: storeList.stores.first(where: \.isAvailable))
}
}
}
}
.navigationSplitViewStyle(.balanced)
.onAppear {
selection = nextDefaultSecret.map(StoreListSelection.secret)
}
.frame(minWidth: 100, idealWidth: 240)
}
}
extension StoreListView {
private var nextDefaultSecret: AnySecret? {
return storeList.allSecrets.first
}
}