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,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -2151,7 +2151,18 @@ enum WorkbenchAdminPage: String, CaseIterable, Identifiable {
|
||||
case .serverCounters:
|
||||
return "SHOW GLOBAL STATUS"
|
||||
case .serverStatus:
|
||||
return "SHOW GLOBAL STATUS"
|
||||
return """
|
||||
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
||||
'Uptime', 'Connections', 'Max_used_connections',
|
||||
'Threads_connected', 'Threads_running', 'Threads_cached',
|
||||
'Queries', 'Questions', 'Slow_queries',
|
||||
'Bytes_received', 'Bytes_sent',
|
||||
'Aborted_clients', 'Aborted_connects',
|
||||
'Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads',
|
||||
'Innodb_data_reads', 'Innodb_data_writes',
|
||||
'Created_tmp_tables', 'Created_tmp_disk_tables'
|
||||
)
|
||||
"""
|
||||
|
||||
case .clientConnections:
|
||||
return "SHOW FULL PROCESSLIST"
|
||||
@@ -2535,48 +2546,32 @@ struct WorkbenchAdministrationView: View {
|
||||
.padding(18)
|
||||
}
|
||||
|
||||
// Keep the administration pages distinct: the complete server-status table and
|
||||
// system-variable grid must never be replaced by a dashboard of metric cards.
|
||||
// The live charts occupy a fixed region ABOVE an independently scrolling
|
||||
// counter table. They cannot disappear when the user scrolls the rows.
|
||||
@ViewBuilder
|
||||
private var resultContent: some View {
|
||||
switch page {
|
||||
case .serverCounters:
|
||||
WorkbenchServerCountersScreen(samples: diagnosticSamples, rows: filteredRows)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
case .serverStatus, .serverCounters, .connectionStatistics, .innodbMetrics:
|
||||
WorkbenchLiveMonitorScreen(
|
||||
page: page,
|
||||
samples: diagnosticSamples,
|
||||
rows: filteredRows,
|
||||
allRowCount: result.rows.count
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
case .dashboard:
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
WorkbenchInlinePerformanceDashboard(samples: diagnosticSamples)
|
||||
Text("Server statistics").font(.title2.bold())
|
||||
NativeResultGrid(columns: result.columns, rows: filteredRows)
|
||||
.frame(minHeight: 340)
|
||||
}
|
||||
.padding(18)
|
||||
}
|
||||
case .serverStatus:
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("Server Status").font(.title2.bold())
|
||||
WorkbenchInlinePerformanceDashboard(samples: diagnosticSamples)
|
||||
Text("Server statistics").font(.title2.bold())
|
||||
NativeResultGrid(columns: result.columns, rows: filteredRows)
|
||||
.frame(minHeight: 420)
|
||||
}
|
||||
.padding(18)
|
||||
}
|
||||
case .connectionStatistics, .innodbMetrics:
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
WorkbenchCounterCharts(page: page, samples: diagnosticSamples)
|
||||
Text("All statistics").font(.title2.bold())
|
||||
NativeResultGrid(columns: result.columns, rows: filteredRows)
|
||||
.frame(minHeight: 440)
|
||||
// Dashboard must not show the legacy, unformatted Variable_name table.
|
||||
Text("Server overview").font(.title2.bold())
|
||||
WorkbenchServerOverview(rows: filteredRows)
|
||||
}
|
||||
.padding(18)
|
||||
}
|
||||
default:
|
||||
// Status and System Variables and all other administration screens
|
||||
// retain their original searchable tabular presentation.
|
||||
// Status and System Variables is deliberately a distinct variable
|
||||
// inspector, never replaced by the monitoring screen.
|
||||
NativeResultGrid(columns: result.columns, rows: filteredRows)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
@@ -2632,6 +2627,7 @@ struct WorkbenchAdministrationView: View {
|
||||
return
|
||||
}
|
||||
|
||||
guard !loading else { return }
|
||||
loading = true
|
||||
errorMessage = nil
|
||||
|
||||
@@ -4155,7 +4151,7 @@ private struct WorkbenchInlinePerformanceDashboard: View {
|
||||
Text("\(samples.count) samples · refreshes every 5 seconds")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 410), spacing: 16)], spacing: 12) {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 285), 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)
|
||||
@@ -4170,6 +4166,239 @@ private struct WorkbenchInlinePerformanceDashboard: View {
|
||||
}
|
||||
|
||||
// MARK: - Phase 34: Responsive server-counter workspace
|
||||
// MARK: - Phase 39: pinned, visible monitoring graphs
|
||||
// The charts and counters are siblings, not nested in one long ScrollView.
|
||||
// Single administration monitoring view. The chart region is deliberately outside
|
||||
// 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"]
|
||||
var body: some View {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 175), spacing: 12)], spacing: 12) {
|
||||
ForEach(names, id: \.self) { name in
|
||||
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)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(14)
|
||||
.background(tint.opacity(0.07), in: RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct WorkbenchLiveMonitorScreen: View {
|
||||
let page: WorkbenchAdminPage
|
||||
let samples: [DiagnosticSample]
|
||||
let rows: [[String]]
|
||||
let allRowCount: Int
|
||||
|
||||
@State private var categorySelection = "All"
|
||||
@State private var chartGroup = "Activity"
|
||||
|
||||
private let categories = ["All", "Connections", "Queries", "InnoDB", "Network", "Errors", "Replication", "Other"]
|
||||
|
||||
private var pageTitle: String {
|
||||
switch page {
|
||||
case .serverStatus: return "Server Status"
|
||||
case .serverCounters: return "Server Counters"
|
||||
case .connectionStatistics: return "Connection Statistics"
|
||||
case .innodbMetrics: return "InnoDB Metrics"
|
||||
default: return "Server Monitoring"
|
||||
}
|
||||
}
|
||||
|
||||
private func category(for 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") { return "Queries" }
|
||||
return "Other"
|
||||
}
|
||||
|
||||
private var visibleRows: [[String]] {
|
||||
categorySelection == "All" ? rows : rows.filter { $0.count > 1 && category(for: $0[0]) == categorySelection }
|
||||
}
|
||||
|
||||
private var metrics: [String] {
|
||||
if page == .connectionStatistics {
|
||||
return ["Threads_connected", "Threads_running", "Connections", "Aborted_connects"]
|
||||
}
|
||||
if page == .innodbMetrics {
|
||||
return ["Innodb_buffer_pool_read_requests", "Innodb_buffer_pool_reads", "Innodb_data_reads", "Innodb_data_writes"]
|
||||
}
|
||||
switch chartGroup {
|
||||
case "Network": return ["Bytes_received", "Bytes_sent", "Connections", "Aborted_connects"]
|
||||
case "InnoDB": return ["Innodb_buffer_pool_read_requests", "Innodb_buffer_pool_reads", "Innodb_data_reads", "Innodb_data_writes"]
|
||||
default: return ["Queries", "Threads_connected", "Threads_running", "Slow_queries"]
|
||||
}
|
||||
}
|
||||
|
||||
private func value(_ metric: String, in sample: DiagnosticSample) -> Double? {
|
||||
guard let row = sample.rows.first(where: { $0.count > 1 && $0[0].caseInsensitiveCompare(metric) == .orderedSame }) else { return nil }
|
||||
return Double(row[1])
|
||||
}
|
||||
|
||||
private struct Reading: Identifiable {
|
||||
let id: Int
|
||||
let time: Date
|
||||
let value: Double
|
||||
}
|
||||
|
||||
private func readings(for metric: String) -> [Reading] {
|
||||
let gauge = metric == "Threads_connected" || metric == "Threads_running"
|
||||
return samples.indices.compactMap { index in
|
||||
guard let current = value(metric, in: samples[index]), current.isFinite else { return nil }
|
||||
if gauge { return Reading(id: index, time: samples[index].at, value: current) }
|
||||
guard index > 0, let previous = value(metric, in: samples[index - 1]), current >= previous else { return nil }
|
||||
let interval = samples[index].at.timeIntervalSince(samples[index - 1].at)
|
||||
guard interval > 0 else { return nil }
|
||||
return Reading(id: index, time: samples[index].at, value: (current - previous) / interval)
|
||||
}
|
||||
}
|
||||
|
||||
private func chart(_ metric: String) -> some View {
|
||||
let points = readings(for: metric)
|
||||
let tint = WorkbenchCounterCharts.tint(for: metric)
|
||||
let gauge = metric == "Threads_connected" || metric == "Threads_running"
|
||||
return VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(metric.replacingOccurrences(of: "_", with: " "))
|
||||
.font(.subheadline.weight(.semibold)).lineLimit(1)
|
||||
Spacer(minLength: 8)
|
||||
Text(points.last.map { $0.value.formatted(.number.precision(.fractionLength(1))) } ?? "—")
|
||||
.font(.headline.monospacedDigit()).foregroundStyle(tint)
|
||||
Text(gauge ? "current" : "/sec")
|
||||
.font(.caption2).foregroundStyle(.secondary)
|
||||
}
|
||||
Chart(points) { point in
|
||||
AreaMark(x: .value("Time", point.time), y: .value("Value", point.value))
|
||||
.foregroundStyle(tint.opacity(0.16))
|
||||
LineMark(x: .value("Time", point.time), y: .value("Value", point.value))
|
||||
.foregroundStyle(tint).lineStyle(StrokeStyle(lineWidth: 2.5))
|
||||
}
|
||||
.chartYAxis { AxisMarks(position: .leading) }
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 90)
|
||||
.overlay {
|
||||
if points.isEmpty {
|
||||
Text("Waiting for samples…")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(9)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(tint.opacity(0.065), in: RoundedRectangle(cornerRadius: 12))
|
||||
.overlay(RoundedRectangle(cornerRadius: 12).stroke(tint.opacity(0.24)))
|
||||
}
|
||||
|
||||
private var statusSummary: some View {
|
||||
let names = ["Uptime", "Threads_connected", "Threads_running", "Queries", "Slow_queries", "Max_used_connections"]
|
||||
return HStack(spacing: 8) {
|
||||
ForEach(names, id: \.self) { name in
|
||||
let raw = rows.first(where: { $0.count > 1 && $0[0].caseInsensitiveCompare(name) == .orderedSame })?.last ?? "—"
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(name.replacingOccurrences(of: "_", with: " "))
|
||||
.font(.caption2).foregroundStyle(.secondary).lineLimit(1)
|
||||
Text(WorkbenchCounterCharts.displayValue(raw, for: name))
|
||||
.font(.headline.monospacedDigit()).lineLimit(1).minimumScaleFactor(0.7)
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 45, alignment: .leading)
|
||||
.padding(9)
|
||||
.background(Color(nsColor: .controlBackgroundColor), in: RoundedRectangle(cornerRadius: 9))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Charts and counters are sibling regions. Neither is wrapped in the
|
||||
// other region's ScrollView; the charts stay on-screen while rows scroll.
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let width = max(0, geometry.size.width - 32)
|
||||
let twoColumns = width >= 640
|
||||
let gridColumns = Array(repeating: GridItem(.flexible(minimum: 0), spacing: 12), count: twoColumns ? 2 : 1)
|
||||
let cardHeight: CGFloat = twoColumns ? 160 : 125
|
||||
let chartAreaHeight: CGFloat = twoColumns ? 334 : min(285, max(150, geometry.size.height * 0.40))
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack(spacing: 10) {
|
||||
Label(pageTitle, systemImage: "chart.xyaxis.line")
|
||||
.font(.title3.bold())
|
||||
Spacer(minLength: 4)
|
||||
Text("Live · \(samples.count) samples · 5s")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
if page == .serverStatus || page == .serverCounters {
|
||||
Picker("Graphs", selection: $chartGroup) {
|
||||
Text("Activity").tag("Activity")
|
||||
Text("Network").tag("Network")
|
||||
Text("InnoDB").tag("InnoDB")
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.frame(maxWidth: 350)
|
||||
}
|
||||
|
||||
// A bounded graph viewport, with two complete columns on
|
||||
// desktop. On narrow windows, only this area scrolls.
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: gridColumns, spacing: 12) {
|
||||
ForEach(metrics, id: \.self) { metric in
|
||||
chart(metric)
|
||||
.frame(height: cardHeight)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.frame(height: min(chartAreaHeight, max(135, geometry.size.height * 0.54)))
|
||||
.accessibilityIdentifier("monitor-graphs-visible")
|
||||
|
||||
Divider()
|
||||
if page == .serverStatus {
|
||||
Text("Server overview")
|
||||
.font(.headline)
|
||||
ScrollView(.vertical) {
|
||||
WorkbenchServerOverview(rows: rows)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
HStack {
|
||||
Text(page == .connectionStatistics ? "Connection statistics" : page == .innodbMetrics ? "InnoDB statistics" : "All MySQL server counters")
|
||||
.font(.headline)
|
||||
Spacer(minLength: 6)
|
||||
if page == .serverCounters {
|
||||
Picker("Category", selection: $categorySelection) {
|
||||
ForEach(categories, id: \.self) { Text($0).tag($0) }
|
||||
}
|
||||
.frame(width: 155)
|
||||
}
|
||||
Text("\(visibleRows.count) counters")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
WorkbenchColouredCounterTable(columns: ["Metric", "Value"], rows: visibleRows)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private struct WorkbenchServerCountersScreen: View {
|
||||
let samples: [DiagnosticSample]
|
||||
let rows: [[String]]
|
||||
@@ -4459,7 +4688,7 @@ private struct WorkbenchCounterCharts: View {
|
||||
Text("\(samples.count) samples · 5-second refresh")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 410), spacing: 16)], spacing: 12) {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 285), spacing: 14)], spacing: 12) {
|
||||
ForEach(metrics, id: \.self) { metric in graph(metric) }
|
||||
}
|
||||
Text("Live MySQL data • refreshes every five seconds. Rates appear after two successful samples; thread counts show current values.")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 530 KiB After Width: | Height: | Size: 460 KiB |
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
# Phase 10 — Live export progress
|
||||
|
||||
- Updates export progress while each streamed table is being written, rather than only after the table completes.
|
||||
- Displays current-table row count, expected row count, and total exported rows.
|
||||
- Throttles UI updates to every 500 rows (and at the last row) to reduce overhead.
|
||||
- Retains Phase 9's single-result streaming, InnoDB snapshot, row count verification and SHA-256 sidecars.
|
||||
|
||||
Limitations: Large individual values remain constrained by MySQL packet sizes. Progress updates are best effort and may be delayed by networking. Export consistency excludes concurrent DDL and non-InnoDB data. This source has not been built with Xcode or tested against a live server.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Phase 11 — Streaming SQL import
|
||||
|
||||
- Reads SQL dumps in 64 KiB file chunks, buffering only a line and the current SQL statement.
|
||||
- Supports DELIMITER directives, SQL quoting, line/block comments and routine bodies.
|
||||
- Reports statement number and starting line on SQL execution errors.
|
||||
- Reports approximate byte progress and allows cancellation between statements.
|
||||
- Verifies SHA-256 sidecar before import if present.
|
||||
|
||||
## Limitations
|
||||
- A single huge SQL statement or line is still buffered in memory.
|
||||
- Import is nontransactional and cancellation can leave partial changes.
|
||||
- Parser assumes UTF-8 input and standard MySQL SQL mode (not NO_BACKSLASH_ESCAPES).
|
||||
- This has not been built with Xcode or integration-tested against MySQL.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Phase 12 — Restore Validation
|
||||
|
||||
- Exports now create `<backup>.sql.manifest.json` alongside SQL and SHA-256 files.
|
||||
- The manifest records the selected database, table names and snapshot row counts, view names, trigger names, and routine names/types.
|
||||
- **Verify Restored Database…** selects an SQL backup and checks its manifest against the selected live database. It optionally verifies the SQL SHA-256 sidecar before checking.
|
||||
- Reports mismatched table row counts, missing or inaccessible views/triggers/routines, and successful checks. Validation is read-only.
|
||||
- Structure-only exports record a `null` row count; row-count comparison is skipped for those tables.
|
||||
- This does **not** validate row content, schema definitions, or guarantee a complete/restorable backup. Do not use as a substitute for a test restore and independent backups.
|
||||
- Requires appropriate MySQL privileges for SHOW CREATE operations. No external utilities.
|
||||
- Full Xcode build and live-server tests have not been performed.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Phase 13 — Administration enhancements
|
||||
|
||||
- Added Server Error Log view (reads `performance_schema.error_log`, available on supported MySQL versions and with appropriate privileges).
|
||||
- Added filter/search for administration result tables.
|
||||
- Added optional 5-second auto-refresh of administration data.
|
||||
- Added account selection and read-only `SHOW GRANTS` inspector to Users and Privileges.
|
||||
- Existing backup/restore functionality preserved.
|
||||
- No user account or server mutations are performed by these features.
|
||||
|
||||
**Validation:** Source-level checks and ZIP integrity only; full macOS Xcode build and live-server tests are still required.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Phase 14 — MySQL Account Management
|
||||
|
||||
Adds a **Manage Accounts…** sheet under Administration → Users and Privileges.
|
||||
|
||||
Operations: create account, change password, lock/unlock account, grant/revoke an individual database privilege. Every operation displays a SQL preview and requires an explicit confirmation. Passwords are masked in previews, never persisted by the account management form, and cleared after execution. Privileges are chosen from an allowlist. User, host, and database inputs use conservative character allowlists.
|
||||
|
||||
The existing account list and Show Grants remain available. Auto-refresh is switched off when account management opens, and the account list refreshes on dismissal.
|
||||
|
||||
**Limitations:** Requires sufficient MySQL privileges. Only basic ASCII account/host/database names supported. No account deletion, global privileges, roles, or complex grant options. SQL mode NO_BACKSLASH_ESCAPES is rejected for password operations. Password values are sent using the native query protocol, which may expose them in server-side SQL logs; parameterized MySQL account DDL isn't implemented. No automatic rollback for account changes.
|
||||
|
||||
**Validation:** Static/source inspection and ZIP integrity only. This environment cannot compile the macOS Xcode target or verify changes against a MySQL server. Test on a disposable MySQL instance before production use.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Phase 15 — Live MySQL Session Management
|
||||
|
||||
Adds session control to Administration → Client Connections. Select a session ID from `SHOW FULL PROCESSLIST`, then choose **Stop Query** (`KILL QUERY`) or **Disconnect Session** (`KILL CONNECTION`). Both actions require a destructive confirmation. The app validates the session ID as a positive UInt64, rejects terminating its own active MySQL connection, reports errors and refreshes the process list afterward. Search and five-second auto-refresh from Phase 13 remain available.
|
||||
|
||||
**Permissions:** MySQL privileges determine which sessions can be seen or terminated. Session IDs may be reused after a connection exits; always verify the selected session before confirming. A race remains possible between selecting an ID and execution. Terminating connections may interrupt work or roll back uncommitted transactions. There is no undo.
|
||||
|
||||
**Scope:** This phase focuses on session management. Advanced server configuration editing and performance diagnostics are not implemented here.
|
||||
|
||||
**Validation:** Source patch and ZIP integrity checked. No macOS Xcode compiler or live MySQL server available in this environment; test on a disposable MySQL instance before production use.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Phase 16 — Native MySQL Diagnostics
|
||||
|
||||
Adds five read-only Administration → Performance screens:
|
||||
|
||||
- **Slow Query Analysis:** `performance_schema.events_statements_summary_by_digest` sorted by cumulative execution time, with counts, rows examined, and first/last seen timestamps. Times are raw picoseconds, **not** milliseconds.
|
||||
- **Table I/O Hotspots:** `performance_schema.table_io_waits_summary_by_table` with read/write counts and timings.
|
||||
- **InnoDB Metrics:** selected `SHOW GLOBAL STATUS` buffer-pool, I/O, lock, and row counters.
|
||||
- **InnoDB Lock Waits:** current edges in `performance_schema.data_lock_waits`.
|
||||
- **Connection Statistics:** connection, abort, thread, and query counters.
|
||||
|
||||
All screens reuse the existing searchable grid, manual refresh, and optional five-second auto-refresh. SQL is read-only. Some screens require MySQL 8.0+ and enabled Performance Schema instrumentation/privileges; errors are displayed in the existing administration error view. The digest and table I/O summaries are cumulative and may be reset by server restarts or instrumentation changes. This phase does **not** enable slow-query logging or change server configuration.
|
||||
|
||||
## Validation
|
||||
|
||||
Source inspection and ZIP integrity check performed. **Not compiled with Xcode or tested on a live MySQL server.** Test on a nonproduction MySQL 8.0+ instance first.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Phase 17 — SQL Editor Productivity
|
||||
|
||||
- Added persisted query execution history (latest 200 statements), including timestamp, connection, schema, success/failure, and error details. Schema-list refreshes are not recorded.
|
||||
- Added History panel with filtering, reopen in a new tab, and clear history.
|
||||
- Added a persistent saved SQL query library, with save, filter, open, and delete.
|
||||
- Added AppKit SQL keyword completion via NSTextViewDelegate's standard completion interface (typically Esc or Option-Esc depending on macOS key bindings). Completions are keyword-based, not live schema-aware.
|
||||
- Preserved the existing SQL syntax highlighting and query tab handling.
|
||||
|
||||
## Caveats
|
||||
|
||||
History and saved SQL are stored in local UserDefaults and may contain sensitive SQL text; avoid executing statements containing literal secrets if local history retention is a concern. Results remain subject to the existing protocol row cap. This phase has not been built in Xcode or tested against a live server.
|
||||
@@ -1,18 +0,0 @@
|
||||
# Phase 18 — SQL Editor and Result Grid
|
||||
|
||||
Built from the Phase 17 project archive and includes the correction for the Phase 17 `localizedCaseInsensitiveHasPrefix` compilation error.
|
||||
|
||||
## Changes
|
||||
- Execute highlighted SQL independently of the complete editor buffer (Run Selection).
|
||||
- SQL completion includes cached table and view names loaded in the Schemas navigator, alongside SQL keywords. Column-aware suggestions are not yet implemented.
|
||||
- Case-insensitive local filtering of query result rows.
|
||||
- Local result sorting by chosen column, ascending or descending.
|
||||
- CSV export of the currently displayed rows, with RFC-style doubled quotes, UTF-8 BOM and CRLF line endings.
|
||||
- Query elapsed wall-clock time, shown after execution completes (includes connection overhead).
|
||||
|
||||
## Limitations
|
||||
- Result grid filters/sorts operate only on rows returned by the existing query engine, not the full database table.
|
||||
- The CSV writer constructs the file in memory and should not be used for huge result sets.
|
||||
- SQL autocomplete suggestions depend on schemas already loaded; no column metadata is fetched.
|
||||
- No new SQL error underlining was added.
|
||||
- Not compiled on macOS or tested against a MySQL server; run xcodebuild on the Mac.
|
||||
@@ -1,12 +0,0 @@
|
||||
# Phase 19 — Column-aware SQL completion and incremental CSV writing
|
||||
|
||||
- Load column names from `information_schema.COLUMNS` when a schema is expanded/refreshed. Cache column names and `table.column` suggestions for the active schema; metadata errors are nonfatal.
|
||||
- SQL completion now includes cached column suggestions alongside tables, views and keywords.
|
||||
- Write CSV results one record at a time to a temporary file, with UTF-8 BOM, CRLF and quote escaping; move into place only on success.
|
||||
- Retains all Phase 18 features.
|
||||
|
||||
## Limitations
|
||||
- Metadata suggestions require loading the schema navigator first and sufficient `information_schema` visibility.
|
||||
- CSV export still operates on the existing in-memory query result (subject to query result limits); it does not fetch additional database rows.
|
||||
- Result-grid filtering/sorting still creates an in-memory sorted list.
|
||||
- Not compiled on macOS or tested against MySQL. Build and test with Xcode before production use.
|
||||
@@ -1,14 +0,0 @@
|
||||
# Phase 20 — Result Grid Improvements
|
||||
|
||||
- Added per-column visibility menu with Show All.
|
||||
- Added 1,000-row pages for native result grid rendering, with Previous/Next controls.
|
||||
- Added matching row counts and current visible range.
|
||||
- Sorting/filtering apply to the complete loaded result set before paging.
|
||||
- CSV export still includes all filtered/sorted loaded rows, even those on other pages or in hidden columns.
|
||||
- Page resets on filter/sort changes and new column schemas.
|
||||
|
||||
## Limitations
|
||||
|
||||
- This is UI paging, not database-side streaming: the query result is still loaded in memory.
|
||||
- Sorting and filtering still process all loaded rows and may be slow on large result sets.
|
||||
- No macOS Xcode build or live MySQL test has been performed.
|
||||
@@ -1,17 +0,0 @@
|
||||
# Phase 21 — SQL Query Plan Inspector
|
||||
|
||||
Based on Phase 20.
|
||||
|
||||
## Added
|
||||
- **Explain** button in SQL editor toolbar. Uses selected SQL when present, otherwise editor SQL.
|
||||
- Separate query-plan window showing MySQL `EXPLAIN` in table format or `EXPLAIN FORMAT=JSON`.
|
||||
- Shows optimizer plan columns in the existing native result grid; JSON format is selectable and copyable.
|
||||
- Displays diagnostic query duration and server errors.
|
||||
- Limits analysis to SELECT/WITH statements, does not run `EXPLAIN ANALYZE` (which executes the query).
|
||||
- Does not replace editor query results or add EXPLAIN to normal query history.
|
||||
|
||||
## Notes
|
||||
- Requires an active connection and MySQL permissions to explain the query.
|
||||
- `EXPLAIN` gives estimated plans, not actual execution metrics.
|
||||
- Only a single SQL statement should be selected; multi-statement SQL is not supported in the plan inspector.
|
||||
- Not built in Xcode or tested against a live MySQL server in this environment.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Phase 22 — SQL diagnostics and plan observations
|
||||
|
||||
- Adds read-only interpretation of tabular MySQL EXPLAIN output: full scans, index scans, missing selected index, temporary tables, filesort and estimated row counts.
|
||||
- Adds Copy Plan for tabular and JSON EXPLAIN output.
|
||||
- Displays MySQL execution errors directly above the SQL editor, with selectable error text. No inaccurate error-line highlighting is attempted.
|
||||
- Index observations are explicitly advisory and do not generate or execute DDL.
|
||||
- Preserves Phase 21 and earlier project files.
|
||||
|
||||
Build and runtime validation on macOS / a MySQL server are still required.
|
||||
@@ -1,7 +0,0 @@
|
||||
# Phase 23 — Query Plan History and Comparison
|
||||
|
||||
Adds locally saved EXPLAIN snapshots (Table or JSON), history scoped to the same connection name, database, and SQL statement, baseline comparison, and deletion. Table-plan comparison includes a sum of optimizer-estimated rows across steps, not measured execution times. Snapshots are stored in UserDefaults (max 40, max encoded size 2 MB).
|
||||
|
||||
The existing EXPLAIN inspector and query plan observations remain unchanged. No SQL statements are executed beyond EXPLAIN.
|
||||
|
||||
**Validation:** Source changes and archive structure inspected; no macOS/Xcode build or live MySQL tests performed.
|
||||
@@ -1,12 +0,0 @@
|
||||
# Phase 24 — Query Performance Diagnostics
|
||||
|
||||
Builds on Phase 23.
|
||||
|
||||
- Adds a read-only index diagnostic section to the EXPLAIN inspector. Flags full scans with/without candidate indexes, and optimizer steps without a selected index.
|
||||
- Adds per-step tabular EXPLAIN baseline comparisons for access type, selected index, possible indexes, estimated rows, filtered percentage, and Extra information.
|
||||
- Adds **Copy Diagnostics** to copy SQL, plan observations, index analysis, and baseline differences.
|
||||
- Preserves the Phase 23 query plan history and existing database management features.
|
||||
|
||||
## Important
|
||||
|
||||
The application has not been compiled in Xcode or tested against MySQL in this environment. The analysis uses optimizer estimates, not measured timings. It intentionally does not generate or apply CREATE INDEX statements. Compare plans only for equivalent schema/data conditions.
|
||||
@@ -1,14 +0,0 @@
|
||||
# Phase 25–26 — Performance sampling and diagnostics
|
||||
|
||||
- Samples the Slow Query Analysis, Connection Statistics, InnoDB Metrics, Dashboard, and Server Status screens on successful refresh.
|
||||
- `View Trends…` compares current and previous counter values, reporting changes per second when values are monotonic.
|
||||
- Tracks up to 60 snapshots per open administration screen in memory; `Clear Samples` removes them.
|
||||
- Slow-query snapshots compare execution counts by Performance Schema SQL digest text.
|
||||
- No new server privileges, schema modifications, third-party dependencies or external MySQL binaries.
|
||||
|
||||
## Limitations
|
||||
- Samples are not persisted, and are discarded when the screen is closed.
|
||||
- Slow query analysis is limited to the top 100 digests returned by the existing SQL; digests dropping out of the top 100 will not be comparable.
|
||||
- For cumulative counters, server restarts or Performance Schema resets can invalidate deltas; negative deltas are suppressed.
|
||||
- A counter rate is not the same as query latency, and the SQL digest data may be truncated.
|
||||
- Not compiled with Xcode or tested on a live MySQL server in this environment.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Phase 27 — Charts, colours, schema editor
|
||||
|
||||
- Imports Swift Charts; adds coloured line/area charts to Administration > View Trends, based on existing in-memory samples.
|
||||
- Metric picker and coloured rates in performance counters.
|
||||
- Schema tree: right-click schema > Create Table; right-click table > Edit Table Schema.
|
||||
- Schema inspector displays SHOW FULL COLUMNS, SHOW INDEX, SHOW CREATE TABLE.
|
||||
- SQL DDL editing with confirmation; requires suitable MySQL permissions. No automatic table reconstruction.
|
||||
|
||||
IMPORTANT: Not compiled in Xcode here. Test against a disposable MySQL database. DDL may implicitly commit.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Phase 28 — Front-screen charts
|
||||
|
||||
- The application opens on the Dashboard instead of the SQL editor.
|
||||
- A Dashboard toolbar button returns to the performance dashboard from anywhere.
|
||||
- Six colour-coded Swift Charts panels are embedded above server counters: queries/sec, connected threads, running threads, slow queries/sec, bytes received/sec, and bytes sent/sec.
|
||||
- Charts use actual MySQL SHOW GLOBAL STATUS readings and refresh every 5 seconds while the administration screen is open.
|
||||
- The first rate value requires two successful samples; no synthetic data is used.
|
||||
- Retains the Phase 27 SQL-based schema editor and the existing Trends window for other administration pages.
|
||||
- Build and live MySQL verification must be performed on macOS with Xcode.
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,7 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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.
|
||||
@@ -1 +0,0 @@
|
||||
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.
|
||||
@@ -1 +0,0 @@
|
||||
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.
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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,11 +0,0 @@
|
||||
# Phase 37 — Server Counters Graphs First
|
||||
|
||||
Based on the exact Xcode project supplied in Archive(20261017-104400).zip.
|
||||
|
||||
- Server Counters opens with the large performance graphs as the first content, with no overview cards pushing them down.
|
||||
- Responsive two-column grid at typical desktop widths; each chart is 205 points tall.
|
||||
- Coloured chart lines and filled areas, subtle matching chart borders, current readings, time and value axes.
|
||||
- Full searchable server counter list remains below the graphs, grouped by category.
|
||||
- Existing icon resources, Server Status, Status and System Variables, and schema editor preserved.
|
||||
|
||||
Note: source-only change; requires Xcode compilation and live MySQL verification on macOS.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Phase 4 — SQL dump importer
|
||||
|
||||
- Adds parsing for MySQL `DELIMITER` directives, including stored procedure/function/trigger bodies.
|
||||
- Preserves MySQL executable comments (`/*! ... */`) and block comments.
|
||||
- Handles quotes, escaped quotes, and semicolons inside strings.
|
||||
- Updates the import interface explanation.
|
||||
- Does not require mysql, mysqldump or Homebrew.
|
||||
|
||||
## Important limitations
|
||||
|
||||
This is not a production-ready backup/restore solution. The importer still reads the entire SQL file into memory, executes each statement through the existing connection runner, and does not roll back statements already executed after an error or cancellation. Server privileges and server-specific SQL syntax may prevent imports. Validate against a disposable database before using with valuable data. The macOS Xcode build has not been run in this environment.
|
||||
|
||||
## Build
|
||||
|
||||
`xcodebuild -project WorkbenchReplacement.xcodeproj -scheme WorkbenchReplacement -configuration Debug -destination 'platform=macOS,arch=arm64' -derivedDataPath ./build build`
|
||||
@@ -0,0 +1,18 @@
|
||||
# Phase 42 — Monitoring layout corrections
|
||||
|
||||
Based on the project uploaded 17 October 2026.
|
||||
|
||||
- Removed the legacy `NativeResultGrid` from the Dashboard's Server statistics section; it is replaced by a responsive, formatted Server overview.
|
||||
- Reworked the shared Server Status, Server Counters, Connection Statistics and InnoDB monitoring view to size its charts from the actual available content width.
|
||||
- The chart area scrolls independently when the window is short, and the complete metrics list remains independently scrollable.
|
||||
- Responsive one-column charts on narrow windows, two columns on wider windows; no forced horizontal chart overflow.
|
||||
- Kept the Status and System Variables inspector separate.
|
||||
- Preserved the existing icon and schema editor. Icon artwork has **not** been redesigned in this release.
|
||||
|
||||
## Verification
|
||||
|
||||
`swiftc -frontend -parse` passed for all four Swift files on Linux. This is a syntax check, **not** a macOS Xcode build or visual UI test. A real Xcode build and UI inspection are still required.
|
||||
|
||||
## Build
|
||||
|
||||
Run `xcodebuild -project WorkbenchReplacement.xcodeproj -scheme WorkbenchReplacement -configuration Debug -destination 'platform=macOS,arch=arm64' -derivedDataPath ./build clean build` from the project directory.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Phase 43 — monitoring view containment correction
|
||||
|
||||
- Replaced the Phase 42 monitoring view's unbounded chart/table interaction with a bounded, two-column chart region and independent statistics scrolling.
|
||||
- Server Status now shows a formatted server overview below charts instead of a `Variable name / Value` counter table.
|
||||
- Server Counters retains all `SHOW GLOBAL STATUS` counters and category filtering.
|
||||
- Connection Statistics and InnoDB Metrics use the same bounded graph container, with page-specific metrics.
|
||||
- Status and System Variables remains a separate table inspector.
|
||||
- The chart region is limited to 54% of the available screen height; on short or narrow windows it scrolls independently.
|
||||
- All documentation lives in `docs/`.
|
||||
|
||||
## Verification limits
|
||||
|
||||
Swift syntax parsing and ZIP integrity were checked in the build environment. The project has not been compiled with macOS Xcode or visually tested against a MySQL server. The icon and project format have not been modified in this phase.
|
||||
@@ -1,5 +0,0 @@
|
||||
# Phase 5 — Built-in MySQL routine and trigger export
|
||||
|
||||
Adds opt-in export of stored procedures, functions and triggers via SHOW CREATE, using DELIMITER directives compatible with the Phase 4 importer. Export requires relevant MySQL privileges. The export includes these objects only when table structure export is enabled. The existing schema/table/view selection is unchanged.
|
||||
|
||||
Limitations: not tested in Xcode or against a live MySQL instance; no consistent snapshot, deterministic pagination, or complete backup verification. Do not use as the only production backup. The importer executes individual statements and does not provide rollback on failure.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Phase 6 — SQL backup integrity
|
||||
|
||||
Adds streaming SHA-256 checksum creation for successfully exported SQL files, a **Verify Backup…** button, and automatic checksum validation before importing when a `.sha256` sidecar is present.
|
||||
|
||||
The `.sha256` file must be kept alongside the `.sql` file with the same basename. The verifier reads the SQL file in 1 MiB blocks rather than loading the file into memory. Third-party SQL imports without a sidecar are allowed.
|
||||
|
||||
**Not yet implemented:** transaction-consistent snapshots, reliable ordering of table rows across pages, import streaming, rollback on partial restore, cryptographic signing/authenticity, or end-to-end restoration tests. A matching SHA-256 proves file integrity relative to the stored checksum, not database completeness or correctness. Do not rely on this as a production backup system.
|
||||
|
||||
This Linux environment cannot run Xcode; build and live-MySQL testing are required on macOS.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Phase 7 — Safer table data exports
|
||||
|
||||
- Requires a non-null unique index (prefers PRIMARY) for each table being exported with data.
|
||||
- Adds explicit ORDER BY to every 250-row data page, preventing arbitrary server ordering.
|
||||
- Checks row count before and after export, and compares it to rows written. On mismatch, aborts and deletes the temporary SQL file.
|
||||
- Keeps Phase 6 SHA-256 sidecar verification and all existing UI features.
|
||||
|
||||
## Limitations
|
||||
|
||||
This is **not a consistent snapshot**. Concurrent updates can still change row contents without changing counts, and inserts/deletes can cancel each other out. OFFSET pagination is still used; a future phase must introduce a dedicated, isolated snapshot connection and keyset paging. Tables lacking a non-null unique index cannot be data-exported in this version. No Xcode or live server test was performed. Do not rely on these exports as sole production backups.
|
||||
@@ -1,17 +0,0 @@
|
||||
# Phase 8 — Dedicated InnoDB snapshot exports
|
||||
|
||||
- Uses an isolated native MySQL session for the entire export.
|
||||
- Starts a REPEATABLE READ consistent, read-only transaction.
|
||||
- Rejects non-InnoDB tables when exporting data.
|
||||
- Keeps the same session across all paginated reads and row-count checks.
|
||||
- Closes the dedicated session on cancellation/failure (rolling back its transaction).
|
||||
- Preserves all Phase 7 export, import and integrity features.
|
||||
|
||||
## Limitations
|
||||
|
||||
This is NOT a full production backup guarantee: schema changes during export,
|
||||
non-transactional objects, and offset pagination remain limitations. A stable
|
||||
snapshot makes offset pagination consistent for InnoDB row data but not fast
|
||||
for large offsets. Backup and restore validation against live MySQL is still
|
||||
required. The MySQL wire protocol does not currently negotiate TLS, so do
|
||||
not send credentials over untrusted networks.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Phase 9 — Native streamed table export
|
||||
|
||||
- Removes the non-null unique index requirement (including LIBARC).
|
||||
- Uses a single MySQL SELECT result stream per table, within the existing dedicated InnoDB consistent-snapshot session.
|
||||
- Does not use LIMIT/OFFSET and does not buffer complete table results or impose the SQL editor's 100,000-row limit.
|
||||
- Uses optional unique-key ORDER BY when available, but supports tables without a unique key.
|
||||
- Counts streamed rows and compares against snapshot row count.
|
||||
- Existing SHA-256 verification and temporary-file cleanup remain in place.
|
||||
|
||||
Limitations: The SQL exporter still formats rows using MySQL SQL expressions; very large individual values and packet sizes may be limited by server/client protocol settings. Non-InnoDB tables are still rejected for data exports. DDL changes during backup are not protected. Streaming cancellation may wait for network reads. Not Xcode-built or live-server tested.
|
||||
Reference in New Issue
Block a user