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,138 @@
<x-filament-panels::page>
{{-- The headline. Whether anything is actually wrong is the one thing
somebody opening this page came to find out. --}}
@if ($this->problems()->isEmpty())
<div class="rounded-xl border border-success-300 bg-success-50 p-4 dark:border-success-700/50 dark:bg-success-500/10">
<div class="flex items-center gap-3">
<x-filament::icon
icon="heroicon-o-check-circle"
class="h-5 w-5 shrink-0 text-success-600 dark:text-success-400"
/>
<p class="font-semibold text-success-800 dark:text-success-300">
{{ __('health.all_clear') }}
</p>
</div>
</div>
@else
@php($tone = $this->hasErrors() ? 'danger' : 'warning')
<div @class([
'rounded-xl border p-4',
'border-danger-300 bg-danger-50 dark:border-danger-700/50 dark:bg-danger-500/10' => $tone === 'danger',
'border-warning-300 bg-warning-50 dark:border-warning-700/50 dark:bg-warning-500/10' => $tone === 'warning',
])>
<div class="flex items-start gap-3">
<x-filament::icon
icon="heroicon-o-exclamation-triangle"
@class([
'mt-0.5 h-5 w-5 shrink-0',
'text-danger-600 dark:text-danger-400' => $tone === 'danger',
'text-warning-600 dark:text-warning-400' => $tone === 'warning',
])
/>
<div class="space-y-1">
<p @class([
'font-semibold',
'text-danger-800 dark:text-danger-300' => $tone === 'danger',
'text-warning-800 dark:text-warning-300' => $tone === 'warning',
])>
{{ trans_choice('health.problem_summary', $this->problems()->count(), ['count' => $this->problems()->count()]) }}
</p>
<p @class([
'text-sm',
'text-danger-700 dark:text-danger-400' => $tone === 'danger',
'text-warning-700 dark:text-warning-400' => $tone === 'warning',
])>
{{ __('health.problem_hint') }}
</p>
</div>
</div>
</div>
@endif
{{-- Failing checks, worst first. --}}
@foreach ($this->problems() as $finding)
<x-filament::section>
<x-slot name="heading">
<span class="flex items-center gap-2">
<x-filament::icon
:icon="$finding->severity->getIcon()"
@class([
'h-5 w-5 shrink-0',
'text-danger-500' => $finding->severity->getColor() === 'danger',
'text-warning-500' => $finding->severity->getColor() === 'warning',
])
/>
{{ $finding->title() }}
<x-filament::badge :color="$finding->severity->getColor()" size="sm">
{{ $finding->count }}
</x-filament::badge>
</span>
</x-slot>
<div class="space-y-3">
<p class="text-sm text-gray-600 dark:text-gray-300">
{{ $finding->description() }}
</p>
{{-- Examples rather than only a number: "12 pages" is a
statistic, "12, starting with these" is something
somebody can act on. --}}
@if (filled($finding->samples))
<ul class="space-y-1 text-sm text-gray-500 dark:text-gray-400">
@foreach ($finding->samples as $sample)
<li class="flex items-start gap-2">
<span class="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-gray-400"></span>
<span>{{ $sample }}</span>
</li>
@endforeach
@if ($finding->count > count($finding->samples))
<li class="text-xs italic">
{{ __('health.and_more', ['count' => $finding->count - count($finding->samples)]) }}
</li>
@endif
</ul>
@endif
@if ($finding->url)
<x-filament::link :href="$finding->url" size="sm">
{{ __('health.actions.go_fix') }}
</x-filament::link>
@endif
</div>
</x-filament::section>
@endforeach
{{-- What passed, collapsed. Worth showing at all: it says what was
actually looked at, so a clean page is not mistaken for a page that
never ran. --}}
@if ($this->passing()->isNotEmpty())
<x-filament::section collapsible collapsed>
<x-slot name="heading">
{{ trans_choice('health.passing', $this->passing()->count(), ['count' => $this->passing()->count()]) }}
</x-slot>
<ul class="space-y-2">
@foreach ($this->passing() as $finding)
<li class="flex items-start gap-2 text-sm">
<x-filament::icon
icon="heroicon-o-check-circle"
class="mt-0.5 h-4 w-4 shrink-0 text-success-500"
/>
<span>
<span class="font-medium text-gray-700 dark:text-gray-200">{{ $finding->title() }}</span>
<span class="text-gray-500 dark:text-gray-400"> — {{ $finding->description() }}</span>
</span>
</li>
@endforeach
</ul>
</x-filament::section>
@endif
</x-filament-panels::page>

