Infrastructure
Process List code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/process-list.blade.php
@props([
'output' => null,
'rows' => [],
'title' => 'Processes by CPU',
'description' => null,
'command' => 'ps auxww --sort=-%cpu',
'capturedAt' => null,
'host' => null,
'limit' => null,
'emptyTitle' => 'No process output',
'emptyDescription' => 'Capture ps output and pass it to the component to inspect running processes.',
])
@php
/*
| Process list viewer for `ps auxww --sort=-%cpu` output. It accepts either
| the raw command output or normalized row arrays. Shell execution stays with
| the caller, so this component remains a safe display primitive.
*/
$parseProcessOutput = function (?string $rawOutput): array {
if (! is_string($rawOutput) || trim($rawOutput) === '') {
return [];
}
$lines = preg_split('/\R/', trim($rawOutput)) ?: [];
$processes = [];
foreach ($lines as $index => $line) {
$line = trim($line);
if ($line === '' || ($index === 0 && str_starts_with($line, 'USER'))) {
continue;
}
$columns = preg_split('/\s+/', $line, 11);
if (! is_array($columns) || count($columns) < 10) {
continue;
}
$processes[] = [
'user' => $columns[0] ?? '',
'pid' => $columns[1] ?? '',
'cpu' => (float) ($columns[2] ?? 0),
'memory' => (float) ($columns[3] ?? 0),
'vsz' => $columns[4] ?? '',
'rss' => $columns[5] ?? '',
'tty' => $columns[6] ?? '',
'stat' => $columns[7] ?? '',
'started' => $columns[8] ?? '',
'time' => $columns[9] ?? '',
'command' => $columns[10] ?? '',
];
}
return $processes;
};
$normalizeProcess = function (array $process): array {
return [
'user' => (string) ($process['user'] ?? $process['USER'] ?? ''),
'pid' => (string) ($process['pid'] ?? $process['PID'] ?? ''),
'cpu' => (float) ($process['cpu'] ?? $process['%cpu'] ?? $process['%CPU'] ?? 0),
'memory' => (float) ($process['memory'] ?? $process['mem'] ?? $process['%mem'] ?? $process['%MEM'] ?? 0),
'vsz' => (string) ($process['vsz'] ?? $process['VSZ'] ?? ''),
'rss' => (string) ($process['rss'] ?? $process['RSS'] ?? ''),
'tty' => (string) ($process['tty'] ?? $process['TTY'] ?? ''),
'stat' => (string) ($process['stat'] ?? $process['STAT'] ?? ''),
'started' => (string) ($process['started'] ?? $process['start'] ?? $process['START'] ?? ''),
'time' => (string) ($process['time'] ?? $process['TIME'] ?? ''),
'command' => (string) ($process['command'] ?? $process['COMMAND'] ?? ''),
];
};
$processes = collect(! empty($rows) ? $rows : $parseProcessOutput($output))
->filter(fn ($process) => is_array($process))
->map($normalizeProcess)
->when(is_numeric($limit) && (int) $limit > 0, fn ($items) => $items->take((int) $limit))
->values();
$formatPercent = function (float $value): string {
$formatted = number_format($value, $value >= 10 || floor($value) === $value ? 0 : 1);
return "{$formatted}%";
};
$formatKilobytes = function (string $value): string {
if (! is_numeric($value)) {
return $value;
}
$kilobytes = (float) $value;
if ($kilobytes >= 1048576) {
return number_format($kilobytes / 1048576, 1).' GB';
}
if ($kilobytes >= 1024) {
return number_format($kilobytes / 1024, 1).' MB';
}
return number_format($kilobytes).' KB';
};
$cpuVariant = function (float $cpu): string {
return match (true) {
$cpu >= 75 => 'red',
$cpu >= 40 => 'amber',
$cpu >= 10 => 'blue',
default => 'gray',
};
};
$buildProcessDetails = function (array $process) use ($formatPercent, $formatKilobytes): array {
$details = [
'PID' => $process['pid'],
'User' => $process['user'],
'CPU' => $formatPercent((float) $process['cpu']),
'Memory' => $formatPercent((float) $process['memory']),
'VSZ' => $process['vsz'] !== '' ? $formatKilobytes($process['vsz']) : null,
'RSS' => $process['rss'] !== '' ? $formatKilobytes($process['rss']) : null,
'TTY' => $process['tty'],
'State' => $process['stat'],
'Started' => $process['started'] !== '' ? $process['started'] : null,
'Time' => $process['time'],
];
return array_filter(
$details,
fn (?string $value): bool => is_string($value) && $value !== '',
);
};
$modalPrefix = 'process-'.substr(md5(json_encode([
$command,
$capturedAt,
$host,
$processes->pluck('pid')->all(),
])), 0, 10);
$visibleProcesses = $processes->count();
$totalCpu = $processes->sum('cpu');
$totalMemory = $processes->sum('memory');
$topProcess = $processes->first();
@endphp
<section {{ $attributes->merge(['class' => 'overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900']) }}>
<div class="flex flex-col gap-4 px-5 py-4 sm:flex-row sm:items-start sm:justify-between sm:px-6">
<div class="min-w-0">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">{{ $title }}</h3>
@if ($description)
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ $description }}</p>
@endif
</div>
<div class="flex shrink-0 flex-wrap items-center gap-2">
<x-ui.badge variant="gray" :pill="false" icon="terminal-window" icon-weight="regular">
<span class="font-mono">{{ $command }}</span>
</x-ui.badge>
@if ($capturedAt)
<x-ui.badge variant="gray" :pill="false" icon="clock" icon-weight="regular">{{ $capturedAt }}</x-ui.badge>
@endif
</div>
</div>
@if ($visibleProcesses > 0)
<div class="grid gap-4 border-b border-gray-200 px-5 py-4 dark:border-gray-800 sm:grid-cols-2 lg:grid-cols-4 sm:px-6">
<div class="min-w-0">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">Processes</p>
<p class="mt-1 text-lg font-semibold tabular-nums text-gray-900 dark:text-white">{{ number_format($visibleProcesses) }}</p>
</div>
<div class="min-w-0">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">Visible CPU</p>
<p class="mt-1 text-lg font-semibold tabular-nums text-gray-900 dark:text-white">{{ $formatPercent($totalCpu) }}</p>
</div>
<div class="min-w-0">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">Visible memory</p>
<p class="mt-1 text-lg font-semibold tabular-nums text-gray-900 dark:text-white">{{ $formatPercent($totalMemory) }}</p>
</div>
<div class="min-w-0">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ $host ? 'Host' : 'Top command' }}</p>
<p class="mt-1 truncate text-sm font-semibold text-gray-900 dark:text-white" title="{{ $host ?: ($topProcess['command'] ?? '') }}">
{{ $host ?: \Illuminate\Support\Str::limit($topProcess['command'] ?? 'Unknown', 44) }}
</p>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-[58rem] divide-y divide-gray-200 text-sm dark:divide-gray-800">
<caption class="sr-only">{{ $title }} process list</caption>
<thead class="bg-gray-50 dark:bg-gray-900/60">
<tr>
<th scope="col" class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 sm:px-6">PID</th>
<th scope="col" class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">User</th>
<th scope="col" class="px-5 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">CPU</th>
<th scope="col" class="px-5 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Memory</th>
<th scope="col" class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">State</th>
<th scope="col" class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Time</th>
<th scope="col" class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 sm:px-6">Command</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-800 dark:bg-gray-900">
@foreach ($processes as $process)
@php
$cpu = (float) $process['cpu'];
$memory = (float) $process['memory'];
$cpuWidth = max(1, min(100, (int) round($cpu)));
$memoryWidth = max(1, min(100, (int) round($memory)));
$modalName = $modalPrefix.'-'.$process['pid'];
@endphp
<tr class="transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/40">
<td class="whitespace-nowrap px-5 py-4 sm:px-6">
<button
type="button"
class="font-mono text-xs tabular-nums text-brand-600 underline-offset-2 transition hover:text-brand-500 hover:underline dark:text-brand-400"
aria-label="View details for process {{ $process['pid'] }}"
x-on:click="$dispatch('open-modal', @js($modalName))"
>
{{ $process['pid'] }}
</button>
</td>
<td class="whitespace-nowrap px-5 py-4 text-gray-700 dark:text-gray-200">{{ $process['user'] }}</td>
<td class="whitespace-nowrap px-5 py-4 text-right">
<div class="ml-auto flex w-28 items-center justify-end gap-2">
<div class="h-1.5 w-14 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800" role="presentation" aria-hidden="true">
<div class="h-full rounded-full bg-brand-500 dark:bg-brand-400" style="width: {{ $cpuWidth }}%"></div>
</div>
<x-ui.badge :variant="$cpuVariant($cpu)" size="sm" :pill="false" aria-label="CPU usage {{ $formatPercent($cpu) }}">{{ $formatPercent($cpu) }}</x-ui.badge>
</div>
</td>
<td class="whitespace-nowrap px-5 py-4 text-right">
<div class="ml-auto flex w-32 items-center justify-end gap-2">
<div class="h-1.5 w-14 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800" role="presentation" aria-hidden="true">
<div class="h-full rounded-full bg-gray-400 dark:bg-gray-500" style="width: {{ $memoryWidth }}%"></div>
</div>
<span class="font-mono text-xs tabular-nums text-gray-600 dark:text-gray-300" aria-label="Memory usage {{ $formatPercent($memory) }}">{{ $formatPercent($memory) }}</span>
</div>
</td>
<td class="whitespace-nowrap px-5 py-4 text-gray-500 dark:text-gray-400">
<span class="font-mono text-xs">{{ $process['stat'] }}</span>
<span class="ml-2 text-xs">{{ $process['tty'] }}</span>
</td>
<td class="whitespace-nowrap px-5 py-4 font-mono text-xs text-gray-500 dark:text-gray-400">
<span>{{ $process['time'] }}</span>
@if ($process['rss'] !== '')
<span class="ml-2 text-gray-400 dark:text-gray-500">{{ $formatKilobytes($process['rss']) }} RSS</span>
@endif
</td>
<td class="max-w-md px-5 py-4 sm:px-6">
<code class="block truncate font-mono text-xs text-gray-800 dark:text-gray-100" title="{{ $process['command'] }}">
{{ $process['command'] }}
</code>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@foreach ($processes as $process)
@php
$modalName = $modalPrefix.'-'.$process['pid'];
$processDetails = $buildProcessDetails($process);
@endphp
<x-ui.modal
:name="$modalName"
:title="'Process '.$process['pid']"
:description="\Illuminate\Support\Str::limit($process['command'], 72)"
maxWidth="lg"
>
<x-ui.description-list :items="[]">
@foreach ($processDetails as $label => $value)
<x-ui.description-list.item :label="$label">{{ $value }}</x-ui.description-list.item>
@endforeach
<x-ui.description-list.item label="Command">
<code class="block break-all rounded-md bg-gray-100 px-3 py-2 font-mono text-xs text-gray-800 dark:bg-gray-800 dark:text-gray-100">{{ $process['command'] }}</code>
</x-ui.description-list.item>
</x-ui.description-list>
<x-slot:footer>
<x-ui.button variant="outline" type="button" @click="close()">
Close
</x-ui.button>
</x-slot:footer>
</x-ui.modal>
@endforeach
@else
<x-ui.empty-state :title="$emptyTitle" :description="$emptyDescription" icon="terminal-window" class="rounded-none border-0 shadow-none" />
@endif
</section>