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>
217 lines
7.1 KiB
PHP
217 lines
7.1 KiB
PHP
<?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;
|
|
}
|
|
}
|