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)