From e1a9b4037e1af2899f41b88a502eae25388fac8d Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Sat, 17 Oct 2026 11:31:54 +0100 Subject: [PATCH] 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. --- PHASE30-NOTES.md | 11 +++ PHASE31-NOTES.md | 7 ++ PHASE32-NOTES.md | 8 ++ PHASE33-NOTES.md | 1 + PHASE34-NOTES.md | 1 + PHASE35-NOTES.md | 9 +++ WorkbenchReplacement/App.swift | 7 ++ WorkbenchReplacement/MainView.swift | 113 ++++++++++++++++++++-------- 8 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 PHASE30-NOTES.md create mode 100644 PHASE31-NOTES.md create mode 100644 PHASE32-NOTES.md create mode 100644 PHASE33-NOTES.md create mode 100644 PHASE34-NOTES.md create mode 100644 PHASE35-NOTES.md diff --git a/PHASE30-NOTES.md b/PHASE30-NOTES.md new file mode 100644 index 0000000..d6917b0 --- /dev/null +++ b/PHASE30-NOTES.md @@ -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. diff --git a/PHASE31-NOTES.md b/PHASE31-NOTES.md new file mode 100644 index 0000000..3bc03c3 --- /dev/null +++ b/PHASE31-NOTES.md @@ -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. diff --git a/PHASE32-NOTES.md b/PHASE32-NOTES.md new file mode 100644 index 0000000..41ebcdc --- /dev/null +++ b/PHASE32-NOTES.md @@ -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. diff --git a/PHASE33-NOTES.md b/PHASE33-NOTES.md new file mode 100644 index 0000000..f9e9693 --- /dev/null +++ b/PHASE33-NOTES.md @@ -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. diff --git a/PHASE34-NOTES.md b/PHASE34-NOTES.md new file mode 100644 index 0000000..df18a56 --- /dev/null +++ b/PHASE34-NOTES.md @@ -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. diff --git a/PHASE35-NOTES.md b/PHASE35-NOTES.md new file mode 100644 index 0000000..1b9f341 --- /dev/null +++ b/PHASE35-NOTES.md @@ -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. diff --git a/WorkbenchReplacement/App.swift b/WorkbenchReplacement/App.swift index 4efd187..4dce51d 100644 --- a/WorkbenchReplacement/App.swift +++ b/WorkbenchReplacement/App.swift @@ -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) } diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index f951174..506dd43 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -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.")