PONGo_ios/OLTDetailView.swift
Jon Vanvik 203465913c Apply code-review fixes: identity/pagination, load races, TLS pin, leaks
Addresses a multi-agent code review (26 confirmed findings + a URLSession
leak) across correctness, concurrency, security, and cleanup:

- MongoDocument.id: deterministic fallback (was UUID() on every access) to stop
  SwiftUI List/ForEach identity churn; getAll pagination drives its cursor off
  the real _id via documentId, so a missing _id stops paging instead of looping
  on a fabricated cursor.
- View models: per-load generation guard (+ captured-tab guard on ONU detail)
  so a cancelled or overlapping load can't commit stale results or land data on
  the wrong tab.
- JSONValue.intValue: guard Int(d) against NaN/inf/out-of-range (crash).
- ServerTrustEvaluator: hash the real SubjectPublicKeyInfo (prepend the
  algorithm-specific ASN.1 SPKI header) so a standard openssl-captured
  public-key pin actually matches; unknown key types fail closed.
- AppEnvironment.configure: clear stale cookies + Keychain creds on host change.
- FirmwareFile.isCompatible: empty metadata no longer passes as compatible-for-all.
- APIClient: invalidate URLSession on deinit (per-configure/probe leak).
- Dashboard best-effort sections keep last-good on transient failure; unified
  optical thresholds; LoginView prefill-once; populatedBuckets unique ForEach id;
  Format rounding rollover; dead-code/docstring/dedup cleanups.

Verification pending on the Mac (no Swift toolchain on the Linux dev box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:36:49 +02:00

142 lines
5.8 KiB
Swift

import SwiftUI
struct OLTDetailView: View {
let connection: MCMSConnection
@State private var vm: OLTDetailViewModel
@State private var showResetConfirm = false
private let title: String
init(connection: MCMSConnection, oltId: String, title: String? = nil) {
self.connection = connection
self.title = title ?? oltId
_vm = State(initialValue: OLTDetailViewModel(connection: connection, oltId: oltId))
}
var body: some View {
Group {
switch vm.phase {
case .idle:
LoadingView()
case .loading where vm.olt == nil:
LoadingView()
case .failed(let msg):
ErrorStateView(message: msg) { Task { await vm.load() } }
default:
content
}
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Button(role: .destructive) { showResetConfirm = true } label: {
Label("Reset OLT", systemImage: "arrow.clockwise")
}
} label: {
Image(systemName: "ellipsis.circle")
}
.disabled(vm.isPerformingAction)
}
}
.sheet(isPresented: $showResetConfirm) {
ResetConfirmSheet(deviceKind: "OLT", deviceName: title) {
Task { await vm.reset() }
}
}
.alert("Operation", isPresented: Binding(
get: { vm.actionMessage != nil },
set: { if !$0 { vm.actionMessage = nil } }
)) {
Button("OK") { vm.actionMessage = nil }
} message: {
Text(vm.actionMessage ?? "")
}
.task { if vm.phase == .idle { await vm.load() } }
.refreshable { await vm.load() }
}
@ViewBuilder
private var content: some View {
if let olt = vm.olt {
List {
Section("Status") {
LabeledContent("ONUs", value: onuSummary(olt))
if let model = olt.model { LabeledContent("Model", value: model) }
if let mode = olt.ponMode { LabeledContent("PON mode", value: mode) }
if let fw = olt.firmwareVersion { LabeledContent("Firmware", value: fw) }
if let online = olt.onlineTime {
if let up = MongoTimestamp.duration(from: online, to: olt.lastUpdate) {
LabeledContent("Uptime", value: up)
}
LabeledContent("Online since", value: MongoTimestamp.displayString(online) ?? online)
}
if let updated = MongoTimestamp.displayString(olt.lastUpdate) {
LabeledContent("Last update", value: updated)
}
}
if olt.rxRateBps != nil || olt.txRateBps != nil || olt.txUtilPercent != nil {
Section("PON traffic") {
LabeledContent("Downstream",
value: "\(Format.bps(olt.txRateBps)) · \(Format.percent(olt.txUtilPercent)) util")
LabeledContent("Upstream",
value: "\(Format.bps(olt.rxRateBps)) · \(Format.percent(olt.rxUtilPercent)) util")
}
}
if olt.asicTempC != nil || olt.voltage != nil {
Section("Environment") {
if let temp = olt.asicTempC { LabeledContent("ASIC temp", value: "\(temp) °C") }
if let volts = olt.voltage {
LabeledContent("Voltage", value: "\(volts.formatted(.number.precision(.fractionLength(2)))) V")
}
}
}
if let switchName = olt.switchName {
Section("Uplink") {
LabeledContent("Switch", value: switchName)
if let port = olt.switchPort { LabeledContent("Port", value: port) }
if let addr = olt.switchAddress { LabeledContent("Address", value: addr) }
}
}
if olt.populatedBuckets.isEmpty {
Section { Text("No ONUs registered on this OLT.").foregroundStyle(.secondary) }
} else {
ForEach(olt.populatedBuckets, id: \.name) { bucket in
Section {
ForEach(bucket.ids, id: \.self) { onuId in
NavigationLink {
ONUDetailView(connection: connection, onuId: onuId)
} label: {
Text(onuId)
}
}
} header: {
HStack {
StateBadge(text: bucket.state.label, color: bucket.state.color)
Spacer()
Text("\(bucket.ids.count)")
.font(.caption.monospacedDigit())
.foregroundStyle(.secondary)
}
}
}
}
Section("Raw document") {
JSONTreeView(value: olt.raw)
}
}
}
}
private func onuSummary(_ olt: OLTStateDoc) -> String {
let down = olt.totalONUs - olt.onlineONUs
var summary = "\(olt.totalONUs) · \(olt.onlineONUs) online"
if down > 0 { summary += " · \(down) down" }
return summary
}
}