diff --git a/app/Extensions/OAuth/Providers/DiscordProvider.php b/app/Extensions/OAuth/Providers/DiscordProvider.php index e4767ebd9..83b5309a2 100644 --- a/app/Extensions/OAuth/Providers/DiscordProvider.php +++ b/app/Extensions/OAuth/Providers/DiscordProvider.php @@ -42,7 +42,7 @@ final class DiscordProvider extends OAuthProvider ->label('Redirect URL') ->dehydrated() ->disabled() - //TODO ->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->hintCopy() ->formatStateUsing(fn () => url('/auth/oauth/callback/discord')), ]), ], parent::getSetupSteps()); diff --git a/app/Extensions/OAuth/Providers/GithubProvider.php b/app/Extensions/OAuth/Providers/GithubProvider.php index 2ac3e7860..388d1add1 100644 --- a/app/Extensions/OAuth/Providers/GithubProvider.php +++ b/app/Extensions/OAuth/Providers/GithubProvider.php @@ -8,7 +8,6 @@ use Filament\Forms\Components\TextInput; use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Blade; use Illuminate\Support\HtmlString; -use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; final class GithubProvider extends OAuthProvider { @@ -34,7 +33,7 @@ final class GithubProvider extends OAuthProvider ->label('Authorization callback URL') ->dehydrated() ->disabled() - //TODO ->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->hintCopy() ->default(fn () => url('/auth/oauth/callback/github')), TextEntry::make('register_application') ->hiddenLabel() diff --git a/app/Extensions/OAuth/Providers/GitlabProvider.php b/app/Extensions/OAuth/Providers/GitlabProvider.php index fb623d400..c12f78d85 100644 --- a/app/Extensions/OAuth/Providers/GitlabProvider.php +++ b/app/Extensions/OAuth/Providers/GitlabProvider.php @@ -8,7 +8,6 @@ use Filament\Forms\Components\TextInput; use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Blade; use Illuminate\Support\HtmlString; -use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; final class GitlabProvider extends OAuthProvider { @@ -54,7 +53,7 @@ final class GitlabProvider extends OAuthProvider ->label('Redirect URI') ->dehydrated() ->disabled() - //TODO ->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->hintCopy() ->default(fn () => url('/auth/oauth/callback/gitlab')), ]), ], parent::getSetupSteps()); diff --git a/app/Filament/Admin/Resources/DatabaseHostResource/Pages/CreateDatabaseHost.php b/app/Filament/Admin/Resources/DatabaseHostResource/Pages/CreateDatabaseHost.php index e7b3d8aae..f4d3528b3 100644 --- a/app/Filament/Admin/Resources/DatabaseHostResource/Pages/CreateDatabaseHost.php +++ b/app/Filament/Admin/Resources/DatabaseHostResource/Pages/CreateDatabaseHost.php @@ -97,14 +97,14 @@ class CreateDatabaseHost extends CreateRecord ->default(fn (Get $get) => "CREATE USER '{$get('username')}'@'{$get('panel_ip')}' IDENTIFIED BY '{$get('password')}';") ->disabled() ->dehydrated(false) - // TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->columnSpanFull(), TextInput::make('assign_permissions') ->label(trans('admin/databasehost.setup.command_assign_permissions')) ->default(fn (Get $get) => "GRANT ALL PRIVILEGES ON *.* TO '{$get('username')}'@'{$get('panel_ip')}' WITH GRANT OPTION;") ->disabled() ->dehydrated(false) - // TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->columnSpanFull(), TextEntry::make('cli_exit') ->hiddenLabel() diff --git a/app/Filament/Admin/Resources/NodeResource/Pages/EditNode.php b/app/Filament/Admin/Resources/NodeResource/Pages/EditNode.php index b34669630..34157063f 100644 --- a/app/Filament/Admin/Resources/NodeResource/Pages/EditNode.php +++ b/app/Filament/Admin/Resources/NodeResource/Pages/EditNode.php @@ -293,7 +293,7 @@ class EditNode extends EditRecord 'lg' => 2, ]) ->label(trans('admin/node.node_uuid')) - // TODO ->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->hintCopy() ->disabled(), TagsInput::make('tags') ->label(trans('admin/node.tags')) @@ -554,7 +554,7 @@ class EditNode extends EditRecord ->label('/etc/pelican/config.yml') ->disabled() ->rows(19) - //TODO ->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->hintCopy() ->columnSpanFull(), Grid::make() ->columns() @@ -591,7 +591,7 @@ class EditNode extends EditRecord ->label(trans('admin/node.auto_command')) ->readOnly() ->autosize() - //TODO ->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->hintCopy() ->formatStateUsing(fn (NodeAutoDeployService $service, Node $node, Set $set, Get $get) => $set('generatedToken', $service->handle(request(), $node, $get('docker')))), ]) ->mountUsing(function (Schema $schema) { diff --git a/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php index 7b64cc744..8e55b60a3 100644 --- a/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php @@ -180,7 +180,7 @@ class EditServer extends EditRecord TextInput::make('uuid') ->label(trans('admin/server.uuid')) - //TODO ->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->suffixCopy() ->columnSpan([ 'default' => 2, 'sm' => 1, @@ -191,7 +191,7 @@ class EditServer extends EditRecord ->dehydrated(false), TextInput::make('uuid_short') ->label(trans('admin/server.short_uuid')) - //TODO ->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->suffixCopy() ->columnSpan([ 'default' => 2, 'sm' => 1, @@ -620,7 +620,7 @@ class EditServer extends EditRecord ->hintAction(PreviewStartupAction::make('preview')), Textarea::make('defaultStartup') - //TODO ->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->hintCopy() ->label(trans('admin/server.default_startup')) ->disabled() ->autosize() @@ -719,13 +719,13 @@ class EditServer extends EditRecord ->label(trans('admin/databasehost.table.host')) ->disabled() ->formatStateUsing(fn ($record) => $record->address()) - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->columnSpan(1), TextInput::make('database') ->label(trans('admin/databasehost.table.database')) ->disabled() ->formatStateUsing(fn ($record) => $record->database) - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->hintAction( Action::make('Delete') ->label(trans('filament-actions::delete.single.modal.actions.delete.label')) @@ -746,7 +746,7 @@ class EditServer extends EditRecord ->label(trans('admin/databasehost.table.username')) ->disabled() ->formatStateUsing(fn ($record) => $record->username) - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->columnSpan(1), TextInput::make('password') ->label(trans('admin/databasehost.table.password')) @@ -755,7 +755,7 @@ class EditServer extends EditRecord ->revealable() ->columnSpan(1) ->hintAction(RotateDatabasePasswordAction::make()) - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->formatStateUsing(fn (Database $database) => $database->password), TextInput::make('remote') ->disabled() @@ -773,8 +773,8 @@ class EditServer extends EditRecord ->revealable() ->label(trans('admin/databasehost.table.connection_string')) ->columnSpan(2) - ->formatStateUsing(fn (Database $record) => $record->jdbc), - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), + ->formatStateUsing(fn (Database $record) => $record->jdbc) + ->suffixCopy(), ]) ->relationship('databases') ->deletable(false) diff --git a/app/Filament/Components/Actions/CopyAction.php b/app/Filament/Components/Actions/CopyAction.php new file mode 100644 index 000000000..00bfa5139 --- /dev/null +++ b/app/Filament/Components/Actions/CopyAction.php @@ -0,0 +1,46 @@ +icon('tabler-clipboard-copy'); + + $this->successNotificationTitle(trans('filament::components/copyable.messages.copied')); + + $this->extraAttributes(fn () => [ + 'x-on:click' => new HtmlString('window.navigator.clipboard.writeText('.$this->getCopyable().'); $tooltip('.Js::from($this->getSuccessNotificationTitle()).');'), + ]); + } + + public function copyable(Closure|string|null $copyable): self + { + $this->copyable = $copyable; + + return $this; + } + + public function getCopyable(): ?string + { + return Js::from($this->evaluate($this->copyable)); + } +} diff --git a/app/Filament/Server/Pages/Settings.php b/app/Filament/Server/Pages/Settings.php index 4331d2c42..6a7bf3184 100644 --- a/app/Filament/Server/Pages/Settings.php +++ b/app/Filament/Server/Pages/Settings.php @@ -165,7 +165,7 @@ class Settings extends ServerFormPage ->label('Connection') ->columnSpan(1) ->disabled() - //TODO ->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->suffixCopy() ->hintAction( Action::make('connect_sftp') ->label('Connect to SFTP') @@ -185,7 +185,7 @@ class Settings extends ServerFormPage TextInput::make('username') ->label('Username') ->columnSpan(1) - //TODO ->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->suffixCopy() ->disabled() ->formatStateUsing(fn (Server $server) => auth()->user()->username . '.' . $server->uuid_short), TextEntry::make('password') diff --git a/app/Filament/Server/Resources/DatabaseResource.php b/app/Filament/Server/Resources/DatabaseResource.php index d3fc52ccf..4e114cd2b 100644 --- a/app/Filament/Server/Resources/DatabaseResource.php +++ b/app/Filament/Server/Resources/DatabaseResource.php @@ -69,12 +69,12 @@ class DatabaseResource extends Resource return $schema ->components([ TextInput::make('host') - ->formatStateUsing(fn (Database $database) => $database->address()), - // TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), - TextInput::make('database'), - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), - TextInput::make('username'), - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), + ->formatStateUsing(fn (Database $database) => $database->address()) + ->suffixCopy(), + TextInput::make('database') + ->suffixCopy(), + TextInput::make('username') + ->suffixCopy(), TextInput::make('password') ->password()->revealable() ->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) @@ -82,7 +82,7 @@ class DatabaseResource extends Resource RotateDatabasePasswordAction::make() ->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server)) ) - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->formatStateUsing(fn (Database $database) => $database->password), TextInput::make('remote') ->label('Connections From'), @@ -92,7 +92,7 @@ class DatabaseResource extends Resource ->label('JDBC Connection String') ->password()->revealable() ->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) - //TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->suffixCopy() ->columnSpanFull() ->formatStateUsing(fn (Database $database) => $database->jdbc), ]); diff --git a/app/Livewire/Installer/Steps/QueueStep.php b/app/Livewire/Installer/Steps/QueueStep.php index 7fb183285..48afcc32b 100644 --- a/app/Livewire/Installer/Steps/QueueStep.php +++ b/app/Livewire/Installer/Steps/QueueStep.php @@ -48,14 +48,14 @@ class QueueStep TextInput::make('crontab') ->label(new HtmlString('Run the following command to set up your crontab. Note that www-data is your webserver user. On some systems this username might be different!')) ->disabled() - //TODO ->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->hintCopy() ->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -') ->hidden(fn () => @file_exists('/.dockerenv')) ->columnSpanFull(), TextInput::make('queueService') ->label(new HtmlString('To setup the queue worker service you simply have to run the following command.')) ->disabled() - //TODO ->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) + ->hintCopy() ->default('sudo php ' . base_path() . '/artisan p:environment:queue-service') ->hidden(fn () => @file_exists('/.dockerenv')) ->columnSpanFull(), diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index a5531954f..279831f54 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -36,10 +36,13 @@ use App\Extensions\OAuth\Providers\CommonProvider; use App\Extensions\OAuth\Providers\DiscordProvider; use App\Extensions\OAuth\Providers\GithubProvider; use App\Extensions\OAuth\Providers\SteamProvider; +use App\Filament\Components\Actions\CopyAction; use App\Services\Helpers\SoftwareVersionService; use Dedoc\Scramble\Scramble; use Dedoc\Scramble\Support\Generator\OpenApi; use Dedoc\Scramble\Support\Generator\SecurityScheme; +use Filament\Forms\Components\Field; +use Filament\Forms\Components\TextInput; use Filament\Support\Colors\Color; use Filament\Support\Facades\FilamentColor; use Filament\Support\Facades\FilamentView; @@ -190,6 +193,16 @@ class AppServiceProvider extends ServiceProvider $component->dispatch('alertBannerSent'); }); + Field::macro('suffixCopy', function () { + /** @var TextInput $this */ + return $this->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null); // @phpstan-ignore varTag.nativeType + }); + + Field::macro('hintCopy', function () { + /** @var Field $this */ + return $this->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null); // @phpstan-ignore varTag.nativeType + }); + // Don't run any health checks during tests if (!$app->runningUnitTests()) { Health::checks([ diff --git a/phpstan.neon b/phpstan.neon index 3320b0842..8654e5d87 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,6 +14,9 @@ parameters: ignoreErrors: - identifier: argument.templateType - identifier: missingType.generics + - '#Call to an undefined method Filament\\Forms\\Components\\TextInput::suffixCopy\(\)#' + - '#Call to an undefined method Filament\\Forms\\Components\\TextInput::hintCopy\(\)#' + - '#Call to an undefined method Filament\\Forms\\Components\\Textarea::hintCopy\(\)#' # We are getting and setting environment variables directly -