'refreshData']; public function mount(): void { $this->loadData(); } public function refreshData(): void { $this->loadData(); $this->resetTable(); } public function getAgent(): AgentClient { if ($this->agent === null) { $this->agent = new AgentClient(); } return $this->agent; } public function getUsername(): string { return Auth::user()->username; } public function loadData(): void { try { $result = $this->getAgent()->mysqlListUsers($this->getUsername()); $this->users = $result['users'] ?? []; // Filter out the master admin user $this->users = array_values(array_filter($this->users, function($user) { return $user['user'] !== $this->getUsername() . '_admin'; })); $this->userGrants = []; foreach ($this->users as $user) { $this->loadUserGrants($user['user'], $user['host']); } } catch (Exception $e) { $this->users = []; } try { $result = $this->getAgent()->mysqlListDatabases($this->getUsername()); $this->databases = $result['databases'] ?? []; } catch (Exception $e) { $this->databases = []; } } protected function loadUserGrants(string $user, string $host): void { try { $result = $this->getAgent()->mysqlGetPrivileges($this->getUsername(), $user, $host); $this->userGrants["$user@$host"] = $result['parsed'] ?? []; } catch (Exception $e) { $this->userGrants["$user@$host"] = []; } } public function getUserGrants(string $user, string $host): array { return $this->userGrants["$user@$host"] ?? []; } public function table(Table $table): Table { return $table ->records(fn () => $this->users) ->columns([ TextColumn::make('user') ->label(__('User')) ->icon('heroicon-o-user') ->iconColor('primary') ->description(fn (array $record): string => '@ ' . $record['host']) ->weight('medium') ->searchable(), ViewColumn::make('privileges') ->label(__('Database Privileges')) ->view('filament.jabali.tables.columns.user-privileges'), ]) ->recordActions([ Action::make('addPrivileges') ->label(__('Add Access')) ->icon('heroicon-o-plus') ->color('success') ->modalHeading(__('Add Database Access')) ->modalDescription(fn (array $record): string => __('Grant privileges to :user', ['user' => $record['user'] . '@' . $record['host']])) ->modalIcon('heroicon-o-shield-check') ->modalIconColor('success') ->modalWidth('lg') ->modalSubmitActionLabel(__('Grant Access')) ->form(fn (array $record): array => $this->getPrivilegesForm()) ->action(function (array $data, array $record): void { $this->grantPrivileges($record['user'], $record['host'], $data); }), Action::make('changePassword') ->label(__('Password')) ->icon('heroicon-o-key') ->color('warning') ->modalHeading(__('Change Password')) ->modalDescription(fn (array $record): string => $record['user'] . '@' . $record['host']) ->modalIcon('heroicon-o-key') ->modalIconColor('warning') ->modalSubmitActionLabel(__('Change Password')) ->form([ TextInput::make('password') ->label(__('New Password')) ->password() ->revealable() ->required() ->minLength(8) ->default(fn () => $this->generateSecurePassword()) ->suffixActions([ Action::make('generate') ->icon('heroicon-o-arrow-path') ->tooltip(__('Generate secure password')) ->action(fn ($set) => $set('password', $this->generateSecurePassword())), Action::make('copy') ->icon('heroicon-o-clipboard-document') ->tooltip(__('Copy to clipboard')) ->action(function ($state, $livewire) { if ($state) { $escaped = addslashes($state); $livewire->js("navigator.clipboard.writeText('{$escaped}')"); Notification::make() ->title(__('Copied to clipboard')) ->success() ->duration(2000) ->send(); } }), ]), ]) ->action(function (array $data, array $record): void { $this->changePassword($record['user'], $record['host'], $data['password']); }), Action::make('delete') ->label(__('Delete')) ->icon('heroicon-o-trash') ->color('danger') ->requiresConfirmation() ->modalHeading(__('Delete User')) ->modalDescription(fn (array $record): string => __("Delete user ':user'?", ['user' => $record['user'] . '@' . $record['host']])) ->modalIcon('heroicon-o-trash') ->modalIconColor('danger') ->action(function (array $record): void { $this->deleteUser($record['user'], $record['host']); }), ]) ->emptyStateHeading(__('No database users yet')) ->emptyStateDescription(__('Click "New User" to create one')) ->emptyStateIcon('heroicon-o-users') ->striped(); } public function getTableRecordKey(Model|array $record): string { return is_array($record) ? $record['user'] . '@' . $record['host'] : $record->getKey(); } protected function getPrivilegesForm(): array { $dbOptions = []; foreach ($this->databases as $db) { $dbOptions[$db['name']] = $db['name']; } return [ Select::make('database') ->label(__('Database')) ->options($dbOptions) ->required() ->searchable() ->live(), Radio::make('privilege_type') ->label(__('Privilege Type')) ->options([ 'all' => __('ALL PRIVILEGES'), 'specific' => __('Specific privileges'), ]) ->default('all') ->required() ->live(), CheckboxList::make('specific_privileges') ->label(__('Select Privileges')) ->options([ 'SELECT' => 'SELECT', 'INSERT' => 'INSERT', 'UPDATE' => 'UPDATE', 'DELETE' => 'DELETE', 'CREATE' => 'CREATE', 'DROP' => 'DROP', 'INDEX' => 'INDEX', 'ALTER' => 'ALTER', ]) ->columns(2) ->visible(fn (callable $get): bool => $get('privilege_type') === 'specific'), ]; } public function grantPrivileges(string $user, string $host, array $data): void { $privs = $data['privilege_type'] === 'all' ? ['ALL'] : ($data['specific_privileges'] ?? []); try { $this->getAgent()->mysqlGrantPrivileges($this->getUsername(), $user, $data['database'], $privs, $host); Notification::make()->title(__('Privileges granted'))->success()->send(); $this->loadData(); $this->resetTable(); } catch (Exception $e) { Notification::make()->title(__('Error'))->body($e->getMessage())->danger()->send(); } } public function revokePrivileges(string $user, string $host, string $database): void { try { $this->getAgent()->mysqlRevokePrivileges($this->getUsername(), $user, $database, $host); Notification::make()->title(__('Access revoked'))->success()->send(); $this->loadData(); $this->resetTable(); } catch (Exception $e) { Notification::make()->title(__('Error'))->body($e->getMessage())->danger()->send(); } } public function changePassword(string $user, string $host, string $password): void { try { $this->getAgent()->mysqlChangePassword($this->getUsername(), $user, $password, $host); MysqlCredential::updateOrCreate( ['user_id' => Auth::id(), 'mysql_username' => $user], ['mysql_password_encrypted' => Crypt::encryptString($password)] ); Notification::make()->title(__('Password changed'))->success()->send(); } catch (Exception $e) { Notification::make()->title(__('Error'))->body($e->getMessage())->danger()->send(); } } public function deleteUser(string $user, string $host): void { try { $this->getAgent()->mysqlDeleteUser($this->getUsername(), $user, $host); MysqlCredential::where('user_id', Auth::id())->where('mysql_username', $user)->delete(); Notification::make()->title(__('User deleted'))->success()->send(); $this->loadData(); $this->resetTable(); } catch (Exception $e) { Notification::make()->title(__('Error'))->body($e->getMessage())->danger()->send(); } } public function generateSecurePassword(int $length = 16): string { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; $password = ''; for ($i = 0; $i < $length; $i++) { $password .= $chars[random_int(0, strlen($chars) - 1)]; } return $password; } public function render() { return view('livewire.database-users-table'); } }