Use Wizard for server/node create pages (#352)

* Update create server flow

* Update create node & buttons

* Remove duplicate

* Composer Update

Update some of the packages <3

* Small adjustments

* pint

---------

Co-authored-by: Lance Pioch <git@lance.sh>
This commit is contained in:
Charles 2024-06-09 15:07:33 -04:00 committed by GitHub
parent 8080435eca
commit 9114685680
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1110 additions and 1046 deletions

View File

@ -4,8 +4,10 @@ namespace App\Filament\Resources\NodeResource\Pages;
use App\Filament\Resources\NodeResource; use App\Filament\Resources\NodeResource;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\Tabs; use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Wizard;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
class CreateNode extends CreateRecord class CreateNode extends CreateRecord
@ -18,19 +20,19 @@ class CreateNode extends CreateRecord
public function form(Forms\Form $form): Forms\Form public function form(Forms\Form $form): Forms\Form
{ {
return $form->schema([ return $form
Tabs::make('Tabs') ->schema([
Wizard::make([
Wizard\Step::make('basic')
->label('Basic Settings')
->icon('tabler-server')
->columnSpanFull()
->columns([ ->columns([
'default' => 2, 'default' => 2,
'sm' => 3, 'sm' => 3,
'md' => 3, 'md' => 3,
'lg' => 4, 'lg' => 4,
]) ])
->persistTabInQueryString()
->columnSpanFull()
->tabs([
Tabs\Tab::make('Basic Settings')
->icon('tabler-server')
->schema([ ->schema([
Forms\Components\TextInput::make('fqdn') Forms\Components\TextInput::make('fqdn')
->columnSpan(2) ->columnSpan(2)
@ -184,30 +186,17 @@ class CreateNode extends CreateRecord
]) ])
->default(fn () => request()->isSecure() ? 'https' : 'http'), ->default(fn () => request()->isSecure() ? 'https' : 'http'),
]), ]),
Tabs\Tab::make('Advanced Settings') Wizard\Step::make('advanced')
->label('Advanced Settings')
->icon('tabler-server-cog') ->icon('tabler-server-cog')
->schema([ ->columnSpanFull()
Forms\Components\TextInput::make('upload_size') ->columns([
->label('Upload Limit') 'default' => 2,
->helperText('Enter the maximum size of files that can be uploaded through the web-based file manager.') 'sm' => 3,
->columnSpan(1) 'md' => 3,
->numeric()->required() 'lg' => 4,
->default(256)
->minValue(1)
->maxValue(1024)
->suffix('MiB'),
Forms\Components\ToggleButtons::make('public')
->label('Automatic Allocation')->inline()
->default(true)
->columnSpan(1)
->options([
true => 'Yes',
false => 'No',
]) ])
->colors([ ->schema([
true => 'success',
false => 'danger',
]),
Forms\Components\ToggleButtons::make('maintenance_mode') Forms\Components\ToggleButtons::make('maintenance_mode')
->label('Maintenance Mode')->inline() ->label('Maintenance Mode')->inline()
->columnSpan(1) ->columnSpan(1)
@ -222,13 +211,46 @@ class CreateNode extends CreateRecord
true => 'danger', true => 'danger',
false => 'success', false => 'success',
]), ]),
Forms\Components\ToggleButtons::make('public')
->columnSpan(1)
->label('Automatic Allocation')->inline()
->options([
true => 'Yes',
false => 'No',
])
->colors([
true => 'success',
false => 'danger',
]),
Forms\Components\TagsInput::make('tags') Forms\Components\TagsInput::make('tags')
->label('Tags') ->label('Tags')
->disabled() ->disabled()
->placeholder('Not Implemented') ->placeholder('Not Implemented')
->hintIcon('tabler-question-mark') ->hintIcon('tabler-question-mark')
->hintIconTooltip('Not Implemented') ->hintIconTooltip('Not Implemented')
->columnSpan(1), ->columnSpan(2),
Forms\Components\TextInput::make('upload_size')
->label('Upload Limit')
->helperText('Enter the maximum size of files that can be uploaded through the web-based file manager.')
->columnSpan(1)
->numeric()->required()
->default(256)
->minValue(1)
->maxValue(1024)
->suffix('MiB'),
Forms\Components\TextInput::make('daemon_sftp')
->columnSpan(1)
->label('SFTP Port')
->minValue(0)
->maxValue(65536)
->default(2022)
->required()
->integer(),
Forms\Components\TextInput::make('daemon_sftp_alias')
->columnSpan(2)
->label('SFTP Alias')
->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
Forms\Components\Grid::make() Forms\Components\Grid::make()
->columns(6) ->columns(6)
->columnSpanFull() ->columnSpanFull()
@ -353,7 +375,16 @@ class CreateNode extends CreateRecord
->suffix('%'), ->suffix('%'),
]), ]),
]), ]),
]), ])->columnSpanFull()
->nextAction(fn (Action $action) => $action->label('Next Step'))
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
<x-filament::button
type="submit"
size="sm"
>
Create Node
</x-filament::button>
BLADE))),
]); ]);
} }
@ -363,4 +394,9 @@ class CreateNode extends CreateRecord
'tab' => '-configuration-tab', 'tab' => '-configuration-tab',
]; ];
} }
protected function getFormActions(): array
{
return [];
}
} }

