228 lines
8.4 KiB
Swift
228 lines
8.4 KiB
Swift
import SwiftUI
|
|
|
|
public struct ContentView: View {
|
|
@State private var store = SystemTelemetryStore.shared
|
|
|
|
public init() {}
|
|
|
|
public var body: some View {
|
|
NavigationSplitView {
|
|
sidebarContent
|
|
} detail: {
|
|
detailContent
|
|
}
|
|
.frame(minWidth: 800, minHeight: 520)
|
|
.onAppear {
|
|
store.start()
|
|
}
|
|
}
|
|
|
|
private var sidebarContent: some View {
|
|
List {
|
|
Section("System Status") {
|
|
Label("Dashboard", systemImage: "gauge.with.needle")
|
|
Label("Architecture", systemImage: "cpu")
|
|
}
|
|
|
|
Section("Telemetry Domains") {
|
|
Label("CPU & Thermals", systemImage: "flame")
|
|
Label("Fans & Cooling", systemImage: "fanblades")
|
|
Label("Memory & Swap", systemImage: "memorychip")
|
|
Label("Disks & Volumes", systemImage: "internaldrive")
|
|
Label("Processes", systemImage: "list.bullet.rectangle")
|
|
Label("Network", systemImage: "network")
|
|
Label("Graphics (GPU)", systemImage: "display")
|
|
}
|
|
}
|
|
.listStyle(.sidebar)
|
|
.navigationSplitViewColumnWidth(min: 200, ideal: 220, max: 280)
|
|
}
|
|
|
|
private var detailContent: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 20) {
|
|
// Header Banner
|
|
headerCard
|
|
|
|
// Telemetry Engine Control
|
|
engineControlCard
|
|
|
|
// System Specifications
|
|
specsCard
|
|
|
|
// Providers Status
|
|
providersCard
|
|
}
|
|
.padding(24)
|
|
}
|
|
.navigationTitle("MacMonitor Dashboard")
|
|
}
|
|
|
|
private var headerCard: some View {
|
|
HStack(alignment: .center, spacing: 16) {
|
|
Image(systemName: "macpro.gen3.fill")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 48, height: 48)
|
|
.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 ? "ENGINE ACTIVE" : "ENGINE 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))
|
|
}
|
|
|
|
private var engineControlCard: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Telemetry Coordinator Engine")
|
|
.font(.headline)
|
|
|
|
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("Polling 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)
|
|
}
|
|
|
|
HStack {
|
|
Text("Last Sample Cycle: \(store.lastUpdateTimestamp.formatted(date: .omitted, time: .standard))")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
Text("Queue: com.i3omb.macmonitor.telemetry (QOS_UTILITY)")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.padding(16)
|
|
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
if store.latestSnapshot.isEmpty {
|
|
HStack(spacing: 12) {
|
|
Image(systemName: "info.circle")
|
|
.foregroundStyle(.secondary)
|
|
Text("Telemetry coordinator initialized. Awaiting provider metrics.")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding(.vertical, 8)
|
|
} else {
|
|
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))
|
|
}
|
|
}
|