Skip to content

Conversation

@Boy132
Copy link
Member

@Boy132 Boy132 commented Dec 4, 2025

(Backend) Refactors for subuser permissions:

  • Removed Permissions model
  • Added enum for subuser permissions
  • Moved all permission stuff to Subuser model
  • Allow to register custom subuser permissions (closes Allow custom subuser permissions #1923)

Permission names have not changed! So (in theory) no breaking changes for existing installs!

@Boy132 Boy132 self-assigned this Dec 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

📝 Walkthrough

Walkthrough

Migrates permission handling from the removed App\Models\Permission model to a new App\Enums\SubuserPermission enum; updates request signatures, controllers, policies, Filament resources, services, models, factories, transformers, and tests; adds static permission aggregation APIs on Subuser and extends User to accept enum-typed permission checks.

Changes

Cohort / File(s) Summary
Core types & models
app/Enums/SubuserPermission.php, app/Models/Permission.php, app/Models/Subuser.php, app/Contracts/Http/ClientPermissionsRequest.php, app/Models/User.php
Added SubuserPermission enum and helpers; removed App\Models\Permission; added Subuser static APIs (allPermissionData, allPermissionKeys, doesPermissionExist, registerCustomPermissions); updated client permission contract and User to accept `SubuserPermission
API request classes
app/Http/Requests/Api/Client/Servers/... (e.g. Backups/*, Databases/*, Files/*, Network/*, Schedules/*, Startup/*, Subusers/*, Settings/*, SendPowerRequest.php, SendCommandRequest.php)
~44 request classes: changed permission() return type from string to SubuserPermission, replaced returned Permission constants with corresponding SubuserPermission values, and updated imports.
Controllers & services
app/Http/Controllers/Api/Client/ClientController.php, app/Http/Controllers/Api/Client/Servers/..., app/Http/Controllers/Api/Remote/SftpAuthenticationController.php, app/Services/Servers/GetUserPermissionsService.php, app/Services/Subusers/*
Replaced Permission usage with SubuserPermission and Subuser static APIs; ClientController::permissions() now returns Subuser::allPermissionKeys(); controllers/services updated to use enum values for checks.
Filament resources, pages & components
app/Filament/... (resources, pages, widgets, components, app/Filament/Server/Resources/Subusers/SubuserResource.php)
Swapped Permission constants for SubuserPermission across Filament pages/resources/actions/widgets; SubuserResource sources permission sections from Subuser::allPermissionData(), filters hidden groups, and stores enum-based values; authorization closures updated.
Policies
app/Policies/*.php (e.g., ServerPolicy.php, AllocationPolicy.php, BackupPolicy.php, DatabasePolicy.php, FilePolicy.php, SchedulePolicy.php, SubuserPolicy.php, ActivityLogPolicy.php)
Replaced Permission constants with SubuserPermission enum values; ServerPolicy now uses Subuser::doesPermissionExist() for existence checks; method signatures unchanged.
Transformers
app/Transformers/Api/Client/DatabaseTransformer.php, app/Transformers/Api/Client/ServerTransformer.php
Replaced visibility/permission checks to use SubuserPermission enum variants; imports updated.
Factories & database
database/Factories/SubuserFactory.php, database/Factories/PermissionFactory.php
Updated factories to reference SubuserPermission (e.g., websocket value); removed App\Models\Permission import from PermissionFactory (may affect name resolution).
Filament server resource groups
app/Filament/Server/Resources/*/* (Allocations, Backups, Databases, Files, Schedules, Settings, etc.)
Numerous authorization/visibility/disabled closures switched to SubuserPermission enum values; behavior otherwise unchanged.
Tests
tests/Feature/*, tests/Integration/Api/Client/*, tests/Filament/* (30+ files)
Updated imports, fixtures and assertions to use SubuserPermission enum and Subuser::allPermissionKeys(); replaced Permission constants with enum values across test suites.

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Refactor subuser permissions' accurately reflects the main objective of the pull request, which is a comprehensive refactoring of the subuser permissions system.
Description check ✅ Passed The description clearly outlines the key changes: removal of Permission model, addition of enum, migration to Subuser model, and custom permissions support, all directly related to the changeset.
Linked Issues check ✅ Passed The PR addresses issue #1923 by implementing custom subuser permissions registration via Subuser::registerCustomPermissions() method, fulfilling the primary objective.
Out of Scope Changes check ✅ Passed All changes consistently focus on the permission refactoring scope: replacing Permission model with SubuserPermission enum, updating related imports, and centralizing permission logic in Subuser model.

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.

@Boy132 Boy132 force-pushed the boy132/refactor-subuser-permissions branch from ba9844b to b51ffe4 Compare December 4, 2025 09:17
@Boy132
Copy link
Member Author

Boy132 commented Dec 4, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (9)
app/Services/Subusers/SubuserUpdateService.php (1)

5-6: Clarify / harden permission type handling in SubuserUpdateService::handle

The updated filter correctly preserves the special case for WebsocketConnect and delegates all other entries through user()->can(...), which fits the new SubuserPermission flow. One small improvement:

  • The docblock states string[] $permissions, but elsewhere in the codebase tests appear to pass SubuserPermission enum cases directly. In that scenario, the comparison to SubuserPermission::WebsocketConnect->value will not match, and the special case will only be honored via can(...) (i.e. the user must actually have that permission).

To keep behavior predictable, consider either:

  • Enforcing string inputs at call sites (always passing ->value), or
  • Supporting both forms here, e.g.:
->filter(function ($permission) use ($server) {
    $isWebsocket =
        $permission === SubuserPermission::WebsocketConnect
        || $permission === SubuserPermission::WebsocketConnect->value;

    return $isWebsocket || user()?->can($permission, $server);
})

Also applies to: 21-28

tests/Feature/SettingsControllerTest.php (1)

4-7: Feature tests now consistently exercise SubuserPermission-based settings access

The updated tests correctly migrate from Permission constants to SubuserPermission cases and keep the expected behavior:

  • Rename: unauthorized with only WebsocketConnect, allowed with WebsocketConnect + SettingsRename.
  • Description: gated on SettingsDescription and the config flag.
  • Docker image: unauthorized with only WebsocketConnect, authorized and validated with StartupDockerImage.
  • Reinstall: gated on SettingsReinstall.

Since generateTestAccount() stores the passed array directly as the Subuser’s permissions, just ensure the Subuser model’s casting for permissions is configured to accept enum cases as well as string values so these tests match runtime behavior.

Also applies to: 14-26, 28-76, 78-91, 95-135, 138-155, 157-179

tests/Integration/Api/Client/ClientControllerTest.php (1)

158-193: Use enum values when seeding permissions for Subuser records

Here you’re persisting permissions as [SubuserPermission::WebsocketConnect] directly. Elsewhere (e.g., generateTestAccount() in CreatesTestModels) permissions are normalized to ->value before storage. For consistency and to avoid JSON blobs containing enum objects instead of scalar permission keys, consider storing the string values here as well.

 Subuser::query()->create([
     'user_id' => $users[0]->id,
     'server_id' => $servers[1]->id,
-    'permissions' => [SubuserPermission::WebsocketConnect],
+    'permissions' => [SubuserPermission::WebsocketConnect->value],
 ]);

 // …

 Subuser::query()->create([
     'user_id' => $users[0]->id,
     'server_id' => $servers[1]->id,
-    'permissions' => [SubuserPermission::WebsocketConnect],
+    'permissions' => [SubuserPermission::WebsocketConnect->value],
 ]);

 // …

 Subuser::query()->create([
     'user_id' => $users[0]->id,
     'server_id' => $servers[1]->id,
-    'permissions' => [SubuserPermission::WebsocketConnect],
+    'permissions' => [SubuserPermission::WebsocketConnect->value],
 ]);

Also applies to: 239-243, 274-278

app/Enums/SubuserPermission.php (1)

70-87: Unused $permission variable from destructuring.

The static analysis correctly flags that $permission is destructured but never used. Since only the group is needed, consider simplifying.

 public function getIcon(): ?string
 {
-    [$group, $permission] = $this->split();
+    [$group] = $this->split();

     return match ($group) {
app/Http/Requests/Api/Client/Servers/Backups/RenameBackupRequest.php (1)

10-12: Migration correct, but semantically unusual permission choice.

The migration to SubuserPermission::BackupDelete is consistent with the previous implementation (preserving existing behavior as intended). However, requiring "delete" permission for a rename operation is semantically unusual—rename is typically an "update" operation. Since there's no BackupUpdate permission defined in the enum, this may warrant adding one in a future iteration, or it may be an intentional design choice to keep backup permissions simple.

app/Http/Controllers/Api/Client/Servers/SubuserController.php (1)

136-149: Tighten typing and normalization in getCleanedPermissions()

The implementation effectively returns a list of permission-key strings, but the PHPDoc is overly loose and keys may remain non-sequential:

  • Docblock says array<array-key, mixed> while the method returns string[].
  • Returning a reindexed list avoids any surprises from preserved request keys.

You could make this more explicit and normalized:

-    /**
-     * @return array<array-key, mixed>
-     */
-    protected function getCleanedPermissions(Request $request): array
-    {
-        return collect($request->input('permissions') ?? [])
-            ->intersect(Subuser::allPermissionKeys())
-            ->push(SubuserPermission::WebsocketConnect->value)
-            ->unique()
-            ->toArray();
-    }
+    /**
+     * @return string[]
+     */
+    protected function getCleanedPermissions(Request $request): array
+    {
+        return collect($request->input('permissions') ?? [])
+            ->intersect(Subuser::allPermissionKeys())
+            ->push(SubuserPermission::WebsocketConnect->value)
+            ->unique()
+            ->values()
+            ->toArray();
+    }
app/Models/Subuser.php (2)

36-47: Fix inaccurate PHPDoc for $customPermissions and registerCustomPermission()

The documented shapes don’t match the actual data structure:

  • $customPermissions is documented as array<string, array<string>>, but you’re storing a list of associative arrays with keys name, icon, and permissions.
  • $permissions in registerCustomPermission() is documented as array<string, array<string>>, but appears to be a simple list of permission slugs (string[]).

Consider tightening these to better reflect reality, e.g.:

-    /** @var array<string, array<string>> */
-    protected static array $customPermissions = [];
+    /** @var list<array{name: string, icon: string, permissions: string[]}> */
+    protected static array $customPermissions = [];

-    /** @param array<string, array<string>> $permissions */
-    public static function registerCustomPermission(string $name, string $icon, array $permissions): void
+    /** @param string[] $permissions */
+    public static function registerCustomPermission(string $name, string $icon, array $permissions): void

This will help static analysis and IDEs understand the structure more accurately.


87-125: Permission aggregation helpers look correct; consider future-proofing if usage grows

allPermissionData() and allPermissionKeys() correctly derive grouped permission metadata and flattened keys from SubuserPermission::cases() plus $customPermissions, and doesPermissionExist() safely validates keys via allPermissionKeys().

No functional issues stand out here. If these helpers end up in hot paths (e.g., called many times per request), you might later consider memoizing the computed arrays and invalidating them from registerCustomPermission(), but that’s an optimization, not a blocker.

app/Filament/Server/Resources/Databases/DatabaseResource.php (1)

213-236: Resource-level permission checks align with SubuserPermission; handle unused $record warnings

The canViewAny/canView/canCreate/canEdit/canDelete methods now consistently delegate to:

  • DatabaseRead for view/viewAny
  • DatabaseCreate for create
  • DatabaseUpdate for edit
  • DatabaseDelete for delete

This matches the intended enum-based permission scheme.

PHPMD’s “unused parameter $record” warnings for canView, canEdit, and canDelete stem from Filament’s required method signature. If these warnings are noisy in CI, consider one of:

  • Renaming the parameter to $_record to signal intentional non-use, or
  • Adding an appropriate PHPMD suppression annotation for these methods.

Functionally, the implementation is fine as-is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bda2f9a and 88a0f7e.

📒 Files selected for processing (107)
  • app/Contracts/Http/ClientPermissionsRequest.php (1 hunks)
  • app/Enums/SubuserPermission.php (1 hunks)
  • app/Extensions/Features/Schemas/GSLTokenSchema.php (2 hunks)
  • app/Extensions/Features/Schemas/JavaVersionSchema.php (2 hunks)
  • app/Filament/Admin/Resources/Roles/Pages/CreateRole.php (2 hunks)
  • app/Filament/App/Resources/Servers/Pages/ListServers.php (2 hunks)
  • app/Filament/Components/Actions/ExportScheduleAction.php (2 hunks)
  • app/Filament/Components/Actions/ImportScheduleAction.php (2 hunks)
  • app/Filament/Server/Pages/Console.php (3 hunks)
  • app/Filament/Server/Pages/Settings.php (7 hunks)
  • app/Filament/Server/Pages/Startup.php (5 hunks)
  • app/Filament/Server/Resources/Activities/ActivityResource.php (2 hunks)
  • app/Filament/Server/Resources/Allocations/AllocationResource.php (6 hunks)
  • app/Filament/Server/Resources/Backups/BackupResource.php (7 hunks)
  • app/Filament/Server/Resources/Databases/DatabaseResource.php (4 hunks)
  • app/Filament/Server/Resources/Files/FileResource.php (2 hunks)
  • app/Filament/Server/Resources/Files/Pages/DownloadFiles.php (2 hunks)
  • app/Filament/Server/Resources/Files/Pages/EditFiles.php (4 hunks)
  • app/Filament/Server/Resources/Files/Pages/ListFiles.php (17 hunks)
  • app/Filament/Server/Resources/Schedules/Pages/ViewSchedule.php (2 hunks)
  • app/Filament/Server/Resources/Schedules/ScheduleResource.php (2 hunks)
  • app/Filament/Server/Resources/Users/UserResource.php (8 hunks)
  • app/Filament/Server/Widgets/ServerConsole.php (3 hunks)
  • app/Http/Controllers/Api/Client/ClientController.php (2 hunks)
  • app/Http/Controllers/Api/Client/Servers/ActivityLogController.php (2 hunks)
  • app/Http/Controllers/Api/Client/Servers/BackupController.php (7 hunks)
  • app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php (2 hunks)
  • app/Http/Controllers/Api/Client/Servers/SubuserController.php (4 hunks)
  • app/Http/Controllers/Api/Client/Servers/WebsocketController.php (2 hunks)
  • app/Http/Controllers/Api/Remote/SftpAuthenticationController.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Backups/RenameBackupRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Databases/RotatePasswordRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/ChmodFilesRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/DecompressFilesRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Network/DeleteAllocationRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Network/GetNetworkRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Network/NewAllocationRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Network/UpdateAllocationRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Schedules/DeleteScheduleRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Schedules/StoreScheduleRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Schedules/TriggerScheduleRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Schedules/UpdateScheduleRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Schedules/ViewScheduleRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/SendCommandRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/SendPowerRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Settings/DescriptionServerRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php (2 hunks)
  • app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Subusers/GetSubuserRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php (1 hunks)
  • app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php (1 hunks)
  • app/Models/Permission.php (0 hunks)
  • app/Models/Subuser.php (3 hunks)
  • app/Models/User.php (3 hunks)
  • app/Policies/ServerPolicy.php (2 hunks)
  • app/Services/Servers/GetUserPermissionsService.php (2 hunks)
  • app/Services/Subusers/SubuserCreationService.php (2 hunks)
  • app/Services/Subusers/SubuserUpdateService.php (2 hunks)
  • app/Transformers/Api/Client/DatabaseTransformer.php (2 hunks)
  • app/Transformers/Api/Client/ServerTransformer.php (5 hunks)
  • database/Factories/PermissionFactory.php (0 hunks)
  • database/Factories/SubuserFactory.php (2 hunks)
  • tests/Feature/SettingsControllerTest.php (10 hunks)
  • tests/Filament/Admin/ListEggsTest.php (0 hunks)
  • tests/Filament/Admin/ListNodesTest.php (0 hunks)
  • tests/Integration/Api/Client/ClientControllerTest.php (7 hunks)
  • tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/CommandControllerTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php (4 hunks)
  • tests/Integration/Api/Client/Server/PowerControllerTest.php (4 hunks)
  • tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php (2 hunks)
  • tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php (2 hunks)
  • tests/Integration/Api/Client/Server/SettingsControllerTest.php (4 hunks)
  • tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php (10 hunks)
  • tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php (3 hunks)
  • tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php (5 hunks)
  • tests/Integration/Api/Client/Server/WebsocketControllerTest.php (4 hunks)
⛔ Files not processed due to max files limit (2)
  • tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php
  • tests/Traits/Integration/CreatesTestModels.php
💤 Files with no reviewable changes (4)
  • database/Factories/PermissionFactory.php
  • app/Models/Permission.php
  • tests/Filament/Admin/ListNodesTest.php
  • tests/Filament/Admin/ListEggsTest.php
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-12T17:33:57.388Z
Learnt from: lancepioch
Repo: pelican-dev/panel PR: 1595
File: app/Services/Servers/ServerCreationService.php:182-184
Timestamp: 2025-08-12T17:33:57.388Z
Learning: In the Pelican Panel codebase, when updating allocations to assign them to servers, the preferred pattern is to use whereNull('server_id'), lockForUpdate(), and direct property assignment with save() rather than mass update methods, to prevent race conditions and mass-assignment issues while ensuring model events fire properly.

Applied to files:

  • app/Filament/Server/Resources/Allocations/AllocationResource.php
📚 Learning: 2025-11-05T22:21:31.863Z
Learnt from: notAreYouScared
Repo: pelican-dev/panel PR: 1865
File: app/Filament/Admin/Resources/Nodes/Pages/EditNode.php:682-682
Timestamp: 2025-11-05T22:21:31.863Z
Learning: In app/Filament/Admin/Resources/Nodes/Pages/EditNode.php, the diagnostics tab's upload action intentionally does not use the iconButton() modifier, while the pull action does. This UI difference is intentional.

Applied to files:

  • app/Filament/Server/Resources/Files/Pages/ListFiles.php
🧬 Code graph analysis (66)
tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Requests/Api/Client/Servers/Network/NewAllocationRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Filament/Server/Resources/Allocations/AllocationResource.php (7)
app/helpers.php (1)
  • user (127-130)
app/Models/Server.php (1)
  • allocation (280-283)
app/Filament/Server/Resources/Backups/BackupResource.php (2)
  • canCreate (306-309)
  • canDelete (311-314)
app/Filament/Server/Resources/Databases/DatabaseResource.php (3)
  • canCreate (223-226)
  • canEdit (228-231)
  • canDelete (233-236)
app/Filament/Server/Resources/Files/FileResource.php (3)
  • canCreate (38-41)
  • canEdit (43-46)
  • canDelete (48-51)
app/Filament/Server/Resources/Schedules/ScheduleResource.php (3)
  • canCreate (72-75)
  • canEdit (77-80)
  • canDelete (82-85)
app/Filament/Server/Resources/Users/UserResource.php (3)
  • canCreate (72-75)
  • canEdit (77-80)
  • canDelete (82-85)
app/Http/Requests/Api/Client/Servers/Files/ChmodFilesRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Requests/Api/Client/Servers/Schedules/TriggerScheduleRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Filament/Components/Actions/ExportScheduleAction.php (1)
app/helpers.php (1)
  • user (127-130)
app/Http/Requests/Api/Client/Servers/SendCommandRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Subusers/GetSubuserRequest.php (1)
app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php (1)
  • SubuserRequest (14-77)
app/Http/Requests/Api/Client/Servers/Network/DeleteAllocationRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
tests/Feature/SettingsControllerTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php (3)
tests/Pest.php (1)
  • generateTestAccount (127-145)
tests/Integration/Api/Client/ClientApiIntegrationTestCase.php (1)
  • link (29-52)
app/Models/Server.php (1)
  • allocation (280-283)
tests/Integration/Api/Client/Server/WebsocketControllerTest.php (1)
tests/Traits/Integration/CreatesTestModels.php (1)
  • generateTestAccount (72-90)
app/Extensions/Features/Schemas/GSLTokenSchema.php (3)
app/helpers.php (1)
  • user (127-130)
app/Models/User.php (1)
  • can (362-371)
app/Models/Schedule.php (1)
  • server (141-144)
tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php (1)
tests/Traits/Integration/CreatesTestModels.php (1)
  • generateTestAccount (72-90)
app/Services/Subusers/SubuserUpdateService.php (2)
app/helpers.php (1)
  • user (127-130)
app/Models/User.php (1)
  • can (362-371)
app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php (1)
app/Http/Requests/Api/Client/Servers/Settings/DescriptionServerRequest.php (1)
  • permission (16-19)
tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Filament/Server/Resources/Files/FileResource.php (6)
app/helpers.php (1)
  • user (127-130)
app/Filament/Server/Resources/Allocations/AllocationResource.php (3)
  • canCreate (130-133)
  • canEdit (135-138)
  • canDelete (140-143)
app/Filament/Server/Resources/Backups/BackupResource.php (2)
  • canCreate (306-309)
  • canDelete (311-314)
app/Filament/Server/Resources/Databases/DatabaseResource.php (3)
  • canCreate (223-226)
  • canEdit (228-231)
  • canDelete (233-236)
app/Filament/Server/Resources/Schedules/ScheduleResource.php (3)
  • canCreate (72-75)
  • canEdit (77-80)
  • canDelete (82-85)
app/Filament/Server/Resources/Users/UserResource.php (3)
  • canCreate (72-75)
  • canEdit (77-80)
  • canDelete (82-85)
app/Filament/Server/Widgets/ServerConsole.php (1)
app/Models/User.php (1)
  • can (362-371)
tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Requests/Api/Client/Servers/Network/GetNetworkRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Network/UpdateAllocationRequest.php (2)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Models/Allocation.php (1)
  • Allocation (48-141)
app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Filament/Server/Resources/Activities/ActivityResource.php (1)
app/helpers.php (1)
  • user (127-130)
app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php (2)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php (1)
  • permission (9-12)
app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Filament/Server/Resources/Users/UserResource.php (2)
app/Models/Subuser.php (3)
  • Subuser (24-126)
  • user (82-85)
  • allPermissionData (88-106)
app/Models/User.php (1)
  • can (362-371)
tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Controllers/Api/Client/Servers/BackupController.php (1)
app/Models/User.php (1)
  • can (362-371)
tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Schedules/DeleteScheduleRequest.php (1)
app/Http/Requests/Api/Client/Servers/Schedules/ViewScheduleRequest.php (1)
  • ViewScheduleRequest (12-43)
app/Models/User.php (3)
app/Models/Subuser.php (3)
  • server (74-77)
  • Subuser (24-126)
  • doesPermissionExist (118-125)
app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php (1)
  • permission (10-13)
app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php (1)
  • permission (9-12)
app/Http/Requests/Api/Client/Servers/SendPowerRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Settings/DescriptionServerRequest.php (1)
app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php (1)
  • permission (17-20)
app/Transformers/Api/Client/DatabaseTransformer.php (2)
app/Models/User.php (1)
  • can (362-371)
app/Models/Database.php (1)
  • server (84-87)
app/Http/Controllers/Api/Remote/SftpAuthenticationController.php (1)
app/Http/Controllers/Api/Client/ClientController.php (1)
  • permissions (85-93)
app/Http/Controllers/Api/Client/Servers/SubuserController.php (3)
app/Services/Subusers/SubuserCreationService.php (1)
  • handle (39-77)
app/Services/Subusers/SubuserUpdateService.php (1)
  • handle (21-61)
app/Models/Subuser.php (4)
  • server (74-77)
  • user (82-85)
  • Subuser (24-126)
  • allPermissionKeys (109-116)
tests/Integration/Api/Client/ClientControllerTest.php (3)
app/Models/Subuser.php (4)
  • Subuser (24-126)
  • allPermissionKeys (109-116)
  • user (82-85)
  • server (74-77)
tests/Traits/Integration/CreatesTestModels.php (1)
  • generateTestAccount (72-90)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Extensions/Features/Schemas/JavaVersionSchema.php (2)
app/helpers.php (1)
  • user (127-130)
app/Models/User.php (1)
  • can (362-371)
app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php (1)
app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php (1)
  • SubuserRequest (14-77)
app/Filament/Server/Resources/Files/Pages/ListFiles.php (4)
app/helpers.php (1)
  • user (127-130)
app/Filament/Server/Resources/Databases/DatabaseResource.php (1)
  • canEdit (228-231)
app/Filament/Server/Resources/Files/FileResource.php (1)
  • canEdit (43-46)
app/Filament/Server/Resources/Schedules/ScheduleResource.php (1)
  • canEdit (77-80)
app/Filament/Server/Pages/Console.php (2)
app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php (1)
  • authorize (23-46)
app/Models/User.php (1)
  • can (362-371)
app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
tests/Integration/Api/Client/Server/SettingsControllerTest.php (2)
app/Models/Server.php (1)
  • user (262-265)
tests/Pest.php (1)
  • generateTestAccount (127-145)
tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php (1)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Filament/Components/Actions/ImportScheduleAction.php (1)
app/helpers.php (1)
  • user (127-130)
app/Http/Requests/Api/Client/Servers/Databases/RotatePasswordRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Filament/Server/Resources/Schedules/ScheduleResource.php (6)
app/helpers.php (1)
  • user (127-130)
app/Filament/Server/Resources/Allocations/AllocationResource.php (3)
  • canCreate (130-133)
  • canEdit (135-138)
  • canDelete (140-143)
app/Filament/Server/Resources/Backups/BackupResource.php (2)
  • canCreate (306-309)
  • canDelete (311-314)
app/Filament/Server/Resources/Databases/DatabaseResource.php (3)
  • canCreate (223-226)
  • canEdit (228-231)
  • canDelete (233-236)
app/Filament/Server/Resources/Files/FileResource.php (3)
  • canCreate (38-41)
  • canEdit (43-46)
  • canDelete (48-51)
app/Filament/Server/Resources/Users/UserResource.php (3)
  • canCreate (72-75)
  • canEdit (77-80)
  • canDelete (82-85)
tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php (1)
tests/Traits/Integration/CreatesTestModels.php (1)
  • generateTestAccount (72-90)
app/Filament/Server/Resources/Backups/BackupResource.php (6)
app/helpers.php (1)
  • user (127-130)
app/Filament/Server/Resources/Allocations/AllocationResource.php (2)
  • canCreate (130-133)
  • canDelete (140-143)
app/Filament/Server/Resources/Databases/DatabaseResource.php (2)
  • canCreate (223-226)
  • canDelete (233-236)
app/Filament/Server/Resources/Files/FileResource.php (2)
  • canCreate (38-41)
  • canDelete (48-51)
app/Filament/Server/Resources/Schedules/ScheduleResource.php (2)
  • canCreate (72-75)
  • canDelete (82-85)
app/Filament/Server/Resources/Users/UserResource.php (2)
  • canCreate (72-75)
  • canDelete (82-85)
app/Policies/ServerPolicy.php (1)
app/Models/Subuser.php (2)
  • Subuser (24-126)
  • doesPermissionExist (118-125)
app/Models/Subuser.php (2)
app/Http/Controllers/Api/Client/ClientController.php (1)
  • permissions (85-93)
app/Enums/SubuserPermission.php (2)
  • split (60-63)
  • isHidden (65-68)
app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php (2)
app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php (1)
  • SubuserRequest (14-77)
app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php (1)
  • permission (10-13)
tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php (2)
app/Models/Allocation.php (1)
  • server (129-132)
tests/Pest.php (1)
  • generateTestAccount (127-145)
app/Http/Requests/Api/Client/Servers/Backups/RenameBackupRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php (1)
app/Http/Requests/Api/Client/ClientApiRequest.php (1)
  • ClientApiRequest (9-30)
🪛 PHPMD (2.15.0)
tests/Integration/Api/Client/Server/CommandControllerTest.php

55-55: Avoid unused local variables such as '$user'. (undefined)

(UnusedLocalVariable)

app/Filament/Server/Resources/Allocations/AllocationResource.php

135-135: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


140-140: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Enums/SubuserPermission.php

72-72: Avoid unused local variables such as '$permission'. (undefined)

(UnusedLocalVariable)

app/Filament/Server/Resources/Files/FileResource.php

43-43: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


48-48: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Filament/Server/Resources/Users/UserResource.php

77-77: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


82-82: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Filament/Server/Resources/Databases/DatabaseResource.php

218-218: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


228-228: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


233-233: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Filament/Server/Resources/Schedules/ScheduleResource.php

77-77: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


82-82: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Filament/Server/Resources/Backups/BackupResource.php

311-311: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

@Boy132 Boy132 marked this pull request as ready for review December 4, 2025 10:53
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

🧹 Nitpick comments (1)
app/Filament/Server/Resources/Users/UserResource.php (1)

5-10: Enum‑based permission checks look consistent with the new model

Importing SubuserPermission/Subuser and swapping canViewAny/canCreate/canEdit/canDelete to user()?->can(SubuserPermission::User*, Filament::getTenant()) matches the rest of the enum‑based refactor and keeps the resource aligned with the new User* permission names. The $record parameter in canEdit/canDelete remains unused but is required by Filament’s static signature, so PHPMD’s unused‑parameter warning can be safely ignored or suppressed.

Also applies to: 67-85

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88a0f7e and 548ba4d.

📒 Files selected for processing (6)
  • app/Contracts/Http/ClientPermissionsRequest.php (1 hunks)
  • app/Filament/Server/Resources/Users/UserResource.php (8 hunks)
  • app/Http/Controllers/Api/Client/Servers/SubuserController.php (4 hunks)
  • app/Models/Subuser.php (3 hunks)
  • database/Factories/SubuserFactory.php (2 hunks)
  • tests/Integration/Api/Client/ClientControllerTest.php (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/Integration/Api/Client/ClientControllerTest.php
  • app/Contracts/Http/ClientPermissionsRequest.php
🧰 Additional context used
🧬 Code graph analysis (3)
app/Http/Controllers/Api/Client/Servers/SubuserController.php (2)
app/Http/Controllers/Api/Client/ClientController.php (1)
  • permissions (85-93)
app/Models/Subuser.php (4)
  • server (83-86)
  • user (91-94)
  • Subuser (24-148)
  • allPermissionKeys (131-138)
app/Filament/Server/Resources/Users/UserResource.php (8)
app/Models/Server.php (1)
  • Server (134-525)
app/Models/Subuser.php (4)
  • Subuser (24-148)
  • user (91-94)
  • allPermissionData (97-128)
  • server (83-86)
app/helpers.php (1)
  • user (127-130)
app/Models/User.php (1)
  • can (362-371)
app/Filament/Server/Resources/Files/FileResource.php (3)
  • canCreate (38-41)
  • canEdit (43-46)
  • canDelete (48-51)
app/Filament/Server/Resources/Databases/DatabaseResource.php (3)
  • canCreate (223-226)
  • canEdit (228-231)
  • canDelete (233-236)
app/Filament/Server/Resources/Backups/BackupResource.php (2)
  • canCreate (306-309)
  • canDelete (311-314)
app/Filament/Server/Resources/Schedules/ScheduleResource.php (3)
  • canCreate (72-75)
  • canEdit (77-80)
  • canDelete (82-85)
app/Models/Subuser.php (2)
app/Http/Controllers/Api/Client/ClientController.php (1)
  • permissions (85-93)
app/Enums/SubuserPermission.php (2)
  • split (60-63)
  • isHidden (65-68)
🪛 PHPMD (2.15.0)
app/Filament/Server/Resources/Users/UserResource.php

77-77: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


82-82: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (7)
database/Factories/SubuserFactory.php (1)

5-26: Enum-backed permission usage looks correct and backward‑compatible

Switching the factory default from the old Permission constant to SubuserPermission::WebsocketConnect->value keeps the stored value as the enum backing value, which should preserve existing behavior and data format while aligning with the new enum-based API. No issues from this change in the factory.

app/Filament/Server/Resources/Users/UserResource.php (2)

95-125: Using Subuser::allPermissionData() correctly drives dynamic permission tabs

Iterating over Subuser::allPermissionData() and skipping $data['hidden'] ensures hidden/internal permissions (e.g., websocket connect) don’t surface in the UI while still allowing custom permission groups to appear. The Section::make()->contained(false) change is purely presentational and doesn’t affect behavior.


160-173: WebsocketConnect auto‑grant is consistent, but double‑check that it’s intended

Both the edit and invite flows now:

  • Authorize using SubuserPermission::UserUpdate / UserCreate, and
  • Build $permissions from the selected groups and always push(SubuserPermission::WebsocketConnect->value) before deduping.

This keeps Filament behavior consistent with the API controller (which also forces WebsocketConnect) and guarantees that any subuser with UI‑assigned permissions can connect via websockets. Please confirm that granting WebsocketConnect even when no other permissions are selected is the desired behavior.

Also applies to: 243-249, 296-303

app/Http/Controllers/Api/Client/Servers/SubuserController.php (2)

83-96: Store flow now logs persisted permissions and returns the created subuser

Using $permissions = $this->getCleanedPermissions($request) and then logging ['email' => $email, 'permissions' => $subuser->permissions] ensures the activity log reflects the actual saved state instead of the raw request payload, and returning the transformed $subuser keeps the API response coherent with that change.


109-119: Permission cleaning logic is sound; confirm always‑on WebsocketConnect

getCleanedPermissions():

collect($request->input('permissions') ?? [])
    ->intersect(Subuser::allPermissionKeys())
    ->push(SubuserPermission::WebsocketConnect->value)
    ->unique()
    ->values()
    ->toArray();

This:

  • Discards any unknown permission strings,
  • Guarantees a well‑formed string[] for the services, and
  • Always adds WebsocketConnect (even if the client omits it or sends no permissions).

That behavior matches the Filament resource and centralizes sanitization for both store and update. Please confirm that granting websocket access to every created/updated subuser is intentional from an API perspective.

Also applies to: 136-151

app/Models/Subuser.php (2)

36-56: Custom permissions registry API is clear and composable

$customPermissions plus registerCustomPermissions($name, array $permissions, ?string $icon = null, ?bool $hidden = null) gives extensions a straightforward way to add/augment permission groups and reuse the same structure as enum‑derived groups. Merging permissions across multiple calls is handy for plugins that register in stages.


130-147: Permission key helpers are cohesive and match the new enum model

allPermissionKeys() composing "$data['name'].$permission" from allPermissionData() and doesPermissionExist(string|SubuserPermission $permission) normalizing enums to strings give you a single authoritative source of valid permission identifiers. That fits well with the new controller/resource code that intersects or validates against Subuser::allPermissionKeys().

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/Policies/Server/BackupPolicy.php (1)

17-20: Unused $record parameters are expected; optionally silence static‑analysis noise

The $record parameters in view() and delete() are unused but are part of Laravel’s expected policy method signatures, so they should not be removed. If PHPMD/other tools are too noisy, you can optionally:

  • Add a small comment or annotation indicating the parameter is required but unused, or
  • Rename to /** @noinspection PhpUnusedParameterInspection */ or _record if your tooling respects that style.

Not strictly necessary, but may keep static‑analysis output cleaner.

Also applies to: 27-30

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 548ba4d and 32e4825.

📒 Files selected for processing (12)
  • app/Filament/Server/Resources/Allocations/AllocationResource.php (5 hunks)
  • app/Filament/Server/Resources/Backups/BackupResource.php (6 hunks)
  • app/Filament/Server/Resources/Databases/DatabaseResource.php (3 hunks)
  • app/Filament/Server/Resources/Users/UserResource.php (7 hunks)
  • app/Policies/Server/ActivityLogPolicy.php (2 hunks)
  • app/Policies/Server/AllocationPolicy.php (2 hunks)
  • app/Policies/Server/BackupPolicy.php (2 hunks)
  • app/Policies/Server/DatabasePolicy.php (2 hunks)
  • app/Policies/Server/FilePolicy.php (2 hunks)
  • app/Policies/Server/SchedulePolicy.php (2 hunks)
  • app/Policies/Server/ServerPolicy.php (2 hunks)
  • app/Policies/Server/UserPolicy.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/Filament/Server/Resources/Databases/DatabaseResource.php
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-02T19:55:23.924Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1837
File: app/Filament/Server/Resources/Schedules/ScheduleResource.php:337-338
Timestamp: 2025-12-02T19:55:23.924Z
Learning: In Filament resources, when ViewAction is hidden using `->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed())`, this is intentional UX design: the ViewAction (read-only) should be hidden when users have edit privileges, showing the EditAction instead. The ViewAction serves as a fallback for users who only have view permissions. This pattern is correct and should not be flagged as inverted logic.

Applied to files:

  • app/Policies/Server/AllocationPolicy.php
  • app/Policies/Server/FilePolicy.php
  • app/Policies/Server/UserPolicy.php
📚 Learning: 2025-08-12T17:33:57.388Z
Learnt from: lancepioch
Repo: pelican-dev/panel PR: 1595
File: app/Services/Servers/ServerCreationService.php:182-184
Timestamp: 2025-08-12T17:33:57.388Z
Learning: In the Pelican Panel codebase, when updating allocations to assign them to servers, the preferred pattern is to use whereNull('server_id'), lockForUpdate(), and direct property assignment with save() rather than mass update methods, to prevent race conditions and mass-assignment issues while ensuring model events fire properly.

Applied to files:

  • app/Filament/Server/Resources/Allocations/AllocationResource.php
🧬 Code graph analysis (7)
app/Policies/Server/ServerPolicy.php (1)
app/Models/Subuser.php (2)
  • Subuser (24-148)
  • doesPermissionExist (140-147)
app/Policies/Server/FilePolicy.php (7)
app/Policies/Server/ActivityLogPolicy.php (1)
  • view (17-20)
app/Policies/Server/AllocationPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/BackupPolicy.php (3)
  • view (17-20)
  • create (22-25)
  • delete (27-30)
app/Policies/Server/DatabasePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/SchedulePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/UserPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Models/User.php (1)
  • User (95-493)
app/Policies/Server/UserPolicy.php (9)
app/Policies/Server/ActivityLogPolicy.php (1)
  • view (17-20)
app/Policies/Server/AllocationPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/BackupPolicy.php (3)
  • view (17-20)
  • create (22-25)
  • delete (27-30)
app/Policies/Server/DatabasePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/FilePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/SchedulePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Admin/DefaultPolicies.php (3)
  • view (15-18)
  • create (20-23)
  • delete (30-33)
app/Models/User.php (1)
  • User (95-493)
app/Policies/Admin/UserPolicy.php (1)
  • delete (22-25)
app/Policies/Server/SchedulePolicy.php (7)
app/Policies/Server/ActivityLogPolicy.php (1)
  • view (17-20)
app/Policies/Server/AllocationPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/BackupPolicy.php (3)
  • view (17-20)
  • create (22-25)
  • delete (27-30)
app/Policies/Server/DatabasePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/FilePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Server/UserPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/Admin/DefaultPolicies.php (3)
  • view (15-18)
  • create (20-23)
  • delete (30-33)
app/Policies/Server/ActivityLogPolicy.php (7)
app/Policies/Server/AllocationPolicy.php (1)
  • view (17-20)
app/Policies/Server/BackupPolicy.php (1)
  • view (17-20)
app/Policies/Server/DatabasePolicy.php (1)
  • view (17-20)
app/Policies/Server/FilePolicy.php (1)
  • view (17-20)
app/Policies/Server/SchedulePolicy.php (1)
  • view (17-20)
app/Policies/Server/UserPolicy.php (1)
  • view (17-20)
app/Models/User.php (1)
  • User (95-493)
app/Filament/Server/Resources/Users/UserResource.php (2)
app/Models/Subuser.php (3)
  • Subuser (24-148)
  • allPermissionData (97-128)
  • user (91-94)
app/Models/User.php (1)
  • can (362-371)
app/Filament/Server/Resources/Backups/BackupResource.php (2)
app/helpers.php (1)
  • user (127-130)
app/Services/Subusers/SubuserCreationService.php (1)
  • handle (39-77)
🪛 PHPMD (2.15.0)
app/Policies/Server/AllocationPolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/BackupPolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/FilePolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/UserPolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/DatabasePolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/SchedulePolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/ActivityLogPolicy.php

17-17: Avoid unused parameters such as '$model'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (14)
app/Filament/Server/Resources/Allocations/AllocationResource.php (1)

5-5: SubuserPermission-based authorization wiring looks correct and consistent

The switch from the old Permission model to SubuserPermission here is coherent:

  • AllocationUpdate is used for both editing notes and changing the primary allocation.
  • AllocationDelete guards the detach action.
  • AllocationCreate guards adding new allocations.
  • All checks correctly scope to the current $server tenant and use null-safe user()?->can(...), keeping guests/read-only users locked out.

This preserves prior intent while aligning with the new enum-based permission system; no changes needed from this file.

Also applies to: 60-61, 72-73, 84-85, 104-105

app/Policies/Server/ServerPolicy.php (1)

6-6: LGTM! Clean migration to enum-based permission validation.

The change correctly delegates permission existence checking to the Subuser model, which now handles both string-based and SubuserPermission enum-based validation through doesPermissionExist(). The authorization logic remains intact while aligning with the new enum-based permission system.

Also applies to: 21-21

app/Policies/Server/SchedulePolicy.php (1)

5-5: LGTM! Consistent migration to SubuserPermission enum.

All permission checks have been correctly updated to use the SubuserPermission enum constants. The method signatures and authorization logic remain unchanged, ensuring consistency with the broader refactoring effort.

Note: The PHPMD warnings about unused $record parameters are false positives—these parameters are required by Laravel's policy method signatures.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/Server/AllocationPolicy.php (1)

5-5: LGTM! Consistent enum-based authorization.

The migration from Permission constants to SubuserPermission enum values is correctly implemented across all policy methods. The authorization flow remains consistent with other server policies.

Note: The PHPMD warnings about unused $record parameters are false positives—these are required by Laravel's policy interface.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/Server/ActivityLogPolicy.php (1)

5-5: LGTM! Proper migration to enum-based permissions.

Both viewAny() and view() methods correctly use SubuserPermission::ActivityRead, maintaining the read-only nature of activity logs while aligning with the new permission system.

Note: The PHPMD warning about the unused $model parameter is a false positive—it's required by Laravel's policy signature.

Also applies to: 14-14, 19-19

app/Filament/Server/Resources/Users/UserResource.php (3)

5-5: LGTM! Enhanced permission filtering.

The migration to Subuser::allPermissionData() is correct and adds a beneficial check to filter out hidden permissions from the UI (line 75-76). This ensures only relevant permissions are displayed to users during subuser management.

Also applies to: 9-9, 74-77


141-141: LGTM! Proper enum-based authorization.

Authorization checks for user creation and editing correctly use SubuserPermission::UserCreate and SubuserPermission::UserUpdate respectively, maintaining proper access control while aligning with the new enum-based system.

Also applies to: 227-227


150-150: LGTM! WebSocket permission auto-inclusion.

Both create and edit actions correctly ensure SubuserPermission::WebsocketConnect is included in the permission set. This is essential for subusers to maintain real-time server communication functionality.

Also applies to: 281-281

app/Policies/Server/DatabasePolicy.php (1)

5-5: LGTM! Complete enum migration.

All database policy methods correctly use the corresponding SubuserPermission enum constants (DatabaseRead, DatabaseCreate, DatabaseUpdate, DatabaseDelete). The authorization logic is properly maintained throughout the refactoring.

Note: The PHPMD warnings about unused $record parameters are false positives—these parameters are required by Laravel's policy signatures.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/Server/FilePolicy.php (1)

5-5: LGTM! Granular file permission migration.

The migration correctly preserves the distinction between FileRead (for listing files in viewAny()) and FileReadContent (for viewing file contents in view()), maintaining the appropriate permission granularity. All other file operations properly use their corresponding enum constants.

Note: The PHPMD warnings about unused $record parameters are false positives—these are required by Laravel's policy interface.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/Server/UserPolicy.php (1)

5-5: LGTM! Clean CRUD permission migration.

All user management policy methods correctly use the corresponding SubuserPermission enum constants (UserRead, UserCreate, UserUpdate, UserDelete). The refactoring maintains consistent authorization patterns across the codebase.

Note: The PHPMD warnings about unused $record parameters are false positives—these parameters are required by Laravel's policy method signatures.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Filament/Server/Resources/Backups/BackupResource.php (2)

5-8: Enum import aligns with refactor

The App\Enums\SubuserPermission import cleanly replaces the old Permission model usage in this resource; no remaining Permission references here and the enum fits the new project‑wide pattern.


131-131: Permission mapping looks consistent; verify enum integration with can()

  • BackupDelete for rename/lock and for controlling the is_locked flag, BackupDownload for download, BackupRestore for restore, and BackupCreate for the toolbar create action all mirror the previous Permission constants and preserve behavior.
  • The use of user()?->can(SubuserPermission::..., $server) assumes your authorization layer accepts enum abilities (or converts them to their string values). If Gate/can() is still strictly string‑based, you may need to pass ->value instead or ensure the enum is handled centrally.

Functionally this looks correct for the refactor; just confirm the enum abilities are wired correctly in your auth layer/tests so these checks don’t silently fail.

Also applies to: 162-162, 171-171, 179-179, 261-261, 272-273

app/Policies/Server/BackupPolicy.php (1)

5-5: Policy permission checks correctly migrated to SubuserPermission

  • viewAny / viewBackupRead
  • createBackupCreate
  • deleteBackupDelete
  • All checks target Filament::getTenant(), which is consistent with server‑scoped backup permissions.

This is a straightforward, behavior‑preserving replacement of the old Permission constants. As with the resource, just ensure your gate/authorization layer supports passing SubuserPermission cases directly into $user->can(...) (or converts them to their backing values) so these policies are actually invoked as expected.

Also applies to: 14-14, 19-19, 24-24, 29-29

@rmartinoscar
Copy link
Member

branch:
image
image

main:
image
image

They both have every perms yet there's one more now and they are in completely random order (It would be much better if it was ordered like navigation [console, files, backups, schedules, users, databases, network, activity, startup, settings]).

@Boy132 Boy132 requested a review from rmartinoscar December 11, 2025 12:24
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 (2)
app/Filament/Server/Resources/Subusers/SubuserResource.php (2)

68-101: Permission tabs: consider stabilizing ordering and hardening hidden handling

The refactor to Subuser::allPermissionData() and skipping hidden groups looks good and matches the new enum-based permission API. However:

  • The tab / checkbox ordering now depends entirely on the order returned by allPermissionData(), which is what surfaced as “random” ordering in the UI discussion. If you want to keep a consistent order (e.g. console, files, backups, schedules, users, databases, network, activity, startup, settings), consider either:
    • enforcing that order inside Subuser::allPermissionData(), or
    • sorting the returned array here before the foreach, with any unknown/custom groups appended to the end.
  • For third‑party/custom permission groups, a missing hidden key would currently trigger a PHP notice. If the intent is to treat missing as false, you can make this slightly more defensive:
-foreach (Subuser::allPermissionData() as $data) {
-    if ($data['hidden']) {
+foreach (Subuser::allPermissionData() as $data) {
+    if ($data['hidden'] ?? false) {
         continue;
     }

These are UX/robustness tweaks; the current logic is otherwise sound.


144-146: Optional: Guard WebsocketConnect permission with existence check for defensive consistency

Both update and invite flows always add SubuserPermission::WebsocketConnect->value before de-duplicating permissions (lines 144-146 and 271-276):

->push(SubuserPermission::WebsocketConnect->value)
    ->unique()
    ->all();

This ensures the websocket permission is always present. To make the behavior more resilient to future permission customizations, consider guarding with Subuser::doesPermissionExist() (a method already used elsewhere in the codebase like ServerPolicy.php):

-    ->push(SubuserPermission::WebsocketConnect->value)
+    ->when(
+        Subuser::doesPermissionExist(SubuserPermission::WebsocketConnect),
+        fn ($permissions) => $permissions->push(SubuserPermission::WebsocketConnect->value),
+    )
     ->unique()
     ->all();

This prevents passing invalid permissions downstream if permission sets are customized more aggressively in the future.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32e4825 and 0a3bf2f.

📒 Files selected for processing (10)
  • app/Filament/Server/Resources/Subusers/SubuserResource.php (6 hunks)
  • app/Policies/ActivityLogPolicy.php (2 hunks)
  • app/Policies/AllocationPolicy.php (2 hunks)
  • app/Policies/BackupPolicy.php (2 hunks)
  • app/Policies/DatabasePolicy.php (2 hunks)
  • app/Policies/FilePolicy.php (2 hunks)
  • app/Policies/SchedulePolicy.php (2 hunks)
  • app/Policies/ServerPolicy.php (2 hunks)
  • app/Policies/SubuserPolicy.php (2 hunks)
  • tests/Feature/SettingsControllerTest.php (10 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-06T10:11:54.301Z
Learnt from: Boy132
Repo: pelican-dev/panel PR: 1866
File: app/Policies/PluginPolicy.php:7-7
Timestamp: 2025-11-06T10:11:54.301Z
Learning: In the codebase, the `DefaultPolicies` trait is located in the `App\Policies` namespace (file: app/Policies/DefaultPolicies.php). Policy classes in the same namespace can use this trait without requiring an import statement.

Applied to files:

  • app/Policies/FilePolicy.php
  • app/Policies/SubuserPolicy.php
  • app/Policies/BackupPolicy.php
  • app/Policies/ActivityLogPolicy.php
  • app/Policies/AllocationPolicy.php
  • app/Policies/SchedulePolicy.php
  • app/Policies/DatabasePolicy.php
📚 Learning: 2025-12-02T19:55:23.957Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1837
File: app/Filament/Server/Resources/Schedules/ScheduleResource.php:337-338
Timestamp: 2025-12-02T19:55:23.957Z
Learning: In Filament resources, when ViewAction is hidden using `->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed())`, this is intentional UX design: the ViewAction (read-only) should be hidden when users have edit privileges, showing the EditAction instead. The ViewAction serves as a fallback for users who only have view permissions. This pattern is correct and should not be flagged as inverted logic.

Applied to files:

  • app/Policies/FilePolicy.php
  • app/Policies/SubuserPolicy.php
🧬 Code graph analysis (6)
app/Policies/FilePolicy.php (6)
app/Policies/ActivityLogPolicy.php (1)
  • view (17-20)
app/Policies/AllocationPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/DatabasePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/SchedulePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/SubuserPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Models/User.php (1)
  • User (95-493)
app/Policies/SubuserPolicy.php (9)
app/Policies/ActivityLogPolicy.php (1)
  • view (17-20)
app/Policies/AllocationPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/BackupPolicy.php (3)
  • view (17-20)
  • create (22-25)
  • delete (27-30)
app/Policies/DatabasePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/FilePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/SchedulePolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Policies/DefaultAdminPolicies.php (3)
  • view (15-18)
  • create (20-23)
  • delete (30-33)
app/Models/User.php (1)
  • User (95-493)
app/Policies/UserPolicy.php (1)
  • delete (22-25)
app/Policies/ActivityLogPolicy.php (7)
app/Policies/AllocationPolicy.php (1)
  • view (17-20)
app/Policies/BackupPolicy.php (1)
  • view (17-20)
app/Policies/DatabasePolicy.php (1)
  • view (17-20)
app/Policies/FilePolicy.php (1)
  • view (17-20)
app/Policies/SchedulePolicy.php (1)
  • view (17-20)
app/Policies/SubuserPolicy.php (1)
  • view (17-20)
app/Models/User.php (1)
  • User (95-493)
app/Policies/ServerPolicy.php (1)
app/Models/Subuser.php (2)
  • Subuser (24-148)
  • doesPermissionExist (140-147)
app/Policies/SchedulePolicy.php (3)
app/Policies/ActivityLogPolicy.php (1)
  • view (17-20)
app/Policies/BackupPolicy.php (3)
  • view (17-20)
  • create (22-25)
  • delete (27-30)
app/Policies/SubuserPolicy.php (4)
  • view (17-20)
  • create (22-25)
  • edit (27-30)
  • delete (32-35)
app/Filament/Server/Resources/Subusers/SubuserResource.php (2)
app/Models/Subuser.php (2)
  • Subuser (24-148)
  • allPermissionData (97-128)
app/Http/Controllers/Api/Client/ClientController.php (1)
  • permissions (85-93)
🪛 PHPMD (2.15.0)
app/Policies/FilePolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/SubuserPolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/BackupPolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/ActivityLogPolicy.php

17-17: Avoid unused parameters such as '$model'. (undefined)

(UnusedFormalParameter)

app/Policies/AllocationPolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/SchedulePolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/DatabasePolicy.php

17-17: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


27-27: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (12)
app/Policies/ActivityLogPolicy.php (1)

5-5: LGTM! Clean migration to SubuserPermission enum.

The refactoring correctly updates the permission checks to use the new SubuserPermission::ActivityRead enum value, improving type safety over the previous string-based constants.

Also applies to: 14-14, 19-19

app/Policies/BackupPolicy.php (1)

5-5: LGTM! Consistent permission enum migration.

All backup-related permission checks have been correctly migrated to use the SubuserPermission enum values (BackupRead, BackupCreate, BackupDelete), maintaining the same authorization logic.

Also applies to: 14-14, 19-19, 24-24, 29-29

app/Policies/SubuserPolicy.php (1)

5-5: LGTM! Complete CRUD permission coverage.

All subuser management operations are properly covered with the corresponding SubuserPermission enum values (UserRead, UserCreate, UserUpdate, UserDelete).

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/ServerPolicy.php (1)

6-6: LGTM! Proper adaptation of generic permission validation.

The change to use Subuser::doesPermissionExist($ability) is appropriate for the before() hook, which needs to validate whether a dynamic ability string represents a valid subuser permission before evaluating access rules.

Also applies to: 25-25

app/Policies/DatabasePolicy.php (1)

5-5: LGTM! Complete database permission coverage.

All database operations are correctly mapped to their corresponding SubuserPermission enum values (DatabaseRead, DatabaseCreate, DatabaseUpdate, DatabaseDelete).

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/SchedulePolicy.php (1)

5-5: LGTM! Schedule permissions properly migrated.

All schedule-related operations are correctly updated to use the SubuserPermission enum values (ScheduleRead, ScheduleCreate, ScheduleUpdate, ScheduleDelete).

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/FilePolicy.php (1)

5-5: LGTM! File permissions with proper granularity.

The migration correctly maintains the granular permission model for file operations, distinguishing between FileRead (listing files) and FileReadContent (viewing file contents), along with the standard CRUD operations.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

app/Policies/AllocationPolicy.php (1)

5-5: LGTM! Allocation permissions complete.

All network allocation operations are correctly migrated to use the SubuserPermission enum values (AllocationRead, AllocationCreate, AllocationUpdate, AllocationDelete), completing the consistent refactoring across all policy files.

Also applies to: 14-14, 19-19, 24-24, 29-29, 34-34

tests/Feature/SettingsControllerTest.php (3)

4-179: Enum permission migration in tests looks consistent

The switch from the old Permission model constants to SubuserPermission (import at Line 4 and all generateTestAccount([...]) calls) keeps the same permission semantics per test:

  • rename tests use WebsocketConnect (for the “cannot rename” case) and WebsocketConnect + SettingsRename (for the “can rename” case),
  • description tests use SettingsDescription,
  • docker image tests use WebsocketConnect for the unauthorized case and StartupDockerImage for the authorized cases,
  • reinstall uses SettingsReinstall.

Given generateTestAccount is documented to accept permission enums/values directly, this is a straightforward, type‑safer refactor with no behavior change in the test flows. Based on relevant snippet in test/Pest.php, this looks correct.


13-76: Confirm intended base-permission requirements for rename vs description

Rename tests now explicitly require WebsocketConnect + SettingsRename for the positive case (Line 64), while description tests only require SettingsDescription (Lines 29, 48). If the new permission system is meant to standardize on a common “base” permission (e.g., WebsocketConnect or similar) for all settings mutations, it might be worth double‑checking that this asymmetry between rename and description is intentional and reflected in the policies, not just legacy behavior preserved from the old Permission model.


78-179: Verify docker-image and reinstall tests match updated policy expectations

For docker image and reinstall endpoints, the tests now use:

  • WebsocketConnect only for the “unauthorized to change docker image” case (Line 79),
  • StartupDockerImage for all “can/cannot change docker image” cases (Lines 95, 115, 138),
  • SettingsReinstall for the reinstall case (Line 158).

This mirrors the old Permission‑constant usage, but it’s worth confirming that, under the refactored Subuser permission logic, these endpoints don’t also require some additional base permission (like WebsocketConnect) for authorized calls, and that the policy layer is aligned with what these tests encode.

app/Filament/Server/Resources/Subusers/SubuserResource.php (1)

5-5: Enum-based authorization implementation is correctly aligned across all layers

The SubuserPermission enum is properly imported and used consistently:

  • Edit action (line 137): user()?->can(SubuserPermission::UserUpdate, $server)
  • Invite/Create action (line 220): user()?->can(SubuserPermission::UserCreate, $server)
  • SubuserPolicy methods match exactly: create() uses UserCreate, edit() uses UserUpdate
  • All authorization checks use the same enum values throughout the codebase

Policy/gate definitions are properly aligned with the enum, ensuring no drift between what's checked in actions and what's defined in policies.

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

🧹 Nitpick comments (2)
app/Enums/SubuserPermission.php (2)

7-58: Consider reordering enum cases to match desired UI grouping/order

Enum case order will typically dictate how permissions appear when iterated (e.g., in the UI). Right now the groups are ordered as: websocket, control, file, backup, schedule, user, database, allocation, activity, startup, settings. The PR discussion suggests a more intentional order like: console(control), files(file), backups(backup), schedules(schedule), users(user), databases(database), network(allocation), activity(activity), startup(startup), settings(settings).

If the UI builds from SubuserPermission directly (or via Subuser::allPermissionKeys() in declaration order), it would be worth reordering the enum cases to match that sequence so the display feels consistent and non-random.


65-68: Explicit isHidden() behavior looks fine but may need future-proofing

Hard-coding WebsocketConnect as the only hidden permission is clear and easy to follow. If more “hidden-only” permissions are added in the future, consider switching this to a match or a small in_array([...]) to keep the logic from growing into a long chain of comparisons.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a3bf2f and 9e7aff2.

📒 Files selected for processing (1)
  • app/Enums/SubuserPermission.php (1 hunks)
🧰 Additional context used
🪛 PHPMD (2.15.0)
app/Enums/SubuserPermission.php

72-72: Avoid unused local variables such as '$permission'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (1)
app/Enums/SubuserPermission.php (1)

59-64: split() implementation is straightforward and clear

split() correctly returns [group, permission] by splitting on the first dot with a limit of 2, which matches how the enum values are structured. The docblock return type also looks good.

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

🧹 Nitpick comments (1)
app/Filament/Server/Resources/Subusers/SubuserResource.php (1)

71-75: Hidden groups handled correctly; consider enforcing a stable permission order

Skipping entries with $data['hidden'] and building tabs/options from Subuser::allPermissionData() looks good, and keeping hidden groups out of $permissionsArray ensures “Assign all” doesn’t touch system/hidden permissions.

Given earlier feedback about “random” permission ordering in the UI, the order here is entirely dictated by the order returned from Subuser::allPermissionData() (and by the permissions arrays within each group). If that method aggregates from multiple sources or enums without explicit sorting, you’ll keep seeing ordering changes across refactors. Consider:

  • Defining a canonical group order (e.g. ['console', 'files', 'backups', 'schedules', 'users', 'databases', 'network', 'activity', 'startup', 'settings']) and sorting the data against it, ideally inside Subuser::allPermissionData(), or
  • Applying the same ordering just before this foreach so the Filament resource doesn’t depend on incidental array order.

This would directly address the “random order” concern while keeping the hidden‑group behavior as-is.

Also applies to: 85-101

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e7aff2 and 6028d7b.

📒 Files selected for processing (1)
  • app/Filament/Server/Resources/Subusers/SubuserResource.php (7 hunks)
🔇 Additional comments (3)
app/Filament/Server/Resources/Subusers/SubuserResource.php (3)

5-5: Enum import aligns with new permission model

Switching to App\Enums\SubuserPermission here is consistent with the PR’s move away from the Permission model; no issues spotted.


137-137: Enum-based authorization for edit/invite looks consistent

Using user()?->can(SubuserPermission::UserUpdate, $server) for the edit action and SubuserPermission::UserCreate for the invite action matches the new enum-driven permission model and clearly separates “edit” vs “invite” capabilities. Assuming corresponding policies/gate checks already accept these enum cases, this is a clean, type-safe replacement for the old constants.

Also applies to: 220-220


144-144: Always adding WebsocketConnect centralizes the hidden/system permission

Appending SubuserPermission::WebsocketConnect->value and then ->unique() in both the update and invite flows ensures every subuser retains this hidden/system permission regardless of the checkbox selections, which aligns with the hidden-group handling in the form.

This looks intentional and matches the idea of a non-user-configurable permission. If that’s the desired behavior (i.e., subusers should never lose websocket access), the implementation here is sound.

Also applies to: 274-274

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/Server/Resources/Subusers/SubuserResource.php (1)

71-74: Consider ordering permissions consistently for better UX.

As noted in the PR comments, permissions currently appear in the order returned by Subuser::allPermissionData(), which may not be predictable. For a more consistent user experience, consider sorting the permission groups before building the tabs.

Apply this diff to sort permissions in a consistent order:

-        foreach (Subuser::allPermissionData() as $data) {
+        $desiredOrder = ['console', 'files', 'backups', 'schedules', 'users', 'databases', 'network', 'activity', 'startup', 'settings'];
+        $permissionData = collect(Subuser::allPermissionData())
+            ->sortBy(fn ($data) => array_search($data['name'], $desiredOrder, true) !== false 
+                ? array_search($data['name'], $desiredOrder, true) 
+                : PHP_INT_MAX);
+        
+        foreach ($permissionData as $data) {
             if ($data['hidden']) {
                 continue;
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6028d7b and d947b40.

📒 Files selected for processing (1)
  • app/Filament/Server/Resources/Subusers/SubuserResource.php (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-02T19:55:23.957Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1837
File: app/Filament/Server/Resources/Schedules/ScheduleResource.php:337-338
Timestamp: 2025-12-02T19:55:23.957Z
Learning: In Filament resources, when ViewAction is hidden using `->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed())`, this is intentional UX design: the ViewAction (read-only) should be hidden when users have edit privileges, showing the EditAction instead. The ViewAction serves as a fallback for users who only have view permissions. This pattern is correct and should not be flagged as inverted logic.

Applied to files:

  • app/Filament/Server/Resources/Subusers/SubuserResource.php
🧬 Code graph analysis (1)
app/Filament/Server/Resources/Subusers/SubuserResource.php (2)
app/Models/Subuser.php (2)
  • Subuser (24-148)
  • allPermissionData (97-128)
app/Enums/SubuserPermission.php (1)
  • isHidden (65-68)
🔇 Additional comments (5)
app/Filament/Server/Resources/Subusers/SubuserResource.php (5)

5-5: LGTM!

The import of SubuserPermission enum correctly supports the refactoring from the Permission model.


71-74: LGTM!

The filtering logic correctly excludes hidden permission groups from the UI tabs. The use of Subuser::allPermissionData() properly centralizes permission metadata.


91-91: LGTM!

The ->contained(false) styling adjustment appears intentional based on the Section containment control changes described in the PR.


117-122: LGTM!

The permissions count calculation correctly filters out hidden permissions while properly handling custom permissions. The use of tryFrom() with the ?? false fallback ensures custom permissions (not in the enum) are counted, which is the desired behavior.

This implementation addresses the previous review concern about the brittle magic -1 subtraction.


140-140: LGTM!

The authorization checks and permission assignments correctly use the SubuserPermission enum:

  • Authorization checks use SubuserPermission::UserUpdate and SubuserPermission::UserCreate
  • WebsocketConnect permission is properly pushed using SubuserPermission::WebsocketConnect->value in both create and update flows

This refactoring eliminates hard-coded permission strings and leverages type-safe enum values.

Also applies to: 147-147, 223-223, 277-277

@Boy132 Boy132 merged commit 760aaf9 into main Dec 11, 2025
25 checks passed
@Boy132 Boy132 deleted the boy132/refactor-subuser-permissions branch December 11, 2025 13:34
@github-actions github-actions bot locked and limited conversation to collaborators Dec 11, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow custom subuser permissions

3 participants