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:
216
app/Filament/Pages/SiteMaintenance.php
Normal file
216
app/Filament/Pages/SiteMaintenance.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user