mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Selected details
This commit is contained in:
parent
9cd833b7fe
commit
cbcd452b4b
2 changed files with 313 additions and 312 deletions
|
|
@ -33,373 +33,371 @@ struct NodeDetail: View {
|
|||
var columnVisibility = NavigationSplitViewVisibility.all
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
let connectedNode = getNodeInfo(
|
||||
id: bleManager.connectedPeripheral?.num ?? -1,
|
||||
context: context
|
||||
)
|
||||
List {
|
||||
let connectedNode = getNodeInfo(
|
||||
id: bleManager.connectedPeripheral?.num ?? -1,
|
||||
context: context
|
||||
)
|
||||
|
||||
Section("Hardware") {
|
||||
NodeInfoItem(node: node)
|
||||
Section("Hardware") {
|
||||
NodeInfoItem(node: node)
|
||||
}
|
||||
|
||||
Section("Node") {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Node Number")
|
||||
} icon: {
|
||||
Image(systemName: "number")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
Spacer()
|
||||
Text(String(node.num))
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
|
||||
Section("Node") {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Node Number")
|
||||
} icon: {
|
||||
Image(systemName: "number")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
Spacer()
|
||||
Text(String(node.num))
|
||||
.textSelection(.enabled)
|
||||
HStack {
|
||||
Label {
|
||||
Text("User Id")
|
||||
} icon: {
|
||||
Image(systemName: "person")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
Text(node.user?.userId ?? "?")
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
|
||||
if let metadata = node.metadata {
|
||||
HStack {
|
||||
Label {
|
||||
Text("User Id")
|
||||
Text("firmware.version")
|
||||
} icon: {
|
||||
Image(systemName: "person")
|
||||
Image(systemName: "memorychip")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
Text(node.user?.userId ?? "?")
|
||||
Text(metadata.firmwareVersion ?? "unknown".localized)
|
||||
}
|
||||
}
|
||||
|
||||
if let role = node.user?.role, let deviceRole = DeviceRoles(rawValue: Int(role)) {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Role")
|
||||
} icon: {
|
||||
Image(systemName: deviceRole.systemName)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
Text(deviceRole.name)
|
||||
}
|
||||
}
|
||||
|
||||
if let dm = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity, dm.uptimeSeconds > 0 {
|
||||
HStack {
|
||||
Label {
|
||||
Text("\("uptime".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
let now = Date.now
|
||||
let later = now + TimeInterval(dm.uptimeSeconds)
|
||||
let uptime = (now..<later).formatted(.components(style: .narrow))
|
||||
Text(uptime)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
if let metadata = node.metadata {
|
||||
HStack {
|
||||
Label {
|
||||
Text("firmware.version")
|
||||
} icon: {
|
||||
Image(systemName: "memorychip")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
Text(metadata.firmwareVersion ?? "unknown".localized)
|
||||
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 {
|
||||
HStack {
|
||||
Label {
|
||||
Text("First heard")
|
||||
} icon: {
|
||||
Image(systemName: "clock")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
|
||||
if let role = node.user?.role, let deviceRole = DeviceRoles(rawValue: Int(role)) {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Role")
|
||||
} icon: {
|
||||
Image(systemName: deviceRole.systemName)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
Text(deviceRole.name)
|
||||
}
|
||||
}
|
||||
|
||||
if let dm = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity, dm.uptimeSeconds > 0 {
|
||||
HStack {
|
||||
Label {
|
||||
Text("\("uptime".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
let now = Date.now
|
||||
let later = now + TimeInterval(dm.uptimeSeconds)
|
||||
let uptime = (now..<later).formatted(.components(style: .narrow))
|
||||
Text(uptime)
|
||||
Spacer()
|
||||
if dateFormatRelative, let text = Self.relativeFormatter.string(for: firstHeard) {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
Text(firstHeard.formatted())
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 {
|
||||
HStack {
|
||||
Label {
|
||||
Text("First heard")
|
||||
} icon: {
|
||||
Image(systemName: "clock")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
if dateFormatRelative, let text = Self.relativeFormatter.string(for: firstHeard) {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
Text(firstHeard.formatted())
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}.onTapGesture {
|
||||
dateFormatRelative.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if let lastHeard = node.lastHeard, lastHeard.timeIntervalSince1970 > 0 {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Last heard")
|
||||
} icon: {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
if dateFormatRelative, let text = Self.relativeFormatter.string(for: lastHeard) {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
Text(lastHeard.formatted())
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}.onTapGesture {
|
||||
dateFormatRelative.toggle()
|
||||
}
|
||||
}.onTapGesture {
|
||||
dateFormatRelative.toggle()
|
||||
}
|
||||
}
|
||||
if node.hasPositions && UserDefaults.environmentEnableWeatherKit || node.hasEnvironmentMetrics {
|
||||
Section("Environment") {
|
||||
if !node.hasEnvironmentMetrics {
|
||||
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)
|
||||
|
||||
if let lastHeard = node.lastHeard, lastHeard.timeIntervalSince1970 > 0 {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Last heard")
|
||||
} icon: {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
if dateFormatRelative, let text = Self.relativeFormatter.string(for: lastHeard) {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
VStack {
|
||||
if node.latestEnvironmentMetrics?.iaq ?? -1 > 0 {
|
||||
IndoorAirQuality(iaq: Int(node.latestEnvironmentMetrics?.iaq ?? 0), displayMode: .gradient)
|
||||
.padding(.vertical)
|
||||
}
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
WeatherConditionsCompactWidget(temperature: String(node.latestEnvironmentMetrics?.temperature.shortFormattedTemperature() ?? "99°"), symbolName: "cloud.sun", description: "TEMP")
|
||||
if node.latestEnvironmentMetrics?.relativeHumidity ?? 0.0 > 0.0 {
|
||||
HumidityCompactWidget(humidity: Int(node.latestEnvironmentMetrics?.relativeHumidity ?? 0.0), dewPoint: String(format: "%.0f", calculateDewPoint(temp: node.latestEnvironmentMetrics?.temperature ?? 0.0, relativeHumidity: node.latestEnvironmentMetrics?.relativeHumidity ?? 0.0)) + "°")
|
||||
}
|
||||
if node.latestEnvironmentMetrics?.barometricPressure ?? 0.0 > 0.0 {
|
||||
PressureCompactWidget(pressure: String(format: "%.2f", node.latestEnvironmentMetrics?.barometricPressure ?? 0.0), unit: "hPA", low: node.latestEnvironmentMetrics?.barometricPressure ?? 0.0 <= 1009.144)
|
||||
}
|
||||
if node.latestEnvironmentMetrics?.windSpeed ?? 0.0 > 0.0 {
|
||||
WindCompactWidget(speed: String(node.latestEnvironmentMetrics?.windSpeed ?? 0.0), gust: String(node.latestEnvironmentMetrics?.windGust ?? 0.0), direction: "")
|
||||
}
|
||||
}
|
||||
.padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical)
|
||||
Text(lastHeard.formatted())
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}.onTapGesture {
|
||||
dateFormatRelative.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.hasPositions && UserDefaults.environmentEnableWeatherKit || node.hasEnvironmentMetrics {
|
||||
Section("Environment") {
|
||||
if !node.hasEnvironmentMetrics {
|
||||
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)
|
||||
} else {
|
||||
VStack {
|
||||
if node.latestEnvironmentMetrics?.iaq ?? -1 > 0 {
|
||||
IndoorAirQuality(iaq: Int(node.latestEnvironmentMetrics?.iaq ?? 0), displayMode: .gradient)
|
||||
.padding(.vertical)
|
||||
}
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
WeatherConditionsCompactWidget(temperature: String(node.latestEnvironmentMetrics?.temperature.shortFormattedTemperature() ?? "99°"), symbolName: "cloud.sun", description: "TEMP")
|
||||
if node.latestEnvironmentMetrics?.relativeHumidity ?? 0.0 > 0.0 {
|
||||
HumidityCompactWidget(humidity: Int(node.latestEnvironmentMetrics?.relativeHumidity ?? 0.0), dewPoint: String(format: "%.0f", calculateDewPoint(temp: node.latestEnvironmentMetrics?.temperature ?? 0.0, relativeHumidity: node.latestEnvironmentMetrics?.relativeHumidity ?? 0.0)) + "°")
|
||||
}
|
||||
if node.latestEnvironmentMetrics?.barometricPressure ?? 0.0 > 0.0 {
|
||||
PressureCompactWidget(pressure: String(format: "%.2f", node.latestEnvironmentMetrics?.barometricPressure ?? 0.0), unit: "hPA", low: node.latestEnvironmentMetrics?.barometricPressure ?? 0.0 <= 1009.144)
|
||||
}
|
||||
if node.latestEnvironmentMetrics?.windSpeed ?? 0.0 > 0.0 {
|
||||
WindCompactWidget(speed: String(node.latestEnvironmentMetrics?.windSpeed ?? 0.0), gust: String(node.latestEnvironmentMetrics?.windGust ?? 0.0), direction: "")
|
||||
}
|
||||
}
|
||||
.padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("Logs") {
|
||||
ForEach(NodeDetails.allCases) { detail in
|
||||
// List( selection: $selectedDetails) { detail in
|
||||
switch detail {
|
||||
case .deviceMetricsLog:
|
||||
NavigationLink {
|
||||
DeviceMetricsLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Device Metrics Log")
|
||||
} icon: {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasDeviceMetrics)
|
||||
case .nodeMap:
|
||||
NavigationLink {
|
||||
if #available (iOS 17, macOS 14, *) {
|
||||
NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num)
|
||||
} else {
|
||||
NodeMapMapkit(node: node)
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text("Node Map")
|
||||
} icon: {
|
||||
Image(systemName: "map")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasPositions)
|
||||
case .positionLog:
|
||||
NavigationLink {
|
||||
PositionLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Position Log")
|
||||
} icon: {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasPositions)
|
||||
case .environmentMetricsLog:
|
||||
NavigationLink {
|
||||
EnvironmentMetricsLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Environment Metrics Log")
|
||||
} icon: {
|
||||
Image(systemName: "cloud.sun.rain")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasEnvironmentMetrics)
|
||||
case .traceRouteLog:
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink {
|
||||
TraceRouteLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Trace Route Log")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(node.traceRoutes?.count ?? 0 == 0)
|
||||
}
|
||||
case .detectionSensorLog:
|
||||
NavigationLink {
|
||||
DetectionSensorLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Detection Sensor Log")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasDetectionSensorMetrics)
|
||||
case .paxCounterLog:
|
||||
if node.hasPax {
|
||||
NavigationLink {
|
||||
PaxCounterLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("paxcounter.log")
|
||||
} icon: {
|
||||
Image(systemName: "figure.walk.motion")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasPax)
|
||||
}
|
||||
Section("Logs") {
|
||||
ForEach(NodeDetails.allCases) { detail in
|
||||
// List( selection: $selectedDetails) { detail in
|
||||
switch detail {
|
||||
case .deviceMetricsLog:
|
||||
NavigationLink {
|
||||
DeviceMetricsLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Device Metrics Log")
|
||||
} icon: {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasDeviceMetrics)
|
||||
case .nodeMap:
|
||||
NavigationLink {
|
||||
if #available (iOS 17, macOS 14, *) {
|
||||
NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num)
|
||||
} else {
|
||||
NodeMapMapkit(node: node)
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text("Node Map")
|
||||
} icon: {
|
||||
Image(systemName: "map")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasPositions)
|
||||
case .positionLog:
|
||||
NavigationLink {
|
||||
PositionLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Position Log")
|
||||
} icon: {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasPositions)
|
||||
case .environmentMetricsLog:
|
||||
NavigationLink {
|
||||
EnvironmentMetricsLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Environment Metrics Log")
|
||||
} icon: {
|
||||
Image(systemName: "cloud.sun.rain")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasEnvironmentMetrics)
|
||||
case .traceRouteLog:
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink {
|
||||
TraceRouteLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Trace Route Log")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(node.traceRoutes?.count ?? 0 == 0)
|
||||
}
|
||||
case .detectionSensorLog:
|
||||
NavigationLink {
|
||||
DetectionSensorLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Detection Sensor Log")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasDetectionSensorMetrics)
|
||||
case .paxCounterLog:
|
||||
if node.hasPax {
|
||||
NavigationLink {
|
||||
PaxCounterLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("paxcounter.log")
|
||||
} icon: {
|
||||
Image(systemName: "figure.walk.motion")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
.disabled(!node.hasPax)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Actions") {
|
||||
FavoriteNodeButton(
|
||||
bleManager: bleManager,
|
||||
context: context,
|
||||
node: node
|
||||
)
|
||||
|
||||
if let user = node.user {
|
||||
NodeAlertsButton(
|
||||
context: context,
|
||||
node: node,
|
||||
user: user
|
||||
)
|
||||
}
|
||||
|
||||
Section("Actions") {
|
||||
FavoriteNodeButton(
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral,
|
||||
node.num != connectedPeripheral.num {
|
||||
ExchangePositionsButton(
|
||||
bleManager: bleManager,
|
||||
context: context,
|
||||
node: node
|
||||
)
|
||||
|
||||
if let user = node.user {
|
||||
NodeAlertsButton(
|
||||
context: context,
|
||||
node: node,
|
||||
user: user
|
||||
)
|
||||
}
|
||||
TraceRouteButton(
|
||||
bleManager: bleManager,
|
||||
node: node
|
||||
)
|
||||
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral,
|
||||
node.num != connectedPeripheral.num {
|
||||
ExchangePositionsButton(
|
||||
bleManager: bleManager,
|
||||
node: node
|
||||
)
|
||||
|
||||
TraceRouteButton(
|
||||
bleManager: bleManager,
|
||||
node: node
|
||||
)
|
||||
|
||||
if let connectedNode {
|
||||
if node.isStoreForwardRouter {
|
||||
ClientHistoryButton(
|
||||
bleManager: bleManager,
|
||||
connectedNode: connectedNode,
|
||||
node: node
|
||||
)
|
||||
}
|
||||
|
||||
DeleteNodeButton(
|
||||
if let connectedNode {
|
||||
if node.isStoreForwardRouter {
|
||||
ClientHistoryButton(
|
||||
bleManager: bleManager,
|
||||
context: context,
|
||||
connectedNode: connectedNode,
|
||||
node: node
|
||||
)
|
||||
}
|
||||
|
||||
DeleteNodeButton(
|
||||
bleManager: bleManager,
|
||||
context: context,
|
||||
connectedNode: connectedNode,
|
||||
node: node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let metadata = node.metadata,
|
||||
let connectedNode,
|
||||
self.bleManager.connectedPeripheral != nil {
|
||||
Section("Administration") {
|
||||
if connectedNode.myInfo?.hasAdmin ?? false {
|
||||
Button {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(
|
||||
fromUser: connectedNode.user!,
|
||||
toUser: node.user!,
|
||||
adminIndex: connectedNode.myInfo!.adminIndex,
|
||||
context: context
|
||||
)
|
||||
if adminMessageId > 0 {
|
||||
Logger.mesh.info("Sent node metadata request from node details")
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text("Refresh device metadata")
|
||||
} icon: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if metadata.canShutdown {
|
||||
Button {
|
||||
showingShutdownConfirm = true
|
||||
} label: {
|
||||
Label("Power Off", systemImage: "power")
|
||||
}.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $showingShutdownConfirm
|
||||
) {
|
||||
Button("Shutdown Node?", role: .destructive) {
|
||||
if !bleManager.sendShutdown(
|
||||
fromUser: connectedNode.user!,
|
||||
toUser: node.user!,
|
||||
adminIndex: connectedNode.myInfo!.adminIndex
|
||||
) {
|
||||
Logger.mesh.warning("Shutdown Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let metadata = node.metadata,
|
||||
let connectedNode,
|
||||
self.bleManager.connectedPeripheral != nil {
|
||||
Section("Administration") {
|
||||
if connectedNode.myInfo?.hasAdmin ?? false {
|
||||
Button {
|
||||
showingRebootConfirm = true
|
||||
} label: {
|
||||
Label(
|
||||
"reboot",
|
||||
systemImage: "arrow.triangle.2.circlepath"
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(
|
||||
fromUser: connectedNode.user!,
|
||||
toUser: node.user!,
|
||||
adminIndex: connectedNode.myInfo!.adminIndex,
|
||||
context: context
|
||||
)
|
||||
if adminMessageId > 0 {
|
||||
Logger.mesh.info("Sent node metadata request from node details")
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text("Refresh device metadata")
|
||||
} icon: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if metadata.canShutdown {
|
||||
Button {
|
||||
showingShutdownConfirm = true
|
||||
} label: {
|
||||
Label("Power Off", systemImage: "power")
|
||||
}.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $showingRebootConfirm
|
||||
isPresented: $showingShutdownConfirm
|
||||
) {
|
||||
Button("reboot.node", role: .destructive) {
|
||||
if !bleManager.sendReboot(
|
||||
Button("Shutdown Node?", role: .destructive) {
|
||||
if !bleManager.sendShutdown(
|
||||
fromUser: connectedNode.user!,
|
||||
toUser: node.user!,
|
||||
adminIndex: connectedNode.myInfo!.adminIndex
|
||||
) {
|
||||
Logger.mesh.warning("Reboot Failed")
|
||||
Logger.mesh.warning("Shutdown Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
showingRebootConfirm = true
|
||||
} label: {
|
||||
Label(
|
||||
"reboot",
|
||||
systemImage: "arrow.triangle.2.circlepath"
|
||||
)
|
||||
}.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $showingRebootConfirm
|
||||
) {
|
||||
Button("reboot.node", role: .destructive) {
|
||||
if !bleManager.sendReboot(
|
||||
fromUser: connectedNode.user!,
|
||||
toUser: node.user!,
|
||||
adminIndex: connectedNode.myInfo!.adminIndex
|
||||
) {
|
||||
Logger.mesh.warning("Reboot Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ struct NodeList: View {
|
|||
|
||||
@State private var columnVisibility = NavigationSplitViewVisibility.all
|
||||
@State private var selectedNode: NodeInfoEntity?
|
||||
@State private var selectedDetails: NodeDetails?
|
||||
@State private var searchText = ""
|
||||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
|
|
@ -251,14 +252,16 @@ struct NodeList: View {
|
|||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: router.navigationState) { state in
|
||||
.onChange(of: router.navigationState) { _ in
|
||||
// Handle deep link routing
|
||||
if case .nodes(let selected) = router.navigationState {
|
||||
self.selectedNode = selected?.selectedNodeNum.flatMap {
|
||||
getNodeInfo(id: $0, context: context)
|
||||
}
|
||||
self.selectedDetails = selected?.details
|
||||
} else {
|
||||
self.selectedNode = nil
|
||||
self.selectedDetails = nil
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue