diff --git a/resources/css/app.css b/resources/css/app.css index 33cb50af21..1a8d0f0cba 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -2806,6 +2806,12 @@ html[data-theme="custom"]:not(.dark) :is(.application-settings-section, .applica background: transparent; } +/* Destructive items of a table row action menu (x-table.row-actions). */ +.row-actions-danger .listbox-option, +.dark .row-actions-danger .listbox-option { + color: var(--color-error); +} + /* Searchable listbox: sticky search field + scrollable options */ .searchable-listbox-panel { max-height: 18rem; @@ -3150,7 +3156,7 @@ input[type="search"]::-webkit-search-results-decoration { } .env-table-grid { - grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 6rem 4rem 4.5rem 4.8rem 4.2rem 3rem; + grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 6rem 4rem 4.5rem 4.8rem 4.2rem 4.5rem; } /* File browser: icon, name, perms, size, modified, actions. @@ -3198,11 +3204,11 @@ input[type="search"]::-webkit-search-results-decoration { } .environment-table-scroll .env-table-grid { - min-width: 53rem; + min-width: 54.5rem; } .env-table-grid.env-table-grid-no-type { - grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 4rem 4.5rem 4.8rem 4.2rem 3rem; + grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 4rem 4.5rem 4.8rem 4.2rem 4.5rem; } @media (max-width: 768px) { @@ -3225,20 +3231,20 @@ input[type="search"]::-webkit-search-results-decoration { } .data-table-row.env-table-grid > :last-child { - display: block; + display: flex; justify-self: end; } } /* Shared variables only store value shape (multiline), not per-resource flags. */ .env-table-grid-shared { - grid-template-columns: minmax(0, 1.6fr) 6rem minmax(0, 1fr) 4.5rem 3rem; + grid-template-columns: minmax(0, 1.6fr) 6rem minmax(0, 1fr) 4.5rem 4.5rem; } /* Shared env vars collapse secondary columns on tablet and use cards on phone. */ @media (max-width: 1100px) { .env-table-grid-shared { - grid-template-columns: minmax(0, 1.4fr) 6rem minmax(0, 1fr) 3rem; + grid-template-columns: minmax(0, 1.4fr) 6rem minmax(0, 1fr) 4.5rem; gap: 0.75rem; } @@ -3250,7 +3256,7 @@ input[type="search"]::-webkit-search-results-decoration { @media (max-width: 900px) { .env-table-grid-shared { - grid-template-columns: minmax(0, 1fr) 6rem 3rem; + grid-template-columns: minmax(0, 1fr) 6rem 4.5rem; } .env-table-grid-shared > :nth-child(3) { @@ -3505,7 +3511,7 @@ input[type="search"]::-webkit-search-results-decoration { } .service-backup-table-grid { - grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem 6.5rem minmax(8rem, 1fr) 12.5rem; + grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem 6.5rem minmax(8rem, 1fr) 4.5rem; width: 100%; } @@ -3533,11 +3539,11 @@ input[type="search"]::-webkit-search-results-decoration { } .volumes-table-grid { - grid-template-columns: minmax(9rem, 1.2fr) minmax(6rem, 1fr) 5rem 15rem; + grid-template-columns: minmax(9rem, 1.2fr) minmax(6rem, 1fr) 5rem 4.5rem; } .volumes-table-grid-with-pr { - grid-template-columns: minmax(9rem, 1.1fr) minmax(6rem, 1fr) 8.5rem 5rem 15rem; + grid-template-columns: minmax(9rem, 1.1fr) minmax(6rem, 1fr) 8.5rem 5rem 4.5rem; } .volumes-table-grid-readonly.has-source { @@ -3545,11 +3551,11 @@ input[type="search"]::-webkit-search-results-decoration { } .volumes-table-grid.has-source { - grid-template-columns: minmax(9rem, 1.1fr) minmax(14rem, 1.5fr) minmax(6rem, 1fr) 5rem 15rem; + grid-template-columns: minmax(9rem, 1.1fr) minmax(14rem, 1.5fr) minmax(6rem, 1fr) 5rem 4.5rem; } .volumes-table-grid-with-pr.has-source { - grid-template-columns: minmax(9rem, 1fr) minmax(14rem, 1.4fr) minmax(6rem, 1fr) 8.5rem 5rem 15rem; + grid-template-columns: minmax(9rem, 1fr) minmax(14rem, 1.4fr) minmax(6rem, 1fr) 8.5rem 5rem 4.5rem; } .volumes-mobile-label { @@ -3795,7 +3801,7 @@ input[type="search"]::-webkit-search-results-decoration { } .server-resources-unmanaged-table-grid { - grid-template-columns: minmax(9rem, 1fr) minmax(12rem, 1.5fr) 7rem minmax(9rem, auto); + grid-template-columns: minmax(9rem, 1fr) minmax(12rem, 1.5fr) 7rem 4.5rem; } .server-images-table-grid { @@ -6821,13 +6827,14 @@ html.dark .resource-glyph { color: var(--color-brand-light); } +/* Row and card actions stay neutral: the brand color is for the primary action of an area. */ html.dark .card-icon-button { - color: var(--color-brand-light); + color: var(--color-fg-faint); } html.dark .card-icon-button:hover { - background-color: color-mix(in srgb, var(--color-brand) 16%, transparent); - color: #fff; + background-color: rgb(255 255 255 / 0.06); + color: var(--color-fg); } html.dark .app-main :is(article.group, a.group).rounded-xl { diff --git a/resources/views/components/shared-variables/editor.blade.php b/resources/views/components/shared-variables/editor.blade.php index 18ed216172..2d33ef22cb 100644 --- a/resources/views/components/shared-variables/editor.blade.php +++ b/resources/views/components/shared-variables/editor.blade.php @@ -116,7 +116,7 @@ Scope {{ count($readOnlyKeys) ? 'Value / comment' : 'Comment' }} Multiline - + Actions @foreach ($variables as $env) @if (in_array($env->key, $readOnlyKeys)) diff --git a/resources/views/components/table/row-action.blade.php b/resources/views/components/table/row-action.blade.php new file mode 100644 index 0000000000..3996e27665 --- /dev/null +++ b/resources/views/components/table/row-action.blade.php @@ -0,0 +1,10 @@ +{{-- One item of . Renders a link (with wire:navigate when enabled) when href is set. --}} +@props([ + 'href' => null, +]) + +@if ($href) + class('listbox-option justify-start!') }}>{{ $slot }} +@else + class('listbox-option justify-start!') }}>{{ $slot }} +@endif diff --git a/resources/views/components/table/row-actions.blade.php b/resources/views/components/table/row-actions.blade.php new file mode 100644 index 0000000000..9b2599e9a3 --- /dev/null +++ b/resources/views/components/table/row-actions.blade.php @@ -0,0 +1,27 @@ +{{-- Row action menu for tables: a "..." icon button at the far right of the row. + Put items in the default slot. Put destructive items in + the danger slot: they show in red, below a divider. A click on any item + closes the menu, also when the item opens a modal. --}} +@props([ + 'label' => 'Actions', +]) + + + + + + + + + {{ $slot }} + @if (isset($danger) && $danger->isNotEmpty()) + @if ($slot->isNotEmpty()) + + @endif + + {{ $danger }} + + @endif + + diff --git a/resources/views/livewire/admin/index.blade.php b/resources/views/livewire/admin/index.blade.php index c2fb4b271e..5320f2de50 100644 --- a/resources/views/livewire/admin/index.blade.php +++ b/resources/views/livewire/admin/index.blade.php @@ -64,7 +64,7 @@ Name Email Subscription - Action + Actions @foreach ($foundUsers as $user) @php diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index d551fd7d4b..dbcebc8bab 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -162,7 +162,7 @@ - + - + Actions @foreach ($dashboardProjects as $project) @php $firstEnvironment = $project->environments->first(); @endphp + class="relative grid grid-cols-[minmax(0,1fr)_7rem_4.5rem] gap-4 @2xl:min-w-[560px] @2xl:grid-cols-[minmax(0,1.2fr)_7rem_minmax(0,1.5fr)_4.5rem] {{ $tableRow }}"> diff --git a/resources/views/livewire/project/application/partials/domain-row.blade.php b/resources/views/livewire/project/application/partials/domain-row.blade.php index 23d2bf1f0a..874914428d 100644 --- a/resources/views/livewire/project/application/partials/domain-row.blade.php +++ b/resources/views/livewire/project/application/partials/domain-row.blade.php @@ -131,14 +131,14 @@ @can('update', $application) - - - - @unless ($labelsAreWritable) - @if ($isSuggested) + @if ($labelsAreWritable || $isSuggested) + + + + @if ($isSuggested && ! $labelsAreWritable) @if ($row['needs_force_add'] ?? false) Continue @@ -148,29 +148,34 @@ Add domain @endif - @else - - - - - - - - - - @endif - @endunless + @else + + + Check DNS + + + Edit + + + + + Delete + + + + + @endif @endcan diff --git a/resources/views/livewire/project/application/preview-domains.blade.php b/resources/views/livewire/project/application/preview-domains.blade.php index c84480eb9a..f21096b91d 100644 --- a/resources/views/livewire/project/application/preview-domains.blade.php +++ b/resources/views/livewire/project/application/preview-domains.blade.php @@ -177,34 +177,33 @@ @can('update', $preview->application) - - - - - - - - - - - - - + + + Check DNS + + + Edit + + + + + Delete + + + + @endcan diff --git a/resources/views/livewire/project/database/scheduled-backups.blade.php b/resources/views/livewire/project/database/scheduled-backups.blade.php index aba61ec48c..67c8f441df 100644 --- a/resources/views/livewire/project/database/scheduled-backups.blade.php +++ b/resources/views/livewire/project/database/scheduled-backups.blade.php @@ -62,7 +62,7 @@ Latest run S3 storage Executions - Action + Actions @foreach ($database->scheduledBackups as $backup) @php @@ -87,9 +87,10 @@ || @js(strtolower($s3StorageNames)).includes(search.toLowerCase())" class="data-table-row scheduled-backups-table-grid border-b border-neutral-200 last:border-b-0 dark:border-white/[0.06]"> - + {{ $backup->frequency }} - + @@ -107,7 +108,10 @@ - Manage + + + @endforeach diff --git a/resources/views/livewire/project/index.blade.php b/resources/views/livewire/project/index.blade.php index de563683a8..75e060fd9f 100644 --- a/resources/views/livewire/project/index.blade.php +++ b/resources/views/livewire/project/index.blade.php @@ -149,7 +149,7 @@ - + Actions diff --git a/resources/views/livewire/project/service/backup-executions.blade.php b/resources/views/livewire/project/service/backup-executions.blade.php index 761b2e2dce..ff12f2f6e3 100644 --- a/resources/views/livewire/project/service/backup-executions.blade.php +++ b/resources/views/livewire/project/service/backup-executions.blade.php @@ -41,7 +41,7 @@ @else - + TargetTypeScheduleStatusStartedSizeActions @foreach ($executions as $execution) @@ -56,7 +56,7 @@ + class="data-table-row grid min-w-[900px] cursor-pointer grid-cols-[minmax(150px,1.4fr)_100px_100px_110px_190px_90px_4.5rem] text-left text-[13px] text-neutral-700 dark:text-fg-dim"> {{ $execution['target'] }} Resource Image Status - + Actions @endif diff --git a/resources/views/livewire/project/service/partials/domain-table.blade.php b/resources/views/livewire/project/service/partials/domain-table.blade.php index be5b6e70f4..1e8496e985 100644 --- a/resources/views/livewire/project/service/partials/domain-table.blade.php +++ b/resources/views/livewire/project/service/partials/domain-table.blade.php @@ -165,13 +165,13 @@ @can('update', $service) - - - @if ($isSuggested) + + + @if ($row['needs_force_add'] ?? false) @endif @else - - - - - - - - - - + + + Check DNS + + + Edit + + + + + Delete + + + + @endif @endcan diff --git a/resources/views/livewire/project/service/resource-card.blade.php b/resources/views/livewire/project/service/resource-card.blade.php index b0ca8c56fa..549b0c54c5 100644 --- a/resources/views/livewire/project/service/resource-card.blade.php +++ b/resources/views/livewire/project/service/resource-card.blade.php @@ -15,7 +15,7 @@ - - @if ($isDatabase && ($resource->isBackupSolutionAvailable() || $resource->is_migrated)) - - - - @endif - @if ($isApplication && $resource->fqdn) - @can('update', $service) - - - - @endcan - @endif + @php + $hasBackupsAction = $isDatabase && ($resource->isBackupSolutionAvailable() || $resource->is_migrated); + $hasDomainsAction = $isApplication && $resource->fqdn && auth()->user()?->can('update', $service); + @endphp + + @if ($hasBackupsAction || $hasDomainsAction) + + @if ($hasBackupsAction) + Backups + @endif + @if ($hasDomainsAction) + Domains + @endif + Settings + + @else + @endif diff --git a/resources/views/livewire/project/service/volume-backup/index.blade.php b/resources/views/livewire/project/service/volume-backup/index.blade.php index 041054e5bc..15a349c2fc 100644 --- a/resources/views/livewire/project/service/volume-backup/index.blade.php +++ b/resources/views/livewire/project/service/volume-backup/index.blade.php @@ -290,18 +290,17 @@ :data-tooltip="$databaseS3Tooltip" :aria-label="$databaseS3Tooltip" tabindex="0" /> {{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }} - - Back up now - - - + + @can('update', $service) + + Back up now + Edit + + @endcan @endforeach @@ -351,16 +350,15 @@ {{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }} - - Back up now - - - + + @can('update', $service) + + Back up now + Edit + + @endcan @endforeach diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index 55b614a968..1a7eee71ec 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -235,7 +235,7 @@ Multiline Buildtime Runtime - + Actions @php $renderedScope = null; @endphp @foreach ($this->environmentVariablePageRows as $row) diff --git a/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php b/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php index 86dd4b8df6..0631d56e57 100644 --- a/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php @@ -29,7 +29,7 @@ - - - - + @unless ($isValueHidden) @endunless diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 5b77e024cb..44f7c664fc 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -87,7 +87,7 @@ @endif @endforeach @endif - + @if ($this->connectLink) diff --git a/resources/views/livewire/project/shared/file-browser.blade.php b/resources/views/livewire/project/shared/file-browser.blade.php index dd916a77d0..c389d4e996 100644 --- a/resources/views/livewire/project/shared/file-browser.blade.php +++ b/resources/views/livewire/project/shared/file-browser.blade.php @@ -269,81 +269,56 @@ {{ $entry['mtime'] ? \Illuminate\Support\Carbon::createFromTimestamp($entry['mtime'])->toDateTimeString() : '-' }} - {{-- Actions kebab. The entry name lives in this - row's x-data as a JS string so the menu can - call $wire.* with it directly - passing @js() - through a component attribute double-encodes - the quotes and breaks the handler. --}} + {{-- The entry name lives in this row's x-data as a JS + string so the menu can call $wire.* with it directly - + passing @js() through a component attribute + double-encodes the quotes and breaks the handler. --}} - - - - - - - @if ($entry['type'] !== 'dir') - - - Edit - - @endif - - - Download - - - {{-- Rename --}} - - - - - Rename - - - $wire.renameEntry(name, newName)); $dispatch('close-modal')" - class="flex flex-col gap-4"> - - - Rename - - - - - {{-- Delete --}} - - - - - Delete - - - - - This permanently deletes - {{ $entry['name'] }} - inside the container. This cannot be undone. - - - Delete - + x-data="{ name: @js($entry['name']), isDir: @js($entry['type'] === 'dir') }"> + + @if ($entry['type'] !== 'dir') + + Edit + + @endif + + Download + + + + Rename + + $wire.renameEntry(name, newName)); $dispatch('close-modal')" + class="flex flex-col gap-4"> + + + Rename + + + + + + + Delete + + + + This permanently deletes + {{ $entry['name'] }} + inside the container. This cannot be undone. + + + Delete - - - - + + + + @endforeach diff --git a/resources/views/livewire/project/shared/storages/all.blade.php b/resources/views/livewire/project/shared/storages/all.blade.php index f13568bb07..88a9deab0d 100644 --- a/resources/views/livewire/project/shared/storages/all.blade.php +++ b/resources/views/livewire/project/shared/storages/all.blade.php @@ -126,13 +126,13 @@ @if ($showBackupAction) - @if ($canUpdate) + @if ($canUpdate && ! $form['canDeleteStale'] && ! $form['canDeleteToApplyDriverOptions']) - - Backup - + + + @if ($resource instanceof \App\Models\Application) @endif + @elseif ($canUpdate || $form['canDeleteStale'] || $form['canDeleteToApplyDriverOptions']) + + @if ($canUpdate) + + + Backup + + @if ($resource instanceof \App\Models\Application) + + @else + + @endif + + @endif + @if ($form['canDeleteStale'] || $form['canDeleteToApplyDriverOptions']) + + @if ($form['canDeleteStale']) + + + Delete stale entry + + + @endif + @if ($form['canDeleteToApplyDriverOptions']) + + + Delete + + + @endif + + @endif + @else — @endif - - @if ($form['canDeleteStale']) - - @endif - - @if ($form['canDeleteToApplyDriverOptions']) - - @endif @endif @@ -258,50 +287,63 @@ - - Update - - - @if ($showBackupAction && ! $form['isShared']) - - - - Backup - - - @if ($resource instanceof \App\Models\Application) - - @else - - @endif - - @elseif (method_exists($resource, 'isBackupSolutionAvailable') && $resource->isBackupSolutionAvailable()) - - - - Backup - - - - + Update + @php + $hasBackupAction = auth()->user()?->can('update', $resource) + && (($showBackupAction && ! $form['isShared']) + || (method_exists($resource, 'isBackupSolutionAvailable') && $resource->isBackupSolutionAvailable())); + @endphp + @if ($form['isShared'] && ! $hasBackupAction) + + + + @else + + Update + @can('update', $resource) + @if ($showBackupAction && ! $form['isShared']) + + + Backup + + @if ($resource instanceof \App\Models\Application) + + @else + + @endif + + @elseif (method_exists($resource, 'isBackupSolutionAvailable') && $resource->isBackupSolutionAvailable()) + + + Backup + + + + @endif + @endcan + @unless ($form['isShared']) + + + + Delete + + + + @endunless + @endif - - @unless ($form['isShared']) - - @endunless diff --git a/resources/views/livewire/project/shared/storages/volume-backups/executions.blade.php b/resources/views/livewire/project/shared/storages/volume-backups/executions.blade.php index 3d29c960e6..21b0a13a2a 100644 --- a/resources/views/livewire/project/shared/storages/volume-backups/executions.blade.php +++ b/resources/views/livewire/project/shared/storages/volume-backups/executions.blade.php @@ -146,15 +146,32 @@ @endif - @if ($recoveryPending) + @if ($recoveryPending && $execution->status !== 'running') + + Retry recovery + + + + Delete + + + + + @elseif ($recoveryPending) - @endif - @if ($execution->status !== 'running') + @elseif ($execution->status !== 'running') - + Actions diff --git a/resources/views/livewire/security/api-tokens.blade.php b/resources/views/livewire/security/api-tokens.blade.php index a2a9de3018..dd634666b4 100644 --- a/resources/views/livewire/security/api-tokens.blade.php +++ b/resources/views/livewire/security/api-tokens.blade.php @@ -187,23 +187,15 @@ @if (auth()->id() === $token->tokenable_id) - - - - - - - + + Edit - - + + {{ $token->isPaused() ? 'Resume' : 'Pause' }} - + - - Roll - + Roll - + - - Revoke - + Revoke - + + @endif diff --git a/resources/views/livewire/security/cloud-init-scripts.blade.php b/resources/views/livewire/security/cloud-init-scripts.blade.php index 0267d4358e..9a79b94fcd 100644 --- a/resources/views/livewire/security/cloud-init-scripts.blade.php +++ b/resources/views/livewire/security/cloud-init-scripts.blade.php @@ -28,10 +28,10 @@ icon-name="file-content" size="sm" /> @else - + Script Last updated - + Actions @foreach ($scripts as $script) @can('view', $script) @@ -40,7 +40,7 @@ class="border-b border-neutral-200 last:border-b-0 dark:border-white/[0.07]"> updated_at->diffForHumans() }} - - - + + + + + @else - + Token Provider Description - + Actions @foreach ($tokens as $savedToken) {{ $savedToken->description ?: '-' }} - - - + + + + + @endif - - - + + + + + + :x-effect="$form === 'editForm' ? 'options[0].label = editingToken?.keepLabel ?? options[0].label' : null" /> diff --git a/resources/views/livewire/security/private-key/index.blade.php b/resources/views/livewire/security/private-key/index.blade.php index 70b87a5845..fa59df1506 100644 --- a/resources/views/livewire/security/private-key/index.blade.php +++ b/resources/views/livewire/security/private-key/index.blade.php @@ -68,18 +68,18 @@ icon-name="keys" /> @else - + Private key Description Status - + Actions @foreach ($privateKeys as $key) @can('view', $key) @endif - name), description: @js($key->description ?? '') })" - wire:click="openEditor('{{ $key->uuid }}')"> - - + + name), description: @js($key->description ?? '') })" + wire:click="openEditor('{{ $key->uuid }}')"> + + + @else - - Actions + Actions @foreach ($visibleImages as $image) @endif - + @if ($image['containers'] === []) @can('update', $server) - + :confirmWithPassword="false"> + + + + + + @endcan @endif diff --git a/resources/views/livewire/server/docker-registries/server-registries.blade.php b/resources/views/livewire/server/docker-registries/server-registries.blade.php index 5d7304a785..48117063f2 100644 --- a/resources/views/livewire/server/docker-registries/server-registries.blade.php +++ b/resources/views/livewire/server/docker-registries/server-registries.blade.php @@ -38,11 +38,11 @@ @else + class="grid min-w-[760px] grid-cols-[minmax(0,1fr)_9rem_minmax(0,1fr)_4.5rem] border-b border-neutral-200 px-4 py-2 text-[11px] font-medium text-neutral-500 dark:border-white/[0.08] dark:text-fg-faint"> Registry Status Used by - + Actions @foreach ($visibleRows as $row) @php @@ -53,7 +53,7 @@ @endphp - + {{ $row['registry'] }} @if ($row['logged_in'] && ($loginChecks[$row['registry']]['ok'] ?? true) === false) @@ -83,8 +83,34 @@ @endif - - @if ($row['logged_in']) + + @if ($row['logged_in'] && $row['source'] === 'auths') + + Test + + + Edit + + + + + + + Delete + + + + + @elseif ($row['logged_in']) Test @@ -95,20 +121,6 @@ :key="'registry-login-'.$server->uuid.'-'.$row['registry']" /> @endif - @if ($row['logged_in'] && $row['source'] === 'auths') - - - - - @endif @if ($usedByCount > 1) diff --git a/resources/views/livewire/server/proxy/certificates.blade.php b/resources/views/livewire/server/proxy/certificates.blade.php index 43a0bfd091..076650ba83 100644 --- a/resources/views/livewire/server/proxy/certificates.blade.php +++ b/resources/views/livewire/server/proxy/certificates.blade.php @@ -37,7 +37,7 @@ Resolver Alternative names Expires - Actions + Actions @@ -71,22 +71,31 @@ {{ $certificate['expires_at'] ?? 'Unknown' }} - + @can('update', $server) - + + + + + + + + + @endcan @@ -115,7 +124,7 @@ Backup Created Size - Actions + Actions @@ -127,28 +136,40 @@ {{ $backup['created_at'] }} {{ formatBytes($backup['size']) }} - - - + + + + + Restore + + + + + + Delete + + + + diff --git a/resources/views/livewire/server/resources.blade.php b/resources/views/livewire/server/resources.blade.php index 1026ac5619..cdcc66b822 100644 --- a/resources/views/livewire/server/resources.blade.php +++ b/resources/views/livewire/server/resources.blade.php @@ -112,7 +112,7 @@ - Actions + Actions @foreach ($resources as $resource) @php($containerState = (string) data_get($resource, 'State', 'unknown')) @@ -130,30 +130,39 @@ ? 'success' : ($containerState === 'exited' ? 'error' : 'warning')" /> - - @if ($containerState === 'running') - - Restart - - - Stop - - @elseif ($containerState === 'exited') - - Start - - @elseif ($containerState === 'restarting') - - Stop - + + @if (in_array($containerState, ['running', 'exited', 'restarting'], true)) + @can('update', $server) + @if ($containerState === 'running') + + + Restart + + + Stop + + + @elseif ($containerState === 'exited') + + + + @else + + + + @endif + @endcan @endif diff --git a/resources/views/livewire/server/security/patches.blade.php b/resources/views/livewire/server/security/patches.blade.php index 7a137bf7fa..059c88147b 100644 --- a/resources/views/livewire/server/security/patches.blade.php +++ b/resources/views/livewire/server/security/patches.blade.php @@ -223,7 +223,7 @@ Package New version - Action + Actions @foreach ($updates as $update) + class="grid min-w-[780px] grid-cols-[minmax(12rem,1fr)_9rem_7rem_minmax(12rem,1.2fr)_4.5rem] border-b border-neutral-200 bg-neutral-50 px-4 py-2.5 text-[11px] font-medium text-neutral-500 dark:border-white/[0.08] dark:bg-white/[0.05] dark:text-fg-faint"> Backup target Frequency Status Storage + Actions @foreach ($groupedBackups as $backups) @php @@ -77,7 +78,7 @@ ])->values()->all(); @endphp @if ($resourceLink) @@ -104,16 +105,20 @@ portal /> Move - - - - Disable - - - + + + + + + + + + @endforeach @@ -131,7 +136,7 @@ ])->values()->all(); @endphp {{ $targetName }} @@ -149,16 +154,20 @@ portal /> Move - - - - Disable - - - + + + + + + + + + @endforeach diff --git a/resources/views/livewire/team/admin-view.blade.php b/resources/views/livewire/team/admin-view.blade.php index c207c12028..3f98a3e827 100644 --- a/resources/views/livewire/team/admin-view.blade.php +++ b/resources/views/livewire/team/admin-view.blade.php @@ -74,8 +74,9 @@ shortConfirmationLabel="User name"> - Delete + class="icon-button shrink-0 text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300" + title="Delete user" aria-label="Delete {{ $user->name }}"> + diff --git a/resources/views/livewire/team/invitations.blade.php b/resources/views/livewire/team/invitations.blade.php index 857c3b9a68..f8f885b03b 100644 --- a/resources/views/livewire/team/invitations.blade.php +++ b/resources/views/livewire/team/invitations.blade.php @@ -7,7 +7,7 @@ + class="data-table-header grid-cols-[minmax(13rem,1.2fr)_7rem_7rem_minmax(15rem,1.5fr)_4.5rem]"> @foreach ($sortedInvitations as $invite) + class="data-table-row grid-cols-[minmax(13rem,1.2fr)_7rem_7rem_minmax(15rem,1.5fr)_4.5rem] border-b border-neutral-200 last:border-b-0 dark:border-white/[0.07]"> {{ $invite->email }} @@ -35,12 +35,20 @@ title="{{ $invite->link }}">{{ $invite->link }} - - - Revoke - + + + + + + + + @endforeach diff --git a/resources/views/livewire/team/member.blade.php b/resources/views/livewire/team/member.blade.php index 4cff9664bd..758c3d297e 100644 --- a/resources/views/livewire/team/member.blade.php +++ b/resources/views/livewire/team/member.blade.php @@ -52,61 +52,56 @@ @can('manageMembers', currentTeam()) @if ($member->id !== Auth::id()) - - - Manage - - - - @if (Auth::user()->isOwner()) - @if (data_get($member, 'pivot.role') !== 'owner') - - Make owner - - @endif - @if (data_get($member, 'pivot.role') !== 'admin') - - Make admin - - @endif - @if (data_get($member, 'pivot.role') !== 'member') - - Make member - - @endif - @elseif (Auth::user()->isAdmin()) - @if (data_get($member, 'pivot.role') === 'admin') - - Make member - - @elseif (data_get($member, 'pivot.role') === 'member') - - Make admin - - @endif - @endif - @if (data_get($member, 'pivot.role') === 'member') - - Edit access - - @endif - - - Remove member + @if (data_get($member, 'pivot.role') === 'owner' && ! Auth::user()->isOwner()) + + + + - - - + + + @else + + @if (Auth::user()->isOwner()) + @if (data_get($member, 'pivot.role') !== 'owner') + Make owner + @endif + @if (data_get($member, 'pivot.role') !== 'admin') + Make admin + @endif + @if (data_get($member, 'pivot.role') !== 'member') + Make member + @endif + @elseif (Auth::user()->isAdmin()) + @if (data_get($member, 'pivot.role') === 'admin') + Make member + @elseif (data_get($member, 'pivot.role') === 'member') + Make admin + @endif + @endif + @if (data_get($member, 'pivot.role') === 'member') + + Edit access + + @endif + + + + Remove member + + + + + @endif @endif @endcan diff --git a/tests/Feature/BackupNowAvailabilityTest.php b/tests/Feature/BackupNowAvailabilityTest.php index c1a35d979b..52c5ccd5ed 100644 --- a/tests/Feature/BackupNowAvailabilityTest.php +++ b/tests/Feature/BackupNowAvailabilityTest.php @@ -121,7 +121,7 @@ it('refreshes service backup buttons when the service status changes', function ? ['service' => $this->service] : ['backup' => $this->backups['service']->fresh(), 'availableS3Storages' => collect()]; $component = Livewire::test($componentClass, $parameters); - $disabledButton = '/]*wire:click(\.stop)?="backupNow/s'; + $disabledButton = '/]*\sdisabled\b)(?=[^>]*wire:click(\.stop)?="backupNow)[^>]*>/s'; $database->update(['status' => 'running:healthy']); $component->dispatch('echo-private:team.'.currentTeam()->id.','.$event); diff --git a/tests/Feature/MutableLivewireComponentsAuthorizationTest.php b/tests/Feature/MutableLivewireComponentsAuthorizationTest.php index 47b215d5f0..92a0f745bb 100644 --- a/tests/Feature/MutableLivewireComponentsAuthorizationTest.php +++ b/tests/Feature/MutableLivewireComponentsAuthorizationTest.php @@ -105,8 +105,8 @@ it('declares update authorization on service backup mutation controls', function expect($volumeBackupView) ->toMatch('/]*:title="\'Edit backup schedule\'")(?=[^>]*canGate="update")(?=[^>]*:canResource="\$service")[^>]*>/') - ->toMatch('/toMatch('/]*wire:click\.stop="backupNow\(\'storage\',[^"]+")(?=[^>]*canGate="update")(?=[^>]*:canResource="\$service")[^>]*>/'); + ->toMatch('/@can\(\'update\', \$service\)\s*toMatch('/@can\(\'update\', \$service\)\s*toContain('canGate="update"') ->toContain(':canResource="$resource"'); - preg_match_all('/]*>\s*Backup\s*<\/x-forms\.button>/s', $view, $backupButtons); + $backupAction = '/Backup<\/x-table\.row-action>/'; + preg_match('/@if \(\$canUpdate\)\s*/s', $view, $canUpdateBlock); + preg_match('/@can\(\'update\', \$resource\)(.*?)@endcan/s', $view, $canBlock); - expect($backupButtons[0])->toHaveCount(3); - - foreach ($backupButtons[0] as $backupButton) { - expect($backupButton) - ->toContain('canGate="update"') - ->toContain(':canResource="$resource"'); - } + expect(preg_match_all($backupAction, $view))->toBe(3) + ->and(preg_match_all($backupAction, $canUpdateBlock[0] ?? ''))->toBe(1) + ->and(preg_match_all($backupAction, $canBlock[0] ?? ''))->toBe(2); }); it('creates named volumes without a host path in swarm mode', function () { @@ -329,7 +327,8 @@ YAML, ]); Livewire::test(All::class, ['resource' => $application]) - ->assertSee('Delete stale volume entry') + ->assertSee('Remove stale volume entry?') + ->assertSee('Delete stale entry') ->call('delete', $volume->id, 'password'); expect($volume->fresh())->toBeNull(); diff --git a/tests/Feature/ServiceResourceRoutingTest.php b/tests/Feature/ServiceResourceRoutingTest.php index 9d910aebf0..32d929e9b2 100644 --- a/tests/Feature/ServiceResourceRoutingTest.php +++ b/tests/Feature/ServiceResourceRoutingTest.php @@ -463,11 +463,11 @@ test('service storage backups page includes schedules from all compose databases 'service_uuid' => $this->ownService->uuid, ])) ->assertOk() - ->assertSee('>Database', false) + ->assertSee('>Database', false) ->assertSee('own-db') ->assertSee('analytics-db') ->assertSee("wire:click=\"openSchedule('{$backups->first()->uuid}')\"", false) - ->assertSee("wire:click.stop=\"backupNow('database', '{$backups->first()->uuid}')\"", false); + ->assertSee("wire:click=\"backupNow('database', '{$backups->first()->uuid}')\"", false); }); test('service backup settings open automatically from the creation redirect', function () { @@ -521,18 +521,18 @@ test('service backups have explicit settings actions for database and storage sc $dom = new DOMDocument; @$dom->loadHTML($html); $xpath = new DOMXPath($dom); - $buttons = $xpath->query('//button[@aria-label="Edit backup schedule"]'); + $buttons = $xpath->query('//button[@role="menuitem"][normalize-space()="Edit"]'); $actions = []; foreach ($buttons as $button) { - $actions[] = $button->getAttribute('wire:click.stop'); + $actions[] = $button->getAttribute('wire:click'); } expect($actions)->toContain("openSchedule('{$databaseBackup->uuid}')", "openSchedule('{$volumeBackup->uuid}')"); foreach (['database' => $databaseBackup, 'storage' => $volumeBackup] as $type => $backup) { - $backupAction = "wire:click.stop=\"backupNow('{$type}', '{$backup->uuid}')\""; - $settingsAction = "wire:click.stop=\"openSchedule('{$backup->uuid}')\""; - expect(strpos($html, $backupAction))->toBeLessThan(strpos($html, $settingsAction)); + $backupAction = "wire:click=\"backupNow('{$type}', '{$backup->uuid}')\""; + $settingsAction = "wire:click=\"openSchedule('{$backup->uuid}')\""; + expect(strpos($html, $backupAction))->toBeLessThan(strrpos($html, $settingsAction)); } });
- This permanently deletes - {{ $entry['name'] }} - inside the container. This cannot be undone. -
+ This permanently deletes + {{ $entry['name'] }} + inside the container. This cannot be undone. +
{{ $savedToken->description ?: '-' }}