Files
jabali-panel/app/Filament/Admin/Resources/Users/Schemas/UserForm.php
2026-01-24 19:36:46 +02:00

218 lines
10 KiB
PHP

<?php
namespace App\Filament\Admin\Resources\Users\Schemas;
use App\Models\DnsSetting;
use Filament\Actions\Action;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Group;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
class UserForm
{
/**
* Generate a secure password with uppercase, lowercase, numbers, and special characters
*/
public static function generateSecurePassword(int $length = 16): string
{
$uppercase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
$lowercase = 'abcdefghjkmnpqrstuvwxyz';
$numbers = '23456789';
$special = '!@#$%^&*';
// Ensure at least one of each type
$password = $uppercase[random_int(0, strlen($uppercase) - 1)]
. $lowercase[random_int(0, strlen($lowercase) - 1)]
. $numbers[random_int(0, strlen($numbers) - 1)]
. $special[random_int(0, strlen($special) - 1)];
// Fill rest with random characters from all pools
$allChars = $uppercase . $lowercase . $numbers . $special;
for ($i = 4; $i < $length; $i++) {
$password .= $allChars[random_int(0, strlen($allChars) - 1)];
}
// Shuffle the password
return str_shuffle($password);
}
public static function configure(Schema $schema): Schema
{
return $schema
->columns(1)
->components([
Section::make(__('User Information'))
->schema([
TextInput::make('name')
->label(__('Name'))
->required()
->maxLength(255),
TextInput::make('username')
->label(__('Username'))
->required()
->maxLength(32)
->alphaNum()
->unique(ignoreRecord: true)
->rules(['regex:/^[a-z][a-z0-9_]{0,31}$/'])
->helperText(__('Lowercase letters, numbers, and underscores only. Must start with a letter.'))
->disabled(fn (string $operation): bool => $operation === 'edit'),
TextInput::make('email')
->label(__('Email address'))
->email()
->required()
->unique(ignoreRecord: true)
->maxLength(255),
TextInput::make('password')
->password()
->revealable()
->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null)
->dehydrated(fn ($state) => filled($state))
->required(fn (string $operation): bool => $operation === 'create')
->minLength(8)
->rules([
'regex:/[a-z]/', // lowercase
'regex:/[A-Z]/', // uppercase
'regex:/[0-9]/', // number
])
->suffixActions([
Action::make('generatePassword')
->icon('heroicon-o-arrow-path')
->tooltip(__('Generate secure password'))
->action(function ($set) {
$password = self::generateSecurePassword();
$set('password', $password);
}),
Action::make('copyPassword')
->icon('heroicon-o-clipboard-document')
->tooltip(__('Copy to clipboard'))
->action(function ($state, $livewire) {
if ($state) {
$escaped = addslashes($state);
$livewire->js("navigator.clipboard.writeText('{$escaped}')");
\Filament\Notifications\Notification::make()
->title(__('Copied to clipboard'))
->success()
->duration(2000)
->send();
}
}),
])
->helperText(__('Minimum 8 characters with uppercase, lowercase, and numbers'))
->label(fn (string $operation): string => $operation === 'create' ? __('Password') : __('New Password')),
])
->columns(2),
Section::make(__('Account Settings'))
->schema([
Toggle::make('is_admin')
->label(__('Administrator'))
->helperText(__('Grant full administrative access'))
->inline(false),
Toggle::make('is_active')
->label(__('Active'))
->default(true)
->helperText(__('Inactive users cannot log in'))
->inline(false),
Toggle::make('create_linux_user')
->label(__('Create Linux User'))
->default(true)
->helperText(__('Create a system user account'))
->visibleOn('create')
->dehydrated(false)
->inline(false),
DateTimePicker::make('email_verified_at')
->label(__('Email Verified At')),
])
->columns(4),
Section::make(__('Disk Quota'))
->schema([
Placeholder::make('current_usage')
->label(__('Current Usage'))
->content(function ($record) {
if (!$record) {
return __('N/A');
}
$used = $record->disk_usage_formatted;
$quotaMb = $record->disk_quota_mb;
if (!$quotaMb || $quotaMb <= 0) {
return "{$used} (" . __('Unlimited') . ")";
}
$quota = $quotaMb >= 1024
? number_format($quotaMb / 1024, 1) . ' GB'
: $quotaMb . ' MB';
$percent = $record->disk_usage_percent;
return "{$used} / {$quota} ({$percent}%)";
})
->visibleOn('edit'),
Toggle::make('unlimited_quota')
->label(__('Unlimited Quota'))
->helperText(__('No disk space limit'))
->default(fn () => (int) DnsSetting::get('default_quota_mb', 5120) === 0)
->live()
->afterStateHydrated(function (Toggle $component, $state, $record) {
if ($record) {
$component->state(!$record->disk_quota_mb || $record->disk_quota_mb <= 0);
}
})
->afterStateUpdated(function ($state, callable $set) {
if ($state) {
$set('disk_quota_mb', 0);
} else {
$set('disk_quota_mb', (int) DnsSetting::get('default_quota_mb', 5120));
}
})
->inline(false),
TextInput::make('disk_quota_mb')
->label(__('Disk Quota'))
->numeric()
->minValue(1)
->default(fn () => (int) DnsSetting::get('default_quota_mb', 5120))
->helperText(fn ($state) => $state && $state > 0 ? number_format($state / 1024, 1) . ' GB' : null)
->suffix('MB')
->visible(fn ($get) => !$get('unlimited_quota'))
->required(fn ($get) => !$get('unlimited_quota')),
])
->description(fn () => !(bool) DnsSetting::get('quotas_enabled', false) ? __('Note: Quotas are currently disabled in Server Settings.') : null)
->columns(3),
Section::make(__('System Information'))
->schema([
Placeholder::make('home_directory_display')
->label(__('Home Directory'))
->content(fn ($record) => $record?->home_directory ?? '/home/' . __('username')),
Placeholder::make('created_at_display')
->label(__('Created'))
->content(fn ($record) => $record?->created_at?->format('M d, Y H:i') ?? __('N/A')),
Placeholder::make('updated_at_display')
->label(__('Last Updated'))
->content(fn ($record) => $record?->updated_at?->format('M d, Y H:i') ?? __('N/A')),
])
->columns(3)
->visibleOn('edit')
->collapsible(),
]);
}
}