From 2d5d48949aa722727e0d847f2853e27f094d7e18 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Sat, 17 Oct 2026 16:25:02 +0100 Subject: [PATCH] Improved progress display Previously, the progress bar updated only after completing an entire table. For large tables such as LIBARC, this could make the application appear to have stopped responding. Phase 10 updates the interface while rows are being exported. --- WorkbenchReplacement/MainView.swift | 161 ++++++++++++++-------------- docs/PHASE44-GRAPHS-LAYOUT.md | 9 ++ 2 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 docs/PHASE44-GRAPHS-LAYOUT.md 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.