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:
@@ -0,0 +1,11 @@
|
||||
# Phase 30 — Server Counters graphs fix
|
||||
|
||||
- Adds a dedicated **Server Counters** item under **Administration → Management**.
|
||||
- Retrieves all actual server counters using `SHOW GLOBAL STATUS` (not `SHOW GLOBAL VARIABLES`).
|
||||
- Places nine colour-coded Swift Charts above the counters, directly on the Server Counters screen.
|
||||
- Graphs show current thread counts and per-second rates for cumulative counters.
|
||||
- Counter cards remain visible beneath the charts; the filter field filters the cards.
|
||||
- Samples are collected every five seconds when Auto-refresh is enabled. The first rate point requires two successful samples.
|
||||
- Existing dashboard, SQL editor and schema editor are preserved.
|
||||
|
||||
This release was not compiled on macOS or tested against a live MySQL server.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Phase 31 — Server Counters Layout
|
||||
|
||||
- Server counter cards appear first, with a compact responsive grid.
|
||||
- Live graphs appear directly below the counter cards in the same scroll view.
|
||||
- Bytes counters are displayed in readable units; uptime in days/hours/minutes; numeric counters use separators.
|
||||
- Existing graph collection, automatic refresh and schema editing are retained.
|
||||
- Requires macOS/Xcode compilation and live MySQL verification.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Phase 32 — Administration View Restoration
|
||||
|
||||
- Server Counters: inline coloured graphs followed by the complete searchable MySQL status grid (no omitted counters).
|
||||
- Server Status: full SHOW GLOBAL STATUS data restored below the performance overview, with search.
|
||||
- Status and System Variables: restored original native table/grid presentation; no inappropriate charts or counter tiles.
|
||||
- Connection Statistics and InnoDB: retain graphs and complete tabular results.
|
||||
- No MySQL mutations; monitoring remains read-only.
|
||||
- Xcode build and live MySQL validation must be performed on macOS.
|
||||
@@ -0,0 +1 @@
|
||||
Phase 33: Server Counters fixed split layout. Nine real MySQL sample charts pinned in horizontal scroll strip above independently scrolling full coloured SHOW GLOBAL STATUS table. All counters retained. Auto-refresh does not blank existing data. No Xcode build performed.
|
||||
@@ -0,0 +1 @@
|
||||
Phase 34: Responsive Server Counters redesign. Six compact overview tiles, larger adaptive graph grid, complete counter table with category filters and existing search. Server Status and Status and System Variables navigation unchanged. Xcode compilation and MySQL runtime validation not available in this environment.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Phase 35 — Icon restoration
|
||||
|
||||
- Retains the Phase 34 administration and server-counter views unchanged.
|
||||
- Restores toolbar branding by loading the bundled `WorkbenchBrand.png` rather than a generic SF Symbol.
|
||||
- Explicitly sets the macOS Dock icon at app launch from bundled `WorkbenchReplacement.icns`.
|
||||
- Retains the `CFBundleIconFile=WorkbenchReplacement` setting for Finder icon handling.
|
||||
- Includes both original branding files.
|
||||
|
||||
This release has not been built with Xcode or run on macOS here. Finder/Dock caching may require quitting old copies and rebuilding.
|
||||
@@ -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