132 lines
4.7 KiB
Swift
132 lines
4.7 KiB
Swift
import SwiftUI
|
||
|
||
struct InstanceDetailView: View {
|
||
@StateObject private var model: DeviceViewModel
|
||
@State private var revealsSensitive = false
|
||
|
||
init(instance: Instance, model: DeviceViewModel? = nil) {
|
||
_model = StateObject(wrappedValue: model ?? DefaultContainer.makeDeviceModel(instance: instance))
|
||
}
|
||
|
||
var body: some View {
|
||
ZStack {
|
||
AITheme.canvas.ignoresSafeArea()
|
||
ScrollView {
|
||
LazyVStack(spacing: 12) {
|
||
header
|
||
if let errorMessage = model.errorMessage {
|
||
AICard {
|
||
Text(errorMessage)
|
||
.font(.subheadline)
|
||
.foregroundStyle(AITheme.warning)
|
||
}
|
||
}
|
||
if let snapshot = model.snapshot {
|
||
ForEach(snapshot.sections) { section in
|
||
sectionCard(section)
|
||
}
|
||
}
|
||
}
|
||
.padding(16)
|
||
}
|
||
}
|
||
.navigationTitle(model.instance.name)
|
||
.toolbar {
|
||
ToolbarItem(placement: .primaryAction) {
|
||
Button {
|
||
revealsSensitive.toggle()
|
||
} label: {
|
||
Label(revealsSensitive ? "隐藏敏感值" : "显示敏感值",
|
||
systemImage: revealsSensitive ? "eye.slash" : "eye")
|
||
}
|
||
.buttonStyle(AIButtonStyle(kind: .secondary, size: .compact))
|
||
}
|
||
}
|
||
.refreshable {
|
||
await model.refresh()
|
||
}
|
||
.task {
|
||
await model.refresh()
|
||
}
|
||
}
|
||
|
||
private var header: some View {
|
||
AICard {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text("设备详情")
|
||
.font(.headline)
|
||
Text(model.instance.origin.absoluteString)
|
||
.font(.subheadline)
|
||
.foregroundStyle(AITheme.muted)
|
||
if model.isLoading {
|
||
Text("正在读取设备能力…")
|
||
.font(.subheadline)
|
||
.foregroundStyle(AITheme.muted)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private func sectionCard(_ section: DeviceSection) -> some View {
|
||
AICard {
|
||
VStack(alignment: .leading, spacing: 12) {
|
||
Text(section.title)
|
||
.font(.headline)
|
||
if section.values.isEmpty {
|
||
Text("此模块当前不可用或未返回数据。")
|
||
.font(.subheadline)
|
||
.foregroundStyle(AITheme.muted)
|
||
} else {
|
||
ForEach(section.values.sorted(by: { $0.key < $1.key }), id: \.key) { item in
|
||
HStack(alignment: .top, spacing: 10) {
|
||
Text(fieldLabel(item.key))
|
||
.font(.subheadline)
|
||
.foregroundStyle(AITheme.muted)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
Text(display(item.key, item.value))
|
||
.font(.subheadline)
|
||
.foregroundStyle(AITheme.ink)
|
||
.multilineTextAlignment(.trailing)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private let fieldLabels: [String: String] = [
|
||
"model": "型号",
|
||
"manufacturer": "制造商",
|
||
"imei": "IMEI",
|
||
"revision": "固件版本",
|
||
"online": "在线",
|
||
"powered": "基带电源",
|
||
"present": "SIM 在位",
|
||
"iccid": "ICCID",
|
||
"imsi": "IMSI",
|
||
"phone_numbers": "号码",
|
||
"sms_center": "短信中心",
|
||
"operator_name": "运营商",
|
||
"registration_status": "注册状态",
|
||
"technology_preference": "网络制式",
|
||
"signal_strength": "信号强度",
|
||
"core_count": "核心数",
|
||
"model_name": "CPU 型号",
|
||
"state": "连接状态",
|
||
"latency_ms": "延迟(ms)"
|
||
]
|
||
|
||
private let sensitiveKeys: Set<String> = ["imei", "iccid", "imsi", "phone_numbers", "sms_center"]
|
||
|
||
private func fieldLabel(_ key: String) -> String {
|
||
fieldLabels[key] ?? key.replacingOccurrences(of: "_", with: " ")
|
||
}
|
||
|
||
private func display(_ key: String, _ value: String) -> String {
|
||
guard sensitiveKeys.contains(key), !revealsSensitive else { return value }
|
||
guard let first = value.first, value.count > 6 else { return String(repeating: "•", count: min(value.count, 8)) }
|
||
let tail = value.suffix(4)
|
||
return "\(first) •••• \(tail)"
|
||
}
|
||
}
|