135 lines
4.7 KiB
Swift
135 lines
4.7 KiB
Swift
import SwiftUI
|
|
|
|
public struct QuickGlancePopoverView: View {
|
|
var store: SystemTelemetryStore
|
|
|
|
public init(store: SystemTelemetryStore) {
|
|
self.store = store
|
|
}
|
|
|
|
public var body: some View {
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
// Header
|
|
HStack {
|
|
Label("MacMonitor", systemImage: "macmini")
|
|
.font(.headline)
|
|
Spacer()
|
|
Button {
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
if let window = NSApp.windows.first(where: { $0.canBecomeMain }) {
|
|
window.makeKeyAndOrderFront(nil)
|
|
}
|
|
} label: {
|
|
Image(systemName: "arrow.up.right.square")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.help("Open Main Window")
|
|
}
|
|
|
|
Divider()
|
|
|
|
// Core Metrics Grid
|
|
VStack(spacing: 8) {
|
|
glanceRow(
|
|
icon: "cpu",
|
|
title: "CPU Activity",
|
|
value: String(format: "%.1f%%", store.cpuLoad.totalLoad),
|
|
subtitle: "\(store.logicalCpuCount) Cores"
|
|
)
|
|
|
|
glanceRow(
|
|
icon: "memorychip",
|
|
title: "Memory",
|
|
value: String(format: "%.1f%%", store.memory.utilizationPercentage),
|
|
subtitle: "\(formatBytes(store.memory.usedBytes)) / \(formatBytes(store.memory.totalBytes))"
|
|
)
|
|
|
|
glanceRow(
|
|
icon: "thermometer.medium",
|
|
title: "Thermals",
|
|
value: String(format: "%.1f°C", store.cpuThermal.packageTemperature),
|
|
subtitle: "Fans: \(store.fans.map { "\(Int($0.currentRPM)) RPM" }.joined(separator: ", "))"
|
|
)
|
|
|
|
glanceRow(
|
|
icon: "bolt.fill",
|
|
title: "Power",
|
|
value: String(format: "%.1f W", store.power.systemTotalWatts),
|
|
subtitle: store.batteryHealth.isCharging ? "Charging (\(Int(store.batteryHealth.healthPercent))%)" : "Battery (\(Int(store.batteryHealth.healthPercent))%)"
|
|
)
|
|
|
|
glanceRow(
|
|
icon: "network",
|
|
title: "Network",
|
|
value: "↓ \(formatBps(totalDownloadBps)) / ↑ \(formatBps(totalUploadBps))",
|
|
subtitle: "\(store.networkBandwidth.count) active interfaces"
|
|
)
|
|
}
|
|
|
|
Divider()
|
|
|
|
// Footer action
|
|
HStack {
|
|
Text("Last updated: \(store.lastUpdateTimestamp.formatted(date: .omitted, time: .standard))")
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
Spacer()
|
|
Button("Quit") {
|
|
NSApplication.shared.terminate(nil)
|
|
}
|
|
.font(.caption)
|
|
}
|
|
}
|
|
.padding(14)
|
|
.frame(width: 320)
|
|
}
|
|
|
|
private var totalDownloadBps: Double {
|
|
store.networkBandwidth.reduce(0.0) { $0 + $1.downloadBps }
|
|
}
|
|
|
|
private var totalUploadBps: Double {
|
|
store.networkBandwidth.reduce(0.0) { $0 + $1.uploadBps }
|
|
}
|
|
|
|
private func glanceRow(icon: String, title: String, value: String, subtitle: String) -> some View {
|
|
HStack {
|
|
Image(systemName: icon)
|
|
.frame(width: 18)
|
|
.foregroundColor(.accentColor)
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(title)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
Text(subtitle)
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
Spacer()
|
|
Text(value)
|
|
.font(.subheadline)
|
|
.fontWeight(.semibold)
|
|
.fontDesign(.monospaced)
|
|
}
|
|
}
|
|
|
|
private func formatBytes(_ bytes: UInt64) -> String {
|
|
let formatter = ByteCountFormatter()
|
|
formatter.allowedUnits = [.useAll]
|
|
formatter.countStyle = .memory
|
|
return formatter.string(fromByteCount: Int64(bytes))
|
|
}
|
|
|
|
private func formatBps(_ bps: Double) -> String {
|
|
if bps < 1024 {
|
|
return String(format: "%.0f B/s", bps)
|
|
} else if bps < 1024 * 1024 {
|
|
return String(format: "%.1f KB/s", bps / 1024)
|
|
} else {
|
|
return String(format: "%.2f MB/s", bps / (1024 * 1024))
|
|
}
|
|
}
|
|
}
|