fix(ios): improve fleet overview and app identity

- Show stable group health in a fixed 2x2 metric grid
- Add link-chain state to fleet cards and parse hardware/control/SIM/cellular
- Replace the wrapped icon with a full-bleed app icon
- Bump the IPA build number
This commit is contained in:
chick
2026-09-13 00:46:26 +08:00
parent 409345cba3
commit 2ff23d1fb0
23 changed files with 3600 additions and 269 deletions
@@ -26,10 +26,16 @@ public enum ConnectionStatus: String, Equatable, Sendable {
public struct ConnectionResult: Equatable, Sendable {
public let status: ConnectionStatus
public let requestLatencyMilliseconds: Int?
public let checkedAt: Date
public init(status: ConnectionStatus, checkedAt: Date) {
public init(
status: ConnectionStatus,
requestLatencyMilliseconds: Int? = nil,
checkedAt: Date
) {
self.status = status
self.requestLatencyMilliseconds = requestLatencyMilliseconds
self.checkedAt = checkedAt
}
}
@@ -42,21 +48,50 @@ public struct ConnectionProbe: Sendable {
}
public func test(_ instance: Instance) async throws -> ConnectionResult {
let startedAt = DispatchTime.now()
do {
let response = try await client.send(.health, baseURL: instance.origin, cookie: nil)
let latency = Self.elapsedMilliseconds(since: startedAt)
if response.status == 401 || response.status == 403 {
return ConnectionResult(status: .authRequired, checkedAt: Date())
return ConnectionResult(
status: .authRequired,
requestLatencyMilliseconds: latency,
checkedAt: Date()
)
}
return ConnectionResult(status: .online, checkedAt: Date())
return ConnectionResult(
status: .online,
requestLatencyMilliseconds: latency,
checkedAt: Date()
)
} catch {
let latency = Self.elapsedMilliseconds(since: startedAt)
switch error {
case UpstreamError.unauthorized:
return ConnectionResult(status: .authRequired, checkedAt: Date())
return ConnectionResult(
status: .authRequired,
requestLatencyMilliseconds: latency,
checkedAt: Date()
)
case UpstreamError.timeout, is URLError:
return ConnectionResult(status: .offline, checkedAt: Date())
return ConnectionResult(
status: .offline,
requestLatencyMilliseconds: latency,
checkedAt: Date()
)
default:
return ConnectionResult(status: .degraded, checkedAt: Date())
return ConnectionResult(
status: .degraded,
requestLatencyMilliseconds: latency,
checkedAt: Date()
)
}
}
}
private static func elapsedMilliseconds(since startedAt: DispatchTime) -> Int? {
let nanoseconds = DispatchTime.now().uptimeNanoseconds &- startedAt.uptimeNanoseconds
let milliseconds = Double(nanoseconds) / 1_000_000
return milliseconds.isFinite ? Int(milliseconds.rounded()) : nil
}
}
@@ -5,6 +5,10 @@ enum DefaultContainer {
location: documentFileURL("credentials.json")
)
private static let instanceGroups: any InstanceGroupStore = FileInstanceGroupStore(
location: documentFileURL("instance-groups.json")
)
private static let logStore: any LogStore = FileLogStore(
location: documentFileURL("log-center.json")
)
@@ -19,7 +23,13 @@ enum DefaultContainer {
FleetViewModel(
repository: FileInstanceRepository(location: documentFileURL("instances.json")),
probe: ConnectionProbe(client: SimAdminClient()),
credentials: credentials
credentials: credentials,
deviceService: DeviceService(
client: SimAdminClient(),
sessions: InstanceSessionStore(),
credentials: credentials
),
groupStore: instanceGroups
)
}
@@ -64,7 +74,8 @@ enum DefaultContainer {
client: SimAdminClient(),
sessions: InstanceSessionStore(),
credentials: credentials
)
),
probe: ConnectionProbe(client: SimAdminClient())
)
}
}