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:
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
@main
|
||||
struct WorkbenchReplacementApp: App {
|
||||
@@ -7,6 +8,12 @@ struct WorkbenchReplacementApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup("Workbench Replacement") {
|
||||
WorkbenchLaunchView()
|
||||
.onAppear {
|
||||
if let iconURL = Bundle.main.url(forResource: "WorkbenchReplacement", withExtension: "icns"),
|
||||
let icon = NSImage(contentsOf: iconURL) {
|
||||
NSApplication.shared.applicationIconImage = icon
|
||||
}
|
||||
}
|
||||
.environmentObject(model)
|
||||
.frame(minWidth: 800, minHeight: 520)
|
||||
}
|
||||
|
||||
@@ -130,9 +130,10 @@ struct MainView: View {
|
||||
|
||||
private var mainToolbar: some View {
|
||||
HStack(spacing: 16) {
|
||||
Image(systemName: "externaldrive.connected.to.line.below")
|
||||
.font(.system(size: 23))
|
||||
.foregroundStyle(.blue)
|
||||
Image(nsImage: NSImage(named: "WorkbenchBrand") ?? NSImage(contentsOfFile: Bundle.main.path(forResource: "WorkbenchBrand", ofType: "png") ?? "") ?? NSImage())
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 30, height: 30)
|
||||
|
||||
Text("MySQL Workbench")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
@@ -2540,31 +2541,8 @@ struct WorkbenchAdministrationView: View {
|
||||
private var resultContent: some View {
|
||||
switch page {
|
||||
case .serverCounters:
|
||||
// Keep live charts permanently visible while the complete status table scrolls.
|
||||
// Previously both were inside a single ScrollView, so scrolling to the
|
||||
// counters made it appear as though the graphs had disappeared.
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
Label("Live Server Counters", systemImage: "chart.xyaxis.line")
|
||||
.font(.title2.bold())
|
||||
Spacer()
|
||||
Text("\(diagnosticSamples.count) samples · updates every 5 seconds")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
WorkbenchPinnedCounterGraphs(samples: diagnosticSamples)
|
||||
.frame(height: 222)
|
||||
HStack {
|
||||
Text("All MySQL server statistics")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Text("\(filteredRows.count) counters")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
WorkbenchColouredCounterTable(columns: result.columns, rows: filteredRows)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
.padding(14)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
WorkbenchServerCountersScreen(samples: diagnosticSamples, rows: filteredRows)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
case .dashboard:
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
@@ -4177,7 +4155,7 @@ private struct WorkbenchInlinePerformanceDashboard: View {
|
||||
Text("\(samples.count) samples · refreshes every 5 seconds")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 270), spacing: 12)], spacing: 12) {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 360), spacing: 14)], 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)
|
||||
@@ -4191,6 +4169,81 @@ private struct WorkbenchInlinePerformanceDashboard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Phase 34: Responsive server-counter workspace
|
||||
private struct WorkbenchServerCountersScreen: View {
|
||||
let samples: [DiagnosticSample]
|
||||
let rows: [[String]]
|
||||
@State private var category = "All"
|
||||
private let categories = ["All", "Connections", "Queries", "InnoDB", "Network", "Errors", "Replication", "Other"]
|
||||
|
||||
private func group(_ name: String) -> String {
|
||||
let n = name.lowercased()
|
||||
if n.hasPrefix("innodb_") { return "InnoDB" }
|
||||
if n.contains("replica") || n.contains("slave") || n.contains("binlog") || n.contains("relay_log") { return "Replication" }
|
||||
if n.contains("abort") || n.contains("error") || n.contains("failed") || n.contains("denied") { return "Errors" }
|
||||
if n.contains("bytes_") || n.contains("network") { return "Network" }
|
||||
if n.contains("connection") || n.contains("thread") || n.contains("client") { return "Connections" }
|
||||
if n.contains("quer") || n.contains("question") || n.hasPrefix("com_") || n.contains("select") || n.contains("sort") || n.contains("tmp_") { return "Queries" }
|
||||
return "Other"
|
||||
}
|
||||
private func value(_ name: String) -> String {
|
||||
guard let row = rows.first(where: { $0.count >= 2 && $0[0].caseInsensitiveCompare(name) == .orderedSame }) else { return "—" }
|
||||
return WorkbenchCounterCharts.displayValue(row[1], for: name)
|
||||
}
|
||||
private func overview(_ title: String, metric: String, tint: Color) -> some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(title).font(.caption.weight(.medium)).foregroundStyle(.secondary)
|
||||
Text(value(metric)).font(.system(size: 23, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(tint).lineLimit(1).minimumScaleFactor(0.7)
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 66, alignment: .leading)
|
||||
.padding(12)
|
||||
.background(tint.opacity(0.08), in: RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
private var visibleRows: [[String]] {
|
||||
category == "All" ? rows : rows.filter { $0.count >= 2 && group($0[0]) == category }
|
||||
}
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
HStack {
|
||||
Label("Server overview", systemImage: "server.rack").font(.title3.bold())
|
||||
Spacer()
|
||||
Text("Live MySQL status · 5-second refresh").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 155), spacing: 10)], spacing: 10) {
|
||||
overview("Queries", metric: "Queries", tint: .blue)
|
||||
overview("Connected", metric: "Threads_connected", tint: .teal)
|
||||
overview("Running", metric: "Threads_running", tint: .orange)
|
||||
overview("Slow queries", metric: "Slow_queries", tint: .red)
|
||||
overview("Received", metric: "Bytes_received", tint: .purple)
|
||||
overview("Sent", metric: "Bytes_sent", tint: .green)
|
||||
}
|
||||
HStack {
|
||||
Label("Performance trends", systemImage: "chart.xyaxis.line").font(.title3.bold())
|
||||
Spacer()
|
||||
Text("\(samples.count) samples").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
WorkbenchCounterCharts(page: .serverCounters, samples: samples)
|
||||
HStack {
|
||||
Label("All server counters", systemImage: "list.bullet.rectangle").font(.title3.bold())
|
||||
Spacer()
|
||||
Text("\(visibleRows.count) of \(rows.count)").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
Picker("Counter category", selection: $category) {
|
||||
ForEach(categories, id: \.self) { Text($0).tag($0) }
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
WorkbenchColouredCounterTable(columns: ["Variable_name", "Value"], rows: visibleRows)
|
||||
.frame(minHeight: 360, idealHeight: 600)
|
||||
Text("Colours identify counter families, not server health. Error totals are cumulative; a nonzero value does not by itself indicate a current fault.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(18)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Phase 29: Inline coloured counter graphs
|
||||
// MARK: - Phase 33: pinned server graphs and complete coloured statistics
|
||||
private struct WorkbenchPinnedCounterGraphs: View {
|
||||
@@ -4416,7 +4469,7 @@ private struct WorkbenchCounterCharts: View {
|
||||
Text("\(samples.count) samples · 5-second refresh")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 270), spacing: 12)], spacing: 12) {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 360), spacing: 14)], spacing: 12) {
|
||||
ForEach(metrics, id: \.self) { metric in graph(metric) }
|
||||
}
|
||||
Text("Graphs use real MySQL samples. Cumulative counters display change per second; thread counts show current values. Rates appear after two successful samples.")
|
||||
|
||||
Reference in New Issue
Block a user