A quiet line under the shares card reads "SealShare 2.1.0 · Release notes · Website · Made by noNameWEB": the release notes link goes to the installed version's Gitea release, the others to sealshare.nonameweb.ch and nonameweb.ch, each in a new tab. It is page chrome, not content, so it is a footer line rather than a card. The image has neither .git nor CHANGELOG.md, so the version is a constant in config/app.php, bumped with each release. AppVersionTest fails while it differs from the newest released heading in CHANGELOG.md, so a forgotten bump stops CI before the tag's image is built. CHANGELOG (Unreleased) notes it. The dashboard screenshots are unchanged: the line sits below their fold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
83 lines
5.6 KiB
PHP
83 lines
5.6 KiB
PHP
<x-page :title="__('Admin Dashboard')" :description="__('Shares, files and storage at a glance')">
|
|
<x-grid :columns="2" gap="space200">
|
|
<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" :label="__('Disk Usage')" />
|
|
</x-stat>
|
|
</x-grid>
|
|
|
|
{{-- The shares as a list, not a table: a table's columns need more than the page's 40rem, and
|
|
every page keeps that one width. The sort is a full-width select above the list instead of column headers. --}}
|
|
<x-card :title="__('All Shares')" heading="h2" variant="outlined">
|
|
<x-stack gap="space200">
|
|
@if ($shares->total() === 0)
|
|
<x-empty-state icon="link_off" :title="__('No shares yet')" :description="__('Shares appear here once someone uploads files.')" />
|
|
@else
|
|
<x-select
|
|
wire:model.live="sort"
|
|
:label="__('Sort by')"
|
|
:options="[
|
|
['id' => 'newest', 'name' => __('Newest first')],
|
|
['id' => 'oldest', 'name' => __('Oldest first')],
|
|
['id' => 'expiring', 'name' => __('Expiring soonest')],
|
|
['id' => 'largest', 'name' => __('Largest')],
|
|
['id' => 'most-downloaded', 'name' => __('Most downloads')],
|
|
['id' => 'most-files', 'name' => __('Most files')],
|
|
]"
|
|
data-test="shares-sort"
|
|
/>
|
|
|
|
{{-- Each share fits the column on a phone: the token opens it, so delete is the one button;
|
|
the details are two short lines that never clip (admin-shares, app.css). --}}
|
|
<x-list dividers :label="__('All Shares')" class="admin-shares">
|
|
@foreach ($shares as $share)
|
|
<x-list-item :overline="__('Created :time', ['time' => $share->created_at->diffForHumans()])" wire:key="share-{{ $share->id }}" data-test="share-row">
|
|
<a href="{{ route('share.download', $share) }}" target="_blank" rel="noopener" class="md-link"><code>{{ $share->token }}</code></a>
|
|
|
|
<x-slot:description>
|
|
<span class="admin-share-detail md-tabular">{{ trans_choice(':count file|:count files', $share->files_count) }} · {{ Number::fileSize($share->total_size) }} · {{ $share->max_downloads ? trans_choice(':count of :max download|:count of :max downloads', $share->max_downloads, ['count' => $share->download_count, 'max' => $share->max_downloads]) : trans_choice(':count download|:count downloads', $share->download_count) }}</span>
|
|
|
|
{{-- A share at its limit is closed; the cleanup deletes it a day after its last download. --}}
|
|
@if ($share->hasReachedDownloadLimit())
|
|
<span class="admin-share-detail md-ink-error">{{ __('Download limit reached') }}</span>
|
|
@elseif (! $share->expires_at)
|
|
<span class="admin-share-detail">{{ __('Never expires') }}</span>
|
|
@elseif ($share->isExpired())
|
|
<span class="admin-share-detail md-ink-error">{{ __('Expired :time', ['time' => $share->expires_at->diffForHumans()]) }}</span>
|
|
@else
|
|
<span class="admin-share-detail">{{ __('Expires :time', ['time' => $share->expires_at->diffForHumans()]) }}</span>
|
|
@endif
|
|
</x-slot:description>
|
|
|
|
<x-slot:end>
|
|
<x-button icon="delete" :tooltip="__('Delete')" color="error" wire:click="$set('deletingShareId', {{ $share->id }})" data-test="delete-share-{{ $share->id }}" />
|
|
</x-slot:end>
|
|
</x-list-item>
|
|
@endforeach
|
|
</x-list>
|
|
|
|
{{ $shares->links() }}
|
|
@endif
|
|
</x-stack>
|
|
</x-card>
|
|
|
|
{{-- Page chrome, not content, so a quiet line instead of a card: the installed version, its release notes and who makes SealShare. --}}
|
|
<footer class="md-type-body-sm md-ink-variant md-text-center" data-test="dashboard-about">
|
|
SealShare {{ $version }}
|
|
· <a href="https://gitea.nonameweb.ch/noNameWEB/SealShare/releases/tag/v{{ $version }}" target="_blank" rel="noopener" class="md-link md-ink-primary">{{ __('Release notes') }}</a>
|
|
· <a href="https://sealshare.nonameweb.ch" target="_blank" rel="noopener" class="md-link md-ink-primary">{{ __('Website') }}</a>
|
|
· {{ __('Made by') }} <a href="https://nonameweb.ch" target="_blank" rel="noopener" class="md-link md-ink-primary">noNameWEB</a>
|
|
</footer>
|
|
|
|
<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>
|
|
</x-page>
|