diff --git a/Localizable.xcstrings b/Localizable.xcstrings index ce93458d..6e6beca7 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -524,6 +524,9 @@ } } } + }, + "Administration" : { + }, "Advanced" : { @@ -11186,6 +11189,9 @@ } } } + }, + "Logs" : { + }, "Logs:" : { @@ -15307,6 +15313,9 @@ } } } + }, + "Node" : { + }, "Node Core Data Backup %@/%@ - %@ - %@" : { "localizations" : { @@ -15327,7 +15336,7 @@ "Node Map" : { }, - "Node Number:" : { + "Node Number" : { }, "nodelist.filter.distance %@" : { @@ -17038,6 +17047,9 @@ }, "Recording route" : { + }, + "Refresh device metadata" : { + }, "Region" : { @@ -22100,7 +22112,7 @@ "User Details" : { }, - "User Id:" : { + "User Id" : { }, "user.details" : { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 2ff960c5..03cd132f 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -20,210 +20,239 @@ struct NodeDetail: View { var columnVisibility = NavigationSplitViewVisibility.all var body: some View { - - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) NavigationStack { - GeometryReader { _ in - VStack { - ScrollView { - NodeInfoItem(node: node) - let dm = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity - if dm?.uptimeSeconds ?? 0 > 0 { - HStack(alignment: .center) { - let now = Date.now - let later = now + TimeInterval(dm!.uptimeSeconds) - let components = (now.. 0 { - Logger.mesh.info("Sent node metadata request from node details") - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.title3) - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.small) - } - } - Divider() + Spacer() + Text(String(node.num)) + } + + HStack { + Label { + Text("User Id") + } icon: { + Image(systemName: "person") + .symbolRenderingMode(.multicolor) } - VStack { - NavigationLink { - DeviceMetricsLog(node: node) - } label: { - Image(systemName: "flipphone") + Spacer() + Text(node.user?.userId ?? "?") + } + + 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) - .font(.title) - - Text("Device Metrics Log") - .font(.title3) } - .disabled(!node.hasDeviceMetrics) + Spacer() - Divider() - NavigationLink { - if #available (iOS 17, macOS 14, *) { - NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num) - } else { - NodeMapMapkit(node: node) - } - - } label: { - Image(systemName: "map") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Node Map") - .font(.title3) - } - .disabled(!node.hasPositions) - Divider() - NavigationLink { - PositionLog(node: node) - } label: { - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Position Log") - .font(.title3) - } - .disabled(!node.hasPositions) - Divider() - NavigationLink { - EnvironmentMetricsLog(node: node) - } label: { - Image(systemName: "cloud.sun.rain") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Environment Metrics Log") - .font(.title3) - } - .disabled(!node.hasEnvironmentMetrics) - Divider() - if #available(iOS 17.0, macOS 14.0, *) { - NavigationLink { - TraceRouteLog(node: node) - } label: { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Trace Route Log") - .font(.title3) - } - .disabled(node.traceRoutes?.count ?? 0 == 0) - Divider() - } - NavigationLink { - DetectionSensorLog(node: node) - } label: { - Image(systemName: "sensor") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Detection Sensor Log") - .font(.title3) - } - .disabled(!node.hasDetectionSensorMetrics) - Divider() - if node.hasPax { - NavigationLink { - PaxCounterLog(node: node) - } label: { - Image(systemName: "figure.walk.motion") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("paxcounter.log") - .font(.title3) - } - .disabled(!node.hasPax) - Divider() - } + let now = Date.now + let later = now + TimeInterval(dm.uptimeSeconds) + let uptime = (now.. 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 { + Label("Power Off", systemImage: "power") + .onTapGesture { + showingShutdownConfirm = true + } + .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") + } + } + } + } + Label("reboot", systemImage: "arrow.triangle.2.circlepath") + .onTapGesture { + showingRebootConfirm = true + } + .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") + } + } + } } } } - .padding(.bottom, 2) + .listStyle(.insetGrouped) + .onAppear { + if self.bleManager.context == nil { + self.bleManager.context = context + } + } } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index df40aaa5..e94baf3a 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -11,23 +11,24 @@ import MapKit struct NodeInfoItem: View { - @ObservedObject var node: NodeInfoEntity - var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast + @ObservedObject + var node: NodeInfoEntity + + var modemPreset: ModemPresets = ModemPresets( + rawValue: UserDefaults.modemPreset + ) ?? ModemPresets.longFast var body: some View { - - Divider() - HStack { - - VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) - } - if node.user != nil { - Divider() + CircleText( + text: node.user?.shortName ?? "?", + color: Color(UIColor(hex: UInt32(node.num))), + circleSize: 65 + ) + if let user = node.user { VStack(alignment: .center) { - if node.user?.hwModel != "UNSET" { - Image(node.user!.hwModel ?? "unset".localized) + if user.hwModel != "UNSET" { + Image(user.hwModel ?? "unset".localized) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 75, height: 75) @@ -47,8 +48,8 @@ struct NodeInfoItem: View { } } } + if node.snr != 0 && !node.viaMqtt { - Divider() VStack(alignment: .center) { let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) LoRaSignalStrengthIndicator(signalStrength: signalStrength) @@ -61,41 +62,12 @@ struct NodeInfoItem: View { .font(.caption2) } } + if node.telemetries?.count ?? 0 > 0 { - Divider() BatteryGauge(node: node) + .padding() } + Spacer() } - Divider() - HStack(alignment: .center) { - VStack { - HStack { - Image(systemName: "number") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Node Number:").font(.title2) - } - Text(String(node.num)) - .font(.title3) - .foregroundColor(.gray) - .textSelection(.enabled) - } - Divider() - VStack { - HStack { - Image(systemName: "person") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("User Id:").font(.title2) - } - Text(node.user?.userId ?? "?") - .font(.title3) - .foregroundColor(.gray) - .textSelection(.enabled) - } - } - Divider() } } diff --git a/protobufs b/protobufs index 4da558d0..1198b7db 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4da558d0f73c46ef91b74431facee73c09affbfc +Subproject commit 1198b7dbabf9768cb0143d2897707b4c7a51a5da