import Foundation import Observation /// Whether the signed-in account is an instance admin, from `whoami`. A /// failed read counts as not: the account menu then leaves Admin out. enum AdminCheck { nonisolated private struct Who: Decodable, Sendable { let admin: Bool? } static func isAdmin(_ client: GitbayClient) async -> Bool { ((try? await client.read(["whoami"], as: Who.self))?.admin) ?? false } } /// The admin screen: `dashboard`'s server build and worker queues, the /// read the web's /admin page makes. @Observable @MainActor final class AdminOverviewViewModel { nonisolated struct Overview: Sendable, Hashable { let commit: String? let sections: [QueueSection] } private(set) var state: LoadState = .loading private let client: GitbayClient init(client: GitbayClient) { self.client = client } func load() async { do { let dashboard = try await client.read(["dashboard"], as: AdminDashboard.self) guard let queues = dashboard.queues else { state = .empty("Only instance admins can see the worker queues.") return } state = .loaded(Overview(commit: dashboard.server?.commit, sections: queues.sections())) } catch { state = .from(error) } } } /// `admin user list`, narrowed by the web's five filters, and the four /// writes /admin/users makes per row. @Observable @MainActor final class AdminUsersViewModel { let list: PagedListModel var filter = AdminUserFilter.all { didSet { guard filter != oldValue else { return } list.argv = ["admin", "user", "list"] + filter.flags() reloadTask?.cancel() reloadTask = Task { await list.reload() } } } private var reloadTask: Task? private(set) var actionError: String? private(set) var working = false private let client: GitbayClient init(client: GitbayClient) { self.client = client list = PagedListModel( client: client, argv: ["admin", "user", "list"], emptyMessage: "No account matches" ) } func load() async { reloadTask?.cancel() await list.reload() } /// The list's `.task`: reads only while nothing is shown yet. func loadIfNeeded() async { guard list.needsLoad else { return } await load() } func perform(_ action: AdminAction, on username: String) async { working = true actionError = nil defer { working = false } do { try await client.run(action.argv(username)) } catch let error as GitbayError { actionError = error.userFacingMessage } catch { actionError = GitbayError.transport(error).userFacingMessage } reloadTask?.cancel() reloadTask = Task { await list.reload() } await reloadTask?.value } }