View File

@ -9,14 +9,17 @@ use App\Models\Node;
use App\Services\Allocations\AssignmentService; use App\Services\Allocations\AssignmentService;
use App\Services\Servers\RandomWordService; use App\Services\Servers\RandomWordService;
use App\Services\Servers\ServerCreationService; use App\Services\Servers\ServerCreationService;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Validator;
use Closure;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\Wizard;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Closure;
class CreateServer extends CreateRecord class CreateServer extends CreateRecord
{ {
@ -28,6 +31,12 @@ class CreateServer extends CreateRecord
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form return $form
->schema([
Wizard::make([
Wizard\Step::make('Information')
->label('Information')
->icon('tabler-info-circle')
->completedIcon('tabler-check')
->columns([ ->columns([
'default' => 2, 'default' => 2,
'sm' => 2, 'sm' => 2,
@ -35,13 +44,9 @@ class CreateServer extends CreateRecord
'lg' => 6, 'lg' => 6,
]) ])
->schema([ ->schema([
Forms\Components\TextInput::make('external_id')
->maxLength(191)
->hidden(),
Forms\Components\TextInput::make('name') Forms\Components\TextInput::make('name')
->prefixIcon('tabler-server') ->prefixIcon('tabler-server')
->label('Display Name') ->label('Name')
->suffixAction(Forms\Components\Actions\Action::make('random') ->suffixAction(Forms\Components\Actions\Action::make('random')
->icon('tabler-dice-' . random_int(1, 6)) ->icon('tabler-dice-' . random_int(1, 6))
->action(function (Forms\Set $set, Forms\Get $get) { ->action(function (Forms\Set $set, Forms\Get $get) {
@ -80,7 +85,12 @@ class CreateServer extends CreateRecord
->disabledOn('edit') ->disabledOn('edit')
->prefixIcon('tabler-server-2') ->prefixIcon('tabler-server-2')
->default(fn () => ($this->node = Node::query()->latest()->first())?->id) ->default(fn () => ($this->node = Node::query()->latest()->first())?->id)
->columnSpan(2) ->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 2,
])
->live() ->live()
->relationship('node', 'name') ->relationship('node', 'name')
->searchable() ->searchable()
@ -96,7 +106,12 @@ class CreateServer extends CreateRecord
->live() ->live()
->prefixIcon('tabler-network') ->prefixIcon('tabler-network')
->label('Primary Allocation') ->label('Primary Allocation')
->columnSpan(2) ->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 1,
'lg' => 2,
])
->disabled(fn (Forms\Get $get) => $get('node_id') === null) ->disabled(fn (Forms\Get $get) => $get('node_id') === null)
->searchable(['ip', 'port', 'ip_alias']) ->searchable(['ip', 'port', 'ip_alias'])
->afterStateUpdated(function (Forms\Set $set) { ->afterStateUpdated(function (Forms\Set $set) {
@ -212,7 +227,12 @@ class CreateServer extends CreateRecord
Forms\Components\Repeater::make('allocation_additional') Forms\Components\Repeater::make('allocation_additional')
->label('Additional Allocations') ->label('Additional Allocations')
->columnSpan(2) ->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 1,
'lg' => 2,
])
->addActionLabel('Add Allocation') ->addActionLabel('Add Allocation')
->disabled(fn (Forms\Get $get) => $get('allocation_id') === null) ->disabled(fn (Forms\Get $get) => $get('allocation_id') === null)
// ->addable() TODO disable when all allocations are taken // ->addable() TODO disable when all allocations are taken
@ -243,22 +263,37 @@ class CreateServer extends CreateRecord
), ),
), ),
Forms\Components\Textarea::make('description') Forms\Components\TextInput::make('description')
->hidden() ->placeholder('Description')
->default('')
->required()
->columnSpanFull(),
Forms\Components\Select::make('egg_id')
->disabledOn('edit')
->prefixIcon('tabler-egg')
->columnSpan([ ->columnSpan([
'default' => 2, 'default' => 1,
'sm' => 2, 'sm' => 2,
'md' => 2, 'md' => 2,
'lg' => 5, 'lg' => 6,
]) ])
->label('Notes'),
]),
Wizard\Step::make('Egg Configuration')
->label('Egg Configuration')
->icon('tabler-egg')
->completedIcon('tabler-check')
->columns([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 4,
])
->schema([
Forms\Components\Select::make('egg_id')
->prefixIcon('tabler-egg')
->relationship('egg', 'name') ->relationship('egg', 'name')
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 3,
])
->searchable() ->searchable()
->preload() ->preload()
->live() ->live()
@ -317,18 +352,19 @@ class CreateServer extends CreateRecord
Forms\Components\Textarea::make('startup') Forms\Components\Textarea::make('startup')
->hintIcon('tabler-code') ->hintIcon('tabler-code')
->label('Startup Command') ->label('Startup Command')
->hidden(fn (Forms\Get $get) => $get('egg_id') === null)
->required() ->required()
->live() ->live()
->columnSpan([ ->columnSpan([
'default' => 2, 'default' => 1,
'sm' => 4, 'sm' => 2,
'md' => 4, 'md' => 2,
'lg' => 6, 'lg' => 4,
]) ])
->rows(function ($state) { ->rows(function ($state) {
return str($state)->explode("\n")->reduce( return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125), fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
0 1
); );
}), }),
@ -336,17 +372,12 @@ class CreateServer extends CreateRecord
Forms\Components\Hidden::make('start_on_completion')->default(true), Forms\Components\Hidden::make('start_on_completion')->default(true),
Forms\Components\Section::make('Egg Variables') Forms\Components\Section::make('Variables')
->icon('tabler-eggs') ->icon('tabler-eggs')
->iconColor('primary') ->iconColor('primary')
->hidden(fn (Forms\Get $get) => $get('egg_id') === null)
->collapsible() ->collapsible()
->collapsed() ->columnSpanFull()
->columnSpan(([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
]))
->schema([ ->schema([
Forms\Components\Placeholder::make('Select an egg first to show its variables!') Forms\Components\Placeholder::make('Select an egg first to show its variables!')
->hidden(fn (Forms\Get $get) => $get('egg_id')), ->hidden(fn (Forms\Get $get) => $get('egg_id')),
@ -357,6 +388,7 @@ class CreateServer extends CreateRecord
), ),
Forms\Components\Repeater::make('server_variables') Forms\Components\Repeater::make('server_variables')
->label('')
->relationship('serverVariables') ->relationship('serverVariables')
->saveRelationshipsBeforeChildrenUsing(null) ->saveRelationshipsBeforeChildrenUsing(null)
->saveRelationshipsUsing(null) ->saveRelationshipsUsing(null)
@ -413,18 +445,11 @@ class CreateServer extends CreateRecord
}) })
->columnSpan(2), ->columnSpan(2),
]), ]),
]),
Forms\Components\Section::make('Environment Management') Wizard\Step::make('Environment Configuration')
->collapsed() ->label('Environment Configuration')
->icon('tabler-server-cog') ->icon('tabler-brand-docker')
->iconColor('primary') ->completedIcon('tabler-check')
->columns([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 4,
])
->columnSpanFull()
->schema([ ->schema([
Forms\Components\Fieldset::make('Resource Limits') Forms\Components\Fieldset::make('Resource Limits')
->columnSpan([ ->columnSpan([
@ -625,16 +650,19 @@ class CreateServer extends CreateRecord
]) ])
->schema([ ->schema([
Forms\Components\TextInput::make('allocation_limit') Forms\Components\TextInput::make('allocation_limit')
->label('Allocations')
->suffixIcon('tabler-network') ->suffixIcon('tabler-network')
->required() ->required()
->numeric() ->numeric()
->default(0), ->default(0),
Forms\Components\TextInput::make('database_limit') Forms\Components\TextInput::make('database_limit')
->label('Databases')
->suffixIcon('tabler-database') ->suffixIcon('tabler-database')
->required() ->required()
->numeric() ->numeric()
->default(0), ->default(0),
Forms\Components\TextInput::make('backup_limit') Forms\Components\TextInput::make('backup_limit')
->label('Backups')
->suffixIcon('tabler-copy-check') ->suffixIcon('tabler-copy-check')
->required() ->required()
->numeric() ->numeric()
@ -687,7 +715,7 @@ class CreateServer extends CreateRecord
} }
}) })
->placeholder('Enter a custom Image') ->placeholder('Enter a custom Image')
->columnSpan(1), ->columnSpan(2),
Forms\Components\KeyValue::make('docker_labels') Forms\Components\KeyValue::make('docker_labels')
->label('Container Labels') ->label('Container Labels')
@ -705,9 +733,25 @@ class CreateServer extends CreateRecord
->columnSpanFull(), ->columnSpanFull(),
]), ]),
]), ]),
])
->columnSpanFull()
->nextAction(fn (Action $action) => $action->label('Next Step'))
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
<x-filament::button
type="submit"
size="sm"
>
Create Server
</x-filament::button>
BLADE))),
]); ]);
} }
protected function getFormActions(): array
{
return [];
}
protected function handleRecordCreation(array $data): Model protected function handleRecordCreation(array $data): Model
{ {
$data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all(); $data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all();

670
composer.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function r({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=o=>o===null?0:Array.isArray(o)?o.length:typeof o!="object"?0:Object.keys(o).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};

File diff suppressed because one or more lines are too long