From 8f277aaca0fc6c94861e51f239a9b6472af692cf Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 2 Sep 2025 09:05:36 +0200 Subject: [PATCH] Create custom startup variable field (#1615) --- app/Enums/StartupVariableType.php | 10 + .../ServerResource/Pages/CreateServer.php | 94 ++------- .../ServerResource/Pages/EditServer.php | 88 +-------- .../Forms/Fields/StartupVariable.php | 181 ++++++++++++++++++ .../components/startup-variable.blade.php | 67 +++++++ 5 files changed, 277 insertions(+), 163 deletions(-) create mode 100644 app/Enums/StartupVariableType.php create mode 100644 app/Filament/Components/Forms/Fields/StartupVariable.php create mode 100644 resources/views/filament/components/startup-variable.blade.php diff --git a/app/Enums/StartupVariableType.php b/app/Enums/StartupVariableType.php new file mode 100644 index 000000000..f1b90c1b6 --- /dev/null +++ b/app/Enums/StartupVariableType.php @@ -0,0 +1,10 @@ +label('') + ->hiddenLabel() ->relationship('serverVariables', fn (Builder $query) => $query->orderByPowerJoins('variable.sort')) ->saveRelationshipsBeforeChildrenUsing(null) ->saveRelationshipsUsing(null) @@ -439,51 +437,15 @@ class CreateServer extends CreateRecord ->deletable(false) ->default([]) ->hidden(fn ($state) => empty($state)) - ->schema(function () { - - $text = TextInput::make('variable_value') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->required(fn (Get $get) => in_array('required', $get('rules'))) - ->rules( - fn (Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) { - $validator = Validator::make(['validatorkey' => $value], [ - 'validatorkey' => $get('rules'), - ]); - - if ($validator->fails()) { - $message = str($validator->errors()->first())->replace('validatorkey', $get('name'))->toString(); - - $fail($message); - } - }, - ); - - $select = Select::make('variable_value') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->options($this->getSelectOptionsFromRules(...)) - ->selectablePlaceholder(false); - - $components = [$text, $select]; - - foreach ($components as &$component) { - $component = $component - ->live(onBlur: true) - ->hintIcon('tabler-code') - ->label(fn (Get $get) => $get('name')) - ->hintIconTooltip(fn (Get $get) => implode('|', $get('rules'))) - ->prefix(fn (Get $get) => '{{' . $get('env_variable') . '}}') - ->helperText(fn (Get $get) => empty($get('description')) ? '—' : $get('description')) - ->afterStateUpdated(function (Set $set, Get $get, $state) { - $environment = $get($envPath = '../../environment'); - $environment[$get('env_variable')] = $state; - $set($envPath, $environment); - }); - } - - return $components; - }) + ->schema([ + StartupVariable::make('variable_value') + ->fromForm() + ->afterStateUpdated(function (Set $set, Get $get, $state) { + $environment = $get($envPath = '../../environment'); + $environment[$get('env_variable')] = $state; + $set($envPath, $environment); + }), + ]) ->columnSpan(2), ]), ]), @@ -851,40 +813,6 @@ class CreateServer extends CreateRecord } } - private function shouldHideComponent(Get $get, Component $component): bool - { - $containsRuleIn = collect($get('rules'))->reduce( - fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true - ); - - if ($component instanceof Select) { - return $containsRuleIn; - } - - if ($component instanceof TextInput) { - return !$containsRuleIn; - } - - throw new Exception('Component type not supported: ' . $component::class); - } - - /** - * @return array - */ - private function getSelectOptionsFromRules(Get $get): array - { - $inRule = collect($get('rules'))->reduce( - fn ($result, $value) => str($value)->startsWith('in:') ? $value : $result, '' - ); - - return str($inRule) - ->after('in:') - ->explode(',') - ->each(fn ($value) => str($value)->trim()) - ->mapWithKeys(fn ($value) => [$value => $value]) - ->all(); - } - /** * @param string[] $portEntries * @return array diff --git a/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php index 54fcb5407..857b47614 100644 --- a/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php @@ -7,6 +7,7 @@ use App\Enums\SuspendAction; use App\Filament\Admin\Resources\ServerResource; use App\Filament\Components\Forms\Actions\PreviewStartupAction; use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction; +use App\Filament\Components\Forms\Fields\StartupVariable; use App\Filament\Server\Pages\Console; use App\Models\Allocation; use App\Models\Database; @@ -27,10 +28,8 @@ use App\Services\Servers\ToggleInstallService; use App\Services\Servers\TransferServerService; use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderWidgets; -use Closure; use Exception; use Filament\Actions; -use Filament\Forms; use Filament\Forms\Components\Actions as FormActions; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Component; @@ -56,7 +55,6 @@ use Filament\Support\Enums\Alignment; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Validator; use Illuminate\Support\HtmlString; use LogicException; use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; @@ -617,7 +615,7 @@ class EditServer extends EditRecord }), Repeater::make('server_variables') - ->label('') + ->hiddenLabel() ->relationship('serverVariables', function (Builder $query) { /** @var Server $server */ $server = $this->getRecord(); @@ -634,56 +632,16 @@ class EditServer extends EditRecord return $query->orderByPowerJoins('variable.sort'); }) ->grid() - ->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array { - foreach ($data as $key => $value) { - if (!isset($data['variable_value'])) { - $data['variable_value'] = ''; - } - } + ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array { + $data['variable_value'] ??= ''; return $data; }) ->reorderable(false)->addable(false)->deletable(false) - ->schema(function () { - - $text = TextInput::make('variable_value') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->required(fn (ServerVariable $serverVariable) => $serverVariable->variable->getRequiredAttribute()) - ->rules([ - fn (ServerVariable $serverVariable): Closure => function (string $attribute, $value, Closure $fail) use ($serverVariable) { - $validator = Validator::make(['validatorkey' => $value], [ - 'validatorkey' => $serverVariable->variable->rules, - ]); - - if ($validator->fails()) { - $message = str($validator->errors()->first())->replace('validatorkey', $serverVariable->variable->name); - - $fail($message); - } - }, - ]); - - $select = Select::make('variable_value') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->options($this->getSelectOptionsFromRules(...)) - ->selectablePlaceholder(false); - - $components = [$text, $select]; - - foreach ($components as &$component) { - $component = $component - ->live(onBlur: true) - ->hintIcon('tabler-code') - ->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name) - ->hintIconTooltip(fn (ServerVariable $serverVariable) => implode('|', $serverVariable->variable->rules)) - ->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}') - ->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description); - } - - return $components; - }) + ->schema([ + StartupVariable::make('variable_value') + ->fromRecord(), + ]) ->columnSpan(6), ]), Tab::make(trans('admin/server.mounts')) @@ -1145,34 +1103,4 @@ class EditServer extends EditRecord { return null; } - - private function shouldHideComponent(ServerVariable $serverVariable, Forms\Components\Component $component): bool - { - $containsRuleIn = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:'), false); - - if ($component instanceof Select) { - return !$containsRuleIn; - } - - if ($component instanceof TextInput) { - return $containsRuleIn; - } - - throw new Exception('Component type not supported: ' . $component::class); - } - - /** - * @return array - */ - private function getSelectOptionsFromRules(ServerVariable $serverVariable): array - { - $inRule = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:')); - - return str($inRule) - ->after('in:') - ->explode(',') - ->each(fn ($value) => str($value)->trim()) - ->mapWithKeys(fn ($value) => [$value => $value]) - ->all(); - } } diff --git a/app/Filament/Components/Forms/Fields/StartupVariable.php b/app/Filament/Components/Forms/Fields/StartupVariable.php new file mode 100644 index 000000000..9df2dd906 --- /dev/null +++ b/app/Filament/Components/Forms/Fields/StartupVariable.php @@ -0,0 +1,181 @@ +label(fn (StartupVariable $component) => $component->getVariableName()); + + $this->prefix(fn (StartupVariable $component) => '{{' . $component->getVariableEnv() . '}}'); + + $this->hintIcon('tabler-code'); + + $this->hintIconTooltip(fn (StartupVariable $component) => implode('|', $component->getVariableRules())); + + $this->helperText(fn (StartupVariable $component) => !$component->getVariableDesc() ? '—' : $component->getVariableDesc()); + + $this->rules(fn (StartupVariable $component) => $component->getVariableRules()); + + $this->placeholder(fn (StartupVariable $component) => $component->getVariableDefault()); + + $this->live(onBlur: true); + } + + public function fromForm(): static + { + $this->variableName(fn (Get $get) => $get('name')); + $this->variableDesc(fn (Get $get) => $get('description')); + $this->variableEnv(fn (Get $get) => $get('env_variable')); + $this->variableDefault(fn (Get $get) => $get('default_value')); + $this->variableRules(fn (Get $get) => $get('rules')); + + return $this; + } + + public function fromRecord(): static + { + $this->variableName(fn (ServerVariable $record) => $record->variable->name); + $this->variableDesc(fn (ServerVariable $record) => $record->variable->description); + $this->variableEnv(fn (ServerVariable $record) => $record->variable->env_variable); + $this->variableDefault(fn (ServerVariable $record) => $record->variable->default_value); + $this->variableRules(fn (ServerVariable $record) => $record->variable->rules); + + return $this; + } + + public function variableName(string|Closure|null $name): static + { + $this->variableName = $name; + + return $this; + } + + public function variableDesc(string|Closure|null $desc): static + { + $this->variableDesc = $desc; + + return $this; + } + + public function variableEnv(string|Closure|null $envVariable): static + { + $this->variableEnv = $envVariable; + + return $this; + } + + public function variableDefault(string|Closure|null $default): static + { + $this->variableDefault = $default; + + return $this; + } + + /** @param string[]|Closure|null $rules */ + public function variableRules(array|Closure|null $rules): static + { + $this->variableRules = $rules; + + return $this; + } + + public function getVariableName(): ?string + { + return $this->evaluate($this->variableName); + } + + public function getVariableDesc(): ?string + { + return $this->evaluate($this->variableDesc); + } + + public function getVariableEnv(): ?string + { + return $this->evaluate($this->variableEnv); + } + + public function getVariableDefault(): ?string + { + return $this->evaluate($this->variableDefault); + } + + /** @return string[] */ + public function getVariableRules(): array + { + return (array) ($this->evaluate($this->variableRules) ?? []); + } + + public function isRequired(): bool + { + $rules = $this->getVariableRules(); + + return in_array('required', $rules); + } + + public function getType(): StartupVariableType + { + $rules = $this->getVariableRules(); + + if (Arr::first($rules, fn ($value) => str($value)->startsWith('in:'), false)) { + return StartupVariableType::Select; + } + + if (in_array('boolean', $rules)) { + return StartupVariableType::Toggle; + } + + return StartupVariableType::Text; + } + + /** @return string[] */ + public function getSelectOptions(): array + { + $rules = $this->getVariableRules(); + + $inRule = Arr::first($rules, fn ($value) => str($value)->startsWith('in:')); + if ($inRule) { + return str($inRule) + ->after('in:') + ->explode(',') + ->each(fn ($value) => Str::trim($value)) + ->all(); + } + + return []; + } +} diff --git a/resources/views/filament/components/startup-variable.blade.php b/resources/views/filament/components/startup-variable.blade.php new file mode 100644 index 000000000..11e33d6f2 --- /dev/null +++ b/resources/views/filament/components/startup-variable.blade.php @@ -0,0 +1,67 @@ +@php + $statePath = $getStatePath(); + $isRequired = $isRequired(); + $isDisabled = $isDisabled(); + $type = $getType(); +@endphp + + + + {{ $getLabel() }} + + + + @if ($type === \App\Enums\StartupVariableType::Select) + + @if (!$isRequired) + + @endif + + @foreach ($getSelectOptions() as $value) + + @endforeach + + @else + + @endif + +