Add Create Database btn on admin side (#721)

* Add Create Database btn on admin side

* Remove unused function

* readd function

* replace refreshform function

* add authorize, remove database limit check

* add random words, use proper name function, catch exceptions on creation

* add validation, match old client area more

* Add more authorize to Database tab

* Add confirmation to delete

* make password hidden / revealable

* better clarification

* Set default and remove placeholder.

* Remove server import, add database model to auth

* Make same changes for the database host page

* Update app/Filament/Resources/ServerResource/Pages/EditServer.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* Update app/Filament/Resources/DatabaseHostResource/RelationManagers/DatabasesRelationManager.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* Update app/Filament/Resources/DatabaseHostResource/RelationManagers/DatabasesRelationManager.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* Remove each hidden

* Return nothing if user has no perms

* This is the way... Im done messing with it...

* Fix view permission for relationship manager

* Update app/Filament/Resources/DatabaseHostResource/RelationManagers/DatabasesRelationManager.php

* Pint

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
This commit is contained in:
Charles 2024-11-30 22:04:10 -05:00 committed by GitHub
parent b208835ed4
commit cd448cd9a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 113 additions and 20 deletions

View File

@ -101,11 +101,15 @@ class EditDatabaseHost extends EditRecord
public function getRelationManagers(): array public function getRelationManagers(): array
{ {
if (DatabasesRelationManager::canViewForRecord($this->getRecord(), static::class)) {
return [ return [
DatabasesRelationManager::class, DatabasesRelationManager::class,
]; ];
} }
return [];
}
protected function handleRecordUpdate(Model $record, array $data): Model protected function handleRecordUpdate(Model $record, array $data): Model
{ {
if (!$record instanceof DatabaseHost) { if (!$record instanceof DatabaseHost) {

View File

@ -24,21 +24,30 @@ class DatabasesRelationManager extends RelationManager
{ {
return $form return $form
->schema([ ->schema([
TextInput::make('database')->columnSpanFull(), TextInput::make('database')
->columnSpanFull(),
TextInput::make('username'), TextInput::make('username'),
TextInput::make('password') TextInput::make('password')
->password()
->revealable()
->hintAction( ->hintAction(
Action::make('rotate') Action::make('rotate')
->icon('tabler-refresh') ->icon('tabler-refresh')
->requiresConfirmation() ->requiresConfirmation()
->action(fn (DatabasePasswordService $service, Database $database, $set, $get) => $this->rotatePassword($service, $database, $set, $get)) ->action(fn (DatabasePasswordService $service, Database $database, $set, $get) => $this->rotatePassword($service, $database, $set, $get))
->authorize(fn (Database $database) => auth()->user()->can('update database', $database))
) )
->formatStateUsing(fn (Database $database) => $database->password), ->formatStateUsing(fn (Database $database) => $database->password),
TextInput::make('remote')->label('Connections From'), TextInput::make('remote')
TextInput::make('max_connections'), ->label('Connections From')
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
TextInput::make('max_connections')
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
TextInput::make('JDBC') TextInput::make('JDBC')
->label('JDBC Connection String') ->label('JDBC Connection String')
->columnSpanFull() ->columnSpanFull()
->password()
->revealable()
->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')), ->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
]); ]);
} }
@ -48,18 +57,25 @@ class DatabasesRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('servers') ->recordTitleAttribute('servers')
->columns([ ->columns([
TextColumn::make('database')->icon('tabler-database'), TextColumn::make('database')
TextColumn::make('username')->icon('tabler-user'), ->icon('tabler-database'),
TextColumn::make('remote'), TextColumn::make('username')
->icon('tabler-user'),
TextColumn::make('remote')
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
TextColumn::make('server.name') TextColumn::make('server.name')
->icon('tabler-brand-docker') ->icon('tabler-brand-docker')
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])), ->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
TextColumn::make('max_connections'), TextColumn::make('max_connections')
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
DateTimeColumn::make('created_at'), DateTimeColumn::make('created_at'),
]) ])
->actions([ ->actions([
DeleteAction::make(), DeleteAction::make()
ViewAction::make()->color('primary'), ->authorize(fn (Database $database) => auth()->user()->can('delete database', $database)),
ViewAction::make()
->color('primary')
->hidden(fn () => !auth()->user()->can('viewList database')),
]); ]);
} }

View File

