connection updates
This commit is contained in:
+125
-39
@@ -3155,36 +3155,6 @@ struct WorkbenchAdministrationView: View {
|
|||||||
processControls
|
processControls
|
||||||
}
|
}
|
||||||
|
|
||||||
if page == .users && !result.rows.isEmpty {
|
|
||||||
HStack {
|
|
||||||
Picker("Account", selection: $selectedAccount) {
|
|
||||||
Text("Select account").tag(String?.none)
|
|
||||||
ForEach(result.rows.indices, id: \.self) { i in
|
|
||||||
let row = result.rows[i]
|
|
||||||
if row.count >= 2 {
|
|
||||||
Text("\(row[0])@\(row[1])")
|
|
||||||
.tag(Optional("\(row[0])\u{001f}\(row[1])"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: 320)
|
|
||||||
Button("Show Grants") {
|
|
||||||
Task { await showGrants() }
|
|
||||||
}
|
|
||||||
.disabled(selectedAccount == nil)
|
|
||||||
Button("Manage Accounts…") {
|
|
||||||
autoRefresh = false
|
|
||||||
showingAccountManager = true
|
|
||||||
}
|
|
||||||
.disabled(profile == nil)
|
|
||||||
Spacer()
|
|
||||||
Text("Changes require confirmation")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
footer
|
footer
|
||||||
@@ -3466,6 +3436,8 @@ struct WorkbenchAdministrationView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.accessibilityIdentifier("scrollable-dashboard-phase51")
|
.accessibilityIdentifier("scrollable-dashboard-phase51")
|
||||||
|
case .users:
|
||||||
|
accountAdministration
|
||||||
case .serverLogs:
|
case .serverLogs:
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -3516,6 +3488,108 @@ struct WorkbenchAdministrationView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workbench-style account administration: persistent account list and inspector.
|
||||||
|
private var accountAdministration: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack {
|
||||||
|
Label("User Accounts", systemImage: "person.2")
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
|
Text("\(filteredRows.count)")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.padding(12)
|
||||||
|
Divider()
|
||||||
|
List(selection: $selectedAccount) {
|
||||||
|
ForEach(filteredRows.indices, id: \.self) { index in
|
||||||
|
let row = filteredRows[index]
|
||||||
|
if row.count >= 2 {
|
||||||
|
let key = "\(row[0])\u{001f}\(row[1])"
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Label(row[0].isEmpty ? "(anonymous)" : row[0], systemImage: "person.crop.circle")
|
||||||
|
.font(.body.weight(.medium))
|
||||||
|
Text("Host: \(row[1])")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.tag(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.sidebar)
|
||||||
|
Divider()
|
||||||
|
HStack {
|
||||||
|
Button {
|
||||||
|
selectedAccount = nil
|
||||||
|
showingAccountManager = true
|
||||||
|
} label: { Image(systemName: "plus") }
|
||||||
|
.help("Create MySQL account")
|
||||||
|
Spacer()
|
||||||
|
Button { Task { await refresh() } } label: {
|
||||||
|
Image(systemName: "arrow.clockwise")
|
||||||
|
}
|
||||||
|
.help("Refresh accounts")
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.padding(12)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 205, idealWidth: 245, maxWidth: 290)
|
||||||
|
Divider()
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
if let selectedAccount,
|
||||||
|
let row = result.rows.first(where: {
|
||||||
|
$0.count >= 2 && "\($0[0])\u{001f}\($0[1])" == selectedAccount
|
||||||
|
}) {
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
Text(row[0].isEmpty ? "Anonymous account" : row[0])
|
||||||
|
.font(.title2.bold())
|
||||||
|
Text("'\(row[0])'@'\(row[1])'")
|
||||||
|
.font(.system(.subheadline, design: .monospaced))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Button("Edit Account…") {
|
||||||
|
showingAccountManager = true
|
||||||
|
}
|
||||||
|
.disabled(profile == nil)
|
||||||
|
}
|
||||||
|
.padding(18)
|
||||||
|
Divider()
|
||||||
|
Form {
|
||||||
|
Section("Login") {
|
||||||
|
LabeledContent("User", value: row[0])
|
||||||
|
LabeledContent("Host", value: row[1])
|
||||||
|
if row.count > 2 { LabeledContent("Authentication plugin", value: row[2]) }
|
||||||
|
if row.count > 3 {
|
||||||
|
LabeledContent("Account status", value: row[3] == "Y" ? "Locked" : "Active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section("Administrative Actions") {
|
||||||
|
HStack {
|
||||||
|
Button("View Grants…") { Task { await showGrants() } }
|
||||||
|
Button("Manage Privileges…") { showingAccountManager = true }
|
||||||
|
Button("Change Password…") { showingAccountManager = true }
|
||||||
|
}
|
||||||
|
Text("Changes require MySQL administrative privileges and explicit confirmation.")
|
||||||
|
.font(.caption).foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.formStyle(.grouped)
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
} else {
|
||||||
|
ContentUnavailableView("Select a MySQL Account", systemImage: "person.crop.circle.badge.checkmark", description: Text("Choose an account on the left to inspect its login, authentication and privileges."))
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
private func logValue(_ name: String, in row: [String]) -> String {
|
private func logValue(_ name: String, in row: [String]) -> String {
|
||||||
guard let index = result.columns.firstIndex(where: { $0.caseInsensitiveCompare(name) == .orderedSame }),
|
guard let index = result.columns.firstIndex(where: { $0.caseInsensitiveCompare(name) == .orderedSame }),
|
||||||
index < row.count else { return "" }
|
index < row.count else { return "" }
|
||||||
@@ -4963,18 +5037,30 @@ private struct WorkbenchAccountManagerView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
Text("Manage MySQL Accounts").font(.title2.bold())
|
HStack {
|
||||||
Text("Account changes run on \(profile.name). Review the SQL before applying it.")
|
Label("Users and Privileges", systemImage: "person.crop.circle.badge.checkmark")
|
||||||
|
.font(.title2.bold())
|
||||||
|
Spacer()
|
||||||
|
Text(profile.name).foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Text("Manage login credentials, account access and schema privileges. Every change requires SQL review and confirmation.")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Form {
|
Form {
|
||||||
Picker("Operation", selection: $action) {
|
Section("Account") {
|
||||||
ForEach(WorkbenchAccountAction.allCases) { option in
|
TextField("Username", text: $username)
|
||||||
Text(option.rawValue).tag(option)
|
.textContentType(.username)
|
||||||
}
|
TextField("Host", text: $hostname)
|
||||||
|
Text("MySQL accounts are identified by both username and host.")
|
||||||
|
.font(.caption).foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Section("Account Management") {
|
||||||
|
Picker("Operation", selection: $action) {
|
||||||
|
ForEach(WorkbenchAccountAction.allCases) { option in
|
||||||
|
Text(option.rawValue).tag(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
}
|
}
|
||||||
TextField("Username", text: $username)
|
|
||||||
.textContentType(.username)
|
|
||||||
TextField("Host", text: $hostname)
|
|
||||||
if action == .create || action == .password {
|
if action == .create || action == .password {
|
||||||
SecureField("Password", text: $password)
|
SecureField("Password", text: $password)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user