diff --git a/app/Filament/Admin/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php b/app/Filament/Admin/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php index ba7ee06b01..7273ddbe0a 100644 --- a/app/Filament/Admin/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php +++ b/app/Filament/Admin/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php @@ -2,6 +2,7 @@ namespace App\Filament\Admin\Resources\NodeResource\RelationManagers; +use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer; use App\Models\Allocation; use App\Models\Node; use App\Services\Allocations\AssignmentService; @@ -9,6 +10,7 @@ use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; +use Filament\Forms\Get; use Filament\Forms\Set; use Filament\Resources\RelationManagers\RelationManager; use Filament\Tables; @@ -80,6 +82,8 @@ public function table(Table $table): Table ->inlineLabel() ->ipv4() ->helperText("Usually your machine's public IP unless you are port forwarding.") + ->afterStateUpdated(fn (Set $set) => $set('allocation_ports', [])) + ->live() ->required(), TextInput::make('allocation_alias') ->label('Alias') @@ -97,54 +101,10 @@ public function table(Table $table): Table ->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; - } - - $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); - foreach (range($start, $end) as $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()); - } - }) + ->disabled(fn (Get $get) => empty($get('allocation_ip'))) + ->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports', + CreateServer::retrieveValidPorts($this->getOwnerRecord(), $state, $get('allocation_ip'))) + ) ->splitKeys(['Tab', ' ', ',']) ->required(), ]) diff --git a/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php index 745e45aa76..caca65cce0 100644 --- a/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php @@ -198,86 +198,47 @@ public function form(Form $form): Form ->where('node_id', $get('node_id')) ->whereNull('server_id'), ) - ->createOptionForm(fn (Get $get) => [ - Select::make('allocation_ip') - ->options(collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip])) - ->label('IP Address') - ->inlineLabel() - ->ipv4() - ->helperText("Usually your machine's public IP unless you are port forwarding.") - ->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); - - 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); - $range = $start <= $end ? range($start, $end) : range($end, $start); - foreach ($range as $i) { - if ($i > 1024 && $i <= 65535) { - $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; - } - - if ($update) { - $set('allocation_ports', $ports->all()); - } - }) - ->splitKeys(['Tab', ' ', ',']) - ->required(), - ]) + ->createOptionForm(function (Get $get) { + $getPage = $get; + + return [ + Select::make('allocation_ip') + ->options(collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip])) + ->label('IP Address') + ->helperText("Usually your machine's public IP unless you are port forwarding.") + ->afterStateUpdated(fn (Set $set) => $set('allocation_ports', [])) + ->inlineLabel() + ->ipv4() + ->live() + ->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() + ->disabled(fn (Get $get) => empty($get('allocation_ip'))) + ->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports', + CreateServer::retrieveValidPorts(Node::find($getPage('node_id')), $state, $get('allocation_ip'))) + ) + ->splitKeys(['Tab', ' ', ',']) + ->required(), + ]; + }) ->createOptionUsing(function (array $data, Get $get, AssignmentService $assignmentService): int { return collect( $assignmentService->handle(Node::find($get('node_id')), $data) @@ -922,4 +883,88 @@ private function getSelectOptionsFromRules(Get $get): array ->mapWithKeys(fn ($value) => [$value => $value]) ->all(); } + + public static function retrieveValidPorts(Node $node, array $portEntries, string $ip): array + { + $portRangeLimit = AssignmentService::PORT_RANGE_LIMIT; + $portFloor = AssignmentService::PORT_FLOOR; + $portCeil = AssignmentService::PORT_CEIL; + + $ports = collect(); + + $existingPorts = $node + ->allocations() + ->where('ip', $ip) + ->pluck('port') + ->all(); + + foreach ($portEntries as $portEntry) { + $start = $end = $portEntry; + if (str_contains($portEntry, '-')) { + [$start, $end] = explode('-', $portEntry); + } + + if (!is_numeric($start) || !is_numeric($end)) { + Notification::make() + ->title('Invalid Port Range') + ->danger() + ->body("Your port range are not valid integers: $portEntry") + ->send(); + + continue; + } + + $start = (int) $start; + $end = (int) $end; + $range = $start <= $end ? range($start, $end) : range($end, $start); + + if (count($range) > $portRangeLimit) { + Notification::make() + ->title('Too many ports at one time!') + ->danger() + ->body("The current limit is $portRangeLimit number of ports at one time.") + ->send(); + + continue; + } + + foreach ($range as $i) { + // Invalid port number + if ($i <= $portFloor || $i > $portCeil) { + Notification::make() + ->title('Port not in valid range') + ->danger() + ->body("$i is not in the valid port range between $portFloor-$portCeil") + ->send(); + + continue; + } + + // Already exists + if (in_array($i, $existingPorts)) { + Notification::make() + ->title('Port already in use') + ->danger() + ->body("$i is already with an allocation") + ->send(); + + continue; + } + + $ports->push($i); + } + } + + $uniquePorts = $ports->unique()->values(); + if ($ports->count() > $uniquePorts->count()) { + $ports = $uniquePorts; + } + + $sortedPorts = $ports->sort()->values(); + if ($sortedPorts->all() !== $ports->all()) { + $ports = $sortedPorts; + } + + return $ports->all(); + } } diff --git a/app/Filament/Admin/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php b/app/Filament/Admin/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php index f0fca624a5..43a2ed2ab0 100644 --- a/app/Filament/Admin/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php +++ b/app/Filament/Admin/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php @@ -2,6 +2,7 @@ namespace App\Filament\Admin\Resources\ServerResource\RelationManagers; +use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer; use App\Models\Allocation; use App\Models\Server; use App\Services\Allocations\AssignmentService; @@ -9,6 +10,7 @@ use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; +use Filament\Forms\Get; use Filament\Forms\Set; use Filament\Resources\RelationManagers\RelationManager; use Filament\Tables; @@ -78,6 +80,7 @@ public function table(Table $table): Table ->inlineLabel() ->ipv4() ->helperText("Usually your machine's public IP unless you are port forwarding.") + ->afterStateUpdated(fn (Set $set) => $set('allocation_ports', [])) ->required(), TextInput::make('allocation_alias') ->label('Alias') @@ -95,54 +98,9 @@ public function table(Table $table): Table ->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; - } - - $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); - foreach (range($start, $end) as $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()); - } - }) + ->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports', + CreateServer::retrieveValidPorts($this->getOwnerRecord()->node, $state, $get('allocation_ip'))) + ) ->splitKeys(['Tab', ' ', ',']) ->required(), ]) diff --git a/app/Models/Node.php b/app/Models/Node.php index 011abb2527..72e2b26121 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -387,6 +387,8 @@ public function ipAddresses(): array // pass } + $ips->push('0.0.0.0'); + // Only IPV4 $ips = $ips->filter(fn (string $ip) => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false);