Plugin system
[!IMPORTANT]
Review skipped
Draft detected.
Please check the settings in the CodeRabbit UI or the
.coderabbit.yamlfile in this repository. To trigger a single review, invoke the@coderabbitai reviewcommand.You can disable this status message by setting the
reviews.review_statustofalsein the CodeRabbit configuration file.
📝 Walkthrough
Walkthrough
Adds a plugin system: model, service, facade, enums, contract, policies, console commands, Filament admin resource/pages, panel integration, helper, provider hooks, language/frontend build integrations, config/translations, and Docker/Vite updates; loads plugins during app bootstrap and supports panel-scoped plugin loading, installation, updates, and asset management.
Changes
| Cohort / File(s) | Summary |
|---|---|
Core plugin runtime & service app/Services/Helpers/PluginService.php, app/Facades/Plugins.php |
New PluginService centralizes plugin lifecycle (load, loadPanelPlugins, install, update, uninstall, manage composer packages, migrations, buildAssets, download/extract, enable/disable, updateLoadOrder, queries). Plugins facade proxies to the service. |
Model, contracts, enums app/Models/Plugin.php, app/Contracts/Plugins/HasPluginSettings.php, app/Enums/PluginStatus.php, app/Enums/PluginCategory.php |
New Plugin Eloquent model implementing HasPluginSettings; handles discovery, metadata, updates, compatibility, providers/commands, settings delegation, readme, and helpers. New enums PluginStatus and PluginCategory with UI metadata. |
Filament admin UI & resource pages app/Filament/Admin/Resources/Plugins/PluginResource.php, app/Filament/Admin/Resources/Plugins/Pages/ListPlugins.php |
New Filament Resource and List page: tabbed category filters, reorder support, per-record actions (install/update/enable/disable/settings/uninstall/etc.), import from file/URL workflows, dynamic badges and counts, and index/listing UI. |
Console commands & stubs app/Console/Commands/Plugin/*.php, app/Console/Commands/Plugin/*.stub |
Adds multiple Artisan commands: make/install/update/list/disable/uninstall/composer (p:plugin:*) and associated plugin file stubs (Plugin.stub, PluginProvider.stub, PluginConfig.stub). |
Providers & bootstrap integration app/Providers/AppServiceProvider.php, app/Providers/Filament/*PanelProvider.php |
Loads plugins during bootstrap: Plugins::loadPlugins() in AppServiceProvider::register(); panel providers now call Plugins::loadPanelPlugins($panel) before returning panel. |
Policies, roles & permissions app/Policies/PluginPolicy.php, app/Models/Role.php |
New PluginPolicy using DefaultPolicies with modelName 'plugin'; Role::SPECIAL_PERMISSIONS updated to include 'plugin' => ['viewList','create','update','delete']. |
Admin pages/business logic app/Filament/Admin/Resources/Plugins/... (actions/notifications) |
Table and action logic (authorization, modals, slide-overs), reorder handling, import workflows, and plugin-related notifications implemented inside resource/page classes. |
Language, helper & services app/Services/Helpers/LanguageService.php, app/helpers.php |
LanguageService merges core and plugin-provided languages via Plugins::getPluginLanguages(); adds plugin_path() helper for consistent plugin paths. |
Role / policy integrations app/Providers/... (imports) |
Providers import App\Facades\Plugins and adjust control flow to allow plugin loading (assign panel to $panel, load plugins, then return). |
Frontend & build config vite.config.js |
Adds plugin asset globs to Vite input to include plugins/*/resources/css/**/*.css and plugins/*/resources/js/**/*.js in builds. |
Docker / storage symlinks Dockerfile, Dockerfile.dev |
Create /pelican-data/plugins and symlink it to /var/www/html/plugins; update comments and symlink setup accordingly. |
Translations, config, composer & gitignore lang/en/admin/plugin.php, config/panel.php, composer.json, plugins/.gitignore |
Add English translations for plugin admin UI, add panel.plugin config (dev_mode, max_import_size), add php artisan p:plugin:composer to composer post-install-cmd, and add plugins/.gitignore to ignore plugin files except .gitignore. |
Filament pages & list utilities app/Filament/Admin/Resources/Plugins/Pages/ListPlugins.php |
New ListPlugins page supports reorderTable, tabbed category filters via PluginCategory::cases(), and delegates load order updates to Plugins::updateLoadOrder. |
Sequence Diagram(s)
sequenceDiagram
participant App as Application
participant ASP as AppServiceProvider
participant PluginsFacade as Plugins Facade
participant PluginSvc as PluginService
participant PluginModel as Plugin Model
participant Autoloader as Autoloader/Provider Loader
App->>ASP: register()
ASP->>PluginsFacade: Plugins::loadPlugins()
PluginsFacade->>PluginSvc: loadPlugins()
PluginSvc->>PluginModel: getRows()
PluginModel-->>PluginSvc: plugin metadata list
loop per plugin (load_order)
PluginSvc->>PluginModel: shouldLoad(panelId?)
alt compatible & enabled
PluginSvc->>Autoloader: register autoloads, providers, commands
PluginSvc->>PluginSvc: load translations, configs, views, migrations
else mark errored or skip
end
end
PluginSvc-->>PluginsFacade: loaded
PluginsFacade-->>ASP: done
sequenceDiagram
participant AdminUI as Filament Admin
participant PluginResource as PluginResource
participant PluginsSvc as PluginService
participant Filesystem as Filesystem
participant DB as Plugin Model / Metadata
AdminUI->>PluginResource: user imports plugin (file/URL)
PluginResource->>PluginsSvc: downloadPluginFromFile/Url()
PluginsSvc->>Filesystem: validate + save + unzip
Filesystem-->>PluginsSvc: extracted plugin path
PluginsSvc->>DB: setMetaData / update rows
PluginsSvc->>PluginsSvc: manageComposerPackages() -> install packages
PluginsSvc->>PluginsSvc: runPluginMigrations()
PluginsSvc-->>PluginResource: import success / error
PluginResource-->>AdminUI: show notification / update list
Pre-merge checks
❌ Failed checks (1 warning, 1 inconclusive)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 19.74% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
| Description check | ❓ Inconclusive | The description consists only of an embedded image with no textual explanation of the changes. This is insufficient to convey meaningful information about the PR's objectives and scope. | Provide a detailed textual description explaining the plugin system features being introduced, key design decisions, and implementation details. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title 'Plugin system' is directly related to the changeset, which introduces comprehensive plugin system functionality including commands, services, facades, models, and UI resources. |
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
@coderabbitai review
✅ 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.
✅ 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.
@coderabbitai review
✅ 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.
Hey, the permissions system is not fine-grained enough, it won't allow other users that aren't the root admin to import plugins. I imagine this can't be intentional, since this means multiple administrator accounts cannot exist.
I also have this error when the plugins are being unzipped:
#0 /var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(363): App\\Services\\Helpers\\PluginService->downloadPluginFromFile(Object(Livewire\\Features\\SupportFileUploads\\TemporaryUploadedFile))
#1 /var/www/html/app/Filament/Admin/Resources/Plugins/PluginResource.php(212): Illuminate\\Support\\Facades\\Facade::__callStatic('downloadPluginF...', Array)
#2 /var/www/html/vendor/filament/support/src/Concerns/EvaluatesClosures.php(36): App\\Filament\\Admin\\Resources\\Plugins\\PluginResource::{closure:App\\Filament\\Admin\\Resources\\Plugins\\PluginResource::table():209}(Array, Object(App\\Filament\\Admin\\Resources\\Plugins\\Pages\\ListPlugins))
#3 /var/www/html/vendor/filament/actions/src/Action.php(556): Filament\\Support\\Components\\Component->evaluate(Object(Closure), Array)
#4 /var/www/html/vendor/filament/actions/src/Concerns/InteractsWithActions.php(259): Filament\\Actions\\Action->call(Array)
#5 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Filament\\Pages\\BasePage->callMountedAction(Array)
#6 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::{closure:Illuminate\\Container\\BoundMethod::call():35}()
#7 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
#8 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#9 /var/www/html/vendor/livewire/livewire/src/Wrapped.php(23): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array)
#10 /var/www/html/vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php(492): Livewire\\Wrapped->__call('callMountedActi...', Array)
#11 /var/www/html/vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php(101): Livewire\\Mechanisms\\HandleComponents\\HandleComponents->callMethods(Object(App\\Filament\\Admin\\Resources\\Plugins\\Pages\\ListPlugins), Array, Object(Livewire\\Mechanisms\\HandleComponents\\ComponentContext))
#12 /var/www/html/vendor/livewire/livewire/src/LivewireManager.php(102): Livewire\\Mechanisms\\HandleComponents\\HandleComponents->update(Array, Array, Array)
#13 /var/www/html/vendor/livewire/livewire/src/Mechanisms/HandleRequests/HandleRequests.php(94): Livewire\\LivewireManager->update(Array, Array, Array)
#14 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): Livewire\\Mechanisms\\HandleRequests\\HandleRequests->handleUpdate()
#15 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php(265): Illuminate\\Routing\\ControllerDispatcher->dispatch(Object(Illuminate\\Routing\\Route), Object(Livewire\\Mechanisms\\HandleRequests\\HandleRequests), 'handleUpdate')
#16 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php(211): Illuminate\\Routing\\Route->runController()
#17 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(822): Illuminate\\Routing\\Route->run()
#18 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Router->{closure:Illuminate\\Routing\\Router::runRouteWithinStack():821}(Object(Illuminate\\Http\\Request))
#19 /var/www/html/app/Http/Middleware/LanguageMiddleware.php(23): Illuminate\\Pipeline\\Pipeline->{closure:Illuminate\\Pipeline\\Pipeline::prepareDestination():178}(Object(Illuminate\\Http\\Request))
#20 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\\Http\\Middleware\\LanguageMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#21 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#22 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(87): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#24 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /var/www/html/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(48): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#26 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(120): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#28 /var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(63): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest(Object(Illuminate\\Http\\Request), Object(Illuminate\\Session\\Store), Object(Closure))
#29 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Session\\Middleware\\StartSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /var/www/html/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(36): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#31 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /var/www/html/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(74): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#33 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#35 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(821): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#36 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#37 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#38 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#39 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#40 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->{closure:Illuminate\\Foundation\\Http\\Kernel::dispatchToRouter():197}(Object(Illuminate\\Http\\Request))
#41 /var/www/html/vendor/livewire/livewire/src/Features/SupportDisablingBackButtonCache/DisableBackButtonCacheMiddleware.php(19): Illuminate\\Pipeline\\Pipeline->{closure:Illuminate\\Pipeline\\Pipeline::prepareDestination():178}(Object(Illuminate\\Http\\Request))
#42 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Livewire\\Features\\SupportDisablingBackButtonCache\\DisableBackButtonCacheMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#43 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(27): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#44 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#45 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(47): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#46 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#47 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#48 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#49 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#50 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#51 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(48): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#52 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#53 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(58): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#54 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#55 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#56 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#57 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePathEncoding.php(26): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#58 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\ValidatePathEncoding->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#59 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))
#60 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#61 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#62 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1220): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#63 /var/www/html/public/index.php(20): Illuminate\\Foundation\\Application->handleRequest(Object(Illuminate\\Http\\Request))
#64 {main}
@coderabbitai review
✅ 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.