From 8a2373e49f9017d507165e382d65a02099f5505b Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:37:16 +0200 Subject: [PATCH] fix(ui): standardize permission denial callouts --- .../livewire/destination/new/docker.blade.php | 2 +- .../shared/resource-operations.blade.php | 15 +--- .../livewire/project/shared/tags.blade.php | 4 +- .../server/cloudflare-tunnel.blade.php | 13 ++- .../views/livewire/server/proxy.blade.php | 2 +- .../livewire/source/github/create.blade.php | 2 +- .../views/livewire/storage/create.blade.php | 2 +- .../livewire/subscription/index.blade.php | 2 +- .../Feature/ResourcePermissionCalloutTest.php | 82 +++++++++++++++++++ 9 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 tests/Feature/ResourcePermissionCalloutTest.php diff --git a/resources/views/livewire/destination/new/docker.blade.php b/resources/views/livewire/destination/new/docker.blade.php index 953c4d42e..2be64d289 100644 --- a/resources/views/livewire/destination/new/docker.blade.php +++ b/resources/views/livewire/destination/new/docker.blade.php @@ -18,7 +18,7 @@ @else - + You don't have permission to create new destinations. Please contact your team administrator for access. @endcan diff --git a/resources/views/livewire/project/shared/resource-operations.blade.php b/resources/views/livewire/project/shared/resource-operations.blade.php index 658a7cbd3..0c3c8885c 100644 --- a/resources/views/livewire/project/shared/resource-operations.blade.php +++ b/resources/views/livewire/project/shared/resource-operations.blade.php @@ -101,16 +101,10 @@ - @else - - You don't have permission to clone resources. Contact your team administrator to request access. - - @endcan -

Move Resource

-
Transfer this resource between projects and environments.
+

Move Resource

+
Transfer this resource between projects and environments.
- @can('update', $resource) @if ($projects->count() > 0)
@@ -160,9 +154,8 @@
@endif @else - - You don't have permission to move resources between projects or environments. Contact your team - administrator to request access. + + You don't have permission to modify this resource. Contact your team administrator for access. @endcan
diff --git a/resources/views/livewire/project/shared/tags.blade.php b/resources/views/livewire/project/shared/tags.blade.php index 85208b75b..72b8214a9 100644 --- a/resources/views/livewire/project/shared/tags.blade.php +++ b/resources/views/livewire/project/shared/tags.blade.php @@ -10,8 +10,8 @@ Add @else - - You don't have permission to manage tags. Contact your team administrator to request access. + + You don't have permission to manage this resource. Contact your team administrator for access. @endcan @if (data_get($this->resource, 'tags') && count(data_get($this->resource, 'tags')) > 0) diff --git a/resources/views/livewire/server/cloudflare-tunnel.blade.php b/resources/views/livewire/server/cloudflare-tunnel.blade.php index 2ebac9d41..668fae8f1 100644 --- a/resources/views/livewire/server/cloudflare-tunnel.blade.php +++ b/resources/views/livewire/server/cloudflare-tunnel.blade.php @@ -71,6 +71,11 @@ class="underline">documentation. @endif @if (!$isCloudflareTunnelsEnabled && $server->isFunctional()) + @cannot('update', $server) + + You don't have permission to configure Cloudflare Tunnel for this server. + + @endcannot

Automated

