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.
This commit is contained in:
Mark Gregory
2026-10-17 16:25:02 +01:00
parent 283fa76162
commit 2d5d48949a
2 changed files with 91 additions and 79 deletions
+82 -79
View File
@@ -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 {
+9
View File
@@ -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.