Watch: status, environment and notifications screens
A gear on the feed opens a status overview that leads with what is wrong: offline hosts, hosts on a fallback route, tokens expiring within 7 days, and push problems, with healthy hosts folded into one row. Each host opens its route ladder with Reconnect. The notifications page shows registration, the last push, Send test and Unpair. Demo mode gains a Laptop on its VPN fallback, and snapshot tests render the three screens.
This commit is contained in:
1 parent
9707daf1fe
commit
7ca0362745
17 files changed
+1063
-14
No files matched your search
@@ -11,6 +11,7 @@ actor DemoGateway: GatewayAPI {
|
||||
private var messages: [String: [Message]]
|
||||
private let hosts: [HostSummary]
|
||||
private let projectHosts: [ProjectsResponse.Host]
|
||||
private var statusResponse: StatusResponse
|
||||
private var nextId = 1
|
||||
|
||||
init(now: Date = .now) {
|
||||
@@ -19,6 +20,7 @@ actor DemoGateway: GatewayAPI {
|
||||
messages = fixtures.messages
|
||||
hosts = fixtures.hosts
|
||||
projectHosts = fixtures.projectHosts
|
||||
statusResponse = fixtures.status
|
||||
}
|
||||
|
||||
func feed() async throws -> Feed {
|
||||
@@ -93,6 +95,20 @@ actor DemoGateway: GatewayAPI {
|
||||
|
||||
func registerPush(_ registration: PushRegistration) async throws {}
|
||||
|
||||
func status() async throws -> StatusResponse {
|
||||
statusResponse
|
||||
}
|
||||
|
||||
func reconnect(hostId: String) async throws {}
|
||||
|
||||
func testPush() async throws -> PushResult {
|
||||
let result = PushResult(ok: true, at: .now, status: 200, reason: nil)
|
||||
statusResponse.gateway.lastPush = result
|
||||
return result
|
||||
}
|
||||
|
||||
func unpair() async throws {}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func item(_ hostId: String, _ threadId: String) -> FeedItem? {
|
||||
@@ -122,6 +138,7 @@ struct DemoFixtures {
|
||||
let messages: [String: [Message]]
|
||||
let hosts: [HostSummary]
|
||||
let projectHosts: [ProjectsResponse.Host]
|
||||
let status: StatusResponse
|
||||
|
||||
init(now: Date) {
|
||||
func ago(_ minutes: Double) -> Date { now.addingTimeInterval(-minutes * 60) }
|
||||
@@ -129,6 +146,7 @@ struct DemoFixtures {
|
||||
hosts = [
|
||||
HostSummary(id: "mac-mini", label: "Mac mini", status: .online, error: nil),
|
||||
HostSummary(id: "server-gib", label: "server-gib", status: .online, error: nil),
|
||||
HostSummary(id: "laptop", label: "Laptop", status: .online, error: nil),
|
||||
HostSummary(id: "littleman", label: "LittleMan", status: .offline, error: "Connection refused"),
|
||||
]
|
||||
|
||||
@@ -251,6 +269,42 @@ struct DemoFixtures {
|
||||
id: "littleman", label: "LittleMan", status: .offline, error: "Connection refused",
|
||||
projects: [.init(id: "p-site", title: "gbrown.org", model: "claude-opus-5-5")]),
|
||||
]
|
||||
|
||||
// Mac mini and server-gib on their first route, Laptop on its VPN fallback, LittleMan offline.
|
||||
func route(_ url: String, _ kind: RouteKind, _ result: HostRoute.Result, _ error: String? = nil, _ minutes: Double? = nil) -> HostRoute {
|
||||
HostRoute(url: url, kind: kind, result: result, error: error, checkedAt: minutes.map(ago))
|
||||
}
|
||||
func days(_ count: Double) -> Date { now.addingTimeInterval(count * 86_400) }
|
||||
status = StatusResponse(
|
||||
hosts: [
|
||||
HostStatusDetail(
|
||||
id: "mac-mini", label: "Mac mini", status: .online, error: nil, activeUrl: "http://mac.gib:3773",
|
||||
routes: [route("http://mac.gib:3773", .direct, .ok, nil, 95), route("https://t3mac.gbrown.org", .public, .untried)],
|
||||
onlineSince: ago(95), lastOnlineAt: nil, nextRetryAt: nil, tokenExpiresAt: days(29), serverVersion: "0.1.5"),
|
||||
HostStatusDetail(
|
||||
id: "server-gib", label: "server-gib", status: .online, error: nil, activeUrl: "http://server.gib:3773",
|
||||
routes: [route("http://server.gib:3773", .direct, .ok, nil, 300), route("https://t3server.gbrown.org", .public, .untried)],
|
||||
onlineSince: ago(300), lastOnlineAt: nil, nextRetryAt: nil, tokenExpiresAt: days(5.5), serverVersion: "0.1.5"),
|
||||
HostStatusDetail(
|
||||
id: "laptop", label: "Laptop", status: .online, error: nil, activeUrl: "http://laptopvpn.gib:3773",
|
||||
routes: [
|
||||
route("http://laptop.gib:3773", .direct, .failed, "no answer in 5 s", 29),
|
||||
route("http://laptopvpn.gib:3773", .vpn, .ok, nil, 29),
|
||||
route("https://t3laptop.gbrown.org", .public, .untried),
|
||||
route("https://t3laptopvpn.gbrown.org", .public, .untried),
|
||||
],
|
||||
onlineSince: ago(29), lastOnlineAt: nil, nextRetryAt: nil, tokenExpiresAt: days(29.5), serverVersion: "0.1.5"),
|
||||
HostStatusDetail(
|
||||
id: "littleman", label: "LittleMan", status: .offline, error: "Connection refused", activeUrl: nil,
|
||||
routes: [
|
||||
route("http://littleman.gib:3773", .direct, .failed, "refused", 0.3),
|
||||
route("https://t3littleman.gbrown.org", .public, .failed, "502", 0.3),
|
||||
],
|
||||
onlineSince: nil, lastOnlineAt: ago(4), nextRetryAt: now.addingTimeInterval(40),
|
||||
tokenExpiresAt: days(29.5), serverVersion: "0.1.4"),
|
||||
],
|
||||
gateway: .init(startedAt: ago(600), pushConfigured: true, lastPush: PushResult(ok: true, at: ago(2), status: 200, reason: nil)),
|
||||
device: .init(name: "Apple Watch", pairedAt: days(-3), pushRegistered: true, apnsEnvironment: .production))
|
||||
}
|
||||
|
||||
/// A feed for previews, without going through the actor.
|
||||
|
||||
Reference in new issue
Block a user