Files
SystemsMonitor/PegasusMonitor/MonitorMenuView.swift
T
2026-10-21 14:54:07 +01:00

154 lines
8.4 KiB
Swift

import SwiftUI
struct MonitorMenuView: View {
@ObservedObject var monitor: ServerMonitor
@Environment(\.openWindow) private var openWindow
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack {
VStack(alignment: .leading, spacing: 5) {
Label("PEGASUS MONITOR", systemImage: "server.rack").font(.headline)
Label(monitor.status.title, systemImage: monitor.status.symbol)
.foregroundStyle(monitor.status.color)
}
Spacer()
if monitor.offlineCount > 0 {
Text("\(monitor.offlineCount) offline").foregroundStyle(.red).font(.caption.bold())
}
}.padding(14)
Divider()
Text("SERVERS").font(.caption.bold()).foregroundStyle(.secondary).padding(.horizontal, 14).padding(.top, 12)
ScrollView {
LazyVStack(spacing: 2) {
ForEach(monitor.servers) { server in
Button {
monitor.select(server.id)
} label: {
HStack {
Image(systemName: monitor.results[server.id]?.status.symbol ?? "circle.dotted")
.foregroundStyle(server.enabled ? (monitor.results[server.id]?.status.color ?? .secondary) : .gray)
VStack(alignment: .leading, spacing: 2) {
Text(server.name).fontWeight(server.id == monitor.selectedID ? .bold : .regular)
Text(server.host).font(.caption2).foregroundStyle(.secondary)
}
Spacer()
if monitor.results[server.id]?.refreshing == true { ProgressView().controlSize(.mini) }
else if let report = monitor.results[server.id]?.report {
VStack(alignment: .trailing, spacing: 2) {
Text("\(report.counts.ok) OK").font(.caption2)
if let uptime = report.uptimeLabel {
Text("Up \(uptime)").font(.caption2)
}
}.foregroundStyle(.secondary)
}
if server.id == monitor.selectedID { Image(systemName: "checkmark") }
}.padding(.horizontal, 14).padding(.vertical, 8)
.contentShape(Rectangle())
}.buttonStyle(.plain)
}
}
}.frame(maxHeight: 175)
Divider()
if let selected = monitor.selected {
HStack {
Text(selected.name.uppercased()).font(.caption.bold())
Spacer()
Button { Task { await monitor.refreshSelected() } } label: { Image(systemName: "arrow.clockwise") }
.buttonStyle(.plain)
}.padding(.horizontal, 14).padding(.vertical, 10)
ScrollView {
LazyVStack(alignment: .leading, spacing: 0) {
if let report = monitor.report {
HStack(spacing: 8) {
Image(systemName: "clock.arrow.circlepath")
.foregroundStyle(.secondary)
Text("Uptime").font(.system(size: 12, weight: .medium))
Spacer()
Text(report.uptimeLabel ?? "Not available")
.font(.system(size: 12, weight: .medium, design: .monospaced))
.foregroundStyle(.secondary)
}
.padding(.horizontal, 14).padding(.vertical, 10)
Divider()
if !monitor.selectedServices.isEmpty {
Text("SERVICES")
.font(.caption.bold()).foregroundStyle(.secondary)
.padding(.horizontal, 14).padding(.vertical, 8)
ForEach(monitor.selectedServices) { service in
HStack(spacing: 8) {
Image(systemName: service.symbol).foregroundStyle(service.color)
Text(service.name).font(.system(size: 12))
Spacer()
if selected.requiredServices.contains(service.name) {
Text("REQUIRED").font(.system(size: 9, weight: .bold)).foregroundStyle(.orange)
}
Text(service.title).font(.caption).foregroundStyle(.secondary)
}
.padding(.horizontal, 14).padding(.vertical, 5)
}
Divider()
Text("HEALTH GROUPS")
.font(.caption.bold()).foregroundStyle(.secondary)
.padding(.horizontal, 14).padding(.vertical, 8)
}
ForEach(report.groups) { group in
HStack {
Text(group.name).font(.system(size: 12, weight: .medium))
Spacer()
Label(group.status.title, systemImage: group.status.symbol)
.foregroundStyle(group.status.color).font(.caption)
}.padding(.horizontal, 14).padding(.vertical, 8)
Divider().padding(.leading, 14)
}
} else {
Text(monitor.selectedResult?.error ?? "Waiting for health data…")
.font(.caption).foregroundStyle(.secondary).padding(14)
}
}
}.frame(height: 300)
}
Divider()
let totals = monitor.aggregate
Text("ALL SERVERS: \(totals.ok) OK · \(totals.warn) WARN · \(totals.fail) FAIL")
.font(.caption.bold()).padding(14)
if let error = monitor.selectedResult?.error {
Text(error).foregroundStyle(.red).font(.caption).textSelection(.enabled)
.padding(.horizontal, 14).padding(.bottom, 8)
}
Divider()
HStack {
Button("Refresh All") { Task { await monitor.refreshAll() } }
Spacer()
Button("Diagnostics") { openWindow(id: "monitor-diagnostics") }
Button("Settings") { openWindow(id: "monitor-settings") }
Button("Quit") { NSApp.terminate(nil) }
}.buttonStyle(.borderless).padding(14)
}.frame(width: 410)
}
}
struct DiagnosticsView: View {
@ObservedObject var monitor: ServerMonitor
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("\(monitor.selected?.name ?? "Server") — Diagnostics").font(.title2.bold())
Text("Select a server in the menu bar to change this report.").font(.caption).foregroundStyle(.secondary)
ScrollView {
LazyVStack(alignment: .leading, spacing: 10) {
ForEach(monitor.report?.checks ?? []) { check in
HStack(alignment: .top) {
Image(systemName: check.status.symbol).foregroundStyle(check.status.color)
VStack(alignment: .leading, spacing: 2) {
Text(check.name).fontWeight(.medium)
Text(check.detail).font(.caption).foregroundStyle(.secondary)
}
Spacer()
}
}
}
}.textSelection(.enabled)
}.padding(20).frame(minWidth: 650, minHeight: 450)
}
}