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>
84 lines
2.9 KiB
PHP
84 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\Share;
|
|
use App\Models\ShareFile;
|
|
use App\Services\ShareService;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
use Livewire\WithPagination;
|
|
|
|
#[Layout('layouts.app')]
|
|
class AdminDashboard extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
/**
|
|
* The orders the shares list offers, each a column and a direction.
|
|
*
|
|
* @var array<string, array{0: string, 1: string}>
|
|
*/
|
|
public const SORTS = [
|
|
'newest' => ['created_at', 'desc'],
|
|
'oldest' => ['created_at', 'asc'],
|
|
'expiring' => ['expires_at', 'asc'],
|
|
'largest' => ['total_size', 'desc'],
|
|
'most-downloaded' => ['download_count', 'desc'],
|
|
'most-files' => ['files_count', 'desc'],
|
|
];
|
|
|
|
public string $sort = 'newest';
|
|
|
|
/** The share the delete dialog is asking about, while it is open. */
|
|
public ?int $deletingShareId = null;
|
|
|
|
public function deleteShare(int $shareId, ShareService $shareService): void
|
|
{
|
|
$share = Share::query()->findOrFail($shareId);
|
|
$shareService->deleteShare($share);
|
|
|
|
$this->deletingShareId = null;
|
|
}
|
|
|
|
/**
|
|
* A new order starts again from the first page.
|
|
*/
|
|
public function updatedSort(): void
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function render(): mixed
|
|
{
|
|
$shareService = app(ShareService::class);
|
|
|
|
// The sort comes from the browser: only a known order reaches the query.
|
|
[$column, $direction] = self::SORTS[$this->sort] ?? self::SORTS['newest'];
|
|
|
|
// Shares whose files are still being uploaded are not shares yet; their bytes do count as used space.
|
|
$shares = Share::query()
|
|
->whereNotNull('completed_at')
|
|
->withCount('files')
|
|
// Shares that never expire come after every share that does, whichever way expiry is sorted.
|
|
->when($column === 'expires_at', fn ($query) => $query->orderByRaw('expires_at is null'))
|
|
->orderBy($column, $direction)
|
|
->orderByDesc('id')
|
|
->paginate(15);
|
|
|
|
return view('livewire.admin.admin-dashboard', [
|
|
'shares' => $shares,
|
|
'totalShares' => Share::query()->whereNotNull('completed_at')->count(),
|
|
'activeShares' => Share::query()->whereNotNull('completed_at')->where(function ($q) {
|
|
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
|
|
})->where(function ($q) {
|
|
$q->whereNull('max_downloads')->orWhereColumn('download_count', '<', 'max_downloads');
|
|
})->count(),
|
|
'totalFiles' => ShareFile::query()->whereHas('share', fn ($query) => $query->whereNotNull('completed_at'))->count(),
|
|
'usedSpace' => $shareService->getTotalUsedSpace(),
|
|
'maxQuota' => $shareService->getMaxStorageQuota(),
|
|
'version' => config('app.version'),
|
|
]);
|
|
}
|
|
}
|