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
@@ -3,13 +3,24 @@ import SwiftUI
struct InstanceFormView: View {
@Environment(\.dismiss) private var dismiss
@ObservedObject var model: FleetViewModel
let instance: Instance?
@State private var name = ""
@State private var originText = ""
@State private var tagsText = ""
@State private var password = ""
@State private var selectedGroupId: UUID?
@State private var validationMessage: String?
@State private var isSaving = false
init(model: FleetViewModel, instance: Instance? = nil) {
self.model = model
self.instance = instance
_name = State(initialValue: instance?.name ?? "")
_originText = State(initialValue: instance?.origin.absoluteString ?? "")
_tagsText = State(initialValue: instance?.tags.joined(separator: ", ") ?? "")
_selectedGroupId = State(initialValue: instance?.groupId)
}
private var canSave: Bool {
!name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
&& !originText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
@@ -24,7 +35,13 @@ struct InstanceFormView: View {
field("名称", text: $name, placeholder: "Home Node")
field("地址", text: $originText, placeholder: "http://192.168.1.10:3000")
field("标签", text: $tagsText, placeholder: "home, primary")
field("实例密码", text: $password, placeholder: "可选,保存在本机沙盒", secure: true)
groupPicker
field(
"实例密码",
text: $password,
placeholder: instance == nil ? "可选,保存在本机沙盒" : "留空则不修改密码",
secure: true
)
}
}
if let validationMessage {
@@ -56,13 +73,37 @@ struct InstanceFormView: View {
}
}
.background(AITheme.canvas.ignoresSafeArea())
.navigationTitle("添加实例")
.navigationTitle(instance == nil ? "添加实例" : "编辑实例")
#if os(iOS)
.scrollDismissesKeyboard(.interactively)
#endif
}
}
private var groupPicker: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
AIFieldLabel("分组")
Spacer()
Button {
model.showingGroupManager = true
} label: {
Label("管理", systemImage: "folder.badge.gearshape")
}
.buttonStyle(AIButtonStyle(kind: .secondary, size: .toolbarInline, fixedWidth: 72))
}
Picker("分组", selection: $selectedGroupId) {
Text("未分组").tag(UUID?.none)
ForEach(model.groups) { group in
Text(group.name).tag(UUID?.some(group.id))
}
}
.pickerStyle(.menu)
.aiSelectSurface()
}
.accessibilityElement(children: .contain)
}
private func field(
_ title: String,
text: Binding<String>,
@@ -111,11 +152,16 @@ struct InstanceFormView: View {
name: normalizedName,
origin: origin,
tags: tags,
groupId: selectedGroupId,
password: password.isEmpty ? nil : password
)
Task {
do {
try await model.add(input)
if let instance {
try await model.update(instance.id, input)
} else {
try await model.add(input)
}
} catch {
validationMessage = "保存失败,请检查地址是否重复。"
}