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:
@@ -52,7 +52,7 @@ struct MainView: View {
|
||||
@State private var outputExpanded = true
|
||||
|
||||
@State private var navigatorMode = 1
|
||||
@State private var administrationPage: WorkbenchAdminPage? = nil
|
||||
@State private var administrationPage: WorkbenchAdminPage? = .dashboard
|
||||
|
||||
|
||||
var body: some View {
|
||||
@@ -142,6 +142,11 @@ struct MainView: View {
|
||||
.fill(WB.border)
|
||||
.frame(width: 1, height: 30)
|
||||
|
||||
toolbarButton("Dashboard", icon: "chart.xyaxis.line") {
|
||||
navigatorMode = 0
|
||||
administrationPage = .dashboard
|
||||
}
|
||||
|
||||
toolbarButton(
|
||||
"New Connection",
|
||||
icon: "plus.rectangle.on.rectangle"
|
||||
@@ -2215,7 +2220,7 @@ struct WorkbenchAdministrationView: View {
|
||||
@State private var errorMessage: String?
|
||||
@State private var lastUpdated: Date?
|
||||
@State private var searchText = ""
|
||||
@State private var autoRefresh = false
|
||||
@State private var autoRefresh = true
|
||||
@State private var selectedAccount: String? = nil
|
||||
@State private var grants: [String] = []
|
||||
@State private var grantsError: String? = nil
|
||||
@@ -2307,7 +2312,7 @@ struct WorkbenchAdministrationView: View {
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
if supportsTrends && !diagnosticSamples.isEmpty {
|
||||
if supportsTrends && !diagnosticSamples.isEmpty && page != .dashboard && page != .serverStatus {
|
||||
HStack(spacing: 12) {
|
||||
Label("\(diagnosticSamples.count) samples", systemImage: "chart.xyaxis.line")
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -2539,7 +2544,11 @@ struct WorkbenchAdministrationView: View {
|
||||
private var resultContent: some View {
|
||||
if page == .dashboard || page == .serverStatus {
|
||||
ScrollView {
|
||||
LazyVGrid(
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
WorkbenchInlinePerformanceDashboard(samples: diagnosticSamples)
|
||||
Text("Server counters")
|
||||
.font(.headline)
|
||||
LazyVGrid(
|
||||
columns: [
|
||||
GridItem(.adaptive(minimum: 180), spacing: 12)
|
||||
],
|
||||
@@ -2586,6 +2595,7 @@ struct WorkbenchAdministrationView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(18)
|
||||
}
|
||||
} else {
|
||||
@@ -4103,6 +4113,90 @@ private struct WorkbenchAccountManagerView: View {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Phase 28: Front-screen performance charts
|
||||
|
||||
private struct WorkbenchInlinePerformanceDashboard: View {
|
||||
let samples: [DiagnosticSample]
|
||||
|
||||
private struct Reading: Identifiable {
|
||||
let id: Int
|
||||
let time: Date
|
||||
let value: Double
|
||||
}
|
||||
|
||||
private func counter(_ sample: DiagnosticSample, _ name: String) -> Double? {
|
||||
guard let row = sample.rows.first(where: {
|
||||
$0.count > 1 && $0[0].caseInsensitiveCompare(name) == .orderedSame
|
||||
}) else { return nil }
|
||||
return Double(row[1])
|
||||
}
|
||||
|
||||
private func points(_ name: String, rate: Bool) -> [Reading] {
|
||||
samples.indices.compactMap { i in
|
||||
guard let current = counter(samples[i], name) else { return nil }
|
||||
if !rate { return Reading(id: i, time: samples[i].at, value: current) }
|
||||
guard i > 0, let previous = counter(samples[i - 1], name) else { return nil }
|
||||
let interval = samples[i].at.timeIntervalSince(samples[i - 1].at)
|
||||
guard interval > 0, current >= previous else { return nil }
|
||||
return Reading(id: i, time: samples[i].at, value: (current - previous) / interval)
|
||||
}
|
||||
}
|
||||
|
||||
private func panel(_ title: String, metric: String, rate: Bool, tint: Color) -> some View {
|
||||
let readings = points(metric, rate: rate)
|
||||
return VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
Text(title).font(.headline)
|
||||
Spacer()
|
||||
Circle().fill(tint).frame(width: 9, height: 9)
|
||||
}
|
||||
if let last = readings.last {
|
||||
Text(last.value.formatted(.number.precision(.fractionLength(1))) + (rate ? " /sec" : ""))
|
||||
.font(.system(size: 24, weight: .semibold, design: .rounded))
|
||||
.foregroundStyle(tint)
|
||||
} else {
|
||||
Text("Collecting samples…")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
Chart(readings) { reading in
|
||||
AreaMark(x: .value("Time", reading.time), y: .value("Value", reading.value))
|
||||
.foregroundStyle(tint.opacity(0.14))
|
||||
LineMark(x: .value("Time", reading.time), y: .value("Value", reading.value))
|
||||
.foregroundStyle(tint)
|
||||
.lineStyle(StrokeStyle(lineWidth: 2.5))
|
||||
}
|
||||
.chartYAxis { AxisMarks(position: .leading) }
|
||||
.frame(height: 130)
|
||||
}
|
||||
.padding(14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(nsColor: .controlBackgroundColor))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
Label("Live server performance", systemImage: "waveform.path.ecg")
|
||||
.font(.title2.bold())
|
||||
Spacer()
|
||||
Text("\(samples.count) samples · refreshes every 5 seconds")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 270), spacing: 12)], spacing: 12) {
|
||||
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)
|
||||
panel("Slow queries per second", metric: "Slow_queries", rate: true, tint: .red)
|
||||
panel("Bytes received per second", metric: "Bytes_received", rate: true, tint: .purple)
|
||||
panel("Bytes sent per second", metric: "Bytes_sent", rate: true, tint: .green)
|
||||
}
|
||||
Text("Rates are calculated from consecutive MySQL status snapshots. Counters reset after server restarts; graphs show up to 60 in-session samples.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Phase 25/26: Live performance samples and counter rates
|
||||
|
||||
private struct DiagnosticSample: Identifiable {
|
||||
|
||||
Reference in New Issue
Block a user