diff --git a/app/Filament/Pages/Installer/Steps/AdminUserStep.php b/app/Filament/Pages/Installer/Steps/AdminUserStep.php
index f5c2a19b34d..e48f1d1d22f 100644
--- a/app/Filament/Pages/Installer/Steps/AdminUserStep.php
+++ b/app/Filament/Pages/Installer/Steps/AdminUserStep.php
@@ -11,6 +11,7 @@ class AdminUserStep
public static function make(PanelInstaller $installer): Step
{
return Step::make('user')
+ ->afterValidation(fn () => $installer->createAdminUser())
->label('Admin User')
->schema([
TextInput::make('user.email')
@@ -27,7 +28,6 @@ public static function make(PanelInstaller $installer): Step
->required()
->password()
->revealable(),
- ])
- ->afterValidation(fn () => $installer->createAdminUser());
+ ]);
}
}
diff --git a/app/Filament/Pages/Installer/Steps/DatabaseStep.php b/app/Filament/Pages/Installer/Steps/DatabaseStep.php
index 29c47b32908..48342f63919 100644
--- a/app/Filament/Pages/Installer/Steps/DatabaseStep.php
+++ b/app/Filament/Pages/Installer/Steps/DatabaseStep.php
@@ -16,6 +16,17 @@ class DatabaseStep
public static function make(PanelInstaller $installer): Step
{
return Step::make('database')
+ ->afterValidation(function (Get $get) use ($installer) {
+ $driver = $get('env_general.DB_CONNECTION');
+
+ if (!self::testConnection($driver, $get('env_database.DB_HOST'), $get('env_database.DB_PORT'), $get('env_database.DB_DATABASE'), $get('env_database.DB_USERNAME'), $get('env_database.DB_PASSWORD'))) {
+ throw new Halt('Database connection failed');
+ }
+
+ $installer->writeToEnv('env_database');
+
+ $installer->runMigrations($driver);
+ })
->label('Database')
->columns()
->schema([
@@ -58,18 +69,7 @@ public static function make(PanelInstaller $installer): Step
->revealable()
->default(env('DB_PASSWORD'))
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
- ])
- ->afterValidation(function (Get $get) use ($installer) {
- $driver = $get('env_general.DB_CONNECTION');
-
- if (!self::testConnection($driver, $get('env_database.DB_HOST'), $get('env_database.DB_PORT'), $get('env_database.DB_DATABASE'), $get('env_database.DB_USERNAME'), $get('env_database.DB_PASSWORD'))) {
- throw new Halt('Database connection failed');
- }
-
- $installer->writeToEnv('env_database');
-
- $installer->runMigrations($driver);
- });
+ ]);
}
private static function testConnection(string $driver, $host, $port, $database, $username, $password): bool
diff --git a/app/Filament/Pages/Installer/Steps/EnvironmentStep.php b/app/Filament/Pages/Installer/Steps/EnvironmentStep.php
index 5df2ecd40ad..d3e4a6aaf0c 100644
--- a/app/Filament/Pages/Installer/Steps/EnvironmentStep.php
+++ b/app/Filament/Pages/Installer/Steps/EnvironmentStep.php
@@ -40,6 +40,7 @@ class EnvironmentStep
public static function make(PanelInstaller $installer): Step
{
return Step::make('environment')
+ ->afterValidation(fn () => $installer->writeToEnv('env_general'))
->label('Environment')
->columns()
->schema([
@@ -92,7 +93,6 @@ public static function make(PanelInstaller $installer): Step
->inline()
->options(self::DATABASE_DRIVERS)
->default(config('database.default', 'sqlite')),
- ])
- ->afterValidation(fn () => $installer->writeToEnv('env_general'));
+ ]);
}
}
diff --git a/app/Filament/Pages/Installer/Steps/RedisStep.php b/app/Filament/Pages/Installer/Steps/RedisStep.php
index 00ecb18afea..0d3e16f44f3 100644
--- a/app/Filament/Pages/Installer/Steps/RedisStep.php
+++ b/app/Filament/Pages/Installer/Steps/RedisStep.php
@@ -19,6 +19,13 @@ class RedisStep
public static function make(PanelInstaller $installer): Step
{
return Step::make('redis')
+ ->afterValidation(function (Get $get) use ($installer) {
+ if (!self::testConnection($get('env_redis.REDIS_HOST'), $get('env_redis.REDIS_PORT'), $get('env_redis.REDIS_USERNAME'), $get('env_redis.REDIS_PASSWORD'))) {
+ throw new Halt('Redis connection failed');
+ }
+
+ $installer->writeToEnv('env_redis');
+ })
->label('Redis')
->columns()
->schema([
@@ -46,14 +53,7 @@ public static function make(PanelInstaller $installer): Step
->password()
->revealable()
->default(config('database.redis.default.password')),
- ])
- ->afterValidation(function (Get $get) use ($installer) {
- if (!self::testConnection($get('env_redis.REDIS_HOST'), $get('env_redis.REDIS_PORT'), $get('env_redis.REDIS_USERNAME'), $get('env_redis.REDIS_PASSWORD'))) {
- throw new Halt('Redis connection failed');
- }
-
- $installer->writeToEnv('env_redis');
- });
+ ]);
}
private static function testConnection($host, $port, $username, $password): bool
diff --git a/app/Filament/Pages/Installer/Steps/RequirementsStep.php b/app/Filament/Pages/Installer/Steps/RequirementsStep.php
index 5e6d6988d1d..31f32ef830a 100644
--- a/app/Filament/Pages/Installer/Steps/RequirementsStep.php
+++ b/app/Filament/Pages/Installer/Steps/RequirementsStep.php
@@ -73,8 +73,6 @@ public static function make(): Step
]);
return Step::make('requirements')
- ->label('Server Requirements')
- ->schema($fields)
->afterValidation(function () use ($correctPhpVersion, $allExtensionsInstalled, $correctFolderPermissions) {
if (!$correctPhpVersion || !$allExtensionsInstalled || !$correctFolderPermissions) {
Notification::make()
@@ -84,6 +82,8 @@ public static function make(): Step
throw new Halt('Some requirements are missing');
}
- });
+ })
+ ->label('Server Requirements')
+ ->schema($fields);
}
}
diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php
index f3ef2c2b9a1..7c6c4ff72a2 100644
--- a/app/Filament/Pages/Settings.php
+++ b/app/Filament/Pages/Settings.php
@@ -44,11 +44,6 @@ class Settings extends Page implements HasForms
public ?array $data = [];
- public function mount(): void
- {
- $this->form->fill();
- }
-
public static function canAccess(): bool
{
return auth()->user()->can('view settings');
diff --git a/app/Filament/Resources/EggResource/Pages/CreateEgg.php b/app/Filament/Resources/EggResource/Pages/CreateEgg.php
index 5bcaed5a793..c813cb8a772 100644
--- a/app/Filament/Resources/EggResource/Pages/CreateEgg.php
+++ b/app/Filament/Resources/EggResource/Pages/CreateEgg.php
@@ -31,241 +31,241 @@ public function form(Form $form): Form
{
return $form
->schema([
- Tabs::make()->tabs([
- Tab::make('Configuration')
- ->columns([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 4,
- ])
- ->schema([
- TextInput::make('name')
- ->required()
- ->maxLength(255)
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->helperText('A simple, human-readable name to use as an identifier for this Egg.'),
- TextInput::make('author')
- ->maxLength(255)
- ->required()
- ->email()
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->helperText('The author of this version of the Egg.'),
- Textarea::make('description')
- ->rows(3)
- ->columnSpanFull()
- ->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'),
- Textarea::make('startup')
- ->rows(3)
- ->columnSpanFull()
- ->required()
- ->placeholder(implode("\n", [
- 'java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}',
- ]))
- ->helperText('The default startup command that should be used for new servers using this Egg.'),
- TagsInput::make('features')
- ->placeholder('Add Feature')
- ->helperText('')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ]),
- Toggle::make('force_outgoing_ip')
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
+ Tabs::make()
+ ->columnSpanFull()
+ ->persistTabInQueryString()
+ ->tabs([
+ Tab::make('Configuration')
+ ->columns([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 4,
+ ])
+ ->schema([
+ TextInput::make('name')
+ ->required()
+ ->maxLength(255)
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->helperText('A simple, human-readable name to use as an identifier for this Egg.'),
+ TextInput::make('author')
+ ->maxLength(255)
+ ->required()
+ ->email()
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->helperText('The author of this version of the Egg.'),
+ Textarea::make('description')
+ ->rows(3)
+ ->columnSpanFull()
+ ->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'),
+ Textarea::make('startup')
+ ->rows(3)
+ ->columnSpanFull()
+ ->required()
+ ->placeholder(implode("\n", [
+ 'java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}',
+ ]))
+ ->helperText('The default startup command that should be used for new servers using this Egg.'),
+ TagsInput::make('features')
+ ->placeholder('Add Feature')
+ ->helperText('')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ]),
+ Toggle::make('force_outgoing_ip')
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
Required for certain games to work properly when the Node has multiple public IP addresses.
Enabling this option will disable internal networking for any servers using this egg, causing them to be unable to internally access other servers on the same node."),
- Hidden::make('script_is_privileged')
- ->default(1),
- TagsInput::make('tags')
- ->placeholder('Add Tags')
- ->helperText('')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ]),
- TextInput::make('update_url')
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('URLs must point directly to the raw .json file.')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->url(),
- KeyValue::make('docker_images')
- ->live()
- ->columnSpanFull()
- ->required()
- ->addActionLabel('Add Image')
- ->keyLabel('Name')
- ->keyPlaceholder('Java 21')
- ->valueLabel('Image URI')
- ->valuePlaceholder('ghcr.io/parkervcp/yolks:java_21')
- ->helperText('The docker images available to servers using this egg.'),
- ]),
-
- Tab::make('Process Management')
- ->columns()
- ->schema([
- Hidden::make('config_from')
- ->default(null)
- ->label('Copy Settings From')
- // ->placeholder('None')
- // ->relationship('configFrom', 'name', ignoreRecord: true)
- ->helperText('If you would like to default to settings from another Egg select it from the menu above.'),
- TextInput::make('config_stop')
- ->required()
- ->maxLength(255)
- ->label('Stop Command')
- ->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'),
- Textarea::make('config_startup')
- ->rows(10)
- ->json()
- ->label('Start Configuration')
- ->default('{}')
- ->helperText('List of values the daemon should be looking for when booting a server to determine completion.'),
- Textarea::make('config_files')
- ->rows(10)
- ->json()
- ->label('Configuration Files')
- ->default('{}')
- ->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'),
- Textarea::make('config_logs')
- ->rows(10)
- ->json()
- ->label('Log Configuration')
- ->default('{}')
- ->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'),
- ]),
- Tab::make('Egg Variables')
- ->columnSpanFull()
- ->schema([
- Repeater::make('variables')
- ->label('')
- ->addActionLabel('Add New Egg Variable')
- ->grid()
- ->relationship('variables')
- ->name('name')
- ->reorderable()
- ->orderColumn()
- ->collapsible()
- ->collapsed()
- ->columnSpan(2)
- ->defaultItems(0)
- ->itemLabel(fn (array $state) => $state['name'])
- ->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
- $data['default_value'] ??= '';
- $data['description'] ??= '';
- $data['rules'] ??= [];
- $data['user_viewable'] ??= '';
- $data['user_editable'] ??= '';
+ Hidden::make('script_is_privileged')
+ ->default(1),
+ TagsInput::make('tags')
+ ->placeholder('Add Tags')
+ ->helperText('')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ]),
+ TextInput::make('update_url')
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('URLs must point directly to the raw .json file.')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->url(),
+ KeyValue::make('docker_images')
+ ->live()
+ ->columnSpanFull()
+ ->required()
+ ->addActionLabel('Add Image')
+ ->keyLabel('Name')
+ ->keyPlaceholder('Java 21')
+ ->valueLabel('Image URI')
+ ->valuePlaceholder('ghcr.io/parkervcp/yolks:java_21')
+ ->helperText('The docker images available to servers using this egg.'),
+ ]),
- return $data;
- })
- ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
- $data['default_value'] ??= '';
- $data['description'] ??= '';
- $data['rules'] ??= [];
- $data['user_viewable'] ??= '';
- $data['user_editable'] ??= '';
+ Tab::make('Process Management')
+ ->columns()
+ ->schema([
+ Hidden::make('config_from')
+ ->default(null)
+ ->label('Copy Settings From')
+ // ->placeholder('None')
+ // ->relationship('configFrom', 'name', ignoreRecord: true)
+ ->helperText('If you would like to default to settings from another Egg select it from the menu above.'),
+ TextInput::make('config_stop')
+ ->required()
+ ->maxLength(255)
+ ->label('Stop Command')
+ ->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'),
+ Textarea::make('config_startup')
+ ->rows(10)
+ ->json()
+ ->label('Start Configuration')
+ ->default('{}')
+ ->helperText('List of values the daemon should be looking for when booting a server to determine completion.'),
+ Textarea::make('config_files')
+ ->rows(10)
+ ->json()
+ ->label('Configuration Files')
+ ->default('{}')
+ ->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'),
+ Textarea::make('config_logs')
+ ->rows(10)
+ ->json()
+ ->label('Log Configuration')
+ ->default('{}')
+ ->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'),
+ ]),
+ Tab::make('Egg Variables')
+ ->columnSpanFull()
+ ->schema([
+ Repeater::make('variables')
+ ->label('')
+ ->addActionLabel('Add New Egg Variable')
+ ->grid()
+ ->relationship('variables')
+ ->name('name')
+ ->reorderable()
+ ->orderColumn()
+ ->collapsible()
+ ->collapsed()
+ ->columnSpan(2)
+ ->defaultItems(0)
+ ->itemLabel(fn (array $state) => $state['name'])
+ ->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
+ $data['default_value'] ??= '';
+ $data['description'] ??= '';
+ $data['rules'] ??= [];
+ $data['user_viewable'] ??= '';
+ $data['user_editable'] ??= '';
- return $data;
- })
- ->schema([
- TextInput::make('name')
- ->live()
- ->debounce(750)
- ->maxLength(255)
- ->columnSpanFull()
- ->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
- ->required(),
- Textarea::make('description')->columnSpanFull(),
- TextInput::make('env_variable')
- ->label('Environment Variable')
- ->maxLength(255)
- ->prefix('{{')
- ->suffix('}}')
- ->hintIcon('tabler-code')
- ->hintIconTooltip(fn ($state) => "{{{$state}}}")
- ->required(),
- TextInput::make('default_value')->maxLength(255),
- Fieldset::make('User Permissions')
- ->schema([
- Checkbox::make('user_viewable')->label('Viewable'),
- Checkbox::make('user_editable')->label('Editable'),
- ]),
- TagsInput::make('rules')
- ->columnSpanFull()
- ->placeholder('Add Rule')
- ->reorderable()
- ->suggestions([
- 'required',
- 'nullable',
- 'string',
- 'integer',
- 'numeric',
- 'boolean',
- 'alpha',
- 'alpha_dash',
- 'alpha_num',
- 'url',
- 'email',
- 'regex:',
- 'min:',
- 'max:',
- 'between:',
- 'between:1024,65535',
- 'in:',
- 'in:true,false',
- ]),
- ]),
- ]),
- Tab::make('Install Script')
- ->columns(3)
- ->schema([
+ return $data;
+ })
+ ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
+ $data['default_value'] ??= '';
+ $data['description'] ??= '';
+ $data['rules'] ??= [];
+ $data['user_viewable'] ??= '';
+ $data['user_editable'] ??= '';
- Hidden::make('copy_script_from'),
- //->placeholder('None')
- //->relationship('scriptFrom', 'name', ignoreRecord: true),
+ return $data;
+ })
+ ->schema([
+ TextInput::make('name')
+ ->live()
+ ->debounce(750)
+ ->maxLength(255)
+ ->columnSpanFull()
+ ->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
+ ->required(),
+ Textarea::make('description')->columnSpanFull(),
+ TextInput::make('env_variable')
+ ->label('Environment Variable')
+ ->maxLength(255)
+ ->prefix('{{')
+ ->suffix('}}')
+ ->hintIcon('tabler-code')
+ ->hintIconTooltip(fn ($state) => "{{{$state}}}")
+ ->required(),
+ TextInput::make('default_value')->maxLength(255),
+ Fieldset::make('User Permissions')
+ ->schema([
+ Checkbox::make('user_viewable')->label('Viewable'),
+ Checkbox::make('user_editable')->label('Editable'),
+ ]),
+ TagsInput::make('rules')
+ ->columnSpanFull()
+ ->placeholder('Add Rule')
+ ->reorderable()
+ ->suggestions([
+ 'required',
+ 'nullable',
+ 'string',
+ 'integer',
+ 'numeric',
+ 'boolean',
+ 'alpha',
+ 'alpha_dash',
+ 'alpha_num',
+ 'url',
+ 'email',
+ 'regex:',
+ 'min:',
+ 'max:',
+ 'between:',
+ 'between:1024,65535',
+ 'in:',
+ 'in:true,false',
+ ]),
+ ]),
+ ]),
+ Tab::make('Install Script')
+ ->columns(3)
+ ->schema([
- TextInput::make('script_container')
- ->required()
- ->maxLength(255)
- ->default('ghcr.io/pelican-eggs/installers:debian'),
+ Hidden::make('copy_script_from'),
+ //->placeholder('None')
+ //->relationship('scriptFrom', 'name', ignoreRecord: true),
- Select::make('script_entry')
- ->selectablePlaceholder(false)
- ->default('bash')
- ->options(['bash', 'ash', '/bin/bash'])
- ->required(),
+ TextInput::make('script_container')
+ ->required()
+ ->maxLength(255)
+ ->default('ghcr.io/pelican-eggs/installers:debian'),
- MonacoEditor::make('script_install')
- ->columnSpanFull()
- ->fontSize('16px')
- ->language('shell')
- ->lazy()
- ->view('filament.plugins.monaco-editor'),
- ]),
+ Select::make('script_entry')
+ ->selectablePlaceholder(false)
+ ->default('bash')
+ ->options(['bash', 'ash', '/bin/bash'])
+ ->required(),
- ])
- ->columnSpanFull()
- ->persistTabInQueryString(),
+ MonacoEditor::make('script_install')
+ ->columnSpanFull()
+ ->fontSize('16px')
+ ->language('shell')
+ ->lazy()
+ ->view('filament.plugins.monaco-editor'),
+ ]),
+ ]),
]);
}
diff --git a/app/Filament/Resources/EggResource/Pages/EditEgg.php b/app/Filament/Resources/EggResource/Pages/EditEgg.php
index 21089421779..6cd0ebe9edc 100644
--- a/app/Filament/Resources/EggResource/Pages/EditEgg.php
+++ b/app/Filament/Resources/EggResource/Pages/EditEgg.php
@@ -37,252 +37,253 @@ public function form(Form $form): Form
{
return $form
->schema([
- Tabs::make()->tabs([
- Tab::make('Configuration')
- ->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
- ->icon('tabler-egg')
- ->schema([
- TextInput::make('name')
- ->required()
- ->maxLength(255)
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 1,
- ])
- ->helperText('A simple, human-readable name to use as an identifier for this Egg.'),
- TextInput::make('uuid')
- ->label('Egg UUID')
- ->disabled()
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->helperText('This is the globally unique identifier for this Egg which Wings uses as an identifier.'),
- TextInput::make('id')
- ->label('Egg ID')
- ->disabled(),
- Textarea::make('description')
- ->rows(3)
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'),
- TextInput::make('author')
- ->required()
- ->maxLength(255)
- ->email()
- ->disabled()
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->helperText('The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.'),
- Textarea::make('startup')
- ->rows(2)
- ->columnSpanFull()
- ->required()
- ->helperText('The default startup command that should be used for new servers using this Egg.'),
- TagsInput::make('file_denylist')
- ->hidden() // latest wings breaks it.
- ->placeholder('denied-file.txt')
- ->helperText('A list of files that the end user is not allowed to edit.')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ]),
- TagsInput::make('features')
- ->placeholder('Add Feature')
- ->helperText('')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ]),
- Toggle::make('force_outgoing_ip')
- ->inline(false)
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
+ Tabs::make()
+ ->columnSpanFull()
+ ->persistTabInQueryString()
+ ->tabs([
+ Tab::make('Configuration')
+ ->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
+ ->icon('tabler-egg')
+ ->schema([
+ TextInput::make('name')
+ ->required()
+ ->maxLength(255)
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 1,
+ ])
+ ->helperText('A simple, human-readable name to use as an identifier for this Egg.'),
+ TextInput::make('uuid')
+ ->label('Egg UUID')
+ ->disabled()
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->helperText('This is the globally unique identifier for this Egg which Wings uses as an identifier.'),
+ TextInput::make('id')
+ ->label('Egg ID')
+ ->disabled(),
+ Textarea::make('description')
+ ->rows(3)
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'),
+ TextInput::make('author')
+ ->required()
+ ->maxLength(255)
+ ->email()
+ ->disabled()
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->helperText('The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.'),
+ Textarea::make('startup')
+ ->rows(2)
+ ->columnSpanFull()
+ ->required()
+ ->helperText('The default startup command that should be used for new servers using this Egg.'),
+ TagsInput::make('file_denylist')
+ ->hidden() // latest wings breaks it.
+ ->placeholder('denied-file.txt')
+ ->helperText('A list of files that the end user is not allowed to edit.')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ]),
+ TagsInput::make('features')
+ ->placeholder('Add Feature')
+ ->helperText('')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ]),
+ Toggle::make('force_outgoing_ip')
+ ->inline(false)
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
Required for certain games to work properly when the Node has multiple public IP addresses.
Enabling this option will disable internal networking for any servers using this egg, causing them to be unable to internally access other servers on the same node."),
- Hidden::make('script_is_privileged')
- ->helperText('The docker images available to servers using this egg.'),
- TagsInput::make('tags')
- ->placeholder('Add Tags')
- ->helperText('')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ]),
- TextInput::make('update_url')
- ->label('Update URL')
- ->url()
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('URLs must point directly to the raw .json file.')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ]),
- KeyValue::make('docker_images')
- ->live()
- ->columnSpanFull()
- ->required()
- ->addActionLabel('Add Image')
- ->keyLabel('Name')
- ->valueLabel('Image URI')
- ->helperText('The docker images available to servers using this egg.'),
- ]),
- Tab::make('Process Management')
- ->columns()
- ->icon('tabler-server-cog')
- ->schema([
- Select::make('config_from')
- ->label('Copy Settings From')
- ->placeholder('None')
- ->relationship('configFrom', 'name', ignoreRecord: true)
- ->helperText('If you would like to default to settings from another Egg select it from the menu above.'),
- TextInput::make('config_stop')
- ->maxLength(255)
- ->label('Stop Command')
- ->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'),
- Textarea::make('config_startup')
- ->rows(10)
- ->json()
- ->label('Start Configuration')
- ->helperText('List of values the daemon should be looking for when booting a server to determine completion.'),
- Textarea::make('config_files')
- ->rows(10)
- ->json()
- ->label('Configuration Files')
- ->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'),
- Textarea::make('config_logs')
- ->rows(10)
- ->json()
- ->label('Log Configuration')
- ->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'),
- ]),
- Tab::make('Egg Variables')
- ->columnSpanFull()
- ->icon('tabler-variable')
- ->schema([
- Repeater::make('variables')
- ->label('')
- ->grid()
- ->relationship('variables')
- ->name('name')
- ->reorderable()
- ->collapsible()
- ->collapsed()
- ->orderColumn()
- ->addActionLabel('New Variable')
- ->itemLabel(fn (array $state) => $state['name'])
- ->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
- $data['default_value'] ??= '';
- $data['description'] ??= '';
- $data['rules'] ??= [];
- $data['user_viewable'] ??= '';
- $data['user_editable'] ??= '';
+ Hidden::make('script_is_privileged')
+ ->helperText('The docker images available to servers using this egg.'),
+ TagsInput::make('tags')
+ ->placeholder('Add Tags')
+ ->helperText('')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ]),
+ TextInput::make('update_url')
+ ->label('Update URL')
+ ->url()
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('URLs must point directly to the raw .json file.')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ]),
+ KeyValue::make('docker_images')
+ ->live()
+ ->columnSpanFull()
+ ->required()
+ ->addActionLabel('Add Image')
+ ->keyLabel('Name')
+ ->valueLabel('Image URI')
+ ->helperText('The docker images available to servers using this egg.'),
+ ]),
+ Tab::make('Process Management')
+ ->columns()
+ ->icon('tabler-server-cog')
+ ->schema([
+ Select::make('config_from')
+ ->label('Copy Settings From')
+ ->placeholder('None')
+ ->relationship('configFrom', 'name', ignoreRecord: true)
+ ->helperText('If you would like to default to settings from another Egg select it from the menu above.'),
+ TextInput::make('config_stop')
+ ->maxLength(255)
+ ->label('Stop Command')
+ ->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'),
+ Textarea::make('config_startup')
+ ->rows(10)
+ ->json()
+ ->label('Start Configuration')
+ ->helperText('List of values the daemon should be looking for when booting a server to determine completion.'),
+ Textarea::make('config_files')
+ ->rows(10)
+ ->json()
+ ->label('Configuration Files')
+ ->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'),
+ Textarea::make('config_logs')
+ ->rows(10)
+ ->json()
+ ->label('Log Configuration')
+ ->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'),
+ ]),
+ Tab::make('Egg Variables')
+ ->columnSpanFull()
+ ->icon('tabler-variable')
+ ->schema([
+ Repeater::make('variables')
+ ->label('')
+ ->grid()
+ ->relationship('variables')
+ ->name('name')
+ ->reorderable()
+ ->collapsible()
+ ->collapsed()
+ ->orderColumn()
+ ->addActionLabel('New Variable')
+ ->itemLabel(fn (array $state) => $state['name'])
+ ->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
+ $data['default_value'] ??= '';
+ $data['description'] ??= '';
+ $data['rules'] ??= [];
+ $data['user_viewable'] ??= '';
+ $data['user_editable'] ??= '';
- return $data;
- })
- ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
- $data['default_value'] ??= '';
- $data['description'] ??= '';
- $data['rules'] ??= [];
- $data['user_viewable'] ??= '';
- $data['user_editable'] ??= '';
+ return $data;
+ })
+ ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
+ $data['default_value'] ??= '';
+ $data['description'] ??= '';
+ $data['rules'] ??= [];
+ $data['user_viewable'] ??= '';
+ $data['user_editable'] ??= '';
- return $data;
- })
- ->schema([
- TextInput::make('name')
- ->live()
- ->debounce(750)
- ->maxLength(255)
- ->columnSpanFull()
- ->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
- ->required(),
- Textarea::make('description')->columnSpanFull(),
- TextInput::make('env_variable')
- ->label('Environment Variable')
- ->maxLength(255)
- ->prefix('{{')
- ->suffix('}}')
- ->hintIcon('tabler-code')
- ->hintIconTooltip(fn ($state) => "{{{$state}}}")
- ->required(),
- TextInput::make('default_value')->maxLength(255),
- Fieldset::make('User Permissions')
- ->schema([
- Checkbox::make('user_viewable')->label('Viewable'),
- Checkbox::make('user_editable')->label('Editable'),
- ]),
- TagsInput::make('rules')
- ->columnSpanFull()
- ->placeholder('Add Rule')
- ->reorderable()
- ->suggestions([
- 'required',
- 'nullable',
- 'string',
- 'integer',
- 'numeric',
- 'boolean',
- 'alpha',
- 'alpha_dash',
- 'alpha_num',
- 'url',
- 'email',
- 'regex:',
- 'min:',
- 'max:',
- 'between:',
- 'between:1024,65535',
- 'in:',
- 'in:true,false',
- ]),
- ]),
- ]),
- Tab::make('Install Script')
- ->columns(3)
- ->icon('tabler-file-download')
- ->schema([
- Select::make('copy_script_from')
- ->placeholder('None')
- ->relationship('scriptFrom', 'name', ignoreRecord: true),
- TextInput::make('script_container')
- ->required()
- ->maxLength(255)
- ->default('alpine:3.4'),
- TextInput::make('script_entry')
- ->required()
- ->maxLength(255)
- ->default('ash'),
- MonacoEditor::make('script_install')
- ->label('Install Script')
- ->columnSpanFull()
- ->fontSize('16px')
- ->language('shell')
- ->view('filament.plugins.monaco-editor'),
- ]),
- ])
- ->columnSpanFull()
- ->persistTabInQueryString(),
+ return $data;
+ })
+ ->schema([
+ TextInput::make('name')
+ ->live()
+ ->debounce(750)
+ ->maxLength(255)
+ ->columnSpanFull()
+ ->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
+ ->required(),
+ Textarea::make('description')->columnSpanFull(),
+ TextInput::make('env_variable')
+ ->label('Environment Variable')
+ ->maxLength(255)
+ ->prefix('{{')
+ ->suffix('}}')
+ ->hintIcon('tabler-code')
+ ->hintIconTooltip(fn ($state) => "{{{$state}}}")
+ ->required(),
+ TextInput::make('default_value')->maxLength(255),
+ Fieldset::make('User Permissions')
+ ->schema([
+ Checkbox::make('user_viewable')->label('Viewable'),
+ Checkbox::make('user_editable')->label('Editable'),
+ ]),
+ TagsInput::make('rules')
+ ->columnSpanFull()
+ ->placeholder('Add Rule')
+ ->reorderable()
+ ->suggestions([
+ 'required',
+ 'nullable',
+ 'string',
+ 'integer',
+ 'numeric',
+ 'boolean',
+ 'alpha',
+ 'alpha_dash',
+ 'alpha_num',
+ 'url',
+ 'email',
+ 'regex:',
+ 'min:',
+ 'max:',
+ 'between:',
+ 'between:1024,65535',
+ 'in:',
+ 'in:true,false',
+ ]),
+ ]),
+ ]),
+ Tab::make('Install Script')
+ ->columns(3)
+ ->icon('tabler-file-download')
+ ->schema([
+ Select::make('copy_script_from')
+ ->placeholder('None')
+ ->relationship('scriptFrom', 'name', ignoreRecord: true),
+ TextInput::make('script_container')
+ ->required()
+ ->maxLength(255)
+ ->default('alpine:3.4'),
+ TextInput::make('script_entry')
+ ->required()
+ ->maxLength(255)
+ ->default('ash'),
+ MonacoEditor::make('script_install')
+ ->label('Install Script')
+ ->columnSpanFull()
+ ->fontSize('16px')
+ ->language('shell')
+ ->view('filament.plugins.monaco-editor'),
+ ]),
+ ]),
]);
}
@@ -301,33 +302,6 @@ protected function getHeaderActions(): array
->authorize(fn () => auth()->user()->can('export egg')),
Actions\Action::make('importEgg')
->label('Import')
- ->form([
- Placeholder::make('warning')
- ->label('This will overwrite the current egg to the one you upload.'),
- Tabs::make('Tabs')
- ->tabs([
- Tab::make('From File')
- ->icon('tabler-file-upload')
- ->schema([
- FileUpload::make('egg')
- ->label('Egg')
- ->hint('eg. minecraft.json')
- ->acceptedFileTypes(['application/json'])
- ->storeFiles(false),
- ]),
- Tab::make('From URL')
- ->icon('tabler-world-upload')
- ->schema([
- TextInput::make('url')
- ->label('URL')
- ->default(fn (Egg $egg): ?string => $egg->update_url)
- ->hint('Link to the egg file (eg. minecraft.json)')
- ->url(),
- ]),
- ])
- ->contained(false),
-
- ])
->action(function (array $data, Egg $egg): void {
/** @var EggImporterService $eggImportService */
$eggImportService = resolve(EggImporterService::class);
@@ -368,7 +342,33 @@ protected function getHeaderActions(): array
->success()
->send();
})
- ->authorize(fn () => auth()->user()->can('import egg')),
+ ->authorize(fn () => auth()->user()->can('import egg'))
+ ->form([
+ Placeholder::make('warning')
+ ->label('This will overwrite the current egg to the one you upload.'),
+ Tabs::make('Tabs')
+ ->contained(false)
+ ->tabs([
+ Tab::make('From File')
+ ->icon('tabler-file-upload')
+ ->schema([
+ FileUpload::make('egg')
+ ->label('Egg')
+ ->hint('eg. minecraft.json')
+ ->acceptedFileTypes(['application/json'])
+ ->storeFiles(false),
+ ]),
+ Tab::make('From URL')
+ ->icon('tabler-world-upload')
+ ->schema([
+ TextInput::make('url')
+ ->label('URL')
+ ->default(fn (Egg $egg): ?string => $egg->update_url)
+ ->hint('Link to the egg file (eg. minecraft.json)')
+ ->url(),
+ ]),
+ ]),
+ ]),
$this->getSaveFormAction()->formId('form'),
];
}
diff --git a/app/Filament/Resources/EggResource/Pages/ListEggs.php b/app/Filament/Resources/EggResource/Pages/ListEggs.php
index 1a6a3b65090..63c016e9a46 100644
--- a/app/Filament/Resources/EggResource/Pages/ListEggs.php
+++ b/app/Filament/Resources/EggResource/Pages/ListEggs.php
@@ -72,31 +72,6 @@ protected function getHeaderActions(): array
Actions\Action::make('import')
->label('Import')
- ->form([
- Tabs::make('Tabs')
- ->tabs([
- Tab::make('From File')
- ->icon('tabler-file-upload')
- ->schema([
- FileUpload::make('egg')
- ->label('Egg')
- ->hint('This should be the json file ( egg-minecraft.json )')
- ->acceptedFileTypes(['application/json'])
- ->storeFiles(false)
- ->multiple(),
- ]),
- Tab::make('From URL')
- ->icon('tabler-world-upload')
- ->schema([
- TextInput::make('url')
- ->label('URL')
- ->hint('This URL should point to a single json file')
- ->url(),
- ]),
- ])
- ->contained(false),
-
- ])
->action(function (array $data): void {
/** @var EggImporterService $eggImportService */
$eggImportService = resolve(EggImporterService::class);
@@ -141,7 +116,31 @@ protected function getHeaderActions(): array
->success()
->send();
})
- ->authorize(fn () => auth()->user()->can('import egg')),
+ ->authorize(fn () => auth()->user()->can('import egg'))
+ ->form([
+ Tabs::make('Tabs')
+ ->contained(false)
+ ->tabs([
+ Tab::make('From File')
+ ->icon('tabler-file-upload')
+ ->schema([
+ FileUpload::make('egg')
+ ->label('Egg')
+ ->hint('This should be the json file ( egg-minecraft.json )')
+ ->acceptedFileTypes(['application/json'])
+ ->storeFiles(false)
+ ->multiple(),
+ ]),
+ Tab::make('From URL')
+ ->icon('tabler-world-upload')
+ ->schema([
+ TextInput::make('url')
+ ->label('URL')
+ ->hint('This URL should point to a single json file')
+ ->url(),
+ ]),
+ ]),
+ ]),
];
}
}
diff --git a/app/Filament/Resources/MountResource/Pages/CreateMount.php b/app/Filament/Resources/MountResource/Pages/CreateMount.php
index bc423053f40..fa5e263298b 100644
--- a/app/Filament/Resources/MountResource/Pages/CreateMount.php
+++ b/app/Filament/Resources/MountResource/Pages/CreateMount.php
@@ -24,83 +24,87 @@ class CreateMount extends CreateRecord
public function form(Form $form): Form
{
return $form
+ ->columns([
+ 'default' => 1,
+ 'lg' => 2,
+ ])
->schema([
- Section::make()->schema([
- TextInput::make('name')
- ->required()
- ->helperText('Unique name used to separate this mount from another.')
- ->maxLength(64),
- ToggleButtons::make('read_only')
- ->label('Read only?')
- ->helperText('Is the mount read only inside the container?')
- ->options([
- false => 'Writeable',
- true => 'Read only',
- ])
- ->icons([
- false => 'tabler-writing',
- true => 'tabler-writing-off',
- ])
- ->colors([
- false => 'warning',
- true => 'success',
- ])
- ->inline()
- ->default(false)
- ->required(),
- TextInput::make('source')
- ->required()
- ->helperText('File path on the host system to mount to a container.')
- ->maxLength(255),
- TextInput::make('target')
- ->required()
- ->helperText('Where the mount will be accessible inside a container.')
- ->maxLength(255),
- ToggleButtons::make('user_mountable')
- ->hidden()
- ->label('User mountable?')
- ->options([
- false => 'No',
- true => 'Yes',
- ])
- ->icons([
- false => 'tabler-user-cancel',
- true => 'tabler-user-bolt',
- ])
- ->colors([
- false => 'success',
- true => 'warning',
- ])
- ->default(false)
- ->inline()
- ->required(),
- Textarea::make('description')
- ->helperText('A longer description for this mount.')
- ->columnSpanFull(),
- Hidden::make('user_mountable')->default(1),
- ])
- ->columnSpan(1)
+ Section::make()
->columns([
'default' => 1,
'lg' => 2,
+ ])
+ ->schema([
+ TextInput::make('name')
+ ->required()
+ ->helperText('Unique name used to separate this mount from another.')
+ ->maxLength(64),
+ ToggleButtons::make('read_only')
+ ->label('Read only?')
+ ->helperText('Is the mount read only inside the container?')
+ ->options([
+ false => 'Writeable',
+ true => 'Read only',
+ ])
+ ->icons([
+ false => 'tabler-writing',
+ true => 'tabler-writing-off',
+ ])
+ ->colors([
+ false => 'warning',
+ true => 'success',
+ ])
+ ->inline()
+ ->default(false)
+ ->required(),
+ TextInput::make('source')
+ ->required()
+ ->helperText('File path on the host system to mount to a container.')
+ ->maxLength(255),
+ TextInput::make('target')
+ ->required()
+ ->helperText('Where the mount will be accessible inside a container.')
+ ->maxLength(255),
+ ToggleButtons::make('user_mountable')
+ ->hidden()
+ ->label('User mountable?')
+ ->options([
+ false => 'No',
+ true => 'Yes',
+ ])
+ ->icons([
+ false => 'tabler-user-cancel',
+ true => 'tabler-user-bolt',
+ ])
+ ->colors([
+ false => 'success',
+ true => 'warning',
+ ])
+ ->default(false)
+ ->inline()
+ ->required(),
+ Textarea::make('description')
+ ->helperText('A longer description for this mount.')
+ ->columnSpanFull(),
+ Hidden::make('user_mountable')->default(1),
]),
- Group::make()->schema([
- Section::make()->schema([
- Select::make('eggs')->multiple()
- ->relationship('eggs', 'name')
- ->preload(),
- Select::make('nodes')->multiple()
- ->relationship('nodes', 'name')
- ->searchable(['name', 'fqdn'])
- ->preload(),
+ Group::make()
+ ->columns([
+ 'default' => 1,
+ 'lg' => 2,
+ ])
+ ->schema([
+ Section::make()
+ ->schema([
+ Select::make('eggs')->multiple()
+ ->relationship('eggs', 'name')
+ ->preload(),
+ Select::make('nodes')->multiple()
+ ->relationship('nodes', 'name')
+ ->searchable(['name', 'fqdn'])
+ ->preload(),
+ ]),
]),
- ])->columns([
- 'default' => 1,
- 'lg' => 2,
- ]),
- ])->columns([
- 'default' => 1,
- 'lg' => 2,
]);
}
diff --git a/app/Filament/Resources/MountResource/Pages/EditMount.php b/app/Filament/Resources/MountResource/Pages/EditMount.php
index 32e598a3289..6bcdef07bc1 100644
--- a/app/Filament/Resources/MountResource/Pages/EditMount.php
+++ b/app/Filament/Resources/MountResource/Pages/EditMount.php
@@ -20,82 +20,87 @@ class EditMount extends EditRecord
public function form(Form $form): Form
{
return $form
+ ->columns([
+ 'default' => 1,
+ 'lg' => 2,
+ ])
->schema([
- Section::make()->schema([
- TextInput::make('name')
- ->required()
- ->helperText('Unique name used to separate this mount from another.')
- ->maxLength(64),
- ToggleButtons::make('read_only')
- ->label('Read only?')
- ->helperText('Is the mount read only inside the container?')
- ->options([
- false => 'Writeable',
- true => 'Read only',
- ])
- ->icons([
- false => 'tabler-writing',
- true => 'tabler-writing-off',
- ])
- ->colors([
- false => 'warning',
- true => 'success',
- ])
- ->inline()
- ->default(false)
- ->required(),
- TextInput::make('source')
- ->required()
- ->helperText('File path on the host system to mount to a container.')
- ->maxLength(255),
- TextInput::make('target')
- ->required()
- ->helperText('Where the mount will be accessible inside a container.')
- ->maxLength(255),
- ToggleButtons::make('user_mountable')
- ->hidden()
- ->label('User mountable?')
- ->options([
- false => 'No',
- true => 'Yes',
- ])
- ->icons([
- false => 'tabler-user-cancel',
- true => 'tabler-user-bolt',
- ])
- ->colors([
- false => 'success',
- true => 'warning',
- ])
- ->default(false)
- ->inline()
- ->required(),
- Textarea::make('description')
- ->helperText('A longer description for this mount.')
- ->columnSpanFull(),
- ])
+ Section::make()
->columnSpan(1)
->columns([
'default' => 1,
'lg' => 2,
+ ])
+ ->schema([
+ TextInput::make('name')
+ ->required()
+ ->helperText('Unique name used to separate this mount from another.')
+ ->maxLength(64),
+ ToggleButtons::make('read_only')
+ ->label('Read only?')
+ ->helperText('Is the mount read only inside the container?')
+ ->options([
+ false => 'Writeable',
+ true => 'Read only',
+ ])
+ ->icons([
+ false => 'tabler-writing',
+ true => 'tabler-writing-off',
+ ])
+ ->colors([
+ false => 'warning',
+ true => 'success',
+ ])
+ ->inline()
+ ->default(false)
+ ->required(),
+ TextInput::make('source')
+ ->required()
+ ->helperText('File path on the host system to mount to a container.')
+ ->maxLength(255),
+ TextInput::make('target')
+ ->required()
+ ->helperText('Where the mount will be accessible inside a container.')
+ ->maxLength(255),
+ ToggleButtons::make('user_mountable')
+ ->hidden()
+ ->label('User mountable?')
+ ->options([
+ false => 'No',
+ true => 'Yes',
+ ])
+ ->icons([
+ false => 'tabler-user-cancel',
+ true => 'tabler-user-bolt',
+ ])
+ ->colors([
+ false => 'success',
+ true => 'warning',
+ ])
+ ->default(false)
+ ->inline()
+ ->required(),
+ Textarea::make('description')
+ ->helperText('A longer description for this mount.')
+ ->columnSpanFull(),
]),
- Group::make()->schema([
- Section::make()->schema([
- Select::make('eggs')->multiple()
- ->relationship('eggs', 'name')
- ->preload(),
- Select::make('nodes')->multiple()
- ->relationship('nodes', 'name')
- ->searchable(['name', 'fqdn'])
- ->preload(),
+ Group::make()
+ ->columns([
+ 'default' => 1,
+ 'lg' => 2,
+ ])
+ ->schema([
+ Section::make()
+ ->schema([
+ Select::make('eggs')->multiple()
+ ->relationship('eggs', 'name')
+ ->preload(),
+ Select::make('nodes')->multiple()
+ ->relationship('nodes', 'name')
+ ->searchable(['name', 'fqdn'])
+ ->preload(),
+ ]),
]),
- ])->columns([
- 'default' => 1,
- 'lg' => 2,
- ]),
- ])->columns([
- 'default' => 1,
- 'lg' => 2,
]);
}
protected function getHeaderActions(): array
diff --git a/app/Filament/Resources/NodeResource/Pages/CreateNode.php b/app/Filament/Resources/NodeResource/Pages/CreateNode.php
index 3fe97845f94..603e3b7d8c4 100644
--- a/app/Filament/Resources/NodeResource/Pages/CreateNode.php
+++ b/app/Filament/Resources/NodeResource/Pages/CreateNode.php
@@ -29,375 +29,377 @@ public function form(Forms\Form $form): Forms\Form
{
return $form
->schema([
- Wizard::make([
- Step::make('basic')
- ->label('Basic Settings')
- ->icon('tabler-server')
- ->columnSpanFull()
- ->columns([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 3,
- 'lg' => 4,
- ])
- ->schema([
- TextInput::make('fqdn')
- ->columnSpan(2)
- ->required()
- ->autofocus()
- ->live(debounce: 1500)
- ->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure())
- ->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name')
- ->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
- ->helperText(function ($state) {
- if (is_ip($state)) {
- if (request()->isSecure()) {
- return '
+ Wizard::make()
+ ->columnSpanFull()
+ ->nextAction(fn (Action $action) => $action->label('Next Step'))
+ ->submitAction(new HtmlString(Blade::render(<<<'BLADE'
+
+ Create Node
+
+ BLADE)))
+ ->steps([
+ Step::make('basic')
+ ->label('Basic Settings')
+ ->icon('tabler-server')
+ ->columnSpanFull()
+ ->columns([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 3,
+ 'lg' => 4,
+ ])
+ ->schema([
+ TextInput::make('fqdn')
+ ->columnSpan(2)
+ ->required()
+ ->autofocus()
+ ->live(debounce: 1500)
+ ->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure())
+ ->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name')
+ ->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
+ ->helperText(function ($state) {
+ if (is_ip($state)) {
+ if (request()->isSecure()) {
+ return '
Your panel is currently secured via an SSL certificate and that means your nodes require one too.
You must use a domain name, because you cannot get SSL certificates for IP Addresses
';
- }
+ }
- return '';
- }
+ return '';
+ }
- return "
+ return "
This is the domain name that points to your node's IP Address.
If you've already set up this, you can verify it by checking the next field!
";
- })
- ->hintColor('danger')
- ->hint(function ($state) {
- if (is_ip($state) && request()->isSecure()) {
- return 'You cannot connect to an IP Address over SSL';
- }
+ })
+ ->hintColor('danger')
+ ->hint(function ($state) {
+ if (is_ip($state) && request()->isSecure()) {
+ return 'You cannot connect to an IP Address over SSL';
+ }
- return '';
- })
- ->afterStateUpdated(function (Set $set, ?string $state) {
- $set('dns', null);
- $set('ip', null);
+ return '';
+ })
+ ->afterStateUpdated(function (Set $set, ?string $state) {
+ $set('dns', null);
+ $set('ip', null);
- [$subdomain] = str($state)->explode('.', 2);
- if (!is_numeric($subdomain)) {
- $set('name', $subdomain);
- }
+ [$subdomain] = str($state)->explode('.', 2);
+ if (!is_numeric($subdomain)) {
+ $set('name', $subdomain);
+ }
- if (!$state || is_ip($state)) {
- $set('dns', null);
+ if (!$state || is_ip($state)) {
+ $set('dns', null);
- return;
- }
+ return;
+ }
- $validRecords = gethostbynamel($state);
- if ($validRecords) {
- $set('dns', true);
+ $validRecords = gethostbynamel($state);
+ if ($validRecords) {
+ $set('dns', true);
- $set('ip', collect($validRecords)->first());
+ $set('ip', collect($validRecords)->first());
- return;
- }
+ return;
+ }
- $set('dns', false);
- })
- ->maxLength(255),
+ $set('dns', false);
+ })
+ ->maxLength(255),
- TextInput::make('ip')
- ->disabled()
- ->hidden(),
+ TextInput::make('ip')
+ ->disabled()
+ ->hidden(),
- ToggleButtons::make('dns')
- ->label('DNS Record Check')
- ->helperText('This lets you know if your DNS record correctly points to an IP Address.')
- ->disabled()
- ->inline()
- ->default(null)
- ->hint(fn (Get $get) => $get('ip'))
- ->hintColor('success')
- ->options([
- true => 'Valid',
- false => 'Invalid',
- ])
- ->colors([
- true => 'success',
- false => 'danger',
- ])
- ->columnSpan(1),
+ ToggleButtons::make('dns')
+ ->label('DNS Record Check')
+ ->helperText('This lets you know if your DNS record correctly points to an IP Address.')
+ ->disabled()
+ ->inline()
+ ->default(null)
+ ->hint(fn (Get $get) => $get('ip'))
+ ->hintColor('success')
+ ->options([
+ true => 'Valid',
+ false => 'Invalid',
+ ])
+ ->colors([
+ true => 'success',
+ false => 'danger',
+ ])
+ ->columnSpan(1),
- TextInput::make('daemon_listen')
- ->columnSpan(1)
- ->label(trans('strings.port'))
- ->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.')
- ->minValue(1)
- ->maxValue(65535)
- ->default(8080)
- ->required()
- ->integer(),
+ TextInput::make('daemon_listen')
+ ->columnSpan(1)
+ ->label(trans('strings.port'))
+ ->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.')
+ ->minValue(1)
+ ->maxValue(65535)
+ ->default(8080)
+ ->required()
+ ->integer(),
- TextInput::make('name')
- ->label('Display Name')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->required()
- ->helperText('This name is for display only and can be changed later.')
- ->maxLength(100),
+ TextInput::make('name')
+ ->label('Display Name')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->required()
+ ->helperText('This name is for display only and can be changed later.')
+ ->maxLength(100),
- ToggleButtons::make('scheme')
- ->label('Communicate over SSL')
- ->columnSpan(1)
- ->inline()
- ->helperText(function (Get $get) {
- if (request()->isSecure()) {
- return new HtmlString('Your Panel is using a secure SSL connection,
so your Daemon must too.');
- }
+ ToggleButtons::make('scheme')
+ ->label('Communicate over SSL')
+ ->columnSpan(1)
+ ->inline()
+ ->helperText(function (Get $get) {
+ if (request()->isSecure()) {
+ return new HtmlString('Your Panel is using a secure SSL connection,
so your Daemon must too.');
+ }
- if (is_ip($get('fqdn'))) {
- return 'An IP address cannot use SSL.';
- }
+ if (is_ip($get('fqdn'))) {
+ return 'An IP address cannot use SSL.';
+ }
- return '';
- })
- ->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
- ->options([
- 'http' => 'HTTP',
- 'https' => 'HTTPS (SSL)',
- ])
- ->colors([
- 'http' => 'warning',
- 'https' => 'success',
- ])
- ->icons([
- 'http' => 'tabler-lock-open-off',
- 'https' => 'tabler-lock',
- ])
- ->default(fn () => request()->isSecure() ? 'https' : 'http'),
- ]),
- Step::make('advanced')
- ->label('Advanced Settings')
- ->icon('tabler-server-cog')
- ->columnSpanFull()
- ->columns([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 3,
- 'lg' => 4,
- ])
- ->schema([
- ToggleButtons::make('maintenance_mode')
- ->label('Maintenance Mode')
- ->inline()
- ->columnSpan(1)
- ->default(false)
- ->hinticon('tabler-question-mark')
- ->hintIconTooltip("If the node is marked 'Under Maintenance' users won't be able to access servers that are on this node.")
- ->options([
- true => 'Enable',
- false => 'Disable',
- ])
- ->colors([
- true => 'danger',
- false => 'success',
- ]),
- ToggleButtons::make('public')
- ->default(true)
- ->columnSpan(1)
- ->label('Use Node for deployment?')
- ->inline()
- ->options([
- true => 'Yes',
- false => 'No',
- ])
- ->colors([
- true => 'success',
- false => 'danger',
- ]),
- TagsInput::make('tags')
- ->placeholder('Add Tags')
- ->columnSpan(2),
- 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(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
- TextInput::make('daemon_sftp')
- ->columnSpan(1)
- ->label('SFTP Port')
- ->minValue(1)
- ->maxValue(65535)
- ->default(2022)
- ->required()
- ->integer(),
- TextInput::make('daemon_sftp_alias')
- ->columnSpan(2)
- ->label('SFTP Alias')
- ->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
- Grid::make()
- ->columns(6)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_mem')
- ->label('Memory')
- ->inlineLabel()
- ->inline()
- ->afterStateUpdated(function (Set $set) {
- $set('memory', 0);
- $set('memory_overallocate', 0);
- })
- ->formatStateUsing(fn (Get $get) => $get('memory') == 0)
- ->live()
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
- TextInput::make('memory')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_mem'))
- ->label('Memory Limit')
- ->inlineLabel()
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->columnSpan(2)
- ->numeric()
- ->minValue(0)
- ->default(0)
- ->required(),
- TextInput::make('memory_overallocate')
- ->dehydratedWhenHidden()
- ->label('Overallocate')
- ->inlineLabel()
- ->hidden(fn (Get $get) => $get('unlimited_mem'))
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('The % allowable to go over the set limit.')
- ->columnSpan(2)
- ->numeric()
- ->minValue(-1)
- ->maxValue(100)
- ->default(0)
- ->suffix('%')
- ->required(),
- ]),
- Grid::make()
- ->columns(6)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_disk')
- ->label('Disk')
- ->inlineLabel()
- ->inline()
- ->live()
- ->afterStateUpdated(function (Set $set) {
- $set('disk', 0);
- $set('disk_overallocate', 0);
- })
- ->formatStateUsing(fn (Get $get) => $get('disk') == 0)
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
- TextInput::make('disk')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_disk'))
- ->label('Disk Limit')
- ->inlineLabel()
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->columnSpan(2)
- ->numeric()
- ->minValue(0)
- ->default(0)
- ->required(),
- TextInput::make('disk_overallocate')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_disk'))
- ->label('Overallocate')
- ->inlineLabel()
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('The % allowable to go over the set limit.')
- ->columnSpan(2)
- ->numeric()
- ->minValue(-1)
- ->maxValue(100)
- ->default(0)
- ->suffix('%')
- ->required(),
- ]),
- Grid::make()
- ->columns(6)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_cpu')
- ->label('CPU')
- ->inlineLabel()
- ->inline()
- ->live()
- ->afterStateUpdated(function (Set $set) {
- $set('cpu', 0);
- $set('cpu_overallocate', 0);
- })
- ->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
- TextInput::make('cpu')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_cpu'))
- ->label('CPU Limit')
- ->inlineLabel()
- ->suffix('%')
- ->columnSpan(2)
- ->numeric()
- ->default(0)
- ->minValue(0)
- ->required(),
- TextInput::make('cpu_overallocate')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_cpu'))
- ->label('Overallocate')
- ->inlineLabel()
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('The % allowable to go over the set limit.')
- ->columnSpan(2)
- ->numeric()
- ->default(0)
- ->minValue(-1)
- ->maxValue(100)
- ->suffix('%')
- ->required(),
- ]),
- ]),
- ])->columnSpanFull()
- ->nextAction(fn (Action $action) => $action->label('Next Step'))
- ->submitAction(new HtmlString(Blade::render(<<<'BLADE'
-
- Create Node
-
- BLADE))),
+ return '';
+ })
+ ->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
+ ->options([
+ 'http' => 'HTTP',
+ 'https' => 'HTTPS (SSL)',
+ ])
+ ->colors([
+ 'http' => 'warning',
+ 'https' => 'success',
+ ])
+ ->icons([
+ 'http' => 'tabler-lock-open-off',
+ 'https' => 'tabler-lock',
+ ])
+ ->default(fn () => request()->isSecure() ? 'https' : 'http'),
+ ]),
+ Step::make('advanced')
+ ->label('Advanced Settings')
+ ->icon('tabler-server-cog')
+ ->columnSpanFull()
+ ->columns([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 3,
+ 'lg' => 4,
+ ])
+ ->schema([
+ ToggleButtons::make('maintenance_mode')
+ ->label('Maintenance Mode')
+ ->inline()
+ ->columnSpan(1)
+ ->default(false)
+ ->hinticon('tabler-question-mark')
+ ->hintIconTooltip("If the node is marked 'Under Maintenance' users won't be able to access servers that are on this node.")
+ ->options([
+ true => 'Enable',
+ false => 'Disable',
+ ])
+ ->colors([
+ true => 'danger',
+ false => 'success',
+ ]),
+ ToggleButtons::make('public')
+ ->default(true)
+ ->columnSpan(1)
+ ->label('Use Node for deployment?')
+ ->inline()
+ ->options([
+ true => 'Yes',
+ false => 'No',
+ ])
+ ->colors([
+ true => 'success',
+ false => 'danger',
+ ]),
+ TagsInput::make('tags')
+ ->placeholder('Add Tags')
+ ->columnSpan(2),
+ 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(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
+ TextInput::make('daemon_sftp')
+ ->columnSpan(1)
+ ->label('SFTP Port')
+ ->minValue(1)
+ ->maxValue(65535)
+ ->default(2022)
+ ->required()
+ ->integer(),
+ TextInput::make('daemon_sftp_alias')
+ ->columnSpan(2)
+ ->label('SFTP Alias')
+ ->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
+ Grid::make()
+ ->columns(6)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_mem')
+ ->label('Memory')
+ ->inlineLabel()
+ ->inline()
+ ->afterStateUpdated(function (Set $set) {
+ $set('memory', 0);
+ $set('memory_overallocate', 0);
+ })
+ ->formatStateUsing(fn (Get $get) => $get('memory') == 0)
+ ->live()
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan(2),
+ TextInput::make('memory')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_mem'))
+ ->label('Memory Limit')
+ ->inlineLabel()
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(0)
+ ->default(0)
+ ->required(),
+ TextInput::make('memory_overallocate')
+ ->dehydratedWhenHidden()
+ ->label('Overallocate')
+ ->inlineLabel()
+ ->hidden(fn (Get $get) => $get('unlimited_mem'))
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('The % allowable to go over the set limit.')
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(-1)
+ ->maxValue(100)
+ ->default(0)
+ ->suffix('%')
+ ->required(),
+ ]),
+ Grid::make()
+ ->columns(6)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_disk')
+ ->label('Disk')
+ ->inlineLabel()
+ ->inline()
+ ->live()
+ ->afterStateUpdated(function (Set $set) {
+ $set('disk', 0);
+ $set('disk_overallocate', 0);
+ })
+ ->formatStateUsing(fn (Get $get) => $get('disk') == 0)
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan(2),
+ TextInput::make('disk')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_disk'))
+ ->label('Disk Limit')
+ ->inlineLabel()
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(0)
+ ->default(0)
+ ->required(),
+ TextInput::make('disk_overallocate')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_disk'))
+ ->label('Overallocate')
+ ->inlineLabel()
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('The % allowable to go over the set limit.')
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(-1)
+ ->maxValue(100)
+ ->default(0)
+ ->suffix('%')
+ ->required(),
+ ]),
+ Grid::make()
+ ->columns(6)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_cpu')
+ ->label('CPU')
+ ->inlineLabel()
+ ->inline()
+ ->live()
+ ->afterStateUpdated(function (Set $set) {
+ $set('cpu', 0);
+ $set('cpu_overallocate', 0);
+ })
+ ->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan(2),
+ TextInput::make('cpu')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_cpu'))
+ ->label('CPU Limit')
+ ->inlineLabel()
+ ->suffix('%')
+ ->columnSpan(2)
+ ->numeric()
+ ->default(0)
+ ->minValue(0)
+ ->required(),
+ TextInput::make('cpu_overallocate')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_cpu'))
+ ->label('Overallocate')
+ ->inlineLabel()
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('The % allowable to go over the set limit.')
+ ->columnSpan(2)
+ ->numeric()
+ ->default(0)
+ ->minValue(-1)
+ ->maxValue(100)
+ ->suffix('%')
+ ->required(),
+ ]),
+ ]),
+ ]),
]);
}
diff --git a/app/Filament/Resources/NodeResource/Pages/EditNode.php b/app/Filament/Resources/NodeResource/Pages/EditNode.php
index 3890ff36530..31120e5225d 100644
--- a/app/Filament/Resources/NodeResource/Pages/EditNode.php
+++ b/app/Filament/Resources/NodeResource/Pages/EditNode.php
@@ -34,534 +34,536 @@ class EditNode extends EditRecord
public function form(Forms\Form $form): Forms\Form
{
- return $form->schema([
- Tabs::make('Tabs')
- ->columns([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 3,
- 'lg' => 4,
- ])
- ->persistTabInQueryString()
- ->columnSpanFull()
- ->tabs([
- Tab::make('')
- ->label('Overview')
- ->icon('tabler-chart-area-line-filled')
- ->columns(6)
- ->schema([
- Fieldset::make()
- ->label('Node Information')
- ->columns(4)
- ->schema([
- Placeholder::make('')
- ->label('Wings Version')
- ->content(fn (Node $node) => $node->systemInformation()['version'] ?? 'Unknown'),
- Placeholder::make('')
- ->label('CPU Threads')
- ->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
- Placeholder::make('')
- ->label('Architecture')
- ->content(fn (Node $node) => $node->systemInformation()['architecture'] ?? 'Unknown'),
- Placeholder::make('')
- ->label('Kernel')
- ->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? 'Unknown'),
- ]),
- View::make('filament.components.node-cpu-chart')->columnSpan(3),
- View::make('filament.components.node-memory-chart')->columnSpan(3),
- // TODO: Make purdy View::make('filament.components.node-storage-chart')->columnSpan(3),
- ]),
- Tab::make('Basic Settings')
- ->icon('tabler-server')
- ->schema([
- TextInput::make('fqdn')
- ->columnSpan(2)
- ->required()
- ->autofocus()
- ->live(debounce: 1500)
- ->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure())
- ->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name')
- ->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
- ->helperText(function ($state) {
- if (is_ip($state)) {
- if (request()->isSecure()) {
- return '
+ return $form
+ ->schema([
+ Tabs::make('Tabs')
+ ->columns([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 3,
+ 'lg' => 4,
+ ])
+ ->persistTabInQueryString()
+ ->columnSpanFull()
+ ->tabs([
+ Tab::make('')
+ ->label('Overview')
+ ->icon('tabler-chart-area-line-filled')
+ ->columns(6)
+ ->schema([
+ Fieldset::make()
+ ->label('Node Information')
+ ->columns(4)
+ ->schema([
+ Placeholder::make('')
+ ->label('Wings Version')
+ ->content(fn (Node $node) => $node->systemInformation()['version'] ?? 'Unknown'),
+ Placeholder::make('')
+ ->label('CPU Threads')
+ ->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
+ Placeholder::make('')
+ ->label('Architecture')
+ ->content(fn (Node $node) => $node->systemInformation()['architecture'] ?? 'Unknown'),
+ Placeholder::make('')
+ ->label('Kernel')
+ ->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? 'Unknown'),
+ ]),
+ View::make('filament.components.node-cpu-chart')
+ ->columnSpan(3),
+ View::make('filament.components.node-memory-chart')
+ ->columnSpan(3),
+ // TODO: Make purdy View::make('filament.components.node-storage-chart')
+ // ->columnSpan(3),
+ ]),
+ Tab::make('Basic Settings')
+ ->icon('tabler-server')
+ ->schema([
+ TextInput::make('fqdn')
+ ->columnSpan(2)
+ ->required()
+ ->autofocus()
+ ->live(debounce: 1500)
+ ->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure())
+ ->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name')
+ ->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
+ ->helperText(function ($state) {
+ if (is_ip($state)) {
+ if (request()->isSecure()) {
+ return '
Your panel is currently secured via an SSL certificate and that means your nodes require one too.
You must use a domain name, because you cannot get SSL certificates for IP Addresses
';
- }
+ }
- return '';
- }
+ return '';
+ }
- return "
+ return "
This is the domain name that points to your node's IP Address.
If you've already set up this, you can verify it by checking the next field!
";
- })
- ->hintColor('danger')
- ->hint(function ($state) {
- if (is_ip($state) && request()->isSecure()) {
- return 'You cannot connect to an IP Address over SSL';
- }
+ })
+ ->hintColor('danger')
+ ->hint(function ($state) {
+ if (is_ip($state) && request()->isSecure()) {
+ return 'You cannot connect to an IP Address over SSL';
+ }
- return '';
- })
- ->afterStateUpdated(function (Set $set, ?string $state) {
- $set('dns', null);
- $set('ip', null);
+ return '';
+ })
+ ->afterStateUpdated(function (Set $set, ?string $state) {
+ $set('dns', null);
+ $set('ip', null);
- [$subdomain] = str($state)->explode('.', 2);
- if (!is_numeric($subdomain)) {
- $set('name', $subdomain);
- }
+ [$subdomain] = str($state)->explode('.', 2);
+ if (!is_numeric($subdomain)) {
+ $set('name', $subdomain);
+ }
- if (!$state || is_ip($state)) {
- $set('dns', null);
+ if (!$state || is_ip($state)) {
+ $set('dns', null);
- return;
- }
+ return;
+ }
- $validRecords = gethostbynamel($state);
- if ($validRecords) {
- $set('dns', true);
+ $validRecords = gethostbynamel($state);
+ if ($validRecords) {
+ $set('dns', true);
- $set('ip', collect($validRecords)->first());
+ $set('ip', collect($validRecords)->first());
- return;
- }
+ return;
+ }
- $set('dns', false);
- })
- ->maxLength(255),
+ $set('dns', false);
+ })
+ ->maxLength(255),
- TextInput::make('ip')
- ->disabled()
- ->hidden(),
+ TextInput::make('ip')
+ ->disabled()
+ ->hidden(),
- ToggleButtons::make('dns')
- ->label('DNS Record Check')
- ->helperText('This lets you know if your DNS record correctly points to an IP Address.')
- ->disabled()
- ->inline()
- ->default(null)
- ->hint(fn (Get $get) => $get('ip'))
- ->hintColor('success')
- ->options([
- true => 'Valid',
- false => 'Invalid',
- ])
- ->colors([
- true => 'success',
- false => 'danger',
- ])
- ->columnSpan(1),
+ ToggleButtons::make('dns')
+ ->label('DNS Record Check')
+ ->helperText('This lets you know if your DNS record correctly points to an IP Address.')
+ ->disabled()
+ ->inline()
+ ->default(null)
+ ->hint(fn (Get $get) => $get('ip'))
+ ->hintColor('success')
+ ->options([
+ true => 'Valid',
+ false => 'Invalid',
+ ])
+ ->colors([
+ true => 'success',
+ false => 'danger',
+ ])
+ ->columnSpan(1),
- TextInput::make('daemon_listen')
- ->columnSpan(1)
- ->label(trans('strings.port'))
- ->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.')
- ->minValue(1)
- ->maxValue(65535)
- ->default(8080)
- ->required()
- ->integer(),
+ TextInput::make('daemon_listen')
+ ->columnSpan(1)
+ ->label(trans('strings.port'))
+ ->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.')
+ ->minValue(1)
+ ->maxValue(65535)
+ ->default(8080)
+ ->required()
+ ->integer(),
- TextInput::make('name')
- ->label('Display Name')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->required()
- ->helperText('This name is for display only and can be changed later.')
- ->maxLength(100),
+ TextInput::make('name')
+ ->label('Display Name')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->required()
+ ->helperText('This name is for display only and can be changed later.')
+ ->maxLength(100),
- ToggleButtons::make('scheme')
- ->label('Communicate over SSL')
- ->columnSpan(1)
- ->inline()
- ->helperText(function (Get $get) {
- if (request()->isSecure()) {
- return new HtmlString('Your Panel is using a secure SSL connection,
so your Daemon must too.');
- }
+ ToggleButtons::make('scheme')
+ ->label('Communicate over SSL')
+ ->columnSpan(1)
+ ->inline()
+ ->helperText(function (Get $get) {
+ if (request()->isSecure()) {
+ return new HtmlString('Your Panel is using a secure SSL connection,
so your Daemon must too.');
+ }
- if (is_ip($get('fqdn'))) {
- return 'An IP address cannot use SSL.';
- }
+ if (is_ip($get('fqdn'))) {
+ return 'An IP address cannot use SSL.';
+ }
- return '';
- })
- ->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
- ->options([
- 'http' => 'HTTP',
- 'https' => 'HTTPS (SSL)',
- ])
- ->colors([
- 'http' => 'warning',
- 'https' => 'success',
- ])
- ->icons([
- 'http' => 'tabler-lock-open-off',
- 'https' => 'tabler-lock',
- ])
- ->default(fn () => request()->isSecure() ? 'https' : 'http'),
- ]),
- Tab::make('Advanced Settings')
- ->columns(['default' => 1, 'sm' => 1, 'md' => 4, 'lg' => 6])
- ->icon('tabler-server-cog')
- ->schema([
- TextInput::make('id')
- ->label('Node ID')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 1,
- ])
- ->disabled(),
- TextInput::make('uuid')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->label('Node UUID')
- ->hintAction(CopyAction::make())
- ->disabled(),
- TagsInput::make('tags')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 2,
- ])
- ->placeholder('Add Tags'),
- TextInput::make('upload_size')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 2,
- 'lg' => 1,
- ])
- ->label('Upload Limit')
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('Enter the maximum size of files that can be uploaded through the web-based file manager.')
- ->numeric()
- ->required()
- ->minValue(1)
- ->maxValue(1024)
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
- TextInput::make('daemon_sftp')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 3,
- ])
- ->label('SFTP Port')
- ->minValue(1)
- ->maxValue(65535)
- ->default(2022)
- ->required()
- ->integer(),
- TextInput::make('daemon_sftp_alias')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 3,
- ])
- ->label('SFTP Alias')
- ->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
- ToggleButtons::make('public')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 3,
- ])
- ->label('Use Node for deployment?')
- ->inline()
- ->options([
- true => 'Yes',
- false => 'No',
- ])
- ->colors([
- true => 'success',
- false => 'danger',
- ]),
- ToggleButtons::make('maintenance_mode')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 3,
- ])
- ->label('Maintenance Mode')
- ->inline()
- ->hinticon('tabler-question-mark')
- ->hintIconTooltip("If the node is marked 'Under Maintenance' users won't be able to access servers that are on this node.")
- ->options([
- false => 'Disable',
- true => 'Enable',
- ])
- ->colors([
- false => 'success',
- true => 'danger',
- ]),
- Grid::make()
- ->columns(['default' => 1, 'sm' => 1, 'md' => 3, 'lg' => 6])
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_mem')
- ->label('Memory')
- ->inlineLabel()
- ->inline()
- ->afterStateUpdated(function (Set $set) {
- $set('memory', 0);
- $set('memory_overallocate', 0);
- })
- ->formatStateUsing(fn (Get $get) => $get('memory') == 0)
- ->live()
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ]),
- TextInput::make('memory')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_mem'))
- ->label('Memory Limit')
- ->inlineLabel()
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->required()
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->numeric()
- ->minValue(0),
- TextInput::make('memory_overallocate')
- ->dehydratedWhenHidden()
- ->label('Overallocate')
- ->inlineLabel()
- ->required()
- ->hidden(fn (Get $get) => $get('unlimited_mem'))
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('The % allowable to go over the set limit.')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->numeric()
- ->minValue(-1)
- ->maxValue(100)
- ->suffix('%'),
- ]),
- Grid::make()
- ->columns(['default' => 1, 'sm' => 1, 'md' => 3, 'lg' => 6])
- ->schema([
- ToggleButtons::make('unlimited_disk')
- ->label('Disk')
- ->inlineLabel()
- ->inline()
- ->live()
- ->afterStateUpdated(function (Set $set) {
- $set('disk', 0);
- $set('disk_overallocate', 0);
- })
- ->formatStateUsing(fn (Get $get) => $get('disk') == 0)
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ]),
- TextInput::make('disk')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_disk'))
- ->label('Disk Limit')
- ->inlineLabel()
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->required()
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->numeric()
- ->minValue(0),
- TextInput::make('disk_overallocate')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_disk'))
- ->label('Overallocate')
- ->inlineLabel()
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('The % allowable to go over the set limit.')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->required()
- ->numeric()
- ->minValue(-1)
- ->maxValue(100)
- ->suffix('%'),
- ]),
- Grid::make()
- ->columns(6)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_cpu')
- ->label('CPU')
- ->inlineLabel()
- ->inline()
- ->live()
- ->afterStateUpdated(function (Set $set) {
- $set('cpu', 0);
- $set('cpu_overallocate', 0);
- })
- ->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
- TextInput::make('cpu')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_cpu'))
- ->label('CPU Limit')
- ->inlineLabel()
- ->suffix('%')
- ->required()
- ->columnSpan(2)
- ->numeric()
- ->minValue(0),
- TextInput::make('cpu_overallocate')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_cpu'))
- ->label('Overallocate')
- ->inlineLabel()
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('The % allowable to go over the set limit.')
- ->columnSpan(2)
- ->required()
- ->numeric()
- ->minValue(-1)
- ->maxValue(100)
- ->suffix('%'),
- ]),
- ]),
- Tab::make('Configuration File')
- ->icon('tabler-code')
- ->schema([
- Placeholder::make('instructions')
- ->columnSpanFull()
- ->content(new HtmlString('
- Save this file to your daemon\'s root directory, named config.yml
- ')),
- Textarea::make('config')
- ->label('/etc/pelican/config.yml')
- ->disabled()
- ->rows(19)
- ->hintAction(CopyAction::make())
- ->columnSpanFull(),
- Grid::make()
- ->columns()
- ->schema([
- FormActions::make([
- FormActions\Action::make('autoDeploy')
- ->label('Auto Deploy Command')
- ->color('primary')
- ->modalHeading('Auto Deploy Command')
- ->icon('tabler-rocket')
- ->modalSubmitAction(false)
- ->modalCancelAction(false)
- ->modalFooterActionsAlignment(Alignment::Center)
- ->form([
- ToggleButtons::make('docker')
- ->label('Type')
- ->live()
- ->helperText('Choose between Standalone and Docker install.')
- ->inline()
- ->default(false)
- ->afterStateUpdated(fn (bool $state, NodeAutoDeployService $service, Node $node, Set $set) => $set('generatedToken', $service->handle(request(), $node, $state)))
- ->options([
- false => 'Standalone',
- true => 'Docker',
- ])
- ->colors([
- false => 'primary',
- true => 'success',
- ])
- ->columnSpan(1),
- Textarea::make('generatedToken')
- ->label('To auto-configure your node run the following command:')
- ->readOnly()
- ->autosize()
- ->hintAction(fn (string $state) => CopyAction::make()->copyable($state))
- ->formatStateUsing(fn (NodeAutoDeployService $service, Node $node, Set $set, Get $get) => $set('generatedToken', $service->handle(request(), $node, $get('docker')))),
+ return '';
+ })
+ ->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
+ ->options([
+ 'http' => 'HTTP',
+ 'https' => 'HTTPS (SSL)',
+ ])
+ ->colors([
+ 'http' => 'warning',
+ 'https' => 'success',
+ ])
+ ->icons([
+ 'http' => 'tabler-lock-open-off',
+ 'https' => 'tabler-lock',
+ ])
+ ->default(fn () => request()->isSecure() ? 'https' : 'http'),
+ ]),
+ Tab::make('Advanced Settings')
+ ->columns(['default' => 1, 'sm' => 1, 'md' => 4, 'lg' => 6])
+ ->icon('tabler-server-cog')
+ ->schema([
+ TextInput::make('id')
+ ->label('Node ID')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 1,
+ ])
+ ->disabled(),
+ TextInput::make('uuid')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->label('Node UUID')
+ ->hintAction(CopyAction::make())
+ ->disabled(),
+ TagsInput::make('tags')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 2,
+ ])
+ ->placeholder('Add Tags'),
+ TextInput::make('upload_size')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 2,
+ 'lg' => 1,
+ ])
+ ->label('Upload Limit')
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('Enter the maximum size of files that can be uploaded through the web-based file manager.')
+ ->numeric()
+ ->required()
+ ->minValue(1)
+ ->maxValue(1024)
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
+ TextInput::make('daemon_sftp')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 3,
+ ])
+ ->label('SFTP Port')
+ ->minValue(1)
+ ->maxValue(65535)
+ ->default(2022)
+ ->required()
+ ->integer(),
+ TextInput::make('daemon_sftp_alias')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 3,
+ ])
+ ->label('SFTP Alias')
+ ->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
+ ToggleButtons::make('public')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 3,
+ ])
+ ->label('Use Node for deployment?')
+ ->inline()
+ ->options([
+ true => 'Yes',
+ false => 'No',
+ ])
+ ->colors([
+ true => 'success',
+ false => 'danger',
+ ]),
+ ToggleButtons::make('maintenance_mode')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 3,
+ ])
+ ->label('Maintenance Mode')
+ ->inline()
+ ->hinticon('tabler-question-mark')
+ ->hintIconTooltip("If the node is marked 'Under Maintenance' users won't be able to access servers that are on this node.")
+ ->options([
+ false => 'Disable',
+ true => 'Enable',
+ ])
+ ->colors([
+ false => 'success',
+ true => 'danger',
+ ]),
+ Grid::make()
+ ->columns(['default' => 1, 'sm' => 1, 'md' => 3, 'lg' => 6])
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_mem')
+ ->label('Memory')
+ ->inlineLabel()
+ ->inline()
+ ->afterStateUpdated(function (Set $set) {
+ $set('memory', 0);
+ $set('memory_overallocate', 0);
+ })
+ ->formatStateUsing(fn (Get $get) => $get('memory') == 0)
+ ->live()
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ]),
+ TextInput::make('memory')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_mem'))
+ ->label('Memory Limit')
+ ->inlineLabel()
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->required()
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->numeric()
+ ->minValue(0),
+ TextInput::make('memory_overallocate')
+ ->dehydratedWhenHidden()
+ ->label('Overallocate')
+ ->inlineLabel()
+ ->required()
+ ->hidden(fn (Get $get) => $get('unlimited_mem'))
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('The % allowable to go over the set limit.')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->numeric()
+ ->minValue(-1)
+ ->maxValue(100)
+ ->suffix('%'),
+ ]),
+ Grid::make()
+ ->columns(['default' => 1, 'sm' => 1, 'md' => 3, 'lg' => 6])
+ ->schema([
+ ToggleButtons::make('unlimited_disk')
+ ->label('Disk')
+ ->inlineLabel()
+ ->inline()
+ ->live()
+ ->afterStateUpdated(function (Set $set) {
+ $set('disk', 0);
+ $set('disk_overallocate', 0);
+ })
+ ->formatStateUsing(fn (Get $get) => $get('disk') == 0)
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ]),
+ TextInput::make('disk')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_disk'))
+ ->label('Disk Limit')
+ ->inlineLabel()
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->required()
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->numeric()
+ ->minValue(0),
+ TextInput::make('disk_overallocate')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_disk'))
+ ->label('Overallocate')
+ ->inlineLabel()
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('The % allowable to go over the set limit.')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 1,
+ 'lg' => 2,
+ ])
+ ->required()
+ ->numeric()
+ ->minValue(-1)
+ ->maxValue(100)
+ ->suffix('%'),
+ ]),
+ Grid::make()
+ ->columns(6)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_cpu')
+ ->label('CPU')
+ ->inlineLabel()
+ ->inline()
+ ->live()
+ ->afterStateUpdated(function (Set $set) {
+ $set('cpu', 0);
+ $set('cpu_overallocate', 0);
+ })
+ ->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
])
- ->mountUsing(function (Forms\Form $form) {
- Notification::make()
- ->success()
- ->title('Autodeploy Generated')
- ->send();
- $form->fill();
- }),
- ])->fullWidth(),
- FormActions::make([
- FormActions\Action::make('resetKey')
- ->label('Reset Daemon Token')
- ->color('danger')
- ->requiresConfirmation()
- ->modalHeading('Reset Daemon Token?')
- ->modalDescription('Resetting the daemon token will void any request coming from the old token. This token is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this token regularly for security.')
- ->action(function (NodeUpdateService $nodeUpdateService, Node $node) {
- $nodeUpdateService->handle($node, [], true);
- Notification::make()
- ->success()
- ->title('Daemon Key Reset')
- ->send();
- $this->fillForm();
- }),
- ])->fullWidth(),
- ]),
- ]),
- ]),
- ]);
+ ->columnSpan(2),
+ TextInput::make('cpu')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_cpu'))
+ ->label('CPU Limit')
+ ->inlineLabel()
+ ->suffix('%')
+ ->required()
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(0),
+ TextInput::make('cpu_overallocate')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_cpu'))
+ ->label('Overallocate')
+ ->inlineLabel()
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('The % allowable to go over the set limit.')
+ ->columnSpan(2)
+ ->required()
+ ->numeric()
+ ->minValue(-1)
+ ->maxValue(100)
+ ->suffix('%'),
+ ]),
+ ]),
+ Tab::make('Configuration File')
+ ->icon('tabler-code')
+ ->schema([
+ Placeholder::make('instructions')
+ ->columnSpanFull()
+ ->content(new HtmlString('Save this file to your daemon\'s root directory, named config.yml
')),
+ Textarea::make('config')
+ ->label('/etc/pelican/config.yml')
+ ->disabled()
+ ->rows(19)
+ ->hintAction(CopyAction::make())
+ ->columnSpanFull(),
+ Grid::make()
+ ->columns()
+ ->schema([
+ FormActions::make([
+ FormActions\Action::make('autoDeploy')
+ ->label('Auto Deploy Command')
+ ->color('primary')
+ ->modalHeading('Auto Deploy Command')
+ ->icon('tabler-rocket')
+ ->modalSubmitAction(false)
+ ->modalCancelAction(false)
+ ->modalFooterActionsAlignment(Alignment::Center)
+ ->form([
+ ToggleButtons::make('docker')
+ ->label('Type')
+ ->live()
+ ->helperText('Choose between Standalone and Docker install.')
+ ->inline()
+ ->default(false)
+ ->afterStateUpdated(fn (bool $state, NodeAutoDeployService $service, Node $node, Set $set) => $set('generatedToken', $service->handle(request(), $node, $state)))
+ ->options([
+ false => 'Standalone',
+ true => 'Docker',
+ ])
+ ->colors([
+ false => 'primary',
+ true => 'success',
+ ])
+ ->columnSpan(1),
+ Textarea::make('generatedToken')
+ ->label('To auto-configure your node run the following command:')
+ ->readOnly()
+ ->autosize()
+ ->hintAction(fn (string $state) => CopyAction::make()->copyable($state))
+ ->formatStateUsing(fn (NodeAutoDeployService $service, Node $node, Set $set, Get $get) => $set('generatedToken', $service->handle(request(), $node, $get('docker')))),
+ ])
+ ->mountUsing(function (Forms\Form $form) {
+ Notification::make()
+ ->success()
+ ->title('Autodeploy Generated')
+ ->send();
+ $form->fill();
+ }),
+ ])->fullWidth(),
+ FormActions::make([
+ FormActions\Action::make('resetKey')
+ ->label('Reset Daemon Token')
+ ->color('danger')
+ ->requiresConfirmation()
+ ->modalHeading('Reset Daemon Token?')
+ ->modalDescription('Resetting the daemon token will void any request coming from the old token. This token is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this token regularly for security.')
+ ->action(function (NodeUpdateService $nodeUpdateService, Node $node) {
+ $nodeUpdateService->handle($node, [], true);
+ Notification::make()
+ ->success()
+ ->title('Daemon Key Reset')
+ ->send();
+ $this->fillForm();
+ }),
+ ])->fullWidth(),
+ ]),
+ ]),
+ ]),
+ ]);
}
protected function mutateFormDataBeforeFill(array $data): array
diff --git a/app/Filament/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php b/app/Filament/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php
index 01cc5e50e50..b5fc24b8f4b 100644
--- a/app/Filament/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php
+++ b/app/Filament/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php
@@ -72,8 +72,10 @@ public function table(Table $table): Table
//
])
->headerActions([
- Tables\Actions\Action::make('create new allocation')->label('Create Allocations')
- ->form(fn () => [
+ Tables\Actions\Action::make('create new allocation')
+ ->label('Create Allocations')
+ ->action(fn (array $data) => resolve(AssignmentService::class)->handle($this->getOwnerRecord(), $data))
+ ->form([
TextInput::make('allocation_ip')
->datalist($this->getOwnerRecord()->ipAddresses())
->label('IP Address')
@@ -147,8 +149,7 @@ public function table(Table $table): Table
})
->splitKeys(['Tab', ' ', ','])
->required(),
- ])
- ->action(fn (array $data) => resolve(AssignmentService::class)->handle($this->getOwnerRecord(), $data)),
+ ]),
])
->bulkActions([
BulkActionGroup::make([
diff --git a/app/Filament/Resources/RoleResource.php b/app/Filament/Resources/RoleResource.php
index 306c9676da3..71f80ee77cc 100644
--- a/app/Filament/Resources/RoleResource.php
+++ b/app/Filament/Resources/RoleResource.php
@@ -76,8 +76,8 @@ public static function form(Form $form): Form
->hidden(),
Fieldset::make('Permissions')
->columns(3)
- ->schema($permissions)
- ->hidden(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
+ ->hidden(fn (Get $get) => $get('name') === Role::ROOT_ADMIN)
+ ->schema($permissions),
Placeholder::make('permissions')
->content('The Root Admin has all permissions.')
->visible(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php
index eef8ac8b825..f71d015d91a 100644
--- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php
+++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php
@@ -53,762 +53,763 @@ public function form(Form $form): Form
{
return $form
->schema([
- Wizard::make([
- Step::make('Information')
- ->label('Information')
- ->icon('tabler-info-circle')
- ->completedIcon('tabler-check')
- ->columns([
- 'default' => 1,
- 'sm' => 1,
- 'md' => 4,
- 'lg' => 6,
- ])
- ->schema([
- TextInput::make('name')
- ->prefixIcon('tabler-server')
- ->label('Name')
- ->suffixAction(Forms\Components\Actions\Action::make('random')
- ->icon('tabler-dice-' . random_int(1, 6))
- ->action(function (Set $set, 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' => 3,
- 'md' => 2,
- 'lg' => 3,
- ])
- ->required()
- ->maxLength(255),
-
- Select::make('owner_id')
- ->preload()
- ->prefixIcon('tabler-user')
- ->default(auth()->user()->id)
- ->label('Owner')
- ->columnSpan([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 3,
- 'lg' => 3,
- ])
- ->relationship('user', 'username')
- ->searchable(['username', 'email'])
- ->getOptionLabelFromRecordUsing(fn (User $user) => "$user->email | $user->username " . ($user->isRootAdmin() ? '(admin)' : ''))
- ->createOptionForm([
- TextInput::make('username')
- ->alphaNum()
- ->required()
- ->maxLength(255),
-
- TextInput::make('email')
- ->email()
- ->required()
- ->unique()
- ->maxLength(255),
-
- TextInput::make('password')
- ->hintIcon('tabler-question-mark')
- ->hintIconTooltip('Providing a user password is optional. New user email will prompt users to create a password the first time they login.')
- ->password()
- ->revealable(),
- ])
- ->createOptionUsing(function ($data) {
- resolve(UserCreationService::class)->handle($data);
- $this->refreshForm();
- })
- ->required(),
-
- Select::make('node_id')
- ->disabledOn('edit')
- ->prefixIcon('tabler-server-2')
- ->default(fn () => ($this->node = Node::query()->latest()->first())?->id)
- ->columnSpan([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 6,
- 'lg' => 6,
- ])
- ->live()
- ->relationship('node', 'name')
- ->searchable()
- ->preload()
- ->afterStateUpdated(function (Set $set, $state) {
- $set('allocation_id', null);
- $this->node = Node::find($state);
- })
- ->required(),
-
- Select::make('allocation_id')
- ->preload()
- ->live()
- ->prefixIcon('tabler-network')
- ->label('Primary Allocation')
- ->columnSpan([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 2,
- 'lg' => 3,
- ])
- ->disabled(fn (Get $get) => $get('node_id') === null)
- ->searchable(['ip', 'port', 'ip_alias'])
- ->afterStateUpdated(function (Set $set) {
- $set('allocation_additional', null);
- $set('allocation_additional.needstobeastringhere.extra_allocations', null);
- })
- ->getOptionLabelFromRecordUsing(
- fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
- ($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
- )
- ->placeholder(function (Get $get) {
- $node = Node::find($get('node_id'));
-
- if ($node?->allocations) {
- return 'Select an Allocation';
- }
-
- return 'Create a New Allocation';
- })
- ->relationship(
- 'allocation',
- 'ip',
- fn (Builder $query, Get $get) => $query
- ->where('node_id', $get('node_id'))
- ->whereNull('server_id'),
- )
- ->createOptionForm(fn (Get $get) => [
- TextInput::make('allocation_ip')
- ->datalist(Node::find($get('node_id'))?->ipAddresses() ?? [])
- ->label('IP Address')
- ->inlineLabel()
- ->ipv4()
- ->helperText("Usually your machine's public IP unless you are port forwarding.")
- // ->selectablePlaceholder(false)
- ->required(),
- TextInput::make('allocation_alias')
- ->label('Alias')
- ->inlineLabel()
- ->default(null)
- ->datalist([
- $get('name'),
- Egg::find($get('egg_id'))?->name,
- ])
- ->helperText('Optional display name to help you remember what these are.')
- ->required(false),
- TagsInput::make('allocation_ports')
- ->placeholder('Examples: 27015, 27017-27019')
- ->helperText(new HtmlString('
+ Wizard::make()
+ ->columnSpanFull()
+ ->nextAction(fn (Action $action) => $action->label('Next Step'))
+ ->submitAction(new HtmlString(Blade::render(<<<'BLADE'
+
+ Create Server
+
+ BLADE)))
+ ->steps([
+ Step::make('Information')
+ ->label('Information')
+ ->icon('tabler-info-circle')
+ ->completedIcon('tabler-check')
+ ->columns([
+ 'default' => 1,
+ 'sm' => 1,
+ 'md' => 4,
+ 'lg' => 6,
+ ])
+ ->schema([
+ TextInput::make('name')
+ ->prefixIcon('tabler-server')
+ ->label('Name')
+ ->suffixAction(Forms\Components\Actions\Action::make('random')
+ ->icon('tabler-dice-' . random_int(1, 6))
+ ->action(function (Set $set, 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' => 3,
+ 'md' => 2,
+ 'lg' => 3,
+ ])
+ ->required()
+ ->maxLength(255),
+
+ Select::make('owner_id')
+ ->preload()
+ ->prefixIcon('tabler-user')
+ ->default(auth()->user()->id)
+ ->label('Owner')
+ ->columnSpan([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 3,
+ 'lg' => 3,
+ ])
+ ->relationship('user', 'username')
+ ->searchable(['username', 'email'])
+ ->getOptionLabelFromRecordUsing(fn (User $user) => "$user->email | $user->username " . ($user->isRootAdmin() ? '(admin)' : ''))
+ ->createOptionForm([
+ TextInput::make('username')
+ ->alphaNum()
+ ->required()
+ ->maxLength(255),
+
+ TextInput::make('email')
+ ->email()
+ ->required()
+ ->unique()
+ ->maxLength(255),
+
+ TextInput::make('password')
+ ->hintIcon('tabler-question-mark')
+ ->hintIconTooltip('Providing a user password is optional. New user email will prompt users to create a password the first time they login.')
+ ->password()
+ ->revealable(),
+ ])
+ ->createOptionUsing(function ($data) {
+ resolve(UserCreationService::class)->handle($data);
+ $this->refreshForm();
+ })
+ ->required(),
+
+ Select::make('node_id')
+ ->disabledOn('edit')
+ ->prefixIcon('tabler-server-2')
+ ->default(fn () => ($this->node = Node::query()->latest()->first())?->id)
+ ->columnSpan([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 6,
+ 'lg' => 6,
+ ])
+ ->live()
+ ->relationship('node', 'name')
+ ->searchable()
+ ->preload()
+ ->afterStateUpdated(function (Set $set, $state) {
+ $set('allocation_id', null);
+ $this->node = Node::find($state);
+ })
+ ->required(),
+
+ Select::make('allocation_id')
+ ->preload()
+ ->live()
+ ->prefixIcon('tabler-network')
+ ->label('Primary Allocation')
+ ->columnSpan([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 2,
+ 'lg' => 3,
+ ])
+ ->disabled(fn (Get $get) => $get('node_id') === null)
+ ->searchable(['ip', 'port', 'ip_alias'])
+ ->afterStateUpdated(function (Set $set) {
+ $set('allocation_additional', null);
+ $set('allocation_additional.needstobeastringhere.extra_allocations', null);
+ })
+ ->getOptionLabelFromRecordUsing(
+ fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
+ ($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
+ )
+ ->placeholder(function (Get $get) {
+ $node = Node::find($get('node_id'));
+
+ if ($node?->allocations) {
+ return 'Select an Allocation';
+ }
+
+ return 'Create a New Allocation';
+ })
+ ->relationship(
+ 'allocation',
+ 'ip',
+ fn (Builder $query, Get $get) => $query
+ ->where('node_id', $get('node_id'))
+ ->whereNull('server_id'),
+ )
+ ->createOptionForm(fn (Get $get) => [
+ TextInput::make('allocation_ip')
+ ->datalist(Node::find($get('node_id'))?->ipAddresses() ?? [])
+ ->label('IP Address')
+ ->inlineLabel()
+ ->ipv4()
+ ->helperText("Usually your machine's public IP unless you are port forwarding.")
+ // ->selectablePlaceholder(false)
+ ->required(),
+ TextInput::make('allocation_alias')
+ ->label('Alias')
+ ->inlineLabel()
+ ->default(null)
+ ->datalist([
+ $get('name'),
+ Egg::find($get('egg_id'))?->name,
+ ])
+ ->helperText('Optional display name to help you remember what these are.')
+ ->required(false),
+ TagsInput::make('allocation_ports')
+ ->placeholder('Examples: 27015, 27017-27019')
+ ->helperText(new HtmlString('
These are the ports that users can connect to this Server through.
You would have to port forward these on your home network.
'))
- ->label('Ports')
- ->inlineLabel()
- ->live()
- ->afterStateUpdated(function ($state, Set $set) {
- $ports = collect();
- $update = false;
- foreach ($state as $portEntry) {
- if (!str_contains($portEntry, '-')) {
- if (is_numeric($portEntry)) {
- $ports->push((int) $portEntry);
+ ->label('Ports')
+ ->inlineLabel()
+ ->live()
+ ->afterStateUpdated(function ($state, Set $set) {
+ $ports = collect();
+ $update = false;
+ foreach ($state as $portEntry) {
+ if (!str_contains($portEntry, '-')) {
+ if (is_numeric($portEntry)) {
+ $ports->push((int) $portEntry);
+
+ continue;
+ }
+
+ // Do not add non-numerical ports
+ $update = true;
continue;
}
- // Do not add non-numerical ports
$update = true;
+ [$start, $end] = explode('-', $portEntry);
+ if (!is_numeric($start) || !is_numeric($end)) {
+ continue;
+ }
- continue;
+ $start = max((int) $start, 0);
+ $end = min((int) $end, 2 ** 16 - 1);
+ $range = $start <= $end ? range($start, $end) : range($end, $start);
+ foreach ($range as $i) {
+ if ($i > 1024 && $i <= 65535) {
+ $ports->push($i);
+ }
+ }
}
- $update = true;
- [$start, $end] = explode('-', $portEntry);
- if (!is_numeric($start) || !is_numeric($end)) {
- continue;
+ $uniquePorts = $ports->unique()->values();
+ if ($ports->count() > $uniquePorts->count()) {
+ $update = true;
+ $ports = $uniquePorts;
}
- $start = max((int) $start, 0);
- $end = min((int) $end, 2 ** 16 - 1);
- $range = $start <= $end ? range($start, $end) : range($end, $start);
- foreach ($range as $i) {
- if ($i > 1024 && $i <= 65535) {
- $ports->push($i);
- }
+ $sortedPorts = $ports->sort()->values();
+ if ($sortedPorts->all() !== $ports->all()) {
+ $update = true;
+ $ports = $sortedPorts;
}
- }
-
- $uniquePorts = $ports->unique()->values();
- if ($ports->count() > $uniquePorts->count()) {
- $update = true;
- $ports = $uniquePorts;
- }
-
- $sortedPorts = $ports->sort()->values();
- if ($sortedPorts->all() !== $ports->all()) {
- $update = true;
- $ports = $sortedPorts;
- }
-
- if ($update) {
- $set('allocation_ports', $ports->all());
- }
- })
- ->splitKeys(['Tab', ' ', ','])
- ->required(),
- ])
- ->createOptionUsing(function (array $data, Get $get): int {
- return collect(
- resolve(AssignmentService::class)->handle(Node::find($get('node_id')), $data)
- )->first();
- })
- ->required(),
-
- Repeater::make('allocation_additional')
- ->label('Additional Allocations')
- ->columnSpan([
- 'default' => 2,
- 'sm' => 3,
- 'md' => 3,
- 'lg' => 3,
- ])
- ->addActionLabel('Add Allocation')
- ->disabled(fn (Get $get) => $get('allocation_id') === null)
- // ->addable() TODO disable when all allocations are taken
- // ->addable() TODO disable until first additional allocation is selected
- ->simple(
- Select::make('extra_allocations')
- ->live()
- ->preload()
- ->disableOptionsWhenSelectedInSiblingRepeaterItems()
- ->prefixIcon('tabler-network')
- ->label('Additional Allocations')
- ->columnSpan(2)
- ->disabled(fn (Get $get) => $get('../../node_id') === null)
- ->searchable(['ip', 'port', 'ip_alias'])
- ->getOptionLabelFromRecordUsing(
- fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
- ($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
- )
- ->placeholder('Select additional Allocations')
- ->disableOptionsWhenSelectedInSiblingRepeaterItems()
- ->relationship(
- 'allocations',
- 'ip',
- fn (Builder $query, Get $get, Select $component, $state) => $query
- ->where('node_id', $get('../../node_id'))
- ->whereNot('id', $get('../../allocation_id'))
- ->whereNull('server_id'),
- ),
- ),
-
- Textarea::make('description')
- ->placeholder('Description')
- ->rows(3)
- ->columnSpan([
- 'default' => 2,
- 'sm' => 6,
- 'md' => 6,
- 'lg' => 6,
- ])
- ->label('Description'),
- ]),
-
- Step::make('Egg Configuration')
- ->label('Egg Configuration')
- ->icon('tabler-egg')
- ->completedIcon('tabler-check')
- ->columns([
- 'default' => 1,
- 'sm' => 4,
- 'md' => 4,
- 'lg' => 6,
- ])
- ->schema([
- Select::make('egg_id')
- ->prefixIcon('tabler-egg')
- ->relationship('egg', 'name')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 2,
- 'lg' => 4,
- ])
- ->searchable()
- ->preload()
- ->live()
- ->afterStateUpdated(function ($state, Set $set, Get $get, $old) {
- $egg = Egg::query()->find($state);
- $set('startup', $egg->startup ?? '');
- $set('image', '');
-
- $variables = $egg->variables ?? [];
- $serverVariables = collect();
- foreach ($variables as $variable) {
- $serverVariables->add($variable->toArray());
- }
-
- $variables = [];
- $set($path = 'server_variables', $serverVariables->sortBy(['sort'])->all());
- for ($i = 0; $i < $serverVariables->count(); $i++) {
- $set("$path.$i.variable_value", $serverVariables[$i]['default_value']);
- $set("$path.$i.variable_id", $serverVariables[$i]['id']);
- $variables[$serverVariables[$i]['env_variable']] = $serverVariables[$i]['default_value'];
- }
-
- $set('environment', $variables);
-
- $previousEgg = Egg::query()->find($old);
- if (!$get('name') || $previousEgg?->getKebabName() === $get('name')) {
- $set('name', $egg->getKebabName());
- }
- })
- ->required(),
-
- ToggleButtons::make('skip_scripts')
- ->label('Run Egg Install Script?')
- ->default(false)
- ->columnSpan(1)
- ->options([
- false => 'Yes',
- true => 'Skip',
- ])
- ->colors([
- false => 'primary',
- true => 'danger',
- ])
- ->icons([
- false => 'tabler-code',
- true => 'tabler-code-off',
- ])
- ->inline()
- ->required(),
-
- ToggleButtons::make('start_on_completion')
- ->label('Start Server After Install?')
- ->default(true)
- ->required()
- ->columnSpan(1)
- ->options([
- true => 'Yes',
- false => 'No',
- ])
- ->colors([
- true => 'primary',
- false => 'danger',
- ])
- ->icons([
- true => 'tabler-code',
- false => 'tabler-code-off',
- ])
- ->inline(),
-
- Textarea::make('startup')
- ->hintIcon('tabler-code')
- ->label('Startup Command')
- ->hidden(fn (Get $get) => $get('egg_id') === null)
- ->required()
- ->live()
- ->rows(function ($state) {
- return str($state)->explode("\n")->reduce(
- fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
- 1
- );
- })
- ->columnSpan([
- 'default' => 1,
- 'sm' => 4,
- 'md' => 4,
- 'lg' => 6,
- ]),
-
- Hidden::make('environment')->default([]),
-
- Section::make('Variables')
- ->icon('tabler-eggs')
- ->iconColor('primary')
- ->hidden(fn (Get $get) => $get('egg_id') === null)
- ->collapsible()
- ->columnSpanFull()
- ->schema([
- Placeholder::make('Select an egg first to show its variables!')
- ->hidden(fn (Get $get) => $get('egg_id')),
-
- Placeholder::make('The selected egg has no variables!')
- ->hidden(fn (Get $get) => !$get('egg_id') ||
- Egg::query()->find($get('egg_id'))?->variables()?->count()
- ),
-
- Repeater::make('server_variables')
- ->label('')
- ->relationship('serverVariables')
- ->saveRelationshipsBeforeChildrenUsing(null)
- ->saveRelationshipsUsing(null)
- ->grid(2)
- ->reorderable(false)
- ->addable(false)
- ->deletable(false)
- ->default([])
- ->hidden(fn ($state) => empty($state))
- ->schema(function () {
-
- $text = TextInput::make('variable_value')
- ->hidden($this->shouldHideComponent(...))
- ->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(...))
- ->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;
- })
- ->columnSpan(2),
- ]),
- ]),
- Step::make('Environment Configuration')
- ->label('Environment Configuration')
- ->icon('tabler-brand-docker')
- ->completedIcon('tabler-check')
- ->schema([
- Fieldset::make('Resource Limits')
- ->columnSpan(6)
- ->columns([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 3,
- 'lg' => 3,
- ])
- ->schema([
- Grid::make()
- ->columns(4)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_mem')
- ->label('Memory')
- ->inlineLabel()
- ->inline()
- ->default(true)
- ->afterStateUpdated(fn (Set $set) => $set('memory', 0))
- ->live()
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
-
- TextInput::make('memory')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_mem'))
- ->label('Memory Limit')
- ->inlineLabel()
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->default(0)
- ->required()
- ->columnSpan(2)
- ->numeric()
- ->minValue(0),
- ]),
-
- Grid::make()
- ->columns(4)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_disk')
- ->label('Disk Space')
- ->inlineLabel()
- ->inline()
- ->default(true)
- ->live()
- ->afterStateUpdated(fn (Set $set) => $set('disk', 0))
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
-
- TextInput::make('disk')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_disk'))
- ->label('Disk Space Limit')
- ->inlineLabel()
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->default(0)
- ->required()
- ->columnSpan(2)
- ->numeric()
- ->minValue(0),
- ]),
-
- Grid::make()
- ->columns(4)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('unlimited_cpu')
- ->label('CPU')
- ->inlineLabel()
- ->inline()
- ->default(true)
- ->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
- ->live()
- ->options([
- true => 'Unlimited',
- false => 'Limited',
- ])
- ->colors([
- true => 'primary',
- false => 'warning',
- ])
- ->columnSpan(2),
-
- TextInput::make('cpu')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => $get('unlimited_cpu'))
- ->label('CPU Limit')
- ->inlineLabel()
- ->suffix('%')
- ->default(0)
- ->required()
- ->columnSpan(2)
- ->numeric()
- ->minValue(0)
- ->helperText('100% equals one CPU core.'),
- ]),
-
- Grid::make()
- ->columns(4)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('swap_support')
- ->live()
- ->label('Enable Swap Memory')
- ->inlineLabel()
- ->inline()
- ->columnSpan(2)
- ->default('disabled')
- ->afterStateUpdated(function ($state, Set $set) {
- $value = match ($state) {
- 'unlimited' => -1,
- 'disabled' => 0,
- 'limited' => 128,
- default => throw new LogicException('Invalid state'),
- };
-
- $set('swap', $value);
- })
- ->options([
- 'unlimited' => 'Unlimited',
- 'limited' => 'Limited',
- 'disabled' => 'Disabled',
- ])
- ->colors([
- 'unlimited' => 'primary',
- 'limited' => 'warning',
- 'disabled' => 'danger',
- ]),
-
- TextInput::make('swap')
- ->dehydratedWhenHidden()
- ->hidden(fn (Get $get) => match ($get('swap_support')) {
- 'disabled', 'unlimited' => true,
- default => false,
- })
- ->label('Swap Memory')
- ->default(0)
- ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
- ->minValue(-1)
- ->columnSpan(2)
- ->inlineLabel()
- ->required()
- ->integer(),
- ]),
-
- Hidden::make('io')
- ->helperText('The IO performance relative to other running containers')
- ->label('Block IO Proportion')
- ->default(500),
-
- Grid::make()
- ->columns(4)
- ->columnSpanFull()
- ->schema([
- ToggleButtons::make('oom_killer')
- ->label('OOM Killer')
- ->inlineLabel()
- ->inline()
- ->default(false)
- ->columnSpan(2)
- ->options([
- false => 'Disabled',
- true => 'Enabled',
- ])
- ->colors([
- false => 'success',
- true => 'danger',
- ]),
-
- TextInput::make('oom_disabled_hidden')
- ->hidden(),
- ]),
- ]),
-
- Fieldset::make('Feature Limits')
- ->inlineLabel()
- ->columnSpan(6)
- ->columns([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 3,
- 'lg' => 3,
- ])
- ->schema([
- TextInput::make('allocation_limit')
- ->label('Allocations')
- ->suffixIcon('tabler-network')
- ->required()
- ->numeric()
- ->minValue(0)
- ->default(0),
- TextInput::make('database_limit')
- ->label('Databases')
- ->suffixIcon('tabler-database')
- ->required()
- ->numeric()
- ->minValue(0)
- ->default(0),
- TextInput::make('backup_limit')
- ->label('Backups')
- ->suffixIcon('tabler-copy-check')
- ->required()
- ->numeric()
- ->minValue(0)
- ->default(0),
- ]),
- Fieldset::make('Docker Settings')
- ->columns([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 3,
- 'lg' => 4,
- ])
- ->columnSpan(6)
- ->schema([
- Select::make('select_image')
- ->label('Image Name')
- ->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
- ->options(function ($state, Get $get, 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([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 3,
- 'lg' => 2,
- ]),
-
- TextInput::make('image')
- ->label('Image')
- ->debounce(500)
- ->afterStateUpdated(function ($state, Get $get, 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([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 3,
- 'lg' => 2,
- ]),
-
- KeyValue::make('docker_labels')
- ->label('Container Labels')
- ->keyLabel('Title')
- ->valueLabel('Description')
- ->columnSpanFull(),
-
- CheckboxList::make('mounts')
- ->live()
- ->relationship('mounts')
- ->options(fn () => $this->node?->mounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]) ?? [])
- ->descriptions(fn () => $this->node?->mounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]) ?? [])
- ->label('Mounts')
- ->helperText(fn () => $this->node?->mounts->isNotEmpty() ? '' : 'No Mounts exist for this Node')
- ->columnSpanFull(),
- ]),
- ]),
- ])
- ->columnSpanFull()
- ->nextAction(fn (Action $action) => $action->label('Next Step'))
- ->submitAction(new HtmlString(Blade::render(<<<'BLADE'
-
- Create Server
-
- BLADE))),
+
+ if ($update) {
+ $set('allocation_ports', $ports->all());
+ }
+ })
+ ->splitKeys(['Tab', ' ', ','])
+ ->required(),
+ ])
+ ->createOptionUsing(function (array $data, Get $get): int {
+ return collect(
+ resolve(AssignmentService::class)->handle(Node::find($get('node_id')), $data)
+ )->first();
+ })
+ ->required(),
+
+ Repeater::make('allocation_additional')
+ ->label('Additional Allocations')
+ ->columnSpan([
+ 'default' => 2,
+ 'sm' => 3,
+ 'md' => 3,
+ 'lg' => 3,
+ ])
+ ->addActionLabel('Add Allocation')
+ ->disabled(fn (Get $get) => $get('allocation_id') === null)
+ // ->addable() TODO disable when all allocations are taken
+ // ->addable() TODO disable until first additional allocation is selected
+ ->simple(
+ Select::make('extra_allocations')
+ ->live()
+ ->preload()
+ ->disableOptionsWhenSelectedInSiblingRepeaterItems()
+ ->prefixIcon('tabler-network')
+ ->label('Additional Allocations')
+ ->columnSpan(2)
+ ->disabled(fn (Get $get) => $get('../../node_id') === null)
+ ->searchable(['ip', 'port', 'ip_alias'])
+ ->getOptionLabelFromRecordUsing(
+ fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
+ ($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
+ )
+ ->placeholder('Select additional Allocations')
+ ->disableOptionsWhenSelectedInSiblingRepeaterItems()
+ ->relationship(
+ 'allocations',
+ 'ip',
+ fn (Builder $query, Get $get, Select $component, $state) => $query
+ ->where('node_id', $get('../../node_id'))
+ ->whereNot('id', $get('../../allocation_id'))
+ ->whereNull('server_id'),
+ ),
+ ),
+
+ Textarea::make('description')
+ ->placeholder('Description')
+ ->rows(3)
+ ->columnSpan([
+ 'default' => 2,
+ 'sm' => 6,
+ 'md' => 6,
+ 'lg' => 6,
+ ])
+ ->label('Description'),
+ ]),
+
+ Step::make('Egg Configuration')
+ ->label('Egg Configuration')
+ ->icon('tabler-egg')
+ ->completedIcon('tabler-check')
+ ->columns([
+ 'default' => 1,
+ 'sm' => 4,
+ 'md' => 4,
+ 'lg' => 6,
+ ])
+ ->schema([
+ Select::make('egg_id')
+ ->prefixIcon('tabler-egg')
+ ->relationship('egg', 'name')
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 2,
+ 'md' => 2,
+ 'lg' => 4,
+ ])
+ ->searchable()
+ ->preload()
+ ->live()
+ ->afterStateUpdated(function ($state, Set $set, Get $get, $old) {
+ $egg = Egg::query()->find($state);
+ $set('startup', $egg->startup ?? '');
+ $set('image', '');
+
+ $variables = $egg->variables ?? [];
+ $serverVariables = collect();
+ foreach ($variables as $variable) {
+ $serverVariables->add($variable->toArray());
+ }
+
+ $variables = [];
+ $set($path = 'server_variables', $serverVariables->sortBy(['sort'])->all());
+ for ($i = 0; $i < $serverVariables->count(); $i++) {
+ $set("$path.$i.variable_value", $serverVariables[$i]['default_value']);
+ $set("$path.$i.variable_id", $serverVariables[$i]['id']);
+ $variables[$serverVariables[$i]['env_variable']] = $serverVariables[$i]['default_value'];
+ }
+
+ $set('environment', $variables);
+
+ $previousEgg = Egg::query()->find($old);
+ if (!$get('name') || $previousEgg?->getKebabName() === $get('name')) {
+ $set('name', $egg->getKebabName());
+ }
+ })
+ ->required(),
+
+ ToggleButtons::make('skip_scripts')
+ ->label('Run Egg Install Script?')
+ ->default(false)
+ ->columnSpan(1)
+ ->options([
+ false => 'Yes',
+ true => 'Skip',
+ ])
+ ->colors([
+ false => 'primary',
+ true => 'danger',
+ ])
+ ->icons([
+ false => 'tabler-code',
+ true => 'tabler-code-off',
+ ])
+ ->inline()
+ ->required(),
+
+ ToggleButtons::make('start_on_completion')
+ ->label('Start Server After Install?')
+ ->default(true)
+ ->required()
+ ->columnSpan(1)
+ ->options([
+ true => 'Yes',
+ false => 'No',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'danger',
+ ])
+ ->icons([
+ true => 'tabler-code',
+ false => 'tabler-code-off',
+ ])
+ ->inline(),
+
+ Textarea::make('startup')
+ ->hintIcon('tabler-code')
+ ->label('Startup Command')
+ ->hidden(fn (Get $get) => $get('egg_id') === null)
+ ->required()
+ ->live()
+ ->rows(function ($state) {
+ return str($state)->explode("\n")->reduce(
+ fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
+ 1
+ );
+ })
+ ->columnSpan([
+ 'default' => 1,
+ 'sm' => 4,
+ 'md' => 4,
+ 'lg' => 6,
+ ]),
+
+ Hidden::make('environment')->default([]),
+
+ Section::make('Variables')
+ ->icon('tabler-eggs')
+ ->iconColor('primary')
+ ->hidden(fn (Get $get) => $get('egg_id') === null)
+ ->collapsible()
+ ->columnSpanFull()
+ ->schema([
+ Placeholder::make('Select an egg first to show its variables!')
+ ->hidden(fn (Get $get) => $get('egg_id')),
+
+ Placeholder::make('The selected egg has no variables!')
+ ->hidden(fn (Get $get) => !$get('egg_id') ||
+ Egg::query()->find($get('egg_id'))?->variables()?->count()
+ ),
+
+ Repeater::make('server_variables')
+ ->label('')
+ ->relationship('serverVariables')
+ ->saveRelationshipsBeforeChildrenUsing(null)
+ ->saveRelationshipsUsing(null)
+ ->grid(2)
+ ->reorderable(false)
+ ->addable(false)
+ ->deletable(false)
+ ->default([])
+ ->hidden(fn ($state) => empty($state))
+ ->schema(function () {
+
+ $text = TextInput::make('variable_value')
+ ->hidden($this->shouldHideComponent(...))
+ ->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(...))
+ ->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;
+ })
+ ->columnSpan(2),
+ ]),
+ ]),
+ Step::make('Environment Configuration')
+ ->label('Environment Configuration')
+ ->icon('tabler-brand-docker')
+ ->completedIcon('tabler-check')
+ ->schema([
+ Fieldset::make('Resource Limits')
+ ->columnSpan(6)
+ ->columns([
+ 'default' => 1,
+ 'sm' => 2,
+ 'md' => 3,
+ 'lg' => 3,
+ ])
+ ->schema([
+ Grid::make()
+ ->columns(4)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_mem')
+ ->label('Memory')
+ ->inlineLabel()
+ ->inline()
+ ->default(true)
+ ->afterStateUpdated(fn (Set $set) => $set('memory', 0))
+ ->live()
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan(2),
+
+ TextInput::make('memory')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_mem'))
+ ->label('Memory Limit')
+ ->inlineLabel()
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->default(0)
+ ->required()
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(0),
+ ]),
+
+ Grid::make()
+ ->columns(4)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_disk')
+ ->label('Disk Space')
+ ->inlineLabel()
+ ->inline()
+ ->default(true)
+ ->live()
+ ->afterStateUpdated(fn (Set $set) => $set('disk', 0))
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan(2),
+
+ TextInput::make('disk')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_disk'))
+ ->label('Disk Space Limit')
+ ->inlineLabel()
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->default(0)
+ ->required()
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(0),
+ ]),
+
+ Grid::make()
+ ->columns(4)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('unlimited_cpu')
+ ->label('CPU')
+ ->inlineLabel()
+ ->inline()
+ ->default(true)
+ ->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
+ ->live()
+ ->options([
+ true => 'Unlimited',
+ false => 'Limited',
+ ])
+ ->colors([
+ true => 'primary',
+ false => 'warning',
+ ])
+ ->columnSpan(2),
+
+ TextInput::make('cpu')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => $get('unlimited_cpu'))
+ ->label('CPU Limit')
+ ->inlineLabel()
+ ->suffix('%')
+ ->default(0)
+ ->required()
+ ->columnSpan(2)
+ ->numeric()
+ ->minValue(0)
+ ->helperText('100% equals one CPU core.'),
+ ]),
+
+ Grid::make()
+ ->columns(4)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('swap_support')
+ ->live()
+ ->label('Enable Swap Memory')
+ ->inlineLabel()
+ ->inline()
+ ->columnSpan(2)
+ ->default('disabled')
+ ->afterStateUpdated(function ($state, Set $set) {
+ $value = match ($state) {
+ 'unlimited' => -1,
+ 'disabled' => 0,
+ 'limited' => 128,
+ default => throw new LogicException('Invalid state'),
+ };
+
+ $set('swap', $value);
+ })
+ ->options([
+ 'unlimited' => 'Unlimited',
+ 'limited' => 'Limited',
+ 'disabled' => 'Disabled',
+ ])
+ ->colors([
+ 'unlimited' => 'primary',
+ 'limited' => 'warning',
+ 'disabled' => 'danger',
+ ]),
+
+ TextInput::make('swap')
+ ->dehydratedWhenHidden()
+ ->hidden(fn (Get $get) => match ($get('swap_support')) {
+ 'disabled', 'unlimited' => true,
+ default => false,
+ })
+ ->label('Swap Memory')
+ ->default(0)
+ ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
+ ->minValue(-1)
+ ->columnSpan(2)
+ ->inlineLabel()
+ ->required()
+ ->integer(),
+ ]),
+
+ Hidden::make('io')
+ ->helperText('The IO performance relative to other running containers')
+ ->label('Block IO Proportion')
+ ->default(500),
+
+ Grid::make()
+ ->columns(4)
+ ->columnSpanFull()
+ ->schema([
+ ToggleButtons::make('oom_killer')
+ ->label('OOM Killer')
+ ->inlineLabel()
+ ->inline()
+ ->default(false)
+ ->columnSpan(2)
+ ->options([
+ false => 'Disabled',
+ true => 'Enabled',
+ ])
+ ->colors([
+ false => 'success',
+ true => 'danger',
+ ]),
+
+ TextInput::make('oom_disabled_hidden')
+ ->hidden(),
+ ]),
+ ]),
+
+ Fieldset::make('Feature Limits')
+ ->inlineLabel()
+ ->columnSpan(6)
+ ->columns([
+ 'default' => 1,
+ 'sm' => 2,
+ 'md' => 3,
+ 'lg' => 3,
+ ])
+ ->schema([
+ TextInput::make('allocation_limit')
+ ->label('Allocations')
+ ->suffixIcon('tabler-network')
+ ->required()
+ ->numeric()
+ ->minValue(0)
+ ->default(0),
+ TextInput::make('database_limit')
+ ->label('Databases')
+ ->suffixIcon('tabler-database')
+ ->required()
+ ->numeric()
+ ->minValue(0)
+ ->default(0),
+ TextInput::make('backup_limit')
+ ->label('Backups')
+ ->suffixIcon('tabler-copy-check')
+ ->required()
+ ->numeric()
+ ->minValue(0)
+ ->default(0),
+ ]),
+ Fieldset::make('Docker Settings')
+ ->columns([
+ 'default' => 1,
+ 'sm' => 2,
+ 'md' => 3,
+ 'lg' => 4,
+ ])
+ ->columnSpan(6)
+ ->schema([
+ Select::make('select_image')
+ ->label('Image Name')
+ ->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
+ ->options(function ($state, Get $get, 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([
+ 'default' => 1,
+ 'sm' => 2,
+ 'md' => 3,
+ 'lg' => 2,
+ ]),
+
+ TextInput::make('image')
+ ->label('Image')
+ ->debounce(500)
+ ->afterStateUpdated(function ($state, Get $get, 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([
+ 'default' => 1,
+ 'sm' => 2,
+ 'md' => 3,
+ 'lg' => 2,
+ ]),
+
+ KeyValue::make('docker_labels')
+ ->label('Container Labels')
+ ->keyLabel('Title')
+ ->valueLabel('Description')
+ ->columnSpanFull(),
+
+ CheckboxList::make('mounts')
+ ->live()
+ ->relationship('mounts')
+ ->options(fn () => $this->node?->mounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]) ?? [])
+ ->descriptions(fn () => $this->node?->mounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]) ?? [])
+ ->label('Mounts')
+ ->helperText(fn () => $this->node?->mounts->isNotEmpty() ? '' : 'No Mounts exist for this Node')
+ ->columnSpanFull(),
+ ]),
+ ]),
+ ]),
]);
}
diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php
index 48c3bc1fef9..2d0f3c3f475 100644
--- a/app/Filament/Resources/ServerResource/Pages/EditServer.php
+++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php
@@ -498,6 +498,7 @@ public function form(Form $form): Form
->columnSpan(6),
Repeater::make('server_variables')
+ ->columnSpan(6)
->relationship('serverVariables', function (Builder $query) {
/** @var Server $server */
$server = $this->getRecord();
@@ -563,8 +564,7 @@ public function form(Form $form): Form
}
return $components;
- })
- ->columnSpan(6),
+ }),
]),
Tab::make('Mounts')
->icon('tabler-layers-linked')
@@ -578,12 +578,17 @@ public function form(Form $form): Form
->columnSpanFull(),
]),
Tab::make('Databases')
+ ->columns(4)
->icon('tabler-database')
->schema([
Repeater::make('databases')
->grid()
->helperText(fn (Server $server) => $server->databases->isNotEmpty() ? '' : 'No Databases exist for this Server')
->columns(2)
+ ->columnSpan(4)
+ ->relationship('databases')
+ ->deletable(false)
+ ->addable(false)
->schema([
TextInput::make('database')
->columnSpan(2)
@@ -626,12 +631,8 @@ public function form(Form $form): Form
->label('JDBC Connection String')
->columnSpan(2)
->formatStateUsing(fn (Get $get, $record) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($record->password) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database')),
- ])
- ->relationship('databases')
- ->deletable(false)
- ->addable(false)
- ->columnSpan(4),
- ])->columns(4),
+ ]),
+ ]),
Tab::make('Actions')
->icon('tabler-settings')
->schema([
diff --git a/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php b/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php
index 2060c127f3d..96be07e7b73 100644
--- a/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php
+++ b/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php
@@ -69,7 +69,8 @@ public function table(Table $table): Table
->headerActions([
CreateAction::make()->label('Create Allocation')
->createAnother(false)
- ->form(fn () => [
+ ->action(fn (array $data) => resolve(AssignmentService::class)->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord()))
+ ->form([
TextInput::make('allocation_ip')
->datalist($this->getOwnerRecord()->node->ipAddresses())
->label('IP Address')
@@ -143,8 +144,7 @@ public function table(Table $table): Table
})
->splitKeys(['Tab', ' ', ','])
->required(),
- ])
- ->action(fn (array $data) => resolve(AssignmentService::class)->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())),
+ ]),
AssociateAction::make()
->multiple()
->associateAnother(false)
diff --git a/app/Filament/Resources/UserResource/Pages/EditProfile.php b/app/Filament/Resources/UserResource/Pages/EditProfile.php
index b213446972b..b4453815c06 100644
--- a/app/Filament/Resources/UserResource/Pages/EditProfile.php
+++ b/app/Filament/Resources/UserResource/Pages/EditProfile.php
@@ -43,8 +43,13 @@ protected function getForms(): array
return [
'form' => $this->form(
$this->makeForm()
+ ->operation('edit')
+ ->model($this->getUser())
+ ->statePath('data')
+ ->inlineLabel(!static::isSimple())
->schema([
- Tabs::make()->persistTabInQueryString()
+ Tabs::make()
+ ->persistTabInQueryString()
->schema([
Tab::make('Account')
->label(trans('strings.account'))
@@ -200,17 +205,7 @@ protected function getForms(): array
->schema([
Section::make('Create API Key')
->columnSpan(3)
- ->schema([
- TextInput::make('description')
- ->live(),
- TagsInput::make('allowed_ips')
- ->live()
- ->splitKeys([',', ' ', 'Tab'])
- ->placeholder('Example: 127.0.0.1 or 192.168.1.1')
- ->label('Whitelisted IP\'s')
- ->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
- ->columnSpanFull(),
- ])->headerActions([
+ ->headerActions([
Action::make('Create')
->disabled(fn (Get $get) => $get('description') === null)
->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab']))
@@ -225,6 +220,17 @@ protected function getForms(): array
->log();
$action->success();
}),
+ ])
+ ->schema([
+ TextInput::make('description')
+ ->live(),
+ TagsInput::make('allowed_ips')
+ ->live()
+ ->splitKeys([',', ' ', 'Tab'])
+ ->placeholder('Example: 127.0.0.1 or 192.168.1.1')
+ ->label('Whitelisted IP\'s')
+ ->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
+ ->columnSpanFull(),
]),
Section::make('Keys')
->columnSpan(2)
@@ -250,7 +256,8 @@ protected function getForms(): array
});
})
->schema(fn () => [
- Placeholder::make('adf')->label(fn (ApiKey $key) => $key->memo),
+ Placeholder::make('adf')
+ ->label(fn (ApiKey $key) => $key->memo),
]),
]),
]),
@@ -276,11 +283,7 @@ protected function getForms(): array
]),
]),
]),
- ])
- ->operation('edit')
- ->model($this->getUser())
- ->statePath('data')
- ->inlineLabel(!static::isSimple()),
+ ]),
),
];
}
diff --git a/app/Filament/Resources/UserResource/Pages/EditUser.php b/app/Filament/Resources/UserResource/Pages/EditUser.php
index 09e8a4737f5..b59d2014512 100644
--- a/app/Filament/Resources/UserResource/Pages/EditUser.php
+++ b/app/Filament/Resources/UserResource/Pages/EditUser.php
@@ -22,34 +22,36 @@ public function form(Form $form): Form
{
return $form
->schema([
- Section::make()->schema([
- TextInput::make('username')
- ->required()
- ->maxLength(255),
- TextInput::make('email')
- ->email()
- ->required()
- ->maxLength(255),
- TextInput::make('password')
- ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
- ->dehydrated(fn (?string $state): bool => filled($state))
- ->required(fn (string $operation): bool => $operation === 'create')
- ->password()
- ->revealable(),
- Select::make('language')
- ->required()
- ->hidden()
- ->default('en')
- ->options(fn (User $user) => $user->getAvailableLanguages()),
- Hidden::make('skipValidation')->default(true),
- CheckboxList::make('roles')
- ->disabled(fn (User $user) => $user->id === auth()->user()->id)
- ->disableOptionWhen(fn (string $value): bool => $value == Role::getRootAdmin()->id)
- ->relationship('roles', 'name')
- ->label('Admin Roles')
- ->columnSpanFull()
- ->bulkToggleable(false),
- ])->columns(),
+ Section::make()
+ ->columns()
+ ->schema([
+ TextInput::make('username')
+ ->required()
+ ->maxLength(255),
+ TextInput::make('email')
+ ->email()
+ ->required()
+ ->maxLength(255),
+ TextInput::make('password')
+ ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
+ ->dehydrated(fn (?string $state): bool => filled($state))
+ ->required(fn (string $operation): bool => $operation === 'create')
+ ->password()
+ ->revealable(),
+ Select::make('language')
+ ->required()
+ ->hidden()
+ ->default('en')
+ ->options(fn (User $user) => $user->getAvailableLanguages()),
+ Hidden::make('skipValidation')->default(true),
+ CheckboxList::make('roles')
+ ->disabled(fn (User $user) => $user->id === auth()->user()->id)
+ ->disableOptionWhen(fn (string $value): bool => $value == Role::getRootAdmin()->id)
+ ->relationship('roles', 'name')
+ ->label('Admin Roles')
+ ->columnSpanFull()
+ ->bulkToggleable(false),
+ ]),
]);
}
protected function getHeaderActions(): array
diff --git a/app/Filament/Resources/UserResource/Pages/ListUsers.php b/app/Filament/Resources/UserResource/Pages/ListUsers.php
index 2ce3384e5dc..b5c677f52bd 100644
--- a/app/Filament/Resources/UserResource/Pages/ListUsers.php
+++ b/app/Filament/Resources/UserResource/Pages/ListUsers.php
@@ -85,6 +85,25 @@ protected function getHeaderActions(): array
CreateAction::make('create')
->label('Create User')
->createAnother(false)
+ ->successRedirectUrl(route('filament.admin.resources.users.index'))
+ ->action(function (array $data) {
+ $roles = $data['roles'];
+ $roles = collect($roles)->map(fn ($role) => Role::findById($role));
+ unset($data['roles']);
+
+ /** @var UserCreationService $creationService */
+ $creationService = resolve(UserCreationService::class);
+ $user = $creationService->handle($data);
+
+ $user->syncRoles($roles);
+
+ Notification::make()
+ ->title('User Created!')
+ ->success()
+ ->send();
+
+ return redirect()->route('filament.admin.resources.users.index');
+ })
->form([
Grid::make()
->schema([
@@ -110,26 +129,7 @@ protected function getHeaderActions(): array
->columnSpanFull()
->bulkToggleable(false),
]),
- ])
- ->successRedirectUrl(route('filament.admin.resources.users.index'))
- ->action(function (array $data) {
- $roles = $data['roles'];
- $roles = collect($roles)->map(fn ($role) => Role::findById($role));
- unset($data['roles']);
-
- /** @var UserCreationService $creationService */
- $creationService = resolve(UserCreationService::class);
- $user = $creationService->handle($data);
-
- $user->syncRoles($roles);
-
- Notification::make()
- ->title('User Created!')
- ->success()
- ->send();
-
- return redirect()->route('filament.admin.resources.users.index');
- }),
+ ]),
];
}
}