641 lines
27 KiB
Swift
641 lines
27 KiB
Swift
import SwiftUI
|
|
|
|
public struct ContentView: View {
|
|
@State private var store = SystemTelemetryStore.shared
|
|
@State private var selectedTab: String = "Dashboard"
|
|
|
|
public init() {}
|
|
|
|
public var body: some View {
|
|
NavigationSplitView {
|
|
sidebarContent
|
|
} detail: {
|
|
detailContent
|
|
}
|
|
.frame(minWidth: 900, minHeight: 650)
|
|
.onAppear {
|
|
store.start()
|
|
}
|
|
}
|
|
|
|
// MARK: - Sidebar
|
|
private var sidebarContent: some View {
|
|
List(selection: $selectedTab) {
|
|
Section("Overview") {
|
|
Label("Dashboard", systemImage: "gauge.with.needle")
|
|
.tag("Dashboard")
|
|
Label("Hardware Architecture", systemImage: "cpu")
|
|
.tag("Architecture")
|
|
}
|
|
|
|
Section("Primary Telemetry") {
|
|
Label("CPU Load & Cores", systemImage: "chart.bar.xaxis")
|
|
.tag("CPU")
|
|
Label("Thermal Matrix", systemImage: "flame")
|
|
.tag("Thermals")
|
|
Label("Fans & Cooling", systemImage: "fanblades")
|
|
.tag("Fans")
|
|
Label("Memory & Swap", systemImage: "memorychip")
|
|
.tag("Memory")
|
|
Label("Storage Volumes", systemImage: "internaldrive")
|
|
.tag("Storage")
|
|
Label("Power & Voltage", systemImage: "bolt.fill")
|
|
.tag("Power")
|
|
}
|
|
}
|
|
.listStyle(.sidebar)
|
|
.navigationSplitViewColumnWidth(min: 200, ideal: 230, max: 280)
|
|
}
|
|
|
|
// MARK: - Detail Content Router
|
|
@ViewBuilder
|
|
private var detailContent: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 20) {
|
|
headerCard
|
|
engineControlCard
|
|
|
|
switch selectedTab {
|
|
case "Dashboard":
|
|
dashboardView
|
|
case "Architecture":
|
|
specsCard
|
|
providersCard
|
|
case "CPU":
|
|
cpuLoadCard
|
|
specsCard
|
|
case "Thermals":
|
|
cpuThermalCard
|
|
componentThermalCard
|
|
case "Fans":
|
|
fansCard
|
|
case "Memory":
|
|
memoryCard
|
|
case "Storage":
|
|
storageCard
|
|
case "Power":
|
|
powerCard
|
|
default:
|
|
dashboardView
|
|
}
|
|
}
|
|
.padding(24)
|
|
}
|
|
.navigationTitle("MacMonitor — \(selectedTab)")
|
|
}
|
|
|
|
// MARK: - Dashboard Composite View
|
|
private var dashboardView: some View {
|
|
VStack(alignment: .leading, spacing: 20) {
|
|
// Top Row: CPU & Memory Summary Cards
|
|
HStack(alignment: .top, spacing: 16) {
|
|
cpuLoadCard
|
|
memoryCard
|
|
}
|
|
|
|
// Middle Row: Thermals & Fans Summary Cards
|
|
HStack(alignment: .top, spacing: 16) {
|
|
cpuThermalCard
|
|
fansCard
|
|
}
|
|
|
|
// Power & Storage Summary
|
|
HStack(alignment: .top, spacing: 16) {
|
|
powerCard
|
|
storageCard
|
|
}
|
|
|
|
// Detailed Component Thermals Matrix
|
|
componentThermalCard
|
|
}
|
|
}
|
|
|
|
// MARK: - Header Banner
|
|
private var headerCard: some View {
|
|
HStack(alignment: .center, spacing: 16) {
|
|
Image(systemName: "macpro.gen3.fill")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 44, height: 44)
|
|
.foregroundStyle(.blue)
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(store.hostModel)
|
|
.font(.title2.bold())
|
|
Text("Intel x86_64 • \(store.osVersion)")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
HStack(spacing: 8) {
|
|
Circle()
|
|
.fill(store.isRunning ? Color.green : Color.red)
|
|
.frame(width: 10, height: 10)
|
|
Text(store.isRunning ? "ACTIVE" : "PAUSED")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(store.isRunning ? .green : .secondary)
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 6)
|
|
.background(.quaternary, in: Capsule())
|
|
}
|
|
.padding(16)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Telemetry Coordinator Controls
|
|
private var engineControlCard: some View {
|
|
HStack(spacing: 16) {
|
|
Button(action: {
|
|
if store.isRunning { store.stop() } else { store.start() }
|
|
}) {
|
|
Label(store.isRunning ? "Pause Engine" : "Start Engine",
|
|
systemImage: store.isRunning ? "pause.fill" : "play.fill")
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(store.isRunning ? .orange : .green)
|
|
|
|
Button(action: { store.triggerManualSample() }) {
|
|
Label("Sample Now", systemImage: "arrow.clockwise")
|
|
}
|
|
.buttonStyle(.bordered)
|
|
|
|
Spacer()
|
|
|
|
Picker("Interval:", selection: Binding(
|
|
get: { store.sampleInterval },
|
|
set: { store.setSampleInterval($0) }
|
|
)) {
|
|
Text("0.5s").tag(0.5)
|
|
Text("1.0s").tag(1.0)
|
|
Text("2.0s").tag(2.0)
|
|
Text("5.0s").tag(5.0)
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.frame(width: 220)
|
|
|
|
Text("Updated: \(store.lastUpdateTimestamp.formatted(date: .omitted, time: .standard))")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding(14)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - CPU Load Card
|
|
private var cpuLoadCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("CPU Activity", systemImage: "cpu")
|
|
.font(.headline)
|
|
Spacer()
|
|
if store.cpuLoad.isThrottled {
|
|
Text("THROTTLED")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.red)
|
|
.padding(.horizontal, 6)
|
|
.padding(.vertical, 2)
|
|
.background(Color.red.opacity(0.15), in: Capsule())
|
|
}
|
|
Text(String(format: "%.1f%%", store.cpuLoad.totalLoad))
|
|
.font(.title3.bold().monospacedDigit())
|
|
}
|
|
|
|
ProgressView(value: min(store.cpuLoad.totalLoad / 100.0, 1.0))
|
|
.tint(store.cpuLoad.totalLoad > 85 ? .red : (store.cpuLoad.totalLoad > 60 ? .orange : .blue))
|
|
|
|
HStack {
|
|
Text("User: \(String(format: "%.1f%%", store.cpuLoad.userLoad))")
|
|
Spacer()
|
|
Text("System: \(String(format: "%.1f%%", store.cpuLoad.systemLoad))")
|
|
Spacer()
|
|
Text("Idle: \(String(format: "%.1f%%", store.cpuLoad.idleLoad))")
|
|
}
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
|
|
if !store.cpuLoad.perCoreLoad.isEmpty {
|
|
Divider()
|
|
Text("Per-Core Utilization (\(store.cpuLoad.perCoreLoad.count) Cores)")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
|
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 70))], spacing: 8) {
|
|
ForEach(Array(store.cpuLoad.perCoreLoad.enumerated()), id: \.offset) { idx, load in
|
|
VStack(spacing: 4) {
|
|
Text("Core \(idx)")
|
|
.font(.system(size: 10))
|
|
.foregroundStyle(.secondary)
|
|
ProgressView(value: min(load / 100.0, 1.0))
|
|
.tint(load > 85 ? .red : .blue)
|
|
Text(String(format: "%.0f%%", load))
|
|
.font(.system(size: 10, weight: .bold, design: .monospaced))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Memory & Swap Card
|
|
private var memoryCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("Memory & Swap", systemImage: "memorychip")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text(store.memory.pressureLevel)
|
|
.font(.caption.bold())
|
|
.foregroundStyle(pressureColor(store.memory.pressureLevel))
|
|
.padding(.horizontal, 6)
|
|
.padding(.vertical, 2)
|
|
.background(pressureColor(store.memory.pressureLevel).opacity(0.15), in: Capsule())
|
|
Text(String(format: "%.1f%%", store.memory.utilizationPercentage))
|
|
.font(.title3.bold().monospacedDigit())
|
|
}
|
|
|
|
ProgressView(value: min(store.memory.utilizationPercentage / 100.0, 1.0))
|
|
.tint(pressureColor(store.memory.pressureLevel))
|
|
|
|
Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) {
|
|
GridRow {
|
|
Text("Physical RAM:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.totalBytes)).bold()
|
|
Text("Used:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.usedBytes)).bold()
|
|
}
|
|
GridRow {
|
|
Text("Wired:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.wiredBytes))
|
|
Text("Compressed:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.compressedBytes))
|
|
}
|
|
GridRow {
|
|
Text("Active:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.activeBytes))
|
|
Text("Free / Inactive:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.freeBytes + store.memory.inactiveBytes))
|
|
}
|
|
if store.memory.swapTotalBytes > 0 {
|
|
GridRow {
|
|
Text("Swap Total:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.swapTotalBytes))
|
|
Text("Swap Used:").foregroundStyle(.secondary)
|
|
Text(formatBytes(store.memory.swapUsedBytes))
|
|
}
|
|
}
|
|
}
|
|
.font(.caption)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - CPU Thermal Card
|
|
private var cpuThermalCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("CPU Thermals", systemImage: "flame.fill")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text(store.cpuThermal.thermalPressureState)
|
|
.font(.caption.bold())
|
|
.foregroundStyle(store.cpuThermal.isThrottling ? .red : .green)
|
|
.padding(.horizontal, 6)
|
|
.padding(.vertical, 2)
|
|
.background((store.cpuThermal.isThrottling ? Color.red : Color.green).opacity(0.15), in: Capsule())
|
|
Text(String(format: "%.1f°C", store.cpuThermal.packageTemperature))
|
|
.font(.title3.bold().monospacedDigit())
|
|
}
|
|
|
|
HStack(spacing: 24) {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text("Package Temp").font(.caption).foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f°C", store.cpuThermal.packageTemperature)).bold()
|
|
}
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text("Peak Core").font(.caption).foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f°C", store.cpuThermal.peakCoreTemperature)).bold()
|
|
}
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text("Average Core").font(.caption).foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f°C", store.cpuThermal.averageCoreTemperature)).bold()
|
|
}
|
|
}
|
|
|
|
if !store.cpuThermal.coreTemperatures.isEmpty {
|
|
Divider()
|
|
Text("Core Temperature Breakdown")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
|
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))], spacing: 8) {
|
|
ForEach(store.cpuThermal.coreTemperatures, id: \.key) { core in
|
|
VStack(spacing: 2) {
|
|
Text("Core \(core.index)")
|
|
.font(.system(size: 10))
|
|
.foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f°C", core.temperature))
|
|
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
|
.foregroundStyle(core.temperature > 85 ? .red : (core.temperature > 70 ? .orange : .primary))
|
|
}
|
|
.padding(6)
|
|
.background(.quaternary, in: RoundedRectangle(cornerRadius: 6))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Fans & Tachometers Card
|
|
private var fansCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("Cooling Fans", systemImage: "fanblades.fill")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text("\(store.fans.count) Detected")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
if store.fans.isEmpty {
|
|
Text("No active AppleSMC fans detected or passive cooling chassis.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
.padding(.vertical, 8)
|
|
} else {
|
|
ForEach(store.fans) { fan in
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
HStack {
|
|
Text(fan.name).bold()
|
|
Spacer()
|
|
Text(String(format: "%.0f RPM (%.0f%%)", fan.currentRPM, fan.utilization))
|
|
.font(.subheadline.bold().monospacedDigit())
|
|
}
|
|
ProgressView(value: min(fan.utilization / 100.0, 1.0))
|
|
.tint(fan.utilization > 80 ? .red : .blue)
|
|
HStack {
|
|
Text("Min: \(Int(fan.minRPM)) RPM")
|
|
Spacer()
|
|
Text("Target: \(Int(fan.targetRPM)) RPM")
|
|
Spacer()
|
|
Text("Max: \(Int(fan.maxRPM)) RPM")
|
|
}
|
|
.font(.system(size: 10))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding(8)
|
|
.background(.quaternary, in: RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Component Thermals Matrix Card
|
|
private var componentThermalCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("Chassis & Component Thermals", systemImage: "thermometer.medium")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text("\(store.componentTemps.count) Sensors")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
if store.componentTemps.isEmpty {
|
|
Text("Awaiting component thermal probe...")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 160))], spacing: 10) {
|
|
ForEach(store.componentTemps) { item in
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(item.name)
|
|
.font(.system(size: 11, weight: .semibold))
|
|
.lineLimit(1)
|
|
HStack {
|
|
Text(item.category)
|
|
.font(.system(size: 9))
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
Text(String(format: "%.1f°C", item.temperature))
|
|
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
|
.foregroundStyle(item.temperature > 75 ? .red : .primary)
|
|
}
|
|
}
|
|
.padding(8)
|
|
.background(.quaternary, in: RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(16)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Storage Volumes Card
|
|
private var storageCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("Storage Volumes", systemImage: "internaldrive")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text("\(store.storageVolumes.count) Mounted")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
if store.storageVolumes.isEmpty {
|
|
Text("No mounted APFS/HFS volumes discovered.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
ForEach(store.storageVolumes) { vol in
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
HStack {
|
|
Text(vol.volumeName).bold()
|
|
Text("(\(vol.fileSystem))").font(.caption).foregroundStyle(.secondary)
|
|
Spacer()
|
|
Text("\(formatBytes(vol.usedBytes)) / \(formatBytes(vol.totalBytes))")
|
|
.font(.caption.monospacedDigit())
|
|
}
|
|
ProgressView(value: min(vol.usedPercentage / 100.0, 1.0))
|
|
.tint(vol.usedPercentage > 90 ? .red : .blue)
|
|
HStack {
|
|
Text("Mount: \(vol.mountPoint)").font(.system(size: 10)).foregroundStyle(.secondary)
|
|
Spacer()
|
|
Text(String(format: "%.1f%% Used", vol.usedPercentage)).font(.system(size: 10, weight: .bold))
|
|
}
|
|
}
|
|
.padding(8)
|
|
.background(.quaternary, in: RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Power & Voltage Card
|
|
private var powerCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Label("Power & Voltage", systemImage: "bolt.fill")
|
|
.font(.headline)
|
|
Spacer()
|
|
HStack(spacing: 4) {
|
|
Image(systemName: store.power.powerSource.contains("Battery") ? "battery.75" : "powerplug.fill")
|
|
Text(store.power.powerSource)
|
|
}
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
|
|
Text(String(format: "%.1f W", store.power.systemTotalWatts))
|
|
.font(.title3.bold().monospacedDigit())
|
|
}
|
|
|
|
Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) {
|
|
GridRow {
|
|
Text("Total Draw:").foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f W", store.power.systemTotalWatts)).bold()
|
|
Text("CPU Package:").foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f W", store.power.cpuWatts)).bold()
|
|
}
|
|
GridRow {
|
|
Text("CPU Core Voltage:").foregroundStyle(.secondary)
|
|
Text(store.power.cpuVoltage > 0 ? String(format: "%.3f V", store.power.cpuVoltage) : "N/A")
|
|
Text("CPU Current:").foregroundStyle(.secondary)
|
|
Text(store.power.cpuCurrent > 0 ? String(format: "%.2f A", store.power.cpuCurrent) : "N/A")
|
|
}
|
|
if store.power.gpuWatts > 0 || store.power.memoryWatts > 0 {
|
|
GridRow {
|
|
Text("GPU Draw:").foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f W", store.power.gpuWatts))
|
|
Text("Memory Subsystem:").foregroundStyle(.secondary)
|
|
Text(String(format: "%.1f W", store.power.memoryWatts))
|
|
}
|
|
}
|
|
if store.power.powerSource.contains("Battery") {
|
|
GridRow {
|
|
Text("Battery Level:").foregroundStyle(.secondary)
|
|
Text(String(format: "%.0f%% %@", store.power.batteryLevel, store.power.isCharging ? "(Charging)" : ""))
|
|
Text("Status:").foregroundStyle(.secondary)
|
|
Text(store.power.isCharging ? "Charging" : "Discharging")
|
|
}
|
|
}
|
|
}
|
|
.font(.caption)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Hardware Specs Card
|
|
private var specsCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Host Architecture & Kernel")
|
|
.font(.headline)
|
|
|
|
Grid(alignment: .leading, horizontalSpacing: 24, verticalSpacing: 10) {
|
|
GridRow {
|
|
Text("Hardware Model:").foregroundStyle(.secondary)
|
|
Text(store.hostModel).bold()
|
|
Text("Physical Cores:").foregroundStyle(.secondary)
|
|
Text("\(store.physicalCpuCount)").bold()
|
|
}
|
|
GridRow {
|
|
Text("Darwin Kernel:").foregroundStyle(.secondary)
|
|
Text(store.kernelVersion).bold()
|
|
Text("Logical Cores:").foregroundStyle(.secondary)
|
|
Text("\(store.logicalCpuCount)").bold()
|
|
}
|
|
GridRow {
|
|
Text("Target Architecture:").foregroundStyle(.secondary)
|
|
Text("Intel x86_64").bold()
|
|
Text("Deployment Target:").foregroundStyle(.secondary)
|
|
Text("macOS 14.0 (Sonoma)").bold()
|
|
}
|
|
}
|
|
}
|
|
.padding(16)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Providers Card
|
|
private var providersCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Text("Registered Telemetry Providers")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text("\(store.registeredProviderCount) Active")
|
|
.font(.caption.bold())
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 4)
|
|
.background(Color.blue.opacity(0.15))
|
|
.foregroundStyle(.blue)
|
|
.clipShape(Capsule())
|
|
}
|
|
|
|
ForEach(Array(store.latestSnapshot.keys.sorted()), id: \.self) { key in
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundStyle(.green)
|
|
Text(key)
|
|
.font(.system(.body, design: .monospaced))
|
|
Spacer()
|
|
Text("\(store.latestSnapshot[key]?.count ?? 0) metrics")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
}
|
|
.padding(16)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
// MARK: - Formatting Helpers
|
|
private func formatBytes(_ bytes: UInt64) -> String {
|
|
let formatter = ByteCountFormatter()
|
|
formatter.allowedUnits = [.useGB, .useMB]
|
|
formatter.countStyle = .memory
|
|
return formatter.string(fromByteCount: Int64(bytes))
|
|
}
|
|
|
|
private func pressureColor(_ level: String) -> Color {
|
|
switch level {
|
|
case "Critical": return .red
|
|
case "Warning": return .orange
|
|
default: return .green
|
|
}
|
|
}
|
|
}
|