diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index e843c20..496b36f 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -2307,6 +2307,7 @@ struct WorkbenchAdministrationView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } else { resultContent + .layoutPriority(1) } if let guidance = diagnosticGuidance { @@ -2560,15 +2561,22 @@ struct WorkbenchAdministrationView: View { ) .frame(maxWidth: .infinity, maxHeight: .infinity) case .dashboard: - ScrollView { - VStack(alignment: .leading, spacing: 18) { - WorkbenchInlinePerformanceDashboard(samples: diagnosticSamples) - // Dashboard must not show the legacy, unformatted Variable_name table. - Text("Server overview").font(.title2.bold()) + // Keep the live charts visible even while scrolling the overview. + // The old outer ScrollView moved all charts offscreen when scrolled. + VStack(alignment: .leading, spacing: 10) { + WorkbenchInlinePerformanceDashboard(samples: diagnosticSamples) + .frame(maxWidth: .infinity, alignment: .topLeading) + .layoutPriority(2) + Divider() + Text("Server overview").font(.title2.bold()) + ScrollView(.vertical) { WorkbenchServerOverview(rows: filteredRows) + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding(18) + .frame(maxWidth: .infinity, maxHeight: .infinity) } + .padding(16) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) default: // Status and System Variables is deliberately a distinct variable // inspector, never replaced by the monitoring screen. @@ -4134,9 +4142,9 @@ private struct WorkbenchInlinePerformanceDashboard: View { .lineStyle(StrokeStyle(lineWidth: 2.5)) } .chartYAxis { AxisMarks(position: .leading) } - .frame(height: 130) + .frame(height: 92) } - .padding(14) + .padding(10) .frame(maxWidth: .infinity, alignment: .leading) .background(Color(nsColor: .controlBackgroundColor)) .clipShape(RoundedRectangle(cornerRadius: 12)) @@ -4151,7 +4159,9 @@ private struct WorkbenchInlinePerformanceDashboard: View { Text("\(samples.count) samples · refreshes every 5 seconds") .font(.caption).foregroundStyle(.secondary) } - LazyVGrid(columns: [GridItem(.adaptive(minimum: 285), spacing: 14)], spacing: 12) { + LazyVGrid(columns: [GridItem(.flexible(minimum: 0), spacing: 12), + GridItem(.flexible(minimum: 0), spacing: 12), + GridItem(.flexible(minimum: 0), spacing: 12)], spacing: 10) { panel("Queries per second", metric: "Queries", rate: true, tint: .blue) panel("Active connections", metric: "Threads_connected", rate: false, tint: .teal) panel("Running threads", metric: "Threads_running", rate: false, tint: .orange) @@ -4320,83 +4330,76 @@ private struct WorkbenchLiveMonitorScreen: View { } } - // Charts and counters are sibling regions. Neither is wrapped in the - // other region's ScrollView; the charts stay on-screen while rows scroll. + // Fixed-height chart cards live OUTSIDE the independently scrolling table. + // Avoid GeometryReader and nested graph ScrollViews: they previously allowed + // the graph viewport to collapse when the parent administration view laid out. var body: some View { - GeometryReader { geometry in - let width = max(0, geometry.size.width - 32) - let twoColumns = width >= 640 - let gridColumns = Array(repeating: GridItem(.flexible(minimum: 0), spacing: 12), count: twoColumns ? 2 : 1) - let cardHeight: CGFloat = twoColumns ? 160 : 125 - let chartAreaHeight: CGFloat = twoColumns ? 334 : min(285, max(150, geometry.size.height * 0.40)) + VStack(alignment: .leading, spacing: 10) { + HStack(spacing: 10) { + Label(pageTitle + " · LIVE GRAPHS", systemImage: "chart.xyaxis.line") + .font(.title3.bold()) + Spacer(minLength: 4) + Text("\(samples.count) samples · 5s refresh") + .font(.caption) + .foregroundStyle(.secondary) + } - VStack(alignment: .leading, spacing: 10) { - HStack(spacing: 10) { - Label(pageTitle, systemImage: "chart.xyaxis.line") - .font(.title3.bold()) - Spacer(minLength: 4) - Text("Live · \(samples.count) samples · 5s") - .font(.caption).foregroundStyle(.secondary) + if page == .serverStatus || page == .serverCounters { + Picker("Graphs", selection: $chartGroup) { + Text("Activity").tag("Activity") + Text("Network").tag("Network") + Text("InnoDB").tag("InnoDB") } - .frame(maxWidth: .infinity) + .pickerStyle(.segmented) + .frame(maxWidth: 350) + } - if page == .serverStatus || page == .serverCounters { - Picker("Graphs", selection: $chartGroup) { - Text("Activity").tag("Activity") - Text("Network").tag("Network") - Text("InnoDB").tag("InnoDB") - } - .pickerStyle(.segmented) - .frame(maxWidth: 350) - } - - // A bounded graph viewport, with two complete columns on - // desktop. On narrow windows, only this area scrolls. - ScrollView(.vertical) { - LazyVGrid(columns: gridColumns, spacing: 12) { - ForEach(metrics, id: \.self) { metric in - chart(metric) - .frame(height: cardHeight) - .frame(maxWidth: .infinity) - } - } - .frame(maxWidth: .infinity) - } - .frame(height: min(chartAreaHeight, max(135, geometry.size.height * 0.54))) - .accessibilityIdentifier("monitor-graphs-visible") - - Divider() - if page == .serverStatus { - Text("Server overview") - .font(.headline) - ScrollView(.vertical) { - WorkbenchServerOverview(rows: rows) - .frame(maxWidth: .infinity, alignment: .leading) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else { - HStack { - Text(page == .connectionStatistics ? "Connection statistics" : page == .innodbMetrics ? "InnoDB statistics" : "All MySQL server counters") - .font(.headline) - Spacer(minLength: 6) - if page == .serverCounters { - Picker("Category", selection: $categorySelection) { - ForEach(categories, id: \.self) { Text($0).tag($0) } - } - .frame(width: 155) - } - Text("\(visibleRows.count) counters") - .font(.caption).foregroundStyle(.secondary) - } - WorkbenchColouredCounterTable(columns: ["Metric", "Value"], rows: visibleRows) - .frame(maxWidth: .infinity, maxHeight: .infinity) + // Exactly two rows of two graphs. No graph-region scrolling and no + // dependence on the size proposed by a surrounding GeometryReader. + LazyVGrid( + columns: [GridItem(.flexible(minimum: 0), spacing: 12), + GridItem(.flexible(minimum: 0), spacing: 12)], + spacing: 12 + ) { + ForEach(metrics, id: \.self) { metric in + chart(metric) + .frame(height: 148) + .frame(maxWidth: .infinity) } } - .padding(16) - .frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading) - } - } + .frame(maxWidth: .infinity) + .accessibilityIdentifier("monitor-graphs-visible") + Divider() + + if page == .serverStatus { + Text("Server overview").font(.headline) + ScrollView(.vertical) { + WorkbenchServerOverview(rows: rows) + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + HStack { + Text(page == .connectionStatistics ? "Connection statistics" : page == .innodbMetrics ? "InnoDB statistics" : "All MySQL server counters") + .font(.headline) + Spacer(minLength: 6) + if page == .serverCounters { + Picker("Category", selection: $categorySelection) { + ForEach(categories, id: \.self) { Text($0).tag($0) } + } + .frame(width: 155) + } + Text("\(visibleRows.count) counters") + .font(.caption).foregroundStyle(.secondary) + } + WorkbenchColouredCounterTable(columns: ["Metric", "Value"], rows: visibleRows) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .padding(16) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } } private struct WorkbenchServerCountersScreen: View { diff --git a/docs/PHASE44-GRAPHS-LAYOUT.md b/docs/PHASE44-GRAPHS-LAYOUT.md new file mode 100644 index 0000000..7710ae6 --- /dev/null +++ b/docs/PHASE44-GRAPHS-LAYOUT.md @@ -0,0 +1,9 @@ +# Phase 44 — Monitoring graphs layout + +- Removed the GeometryReader and nested graph ScrollView from the active `WorkbenchLiveMonitorScreen`. +- Four live charts now render in a fixed two-column, two-row grid at the top of Server Status, Server Counters, Connection Statistics and InnoDB Metrics. +- The counters/overview region below the charts scrolls independently. +- Gave administration result content higher layout priority than the footer and auxiliary controls. +- Kept the existing five-second sample collection and graph calculation logic. + +Validation: `swiftc -frontend -parse` passed for the Swift source files on Linux. macOS SDK compilation and on-screen appearance have not been verified.