Skip to content

Conversation

@JoanFo1456
Copy link
Contributor

pelican-dev/wings#155
Using Quinten's wings pull request, this fixes the servers not transfering their backups.

@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Adds backup selection to the server transfer flow: the transfer form shows daemon backups for selection, unselected daemon backups are deleted via DeleteBackupService (with per-backup error notifications), and selected backup UUIDs are forwarded to TransferServerService::handle/notify. New localization keys added.

Changes

Cohort / File(s) Summary
Admin UI / Transfer Form
app/Filament/Admin/Resources/Servers/Pages/EditServer.php
Adds a CheckboxList (backups) of server daemon backups with bulk toggle, dynamic columns, and a helper warning_backups; hidden when no daemon backups. Transfer action now injects DeleteBackupService, computes selectedBackupUuids, deletes server daemon backups not selected (reporting per-backup deletion failures via notifications), and passes selected backup UUIDs to TransferServerService::handle.
Server Transfer Service
app/Services/Servers/TransferServerService.php
handle() signature extended to accept ?array $backup_uuid = [] and forwards it to notify(); notify() signature extended to array $backup_uuids = [] and includes backups in the Wings HTTP payload when config('backups.default') equals the daemon adapter constant. Docblocks updated.
Imports / Declarations
app/Filament/.../EditServer.php, app/Services/Servers/TransferServerService.php
Added imports: App\Models\Backup, App\Services\Backups\DeleteBackupService, and Filament form components (CheckboxList, Text).
Localization
lang/en/admin/server.php
Added select_backups, warning_backups, and notifications.backup_transfer_failed localization keys.

Sequence Diagram(s)

sequenceDiagram
  participant AdminUI as Admin UI (Filament)
  participant EditPage as EditServer Page Action
  participant DeleteSvc as DeleteBackupService
  participant TransferSvc as TransferServerService
  participant Wings as Wings API

  AdminUI->>EditPage: Submit transfer form (node_id, allocation_id, additional_allocations, backups[])
  EditPage->>DeleteSvc: deleteBackup(backup) for each daemon backup not in backups[]
  alt deletion failures
    DeleteSvc-->>EditPage: deletion error per-backup
    EditPage-->>AdminUI: notify deletion failures
  end
  EditPage->>TransferSvc: handle(server, node_id, allocation_id, additional_allocations, backups[])
  TransferSvc->>Wings: POST /api/servers/{id}/transfer with token + payload (node, allocations, backups)
  alt Wings accepts
    Wings-->>TransferSvc: 200 OK
    TransferSvc-->>AdminUI: success notification
  else Wings rejects
    Wings-->>TransferSvc: error
    TransferSvc-->>AdminUI: error notification (backup_transfer_failed)
  end
Loading
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Allow backup transfers' directly and concisely describes the main change—enabling backup transfers during server transfers—which aligns with the primary objective of the PR.
Description check ✅ Passed The description references the Wings PR and states the fix is for 'servers not transfering their backups', which is directly related to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3141fe6 and f45da27.

📒 Files selected for processing (3)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
  • app/Services/Servers/TransferServerService.php
  • lang/en/admin/server.php
🧰 Additional context used
🧬 Code graph analysis (2)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)
app/Models/Server.php (3)
  • transfer (386-389)
  • Server (135-552)
  • backups (394-397)
app/Services/Servers/TransferServerService.php (1)
  • handle (54-109)
app/Services/Servers/TransferServerService.php (1)
app/Models/Server.php (2)
  • backups (394-397)
  • Server (135-552)
🔇 Additional comments (3)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)

1056-1063: LGTM! Well-implemented backup selection field.

The multi-select field for backups is properly configured:

  • Disabled when no backups exist
  • Maps backup IDs correctly for form submission
  • Includes helpful warning text for users
  • Uses appropriate icon and placeholder

966-966: Verify backup ID validation in service layer.

The backup IDs are passed directly from form data to the service. While the form field is constrained to the server's backups, ensure the service layer also validates that the provided backup IDs actually belong to the server to prevent potential security issues.

This validation should happen in TransferServerService::handle() or notify() method (see my comments on that file).

app/Services/Servers/TransferServerService.php (1)

32-32: No action needed. The configuration key config('backups.default') is correct and 'wings' is a valid backup driver value defined in config/backups.php. The code properly checks the backup driver configuration to include backups during the server transfer operation.

Copy link
Contributor

@QuintenQVD0 QuintenQVD0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

