fix(metrics): show all container metrics in one table

This commit is contained in:
Andras Bacsai
2026-10-24 23:48:36 +02:00
parent 7654e8d3a9
commit 86a11d6992
3 changed files with 41 additions and 45 deletions
+2 -1
View File
@@ -3695,7 +3695,8 @@ input[type="search"]::-webkit-search-results-decoration {
}
.fleet-containers-table-grid {
grid-template-columns: 2.25rem minmax(9rem, 1.7fr) minmax(6rem, 0.9fr) minmax(8.5rem, 1fr);
grid-template-columns: 2.25rem minmax(9rem, 1.7fr) minmax(6rem, 0.9fr) repeat(4, minmax(7rem, 1fr));
min-width: 54rem;
}
.scheduled-executions-table-grid {
@@ -276,22 +276,7 @@ $serverListboxOptions = array_merge(
{{-- Hottest containers --}}
<x-application.settings-section id="metrics-containers-section" title="Hottest containers" flush
helper="{{ $scoped ? 'Busiest containers on this server, ranked by the selected metric.' : 'Busiest containers across the fleet, ranked by the selected metric.' }}">
<x-slot:actions>
<div class="flex items-center gap-2">
<x-loading compact class="text-neutral-400 dark:text-fg-faint"
wire:loading.flex wire:target="containerMetric" />
<div class="inline-flex items-center gap-0.5 rounded-lg bg-neutral-100 p-1 dark:bg-white/[0.04]">
@foreach (['cpu' => 'CPU', 'memory' => 'Memory', 'disk' => 'Disk', 'network' => 'Network'] as $m => $mlabel)
<button type="button" wire:click="$set('containerMetric', '{{ $m }}')"
wire:loading.attr="disabled" wire:target="containerMetric"
@class([$tabButtonBase, $containerMetric === $m ? $tabButtonActive : $tabButtonInactive])>
{{ $mlabel }}
</button>
@endforeach
</div>
</div>
</x-slot:actions>
helper="{{ $scoped ? 'Busiest containers on this server, sortable by resource usage.' : 'Busiest containers across the fleet, sortable by resource usage.' }}">
@if (empty($topContainers))
<div class="p-4">
<x-empty size="sm" title="No container metrics"
@@ -299,37 +284,28 @@ $serverListboxOptions = array_merge(
</div>
@else
@php
$metricOf = fn ($c) => match ($containerMetric) {
'memory' => $c['memUsed'] ?? 0,
'disk' => $c['diskBytes'] ?? 0,
'network' => $c['net'] ?? 0,
default => $c['cpu'] ?? 0,
};
// Bars are scaled to the leader so the row length reads as "share of the hottest".
$maxMetric = max(1, collect($topContainers)->map($metricOf)->max());
$containerMetrics = ['cpu' => 'CPU', 'memUsed' => 'Memory', 'diskBytes' => 'Disk', 'net' => 'Network'];
$maxMetrics = [];
foreach ($containerMetrics as $key => $label) {
$maxMetrics[$key] = max(1, collect($topContainers)->max($key) ?? 0);
}
@endphp
<div class="data-table transition-opacity" wire:loading.class="pointer-events-none opacity-50"
wire:target="containerMetric" x-data="trafficTable({ per: Number.MAX_SAFE_INTEGER, key: 'value' })">
<div class="data-table" x-data="trafficTable({ per: Number.MAX_SAFE_INTEGER, key: 'cpu' })">
<div class="data-table-header fleet-containers-table-grid border-b-0!">
<span class="text-center">#</span>
@include('livewire.traffic._sort-header', ['column' => 'name', 'label' => 'Container', 'text' => true, 'class' => 'justify-self-start'])
@include('livewire.traffic._sort-header', ['column' => 'server', 'label' => 'Server', 'text' => true, 'class' => 'justify-self-start'])
@include('livewire.traffic._sort-header', ['column' => 'value', 'label' => ['cpu' => 'CPU', 'memory' => 'Memory', 'disk' => 'Disk', 'network' => 'Network'][$containerMetric], 'class' => 'justify-self-end'])
@foreach ($containerMetrics as $key => $label)
@include('livewire.traffic._sort-header', ['column' => $key, 'label' => $label, 'class' => 'justify-self-end'])
@endforeach
</div>
<div class="flex flex-col">
@foreach ($topContainers as $c)
@php
$meta = $this->containerMeta($c['id']);
$pct = min(100, round($metricOf($c) / $maxMetric * 100, 1));
$display = match ($containerMetric) {
'memory' => formatBytes($c['memUsed'] ?? 0),
'disk' => formatBytes($c['diskBytes'] ?? 0),
'network' => formatBytes($c['net'] ?? 0).'/s',
default => round($c['cpu'] ?? 0).'%',
};
@endphp
<div wire:key="fleet-container-{{ $c['serverUuid'] ?? $c['server'] }}-{{ $c['id'] }}"
data-row="{{ json_encode(['name' => $meta['name'], 'server' => $c['server'], 'value' => $metricOf($c)]) }}"
data-row="{{ json_encode(['name' => $meta['name'], 'server' => $c['server'], 'cpu' => $c['cpu'] ?? 0, 'memUsed' => $c['memUsed'] ?? 0, 'diskBytes' => $c['diskBytes'] ?? 0, 'net' => $c['net'] ?? 0]) }}"
x-show="shown($el)"
class="data-table-row fleet-containers-table-grid border-t border-neutral-200 dark:border-white/[0.07]">
<div class="self-center text-center text-[12px] tabular-nums text-neutral-400 dark:text-fg-faint">{{ $loop->iteration }}</div>
@@ -344,13 +320,23 @@ $serverListboxOptions = array_merge(
@endif
</div>
<div class="min-w-0 self-center truncate text-[12px] text-neutral-500 dark:text-fg-dim">{{ $c['server'] }}</div>
<div class="flex items-center justify-end gap-2.5 self-center">
<div class="hidden h-1.5 w-full max-w-[6rem] overflow-hidden rounded-full bg-neutral-100 sm:block dark:bg-white/[0.06]"
title="{{ $pct }}% of the busiest">
<div class="h-full rounded-full bg-[var(--chart-status-3xx)] transition-[width] duration-700 ease-out" style="width: {{ $pct }}%"></div>
@foreach ($containerMetrics as $key => $label)
@php
$pct = min(100, round(($c[$key] ?? 0) / $maxMetrics[$key] * 100, 1));
$display = match ($key) {
'cpu' => round($c[$key] ?? 0).'%',
'net' => formatBytes($c[$key] ?? 0).'/s',
default => formatBytes($c[$key] ?? 0),
};
@endphp
<div class="flex items-center justify-end gap-2.5 self-center">
<div class="hidden h-1.5 w-10 shrink-0 overflow-hidden rounded-full bg-neutral-100 xl:block dark:bg-white/[0.06]"
title="{{ $pct }}% of the busiest">
<div class="h-full rounded-full bg-[var(--chart-status-3xx)] transition-[width] duration-700 ease-out" style="width: {{ $pct }}%"></div>
</div>
<span class="whitespace-nowrap text-right text-[13px] tabular-nums text-black dark:text-fg">{{ $display }}</span>
</div>
<span class="w-20 shrink-0 whitespace-nowrap text-right text-[13px] tabular-nums text-black dark:text-fg">{{ $display }}</span>
</div>
@endforeach
</div>
@endforeach
</div>
+12 -3
View File
@@ -34,8 +34,8 @@ class FakeBrowserFleetMetricsClient extends SentinelMetricsClient
public function containersCurrent(): array
{
return match ($this->server->name) {
'alpha' => [['id' => 'zeta', 'cpu' => 70.0, 'memUsed' => 10, 'memPercent' => 1.0, 'diskBytes' => 1, 'net' => 0.0]],
'bravo' => [['id' => 'beta', 'cpu' => 5.0, 'memUsed' => 20, 'memPercent' => 2.0, 'diskBytes' => 2, 'net' => 0.0]],
'alpha' => [['id' => 'zeta', 'cpu' => 70.0, 'memUsed' => 10, 'memPercent' => 1.0, 'diskBytes' => 1, 'net' => 30.0]],
'bravo' => [['id' => 'beta', 'cpu' => 5.0, 'memUsed' => 20, 'memPercent' => 2.0, 'diskBytes' => 2, 'net' => 10.0]],
default => [],
};
}
@@ -111,7 +111,8 @@ it('sorts the servers and containers tables when a column header is selected', f
expect(visibleFleetMetricsRowNames($page, 'metrics-servers-section'))->toBe(['bravo', 'alpha', 'charlie']);
// A data refresh keeps the sort.
$page->click('button:visible:has-text("7 days")')
$page->click('button:visible:has-text("Last 24 hours")')
->click('[role="option"]:has-text("Last 7 days")')
->assertQueryStringHas('range', '7d');
expect(visibleFleetMetricsRowNames($page, 'metrics-servers-section'))->toBe(['bravo', 'alpha', 'charlie']);
@@ -120,6 +121,14 @@ it('sorts the servers and containers tables when a column header is selected', f
$page->script('document.querySelector(\'#metrics-containers-section button[title="Sort by container"]\').click()');
expect(visibleFleetMetricsRowNames($page, 'metrics-containers-section'))->toBe(['beta', 'zeta']);
foreach (['memory' => ['beta', 'zeta'], 'disk' => ['beta', 'zeta'], 'network' => ['zeta', 'beta'], 'cpu' => ['zeta', 'beta']] as $metric => $expected) {
$page->script("document.querySelector('#metrics-containers-section button[title=\"Sort by {$metric}\"]').click()");
expect(visibleFleetMetricsRowNames($page, 'metrics-containers-section'))->toBe($expected);
$page->script("document.querySelector('#metrics-containers-section button[title=\"Sort by {$metric}\"]').click()");
expect(visibleFleetMetricsRowNames($page, 'metrics-containers-section'))->toBe(array_reverse($expected));
}
$page->assertNoJavaScriptErrors()
->screenshot(filename: 'fleet-metrics-tables-sorted');
});