gitbay/Admin/AdminViewModels.swift
106 lines · 3054 bytes
1import Foundation
2import Observation
3
4/// Whether the signed-in account is an instance admin, from `whoami`. A
5/// failed read counts as not: the account menu then leaves Admin out.
6enum AdminCheck {
7
8 nonisolated private struct Who: Decodable, Sendable {
9 let admin: Bool?
10 }
11
12 static func isAdmin(_ client: GitbayClient) async -> Bool {
13 ((try? await client.read(["whoami"], as: Who.self))?.admin) ?? false
14 }
15}
16
17/// The admin screen: `dashboard`'s server build and worker queues, the
18/// read the web's /admin page makes.
19@Observable
20@MainActor
21final class AdminOverviewViewModel {
22
23 nonisolated struct Overview: Sendable, Hashable {
24 let commit: String?
25 let sections: [QueueSection]
26 }
27
28 private(set) var state: LoadState<Overview> = .loading
29
30 private let client: GitbayClient
31
32 init(client: GitbayClient) {
33 self.client = client
34 }
35
36 func load() async {
37 do {
38 let dashboard = try await client.read(["dashboard"], as: AdminDashboard.self)
39 guard let queues = dashboard.queues else {
40 state = .empty("Only instance admins can see the worker queues.")
41 return
42 }
43 state = .loaded(Overview(commit: dashboard.server?.commit, sections: queues.sections()))
44 } catch {
45 state = .from(error)
46 }
47 }
48}
49
50/// `admin user list`, narrowed by the web's five filters, and the four
51/// writes /admin/users makes per row.
52@Observable
53@MainActor
54final class AdminUsersViewModel {
55
56 let list: PagedListModel<AdminUser>
57 var filter = AdminUserFilter.all {
58 didSet {
59 guard filter != oldValue else { return }
60 list.argv = ["admin", "user", "list"] + filter.flags()
61 reloadTask?.cancel()
62 reloadTask = Task { await list.reload() }
63 }
64 }
65 private var reloadTask: Task<Void, Never>?
66 private(set) var actionError: String?
67 private(set) var working = false
68
69 private let client: GitbayClient
70
71 init(client: GitbayClient) {
72 self.client = client
73 list = PagedListModel(
74 client: client,
75 argv: ["admin", "user", "list"],
76 emptyMessage: "No account matches"
77 )
78 }
79
80 func load() async {
81 reloadTask?.cancel()
82 await list.reload()
83 }
84
85 /// The list's `.task`: reads only while nothing is shown yet.
86 func loadIfNeeded() async {
87 guard list.needsLoad else { return }
88 await load()
89 }
90
91 func perform(_ action: AdminAction, on username: String) async {
92 working = true
93 actionError = nil
94 defer { working = false }
95 do {
96 try await client.run(action.argv(username))
97 } catch let error as GitbayError {
98 actionError = error.userFacingMessage
99 } catch {
100 actionError = GitbayError.transport(error).userFacingMessage
101 }
102 reloadTask?.cancel()
103 reloadTask = Task { await list.reload() }
104 await reloadTask?.value
105 }
106}