mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(ui): use bare status badges and show backup S3 storage as plain text
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ba966846e1
commit
b12c965fbe
@@ -447,16 +447,21 @@ Class names below are examples. Keep the values and structure.
|
||||
|
||||
```css
|
||||
.badge {
|
||||
display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px;
|
||||
border: 1px solid rgb(0 0 0 / 9%); border-radius: 999px; background: rgb(0 0 0 / 4%);
|
||||
font-size: 12px; font-weight: 500; line-height: 1; color: var(--ui-fg-dim);
|
||||
display: inline-flex; align-items: center; gap: 6px; height: 24px;
|
||||
font-size: 13px; line-height: 1; color: var(--ui-fg-dim);
|
||||
}
|
||||
.dark .badge { border-color: rgb(255 255 255 / 12%); background: rgb(255 255 255 / 7%); color: var(--ui-fg); }
|
||||
.dark .badge { color: var(--ui-fg); }
|
||||
.badge-dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
|
||||
```
|
||||
|
||||
- The pill stays neutral. Only the 6px dot has the status color (success,
|
||||
warning, error, neutral). Use a pulsing dot for a state in progress.
|
||||
- The badge is bare: a 6px colored dot and plain text, with no border, fill,
|
||||
or padding. Only the dot has the status color (success, warning, error,
|
||||
neutral). Use a pulsing dot for a state in progress.
|
||||
- Use the bare form everywhere: table rows, cards, headers, and the top bar.
|
||||
Do not wrap a status in a pill.
|
||||
- A clickable badge (a status menu or a link) gets a ghost hover: a subtle
|
||||
rounded background drawn 6px outside the badge, so the dot stays aligned
|
||||
with the text around it.
|
||||
|
||||
### Empty state
|
||||
|
||||
@@ -1235,7 +1240,7 @@ Collections with many rows should use the Cloudflare-inspired table pattern:
|
||||
- filters, sort, view toggles, and Add on the right;
|
||||
- 40px header row and roughly 48px data rows;
|
||||
- subtle row hover;
|
||||
- plain text or the shared status badge rather than large colored chips;
|
||||
- plain text or the shared bare status badge rather than pills or colored chips;
|
||||
- compact action at the far right;
|
||||
- no separate layer card for each item.
|
||||
|
||||
|
||||
@@ -3179,11 +3179,6 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
|
||||
.table-filter-value.table-filter-badge:hover {
|
||||
text-decoration-color: transparent;
|
||||
border-color: var(--color-neutral-400);
|
||||
}
|
||||
|
||||
.dark .table-filter-value.table-filter-badge:hover {
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.env-table-grid {
|
||||
@@ -4259,7 +4254,6 @@ html[data-theme="custom"] .logs-viewer-timestamp {
|
||||
|
||||
.logs-section-status-badge {
|
||||
height: 1.5rem !important;
|
||||
padding-inline: 0.5rem !important;
|
||||
font-size: 0.6875rem !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
const listButton = 'flex w-full items-center gap-2 rounded-md px-1.5 py-1 text-left text-[12px] transition-colors hover:bg-neutral-100 dark:hover:bg-white/[0.06]';
|
||||
const smallButton = danger => `button h-6! shrink-0 px-2! text-[11px]! ${danger ? 'button-error' : ''}`;
|
||||
const faint = 'text-[12px] text-neutral-400 dark:text-fg-faint';
|
||||
const statusPill = 'inline-flex h-6 items-center gap-1.5 rounded-full border border-neutral-200 bg-neutral-100 px-2 text-xs font-medium text-neutral-700 dark:border-white/[0.12] dark:bg-white/[0.07] dark:text-white';
|
||||
const statusPill = 'inline-flex h-6 items-center gap-1.5 text-[13px] text-neutral-700 dark:text-fg';
|
||||
const titleLink = '[&[href]]:cursor-pointer [&[href]]:underline-offset-2 [&[href]]:hover:underline';
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@props([
|
||||
'enabled' => false,
|
||||
'names' => collect(),
|
||||
'tooltip' => null,
|
||||
])
|
||||
|
||||
{{-- S3 is a setting, not a state: plain text keeps the status badge the only pill in the row. --}}
|
||||
<span {{ $attributes->class('inline-flex max-w-full min-w-0 items-center gap-1.5') }}
|
||||
@if ($tooltip) data-tooltip="{{ $tooltip }}" aria-label="{{ $tooltip }}" tabindex="0" @endif>
|
||||
@if (! $enabled)
|
||||
<span class="text-neutral-400 dark:text-fg-faint">Local only</span>
|
||||
@elseif ($names->isEmpty())
|
||||
<x-reicon name="alert-triangle" class="size-3.5 shrink-0 text-red-500" />
|
||||
<span class="text-red-600 dark:text-red-400">Unavailable</span>
|
||||
@else
|
||||
<span class="truncate">{{ $names->first() }}</span>
|
||||
@if ($names->count() > 1)
|
||||
<span class="shrink-0 text-neutral-400 dark:text-fg-faint">+{{ $names->count() - 1 }}</span>
|
||||
@endif
|
||||
@endif
|
||||
</span>
|
||||
@@ -18,15 +18,17 @@
|
||||
];
|
||||
|
||||
$sizeClasses = [
|
||||
'sm' => 'h-4 gap-1 px-1.5 text-[10px]',
|
||||
'md' => 'h-6 gap-1.5 px-2 text-xs',
|
||||
'sm' => 'gap-1 text-[11px]',
|
||||
'md' => 'gap-1.5 text-[13px]',
|
||||
];
|
||||
|
||||
$baseClasses = 'inline-flex max-w-full items-center whitespace-nowrap rounded-full border border-neutral-200 bg-neutral-100 font-medium leading-none text-neutral-700 dark:border-white/[0.12] dark:bg-white/[0.07] dark:text-white ' . ($sizeClasses[$size] ?? $sizeClasses['md']);
|
||||
// Bare: a colored dot and text, no pill. Interactive badges draw their hover background outside the box, so the dot stays aligned with plain text.
|
||||
$baseClasses = 'inline-flex max-w-full items-center whitespace-nowrap text-neutral-700 dark:text-fg ' . ($sizeClasses[$size] ?? $sizeClasses['md']);
|
||||
$interactiveClasses = 'relative isolate before:absolute before:-inset-x-1.5 before:-inset-y-1 before:-z-10 before:rounded-md before:transition-colors hover:before:bg-neutral-100 dark:hover:before:bg-white/[0.07]';
|
||||
@endphp
|
||||
|
||||
@if ($as === 'button')
|
||||
<button {{ $attributes->class([$baseClasses, 'transition-colors'])->merge(['type' => 'button']) }}>
|
||||
<button {{ $attributes->class([$baseClasses, $interactiveClasses])->merge(['type' => 'button']) }}>
|
||||
@if ($dynamic)
|
||||
{{ $slot }}
|
||||
@else
|
||||
@@ -47,7 +49,7 @@
|
||||
@endif
|
||||
</button>
|
||||
@elseif ($as === 'a')
|
||||
<a {{ $attributes->class([$baseClasses, 'transition-colors']) }}>
|
||||
<a {{ $attributes->class([$baseClasses, $interactiveClasses]) }}>
|
||||
@if ($dynamic)
|
||||
{{ $slot }}
|
||||
@else
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
'active' => $storage->uuid === $currentStorage->uuid,
|
||||
])">
|
||||
<x-slot:meta>
|
||||
<span class="inline-flex h-[22px] shrink-0 items-center gap-1.5 rounded-full border border-neutral-200 bg-neutral-100 px-2.5 text-xs font-medium text-black dark:border-white/[0.12] dark:bg-white/[0.08] dark:text-fg"
|
||||
<span class="inline-flex h-[22px] shrink-0 items-center gap-1.5 text-xs text-neutral-700 dark:text-fg"
|
||||
x-data="{ usable: @js((bool) $currentStorage->is_usable) }"
|
||||
@storage-status-changed.window="usable = $event.detail.isUsable">
|
||||
<span class="size-1.5 rounded-full" :class="usable ? 'bg-[#3fb950]' : 'bg-red-500'"></span>
|
||||
@@ -169,7 +169,7 @@
|
||||
'active' => $source->getMorphClass() === $currentSource->getMorphClass() && $source->uuid === $currentSource->uuid,
|
||||
])">
|
||||
<x-slot:meta>
|
||||
<span class="inline-flex h-[22px] shrink-0 items-center gap-1.5 rounded-full border border-neutral-200 bg-neutral-100 px-2.5 text-xs font-medium text-black dark:border-white/[0.12] dark:bg-white/[0.08] dark:text-fg">
|
||||
<span class="inline-flex h-[22px] shrink-0 items-center gap-1.5 text-xs text-neutral-700 dark:text-fg">
|
||||
<span @class([
|
||||
'size-1.5 rounded-full',
|
||||
'bg-[#3fb950]' => $sourceConnected,
|
||||
@@ -189,7 +189,7 @@
|
||||
'active' => $destination->getMorphClass() === $currentDestination->getMorphClass() && $destination->uuid === $currentDestination->uuid,
|
||||
])">
|
||||
<x-slot:meta>
|
||||
<span class="inline-flex h-[22px] shrink-0 items-center gap-1.5 rounded-full border border-neutral-200 bg-neutral-100 px-2.5 text-xs font-medium text-black dark:border-white/[0.12] dark:bg-white/[0.08] dark:text-fg">
|
||||
<span class="inline-flex h-[22px] shrink-0 items-center gap-1.5 text-xs text-neutral-700 dark:text-fg">
|
||||
<span @class([
|
||||
'size-1.5 rounded-full',
|
||||
'bg-[#3fb950]' => $currentDestination->getMorphClass() === 'App\\Models\\StandaloneDocker',
|
||||
|
||||
@@ -206,9 +206,9 @@
|
||||
</span>
|
||||
<span>{{ $backup->frequency }}</span>
|
||||
<span><x-status-badge :status="$statusLabel" :type="$statusType" /></span>
|
||||
<span title="{{ $backup->save_s3 ? ($s3StorageNames->isNotEmpty() ? $s3StorageNames->join(', ') : 'S3 storage unavailable') : 'S3 storage is not configured' }}">
|
||||
<x-status-badge :status="$backup->save_s3 ? match ($s3StorageNames->count()) { 0 => 'Unavailable', 1 => 'Configured', default => $s3StorageNames->count().' storages' } : 'Not set'"
|
||||
:type="$backup->save_s3 ? ($s3StorageNames->isNotEmpty() ? 'success' : 'error') : 'neutral'" />
|
||||
<span class="min-w-0">
|
||||
<x-backup-s3-storage :enabled="$backup->save_s3" :names="$s3StorageNames"
|
||||
:tooltip="$backup->save_s3 ? ($s3StorageNames->isNotEmpty() ? 'S3 storage: '.$s3StorageNames->join(', ') : 'S3 storage: Unavailable') : 'S3 storage: Not configured'" />
|
||||
</span>
|
||||
<span>
|
||||
{{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }}
|
||||
|
||||
@@ -283,10 +283,8 @@
|
||||
</span>
|
||||
<span>{{ $databaseBackup->frequency }}</span>
|
||||
<span><x-status-badge :status="$statusLabel" :type="$statusType" /></span>
|
||||
<span>
|
||||
<x-status-badge :status="$databaseBackup->save_s3 ? match ($databaseS3s->count()) { 0 => 'Unavailable', 1 => 'Configured', default => $databaseS3s->count().' storages' } : 'Not set'"
|
||||
:type="$databaseBackup->save_s3 ? ($databaseS3s->isNotEmpty() ? 'success' : 'error') : 'neutral'"
|
||||
:data-tooltip="$databaseS3Tooltip" :aria-label="$databaseS3Tooltip" tabindex="0" />
|
||||
<span class="min-w-0">
|
||||
<x-backup-s3-storage :enabled="$databaseBackup->save_s3" :names="$databaseS3s->pluck('name')" :tooltip="$databaseS3Tooltip" />
|
||||
</span>
|
||||
<span>{{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }}</span>
|
||||
<span class="flex justify-end" x-on:click.stop x-on:keydown.enter.stop>
|
||||
@@ -341,10 +339,8 @@
|
||||
</span>
|
||||
<span>{{ $backup->frequency }}</span>
|
||||
<span><x-status-badge :status="$statusLabel" :type="$statusType" /></span>
|
||||
<span>
|
||||
<x-status-badge :status="$backup->save_s3 ? match ($volumeS3s->count()) { 0 => 'Unavailable', 1 => 'Configured', default => $volumeS3s->count().' storages' } : 'Not set'"
|
||||
:type="$backup->save_s3 ? ($volumeS3s->isNotEmpty() ? 'success' : 'error') : 'neutral'"
|
||||
:data-tooltip="$volumeS3Tooltip" :aria-label="$volumeS3Tooltip" tabindex="0" />
|
||||
<span class="min-w-0">
|
||||
<x-backup-s3-storage :enabled="$backup->save_s3" :names="$volumeS3s->pluck('name')" :tooltip="$volumeS3Tooltip" />
|
||||
</span>
|
||||
<span>
|
||||
{{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }}
|
||||
|
||||
@@ -246,12 +246,9 @@
|
||||
</template>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex items-center gap-2 text-[11px] font-medium text-neutral-600 dark:text-fg-dim">
|
||||
<span x-show="server.statusType !== 'success'"
|
||||
class="hidden size-2 shrink-0 rounded-full md:inline-block"
|
||||
:class="server.statusType === 'warning' ? 'bg-orange-500 dark:bg-warning' : 'bg-red-500 dark:bg-red-400'"></span>
|
||||
<span x-show="server.statusType === 'success'"
|
||||
class="hidden size-2 shrink-0 rounded-full bg-green-500 md:inline-block dark:bg-green-400"></span>
|
||||
<div class="flex items-center gap-1.5 text-[13px] text-neutral-700 dark:text-fg">
|
||||
<span class="hidden size-1.5 shrink-0 rounded-full md:inline-block"
|
||||
:class="{ 'bg-emerald-500': server.statusType === 'success', 'bg-warning': server.statusType === 'warning', 'bg-red-500': server.statusType !== 'success' && server.statusType !== 'warning' }"></span>
|
||||
<span x-text="server.status"></span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user