private function notify(ServerTransfer $transfer, UnencryptedToken $token, array $backup_ids = []): void
{
$backups = [];
if (config('backups.default') === 'wings') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (config('backups.default') === 'wings') {
if (config('backups.default') === Backup::ADAPTER_DAEMON) {

->helperText(trans('admin/server.warning_backups'))
->prefixIcon('tabler-copy-check')
->disabled(fn (Server $server) => $server->backups->count() === 0)
->options(fn (Server $server) => $server->backups->mapWithKeys(fn ($backup) => [$backup->id => $backup->name ?: $backup->uuid]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the uuid directly? This way you don't have to do lookups later.

Suggested change
->options(fn (Server $server) => $server->backups->mapWithKeys(fn ($backup) => [$backup->id => $backup->name ?: $backup->uuid]))
->options(fn (Server $server) => $server->backups->mapWithKeys(fn ($backup) => [$backup->uuid => $backup->name]))

public function handle(Server $server, int $node_id, ?int $allocation_id = null, ?array $additional_allocations = []): bool
public function handle(Server $server, int $node_id, ?int $allocation_id = null, ?array $additional_allocations = [], ?array $backup_ids = []): bool
{

Copy link
Member

@Boy132 Boy132 Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove blank line.

Suggested change

->options(fn (Get $get) => Allocation::where('node_id', $get('node_id'))->whereNull('server_id')->when($get('allocation_id'), fn ($query) => $query->whereNot('id', $get('allocation_id')))->get()->mapWithKeys(fn (Allocation $allocation) => [$allocation->id => $allocation->address]))
->searchable(['ip', 'port', 'ip_alias'])
->placeholder(trans('admin/server.select_additional')),
Select::make('backups')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make that a checkboxlist. Looks nicer and allows bulk toggle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backup uuids should be added to the ServerTransfer model.

@JoanFo1456
Copy link
Contributor Author

I made your suggestions Boy, and a bunch of other things, as Wings was deleting backups but not the panel.

@JoanFo1456 JoanFo1456 requested a review from Boy132 January 10, 2026 14:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/Filament/Admin/Resources/Servers/Pages/EditServer.php:
- Around line 972-984: The deletion loop is currently operating on
$server->backups->whereNotIn('uuid', $selectedBackupUuids) which includes S3
backups that cannot be selected and thus will be deleted; update the collection
filtering to restrict to only daemon-managed backups before calling ->each(...).
Concretely, chain or apply a filter (e.g., where('disk', 'daemon') or
->filter(fn($b) => $b->disk === 'daemon' / $b->isDaemon()) ) to the
$server->backups query/collection so only daemon backups are passed into the
->each(...) that calls $deleteBackup->handle($backup), leaving S3 backups
untouched and preserving the existing try/catch Notification behavior.
🧹 Nitpick comments (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1)

1079-1083: Consider capping the column count for large backup lists.

The columns calculation ceil($record->backups->...->count() / 4) could produce many columns for servers with numerous backups (e.g., 100 backups → 25 columns), degrading the modal layout.

♻️ Suggested improvement
-    ->columns(fn (Server $record) => (int) ceil($record->backups->where('disk', Backup::ADAPTER_DAEMON)->count() / 4)),
+    ->columns(fn (Server $record) => min(4, (int) ceil($record->backups->where('disk', Backup::ADAPTER_DAEMON)->count() / 4))),
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5de26 and 47586b1.

📒 Files selected for processing (3)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
  • app/Services/Servers/TransferServerService.php
  • lang/en/admin/server.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • lang/en/admin/server.php
  • app/Services/Servers/TransferServerService.php
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-16T19:30:21.443Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1708
File: app/Filament/App/Resources/Servers/Pages/ListServers.php:115-116
Timestamp: 2025-09-16T19:30:21.443Z
Learning: The Filament `recordActions` method accepts both arrays and ActionGroup instances (signature: `array | ActionGroup $actions`), so ActionGroup can be passed directly without wrapping in an array.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-10-15T11:55:53.461Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1801
File: app/Extensions/OAuth/Schemas/AuthentikSchema.php:7-10
Timestamp: 2025-10-15T11:55:53.461Z
Learning: In Filament v4, Wizard Step components use the Filament\Schemas namespace (Filament\Schemas\Components\Wizard\Step), not Filament\Forms.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-09-16T19:32:01.343Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1708
File: app/Filament/App/Resources/Servers/Pages/ListServers.php:228-266
Timestamp: 2025-09-16T19:32:01.343Z
Learning: ActionGroup in Filament automatically hides itself when all child actions are not visible, so additional hidden() logic to prevent empty groups is unnecessary.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-10-15T11:55:53.461Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1801
File: app/Extensions/OAuth/Schemas/AuthentikSchema.php:7-10
Timestamp: 2025-10-15T11:55:53.461Z
Learning: In Filament v4, the Forms component Placeholder was deprecated and removed. Use TextEntry from Filament\Infolists\Components\TextEntry in forms instead, binding values with ->state(). For HTML content, use ->html().

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
🧬 Code graph analysis (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (3)
app/Models/Backup.php (1)
  • Backup (36-107)
app/Models/Server.php (2)
  • Server (135-552)
  • backups (394-397)
app/Services/Backups/DeleteBackupService.php (1)
  • DeleteBackupService (16-85)
🔇 Additional comments (3)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (3)

14-14: LGTM!

The new imports are correctly organized and all are utilized in the implementation.

Also applies to: 19-19, 32-32, 54-54


975-984: Verify intended behavior when backup deletion fails.

If a backup deletion fails, the loop continues and the transfer proceeds. This means:

  • The failed-to-delete backup remains on the source node
  • It won't be transferred (not in selectedBackupUuids)
  • The user sees a notification per failure

Confirm this "best-effort deletion" behavior is acceptable, or consider aborting the transfer if any deletion fails.


1084-1086: LGTM!

The Text schema component is appropriate for displaying the backup warning message in Filament v4.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/Filament/Admin/Resources/Servers/Pages/EditServer.php:
- Around line 968-987: The code currently deletes daemon backups before calling
TransferServerService::handle, risking data loss if the transfer fails; change
the flow so you first call $transfer->handle($server, Arr::get($data,
'node_id'), Arr::get($data, 'allocation_id'), Arr::get($data,
'allocation_additional', []), $selectedBackupUuids) and only after it completes
successfully iterate $server->backups->whereNotIn('uuid',
$selectedBackupUuids)->where('disk', Backup::ADAPTER_DAEMON)->each(...) to call
DeleteBackupService::handle for each backup (keeping the inner try-catch and
Notification on individual delete failures), and ensure the outer try-catch
still handles transfer exceptions so deletions never run when transfer throws.
🧹 Nitpick comments (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1)

1077-1089: Consider improving column calculation for backup selection.

The column count calculation on line 1084 divides the backup count by 4 and rounds up. This results in a single column for 1-4 backups, which may create a vertically elongated layout. Consider using a minimum column count or a different formula for better visual balance.

♻️ Suggested improvement for column calculation
-    ->columns(fn (Server $record) => (int) ceil($record->backups->where('disk', Backup::ADAPTER_DAEMON)->count() / 4)),
+    ->columns(fn (Server $record) => max(2, min(4, (int) ceil($record->backups->where('disk', Backup::ADAPTER_DAEMON)->count() / 4)))),

This ensures:

  • Minimum 2 columns for better layout
  • Maximum 4 columns to prevent over-spreading
  • Scales between 2-4 columns based on backup count

Additionally, consider eager-loading the backups relationship to avoid multiple collection filtering operations throughout the form (lines 1083, 1084, 1089).

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47586b1 and f4c8b02.

📒 Files selected for processing (1)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-16T19:30:21.443Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1708
File: app/Filament/App/Resources/Servers/Pages/ListServers.php:115-116
Timestamp: 2025-09-16T19:30:21.443Z
Learning: The Filament `recordActions` method accepts both arrays and ActionGroup instances (signature: `array | ActionGroup $actions`), so ActionGroup can be passed directly without wrapping in an array.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-10-15T11:55:53.461Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1801
File: app/Extensions/OAuth/Schemas/AuthentikSchema.php:7-10
Timestamp: 2025-10-15T11:55:53.461Z
Learning: In Filament v4, Wizard Step components use the Filament\Schemas namespace (Filament\Schemas\Components\Wizard\Step), not Filament\Forms.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-09-16T19:32:01.343Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1708
File: app/Filament/App/Resources/Servers/Pages/ListServers.php:228-266
Timestamp: 2025-09-16T19:32:01.343Z
Learning: ActionGroup in Filament automatically hides itself when all child actions are not visible, so additional hidden() logic to prevent empty groups is unnecessary.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-10-15T11:55:53.461Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1801
File: app/Extensions/OAuth/Schemas/AuthentikSchema.php:7-10
Timestamp: 2025-10-15T11:55:53.461Z
Learning: In Filament v4, the Forms component Placeholder was deprecated and removed. Use TextEntry from Filament\Infolists\Components\TextEntry in forms instead, binding values with ->state(). For HTML content, use ->html().

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
🧬 Code graph analysis (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (3)
app/Models/Backup.php (1)
  • Backup (36-107)
app/Models/Server.php (3)
  • Server (135-552)
  • transfer (386-389)
  • backups (394-397)
app/Services/Backups/DeleteBackupService.php (1)
  • DeleteBackupService (16-85)
🔇 Additional comments (2)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)

14-14: LGTM: Imports added for backup transfer functionality.

The new imports are appropriate for the backup selection and deletion features being added.

Also applies to: 19-19, 32-32, 54-54


987-987: No issues found. The TransferServerService::handle() method correctly accepts the backup UUIDs array as the fifth parameter with the type hint ?array $backup_uuid = [], as documented on line 51 of the service class.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1)

968-986: Service signature is correct; refactoring suggestions remain optional.

The TransferServerService::handle() method signature correctly accepts the $selectedBackupUuids parameter as the 5th argument (?array $backup_uuid = []), and the code usage matches the service definition.

For future optimization, consider these optional improvements:

  1. Collection vs Query Builder: Line 973 loads all backups into memory before filtering. For servers with many backups, use the query builder:

    $server->backups()
        ->whereNotIn('uuid', $selectedBackupUuids)
        ->where('disk', Backup::ADAPTER_DAEMON)
        ->get()
        ->each(function ($backup) use ($deleteBackup) { ... });
  2. Notification aggregation: If multiple backup deletions fail, consider collecting errors and showing a single summary notification instead of multiple individual notifications.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4c8b02 and 2632a4e.

📒 Files selected for processing (1)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-16T19:30:21.443Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1708
File: app/Filament/App/Resources/Servers/Pages/ListServers.php:115-116
Timestamp: 2025-09-16T19:30:21.443Z
Learning: The Filament `recordActions` method accepts both arrays and ActionGroup instances (signature: `array | ActionGroup $actions`), so ActionGroup can be passed directly without wrapping in an array.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-10-15T11:55:53.461Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1801
File: app/Extensions/OAuth/Schemas/AuthentikSchema.php:7-10
Timestamp: 2025-10-15T11:55:53.461Z
Learning: In Filament v4, Wizard Step components use the Filament\Schemas namespace (Filament\Schemas\Components\Wizard\Step), not Filament\Forms.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-09-16T19:32:01.343Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1708
File: app/Filament/App/Resources/Servers/Pages/ListServers.php:228-266
Timestamp: 2025-09-16T19:32:01.343Z
Learning: ActionGroup in Filament automatically hides itself when all child actions are not visible, so additional hidden() logic to prevent empty groups is unnecessary.

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
📚 Learning: 2025-10-15T11:55:53.461Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1801
File: app/Extensions/OAuth/Schemas/AuthentikSchema.php:7-10
Timestamp: 2025-10-15T11:55:53.461Z
Learning: In Filament v4, the Forms component Placeholder was deprecated and removed. Use TextEntry from Filament\Infolists\Components\TextEntry in forms instead, binding values with ->state(). For HTML content, use ->html().

Applied to files:

  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php
🧬 Code graph analysis (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (3)
app/Models/Backup.php (1)
  • Backup (36-107)
app/Models/Server.php (3)
  • Server (135-552)
  • transfer (386-389)
  • backups (394-397)
app/Services/Backups/DeleteBackupService.php (1)
  • DeleteBackupService (16-85)
🔇 Additional comments (2)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)

14-14: LGTM! New imports are necessary and correctly added.

The imported classes are properly used throughout the file for the backup transfer functionality.

Also applies to: 19-19, 32-32, 54-54


1076-1088: LGTM! Backup selection UI is well-implemented.

The CheckboxList component is properly configured with:

  • Bulk toggle functionality for user convenience
  • Correct filtering to daemon backups only
  • Appropriate visibility logic to hide when no backups exist
  • Warning message to inform users about deletion of unselected backups

The dynamic column calculation on Line 1083 (ceil(count / 4)) aims to display approximately 4 backups per column, which works well for typical scenarios. For servers with very large numbers of backups (e.g., 100+), this might create many columns, but this is an acceptable edge case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants