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,216 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Models\Setting;
use App\Support\Maintenance;
use BackedEnum;
use Carbon\CarbonImmutable;
use Filament\Actions\Action;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Textarea;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Auth;
use UnitEnum;
/**
* Puts the **public site** into maintenance, without touching the panel.
*
* The distinction is the whole feature: `artisan down` would lock out the
* collective as well, exactly when they need to be fixing whatever went wrong.
* Here the team keeps editing, repositories keep syncing, and only visitors of
* the public site see the notice.
*/
class SiteMaintenance extends Page
{
protected string $view = 'filament.pages.site-maintenance';
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedWrenchScrewdriver;
protected static ?int $navigationSort = 90;
/**
* @var array<string, mixed>|null
*/
public ?array $data = [];
public static function canAccess(): bool
{
return Auth::user()?->can('settings.view') ?? false;
}
public static function getNavigationGroup(): string|UnitEnum|null
{
return __('panel.groups.administration');
}
public static function getNavigationLabel(): string
{
return __('maintenance.title');
}
public function getTitle(): string|Htmlable
{
return __('maintenance.title');
}
public function getSubheading(): string|Htmlable|null
{
return __('maintenance.subheading');
}
/**
* A red badge in the sidebar while the site is down — this is not
* something anyone should be able to forget about.
*/
public static function getNavigationBadge(): ?string
{
return Maintenance::isEnabled() ? __('maintenance.badge') : null;
}
public static function getNavigationBadgeColor(): ?string
{
return 'danger';
}
public function mount(): void
{
$this->form->fill([
'message' => Maintenance::messages(),
'until' => Maintenance::until()?->toDateTimeString(),
]);
}
public function form(Schema $schema): Schema
{
return $schema
->statePath('data')
->components([
Section::make(__('maintenance.notice'))
->description(__('maintenance.notice_hint'))
->schema([
Tabs::make('message')
->tabs(collect(config('locales.supported'))
->map(fn (array $meta, string $locale) => Tab::make($locale)
->label(trim(($meta['flag'] ?? '').' '.($meta['native'] ?? $locale)))
->schema([
Textarea::make("message.{$locale}")
->hiddenLabel()
->rows(3)
->placeholder(__('maintenance.default_message', [], $locale))
->maxLength(500),
]))
->values()
->all())
->columnSpanFull(),
DateTimePicker::make('until')
->label(__('maintenance.until'))
->helperText(__('maintenance.until_hint'))
->seconds(false)
->minDate(now()),
]),
]);
}
/**
* @return array<int, Action>
*/
protected function getHeaderActions(): array
{
return [
Action::make('enable')
->label(__('maintenance.enable'))
->icon('heroicon-o-exclamation-triangle')
->color('danger')
->requiresConfirmation()
->modalDescription(__('maintenance.enable_confirm'))
->visible(fn () => ! Maintenance::isEnabled() && $this->canToggle())
->action(function (): void {
$data = $this->form->getState();
Maintenance::enable(
message: array_filter($data['message'] ?? [], fn ($v) => filled($v)),
until: filled($data['until'] ?? null) ? CarbonImmutable::parse($data['until']) : null,
);
Notification::make()
->warning()
->title(__('maintenance.enabled_notice'))
->send();
}),
Action::make('disable')
->label(__('maintenance.disable'))
->icon('heroicon-o-check-circle')
->color('success')
->requiresConfirmation()
->visible(fn () => Maintenance::isEnabled() && $this->canToggle())
->action(function (): void {
Maintenance::disable();
Notification::make()
->success()
->title(__('maintenance.disabled_notice'))
->send();
}),
Action::make('save')
->label(__('maintenance.save_message'))
->color('gray')
->visible(fn () => $this->canToggle())
->action(function (): void {
$data = $this->form->getState();
$messages = array_filter($data['message'] ?? [], fn ($v) => filled($v));
// Saving the wording must not itself take the site down.
if (Maintenance::isEnabled()) {
Maintenance::enable(
message: $messages,
until: filled($data['until'] ?? null) ? CarbonImmutable::parse($data['until']) : null,
);
} else {
Setting::set(Maintenance::KEY, [
...Maintenance::state(),
'enabled' => false,
'message' => $messages,
]);
}
Notification::make()
->success()
->title(__('maintenance.message_saved'))
->send();
}),
];
}
public function isEnabled(): bool
{
return Maintenance::isEnabled();
}
public function startedAt(): ?string
{
return Maintenance::startedAt()?->diffForHumans();
}
public function endsAt(): ?string
{
return Maintenance::until()?->isoFormat('LLL');
}
public function canToggle(): bool
{
return Auth::user()?->can('settings.maintenance') ?? false;
}
}