mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(ui): unify dropdown menu styles and drop sort buttons that duplicate table headers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
34e872482d
commit
c1fc9466b2
@@ -18,7 +18,6 @@
|
||||
's3' => $backup->save_s3 ? ($backup->s3Storages->isNotEmpty() ? $backup->s3Storages : collect([$backup->s3])->filter())->count() : -1,
|
||||
'lastRunAt' => $backup->latestExecution?->finished_at?->timestamp ?? ($backup->latestExecution?->status === 'running' ? now()->timestamp : 0),
|
||||
'executions' => $backup->executions_count,
|
||||
'createdAt' => $backup->created_at?->timestamp ?? 0,
|
||||
])->values()),
|
||||
filterOptions: @js(collect([['value' => 'all', 'label' => 'All targets']])->merge(
|
||||
$backups->map(fn ($backup) => [
|
||||
@@ -26,12 +25,6 @@
|
||||
'label' => $backup->targetType(),
|
||||
])->unique('value')->values()
|
||||
)->values()),
|
||||
sortOptions: [
|
||||
{ value: 'target_asc', label: 'Target A–Z' },
|
||||
{ value: 'target_desc', label: 'Target Z–A' },
|
||||
{ value: 'newest', label: 'Newest first' },
|
||||
{ value: 'oldest', label: 'Oldest first' },
|
||||
],
|
||||
get filteredBackups() {
|
||||
const query = this.search.toLowerCase();
|
||||
const filtered = this.backups.filter((backup) => {
|
||||
@@ -52,8 +45,6 @@
|
||||
const sortValue = sortValues[column] || sortValues.target;
|
||||
const order = direction === 'desc' ? -1 : 1;
|
||||
return filtered.sort((left, right) => {
|
||||
if (this.sortBy === 'newest') return right.createdAt - left.createdAt;
|
||||
if (this.sortBy === 'oldest') return left.createdAt - right.createdAt;
|
||||
const leftValue = sortValue(left);
|
||||
const rightValue = sortValue(right);
|
||||
return order * (typeof leftValue === 'number' ? leftValue - rightValue : leftValue.localeCompare(rightValue)) ||
|
||||
@@ -144,7 +135,7 @@
|
||||
</button></x-slot:trigger>
|
||||
<template x-for="option in filterOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
class="listbox-option"
|
||||
x-on:click="typeFilter = option.value; close()">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="typeFilter === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
@@ -155,28 +146,6 @@
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
|
||||
<x-table.dropdown panel-class="w-48!">
|
||||
<x-slot:trigger><button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3" stroke="currentColor"
|
||||
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button></x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
x-on:click="sortBy = option.value; close()">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor" stroke-width="1.4"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -158,26 +158,29 @@
|
||||
@endforeach
|
||||
@endif
|
||||
</x-table.filter>
|
||||
<x-table.sort>
|
||||
@foreach ([
|
||||
'newest' => 'Newest first',
|
||||
'oldest' => 'Oldest first',
|
||||
] as $sortValue => $sortLabel)
|
||||
<button type="button" class="listbox-option" role="option"
|
||||
aria-selected="{{ $deploymentSort === $sortValue ? 'true' : 'false' }}"
|
||||
wire:click="setDeploymentSort('{{ $sortValue }}')"
|
||||
@click="open = false">
|
||||
<span>{{ $sortLabel }}</span>
|
||||
@if ($deploymentSort === $sortValue)
|
||||
<svg class="size-3.5 shrink-0" viewBox="0 0 24 24" fill="none">
|
||||
<path d="m4.5 12.75 6 6 9-13.5" stroke="currentColor"
|
||||
stroke-width="2.5" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
@endforeach
|
||||
</x-table.sort>
|
||||
{{-- The Started header sorts the full table; the embedded history has no such header. --}}
|
||||
@if ($embedded)
|
||||
<x-table.sort>
|
||||
@foreach ([
|
||||
'newest' => 'Newest first',
|
||||
'oldest' => 'Oldest first',
|
||||
] as $sortValue => $sortLabel)
|
||||
<button type="button" class="listbox-option" role="option"
|
||||
aria-selected="{{ $deploymentSort === $sortValue ? 'true' : 'false' }}"
|
||||
wire:click="setDeploymentSort('{{ $sortValue }}')"
|
||||
@click="open = false">
|
||||
<span>{{ $sortLabel }}</span>
|
||||
@if ($deploymentSort === $sortValue)
|
||||
<svg class="size-3.5 shrink-0" viewBox="0 0 24 24" fill="none">
|
||||
<path d="m4.5 12.75 6 6 9-13.5" stroke="currentColor"
|
||||
stroke-width="2.5" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
@endforeach
|
||||
</x-table.sort>
|
||||
@endif
|
||||
</x-table.toolbar>
|
||||
|
||||
@if ($deployments->isNotEmpty())
|
||||
|
||||
@@ -231,9 +231,12 @@
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
{{ $previewIsStopped ? 'Deploy' : 'Redeploy' }}
|
||||
</button>
|
||||
@if (!$previewIsStopped || auth()->user()->can('delete', $application))
|
||||
<div class="my-1 border-t border-neutral-200 dark:border-white/[0.08]"></div>
|
||||
@endif
|
||||
@if (!$previewIsStopped)
|
||||
<button type="button"
|
||||
class="listbox-option justify-start! gap-2.5! text-error!"
|
||||
class="listbox-option justify-start! gap-2.5! text-error! hover:text-error!"
|
||||
@click="open = false; document.getElementById('preview-stop-trigger-{{ data_get($preview, 'pull_request_id') }}')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5" />
|
||||
@@ -243,7 +246,7 @@
|
||||
@endcan
|
||||
@can('delete', $application)
|
||||
<button type="button"
|
||||
class="listbox-option justify-start! gap-2.5! text-error!"
|
||||
class="listbox-option justify-start! gap-2.5! text-error! hover:text-error!"
|
||||
@click="open = false; document.getElementById('preview-delete-trigger-{{ data_get($preview, 'pull_request_id') }}')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="trash" class="size-3.5" />
|
||||
|
||||
@@ -49,31 +49,33 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<x-table.dropdown panel-class="w-52!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
|
||||
aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<div :class="viewMode === 'grid' ? '' : 'min-[641px]:hidden'">
|
||||
<x-table.dropdown panel-class="w-52!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
|
||||
aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="listbox-option"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</div>
|
||||
|
||||
<x-canvas-view-toggle :list-url="route('project.index')"
|
||||
:canvas-url="route('project.canvas.index')" />
|
||||
|
||||
@@ -132,30 +132,32 @@
|
||||
</div>
|
||||
</x-table.dropdown>
|
||||
|
||||
<x-table.dropdown panel-class="w-48!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div :class="viewMode === 'grid' ? '' : 'min-[769px]:hidden'">
|
||||
<x-table.dropdown panel-class="w-48!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="listbox-option"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</div>
|
||||
|
||||
<x-canvas-view-toggle
|
||||
:list-url="route('project.resource.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid])"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
},
|
||||
's3' => $backup->save_s3 ? ($backup->s3Storages->isNotEmpty() ? $backup->s3Storages : collect([$backup->s3])->filter())->where('team_id', $serviceTeamId)->count() : -1,
|
||||
'lastRunAt' => $backup->latestExecution?->finished_at?->timestamp ?? ($backup->latestExecution?->status === 'running' ? now()->timestamp : 0),
|
||||
'createdAt' => $backup->created_at?->timestamp ?? 0,
|
||||
])->concat($databaseBackups->map(fn ($backup) => [
|
||||
'id' => 'database:'.$backup->id,
|
||||
'name' => strtolower($backup->database->human_name ?: $backup->database->name),
|
||||
@@ -31,7 +30,6 @@
|
||||
},
|
||||
's3' => $backup->save_s3 ? ($backup->s3Storages->isNotEmpty() ? $backup->s3Storages : collect([$backup->s3])->filter())->where('team_id', $serviceTeamId)->count() : -1,
|
||||
'lastRunAt' => $backup->latest_log?->finished_at?->timestamp ?? ($backup->latest_log?->status === 'running' ? now()->timestamp : 0),
|
||||
'createdAt' => $backup->created_at?->timestamp ?? 0,
|
||||
]))->values()),
|
||||
filterOptions: @js(collect([['value' => 'all', 'label' => 'All targets']])->merge(
|
||||
$backups->map(fn ($backup) => [
|
||||
@@ -39,12 +37,6 @@
|
||||
'label' => $backup->targetType(),
|
||||
])->push(['value' => 'database', 'label' => 'Database'])->unique('value')->values()
|
||||
)->values()),
|
||||
sortOptions: [
|
||||
{ value: 'target_asc', label: 'Target A–Z' },
|
||||
{ value: 'target_desc', label: 'Target Z–A' },
|
||||
{ value: 'newest', label: 'Newest first' },
|
||||
{ value: 'oldest', label: 'Oldest first' },
|
||||
],
|
||||
get filteredBackups() {
|
||||
const query = this.search.toLowerCase();
|
||||
const filtered = this.backups.filter((backup) => {
|
||||
@@ -64,8 +56,6 @@
|
||||
const sortValue = sortValues[column] || sortValues.target;
|
||||
const order = direction === 'desc' ? -1 : 1;
|
||||
return filtered.sort((left, right) => {
|
||||
if (this.sortBy === 'newest') return right.createdAt - left.createdAt;
|
||||
if (this.sortBy === 'oldest') return left.createdAt - right.createdAt;
|
||||
const leftValue = sortValue(left);
|
||||
const rightValue = sortValue(right);
|
||||
return order * (typeof leftValue === 'number' ? leftValue - rightValue : leftValue.localeCompare(rightValue)) ||
|
||||
@@ -220,7 +210,7 @@
|
||||
</button></x-slot:trigger>
|
||||
<template x-for="option in filterOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
class="listbox-option"
|
||||
x-on:click="typeFilter = option.value; close()">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="typeFilter === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
@@ -231,28 +221,6 @@
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
|
||||
<x-table.dropdown panel-class="w-48!">
|
||||
<x-slot:trigger><button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3" stroke="currentColor"
|
||||
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button></x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
x-on:click="sortBy = option.value; close()">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor" stroke-width="1.4"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -170,14 +170,17 @@
|
||||
@endforeach
|
||||
@endif
|
||||
</x-table.filter>
|
||||
<x-table.sort>
|
||||
@foreach (['default' => 'Default order', 'name_asc' => 'Name A–Z', 'name_desc' => 'Name Z–A'] as $value => $label)
|
||||
<button type="button" class="listbox-option" wire:click="setTableSort('{{ $value }}')" @click="open = false">
|
||||
<span>{{ $label }}</span>
|
||||
@if ($tableSort === $value)<span>✓</span>@endif
|
||||
</button>
|
||||
@endforeach
|
||||
</x-table.sort>
|
||||
{{-- The table header, with its Name sort, is hidden on small screens (app.css, max-width: 768px). --}}
|
||||
<div class="min-[769px]:hidden">
|
||||
<x-table.sort>
|
||||
@foreach (['default' => 'Default order', 'name_asc' => 'Name A–Z', 'name_desc' => 'Name Z–A'] as $value => $label)
|
||||
<button type="button" class="listbox-option" wire:click="setTableSort('{{ $value }}')" @click="open = false">
|
||||
<span>{{ $label }}</span>
|
||||
@if ($tableSort === $value)<span>✓</span>@endif
|
||||
</button>
|
||||
@endforeach
|
||||
</x-table.sort>
|
||||
</div>
|
||||
@can('manageEnvironment', $resource)
|
||||
{{-- Do not disable Add based on readyToLoad: modal-input uses wire:ignore, so a
|
||||
disabled attribute painted on first load would never re-enable. --}}
|
||||
|
||||
@@ -61,31 +61,33 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<x-table.dropdown panel-class="w-48!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
|
||||
aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div :class="viewMode === 'grid' ? '' : 'min-[641px]:hidden'">
|
||||
<x-table.dropdown panel-class="w-48!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
|
||||
aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="listbox-option"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</div>
|
||||
|
||||
<x-canvas-view-toggle
|
||||
:list-url="route('project.show', ['project_uuid' => $project->uuid])"
|
||||
|
||||
@@ -196,12 +196,12 @@
|
||||
</x-slot:trigger>
|
||||
<button type="button" role="menuitem"
|
||||
x-on:click="close(); editToken(@js(App\Livewire\Forms\ApiTokenForm::valuesOf($token)))"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
|
||||
class="listbox-option justify-start!">
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" role="menuitem" x-on:click="close()"
|
||||
wire:click="setPaused({{ $token->id }}, {{ $token->isPaused() ? 'false' : 'true' }})"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
|
||||
class="listbox-option justify-start!">
|
||||
{{ $token->isPaused() ? 'Resume' : 'Pause' }}
|
||||
</button>
|
||||
<x-modal-confirmation title="Roll API Token?" buttonFullWidth
|
||||
@@ -215,11 +215,12 @@
|
||||
:confirmWithPassword="false" step2ButtonText="Roll token">
|
||||
<x-slot:trigger>
|
||||
<button type="button" role="menuitem" x-on:click="close()"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
|
||||
class="listbox-option justify-start!">
|
||||
Roll
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<div class="my-1 border-t border-neutral-200 dark:border-white/[0.08]"></div>
|
||||
<x-modal-confirmation title="Confirm API Token Revocation?" buttonFullWidth
|
||||
submitAction="revoke({{ $token->id }})" :actions="[
|
||||
'This API token will be permanently revoked.',
|
||||
@@ -230,7 +231,7 @@
|
||||
:confirmWithPassword="false" step2ButtonText="Revoke token">
|
||||
<x-slot:trigger>
|
||||
<button type="button" role="menuitem" x-on:click="close()"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-error hover:bg-error/10">
|
||||
class="listbox-option justify-start! text-error! hover:text-error!">
|
||||
Revoke
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
|
||||
@@ -57,31 +57,33 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<x-table.dropdown panel-class="w-52!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
|
||||
aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div :class="viewMode === 'grid' ? '' : 'min-[641px]:hidden'">
|
||||
<x-table.dropdown panel-class="w-52!">
|
||||
<x-slot:trigger>
|
||||
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
|
||||
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
|
||||
aria-hidden="true">
|
||||
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
Sort
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</x-slot:trigger>
|
||||
<template x-for="option in sortOptions" :key="option.value">
|
||||
<button type="button"
|
||||
class="listbox-option"
|
||||
x-on:click="sortBy = option.value; close(); page = 1">
|
||||
<span class="flex-1" x-text="option.label"></span>
|
||||
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
|
||||
viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
|
||||
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</x-table.dropdown>
|
||||
</div>
|
||||
|
||||
<x-canvas-view-toggle :list-url="route('tags.show')"
|
||||
:canvas-url="route('tags.canvas')" />
|
||||
|
||||
@@ -13,11 +13,11 @@ beforeEach(function () {
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates volume backups for bravo, alpha, and charlie, in that order from oldest to newest.
|
||||
* Creates volume backups for bravo, alpha, and charlie.
|
||||
*/
|
||||
function createSortableVolumeBackups(Model $resource): void
|
||||
{
|
||||
foreach (['bravo', 'alpha', 'charlie'] as $index => $name) {
|
||||
foreach (['bravo', 'alpha', 'charlie'] as $name) {
|
||||
$volume = LocalPersistentVolume::create([
|
||||
'uuid' => (string) new Cuid2,
|
||||
'name' => $name.'-data',
|
||||
@@ -26,15 +26,13 @@ function createSortableVolumeBackups(Model $resource): void
|
||||
'resource_type' => $resource->getMorphClass(),
|
||||
]);
|
||||
|
||||
$backup = ScheduledVolumeBackup::create([
|
||||
ScheduledVolumeBackup::create([
|
||||
'backupable_type' => $volume->getMorphClass(),
|
||||
'backupable_id' => $volume->id,
|
||||
'team_id' => 0,
|
||||
'frequency' => '0 0 * * *',
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
ScheduledVolumeBackup::whereKey($backup->id)->update(['created_at' => now()->subDays(10 - $index)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,24 +53,13 @@ function visibleBackupOrderScript(): string
|
||||
function assertBackupSortOrders(mixed $page, string $screenshot): void
|
||||
{
|
||||
$page->assertSee('alpha-data')
|
||||
->assertScript(visibleBackupOrderScript(), 'alpha-data,bravo-data,charlie-data')
|
||||
->click('Sort')
|
||||
->click('Target Z–A')
|
||||
->assertScript(visibleBackupOrderScript(), 'charlie-data,bravo-data,alpha-data')
|
||||
->click('Sort')
|
||||
->click('Oldest first')
|
||||
->assertScript(visibleBackupOrderScript(), 'bravo-data,alpha-data,charlie-data')
|
||||
->click('Sort')
|
||||
->click('Newest first')
|
||||
->assertScript(visibleBackupOrderScript(), 'charlie-data,alpha-data,bravo-data')
|
||||
->click('[aria-label^="Sort by target"]')
|
||||
->assertScript(visibleBackupOrderScript(), 'alpha-data,bravo-data,charlie-data')
|
||||
->click('[aria-label^="Sort by target"]')
|
||||
->assertScript(visibleBackupOrderScript(), 'charlie-data,bravo-data,alpha-data')
|
||||
->screenshot(filename: $screenshot);
|
||||
}
|
||||
|
||||
it('reorders application storage backups when the sort option changes', function () {
|
||||
it('reorders application storage backups when the target header is clicked', function () {
|
||||
$application = createBrowserApplication($this->stack, [
|
||||
'uuid' => 'app-browser-backup-sort',
|
||||
'name' => 'Backup Sort App',
|
||||
@@ -86,7 +73,7 @@ it('reorders application storage backups when the sort option changes', function
|
||||
assertBackupSortOrders($page, 'application-backup-sort');
|
||||
});
|
||||
|
||||
it('reorders service storage backups when the sort option changes', function () {
|
||||
it('reorders service storage backups when the target header is clicked', function () {
|
||||
['service' => $service, 'serviceApplication' => $serviceApplication] = createBrowserService($this->stack, [
|
||||
'uuid' => 'svc-browser-backup-sort',
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user