connection updates
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user