Initial commit: Pireon Admin — headless admin panel and public API

Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer
collective, backed by the Pireon OAuth identity server. The public site is a
separate, server-rendered application consuming /api/public/v1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 17:55:27 +02:00
commit 3897113313
534 changed files with 64050 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Support\Health\Finding;
use App\Support\Health\HealthReport;
use App\Support\Health\Severity;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use UnitEnum;
/**
* Everything wrong with the public site, in one place.
*
* This page exists because the site is somewhere else. A missing translation,
* two records answering at the same URL, an image whose file is gone: none of
* it is visible where the content is edited. It shows up on the frontend,
* usually late, and usually to a visitor rather than to us.
*/
class SiteHealth extends Page
{
protected string $view = 'filament.pages.site-health';
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedShieldCheck;
protected static ?int $navigationSort = 88;
/**
* Not a public Livewire property: a Finding is a plain object, and
* Livewire can only carry types it knows how to serialise. It is
* recomputed per render instead, which is what opening this page is for.
*
* @var Collection<int, Finding>|null
*/
protected ?Collection $findings = null;
public static function canAccess(): bool
{
// Anyone who edits content benefits from seeing what is wrong with it.
// Individual checks hide themselves behind their own permission.
return Auth::user()?->can('panel.access') ?? false;
}
public static function getNavigationGroup(): string|UnitEnum|null
{
return __('panel.groups.administration');
}
public static function getNavigationLabel(): string
{
return __('health.title');
}
public function getTitle(): string|Htmlable
{
return __('health.title');
}
public function getSubheading(): string|Htmlable|null
{
return __('health.subheading');
}
/**
* @return Collection<int, Finding>
*/
public function findings(): Collection
{
return $this->findings ??= app(HealthReport::class)->runFor(Auth::user());
}
/**
* @return array<int, Action>
*/
protected function getHeaderActions(): array
{
return [
Action::make('refresh')
->label(__('health.actions.refresh'))
->icon('heroicon-o-arrow-path')
->color('gray')
->action(function (): void {
$this->refreshFindings();
Notification::make()
->title(__('health.refreshed'))
->success()
->send();
}),
];
}
public function refreshFindings(): void
{
// The badge is cached so that drawing it does not run ten checks on
// every page of the panel; opening this page is the one moment we
// definitely want fresh answers.
app(HealthReport::class)->forget(Auth::user());
$this->findings = null;
}
/**
* @return Collection<int, Finding>
*/
public function problems(): Collection
{
return $this->findings()->filter(fn (Finding $f) => $f->severity->isProblem());
}
/**
* @return Collection<int, Finding>
*/
public function passing(): Collection
{
return $this->findings()->reject(fn (Finding $f) => $f->severity->isProblem());
}
public function hasErrors(): bool
{
return $this->problems()->contains(fn (Finding $f) => $f->severity === Severity::Error);
}
/**
* A count in the sidebar, because a diagnostics page nobody opens
* diagnoses nothing.
*/
public static function getNavigationBadge(): ?string
{
$problems = app(HealthReport::class)->problemCount(Auth::user());
return $problems > 0 ? (string) $problems : null;
}
public static function getNavigationBadgeColor(): ?string
{
return 'warning';
}
}