mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-11 03:13:17 +00:00
feat(dashboard): refresh metrics and color server status indicators
Refresh visible dashboard metrics every minute and after backgrounding. Replace server status badges with accessible color-coded icon borders and status text.
This commit is contained in:
1 parent
3f47524baf
commit
7037633f45
6 files changed
+103
-28
No files matched your search
@@ -38,6 +38,7 @@ docker/coolify-realtime/node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
/.workspaces
|
/.workspaces
|
||||||
|
/.superpowers/
|
||||||
tests/Browser/Screenshots
|
tests/Browser/Screenshots
|
||||||
tests/v4/Browser/Screenshots
|
tests/v4/Browser/Screenshots
|
||||||
ref
|
ref
|
||||||
|
|||||||
@@ -187,8 +187,13 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="pointer-events-none relative z-10 flex min-w-0 items-start gap-3">
|
<div class="pointer-events-none relative z-10 flex min-w-0 items-start gap-3">
|
||||||
<div
|
<div title="{{ $serverStatus }}" aria-label="Server status: {{ $serverStatus }}"
|
||||||
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.08] dark:bg-white/[0.04] dark:text-fg-dim">
|
@class([
|
||||||
|
'flex size-8 shrink-0 items-center justify-center rounded-lg border bg-neutral-50 text-neutral-500 dark:bg-white/[0.04] dark:text-fg-dim',
|
||||||
|
'border-emerald-500/70' => $serverStatusType === 'success',
|
||||||
|
'border-amber-500/70' => $serverStatusType === 'warning',
|
||||||
|
'border-red-500/70' => $serverStatusType === 'error',
|
||||||
|
])>
|
||||||
<x-reicon name="servers" class="size-4" />
|
<x-reicon name="servers" class="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
@@ -201,12 +206,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@unless ($server->isMetricsEnabled())
|
|
||||||
<div class="pointer-events-none relative z-10 mt-auto flex items-center pt-4">
|
|
||||||
<x-status-badge :status="$serverStatus" :type="$serverStatusType" />
|
|
||||||
</div>
|
|
||||||
@endunless
|
|
||||||
</a>
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,37 @@
|
|||||||
<div
|
<div
|
||||||
class="absolute right-0 bottom-0 h-2/3 w-full rounded-b-xl [&_.apexcharts-svg]:overflow-hidden [&_.apexcharts-svg]:rounded-b-xl [&_.apexcharts-tooltip]:z-30!"
|
class="absolute right-0 bottom-0 h-2/3 w-full rounded-b-xl [&_.apexcharts-svg]:overflow-hidden [&_.apexcharts-svg]:rounded-b-xl [&_.apexcharts-tooltip]:z-30!"
|
||||||
x-init="$wire.loadData()">
|
x-data="{
|
||||||
|
hiddenAt: null,
|
||||||
|
refreshInterval: null,
|
||||||
|
visibilityHandler: null,
|
||||||
|
init() {
|
||||||
|
this.visibilityHandler = () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
this.hiddenAt = Date.now();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hiddenAt && Date.now() - this.hiddenAt >= 60000) {
|
||||||
|
$wire.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hiddenAt = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', this.visibilityHandler);
|
||||||
|
this.refreshInterval = window.setInterval(() => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
$wire.loadData();
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
$wire.loadData();
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
window.clearInterval(this.refreshInterval);
|
||||||
|
document.removeEventListener('visibilitychange', this.visibilityHandler);
|
||||||
|
},
|
||||||
|
}">
|
||||||
<div wire:ignore id="dashboard-server-metrics-{{ $server->uuid }}" class="h-full w-full"></div>
|
<div wire:ignore id="dashboard-server-metrics-{{ $server->uuid }}" class="h-full w-full"></div>
|
||||||
|
|
||||||
@script
|
@script
|
||||||
|
|||||||
@@ -41,14 +41,19 @@
|
|||||||
default => 'Validation required',
|
default => 'Validation required',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$statusType = match (true) {
|
||||||
|
$isReady => 'success',
|
||||||
|
$isTransferredAway || $server->settings->force_disabled => 'error',
|
||||||
|
default => 'warning',
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'uuid' => $server->uuid,
|
'uuid' => $server->uuid,
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
'description' => $server->description ?: 'No description',
|
'description' => $server->description ?: 'No description',
|
||||||
'href' => route('server.show', ['server_uuid' => $server->uuid]),
|
'href' => route('server.show', ['server_uuid' => $server->uuid]),
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
'statusType' => $isReady ? 'success' : 'error',
|
'statusType' => $statusType,
|
||||||
'ready' => $isReady,
|
|
||||||
];
|
];
|
||||||
})->values();
|
})->values();
|
||||||
@endphp
|
@endphp
|
||||||
@@ -121,8 +126,9 @@
|
|||||||
<a :href="server.href" {{ wireNavigate() }}
|
<a :href="server.href" {{ wireNavigate() }}
|
||||||
class="group relative flex min-h-28 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]">
|
class="group relative flex min-h-28 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<div
|
<div :title="server.status" :aria-label="`Server status: ${server.status}`"
|
||||||
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.08] dark:bg-white/[0.04] dark:text-fg-dim">
|
class="flex size-8 shrink-0 items-center justify-center rounded-lg border bg-neutral-50 text-neutral-500 dark:bg-white/[0.04] dark:text-fg-dim"
|
||||||
|
:class="server.statusType === 'success' ? 'border-emerald-500/70' : server.statusType === 'warning' ? 'border-amber-500/70' : 'border-red-500/70'">
|
||||||
<x-reicon name="servers" class="size-4" />
|
<x-reicon name="servers" class="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
@@ -132,13 +138,6 @@
|
|||||||
x-text="server.description"></p>
|
x-text="server.description"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-auto flex items-center pt-4">
|
|
||||||
<x-status-badge dynamic>
|
|
||||||
<span class="size-1.5 rounded-full"
|
|
||||||
:class="server.ready ? 'bg-emerald-500' : 'bg-red-500'"></span>
|
|
||||||
<span x-text="server.status"></span>
|
|
||||||
</x-status-badge>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,8 +153,9 @@
|
|||||||
<a :href="server.href" {{ wireNavigate() }}
|
<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 dark:border-white/[0.07] dark:hover:bg-white/[0.025]">
|
||||||
<div class="flex min-w-0 items-center gap-3">
|
<div class="flex min-w-0 items-center gap-3">
|
||||||
<div
|
<div :title="server.status" :aria-label="`Server status: ${server.status}`"
|
||||||
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.08] dark:bg-white/[0.035] dark:text-fg-dim">
|
class="flex size-8 shrink-0 items-center justify-center rounded-lg border bg-neutral-50 text-neutral-500 dark:bg-white/[0.035] dark:text-fg-dim"
|
||||||
|
:class="server.statusType === 'success' ? 'border-emerald-500/70' : server.statusType === 'warning' ? 'border-amber-500/70' : 'border-red-500/70'">
|
||||||
<x-reicon name="servers" class="size-4" />
|
<x-reicon name="servers" class="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
@@ -165,12 +165,8 @@
|
|||||||
x-text="server.description"></p>
|
x-text="server.description"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="text-[11px] font-medium text-neutral-600 dark:text-fg-dim">
|
||||||
<x-status-badge dynamic>
|
<span x-text="server.status"></span>
|
||||||
<span class="size-1.5 rounded-full"
|
|
||||||
:class="server.ready ? 'bg-emerald-500' : 'bg-red-500'"></span>
|
|
||||||
<span x-text="server.status"></span>
|
|
||||||
</x-status-badge>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -65,6 +65,20 @@ it('configures the dashboard chart as a ten minute cpu and memory sparkline with
|
|||||||
->toContain('labels: { show: false }');
|
->toContain('labels: { show: false }');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('refreshes dashboard metrics every minute while visible and after returning from the background', function () {
|
||||||
|
$chart = file_get_contents(resource_path('views/livewire/dashboard/server-metrics-chart.blade.php'));
|
||||||
|
|
||||||
|
expect($chart)
|
||||||
|
->toContain('window.setInterval')
|
||||||
|
->toContain('60000')
|
||||||
|
->toContain('document.hidden')
|
||||||
|
->toContain("document.addEventListener('visibilitychange'")
|
||||||
|
->toContain('Date.now() - this.hiddenAt >= 60000')
|
||||||
|
->toContain('$wire.loadData()')
|
||||||
|
->toContain('window.clearInterval')
|
||||||
|
->toContain("document.removeEventListener('visibilitychange'");
|
||||||
|
});
|
||||||
|
|
||||||
it('keeps the status badge only on server cards without metrics', function () {
|
it('keeps the status badge only on server cards without metrics', function () {
|
||||||
$dashboard = file_get_contents(resource_path('views/livewire/dashboard.blade.php'));
|
$dashboard = file_get_contents(resource_path('views/livewire/dashboard.blade.php'));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
test('server cards use icon borders instead of ready badges', function () {
|
||||||
|
$dashboard = file_get_contents(resource_path('views/livewire/dashboard.blade.php'));
|
||||||
|
$serverIndex = file_get_contents(resource_path('views/livewire/server/index.blade.php'));
|
||||||
|
|
||||||
|
expect($dashboard)
|
||||||
|
->not->toContain('<x-status-badge :status="$serverStatus"')
|
||||||
|
->toContain("'border-emerald-500/70' => \$serverStatusType === 'success'")
|
||||||
|
->toContain("'border-amber-500/70' => \$serverStatusType === 'warning'")
|
||||||
|
->toContain("'border-red-500/70' => \$serverStatusType === 'error'")
|
||||||
|
->toContain('title="{{ $serverStatus }}"')
|
||||||
|
->toContain('aria-label="Server status: {{ $serverStatus }}"');
|
||||||
|
|
||||||
|
expect(substr_count($serverIndex, '<x-status-badge'))->toBe(1)
|
||||||
|
->and($serverIndex)
|
||||||
|
->toContain("\$isReady => 'success'")
|
||||||
|
->toContain("\$isTransferredAway || \$server->settings->force_disabled => 'error'")
|
||||||
|
->toContain("default => 'warning'")
|
||||||
|
->toContain("server.statusType === 'success' ? 'border-emerald-500/70'")
|
||||||
|
->toContain("server.statusType === 'warning' ? 'border-amber-500/70'")
|
||||||
|
->toContain("'border-red-500/70'")
|
||||||
|
->toContain(':title="server.status"')
|
||||||
|
->toContain(':aria-label="`Server status: ${server.status}`"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('server table keeps status text without a badge', function () {
|
||||||
|
$serverIndex = file_get_contents(resource_path('views/livewire/server/index.blade.php'));
|
||||||
|
|
||||||
|
expect($serverIndex)
|
||||||
|
->toContain('<span x-text="server.status"></span>')
|
||||||
|
->toContain('text-[11px] font-medium')
|
||||||
|
->not->toContain('<x-status-badge dynamic>');
|
||||||
|
});
|
||||||
Reference in new issue
Block a user