connection updates

This commit is contained in:
Mark Gregory
2026-10-19 11:55:36 +01:00
parent 07c7e7d328
commit 7b2176b31b
2 changed files with 112 additions and 0 deletions
+107
View File
@@ -3007,6 +3007,102 @@ private struct DBSynOptionsInspector: View {
}
}
// Resource limits are per MySQL account (username + host), not per saved connection.
private struct DBSynAccountLimitsEditor: View {
let profile: ConnectionProfile
let account: [String]
@Environment(\.dismiss) private var dismiss
@State private var queries = ""
@State private var updates = ""
@State private var connections = ""
@State private var concurrent = ""
@State private var applying = false
@State private var error: String?
@State private var completed = false
private func quoted(_ value: String) -> String {
"'" + value.replacingOccurrences(of: "'", with: "''") + "'"
}
private var values: [String] { [queries, updates, connections, concurrent] }
private var valid: Bool {
values.allSatisfy { value in
guard let number = UInt32(value) else { return false }
return String(number) == value
}
}
private var changed: Bool {
account.count >= 8 && values != Array(account[4...7])
}
private var statement: String {
guard account.count >= 8, valid else { return "" }
return "ALTER USER \(quoted(account[0]))@\(quoted(account[1])) WITH MAX_QUERIES_PER_HOUR \(queries) MAX_UPDATES_PER_HOUR \(updates) MAX_CONNECTIONS_PER_HOUR \(connections) MAX_USER_CONNECTIONS \(concurrent)"
}
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Account Limits").font(.title2.bold())
Text("\(quoted(account[0]))@\(quoted(account[1]))")
.font(.system(.body, design: .monospaced)).textSelection(.enabled)
Form {
TextField("Maximum queries per hour", text: $queries)
TextField("Maximum updates per hour", text: $updates)
TextField("Maximum connections per hour", text: $connections)
TextField("Maximum concurrent connections", text: $concurrent)
}
.textFieldStyle(.roundedBorder)
Text("Enter non-negative whole numbers (0 = unlimited). Requires ALTER USER permission. Changes take effect on the MySQL server.")
.font(.caption).foregroundStyle(.secondary)
Text("SQL preview").font(.headline)
ScrollView {
Text(valid ? statement + ";" : "Enter valid whole numbers in all four fields.")
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(height: 78).padding(10)
.background(.quaternary.opacity(0.4), in: RoundedRectangle(cornerRadius: 8))
if let error {
Text(error).foregroundStyle(.red).textSelection(.enabled)
}
if completed { Text("Account limits updated.").foregroundStyle(.green) }
HStack {
Button("Revert") { loadOriginal(); error = nil; completed = false }
.disabled(applying || !changed)
Spacer()
Button("Close") { dismiss() }
Button("Apply") { Task { await apply() } }
.buttonStyle(.borderedProminent)
.disabled(!valid || !changed || applying)
}
}
.padding(24)
.frame(minWidth: 640, minHeight: 440)
.onAppear { loadOriginal() }
}
private func loadOriginal() {
guard account.count >= 8 else { return }
queries = account[4]; updates = account[5]
connections = account[6]; concurrent = account[7]
}
@MainActor
private func apply() async {
guard valid, changed, !applying else { return }
applying = true
error = nil
completed = false
defer { applying = false }
do {
_ = try await NativeMySQLRunner.run(
profile: profile, database: "",
password: PasswordStore.read(profile.id), sql: statement
)
completed = true
dismiss()
} catch {
self.error = error.localizedDescription
}
}
}
struct WorkbenchAdministrationView: View {
let page: WorkbenchAdminPage
let profile: ConnectionProfile?
@@ -3025,6 +3121,7 @@ struct WorkbenchAdministrationView: View {
@State private var grantsError: String? = nil
@State private var showingGrants = false
@State private var showingAccountManager = false
@State private var editingAccountLimits = false
@State private var selectedProcessID: String? = nil
@State private var pendingProcessAction: WorkbenchProcessAction? = nil
@State private var processActionError: String? = nil
@@ -3278,6 +3375,14 @@ struct WorkbenchAdministrationView: View {
)
}
}
.sheet(isPresented: $editingAccountLimits, onDismiss: {
Task { await refresh() }
}) {
if let profile, let selectedAccount,
let row = result.rows.first(where: { $0.count >= 8 && "\($0[0])\u{001f}\($0[1])" == selectedAccount }) {
DBSynAccountLimitsEditor(profile: profile, account: row)
}
}
.sheet(isPresented: $showingTrends) {
WorkbenchDiagnosticTrendsView(page: page, samples: diagnosticSamples)
}
@@ -3666,6 +3771,8 @@ struct WorkbenchAdministrationView: View {
accountLimit("Concurrent Connections", row: row, index: 7)
Text("0 means unlimited. Values are read from mysql.user.")
.font(.caption).foregroundStyle(.secondary)
Button("Edit Account Limits…") { editingAccountLimits = true }
.disabled(profile == nil)
}.frame(maxWidth: .infinity, alignment: .leading).padding(10)
}
case .roles: