initial commit
This commit is contained in:
commit
8de962eeb8
44 changed files with 4843 additions and 0 deletions
90
FirmwareUpdateSheet.swift
Normal file
90
FirmwareUpdateSheet.swift
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import SwiftUI
|
||||
|
||||
/// Firmware picker for a single ONU. Lists images compatible with the ONU's
|
||||
/// model (with a "show all" escape hatch), and gates the actual push behind a
|
||||
/// service-affecting confirmation. The push stages the image in the ONU's
|
||||
/// **standby** bank, so the running image is untouched until the next reboot.
|
||||
struct FirmwareUpdateSheet: View {
|
||||
let vm: ONUDetailViewModel
|
||||
let state: ONUStateDoc
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var showAll = false
|
||||
@State private var pending: FirmwareFile?
|
||||
|
||||
private var compatible: [FirmwareFile] {
|
||||
vm.firmwareFiles.filter { $0.isCompatible(vendor: state.vendor, equipmentId: state.equipmentId) }
|
||||
}
|
||||
private var visible: [FirmwareFile] { showAll ? vm.firmwareFiles : compatible }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
LabeledContent("Current", value: state.fwVersion ?? "—")
|
||||
if let eq = state.equipmentId { LabeledContent("Model", value: eq) }
|
||||
}
|
||||
|
||||
if let error = vm.firmwareLoadError {
|
||||
Section { Text(error).foregroundStyle(.red).font(.footnote) }
|
||||
} else if vm.firmwareFiles.isEmpty {
|
||||
Section {
|
||||
HStack(spacing: 10) { ProgressView(); Text("Loading firmware…").foregroundStyle(.secondary) }
|
||||
}
|
||||
} else {
|
||||
Section {
|
||||
ForEach(visible) { file in
|
||||
Button { pending = file } label: { fileRow(file) }
|
||||
.disabled(vm.isPerformingAction)
|
||||
}
|
||||
} header: {
|
||||
Text(showAll ? "All firmware" : "Compatible firmware")
|
||||
} footer: {
|
||||
if !showAll && compatible.isEmpty {
|
||||
Text("No image matches this ONU's model. Turn on “Show all” to choose one manually — make sure it's compatible.")
|
||||
}
|
||||
}
|
||||
if vm.firmwareFiles.count > compatible.count {
|
||||
Section { Toggle("Show all firmware", isOn: $showAll) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Update firmware")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } }
|
||||
}
|
||||
.task { if vm.firmwareFiles.isEmpty { await vm.loadFirmware() } }
|
||||
.confirmationDialog(
|
||||
"Stage firmware?",
|
||||
isPresented: Binding(get: { pending != nil }, set: { if !$0 { pending = nil } }),
|
||||
titleVisibility: .visible,
|
||||
presenting: pending
|
||||
) { file in
|
||||
Button("Download \(file.version ?? "firmware")", role: .destructive) {
|
||||
let selected = file
|
||||
dismiss()
|
||||
Task { await vm.upgrade(to: selected) }
|
||||
}
|
||||
Button("Cancel", role: .cancel) { pending = nil }
|
||||
} message: { file in
|
||||
Text("Stage \(file.version ?? "this image") on \(state.id) in the standby bank. It becomes active after the ONU's next reboot — this is service-affecting.")
|
||||
}
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
}
|
||||
|
||||
private func fileRow(_ file: FirmwareFile) -> some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(file.version ?? file.filename ?? "Unknown").font(.body.weight(.medium))
|
||||
Text(subtitle(file)).font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private func subtitle(_ file: FirmwareFile) -> String {
|
||||
var parts: [String] = []
|
||||
if let model = file.models.first { parts.append(model) }
|
||||
if let size = file.sizeBytes { parts.append(Format.bytes(size)) }
|
||||
return parts.joined(separator: " · ")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue