diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php
index 2d92ddda7..b0e61dc32 100644
--- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php
+++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php
@@ -3,16 +3,13 @@
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource;
-use App\Models\Allocation;
use App\Models\Egg;
use App\Models\Node;
-use App\Services\Allocations\AssignmentService;
use App\Services\Servers\RandomWordService;
use App\Services\Servers\ServerCreationService;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Form;
use Filament\Resources\Pages\CreateRecord;
-use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Filament\Forms;
use Filament\Forms\Components\Wizard;
@@ -27,6 +24,9 @@ class CreateServer extends CreateRecord
protected static bool $canCreateAnother = false;
public ?Node $node = null;
+ public ?Egg $egg = null;
+ public array $ports = [];
+ public array $eggDefaultPorts = [];
public function form(Form $form): Form
{
@@ -89,187 +89,24 @@ class CreateServer extends CreateRecord
'default' => 1,
'sm' => 2,
'md' => 2,
- 'lg' => 2,
+ 'lg' => 3,
])
->live()
->relationship('node', 'name')
->searchable()
->preload()
->afterStateUpdated(function (Forms\Set $set, $state) {
- $set('allocation_id', null);
$this->node = Node::find($state);
})
->required(),
- Forms\Components\Select::make('allocation_id')
- ->preload()
- ->live()
- ->prefixIcon('tabler-network')
- ->label('Primary Allocation')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->disabled(fn (Forms\Get $get) => $get('node_id') === null)
- ->searchable(['ip', 'port', 'ip_alias'])
- ->afterStateUpdated(function (Forms\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 (Forms\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, Forms\Get $get) => $query
- ->where('node_id', $get('node_id'))
- ->whereNull('server_id'),
- )
- ->createOptionForm(fn (Forms\Get $get) => [
- Forms\Components\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(),
- Forms\Components\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),
- Forms\Components\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, Forms\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;
- }
-
- $update = true;
- [$start, $end] = explode('-', $portEntry);
- if (!is_numeric($start) || !is_numeric($end)) {
- continue;
- }
-
- $start = max((int) $start, 0);
- $end = min((int) $end, 2 ** 16 - 1);
- for ($i = $start; $i <= $end; $i++) {
- $ports->push($i);
- }
- }
-
- $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;
- }
-
- $ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values();
-
- if ($update) {
- $set('allocation_ports', $ports->all());
- }
- })
- ->splitKeys(['Tab', ' ', ','])
- ->required(),
- ])
- ->createOptionUsing(function (array $data, Forms\Get $get): int {
- return collect(
- resolve(AssignmentService::class)->handle(Node::find($get('node_id')), $data)
- )->first();
- })
- ->required(),
-
- Forms\Components\Repeater::make('allocation_additional')
- ->label('Additional Allocations')
- ->columnSpan([
- 'default' => 1,
- 'sm' => 2,
- 'md' => 1,
- 'lg' => 2,
- ])
- ->addActionLabel('Add Allocation')
- ->disabled(fn (Forms\Get $get) => $get('allocation_id') === null)
- // ->addable() TODO disable when all allocations are taken
- // ->addable() TODO disable until first additional allocation is selected
- ->simple(
- Forms\Components\Select::make('extra_allocations')
- ->live()
- ->preload()
- ->disableOptionsWhenSelectedInSiblingRepeaterItems()
- ->prefixIcon('tabler-network')
- ->label('Additional Allocations')
- ->columnSpan(2)
- ->disabled(fn (Forms\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, Forms\Get $get, Forms\Components\Select $component, $state) => $query
- ->where('node_id', $get('../../node_id'))
- ->whereNot('id', $get('../../allocation_id'))
- ->whereNull('server_id'),
- ),
- ),
-
Forms\Components\TextInput::make('description')
->placeholder('Description')
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
- 'lg' => 6,
+ 'lg' => 3,
])
->label('Notes'),
]),
@@ -285,42 +122,30 @@ class CreateServer extends CreateRecord
'lg' => 4,
])
->schema([
+
Forms\Components\Select::make('egg_id')
+ ->disabledOn('edit')
->prefixIcon('tabler-egg')
- ->relationship('egg', 'name')
->columnSpan([
- 'default' => 1,
+ 'default' => 2,
'sm' => 2,
'md' => 2,
'lg' => 3,
])
+ ->relationship('egg', 'name')
->searchable()
->preload()
->live()
->afterStateUpdated(function ($state, Forms\Set $set, Forms\Get $get, $old) {
- $egg = Egg::query()->find($state);
- $set('startup', $egg->startup);
+ $this->egg = Egg::query()->find($state);
+ $set('startup', $this->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);
+ $this->resetEggVariables($set, $get);
$previousEgg = Egg::query()->find($old);
if (!$get('name') || $previousEgg?->getKebabName() === $get('name')) {
- $set('name', $egg->getKebabName());
+ $set('name', $this->egg->getKebabName());
}
})
->required(),
@@ -349,12 +174,52 @@ class CreateServer extends CreateRecord
->inline()
->required(),
+ Forms\Components\TagsInput::make('ports')
+ ->columnSpan(2)
+ ->placeholder('Example: 25565, 8080, 1337-1340')
+ ->splitKeys(['Tab', ' ', ','])
+ ->hidden(fn () => !$this->egg)
+ ->helperText(new HtmlString('
+ These are the ports that users can connect to this Server through.
+
+ You would typically port forward these on your home network.
+ '))
+ ->label('Ports')
+ ->afterStateUpdated(self::ports(...))
+ ->live(),
+
+ Forms\Components\Repeater::make('assignments')
+ ->columnSpan(2)
+ ->defaultItems(fn () => count($this->eggDefaultPorts))
+ ->label('Port Assignments')
+ ->helperText(fn (Forms\Get $get) => empty($get('ports')) ? 'You must add ports to assign them!' : '')
+ ->live()
+ ->addable(false)
+ ->deletable(false)
+ ->reorderable(false)
+ ->hidden(fn () => !$this->egg)
+ ->simple(
+ Forms\Components\Select::make('port')
+ ->live()
+ ->disabled(fn (Forms\Get $get) => empty($get('../../ports')) || empty($get('../../assignments')))
+ ->prefix(function (Forms\Components\Component $component) {
+ $key = str($component->getStatePath())->beforeLast('.')->afterLast('.')->toString();
+
+ return $key;
+ })
+ ->disableOptionsWhenSelectedInSiblingRepeaterItems()
+ ->options(fn (Forms\Get $get) => $this->ports)
+ ->required(),
+ ),
+
Forms\Components\Textarea::make('startup')
+ ->hidden(fn () => !$this->egg)
->hintIcon('tabler-code')
->label('Startup Command')
- ->hidden(fn (Forms\Get $get) => $get('egg_id') === null)
->required()
->live()
+ ->disabled(fn (Forms\Get $get) => $this->egg === null)
+ ->afterStateUpdated($this->resetEggVariables(...))
->columnSpan([
'default' => 1,
'sm' => 2,
@@ -364,7 +229,7 @@ class CreateServer extends CreateRecord
->rows(function ($state) {
return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
- 1
+ 0
);
}),
@@ -446,6 +311,7 @@ class CreateServer extends CreateRecord
->columnSpan(2),
]),
]),
+
Wizard\Step::make('Environment Configuration')
->label('Environment Configuration')
->icon('tabler-brand-docker')
@@ -754,12 +620,14 @@ class CreateServer extends CreateRecord
protected function handleRecordCreation(array $data): Model
{
- $data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all();
+ foreach (array_keys($this->eggDefaultPorts) as $i => $env) {
+ $data['environment'][$env] = $data['ports'][$data['assignments'][$i]];
+ }
/** @var ServerCreationService $service */
$service = resolve(ServerCreationService::class);
- return $service->handle($data);
+ return $service->handle($data, validateVariables: false);
}
private function shouldHideComponent(Forms\Get $get, Forms\Components\Component $component): bool
@@ -792,4 +660,82 @@ class CreateServer extends CreateRecord
->mapWithKeys(fn ($value) => [$value => $value])
->all();
}
+
+ public function ports($state, Forms\Set $set)
+ {
+ $ports = collect();
+ $update = false;
+ foreach ($state as $portEntry) {
+ if (!str_contains($portEntry, '-')) {
+ if (is_numeric($portEntry)) {
+ $ports->push((int) $portEntry);
+
+ continue;
+ }
+
+ continue;
+ }
+
+ [$start, $end] = explode('-', $portEntry);
+ if (!is_numeric($start) || !is_numeric($end)) {
+ continue;
+ }
+
+ $start = max((int) $start, 0);
+ $end = min((int) $end, 2 ** 16 - 1);
+ for ($i = $start; $i <= $end; $i++) {
+ $ports->push($i);
+ }
+ }
+
+ $uniquePorts = $ports->unique()->values();
+ if ($ports->count() > $uniquePorts->count()) {
+ $ports = $uniquePorts;
+ }
+
+ $ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values();
+
+ $set('ports', $ports->all());
+ $this->ports = $ports->all();
+ }
+
+ public function resetEggVariables(Forms\Set $set, Forms\Get $get)
+ {
+ $set('assignments', []);
+
+ $i = 0;
+ $this->eggDefaultPorts = [];
+ if (str_contains($get('startup'), '{{SERVER_PORT}}')) {
+ $this->eggDefaultPorts['SERVER_PORT'] = null;
+ $set('assignments.SERVER_PORT', ['port' => null]);
+ }
+
+ $variables = $this->egg->variables ?? [];
+ $serverVariables = collect();
+ $this->ports = [];
+ foreach ($variables as $variable) {
+ if (str_contains($variable->rules, 'port')) {
+ $this->eggDefaultPorts[$variable->env_variable] = $variable->default_value;
+ $this->ports[] = (int) $variable->default_value;
+
+ $set("assignments.$i", ['port' => $i++]);
+
+ continue;
+ }
+
+ $serverVariables->add($variable->toArray());
+ }
+
+ $set('ports', $this->ports);
+
+ $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);
+ }
}
diff --git a/app/Filament/Resources/ServerResource/Pages/ListServers.php b/app/Filament/Resources/ServerResource/Pages/ListServers.php
index 0f150144d..4a7c0725e 100644
--- a/app/Filament/Resources/ServerResource/Pages/ListServers.php
+++ b/app/Filament/Resources/ServerResource/Pages/ListServers.php
@@ -68,13 +68,7 @@ class ListServers extends ListRecords
->label('Owner')
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
->sortable(),
- Tables\Columns\SelectColumn::make('allocation_id')
- ->label('Primary Allocation')
- ->options(fn ($state, Server $server) => $server->allocations->mapWithKeys(
- fn ($allocation) => [$allocation->id => $allocation->address])
- )
- ->selectablePlaceholder(false)
- ->sortable(),
+ Tables\Columns\TextColumn::make('ports'),
Tables\Columns\TextColumn::make('image')->hidden(),
Tables\Columns\TextColumn::make('backups_count')
->counts('backups')