connection updates
This commit is contained in:
@@ -2962,6 +2962,8 @@ struct WorkbenchAdministrationView: View {
|
|||||||
// Phase 25/26: bounded, in-session samples. No credentials or SQL persisted.
|
// Phase 25/26: bounded, in-session samples. No credentials or SQL persisted.
|
||||||
@State private var diagnosticSamples: [DiagnosticSample] = []
|
@State private var diagnosticSamples: [DiagnosticSample] = []
|
||||||
@State private var showingTrends = false
|
@State private var showingTrends = false
|
||||||
|
@State private var selectedLogEntry: DBSynLogDetail?
|
||||||
|
@State private var showingFullError = false
|
||||||
|
|
||||||
private var supportsTrends: Bool {
|
private var supportsTrends: Bool {
|
||||||
page == .slowQueries || page == .connectionStatistics ||
|
page == .slowQueries || page == .connectionStatistics ||
|
||||||
@@ -3077,6 +3079,7 @@ struct WorkbenchAdministrationView: View {
|
|||||||
} description: {
|
} description: {
|
||||||
Text(errorMessage)
|
Text(errorMessage)
|
||||||
} actions: {
|
} actions: {
|
||||||
|
Button("View Full Error…") { showingFullError = true }
|
||||||
Button("Retry") {
|
Button("Retry") {
|
||||||
Task { await refresh() }
|
Task { await refresh() }
|
||||||
}
|
}
|
||||||
@@ -3237,6 +3240,12 @@ struct WorkbenchAdministrationView: View {
|
|||||||
.sheet(isPresented: $showingTrends) {
|
.sheet(isPresented: $showingTrends) {
|
||||||
WorkbenchDiagnosticTrendsView(page: page, samples: diagnosticSamples)
|
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) {
|
.sheet(isPresented: $showingGrants) {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("Grants: \(selectedAccount ?? "")")
|
Text("Grants: \(selectedAccount ?? "")")
|
||||||
@@ -3345,6 +3354,11 @@ struct WorkbenchAdministrationView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if page == .serverLogs, let errorMessage {
|
||||||
|
Button("Full Error…") { showingFullError = true }
|
||||||
|
.help(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
if page != .dashboard {
|
if page != .dashboard {
|
||||||
TextField("Filter results", text: $searchText)
|
TextField("Filter results", text: $searchText)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
@@ -3452,6 +3466,40 @@ struct WorkbenchAdministrationView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.accessibilityIdentifier("scrollable-dashboard-phase51")
|
.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:
|
case .serverCounters, .connectionStatistics, .innodbMetrics:
|
||||||
WorkbenchLiveMonitorScreen(
|
WorkbenchLiveMonitorScreen(
|
||||||
page: page,
|
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 {
|
private var footer: some View {
|
||||||
HStack {
|
HStack {
|
||||||
if let lastUpdated {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user