From 5aba461451670b2ce8f7c1b29f55cf2023a61941 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 19 Oct 2026 11:23:26 +0100 Subject: [PATCH] connection updates --- DBSyn/MainView.swift | 103 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/DBSyn/MainView.swift b/DBSyn/MainView.swift index 6051022..dce12f9 100644 --- a/DBSyn/MainView.swift +++ b/DBSyn/MainView.swift @@ -2962,6 +2962,8 @@ struct WorkbenchAdministrationView: View { // Phase 25/26: bounded, in-session samples. No credentials or SQL persisted. @State private var diagnosticSamples: [DiagnosticSample] = [] @State private var showingTrends = false + @State private var selectedLogEntry: DBSynLogDetail? + @State private var showingFullError = false private var supportsTrends: Bool { page == .slowQueries || page == .connectionStatistics || @@ -3077,6 +3079,7 @@ struct WorkbenchAdministrationView: View { } description: { Text(errorMessage) } actions: { + Button("View Full Error…") { showingFullError = true } Button("Retry") { Task { await refresh() } } @@ -3237,6 +3240,12 @@ struct WorkbenchAdministrationView: View { .sheet(isPresented: $showingTrends) { WorkbenchDiagnosticTrendsView(page: page, samples: diagnosticSamples) } + .sheet(item: $selectedLogEntry) { entry in + DBSynLogDetailSheet(title: "MySQL Server Log Entry", details: entry.text) + } + .sheet(isPresented: $showingFullError) { + DBSynLogDetailSheet(title: "MySQL Error Details", details: errorMessage ?? "No error available.") + } .sheet(isPresented: $showingGrants) { VStack(alignment: .leading, spacing: 12) { Text("Grants: \(selectedAccount ?? "")") @@ -3345,6 +3354,11 @@ struct WorkbenchAdministrationView: View { Spacer() + if page == .serverLogs, let errorMessage { + Button("Full Error…") { showingFullError = true } + .help(errorMessage) + } + if page != .dashboard { TextField("Filter results", text: $searchText) .textFieldStyle(.roundedBorder) @@ -3452,6 +3466,40 @@ struct WorkbenchAdministrationView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .accessibilityIdentifier("scrollable-dashboard-phase51") + case .serverLogs: + VStack(alignment: .leading, spacing: 0) { + HStack { + Text("Select an entry to view the complete log message and copy it.") + .font(.caption) + .foregroundStyle(.secondary) + Spacer() + Button("Copy All Logs") { + let all = filteredRows.map { logText(for: $0) }.joined(separator: "\n\n---\n\n") + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(all, forType: .string) + } + .disabled(filteredRows.isEmpty) + } + .padding(12) + List(Array(filteredRows.enumerated()), id: \.offset) { _, row in + Button { + selectedLogEntry = DBSynLogDetail(text: logText(for: row)) + } label: { + VStack(alignment: .leading, spacing: 5) { + Text(logValue("DATA", in: row)) + .lineLimit(2) + .foregroundStyle(.primary) + .frame(maxWidth: .infinity, alignment: .leading) + Text([logValue("LOGGED", in: row), logValue("PRIO", in: row), logValue("ERROR_CODE", in: row)] + .filter { !$0.isEmpty }.joined(separator: " · ")) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.vertical, 4) + } + .buttonStyle(.plain) + } + } case .serverCounters, .connectionStatistics, .innodbMetrics: WorkbenchLiveMonitorScreen( page: page, @@ -3468,6 +3516,16 @@ struct WorkbenchAdministrationView: View { } } + private func logValue(_ name: String, in row: [String]) -> String { + guard let index = result.columns.firstIndex(where: { $0.caseInsensitiveCompare(name) == .orderedSame }), + index < row.count else { return "" } + return row[index] + } + + private func logText(for row: [String]) -> String { + zip(result.columns, row).map { "\($0.0): \($0.1)" }.joined(separator: "\n") + } + private var footer: some View { HStack { if let lastUpdated { @@ -7219,3 +7277,48 @@ private struct DBSynConnectionManager: View { } } } + + +// MARK: - Copyable administration log details + +private struct DBSynLogDetail: Identifiable { + let id = UUID() + let text: String +} + +private struct DBSynLogDetailSheet: View { + let title: String + let details: String + @Environment(\.dismiss) private var dismiss + + var body: some View { + VStack(alignment: .leading, spacing: 14) { + HStack { + Label(title, systemImage: "doc.text.magnifyingglass") + .font(.title2.bold()) + Spacer() + Button("Copy Full Text") { + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(details, forType: .string) + } + .keyboardShortcut("c", modifiers: [.command, .shift]) + Button("Close") { dismiss() } + .keyboardShortcut(.cancelAction) + } + Text("Select text to copy, or use Copy Full Text for the complete error and metadata.") + .font(.caption) + .foregroundStyle(.secondary) + ScrollView([.vertical, .horizontal]) { + Text(details) + .font(.system(.body, design: .monospaced)) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + } + .background(Color(nsColor: .textBackgroundColor)) + .overlay(RoundedRectangle(cornerRadius: 6).stroke(Color(nsColor: .separatorColor))) + } + .padding(20) + .frame(minWidth: 720, minHeight: 420) + } +}