Rework Edit Server Page

a WIP, Also functional
This commit is contained in:
notCharles 2024-05-27 20:02:13 -04:00
parent 15caac51fb
commit dec1cf8e74
4 changed files with 1151 additions and 437 deletions

View File

@ -3,7 +3,9 @@
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource;
use App\Http\Controllers\Admin\ServersController;
use App\Services\Servers\RandomWordService;
use App\Services\Servers\SuspensionService;
use Filament\Actions;
use Filament\Forms;
use App\Enums\ContainerStatus;
@ -13,10 +15,12 @@ use App\Models\Server;
use App\Models\ServerVariable;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Servers\ServerDeletionService;
use Filament\Forms\Components\Tabs;
use Filament\Forms\Form;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Validator;
use Closure;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
class EditServer extends EditRecord
{
@ -25,12 +29,6 @@ class EditServer extends EditRecord
public function form(Form $form): Form
{
return $form
->columns([
'default' => 2,
'sm' => 2,
'md' => 4,
'lg' => 6,
])
->schema([
Forms\Components\ToggleButtons::make('docker')
->label('Container Status')->inline()->inlineLabel()
@ -82,10 +80,18 @@ class EditServer extends EditRecord
'lg' => 3,
]),
Forms\Components\TextInput::make('external_id')
->maxLength(191)
->hidden(),
Tabs::make('Tabs')
->persistTabInQueryString()
->columnSpan(6)
->columns([
'default' => 2,
'sm' => 2,
'md' => 4,
'lg' => 6,
])
->tabs([
Tabs\Tab::make('Information')
->schema([
Forms\Components\TextInput::make('name')
->prefixIcon('tabler-server')
->label('Display Name')
@ -101,7 +107,7 @@ class EditServer extends EditRecord
}))
->columnSpan([
'default' => 2,
'sm' => 4,
'sm' => 2,
'md' => 2,
'lg' => 3,
])
@ -113,7 +119,7 @@ class EditServer extends EditRecord
->label('Owner')
->columnSpan([
'default' => 2,
'sm' => 4,
'sm' => 2,
'md' => 2,
'lg' => 3,
])
@ -123,138 +129,49 @@ class EditServer extends EditRecord
->required(),
Forms\Components\Textarea::make('description')
->hidden()
->required()
->label('Description')
->columnSpanFull(),
Forms\Components\Select::make('egg_id')
->disabledOn('edit')
->prefixIcon('tabler-egg')
Forms\Components\TextInput::make('uuid')
->hintAction(CopyAction::make())
->columnSpan([
'default' => 2,
'sm' => 2,
'md' => 2,
'lg' => 5,
'lg' => 3,
])
->relationship('egg', 'name')
->searchable()
->preload()
->required(),
Forms\Components\ToggleButtons::make('skip_scripts')
->label('Run Egg Install Script?')->inline()
->options([
false => 'Yes',
true => 'Skip',
])
->colors([
false => 'primary',
true => 'danger',
])
->icons([
false => 'tabler-code',
true => 'tabler-code-off',
])
->required(),
Forms\Components\Textarea::make('startup')
->hintIcon('tabler-code')
->label('Startup Command')
->required()
->live()
->readOnly(),
Forms\Components\TextInput::make('uuid_short')
->label('Short UUID')
->hintAction(CopyAction::make())
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
'sm' => 2,
'md' => 2,
'lg' => 3,
])
->rows(function ($state) {
return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
0
);
}),
Forms\Components\Hidden::make('start_on_completion'),
Forms\Components\Section::make('Egg Variables')
->icon('tabler-eggs')
->iconColor('primary')
->collapsible()
->collapsed()
->columnSpan(([
->readOnly(),
Forms\Components\TextInput::make('external_id')
->label('External ID')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
]))
->schema([
Forms\Components\Repeater::make('server_variables')
->relationship('serverVariables')
->grid()
->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array {
foreach ($data as $key => $value) {
if (!isset($data['variable_value'])) {
$data['variable_value'] = '';
}
}
return $data;
})
->reorderable(false)->addable(false)->deletable(false)
->schema(function () {
$text = Forms\Components\TextInput::make('variable_value')
->hidden($this->shouldHideComponent(...))
->maxLength(191)
->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 = Forms\Components\Select::make('variable_value')
->hidden($this->shouldHideComponent(...))
->options($this->getSelectOptionsFromRules(...))
->selectablePlaceholder(false);
$components = [$text, $select];
/** @var Forms\Components\Component $component */
foreach ($components as &$component) {
$component = $component
->live(onBlur: true)
->hintIcon('tabler-code')
->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name)
->hintIconTooltip(fn (ServerVariable $serverVariable) => $serverVariable->variable->rules)
->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}')
->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description);
}
return $components;
})
->columnSpan(2),
'sm' => 2,
'md' => 2,
'lg' => 3,
])
->maxLength(191),
Forms\Components\Select::make('node_id')
->label('Node')
->relationship('node', 'name')
->columnSpan([
'default' => 2,
'sm' => 2,
'md' => 2,
'lg' => 3,
])
->disabled(),
]),
Forms\Components\Section::make('Environment Management')
->collapsed()
->icon('tabler-server-cog')
->iconColor('primary')
->columns([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 4,
])
->columnSpanFull()
Tabs\Tab::make('Environment')
->schema([
Forms\Components\Fieldset::make('Resource Limits')
->columnSpan([
@ -509,7 +426,7 @@ class EditServer extends EditRecord
}
})
->placeholder('Enter a custom Image')
->columnSpan(1),
->columnSpan(2),
Forms\Components\KeyValue::make('docker_labels')
->label('Container Labels')
@ -518,6 +435,201 @@ class EditServer extends EditRecord
->columnSpanFull(),
]),
]),
Tabs\Tab::make('Egg')
->columns([
'default' => 1,
'sm' => 3,
'md' => 3,
'lg' => 5,
])
->schema([
Forms\Components\Select::make('egg_id')
->disabledOn('edit')
->prefixIcon('tabler-egg')
->columnSpan([
'default' => 1,
'sm' => 3,
'md' => 3,
'lg' => 5,
])
->relationship('egg', 'name')
->searchable()
->preload()
->required(),
Forms\Components\ToggleButtons::make('skip_scripts')
->label('Run Egg Install Script?')->inline()
->options([
false => 'Yes',
true => 'Skip',
])
->colors([
false => 'primary',
true => 'danger',
])
->icons([
false => 'tabler-code',
true => 'tabler-code-off',
])
->required(),
Forms\Components\Textarea::make('startup')
->label('Startup Command')
->required()
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->rows(function ($state) {
return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
0
);
}),
Forms\Components\Textarea::make('defaultStartup')
->hintAction(CopyAction::make())
->label('Default Startup Command')
->disabled()
->formatStateUsing(function ($state, Forms\Get $get, Forms\Set $set) {
$egg = Egg::query()->find($get('egg_id'));
return $egg->startup;
})
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
]),
Forms\Components\Repeater::make('server_variables')
->relationship('serverVariables')
->grid()
->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array {
foreach ($data as $key => $value) {
if (!isset($data['variable_value'])) {
$data['variable_value'] = '';
}
}
return $data;
})
->reorderable(false)->addable(false)->deletable(false)
->schema(function () {
$text = Forms\Components\TextInput::make('variable_value')
->hidden($this->shouldHideComponent(...))
->maxLength(191)
->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 = Forms\Components\Select::make('variable_value')
->hidden($this->shouldHideComponent(...))
->options($this->getSelectOptionsFromRules(...))
->selectablePlaceholder(false);
$components = [$text, $select];
/** @var Forms\Components\Component $component */
foreach ($components as &$component) {
$component = $component
->live(onBlur: true)
->hintIcon('tabler-code')
->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name)
->hintIconTooltip(fn (ServerVariable $serverVariable) => $serverVariable->variable->rules)
->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}')
->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description);
}
return $components;
})
->columnSpan(6),
]),
Tabs\Tab::make('Mounts'),
Tabs\Tab::make('Databases'),
Tabs\Tab::make('Actions')
->schema([
Forms\Components\Fieldset::make('Server Actions')
->columns([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 6,
])
->schema([
Forms\Components\Grid::make()
->columnSpan(3)
->schema([
Forms\Components\Actions::make([
Forms\Components\Actions\Action::make('toggleInstall')
->label('Toggle Status')
->action(fn (ServersController $serversController, Server $server) => $serversController->toggleInstall($server)),
])->fullWidth(),
Forms\Components\ToggleButtons::make('')
->hint('If you need to change the install status from uninstalled to installed, or vice versa, you may do so with this button.'),
]),
Forms\Components\Grid::make()
->columnSpan(3)
->schema([
Forms\Components\Actions::make([
Forms\Components\Actions\Action::make('toggleSuspend')
->label('Suspend')
->color('warning')
->hidden(fn (Server $server) => $server->isSuspended())
->action(fn (SuspensionService $suspensionService, Server $server) => $suspensionService->toggle($server, 'suspend')),
Forms\Components\Actions\Action::make('toggleUnsuspend')
->label('Unsuspend')
->color('success')
->hidden(fn (Server $server) => !$server->isSuspended())
->action(fn (SuspensionService $suspensionService, Server $server) => $suspensionService->toggle($server, 'unsuspend')),
])->fullWidth(),
Forms\Components\ToggleButtons::make('')
->hidden(fn (Server $server) => $server->isSuspended())
->hint('This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.'),
Forms\Components\ToggleButtons::make('')
->hidden(fn (Server $server) => !$server->isSuspended())
->hint('This will unsuspend the server and restore normal user access.'),
]),
Forms\Components\Grid::make()
->columnSpan(3)
->schema([
Forms\Components\Actions::make([
Forms\Components\Actions\Action::make('transfer')
->label('Transfer'),
])->fullWidth(),
Forms\Components\ToggleButtons::make('')
->hint('Transfer this server to another node connected to this panel. Warning! This feature has not been fully tested and may have bugs.'),
]),
Forms\Components\Grid::make()
->columnSpan(3)
->schema([
Forms\Components\Actions::make([
Forms\Components\Actions\Action::make('reinstall')
->label('Reinstall')
->color('danger')
->requiresConfirmation()
->action(fn (ServersController $serversController, Server $server) => $serversController->reinstallServer($server)),
])->fullWidth(),
Forms\Components\ToggleButtons::make('')
->hint('This will reinstall the server with the assigned egg scripts. Danger! This could overwrite server data.'),
]),
]),
]),
]),
]);
}
protected function getHeaderActions(): array
@ -543,6 +655,10 @@ class EditServer extends EditRecord
protected function mutateFormDataBeforeSave(array $data): array
{
if (!isset($data['description'])) {
$data['description'] = '';
}
unset($data['docker'], $data['status']);
return $data;

View File

@ -0,0 +1,588 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource;
use App\Services\Servers\RandomWordService;
use Filament\Actions;
use Filament\Forms;
use App\Enums\ContainerStatus;
use App\Enums\ServerState;
use App\Models\Egg;
use App\Models\Server;
use App\Models\ServerVariable;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Servers\ServerDeletionService;
use Filament\Forms\Form;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Validator;
use Closure;
class EditServerOrg extends EditRecord
{
protected static string $resource = ServerResource::class;
public function form(Form $form): Form
{
return $form
->columns([
'default' => 2,
'sm' => 2,
'md' => 4,
'lg' => 6,
])
->schema([
Forms\Components\ToggleButtons::make('docker')
->label('Container Status')->inline()->inlineLabel()
->formatStateUsing(function ($state, Server $server) {
if ($server->node_id === null) {
return 'unknown';
}
/** @var DaemonServerRepository $service */
$service = resolve(DaemonServerRepository::class);
$details = $service->setServer($server)->getDetails();
return $details['state'] ?? 'unknown';
})
->options(fn ($state) => collect(ContainerStatus::cases())->filter(fn ($containerStatus) => $containerStatus->value === $state)->mapWithKeys(
fn (ContainerStatus $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]
))
->colors(collect(ContainerStatus::cases())->mapWithKeys(
fn (ContainerStatus $status) => [$status->value => $status->color()]
))
->icons(collect(ContainerStatus::cases())->mapWithKeys(
fn (ContainerStatus $status) => [$status->value => $status->icon()]
))
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 3,
]),
Forms\Components\ToggleButtons::make('status')
->label('Server State')->inline()->inlineLabel()
->helperText('')
->formatStateUsing(fn ($state) => $state ?? ServerState::Normal)
->options(fn ($state) => collect(ServerState::cases())->filter(fn ($serverState) => $serverState->value === $state)->mapWithKeys(
fn (ServerState $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]
))
->colors(collect(ServerState::cases())->mapWithKeys(
fn (ServerState $state) => [$state->value => $state->color()]
))
->icons(collect(ServerState::cases())->mapWithKeys(
fn (ServerState $state) => [$state->value => $state->icon()]
))
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 3,
]),
Forms\Components\TextInput::make('external_id')
->maxLength(191)
->hidden(),
Forms\Components\TextInput::make('name')
->prefixIcon('tabler-server')
->label('Display Name')
->suffixAction(Forms\Components\Actions\Action::make('random')
->icon('tabler-dice-' . random_int(1, 6))
->action(function (Forms\Set $set, Forms\Get $get) {
$egg = Egg::find($get('egg_id'));
$prefix = $egg ? str($egg->name)->lower()->kebab() . '-' : '';
$word = (new RandomWordService())->word();
$set('name', $prefix . $word);
}))
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 2,
'lg' => 3,
])
->required()
->maxLength(191),
Forms\Components\Select::make('owner_id')
->prefixIcon('tabler-user')
->label('Owner')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 2,
'lg' => 3,
])
->relationship('user', 'username')
->searchable()
->preload()
->required(),
Forms\Components\Textarea::make('description')
->hidden()
->required()
->columnSpanFull(),
Forms\Components\Select::make('egg_id')
->disabledOn('edit')
->prefixIcon('tabler-egg')
->columnSpan([
'default' => 2,
'sm' => 2,
'md' => 2,
'lg' => 5,
])
->relationship('egg', 'name')
->searchable()
->preload()
->required(),
Forms\Components\ToggleButtons::make('skip_scripts')
->label('Run Egg Install Script?')->inline()
->options([
false => 'Yes',
true => 'Skip',
])
->colors([
false => 'primary',
true => 'danger',
])
->icons([
false => 'tabler-code',
true => 'tabler-code-off',
])
->required(),
Forms\Components\Textarea::make('startup')
->hintIcon('tabler-code')
->label('Startup Command')
->required()
->live()
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->rows(function ($state) {
return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
0
);
}),
Forms\Components\Hidden::make('start_on_completion'),
Forms\Components\Section::make('Egg Variables')
->icon('tabler-eggs')
->iconColor('primary')
->collapsible()
->collapsed()
->columnSpan(([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
]))
->schema([
Forms\Components\Repeater::make('server_variables')
->relationship('serverVariables')
->grid()
->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array {
foreach ($data as $key => $value) {
if (!isset($data['variable_value'])) {
$data['variable_value'] = '';
}
}
return $data;
})
->reorderable(false)->addable(false)->deletable(false)
->schema(function () {
$text = Forms\Components\TextInput::make('variable_value')
->hidden($this->shouldHideComponent(...))
->maxLength(191)
->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 = Forms\Components\Select::make('variable_value')
->hidden($this->shouldHideComponent(...))
->options($this->getSelectOptionsFromRules(...))
->selectablePlaceholder(false);
$components = [$text, $select];
/** @var Forms\Components\Component $component */
foreach ($components as &$component) {
$component = $component
->live(onBlur: true)
->hintIcon('tabler-code')
->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name)
->hintIconTooltip(fn (ServerVariable $serverVariable) => $serverVariable->variable->rules)
->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}')
->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description);
}
return $components;
})
->columnSpan(2),
]),
Forms\Components\Section::make('Environment Management')
->collapsed()
->icon('tabler-server-cog')
->iconColor('primary')
->columns([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 4,
])
->columnSpanFull()
->schema([
Forms\Components\Fieldset::make('Resource Limits')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('unlimited_mem')
->label('Memory')->inlineLabel()->inline()
->afterStateUpdated(fn (Forms\Set $set) => $set('memory', 0))
->formatStateUsing(fn (Forms\Get $get) => $get('memory') == 0)
->live()
->options([
true => 'Unlimited',
false => 'Limited',
])
->colors([
true => 'primary',
false => 'warning',
])
->columnSpan(2),
Forms\Components\TextInput::make('memory')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => $get('unlimited_mem'))
->label('Memory Limit')->inlineLabel()
->suffix('MiB')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('unlimited_disk')
->label('Disk Space')->inlineLabel()->inline()
->live()
->afterStateUpdated(fn (Forms\Set $set) => $set('disk', 0))
->formatStateUsing(fn (Forms\Get $get) => $get('disk') == 0)
->options([
true => 'Unlimited',
false => 'Limited',
])
->colors([
true => 'primary',
false => 'warning',
])
->columnSpan(2),
Forms\Components\TextInput::make('disk')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => $get('unlimited_disk'))
->label('Disk Space Limit')->inlineLabel()
->suffix('MiB')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('unlimited_cpu')
->label('CPU')->inlineLabel()->inline()
->afterStateUpdated(fn (Forms\Set $set) => $set('cpu', 0))
->formatStateUsing(fn (Forms\Get $get) => $get('cpu') == 0)
->live()
->options([
true => 'Unlimited',
false => 'Limited',
])
->colors([
true => 'primary',
false => 'warning',
])
->columnSpan(2),
Forms\Components\TextInput::make('cpu')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => $get('unlimited_cpu'))
->label('CPU Limit')->inlineLabel()
->suffix('%')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('swap_support')
->live()
->label('Enable Swap Memory')->inlineLabel()->inline()
->columnSpan(2)
->afterStateUpdated(function ($state, Forms\Set $set) {
$value = match ($state) {
'unlimited' => -1,
'disabled' => 0,
'limited' => 128,
};
$set('swap', $value);
})
->formatStateUsing(function (Forms\Get $get) {
return match (true) {
$get('swap') > 0 => 'limited',
$get('swap') == 0 => 'disabled',
$get('swap') < 0 => 'unlimited',
};
})
->options([
'unlimited' => 'Unlimited',
'limited' => 'Limited',
'disabled' => 'Disabled',
])
->colors([
'unlimited' => 'primary',
'limited' => 'warning',
'disabled' => 'danger',
]),
Forms\Components\TextInput::make('swap')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => match ($get('swap_support')) {
'disabled', 'unlimited', true => true,
'limited', false => false,
})
->label('Swap Memory')->inlineLabel()
->suffix('MiB')
->minValue(-1)
->columnSpan(2)
->required()
->integer(),
]),
Forms\Components\Hidden::make('io')
->helperText('The IO performance relative to other running containers')
->label('Block IO Proportion'),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('oom_killer')
->label('OOM Killer')->inlineLabel()->inline()
->columnSpan(2)
->options([
false => 'Disabled',
true => 'Enabled',
])
->colors([
false => 'success',
true => 'danger',
]),
Forms\Components\TextInput::make('oom_disabled_hidden')
->hidden(),
]),
]),
Forms\Components\Fieldset::make('Feature Limits')
->inlineLabel()
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Forms\Components\TextInput::make('allocation_limit')
->suffixIcon('tabler-network')
->required()
->numeric(),
Forms\Components\TextInput::make('database_limit')
->suffixIcon('tabler-database')
->required()
->numeric(),
Forms\Components\TextInput::make('backup_limit')
->suffixIcon('tabler-copy-check')
->required()
->numeric(),
]),
Forms\Components\Fieldset::make('Docker Settings')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Forms\Components\Select::make('select_image')
->label('Image Name')
->afterStateUpdated(fn (Forms\Set $set, $state) => $set('image', $state))
->options(function ($state, Forms\Get $get, Forms\Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
$currentImage = $get('image');
if (!$currentImage && $images) {
$defaultImage = collect($images)->first();
$set('image', $defaultImage);
$set('select_image', $defaultImage);
}
return array_flip($images) + ['ghcr.io/custom-image' => 'Custom Image'];
})
->selectablePlaceholder(false)
->columnSpan(1),
Forms\Components\TextInput::make('image')
->label('Image')
->debounce(500)
->afterStateUpdated(function ($state, Forms\Get $get, Forms\Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
if (in_array($state, $images)) {
$set('select_image', $state);
} else {
$set('select_image', 'ghcr.io/custom-image');
}
})
->placeholder('Enter a custom Image')
->columnSpan(1),
Forms\Components\KeyValue::make('docker_labels')
->label('Container Labels')
->keyLabel('Label Name')
->valueLabel('Label Description')
->columnSpanFull(),
]),
]),
]);
}
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make('Delete')
->successRedirectUrl(route('filament.admin.resources.servers.index'))
->color('danger')
->after(fn (Server $server) => resolve(ServerDeletionService::class)->handle($server))
->requiresConfirmation(),
Actions\Action::make('console')
->label('Console')
->icon('tabler-terminal')
->url(fn (Server $server) => "/server/$server->uuid_short"),
$this->getSaveFormAction()->formId('form'),
];
}
protected function getFormActions(): array
{
return [];
}
protected function mutateFormDataBeforeSave(array $data): array
{
unset($data['docker'], $data['status']);
return $data;
}
public function getRelationManagers(): array
{
return [
ServerResource\RelationManagers\AllocationsRelationManager::class,
];
}
private function shouldHideComponent(Forms\Get $get, Forms\Components\Component $component): bool
{
$containsRuleIn = str($get('rules'))->explode('|')->reduce(
fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true
);
if ($component instanceof Forms\Components\Select) {
return $containsRuleIn;
}
if ($component instanceof Forms\Components\TextInput) {
return !$containsRuleIn;
}
throw new \Exception('Component type not supported: ' . $component::class);
}
private function getSelectOptionsFromRules(Forms\Get $get): array
{
$inRule = str($get('rules'))->explode('|')->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();
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Enums\ServerState;
use Filament\Notifications\Notification;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Http\Response;
@ -70,7 +71,7 @@ class ServersController extends Controller
* @throws \App\Exceptions\DisplayException
* @throws \App\Exceptions\Model\DataValidationException
*/
public function toggleInstall(Server $server): RedirectResponse
public function toggleInstall(Server $server)
{
if ($server->status === ServerState::InstallFailed) {
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
@ -79,9 +80,13 @@ class ServersController extends Controller
$server->status = $server->isInstalled() ? ServerState::Installing : null;
$server->save();
$this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
Notification::make()
->title('Success!')
->body(trans('admin/server.alerts.install_toggled'))
->success()
->send();
return redirect()->route('admin.servers.view.manage', $server->id);
return null;
}
/**
@ -90,12 +95,15 @@ class ServersController extends Controller
* @throws \App\Exceptions\DisplayException
* @throws \App\Exceptions\Model\DataValidationException
*/
public function reinstallServer(Server $server): RedirectResponse
public function reinstallServer(Server $server)
{
$this->reinstallService->handle($server);
$this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash();
return redirect()->route('admin.servers.view.manage', $server->id);
Notification::make()
->title('Success!')
->body(trans('admin/server.alerts.server_reinstalled'))
->success()
->send();
}
/**

View File

@ -3,6 +3,7 @@
namespace App\Services\Servers;
use App\Enums\ServerState;
use Filament\Notifications\Notification;
use Webmozart\Assert\Assert;
use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
@ -26,7 +27,7 @@ class SuspensionService
*
* @throws \Throwable
*/
public function toggle(Server $server, string $action = self::ACTION_SUSPEND): void
public function toggle(Server $server, string $action = self::ACTION_SUSPEND)
{
Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]);
@ -35,11 +36,12 @@ class SuspensionService
// suspended in the database. Additionally, nothing needs to happen if the server
// is not suspended, and we try to un-suspend the instance.
if ($isSuspending === $server->isSuspended()) {
return;
return Notification::make()->danger()->title('Failed!')->body('Server is already suspended!')->send();
}
// Check if the server is currently being transferred.
if (!is_null($server->transfer)) {
Notification::make()->danger()->title('Failed!')->body('Server is currently being transferred.')->send();
throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.');
}