summaryrefslogtreecommitdiff
path: root/octosentry/SecurityEventListView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'octosentry/SecurityEventListView.swift')
-rw-r--r--octosentry/SecurityEventListView.swift123
1 files changed, 116 insertions, 7 deletions
diff --git a/octosentry/SecurityEventListView.swift b/octosentry/SecurityEventListView.swift
index d817370..84e6638 100644
--- a/octosentry/SecurityEventListView.swift
+++ b/octosentry/SecurityEventListView.swift
@@ -9,6 +9,7 @@ import SwiftUI
struct SecurityEventListView: View {
var store: SecurityEventStore
var authStore: AuthStore
+ var updateStore: UpdateStore
var isStandaloneWindow: Bool = false
@State private var showingRepoManager = false
@Environment(\.openWindow) private var openWindow
@@ -16,6 +17,9 @@ struct SecurityEventListView: View {
var body: some View {
VStack(alignment: .leading, spacing: 0) {
header
+ if let release = updateStore.availableRelease {
+ UpdateBanner(release: release)
+ }
Divider()
if !authStore.isSignedIn {
SignInView(authStore: authStore)
@@ -30,6 +34,9 @@ struct SecurityEventListView: View {
await store.refresh()
store.startPolling()
}
+ .task {
+ await updateStore.checkForUpdate()
+ }
}
private var header: some View {
@@ -142,6 +149,10 @@ private struct RepoManagerView: View {
var store: SecurityEventStore
var authStore: AuthStore
@State private var newRepoText = ""
+ @State private var isBrowsingRepos = false
+ @State private var availableRepos: [String] = []
+ @State private var isLoadingRepos = false
+ @State private var browseErrorMessage: String?
var body: some View {
VStack(alignment: .leading, spacing: 10) {
@@ -171,13 +182,24 @@ private struct RepoManagerView: View {
Divider()
- HStack {
- TextField("owner/repo", text: $newRepoText)
- .textFieldStyle(.roundedBorder)
- .onSubmit(addRepo)
+ if isBrowsingRepos {
+ browsingContent
+ } else {
+ HStack {
+ TextField("owner/repo", text: $newRepoText)
+ .textFieldStyle(.roundedBorder)
+ .onSubmit(addRepo)
+
+ Button("Add", action: addRepo)
+ .disabled(newRepoText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
+ }
- Button("Add", action: addRepo)
- .disabled(newRepoText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
+ Button(action: startBrowsing) {
+ Label("Browse your repos", systemImage: "list.bullet")
+ .font(.caption)
+ }
+ .buttonStyle(.plain)
+ .foregroundStyle(Color.accentColor)
}
if let errorMessage = store.watchListErrorMessage {
@@ -200,6 +222,75 @@ private struct RepoManagerView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
+ @ViewBuilder
+ private var browsingContent: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ HStack {
+ Text("Your Repositories")
+ .font(.caption.weight(.semibold))
+ Spacer()
+ Button {
+ isBrowsingRepos = false
+ } label: {
+ Image(systemName: "xmark.circle")
+ }
+ .buttonStyle(.plain)
+ }
+
+ if isLoadingRepos {
+ ProgressView()
+ .controlSize(.small)
+ .frame(maxWidth: .infinity)
+ } else if let browseErrorMessage {
+ Text(browseErrorMessage)
+ .font(.caption2)
+ .foregroundStyle(.red)
+ } else {
+ let selectableRepos = availableRepos.filter { !store.watchedRepos.contains($0) }
+ if selectableRepos.isEmpty {
+ Text("All visible repos are already watched.")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ } else {
+ ScrollView {
+ LazyVStack(alignment: .leading, spacing: 4) {
+ ForEach(selectableRepos, id: \.self) { repo in
+ Button {
+ Task { await store.addRepo(repo) }
+ isBrowsingRepos = false
+ } label: {
+ Text(repo)
+ .font(.callout)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .buttonStyle(.plain)
+ }
+ }
+ }
+ .frame(maxHeight: 160)
+ }
+ }
+ }
+ }
+
+ private func startBrowsing() {
+ guard authStore.hasRepoAccess else {
+ authStore.requestRepoAccess()
+ return
+ }
+ isBrowsingRepos = true
+ isLoadingRepos = true
+ browseErrorMessage = nil
+ Task {
+ do {
+ availableRepos = try await store.fetchAccessibleRepos()
+ } catch {
+ browseErrorMessage = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription
+ }
+ isLoadingRepos = false
+ }
+ }
+
private func addRepo() {
let text = newRepoText
newRepoText = ""
@@ -207,6 +298,24 @@ private struct RepoManagerView: View {
}
}
+private struct UpdateBanner: View {
+ let release: UpdateChecker.LatestRelease
+
+ var body: some View {
+ Button {
+ NSWorkspace.shared.open(release.htmlURL)
+ } label: {
+ Label("Update available: \(release.version)", systemImage: "arrow.down.circle.fill")
+ .font(.caption)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .buttonStyle(.plain)
+ .foregroundStyle(.blue)
+ .padding(8)
+ .background(.blue.opacity(0.1))
+ }
+}
+
private struct ErrorBanner: View {
let messages: [String]
@@ -262,6 +371,6 @@ private struct StatusView: View {
}
#Preview {
- SecurityEventListView(store: SecurityEventStore(), authStore: AuthStore())
+ SecurityEventListView(store: SecurityEventStore(), authStore: AuthStore(), updateStore: UpdateStore())
.frame(width: 380, height: 420)
}