View File

@@ -0,0 +1,59 @@
<x-filament-panels::page>
{{-- Current state, stated plainly. Whether the public site is down is the
one thing this page must never leave ambiguous. --}}
@if ($this->isEnabled())
<div class="rounded-xl border border-danger-300 bg-danger-50 p-4 dark:border-danger-700/50 dark:bg-danger-500/10">
<div class="flex items-start gap-3">
<x-filament::icon
icon="heroicon-o-exclamation-triangle"
class="mt-0.5 h-5 w-5 shrink-0 text-danger-600 dark:text-danger-400"
/>
<div class="space-y-1">
<p class="font-semibold text-danger-800 dark:text-danger-300">
{{ __('maintenance.status_down') }}
</p>
<p class="text-sm text-danger-700 dark:text-danger-400">
@if ($this->startedAt())
{{ __('maintenance.since', ['time' => $this->startedAt()]) }}
@endif
@if ($this->endsAt())
· {{ __('maintenance.lifts_at', ['time' => $this->endsAt()]) }}
@endif
</p>
<p class="text-sm text-danger-700 dark:text-danger-400">
{{ __('maintenance.panel_unaffected') }}
</p>
</div>
</div>
</div>
@else
<div class="rounded-xl border border-success-300 bg-success-50 p-4 dark:border-success-700/50 dark:bg-success-500/10">
<div class="flex items-center gap-3">
<x-filament::icon
icon="heroicon-o-check-circle"
class="h-5 w-5 shrink-0 text-success-600 dark:text-success-400"
/>
<p class="font-semibold text-success-800 dark:text-success-300">
{{ __('maintenance.status_up') }}
</p>
</div>
</div>
@endif
{{ $this->form }}
<div class="text-sm text-gray-500 dark:text-gray-400">
<p>{{ __('maintenance.bypass_hint') }}</p>
</div>
@unless ($this->canToggle())
<div class="rounded-lg border border-gray-200 p-4 text-sm text-gray-500 dark:border-white/10 dark:text-gray-400">
{{ __('maintenance.no_permission') }}
</div>
@endunless
</x-filament-panels::page>

View File

@@ -0,0 +1,57 @@
<x-filament-panels::page>
@php($groups = $this->groups())
@if ($groups->isEmpty())
<div class="rounded-xl border border-gray-200 p-6 text-center dark:border-white/10">
<p class="text-sm text-gray-500 dark:text-gray-400">
{{ __('panel.trash.empty') }}
</p>
</div>
@else
@foreach ($groups as $group)
<x-filament::section :heading="$group['label']">
<x-slot name="headerEnd">
<x-filament::badge color="gray">{{ $group['records']->count() }}</x-filament::badge>
</x-slot>
<ul class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($group['records'] as $record)
<li class="flex flex-wrap items-center justify-between gap-3 py-2.5">
<div class="min-w-0">
<p class="truncate text-sm font-medium text-gray-800 dark:text-gray-100">
{{ $this->labelFor($record) }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ __('panel.trash.deleted_at', ['when' => $record->deleted_at?->diffForHumans()]) }}
</p>
</div>
<div class="flex shrink-0 items-center gap-2">
<x-filament::button
size="sm"
color="success"
icon="heroicon-o-arrow-uturn-left"
wire:click="restoreRecord(@js($group['model']), @js($record->getKey()))"
>
{{ __('panel.trash.restore') }}
</x-filament::button>
{{-- Erasing is the one thing here that cannot be
undone, so it asks first. --}}
<x-filament::button
size="sm"
color="danger"
icon="heroicon-o-trash"
wire:click="eraseRecord(@js($group['model']), @js($record->getKey()))"
wire:confirm="{{ __('panel.trash.erase_confirm') }}"
>
{{ __('panel.trash.erase') }}
</x-filament::button>
</div>
</li>
@endforeach
</ul>
</x-filament::section>
@endforeach
@endif
</x-filament-panels::page>