Skip to main content

Infrastructure

Forge Disk Usage Card code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/forge-disk-usage-card.blade.php

@props([
    'path',
    'mount' => null,
    'filesystem' => null,
    'used' => null,
    'total' => null,
    'available' => null,
    'percent' => null,
    'inodesUsed' => null,
    'inodesTotal' => null,
    'status' => 'Healthy',
    'statusVariant' => 'green',
    'updatedAt' => null,
    'files' => [],
])

@php
    /*
    | Disk usage card for Forge-style server dashboards. Surfaces mount usage,
    | inode pressure, scan freshness and the largest files by size.
    |
    | Usage:
    |   <x-ui.forge-disk-usage-card
    |       path="/home/forge/app.example.com"
    |       mount="/dev/vda1"
    |       filesystem="ext4"
    |       used="48.2 GB"
    |       total="80 GB"
    |       available="31.8 GB"
    |       :percent="60"
    |       updated-at="2 minutes ago"
    |       :files="[
    |           ['path' => 'storage/logs/laravel.log', 'size' => '842 MB', 'modified' => 'Jun 24, 2026 09:12'],
    |       ]"
    |   />
    */
    $files = collect($files)->map(function (array $file): array {
        return [
            'path' => $file['path'] ?? $file['name'] ?? 'Unknown',
            'size' => $file['size'] ?? null,
            'modified' => $file['modified'] ?? $file['touchedAt'] ?? $file['lastModified'] ?? null,
            'href' => $file['href'] ?? null,
        ];
    })->values();

    $stats = array_filter([
        'Used' => $used,
        'Available' => $available,
        'Total' => $total,
    ], fn (?string $value): bool => filled($value));

    $meterPercent = $percent === null ? null : max(0, min(100, (int) $percent));
    $progressVariant = match (true) {
        $meterPercent === null => 'brand',
        $meterPercent >= 90 => 'red',
        $meterPercent >= 75 => 'amber',
        default => 'brand',
    };

    $mountLabel = collect([$mount, $filesystem])->filter()->implode(' · ');
    $usageLabelId = 'forge-disk-usage-'.substr(md5($path), 0, 8);
    $usageValueText = ($used && $total) ? "{$used} of {$total}" : ($meterPercent !== null ? "{$meterPercent} percent" : null);
@endphp

<article {{ $attributes->merge(['class' => 'min-w-0 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900']) }}>
    <div class="border-b border-gray-200 px-5 py-4 dark:border-gray-800 sm:px-6">
        <div class="flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4">
            <div class="min-w-0">
                <p class="truncate font-mono text-sm font-semibold text-gray-900 dark:text-white">{{ $path }}</p>
                @if ($mountLabel)
                    <p class="mt-1 truncate text-xs text-gray-500 dark:text-gray-400">{{ $mountLabel }}</p>
                @endif
            </div>

            <x-ui.badge :variant="$statusVariant" size="sm" dot class="w-fit sm:shrink-0" aria-label="Disk status: {{ $status }}">{{ $status }}</x-ui.badge>
        </div>

        @if (! empty($stats))
            <dl class="mt-5 grid gap-3 sm:grid-cols-3">
                @foreach ($stats as $label => $value)
                    <div class="min-w-0 rounded-lg bg-gray-50 p-3 dark:bg-gray-950/40">
                        <dt class="text-xs text-gray-500 dark:text-gray-400">{{ $label }}</dt>
                        <dd class="mt-1 truncate text-sm font-semibold tabular-nums text-gray-900 dark:text-white">{{ $value }}</dd>
                    </div>
                @endforeach
            </dl>
        @endif

        @if ($meterPercent !== null)
            <div class="mt-5">
                <div class="mb-1.5 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
                    <span id="{{ $usageLabelId }}" class="font-medium text-gray-700 dark:text-gray-300">Disk usage</span>
                    <span class="tabular-nums">
                        @if ($used && $total)
                            {{ $used }} of {{ $total }}
                        @else
                            {{ $meterPercent }}%
                        @endif
                    </span>
                </div>
                <x-ui.progress
                    :value="$meterPercent"
                    size="sm"
                    :variant="$progressVariant"
                    :aria-labelledby="$usageLabelId"
                    :aria-valuetext="$usageValueText"
                />
            </div>
        @endif

        <x-ui.status-bar class="mt-5" :items="array_values(array_filter([
            $updatedAt ? ['icon' => 'clock-counter-clockwise', 'label' => 'Updated', 'value' => $updatedAt] : null,
            filled($inodesUsed) && filled($inodesTotal) ? ['icon' => 'folders', 'label' => 'Inodes', 'value' => $inodesUsed.' / '.$inodesTotal] : null,
        ]))" />
    </div>

    @if ($files->isNotEmpty())
        <div class="overflow-x-auto">
            <table class="w-full min-w-[36rem] table-fixed divide-y divide-gray-200 text-sm dark:divide-gray-800">
                <caption class="sr-only">Largest files for {{ $path }}</caption>
                <thead class="bg-gray-50 text-xs text-gray-500 dark:bg-gray-950/40 dark:text-gray-400">
                    <tr>
                        <th scope="col" class="w-full px-5 py-3 text-left font-medium sm:px-6">Largest files</th>
                        <th scope="col" class="w-28 px-5 py-3 text-right font-medium whitespace-nowrap sm:px-6">Size</th>
                        <th scope="col" class="hidden w-44 px-5 py-3 text-right font-medium whitespace-nowrap sm:table-cell sm:px-6">Last touched</th>
                    </tr>
                </thead>
                <tbody class="divide-y divide-gray-200 dark:divide-gray-800">
                    @foreach ($files as $file)
                        <tr>
                            <td class="max-w-0 px-5 py-3 sm:px-6">
                                @if ($file['href'])
                                    <a href="{{ $file['href'] }}" class="block min-w-0 font-mono text-sm text-brand-600 hover:text-brand-700 dark:text-brand-400 dark:hover:text-brand-300">
                                        <span class="truncate">{{ $file['path'] }}</span>
                                    </a>
                                @else
                                    <p class="truncate font-mono text-sm text-gray-900 dark:text-white">{{ $file['path'] }}</p>
                                @endif
                                @if ($file['modified'])
                                    <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 sm:hidden">{{ $file['modified'] }}</p>
                                @endif
                            </td>
                            <td class="px-5 py-3 text-right font-medium tabular-nums text-gray-900 dark:text-white sm:px-6">{{ $file['size'] ?? '—' }}</td>
                            <td class="hidden px-5 py-3 text-right text-gray-500 dark:text-gray-400 sm:table-cell sm:px-6">{{ $file['modified'] ?? '—' }}</td>
                        </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
    @endif
</article>