summaryrefslogtreecommitdiff
path: root/Hutch/Views/SystemStatus
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-12 00:15:13 -0500
committerChristian Cleberg <[email protected]>2026-04-12 00:15:13 -0500
commitcb1ea5ea4f163d87053285d3fb7999b12a3558b9 (patch)
tree84b562d12e642ff494194387b1b4e1d15b065eb0 /Hutch/Views/SystemStatus
parent1c5a417277d986ee6fb7dc9dff0332338bdf6f3e (diff)
downloadhutch-cb1ea5ea4f163d87053285d3fb7999b12a3558b9.tar.gz
hutch-cb1ea5ea4f163d87053285d3fb7999b12a3558b9.tar.bz2
hutch-cb1ea5ea4f163d87053285d3fb7999b12a3558b9.zip
harden system status and app reliabilityv2.13.1
Diffstat (limited to 'Hutch/Views/SystemStatus')
-rw-r--r--Hutch/Views/SystemStatus/SystemStatusSummaryRow.swift119
-rw-r--r--Hutch/Views/SystemStatus/SystemStatusView.swift8
-rw-r--r--Hutch/Views/SystemStatus/SystemStatusViewModel.swift34
3 files changed, 157 insertions, 4 deletions
diff --git a/Hutch/Views/SystemStatus/SystemStatusSummaryRow.swift b/Hutch/Views/SystemStatus/SystemStatusSummaryRow.swift
new file mode 100644
index 0000000..5d94dad
--- /dev/null
+++ b/Hutch/Views/SystemStatus/SystemStatusSummaryRow.swift
@@ -0,0 +1,119 @@
+import SwiftUI
+
+struct SystemStatusSummaryRow: View {
+ let title: String
+ let snapshot: SystemStatusSnapshot?
+ let isLoading: Bool
+ let errorMessage: String?
+ let isShowingStaleData: Bool
+
+ init(
+ title: String = "System Status",
+ snapshot: SystemStatusSnapshot?,
+ isLoading: Bool = false,
+ errorMessage: String? = nil,
+ isShowingStaleData: Bool = false
+ ) {
+ self.title = title
+ self.snapshot = snapshot
+ self.isLoading = isLoading
+ self.errorMessage = errorMessage
+ self.isShowingStaleData = isShowingStaleData
+ }
+
+ var body: some View {
+ HStack(spacing: 12) {
+ icon
+ .frame(width: 20)
+
+ VStack(alignment: .leading, spacing: 3) {
+ Text(title)
+ .font(.subheadline.weight(.semibold))
+ .foregroundStyle(.primary)
+
+ Text(primaryMessage)
+ .font(.caption)
+ .foregroundStyle(primaryMessageColor)
+ .lineLimit(2)
+
+ if let metadataMessage {
+ Text(metadataMessage)
+ .font(.caption2)
+ .foregroundStyle(.tertiary)
+ .lineLimit(1)
+ }
+ }
+
+ Spacer(minLength: 8)
+ }
+ .padding(.vertical, 4)
+ .contentShape(Rectangle())
+ }
+
+ @ViewBuilder
+ private var icon: some View {
+ if isLoading && snapshot == nil {
+ ProgressView()
+ .controlSize(.small)
+ } else {
+ Image(systemName: iconName)
+ .foregroundStyle(iconColor)
+ }
+ }
+
+ private var primaryMessage: String {
+ if let snapshot {
+ return snapshot.hasDisruption ? snapshot.bannerSummary : snapshot.overallStatusText
+ }
+ if let errorMessage, !errorMessage.isEmpty {
+ return errorMessage
+ }
+ if isLoading {
+ return "Loading system status…"
+ }
+ return "System status is unavailable right now."
+ }
+
+ private var metadataMessage: String? {
+ if let snapshot {
+ if isShowingStaleData {
+ return "Updated \(snapshot.lastUpdated.relativeDescription) • Showing saved data"
+ }
+ return "Updated \(snapshot.lastUpdated.relativeDescription)"
+ }
+ if errorMessage != nil {
+ return "Open System Status to retry."
+ }
+ return nil
+ }
+
+ private var iconName: String {
+ if let snapshot {
+ return snapshot.hasDisruption ? "exclamationmark.triangle.fill" : "checkmark.circle.fill"
+ }
+ if errorMessage != nil {
+ return "exclamationmark.triangle"
+ }
+ return "server.rack"
+ }
+
+ private var iconColor: Color {
+ if let snapshot {
+ return snapshot.hasDisruption ? .orange : .green
+ }
+ if errorMessage != nil {
+ return .secondary
+ }
+ return .secondary
+ }
+
+ private var primaryMessageColor: Color {
+ if snapshot != nil {
+ return .secondary
+ }
+ if errorMessage != nil {
+ return .secondary
+ }
+ return .secondary
+ }
+}
diff --git a/Hutch/Views/SystemStatus/SystemStatusView.swift b/Hutch/Views/SystemStatus/SystemStatusView.swift
index 053210e..08a46eb 100644
--- a/Hutch/Views/SystemStatus/SystemStatusView.swift
+++ b/Hutch/Views/SystemStatus/SystemStatusView.swift
@@ -31,6 +31,14 @@ struct SystemStatusView: View {
@ViewBuilder
private func content(_ viewModel: SystemStatusViewModel) -> some View {
List {
+ if viewModel.isShowingStaleData, let staleDataMessage = viewModel.staleDataMessage {
+ Section {
+ Label(staleDataMessage, systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+ }
+ }
+
if let snapshot = viewModel.snapshot {
summarySection(snapshot)
servicesSection(snapshot)
diff --git a/Hutch/Views/SystemStatus/SystemStatusViewModel.swift b/Hutch/Views/SystemStatus/SystemStatusViewModel.swift
index 646d7ca..7700208 100644
--- a/Hutch/Views/SystemStatus/SystemStatusViewModel.swift
+++ b/Hutch/Views/SystemStatus/SystemStatusViewModel.swift
@@ -8,6 +8,8 @@ final class SystemStatusViewModel {
private(set) var snapshot: SystemStatusSnapshot?
private(set) var recentIncidents: [StatusIncident] = []
private(set) var isLoading = false
+ private(set) var isShowingStaleData = false
+ private(set) var staleDataMessage: String?
var errorMessage: String?
init(repository: SystemStatusRepository) {
@@ -25,12 +27,24 @@ final class SystemStatusViewModel {
defer { isLoading = false }
errorMessage = nil
+ staleDataMessage = nil
+ isShowingStaleData = false
- async let snapshotTask = repository.snapshot(forceRefresh: forceRefresh)
- async let incidentsTask = repository.recentIncidents(forceRefresh: forceRefresh)
+ async let snapshotTask = repository.snapshotResult(forceRefresh: forceRefresh)
+ async let incidentsTask = repository.recentIncidentsResult(forceRefresh: forceRefresh)
+
+ var refreshWarnings: [String] = []
do {
- snapshot = try await snapshotTask
+ let result = try await snapshotTask
+ snapshot = result.value
+ if result.isStale {
+ isShowingStaleData = true
+ staleDataMessage = "Showing the last saved system status snapshot."
+ if let warning = result.refreshErrorMessage {
+ refreshWarnings.append(warning)
+ }
+ }
} catch {
if snapshot == nil {
errorMessage = error.userFacingMessage
@@ -38,11 +52,23 @@ final class SystemStatusViewModel {
}
do {
- recentIncidents = try await incidentsTask
+ let result = try await incidentsTask
+ recentIncidents = result.value
+ if result.isStale {
+ isShowingStaleData = true
+ staleDataMessage = staleDataMessage ?? "Showing the last saved incident history."
+ if let warning = result.refreshErrorMessage {
+ refreshWarnings.append(warning)
+ }
+ }
} catch {
if errorMessage == nil && recentIncidents.isEmpty {
errorMessage = error.userFacingMessage
}
}
+
+ if hasContent, let firstWarning = refreshWarnings.first {
+ errorMessage = "Showing cached system status. \(firstWarning)"
+ }
}
}