import Foundation import Combine // MARK: - Models struct DashboardMetrics: Codable { let activeAgents: Int let tasksCompleted: Int let tasksFailed: Int let avgResponseTimeMs: Double let uptimePercent: Double let alertCount: Int static let empty = DashboardMetrics( activeAgents: 0, tasksCompleted: 0, tasksFailed: 0, avgResponseTimeMs: 0, uptimePercent: 0, alertCount: 0 ) } struct AuditEntry: Identifiable, Codable { let id: UUID let timestamp: Date let actor: String let action: String let resource: String let outcome: AuditOutcome let detail: String? enum AuditOutcome: String, Codable { case success, failure, warning } } // MARK: - ViewModel @MainActor final class DashboardViewModel: ObservableObject { @Published private(set) var metrics: DashboardMetrics = .empty @Published private(set) var recentAudit: [AuditEntry] = [] @Published private(set) var isLoading: Bool = false @Published private(set) var error: Error? private var pollingTask: Task? private let pollingInterval: TimeInterval private let apiService: APIServiceProtocol init(apiService: APIServiceProtocol = APIService.shared, pollingInterval: TimeInterval = 30) { self.apiService = apiService self.pollingInterval = pollingInterval } deinit { pollingTask?.cancel() } // MARK: - Public func loadDashboard() async { guard !isLoading else { return } isLoading = true error = nil do { async let fetchedMetrics = apiService.fetchMetrics() async let fetchedAudit = apiService.fetchRecentAudit(limit: 20) let (m, a) = try await (fetchedMetrics, fetchedAudit) metrics = m recentAudit = a } catch { self.error = error } isLoading = false } func startPolling() { pollingTask?.cancel() pollingTask = Task { [weak self] in guard let self else { return } await self.loadDashboard() while !Task.isCancelled { try? await Task.sleep(for: .seconds(self.pollingInterval)) guard !Task.isCancelled else { break } await self.loadDashboard() } } } func stopPolling() { pollingTask?.cancel() pollingTask = nil } } // MARK: - API protocol protocol APIServiceProtocol { func fetchMetrics() async throws -> DashboardMetrics func fetchRecentAudit(limit: Int) async throws -> [AuditEntry] }