helper="The SSH domain you configured in Cloudflare. Make sure there is no protocol like http(s):// so you provide a FQDN not a URL. Documentation" /> Continue - @else - - You don't have permission to configure Cloudflare Tunnel for this server. - @endcan
@script @@ -121,10 +122,6 @@ class="flex flex-col gap-2 w-full"> ]" confirmationText="I manually configured Cloudflare Tunnel" confirmationLabel="Please type the confirmation text to confirm that you manually configured Cloudflare Tunnel." shortConfirmationLabel="Confirmation text" /> - @else - - You don't have permission to configure Cloudflare Tunnel for this server. - @endcan @endif diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 127f583d6..143149abb 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -174,7 +174,7 @@ class="underline text-white">Traefik changelog to understand breaking change --}} @else - + You don't have permission to configure proxy settings for this server. @endcan diff --git a/resources/views/livewire/source/github/create.blade.php b/resources/views/livewire/source/github/create.blade.php index 9d5189b43..eedb8809c 100644 --- a/resources/views/livewire/source/github/create.blade.php +++ b/resources/views/livewire/source/github/create.blade.php @@ -63,7 +63,7 @@ class="flex items-center justify-between w-full px-1 py-2 text-left select-none @else - + You don't have permission to create new GitHub Apps. Please contact your team administrator for access. @endcan \ No newline at end of file diff --git a/resources/views/livewire/storage/create.blade.php b/resources/views/livewire/storage/create.blade.php index 78ac717a1..32aeda095 100644 --- a/resources/views/livewire/storage/create.blade.php +++ b/resources/views/livewire/storage/create.blade.php @@ -24,7 +24,7 @@ @else - + You don't have permission to create new S3 storage configurations. Please contact your team administrator for access. diff --git a/resources/views/livewire/subscription/index.blade.php b/resources/views/livewire/subscription/index.blade.php index c78af77f9..dc18f54d4 100644 --- a/resources/views/livewire/subscription/index.blade.php +++ b/resources/views/livewire/subscription/index.blade.php @@ -54,7 +54,7 @@

Subscription

- + You are not an admin so you cannot manage your Team's subscription. If this does not make sense, please contact us. diff --git a/tests/Feature/ResourcePermissionCalloutTest.php b/tests/Feature/ResourcePermissionCalloutTest.php new file mode 100644 index 000000000..36e8100c7 --- /dev/null +++ b/tests/Feature/ResourcePermissionCalloutTest.php @@ -0,0 +1,82 @@ +create(); + $team = Team::factory()->create(); + $user->teams()->attach($team, ['role' => $role]); + + $server = Server::factory()->create(['team_id' => $team->id]); + $destination = $server->standaloneDockers()->firstOrFail(); + $project = Project::factory()->create(['team_id' => $team->id]); + $environment = Environment::factory()->create(['project_id' => $project->id]); + + test()->actingAs($user); + session(['currentTeam' => $team]); + + return Application::factory()->create([ + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} + +it('shows one red insufficient permissions callout for resource operations when update is denied', function () { + $application = resourcePermissionCalloutApplicationFor('member'); + + $component = Livewire::test(ResourceOperations::class, ['resource' => $application]) + ->assertSee('Insufficient Permissions') + ->assertSee('permission to modify this resource') + ->assertSee('team administrator for access') + ->assertDontSee('Access Restricted') + ->assertDontSee("You don't have permission to clone resources") + ->assertDontSee("You don't have permission to move resources"); + + expect(substr_count($component->html(), 'Insufficient Permissions'))->toBe(1) + ->and($component->html())->toContain('bg-red-50') + ->and($component->html())->not->toContain('bg-warning-50'); +}); + +it('shows the red insufficient permissions callout for tags when update is denied', function () { + $application = resourcePermissionCalloutApplicationFor('member'); + + $component = Livewire::test(Tags::class, ['resource' => $application]) + ->assertSee('Insufficient Permissions') + ->assertSee('permission to manage this resource') + ->assertSee('team administrator for access') + ->assertDontSee('Access Restricted') + ->assertDontSee("You don't have permission to manage tags"); + + expect(substr_count($component->html(), 'Insufficient Permissions'))->toBe(1) + ->and($component->html())->toContain('bg-red-50') + ->and($component->html())->not->toContain('bg-warning-50'); +}); + +it('does not use yellow permission callouts in blade views', function () { + $offendingFiles = collect(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(resource_path('views')))) + ->filter(fn (SplFileInfo $file) => $file->isFile() && $file->getExtension() === 'php') + ->filter(function (SplFileInfo $file) { + $contents = file_get_contents($file->getPathname()); + + return str_contains($contents, 'type="warning" title="Permission Required"') + || str_contains($contents, 'title="Access Restricted"'); + }) + ->map(fn (SplFileInfo $file) => str_replace(base_path().'/', '', $file->getPathname())) + ->values() + ->all(); + + expect($offendingFiles)->toBeEmpty(); +});