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-18 11:25:03 +01:00
parent 40386134a8
commit 08a73d5171
2 changed files with 67 additions and 14 deletions
+60 -14
View File
@@ -2573,9 +2573,9 @@ struct WorkbenchAdministrationView: View {
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 14) {
Label("Server overview", systemImage: "server.rack")
Label("Server overview · Live graphs", systemImage: "chart.xyaxis.line")
.font(.title3.bold())
WorkbenchServerOverview(rows: filteredRows)
WorkbenchServerOverview(rows: filteredRows, samples: diagnosticSamples)
if page == .serverStatus {
Divider()
HStack {
@@ -4214,22 +4214,68 @@ private struct WorkbenchInlinePerformanceDashboard: View {
// the counter ScrollView, so scrolling hundreds of counters cannot hide charts.
private struct WorkbenchServerOverview: View {
let rows: [[String]]
private let names = ["Uptime", "Threads_connected", "Threads_running", "Queries", "Slow_queries", "Bytes_received", "Bytes_sent", "Connections"]
let samples: [DiagnosticSample]
private let names = [
"Uptime", "Threads_connected", "Threads_running", "Queries",
"Slow_queries", "Bytes_received", "Bytes_sent", "Connections"
]
private struct OverviewPoint: Identifiable {
let id: Int
let time: Date
let value: Double
}
private func points(for name: String) -> [OverviewPoint] {
samples.enumerated().compactMap { index, sample in
guard let row = sample.rows.first(where: {
$0.count > 1 && $0[0].caseInsensitiveCompare(name) == .orderedSame
}), let value = Double(row[1]), value.isFinite else { return nil }
return OverviewPoint(id: index, time: sample.at, value: value)
}
}
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 175), spacing: 12)], spacing: 12) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 260), spacing: 14)], spacing: 14) {
ForEach(names, id: \.self) { name in
let raw = rows.first(where: { $0.count >= 2 && $0[0].caseInsensitiveCompare(name) == .orderedSame })?.last ?? "—"
let raw = rows.first(where: {
$0.count >= 2 && $0[0].caseInsensitiveCompare(name) == .orderedSame
})?.last ?? "—"
let tint = WorkbenchCounterCharts.tint(for: name)
VStack(alignment: .leading, spacing: 8) {
Text(name.replacingOccurrences(of: "_", with: " "))
.font(.caption).foregroundStyle(.secondary)
Text(WorkbenchCounterCharts.displayValue(raw, for: name))
.font(.title3.bold().monospacedDigit())
.foregroundStyle(tint)
let series = points(for: name)
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .firstTextBaseline) {
Text(name.replacingOccurrences(of: "_", with: " "))
.font(.subheadline.weight(.semibold))
Spacer(minLength: 8)
Text(WorkbenchCounterCharts.displayValue(raw, for: name))
.font(.headline.monospacedDigit())
.foregroundStyle(tint)
}
Chart(series) { point in
AreaMark(x: .value("Time", point.time), y: .value("Value", point.value))
.foregroundStyle(tint.opacity(0.12))
LineMark(x: .value("Time", point.time), y: .value("Value", point.value))
.foregroundStyle(tint)
.lineStyle(StrokeStyle(lineWidth: 2.5))
PointMark(x: .value("Time", point.time), y: .value("Value", point.value))
.foregroundStyle(tint)
}
.chartYAxis { AxisMarks(position: .leading) }
.frame(height: 135)
.overlay {
if series.isEmpty {
Text("Waiting for live samples…")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(14)
.background(tint.opacity(0.07), in: RoundedRectangle(cornerRadius: 12))
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(nsColor: .controlBackgroundColor), in: RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(tint.opacity(0.20)))
}
}
}
@@ -4407,7 +4453,7 @@ private struct WorkbenchLiveMonitorScreen: View {
if page == .serverStatus {
Text("Server overview").font(.headline)
ScrollView(.vertical) {
WorkbenchServerOverview(rows: rows)
WorkbenchServerOverview(rows: rows, samples: samples)
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
+7
View File
@@ -0,0 +1,7 @@
# Phase 50 — Server overview line graphs
The eight Server Overview statistic cards are replaced with actual Swift Charts time-series graphs. Each graph retains the current numeric value in its heading and plots up to 60 sampled values over time. This applies to Dashboard and Server Status. The six existing live rate charts remain unchanged above the overview graphs. The overview also appears in the other monitoring screens where it was previously used.
Charts show raw counter trends (cumulative values for Queries, Bytes, Connections, etc.); the six charts above show per-second rates where applicable. Graphs populate as five-second samples arrive.
Validation: `swiftc -frontend -parse WorkbenchReplacement/MainView.swift` passed in Linux. Native macOS compilation and UI testing have not been performed.