Replaces maryUI and daisyUI with nonameweb/livewire-material: the Vibrant indigo scheme, a system/light/dark theme under sealshare-theme, one top app bar with the account menu, the upload drop zone and link-ready moments, M3 fields, dialogs instead of wire:confirm, snackbars instead of flashed messages, a sortable admin table, and the starter-kit cleanup. Docker builds assets after Composer; CI drops the Flux step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
70 lines
4.2 KiB
PHP
70 lines
4.2 KiB
PHP
<div>
|
|
<h1 class="mb-6 type-headline-md">{{ __('Admin Dashboard') }}</h1>
|
|
|
|
<div class="mb-6 grid grid-cols-2 gap-3 md:grid-cols-4">
|
|
<x-stat :title="__('Total Shares')" :value="$totalShares" icon="link" />
|
|
<x-stat :title="__('Active Shares')" :value="$activeShares" icon="schedule" />
|
|
<x-stat :title="__('Total Files')" :value="$totalFiles" icon="description" />
|
|
<x-stat :title="__('Disk Usage')" :value="Number::fileSize($usedSpace)" icon="hard_drive" :description="Number::fileSize($usedSpace).' / '.Number::fileSize($maxQuota)">
|
|
<x-progress :value="$maxQuota > 0 ? min(100, ($usedSpace / $maxQuota) * 100) : 0" class="mt-2" :label="__('Disk Usage')" />
|
|
</x-stat>
|
|
</div>
|
|
|
|
<x-card :title="__('All Shares')" variant="outlined">
|
|
<div class="-mx-4 overflow-x-auto">
|
|
<x-table>
|
|
<thead>
|
|
<tr>
|
|
<x-sort-header column="token" :sort-by="$sortBy">{{ __('Token') }}</x-sort-header>
|
|
<x-sort-header column="files_count" :sort-by="$sortBy" class="text-end">{{ __('Files') }}</x-sort-header>
|
|
<x-sort-header column="total_size" :sort-by="$sortBy" class="text-end">{{ __('Size') }}</x-sort-header>
|
|
<x-sort-header column="download_count" :sort-by="$sortBy" class="text-end">{{ __('Downloads') }}</x-sort-header>
|
|
<x-sort-header column="expires_at" :sort-by="$sortBy">{{ __('Expires') }}</x-sort-header>
|
|
<x-sort-header column="created_at" :sort-by="$sortBy">{{ __('Created') }}</x-sort-header>
|
|
<th><span class="sr-only">{{ __('Actions') }}</span></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse ($shares as $share)
|
|
<tr wire:key="share-{{ $share->id }}">
|
|
<td class="font-mono">{{ $share->token }}</td>
|
|
<td class="text-end tabular-nums">{{ $share->files_count }}</td>
|
|
<td class="text-end tabular-nums whitespace-nowrap">{{ Number::fileSize($share->total_size) }}</td>
|
|
<td class="text-end tabular-nums">{{ $share->download_count }}</td>
|
|
<td class="whitespace-nowrap">
|
|
@if ($share->expires_at)
|
|
<span @class(['text-error' => $share->isExpired()])>{{ $share->expires_at->diffForHumans() }}</span>
|
|
@else
|
|
<span class="text-on-surface-variant">{{ __('Never') }}</span>
|
|
@endif
|
|
</td>
|
|
<td class="whitespace-nowrap">{{ $share->created_at->diffForHumans() }}</td>
|
|
<td class="text-end whitespace-nowrap">
|
|
<x-button icon="open_in_new" :tooltip="__('Open')" :link="route('share.download', $share)" external />
|
|
<x-button icon="delete" :tooltip="__('Delete')" color="error" wire:click="$set('deletingShareId', {{ $share->id }})" data-test="delete-share-{{ $share->id }}" />
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7">
|
|
<x-empty-state icon="link_off" :title="__('No shares yet')" :description="__('Shares appear here once someone uploads files.')" />
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</x-table>
|
|
</div>
|
|
|
|
<div class="mt-4">{{ $shares->links() }}</div>
|
|
</x-card>
|
|
|
|
<x-modal wire:model="deletingShareId" :title="__('Delete this share?')" icon="delete">
|
|
{{ __('Are you sure you want to delete this share?') }}
|
|
|
|
<x-slot:actions>
|
|
<x-button :label="__('Cancel')" x-on:click="close()" />
|
|
<x-button :label="__('Delete')" danger x-on:click="$wire.deleteShare($wire.deletingShareId)" data-test="confirm-delete-share" />
|
|
</x-slot:actions>
|
|
</x-modal>
|
|
</div>
|