diff --git a/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php b/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php index 3846c562f..b421e6d83 100644 --- a/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php +++ b/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php @@ -3,6 +3,7 @@ namespace App\Filament\Admin\Resources\Nodes\RelationManagers; use App\Filament\Admin\Resources\Servers\Pages\CreateServer; +use App\Filament\Components\Actions\UpdateNodeAllocations; use App\Models\Allocation; use App\Models\Node; use App\Services\Allocations\AssignmentService; @@ -80,7 +81,9 @@ class AllocationsRelationManager extends RelationManager ->searchable() ->label(trans('admin/node.table.ip')), ]) - ->headerActions([ + ->toolbarActions([ + DeleteBulkAction::make() + ->authorize(fn () => user()?->can('update', $this->getOwnerRecord())), Action::make('create new allocation') ->label(trans('admin/node.create_allocation')) ->schema(fn () => [ @@ -118,9 +121,8 @@ class AllocationsRelationManager extends RelationManager ->required(), ]) ->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord(), $data)), - ]) - ->groupedBulkActions([ - DeleteBulkAction::make() + UpdateNodeAllocations::make() + ->nodeRecord($this->getOwnerRecord()) ->authorize(fn () => user()?->can('update', $this->getOwnerRecord())), ]); } diff --git a/app/Filament/Components/Actions/UpdateNodeAllocations.php b/app/Filament/Components/Actions/UpdateNodeAllocations.php new file mode 100644 index 000000000..cf186f82c --- /dev/null +++ b/app/Filament/Components/Actions/UpdateNodeAllocations.php @@ -0,0 +1,106 @@ +label(trans('admin/node.bulk_update_ip')); + + $this->icon('tabler-replace'); + + $this->color('warning'); + + $this->requiresConfirmation(); + + $this->modalHeading(trans('admin/node.bulk_update_ip')); + + $this->modalDescription(trans('admin/node.bulk_update_ip_description')); + + $this->modalIconColor('warning'); + + $this->modalSubmitActionLabel(trans('admin/node.update_ip')); + + $this->schema(function () { + /** @var Node $node */ + $node = $this->record; + + $currentIps = Allocation::where('node_id', $node->id) + ->pluck('ip') + ->unique() + ->values() + ->all(); + + return [ + Select::make('old_ip') + ->label(trans('admin/node.old_ip')) + ->options(array_combine($currentIps, $currentIps)) + ->selectablePlaceholder(false) + ->required() + ->live(), + Select::make('new_ip') + ->label(trans('admin/node.new_ip')) + ->options(fn () => array_combine($node->ipAddresses(), $node->ipAddresses()) ?: []) + ->required() + ->different('old_ip'), + ]; + }); + + $this->action(function (array $data) { + /** @var Node $node */ + $node = $this->record; + $allocations = Allocation::where('node_id', $node->id)->where('ip', $data['old_ip'])->get(); + + if ($allocations->count() === 0) { + Notification::make() + ->title(trans('admin/node.no_allocations_to_update')) + ->warning() + ->send(); + + return; + } + + $updated = 0; + $failed = 0; + + foreach ($allocations as $allocation) { + try { + $allocation->update(['ip' => $data['new_ip']]); + $updated++; + } catch (Exception $exception) { + $failed++; + report($exception); + } + } + + Notification::make() + ->title(trans('admin/node.ip_updated', ['count' => $updated, 'total' => $allocations->count()])) + ->body($failed > 0 ? trans('admin/node.ip_update_failed', ['count' => $failed]) : null) + ->status($failed > 0 ? 'warning' : 'success') + ->persistent() + ->send(); + }); + } + + public function nodeRecord(Node $node): static + { + $this->record = $node; + + return $this; + } +} diff --git a/lang/en/admin/node.php b/lang/en/admin/node.php index 745c63afb..40b514673 100644 --- a/lang/en/admin/node.php +++ b/lang/en/admin/node.php @@ -140,4 +140,13 @@ return [ 'title' => 'Cloudflare Issue', 'body' => 'Your Node is not accessible by Cloudflare', ], + + 'bulk_update_ip' => 'Update IPs', + 'bulk_update_ip_description' => 'Replace an old IP address with a new one for allocations. This is useful when a node\'s IP address changes', + 'update_ip' => 'Update IP', + 'old_ip' => 'Old IP Address', + 'new_ip' => 'New IP Address', + 'no_allocations_to_update' => 'No allocations with the selected old IP address were found', + 'ip_updated' => 'Successfully updated :count of :total allocation(s)', + 'ip_update_failed' => ':count allocation(s) failed to update', ];