updates
This commit is contained in:
@@ -3,6 +3,27 @@ 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) {
|
||||
@@ -70,7 +91,72 @@ struct MonitorMenuView: View {
|
||||
if server.id == monitor.selectedID { Image(systemName: "checkmark") }
|
||||
}.padding(.horizontal, 14).padding(.vertical, 8)
|
||||
.contentShape(Rectangle())
|
||||
}.buttonStyle(.plain)
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +198,20 @@ struct MonitorMenuView: View {
|
||||
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")
|
||||
@@ -124,7 +224,57 @@ struct MonitorMenuView: View {
|
||||
Spacer()
|
||||
Label(group.status.title, systemImage: group.status.symbol)
|
||||
.foregroundStyle(group.status.color).font(.caption)
|
||||
}.padding(.horizontal, 14).padding(.vertical, 8)
|
||||
}
|
||||
.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 {
|
||||
@@ -150,12 +300,18 @@ struct MonitorMenuView: View {
|
||||
Button("Settings") { openWindow(id: "monitor-settings") }
|
||||
Button("Quit") { NSApp.terminate(nil) }
|
||||
}.buttonStyle(.borderless).padding(14)
|
||||
}.frame(width: 410)
|
||||
}
|
||||
.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())
|
||||
@@ -171,9 +327,133 @@ struct DiagnosticsView: View {
|
||||
}
|
||||
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)
|
||||
}
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user