mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 17:30:46 -04:00
feat(ui): dashboard refinements + wire:navigate setting fixes
- Sidebar: keep nav icons at the same x-position when collapsed (muscle memory) - Database URLs: add copy button to internal/public URL fields - Project cards + list: compact icon stats for env/resource counts, add Created column - Server card graph: add CPU/Memory color legend to the tooltip - Server list: add IP address, resource count, and status dot columns - Respect is_wire_navigate_enabled on scheduled-backups links (use wireNavigate helper)
This commit is contained in:
@@ -57,6 +57,7 @@ class Index extends Component
|
||||
'href' => $project->navigateTo(),
|
||||
'environmentCount' => $project->environments->count(),
|
||||
'resourceCount' => $resourceCount,
|
||||
'createdAt' => $project->created_at?->format('M j, Y') ?? '-',
|
||||
'settingsHref' => auth()->user()->can('update', $project)
|
||||
? route('project.edit', ['project_uuid' => $project->uuid])
|
||||
: null,
|
||||
|
||||
@@ -25,6 +25,7 @@ class Input extends Component
|
||||
public bool $readonly = false,
|
||||
public ?string $helper = null,
|
||||
public bool $allowToPeak = true,
|
||||
public bool $copyable = false,
|
||||
public bool $isMultiline = false,
|
||||
public string $defaultClass = 'input',
|
||||
public string $autocomplete = 'off',
|
||||
@@ -72,9 +73,15 @@ class Input extends Component
|
||||
}
|
||||
// Durable class (not type-attr based): Alpine may toggle type to "text" when revealing,
|
||||
// and settings-workspace CSS otherwise overrides utility padding-right.
|
||||
if ($this->type === 'password' && $this->allowToPeak) {
|
||||
$hasPeek = $this->type === 'password' && $this->allowToPeak;
|
||||
if ($hasPeek) {
|
||||
$this->defaultClass = $this->defaultClass.' input-with-password-toggle';
|
||||
}
|
||||
if ($this->copyable) {
|
||||
// Reserve clearance for a single copy button, or for both the peek eye
|
||||
// and the copy button when the field is a maskable password.
|
||||
$this->defaultClass = $this->defaultClass.($hasPeek ? ' input-with-copy-and-peek' : ' input-with-copy-button');
|
||||
}
|
||||
|
||||
// $this->label = Str::title($this->label);
|
||||
return view('components.forms.input');
|
||||
|
||||
+16
-6
@@ -522,6 +522,11 @@ tr td:first-child {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
/* Room for both the peek eye and the copy button on maskable read-only fields. */
|
||||
.input.input-with-copy-and-peek {
|
||||
padding-right: 4.25rem;
|
||||
}
|
||||
|
||||
.lds-heart {
|
||||
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
@@ -1958,6 +1963,11 @@ html[data-theme="custom"] textarea:disabled {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.application-settings-workspace .input.input-with-copy-and-peek,
|
||||
.application-settings-form .input.input-with-copy-and-peek {
|
||||
padding-right: 4.25rem;
|
||||
}
|
||||
|
||||
.application-settings-workspace .input:focus-visible,
|
||||
.application-settings-workspace .select:focus-visible,
|
||||
.application-settings-form .input:focus-visible,
|
||||
@@ -4372,11 +4382,11 @@ html[data-theme="custom"] .runtime-log-columns {
|
||||
.projects-table-grid {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
minmax(220px, 1.7fr)
|
||||
minmax(100px, 0.65fr)
|
||||
minmax(90px, 0.6fr)
|
||||
minmax(220px, 1.5fr)
|
||||
6rem;
|
||||
minmax(200px, 1.7fr)
|
||||
8rem
|
||||
7rem
|
||||
minmax(200px, 1.6fr)
|
||||
5rem;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
@@ -4423,7 +4433,7 @@ html[data-theme="custom"] .runtime-log-columns {
|
||||
|
||||
@media (max-width: 1050px) {
|
||||
.projects-table-grid {
|
||||
grid-template-columns: minmax(220px, 1fr) 7rem 6rem 6rem;
|
||||
grid-template-columns: minmax(200px, 1fr) 8rem 7rem 5rem;
|
||||
}
|
||||
|
||||
.projects-table-grid .project-description {
|
||||
|
||||
@@ -415,15 +415,15 @@
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.sidebar-collapsed .menu-item {
|
||||
justify-content: center;
|
||||
width: var(--button-h, 2rem);
|
||||
height: var(--button-h, 2rem);
|
||||
min-height: var(--button-h, 2rem);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
gap: 0;
|
||||
margin-inline: auto;
|
||||
/* Collapsed rail keeps every nav icon at its expanded x-position (left-aligned,
|
||||
same 10px inset) so muscle memory holds when the sidebar is toggled; only the
|
||||
label is hidden. Unlayered rule outranks the inline lg:justify-center/lg:px-0
|
||||
utilities on each row. The footer collapse toggle keeps its own centered
|
||||
square via the .sidebar-toggle exclusion. */
|
||||
.sidebar-collapsed .menu-item:not(.sidebar-toggle) {
|
||||
justify-content: flex-start;
|
||||
padding-left: 0.625rem;
|
||||
padding-right: 0.625rem;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .sidebar-collapsed-label {
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
<x-forms.input :label="$label . ' URL (internal)'" disabled value="Hidden (only admins can view)" />
|
||||
<x-forms.input :label="$label . ' URL (public)'" disabled value="Hidden (only admins can view)" />
|
||||
@else
|
||||
<x-forms.input :label="$label . ' URL (internal)'" :helper="$urlHelper" type="password" readonly
|
||||
<x-forms.input :label="$label . ' URL (internal)'" :helper="$urlHelper" type="password" readonly copyable
|
||||
wire:model="dbUrl" canGate="update" :canResource="$database" />
|
||||
@if ($dbUrlPublic)
|
||||
<x-forms.input :label="$label . ' URL (public)'" :helper="$urlHelper" type="password" readonly
|
||||
<x-forms.input :label="$label . ' URL (public)'" :helper="$urlHelper" type="password" readonly copyable
|
||||
wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
|
||||
@elseif ($showPublicUrlPlaceholder)
|
||||
<x-forms.input :label="$label . ' URL (public)'" :helper="$urlHelper" readonly
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
@php
|
||||
// Copy affordance reads the live Livewire value. The bound property comes
|
||||
// either from the `id`-derived modelBinding or from a passthrough
|
||||
// `wire:model` attribute (used by read-only fields like DB URLs).
|
||||
$copyModel = $copyable
|
||||
? ($modelBinding !== 'null' ? $modelBinding : $attributes->get('wire:model'))
|
||||
: null;
|
||||
@endphp
|
||||
|
||||
<div @class([
|
||||
'flex-1' => $isMultiline,
|
||||
'w-full' => !$isMultiline,
|
||||
@@ -48,9 +57,18 @@
|
||||
<x-reicon name="eye-off2" x-cloak x-show="type === 'text'" class="size-[18px]" />
|
||||
</button>
|
||||
@endif
|
||||
@if ($copyable)
|
||||
<x-copy-button
|
||||
@if ($copyModel) resolve="$wire.get('{{ $copyModel }}')" @else :value="$value" @endif
|
||||
label="Copy to clipboard"
|
||||
class="absolute top-1/2 z-10 -translate-y-1/2 {{ $allowToPeak ? 'right-8' : 'right-1' }}" />
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@else
|
||||
@if ($copyable)
|
||||
<div class="relative">
|
||||
@endif
|
||||
<input autocomplete="{{ $autocomplete }}" @if ($value) value="{{ $value }}" @endif
|
||||
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
|
||||
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif
|
||||
@@ -61,6 +79,13 @@
|
||||
@if ($htmlId !== 'null') id={{ $htmlId }} @endif name="{{ $name }}"
|
||||
placeholder="{{ $attributes->get('placeholder') }}"
|
||||
@if ($autofocus) x-ref="autofocusInput" autofocus @endif>
|
||||
@if ($copyable)
|
||||
<x-copy-button
|
||||
@if ($copyModel) resolve="$wire.get('{{ $copyModel }}')" @else :value="$value" @endif
|
||||
label="Copy to clipboard"
|
||||
class="absolute top-1/2 right-1 z-10 -translate-y-1/2" />
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
@if (!$label && $helper)
|
||||
<x-helper :helper="$helper" />
|
||||
|
||||
@@ -52,11 +52,10 @@
|
||||
}">
|
||||
{{-- Search is only useful when workspace resources are available --}}
|
||||
@if (isSubscribed() || ! isCloud())
|
||||
<div class="px-1 pb-3" :class="collapsed && 'lg:px-0 lg:flex lg:justify-center'">
|
||||
<div class="px-1 pb-3" :class="collapsed && 'lg:px-0'">
|
||||
<button @click="$dispatch('open-global-search')" type="button"
|
||||
:title="'Search (Press / or ' + modKeyLabel + 'K)'"
|
||||
class="menu-item justify-between !bg-neutral-100 dark:!bg-white/[0.04] hover:!bg-neutral-200 dark:hover:!bg-white/[0.07] !text-fg-faint"
|
||||
:class="collapsed && 'lg:w-8 lg:justify-center lg:px-0'">
|
||||
class="menu-item justify-between !bg-neutral-100 dark:!bg-white/[0.04] hover:!bg-neutral-200 dark:hover:!bg-white/[0.07] !text-fg-faint">
|
||||
<span class="flex items-center gap-2.5 min-w-0">
|
||||
<x-reicon name="search" class="menu-item-icon" />
|
||||
<span class="menu-item-label" :class="collapsed && 'lg:hidden'">Search</span>
|
||||
@@ -239,7 +238,7 @@
|
||||
:class="collapsed ? 'flex-col-reverse justify-center' : 'justify-between'">
|
||||
<x-top-user-menu sidebar />
|
||||
<button type="button" @click="toggleSidebar()" title="Toggle sidebar" aria-label="Toggle sidebar"
|
||||
class="menu-item w-8 shrink-0 justify-center px-0">
|
||||
class="menu-item sidebar-toggle w-8 shrink-0 justify-center px-0">
|
||||
<svg class="menu-item-icon" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" stroke="currentColor" stroke-width="1.6" />
|
||||
<path d="M9 4v16" stroke="currentColor" stroke-width="1.6" />
|
||||
|
||||
@@ -76,13 +76,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto flex items-center justify-between gap-3 pt-4">
|
||||
<p class="min-w-0 truncate text-[11px] text-neutral-500 dark:text-fg-dim">
|
||||
{{ $project->environments->count() }}
|
||||
{{ str('env')->plural($project->environments->count()) }}
|
||||
<span class="px-1 text-neutral-300 dark:text-white/15">·</span>
|
||||
{{ $resourceCount }} {{ str('resource')->plural($resourceCount) }}
|
||||
</p>
|
||||
<div class="mt-auto flex items-center justify-between gap-3 border-t border-neutral-100 pt-2.5 dark:border-white/[0.06]">
|
||||
<div class="flex min-w-0 items-center gap-3 text-[11px] font-medium text-neutral-500 dark:text-fg-dim">
|
||||
<span class="inline-flex items-center gap-1"
|
||||
title="{{ $project->environments->count() }} {{ str('environment')->plural($project->environments->count()) }}">
|
||||
<x-reicon name="layers" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
{{ $project->environments->count() }}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1"
|
||||
title="{{ $resourceCount }} {{ str('resource')->plural($resourceCount) }}">
|
||||
<x-reicon name="grid" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
{{ $resourceCount }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex shrink-0 items-center gap-0.5">
|
||||
@if ($firstEnvironment)
|
||||
|
||||
@@ -102,9 +102,11 @@
|
||||
timeZoneName: 'short',
|
||||
});
|
||||
|
||||
const dot = color => `<span style="display:inline-block;width:8px;height:8px;border-radius:9999px;margin-right:6px;flex:none;background:${color}"></span>`;
|
||||
|
||||
return `<div class="apexcharts-tooltip-custom">
|
||||
<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">${formatPercent(cpu)}</span></div>
|
||||
<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">${formatPercent(memory)}</span></div>
|
||||
<div class="apexcharts-tooltip-custom-value" style="display:flex;align-items:center">${dot('rgb(30, 144, 255)')}CPU: <span class="apexcharts-tooltip-value-bold" style="margin-left:4px">${formatPercent(cpu)}</span></div>
|
||||
<div class="apexcharts-tooltip-custom-value" style="display:flex;align-items:center">${dot('rgb(168, 85, 247)')}Memory: <span class="apexcharts-tooltip-value-bold" style="margin-left:4px">${formatPercent(memory)}</span></div>
|
||||
<div class="apexcharts-tooltip-custom-title">Your time: ${formatLocalTimestamp(timestamp)}</div>
|
||||
<div class="apexcharts-tooltip-custom-title">UTC: ${formatUtcTimestamp(timestamp)}</div>
|
||||
</div>`;
|
||||
|
||||
@@ -100,13 +100,13 @@
|
||||
{{ $backup->save_s3 ? ($backup->s3?->name ?? 'Unavailable') : 'Local only' }}
|
||||
</div>
|
||||
<div class="text-[11px] text-neutral-600 dark:text-fg-dim">
|
||||
<a wire:navigate href="{{ $backupExecutionsRoute }}"
|
||||
<a {{ wireNavigate() }} href="{{ $backupExecutionsRoute }}"
|
||||
class="font-medium hover:underline hover:text-black dark:hover:text-fg">
|
||||
{{ $backup->executions_count ?? $backup->executions()->count() }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<a class="button" wire:navigate href="{{ $backupRoute }}">Manage</a>
|
||||
<a class="button" {{ wireNavigate() }} href="{{ $backupRoute }}">Manage</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@@ -125,14 +125,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto flex items-center justify-between gap-3 pt-4">
|
||||
<p class="min-w-0 truncate text-[11px] text-neutral-500 dark:text-fg-dim">
|
||||
<span
|
||||
x-text="`${project.environmentCount} ${project.environmentCount === 1 ? 'env' : 'envs'}`"></span>
|
||||
<span class="px-1 text-neutral-300 dark:text-white/15">·</span>
|
||||
<span
|
||||
x-text="`${project.resourceCount} ${project.resourceCount === 1 ? 'resource' : 'resources'}`"></span>
|
||||
</p>
|
||||
<div class="mt-auto flex items-center justify-between gap-3 border-t border-neutral-100 pt-2.5 dark:border-white/[0.06]">
|
||||
<div class="flex min-w-0 items-center gap-3 text-[11px] font-medium text-neutral-500 dark:text-fg-dim">
|
||||
<span class="inline-flex items-center gap-1"
|
||||
:title="`${project.environmentCount} ${project.environmentCount === 1 ? 'environment' : 'environments'}`">
|
||||
<x-reicon name="layers" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
<span x-text="project.environmentCount"></span>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1"
|
||||
:title="`${project.resourceCount} ${project.resourceCount === 1 ? 'resource' : 'resources'}`">
|
||||
<x-reicon name="grid" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
<span x-text="project.resourceCount"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex shrink-0 items-center gap-0.5">
|
||||
<a x-show="project.addResourceHref" :href="project.addResourceHref"
|
||||
@@ -161,8 +166,8 @@
|
||||
<div
|
||||
class="projects-table-grid 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">
|
||||
<div>Project</div>
|
||||
<div>Environments</div>
|
||||
<div>Resources</div>
|
||||
<div>Contents</div>
|
||||
<div>Created</div>
|
||||
<div class="project-description">Description</div>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -185,10 +190,20 @@
|
||||
x-text="project.name"></a>
|
||||
</div>
|
||||
|
||||
<div class="text-[12px] text-neutral-600 dark:text-fg-dim"
|
||||
x-text="project.environmentCount"></div>
|
||||
<div class="text-[12px] text-neutral-600 dark:text-fg-dim"
|
||||
x-text="project.resourceCount"></div>
|
||||
<div class="flex items-center gap-3 text-[12px] font-medium text-neutral-600 dark:text-fg-dim">
|
||||
<span class="inline-flex items-center gap-1"
|
||||
:title="`${project.environmentCount} ${project.environmentCount === 1 ? 'environment' : 'environments'}`">
|
||||
<x-reicon name="layers" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
<span x-text="project.environmentCount"></span>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1"
|
||||
:title="`${project.resourceCount} ${project.resourceCount === 1 ? 'resource' : 'resources'}`">
|
||||
<x-reicon name="grid" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
<span x-text="project.resourceCount"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="truncate text-[12px] text-neutral-500 dark:text-fg-dim"
|
||||
x-text="project.createdAt"></div>
|
||||
<p class="project-description truncate text-[12px] text-neutral-500 dark:text-fg-dim"
|
||||
x-text="project.description || '-'"></p>
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
'href' => route('server.show', ['server_uuid' => $server->uuid]),
|
||||
'status' => $status,
|
||||
'statusType' => $statusType,
|
||||
'ip' => $server->isLocalhost() ? 'localhost' : ($server->ip ?: '-'),
|
||||
'resourceCount' => $server->definedResources()->count(),
|
||||
];
|
||||
})->values();
|
||||
@endphp
|
||||
@@ -172,13 +174,15 @@
|
||||
<div x-show="viewMode === 'table'"
|
||||
class="overflow-x-auto rounded-xl border border-neutral-200 bg-white shadow-sm dark:border-white/[0.08] dark:bg-white/[0.05]">
|
||||
<div
|
||||
class="grid min-w-[480px] grid-cols-[minmax(0,1fr)_9.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">
|
||||
class="grid min-w-[480px] grid-cols-[minmax(0,1fr)_9.5rem] border-b border-neutral-200 bg-neutral-50 px-4 py-2.5 text-[11px] font-medium text-neutral-500 md:min-w-[640px] md:grid-cols-[minmax(0,1fr)_11rem_6rem_9.5rem] dark:border-white/[0.08] dark:bg-white/[0.05] dark:text-fg-faint">
|
||||
<div>Server</div>
|
||||
<div class="hidden md:block">IP address</div>
|
||||
<div class="hidden md:block">Resources</div>
|
||||
<div>Status</div>
|
||||
</div>
|
||||
<template x-for="server in filteredServers" :key="server.uuid">
|
||||
<a :href="server.href" {{ wireNavigate() }}
|
||||
class="grid min-h-14 min-w-[480px] grid-cols-[minmax(0,1fr)_9.5rem] items-center border-b border-neutral-200 px-4 py-2.5 text-[12px] transition-colors last:border-b-0 hover:bg-neutral-50 hover:no-underline dark:border-white/[0.07] dark:hover:bg-white/[0.025]">
|
||||
class="grid min-h-14 min-w-[480px] grid-cols-[minmax(0,1fr)_9.5rem] items-center border-b border-neutral-200 px-4 py-2.5 text-[12px] transition-colors last:border-b-0 hover:bg-neutral-50 hover:no-underline md:min-w-[640px] md:grid-cols-[minmax(0,1fr)_11rem_6rem_9.5rem] dark:border-white/[0.07] dark:hover:bg-white/[0.025]">
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
class="flex size-8 shrink-0 items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 text-neutral-500 dark:border-white/[0.1] dark:bg-white/[0.035] dark:text-fg-dim">
|
||||
@@ -192,12 +196,27 @@
|
||||
</div>
|
||||
<span x-show="server.statusType !== 'success'" :data-tooltip="server.status"
|
||||
:aria-label="`Server status: ${server.status}`"
|
||||
class="ml-auto flex size-6 shrink-0 items-center justify-center rounded-md"
|
||||
class="ml-auto flex size-6 shrink-0 items-center justify-center rounded-md md:hidden"
|
||||
:class="server.statusType === 'warning' ? 'text-orange-500 dark:text-warning' : 'text-red-500 dark:text-red-400'">
|
||||
<x-reicon name="alert-triangle" class="size-4" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-[11px] font-medium text-neutral-600 dark:text-fg-dim">
|
||||
<div class="hidden min-w-0 items-center gap-1.5 truncate font-mono text-[12px] text-neutral-600 md:flex dark:text-fg-dim">
|
||||
<x-reicon name="network" class="size-3.5 shrink-0 text-neutral-400 dark:text-fg-faint" />
|
||||
<span class="truncate" x-text="server.ip"></span>
|
||||
</div>
|
||||
<div class="hidden text-[12px] font-medium text-neutral-600 md:block dark:text-fg-dim">
|
||||
<span class="inline-flex items-center gap-1" :title="`${server.resourceCount} ${server.resourceCount === 1 ? 'resource' : 'resources'}`">
|
||||
<x-reicon name="grid" class="size-3.5 text-neutral-400 dark:text-fg-faint" />
|
||||
<span x-text="server.resourceCount"></span>
|
||||
</span>
|
||||
</div>
|
||||
<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>
|
||||
<span x-text="server.status"></span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user