import SwiftUI struct MonitorMenuView: View { @ObservedObject var monitor: ServerMonitor @Environment(\.openWindow) private var openWindow @State private var detailItem: MonitorDetailItem? private func showDetails( title: String, status: String, details: String ) { detailItem = MonitorDetailItem( title: title, status: status, details: details ) } private func copyDetails(_ details: String) { NSPasteboard.general.clearContents() NSPasteboard.general.setString( details, forType: .string ) } 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() HStack { Text("SERVERS") .font(.caption.bold()) .foregroundStyle(.secondary) Spacer() Menu { ForEach(monitor.servers) { server in Button { monitor.select(server.id) } label: { if server.id == monitor.selectedID { Label(server.name, systemImage: "checkmark") } else { Text(server.name) } } } } label: { Label("Switch Server", systemImage: "chevron.down.circle") .font(.caption) } } .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) .contextMenu { Button("Show Details") { let result = monitor.results[server.id] let report = result?.report let failures = report?.checks.filter { $0.status != .ok } ?? [] let checkDetails = failures.map { "\($0.section) / \($0.name) [\($0.status.title)]\\n\($0.detail)" }.joined(separator: "\\n\\n") let missingGroups = report?.groups .filter { $0.status != .ok } .map { "\($0.name): \($0.missing.joined(separator: ", "))" } .joined(separator: "\\n") ?? "" let details = [ "Server: \(server.name)", "Host: \(server.host)", "Status: \(result?.status.title ?? "Unknown")", result?.error ?? "", missingGroups, checkDetails, report?.diagnostics ?? "" ] .filter { !$0.isEmpty } .joined(separator: "\\n\\n") showDetails( title: server.name, status: result?.status.title ?? "Unknown", details: details ) } Button("Copy Error Details") { let result = monitor.results[server.id] let failures = result?.report?.checks .filter { $0.status != .ok } .map { "\($0.name): \($0.detail)" } .joined(separator: "\\n") ?? "" copyDetails( result?.error ?? (failures.isEmpty ? "No errors reported." : failures) ) } Divider() Button("Refresh Server") { Task { await monitor.refresh(id: server.id) } } } } } } .frame(height: min(CGFloat(max(monitor.servers.count, 1)) * 58, 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) .contentShape(Rectangle()) .contextMenu { Button("Show Details") { showDetails( title: service.name, status: service.title, details: service.detail ) } Button("Copy Details") { copyDetails(service.detail) } } } 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) .contentShape(Rectangle()) .contextMenu { Button("Show Details") { let related = report.checks.filter { group.missing.contains($0.name) || ( $0.status != .ok && $0.section == group.name ) } let messages = related.map { "\($0.name): \($0.detail)" }.joined(separator: "\\n\\n") let missing = group.missing.isEmpty ? "" : "Missing: " + group.missing.joined(separator: ", ") let details = [ "Group: \(group.name)", "Status: \(group.status.title)", missing, messages.isEmpty ? (group.status == .ok ? "All required checks passed." : "No matching individual check details were returned.") : messages ] .filter { !$0.isEmpty } .joined(separator: "\\n\\n") showDetails( title: group.name, status: group.status.title, details: details ) } Button("Copy Details") { copyDetails( "\(group.name): \(group.status.title)\\n" + group.missing.joined(separator: "\\n") ) } } 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) .sheet(item: $detailItem) { item in MonitorDetailSheet(item: item) } } } struct DiagnosticsView: View { @ObservedObject var monitor: ServerMonitor @State private var detailItem: MonitorDetailItem? 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() } .contentShape(Rectangle()) .contextMenu { Button("Show Details") { detailItem = MonitorDetailItem( title: check.name, status: check.status.title, details: """ Section: \(check.section) \(check.detail) """ ) } Button("Copy Details") { NSPasteboard.general.clearContents() NSPasteboard.general.setString( check.detail, forType: .string ) } } } } }.textSelection(.enabled) } .padding(20) .frame(minWidth: 650, minHeight: 450) .sheet(item: $detailItem) { item in MonitorDetailSheet(item: item) } } } // MARK: - Monitoring Details struct MonitorDetailItem: Identifiable { let id = UUID() let title: String let status: String let details: String } struct MonitorDetailSheet: View { let item: MonitorDetailItem @Environment(\.dismiss) private var dismiss var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { Image(systemName: icon) .font(.title2) .foregroundStyle(statusColor) VStack(alignment: .leading, spacing: 4) { Text(item.title) .font(.title2.bold()) Text(item.status) .foregroundStyle(statusColor) } Spacer() } Divider() ScrollView { Text(item.details.isEmpty ? "No additional diagnostic information available." : item.details) .font(.system( size: 12, design: .monospaced )) .frame( maxWidth: .infinity, alignment: .leading ) .textSelection(.enabled) .padding(12) } .background(.quaternary.opacity(0.3)) .clipShape(RoundedRectangle(cornerRadius: 8)) HStack { Button("Copy Details") { NSPasteboard.general.clearContents() NSPasteboard.general.setString( item.details, forType: .string ) } Spacer() Button("Close") { dismiss() } .keyboardShortcut(.defaultAction) } } .padding(20) .frame(minWidth: 540, minHeight: 350) } private var icon: String { switch item.status.lowercased() { case "critical", "failed": return "xmark.circle.fill" case "warning", "stopped": return "exclamationmark.triangle.fill" default: return "checkmark.circle.fill" } } private var statusColor: Color { switch item.status.lowercased() { case "critical", "failed": return .red case "warning", "stopped": return .orange default: return .green } } }