From a5858a6d9bd4dc1cbac1baa5c263235ebf9967c0 Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+rmartinoscar@users.noreply.github.com> Date: Wed, 24 Sep 2025 01:22:29 +0200 Subject: [PATCH] Allow clipboard.writeText without HTTPS (#1723) --- .../Resources/ApiKeys/ApiKeyResource.php | 2 +- .../Pages/CreateDatabaseHost.php | 4 ++-- .../Resources/Servers/Pages/EditServer.php | 4 ++-- .../Resources/Servers/Pages/ListServers.php | 2 +- .../Server/Components/SmallStatBlock.php | 22 +++---------------- app/Filament/Server/Pages/Settings.php | 4 ++-- .../Resources/Databases/DatabaseResource.php | 10 ++++----- .../Server/Widgets/ServerOverview.php | 18 ++------------- app/Providers/AppServiceProvider.php | 2 +- lang/en/server/dashboard.php | 2 -- resources/js/app.js | 17 ++++++++++++++ .../components/server-console.blade.php | 2 +- .../server-small-data-block.blade.php | 11 +++++++--- 13 files changed, 45 insertions(+), 55 deletions(-) diff --git a/app/Filament/Admin/Resources/ApiKeys/ApiKeyResource.php b/app/Filament/Admin/Resources/ApiKeys/ApiKeyResource.php index 89f1a3449..896bb4890 100644 --- a/app/Filament/Admin/Resources/ApiKeys/ApiKeyResource.php +++ b/app/Filament/Admin/Resources/ApiKeys/ApiKeyResource.php @@ -79,7 +79,7 @@ class ApiKeyResource extends Resource ->label(trans('admin/apikey.table.key')) ->icon('tabler-clipboard-text') ->state(fn (ApiKey $key) => $key->identifier . $key->token) - ->copyable(fn () => request()->isSecure()), + ->copyable(), TextColumn::make('memo') ->label(trans('admin/apikey.table.description')) ->wrap() diff --git a/app/Filament/Admin/Resources/DatabaseHosts/Pages/CreateDatabaseHost.php b/app/Filament/Admin/Resources/DatabaseHosts/Pages/CreateDatabaseHost.php index c24c599b1..ee710ad12 100644 --- a/app/Filament/Admin/Resources/DatabaseHosts/Pages/CreateDatabaseHost.php +++ b/app/Filament/Admin/Resources/DatabaseHosts/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) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->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) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->columnSpanFull(), TextEntry::make('cli_exit') ->hiddenLabel() diff --git a/app/Filament/Admin/Resources/Servers/Pages/EditServer.php b/app/Filament/Admin/Resources/Servers/Pages/EditServer.php index b2cc9543b..890af818f 100644 --- a/app/Filament/Admin/Resources/Servers/Pages/EditServer.php +++ b/app/Filament/Admin/Resources/Servers/Pages/EditServer.php @@ -179,7 +179,7 @@ class EditServer extends EditRecord TextInput::make('uuid') ->label(trans('admin/server.uuid')) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->columnSpan([ 'default' => 2, 'sm' => 1, @@ -190,7 +190,7 @@ class EditServer extends EditRecord ->dehydrated(false), TextInput::make('uuid_short') ->label(trans('admin/server.short_uuid')) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->columnSpan([ 'default' => 2, 'sm' => 1, diff --git a/app/Filament/App/Resources/Servers/Pages/ListServers.php b/app/Filament/App/Resources/Servers/Pages/ListServers.php index 1294d16a6..4c86174c0 100644 --- a/app/Filament/App/Resources/Servers/Pages/ListServers.php +++ b/app/Filament/App/Resources/Servers/Pages/ListServers.php @@ -77,7 +77,7 @@ class ListServers extends ListRecords ->label('') ->badge() ->visibleFrom('md') - ->copyable(request()->isSecure()) + ->copyable() ->state(fn (Server $server) => $server->allocation->address ?? 'None'), TextColumn::make('cpuUsage') ->label(trans('server/dashboard.resources')) diff --git a/app/Filament/Server/Components/SmallStatBlock.php b/app/Filament/Server/Components/SmallStatBlock.php index 17e4202c7..6ca5515e6 100644 --- a/app/Filament/Server/Components/SmallStatBlock.php +++ b/app/Filament/Server/Components/SmallStatBlock.php @@ -2,28 +2,12 @@ namespace App\Filament\Server\Components; -use Closure; +use Filament\Support\Concerns\CanBeCopied; use Filament\Widgets\StatsOverviewWidget\Stat; -use Illuminate\Contracts\View\View; class SmallStatBlock extends Stat { - protected bool|Closure $copyOnClick = false; + use CanBeCopied; - public function copyOnClick(bool|Closure $copyOnClick = true): static - { - $this->copyOnClick = $copyOnClick; - - return $this; - } - - public function shouldCopyOnClick(): bool - { - return $this->evaluate($this->copyOnClick); - } - - public function render(): View - { - return view('filament.components.server-small-data-block', array_merge($this->getViewData(), $this->extractPublicMethods())); - } + protected string $view = 'filament.components.server-small-data-block'; } diff --git a/app/Filament/Server/Pages/Settings.php b/app/Filament/Server/Pages/Settings.php index 3e40cb09f..903a70e41 100644 --- a/app/Filament/Server/Pages/Settings.php +++ b/app/Filament/Server/Pages/Settings.php @@ -165,7 +165,7 @@ class Settings extends ServerFormPage ->label(trans('server/setting.server_info.sftp.connection')) ->columnSpan(1) ->disabled() - ->copyable(fn () => request()->isSecure()) + ->copyable() ->hintAction( Action::make('connect_sftp') ->label(trans('server/setting.server_info.sftp.action')) @@ -185,7 +185,7 @@ class Settings extends ServerFormPage TextInput::make('username') ->label(trans('server/setting.server_info.sftp.username')) ->columnSpan(1) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->disabled() ->formatStateUsing(fn (Server $server) => auth()->user()->username . '.' . $server->uuid_short), TextEntry::make('password') diff --git a/app/Filament/Server/Resources/Databases/DatabaseResource.php b/app/Filament/Server/Resources/Databases/DatabaseResource.php index f13f07c32..e1fbd8b9a 100644 --- a/app/Filament/Server/Resources/Databases/DatabaseResource.php +++ b/app/Filament/Server/Resources/Databases/DatabaseResource.php @@ -78,13 +78,13 @@ class DatabaseResource extends Resource TextInput::make('host') ->label(trans('server/database.host')) ->formatStateUsing(fn (Database $database) => $database->address()) - ->copyable(fn () => request()->isSecure()), + ->copyable(), TextInput::make('database') ->label(trans('server/database.database')) - ->copyable(fn () => request()->isSecure()), + ->copyable(), TextInput::make('username') ->label(trans('server/database.username')) - ->copyable(fn () => request()->isSecure()), + ->copyable(), TextInput::make('password') ->label(trans('server/database.password')) ->password()->revealable() @@ -93,7 +93,7 @@ class DatabaseResource extends Resource RotateDatabasePasswordAction::make() ->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server)) ) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->formatStateUsing(fn (Database $database) => $database->password), TextInput::make('remote') ->label(trans('server/database.remote')), @@ -104,7 +104,7 @@ class DatabaseResource extends Resource ->label(trans('server/database.jdbc')) ->password()->revealable() ->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) - ->copyable(fn () => request()->isSecure()) + ->copyable() ->columnSpanFull() ->formatStateUsing(fn (Database $database) => $database->jdbc), ]); diff --git a/app/Filament/Server/Widgets/ServerOverview.php b/app/Filament/Server/Widgets/ServerOverview.php index 1c505aa94..3bbffac6d 100644 --- a/app/Filament/Server/Widgets/ServerOverview.php +++ b/app/Filament/Server/Widgets/ServerOverview.php @@ -6,9 +6,7 @@ use App\Enums\ContainerStatus; use App\Filament\Server\Components\SmallStatBlock; use App\Models\Server; use Carbon\CarbonInterface; -use Filament\Notifications\Notification; use Filament\Widgets\StatsOverviewWidget; -use Livewire\Attributes\On; class ServerOverview extends StatsOverviewWidget { @@ -20,10 +18,10 @@ class ServerOverview extends StatsOverviewWidget { return [ SmallStatBlock::make(trans('server/console.labels.name'), $this->server->name) - ->copyOnClick(fn () => request()->isSecure()), + ->copyable(), SmallStatBlock::make(trans('server/console.labels.status'), $this->status()), SmallStatBlock::make(trans('server/console.labels.address'), $this->server?->allocation->address ?? 'None') - ->copyOnClick(fn () => request()->isSecure()), + ->copyable(), SmallStatBlock::make(trans('server/console.labels.cpu'), $this->cpuUsage()), SmallStatBlock::make(trans('server/console.labels.memory'), $this->memoryUsage()), SmallStatBlock::make(trans('server/console.labels.disk'), $this->diskUsage()), @@ -90,16 +88,4 @@ class ServerOverview extends StatsOverviewWidget return $used . ($this->server->disk > 0 ? ' / ' . $total : ' / ∞'); } - - #[On('copyClick')] - public function copyClick(string $value): void - { - $this->js("window.navigator.clipboard.writeText('{$value}');"); - - Notification::make() - ->title(trans('server/dashboard.copied')) - ->body($value) - ->success() - ->send(); - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1217d88f1..be73a3510 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -152,7 +152,7 @@ class AppServiceProvider extends ServiceProvider Field::macro('hintCopy', function () { /** @var Field $this */ - return $this->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null); // @phpstan-ignore varTag.nativeType + return $this->hintAction(CopyAction::make()); // @phpstan-ignore varTag.nativeType }); // Don't run any health checks during tests diff --git a/lang/en/server/dashboard.php b/lang/en/server/dashboard.php index 4023b36ed..e645242e3 100644 --- a/lang/en/server/dashboard.php +++ b/lang/en/server/dashboard.php @@ -25,6 +25,4 @@ return [ 'power_actions' => 'Power Actions', 'power_action_sent' => ':action sent to :name', - - 'copied' => 'Copied to clipboard', ]; diff --git a/resources/js/app.js b/resources/js/app.js index e69de29bb..e0dc6dc92 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -0,0 +1,17 @@ +(() => { + if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') return; + navigator.clipboard = navigator.clipboard || {}; + navigator.clipboard.writeText = async (text) => + new Promise((resolve, reject) => { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.left = '-9999px'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + textarea.select(); + const success = document.execCommand('copy'); + document.body.removeChild(textarea); + success ? resolve() : reject('Fallback copy failed'); + }); +})(); \ No newline at end of file diff --git a/resources/views/filament/components/server-console.blade.php b/resources/views/filament/components/server-console.blade.php index bb1711742..9bebfa357 100644 --- a/resources/views/filament/components/server-console.blade.php +++ b/resources/views/filament/components/server-console.blade.php @@ -102,7 +102,7 @@ terminal.attachCustomKeyEventHandler((event) => { if ((event.ctrlKey || event.metaKey) && event.key === 'c') { - document.execCommand('copy'); // navigator.clipboard.writeText() only works on ssl.. + navigator.clipboard.writeText(terminal.getSelection()) return false; } else if ((event.ctrlKey || event.metaKey) && event.key === 'f') { event.preventDefault(); diff --git a/resources/views/filament/components/server-small-data-block.blade.php b/resources/views/filament/components/server-small-data-block.blade.php index 3be63ee71..e65bc5344 100644 --- a/resources/views/filament/components/server-small-data-block.blade.php +++ b/resources/views/filament/components/server-small-data-block.blade.php @@ -1,6 +1,11 @@
-@if ($shouldCopyOnClick()) - +@if ($isCopyable($value = $getValue())) + @else @endif @@ -8,7 +13,7 @@ {{ $getLabel() }} - {{ $getValue() }} + {{ $value }}