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:
+5
View File
@@ -0,0 +1,5 @@
# Phase 74.6 — Editable account resource limits
Administration → Users and Privileges → Account Limits → Edit Account Limits… opens an editor for four MySQL resource limits. The editor validates unsigned 32-bit whole numbers, previews the ALTER USER SQL, supports Revert, and applies via the native MySQL runner. It refreshes the account list on dismissal. Zero means unlimited. Requires appropriate MySQL permissions.
Validation: Swift syntax parsing passed on Linux. Xcode compilation and live MySQL tests have not been run.