@ -7,6 +7,7 @@ use App\Enums\ServerState;
use App\Filament\Resources\ServerResource; use App\Filament\Resources\ServerResource;
use App\Filament\Resources\ServerResource\RelationManagers\AllocationsRelationManager; use App\Filament\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
use App\Models\Database; use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg; use App\Models\Egg;
use App\Models\Mount; use App\Models\Mount;
use App\Models\Server; use App\Models\Server;
@ -608,7 +609,9 @@ class EditServer extends EditRecord
->columnSpanFull(), ->columnSpanFull(),
]), ]),
Tab::make('Databases') Tab::make('Databases')
->hidden(fn () => !auth()->user()->can('viewList database'))
->icon('tabler-database') ->icon('tabler-database')
->columns(4)
->schema([ ->schema([
Repeater::make('databases') Repeater::make('databases')
->grid() ->grid()
@ -622,35 +625,50 @@ class EditServer extends EditRecord
->formatStateUsing(fn ($record) => $record->database) ->formatStateUsing(fn ($record) => $record->database)
->hintAction( ->hintAction(
Action::make('Delete') Action::make('Delete')
->authorize(fn (Database $database) => auth()->user()->can('delete database', $database))
->color('danger') ->color('danger')
->icon('tabler-trash') ->icon('tabler-trash')
->action(fn (DatabaseManagementService $databaseManagementService, $record) => $databaseManagementService->delete($record)) ->requiresConfirmation()
->modalIcon('tabler-database-x')
->modalHeading('Delete Database?')
->modalSubmitActionLabel(fn (Get $get) => 'Delete ' . $get('database') . '?')
->modalDescription(fn (Get $get) => 'Are you sure you want to delete ' . $get('database') . '?')
->action(function (DatabaseManagementService $databaseManagementService, $record) {
$databaseManagementService->delete($record);
$this->fillForm();
})
), ),
TextInput::make('username') TextInput::make('username')
->disabled() ->disabled()
->formatStateUsing(fn ($record) => $record->username) ->formatStateUsing(fn ($record) => $record->username)
->columnSpan(2), ->columnSpan(1),
TextInput::make('password') TextInput::make('password')
->disabled() ->disabled()
->password()
->revealable()
->columnSpan(1)
->hintAction( ->hintAction(
Action::make('rotate') Action::make('rotate')
->authorize(fn (Database $database) => auth()->user()->can('update database', $database))
->icon('tabler-refresh') ->icon('tabler-refresh')
->requiresConfirmation() ->modalHeading('Change Database Password?')
->action(fn (DatabasePasswordService $service, $record, $set, $get) => $this->rotatePassword($service, $record, $set, $get)) ->action(fn (DatabasePasswordService $service, $record, $set, $get) => $this->rotatePassword($service, $record, $set, $get))
->requiresConfirmation()
) )
->formatStateUsing(fn (Database $database) => $database->password) ->formatStateUsing(fn (Database $database) => $database->password),
->columnSpan(2),
TextInput::make('remote') TextInput::make('remote')
->disabled() ->disabled()
->formatStateUsing(fn ($record) => $record->remote) ->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote)
->columnSpan(1) ->columnSpan(1)
->label('Connections From'), ->label('Connections From'),
TextInput::make('max_connections') TextInput::make('max_connections')
->disabled() ->disabled()
->formatStateUsing(fn ($record) => $record->max_connections) ->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections)
->columnSpan(1), ->columnSpan(1),
TextInput::make('JDBC') TextInput::make('JDBC')
->disabled() ->disabled()
->password()
->revealable()
->label('JDBC Connection String') ->label('JDBC Connection String')
->columnSpan(2) ->columnSpan(2)
->formatStateUsing(fn (Get $get, $record) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($record->password) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database')), ->formatStateUsing(fn (Get $get, $record) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($record->password) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database')),
@ -659,7 +677,57 @@ class EditServer extends EditRecord
->deletable(false) ->deletable(false)
->addable(false) ->addable(false)
->columnSpan(4), ->columnSpan(4),
])->columns(4), Forms\Components\Actions::make([
Action::make('createDatabase')
->authorize(fn () => auth()->user()->can('create database'))
->disabled(fn () => DatabaseHost::query()->count() < 1)
->label(fn () => DatabaseHost::query()->count() < 1 ? 'No Database Hosts' : 'Create Database')
->color(fn () => DatabaseHost::query()->count() < 1 ? 'danger' : 'primary')
->modalSubmitActionLabel('Create Database')
->action(function (array $data, DatabaseManagementService $service, Server $server, RandomWordService $randomWordService) {
if (empty($data['database'])) {
$data['database'] = $randomWordService->word() . random_int(1, 420);
}
if (empty($data['remote'])) {
$data['remote'] = '%';
}
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
try {
$service->setValidateDatabaseLimit(false)->create($server, $data);
} catch (Exception $e) {
Notification::make()
->title('Failed to Create Database')
->body($e->getMessage())
->danger()
->persistent()->send();
}
$this->fillForm();
})
->form([
Select::make('database_host_id')
->label('Database Host')
->required()
->placeholder('Select Database Host')
->relationship('node.databaseHosts', 'name')
->default(fn () => (DatabaseHost::query()->first())?->id)
->selectablePlaceholder(false),
TextInput::make('database')
->label('Database Name')
->alphaDash()
->prefix(fn (Server $server) => 's' . $server->id . '_')
->hintIcon('tabler-question-mark')
->hintIconTooltip('Leaving this blank will auto generate a random name'),
TextInput::make('remote')
->columnSpan(1)
->regex('/^[\w\-\/.%:]+$/')
->label('Connections From')
->hintIcon('tabler-question-mark')
->hintIconTooltip('Where connections should be allowed from. Leave blank to allow connections from anywhere.'),
]),
])->alignCenter()->columnSpanFull(),
]),
Tab::make('Actions') Tab::make('Actions')
->icon('tabler-settings') ->icon('tabler-settings')
->schema([ ->schema([

View File

@ -243,6 +243,11 @@ class Node extends Model
return $this->hasMany(Allocation::class); return $this->hasMany(Allocation::class);
} }
public function databaseHosts(): HasMany
{
return $this->hasMany(DatabaseHost::class);
}
/** /**
* Returns a boolean if the node is viable for an additional server to be placed on it. * Returns a boolean if the node is viable for an additional server to be placed on it.
*/ */