341 lines
16 KiB
PHP
341 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Helper\ColorHelper;
|
|
use BackedEnum;
|
|
use Filament\Actions\Action;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Forms\Components\ColorPicker;
|
|
use Filament\Forms\Components\FileUpload;
|
|
use Filament\Forms\Components\Repeater;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TagsInput;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Notifications\Notification;
|
|
use Inerba\DbConfig\AbstractPageSettings;
|
|
use Filament\Schemas\Components;
|
|
use Filament\Schemas\Schema;
|
|
use Phiki\Phast\Text;
|
|
|
|
class PanelSettings extends AbstractPageSettings
|
|
{
|
|
/**
|
|
* @var array<string, mixed> | null
|
|
*/
|
|
public ?array $data = [];
|
|
|
|
protected static ?string $title = 'Panel';
|
|
|
|
protected static string | BackedEnum | null $navigationIcon = 'heroicon-o-rectangle-group'; // Uncomment if you want to set a custom navigation icon
|
|
|
|
// protected ?string $subheading = ''; // Uncomment if you want to set a custom subheading
|
|
|
|
// protected static ?string $slug = 'panel-settings'; // Uncomment if you want to set a custom slug
|
|
|
|
protected string $view = 'filament.pages.panel-settings';
|
|
|
|
protected function settingName(): string
|
|
{
|
|
return 'panel';
|
|
}
|
|
|
|
/**
|
|
* Provide default values.
|
|
*
|
|
*/
|
|
|
|
protected function getPanelID(): string
|
|
{
|
|
return Filament::getCurrentOrDefaultPanel()->getId();
|
|
}
|
|
|
|
public function getDefaultData(): array
|
|
{
|
|
return [
|
|
'panel_id' => $this->getPanelID(),
|
|
'panel_route_'.$this->getPanelID() => $this->getPanelID(),
|
|
'panel_show_logo_'.$this->getPanelID() => 'hide',
|
|
'panel_spa_'.$this->getPanelID() => false,
|
|
'panel_spa_prefetch_'.$this->getPanelID() => false,
|
|
'panel_spa_exceptions_'.$this->getPanelID() => [],
|
|
'panel_enable_login_'.$this->getPanelID() => true,
|
|
'panel_font_'.$this->getPanelID() => 'Poppins',
|
|
'panel_color_isRGB_'.$this->getPanelID() => false,
|
|
'panel_enable_mfa_'.$this->getPanelID() => false,
|
|
'panel_mfa_type_'.$this->getPanelID() => 'app',
|
|
'panel_mfa_app_recoverable_'.$this->getPanelID() => false,
|
|
'panel_mfa_app_recovery_code_regeneratable_'.$this->getPanelID() => false,
|
|
'panel_mfa_app_code_window_'.$this->getPanelID() => 4,
|
|
'panel_mfa_app_recovery_code_count_'.$this->getPanelID() => 8,
|
|
'panel_mfa_email_code_expiry_'.$this->getPanelID() => 2,
|
|
'panel_mfa_is_required_'.$this->getPanelID() => 'no',
|
|
|
|
];
|
|
}
|
|
|
|
public function getHeaderActions(): array
|
|
{
|
|
return [
|
|
Action::make('filament-optimize')
|
|
->label('Optimize Filament')
|
|
->color('gray')
|
|
->icon('heroicon-o-paper-airplane')
|
|
->action(fn() => $this->filamentOptimize()),
|
|
|
|
Action::make('filament-optimize-clear')
|
|
->label('Clear Optimized Files')
|
|
->color('danger')
|
|
->icon('heroicon-o-trash')
|
|
->action(fn() => $this->filamentOptimizeClear()),
|
|
|
|
...parent::getHeaderActions(),
|
|
];
|
|
}
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->components([
|
|
Components\Section::make('Panel Branding')->schema([
|
|
TextInput::make('panel_id')
|
|
->label('Panel ID')
|
|
->disabled(),
|
|
TextInput::make('panel_name_'.$this->getPanelID())
|
|
->label('Panel Name')
|
|
->required(),
|
|
TextInput::make('panel_route_'.$this->getPanelID())
|
|
->label('Panel Route')
|
|
->required(),
|
|
Select::make('panel_show_logo_'.$this->getPanelID())
|
|
->label('Panel Show Logo')
|
|
->native(false)
|
|
->options([
|
|
'show' => 'Show Logo',
|
|
'hide' => 'Hide Logo'
|
|
])
|
|
->live()
|
|
->required(),
|
|
FileUpload::make('panel_logo_light_'.$this->getPanelID())
|
|
->label('Panel Logo')
|
|
->visible(fn($get): bool => $get('panel_show_logo_'.$this->getPanelID()) === 'show')
|
|
->required(fn($get): bool => $get('panel_show_logo_'.$this->getPanelID()) === 'show')
|
|
->image()
|
|
->disk('public')
|
|
->visibility('public')
|
|
->directory('panel-assets')
|
|
->columnSpan(1)
|
|
->helperText('Upload a logo for panel'),
|
|
|
|
FileUpload::make('panel_logo_dark_'.$this->getPanelID())
|
|
->label('Panel Dark Mode Logo')
|
|
->visible(fn($get): bool => $get('panel_show_logo_'.$this->getPanelID()) === 'show')
|
|
->required(fn($get): bool => $get('panel_show_logo_'.$this->getPanelID()) === 'show')
|
|
->image()
|
|
->disk('public')
|
|
->visibility('public')
|
|
->directory('panel-assets')
|
|
->columnSpan(1)
|
|
->helperText('Upload a logo for dark mode panel'),
|
|
|
|
TextInput::make('panel_logo_height_'.$this->getPanelID())
|
|
->label('Panel Logo Height')
|
|
->helperText('Use value like `30px`, `2rem`, `20%`, etc. This value applies as style attribute - height: 30px;')
|
|
->columnSpanFull()
|
|
->visible(fn($get): bool => $get('panel_show_logo_'.$this->getPanelID()) === 'show')
|
|
->required(fn($get): bool => $get('panel_show_logo_'.$this->getPanelID()) === 'show'),
|
|
|
|
FileUpload::make('panel_favicon_'.$this->getPanelID())
|
|
->label('Panel Favicon')
|
|
->image()
|
|
->disk('public')
|
|
->visibility('public')
|
|
->directory('panel-assets')
|
|
->columnSpanFull()
|
|
->helperText('Upload a favicon for panel'),
|
|
|
|
TextInput::make('panel_font_' . $this->getPanelID())
|
|
->label('Panel Font')
|
|
->placeholder('e.g. Inter, Poppins, or custom font name')
|
|
->datalist([
|
|
'Barlow' => 'Barlow',
|
|
'DM Sans' => 'DM Sans',
|
|
'Hind' => 'Hind',
|
|
'IBM Plex Sans' => 'IBM Plex Sans',
|
|
'Inter' => 'Inter',
|
|
'Lato' => 'Lato',
|
|
'Manrope' => 'Manrope',
|
|
'Mulish' => 'Mulish',
|
|
'Nunito' => 'Nunito',
|
|
'Open Sans' => 'Open Sans',
|
|
'Poppins' => 'Poppins',
|
|
'Public Sans' => 'Public Sans',
|
|
'Quicksand' => 'Quicksand',
|
|
'Roboto' => 'Roboto',
|
|
'Rubik' => 'Rubik',
|
|
'Source Sans Pro' => 'Source Sans Pro',
|
|
'Work Sans' => 'Work Sans',
|
|
]
|
|
)
|
|
->columnSpanFull()
|
|
->helperText('Enter a Google Font name or type a custom one'),
|
|
|
|
Repeater::make('panel_color_'.$this->getPanelID())
|
|
->label('Panel Color')
|
|
->schema([
|
|
TextInput::make('panel_color_name_'.$this->getPanelID())
|
|
->label('Panel Color Name')
|
|
->required(),
|
|
ColorPicker::make('panel_color_'.$this->getPanelID())
|
|
->label('Pick Panel Color')
|
|
->rgb()
|
|
->regex('/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/')
|
|
->live()
|
|
->afterStateUpdated(function ($state, callable $set): void {
|
|
$shades = ColorHelper::generateOklchFromRGBShades($state);
|
|
$set('panel_color_shade_' . $this->getPanelID(), $shades ?? '// Invalid RGB');
|
|
})
|
|
->required(),
|
|
Textarea::make('panel_color_shade_'.$this->getPanelID())
|
|
->label('Panel Color Shade')
|
|
->rows(3)
|
|
->placeholder('Choose a color to generate shade')
|
|
->columnSpanFull()
|
|
])->columns(2)->columnSpanFull(),
|
|
|
|
Toggle::make('panel_color_isRGB_'.$this->getPanelID())
|
|
->label('Use Panel Color as RGB')
|
|
->helperText('If toggled `OFF` Oklch Color Shades will be used, else Only RGB will be used'),
|
|
|
|
Toggle::make('panel_default')
|
|
->label('Is this default panel?')
|
|
->helperText('Toggle `ON` to set this panel as default')
|
|
->dehydrateStateUsing(fn(bool $state, $get) => $state ? $get('panel_id') : null)
|
|
|
|
|
|
])->columns(2),
|
|
|
|
Components\Section::make('Panel Navigation')->schema([
|
|
|
|
Toggle::make('panel_spa_'.$this->getPanelID())
|
|
->label('Enable Panel SPA')
|
|
->live(),
|
|
|
|
Toggle::make('panel_spa_prefetch_'.$this->getPanelID())
|
|
->label('Panel Spa Prefetching')
|
|
->visible(fn($get) => $get('panel_spa_'.$this->getPanelID())),
|
|
|
|
TagsInput::make('panel_spa_exceptions_'.$this->getPanelID())
|
|
->label('Panel Spa Exceptions')
|
|
->visible(fn($get) => $get('panel_spa_'.$this->getPanelID()))
|
|
->columnSpanFull(),
|
|
])->columns(2),
|
|
|
|
Components\Section::make('Panel Authentication')->schema([
|
|
Toggle::make('panel_enable_login_'.$this->getPanelID())
|
|
->label('Enable Login'),
|
|
Toggle::make('panel_enable_registration_'.$this->getPanelID())
|
|
->label('Enable Registration'),
|
|
Toggle::make('panel_enable_profile_'.$this->getPanelID())
|
|
->live()
|
|
->label('Enable Profile'),
|
|
Toggle::make('panel_enable_mfa_'.$this->getPanelID())
|
|
->visible(fn($get) => $get('panel_enable_profile_'.$this->getPanelID()))
|
|
->live()
|
|
->label('Enable MFA'),
|
|
Select::make('panel_mfa_type_'.$this->getPanelID())
|
|
->label('MFA Type')
|
|
->visible(fn($get) => $get('panel_enable_mfa_'.$this->getPanelID()))
|
|
->options([
|
|
'app' => 'Authenticator App',
|
|
'email' => 'Email',
|
|
])
|
|
->live()
|
|
->required(fn($get) => $get('panel_enable_mfa_'.$this->getPanelID())),
|
|
TextInput::make('panel_mfa_app_brand_name_'.$this->getPanelID())
|
|
->label('MFA Authenticator Brand Name')
|
|
->helperText('Optional: Leave to use app name as authenticator brand name')
|
|
->visible(fn($get): bool => $get('panel_enable_mfa_'.$this->getPanelID()) && $get('panel_mfa_type_'.$this->getPanelID()) === 'app'),
|
|
|
|
Toggle::make('panel_mfa_app_recoverable_'.$this->getPanelID())
|
|
->label('MFA Authenticator Recoverable')
|
|
->live()
|
|
->visible(fn($get): bool => $get('panel_enable_mfa_'.$this->getPanelID()) && $get('panel_mfa_type_'.$this->getPanelID()) === 'app'),
|
|
|
|
Toggle::make('panel_mfa_app_recovery_code_regeneratable_'.$this->getPanelID())
|
|
->label('Allow to regenerate recovery code')
|
|
->live()
|
|
->visible(fn($get): bool => $get('panel_enable_mfa_'.$this->getPanelID()) && $get('panel_mfa_app_recoverable_'.$this->getPanelID()) && $get('panel_mfa_type_'.$this->getPanelID()) === 'app'),
|
|
|
|
TextInput::make('panel_mfa_app_recovery_code_count_'.$this->getPanelID())
|
|
->label('MFA Authenticator Recovery Code Count')
|
|
->numeric()
|
|
->minValue(8)
|
|
->maxValue(16)
|
|
->visible(fn($get): bool => $get('panel_enable_mfa_'.$this->getPanelID()) && $get('panel_mfa_app_recoverable_'.$this->getPanelID()) && $get('panel_mfa_type_'.$this->getPanelID()) === 'app'),
|
|
|
|
TextInput::make('panel_mfa_app_code_window_'.$this->getPanelID())
|
|
->label('MFA Authenticator Code Window')
|
|
->numeric()
|
|
->minValue(1)
|
|
->maxValue(8)
|
|
->visible(fn($get): bool => $get('panel_enable_mfa_'.$this->getPanelID()) && $get('panel_mfa_type_'.$this->getPanelID()) === 'app'),
|
|
|
|
TextInput::make('panel_mfa_email_code_expiry_'.$this->getPanelID())
|
|
->label('MFA Email Expiry (in minutes)')
|
|
->numeric()
|
|
->minValue(1)
|
|
->maxValue(8)
|
|
->visible(fn($get): bool => $get('panel_enable_mfa_'.$this->getPanelID()) && $get('panel_mfa_type_'.$this->getPanelID()) === 'email'),
|
|
|
|
Select::make('panel_mfa_is_required_'.$this->getPanelID())
|
|
->label('MFA Is Required')
|
|
->options([
|
|
'no' => 'No',
|
|
'yes' => 'Yes',
|
|
])
|
|
->columnSpanFull()
|
|
->visible(fn($get) => $get('panel_enable_mfa_'.$this->getPanelID()))
|
|
->helperText('Force to enable MFA, even for those panels in which MFA is not set yet!')
|
|
|
|
])->columns(2)
|
|
])
|
|
->statePath('data');
|
|
}
|
|
|
|
private function filamentOptimize(): void
|
|
{
|
|
try {
|
|
\Artisan::queue('filament:optimize');
|
|
Notification::make()
|
|
->title('Filament optimization successful!')
|
|
->success()
|
|
->send();
|
|
} catch (\Exception $e) {
|
|
\Log::error('Filament optimization failed', ['exception' => $e->getMessage()]);
|
|
Notification::make()
|
|
->title('Filament optimization failed: ' . $e->getMessage())
|
|
->danger()
|
|
->send();
|
|
}
|
|
}
|
|
private function filamentOptimizeClear(): void
|
|
{
|
|
try {
|
|
\Artisan::queue('filament:optimize-clear');
|
|
Notification::make()
|
|
->title('Cache files cleared successful!')
|
|
->success()
|
|
->send();
|
|
} catch (\Exception $e) {
|
|
\Log::error('Filament optimize clear failed', ['exception' => $e->getMessage()]);
|
|
Notification::make()
|
|
->title('Failed to clear cache files: ' . $e->getMessage())
|
|
->danger()
|
|
->send();
|
|
}
|
|
}
|
|
}
|