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>
142 lines
5.8 KiB
Swift
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
|
|
}
|
|
}
|