diff --git a/docs/panel/advanced/plugins.mdx b/docs/panel/advanced/plugins.mdx new file mode 100644 index 00000000..06fc0aee --- /dev/null +++ b/docs/panel/advanced/plugins.mdx @@ -0,0 +1,345 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Plugins + +Plugins allow you to add new functionality to the panel without modifying any panel core files. + + + The plugin system is still in development and features might be missing! + + +## Install a Plugin + + + + Use the Import Button on the plugin list to upload & install a plugin. + + + Move the plugin folder to `plugins` inside your panel directory (`/var/www/pelican/plugins` by default). Then run `php artisan p:plugin:install` inside your panel directory and select the new plugin. + + + + + Check out our [Plugins repo](https://github.com/pelican-dev/plugins)! It contains free and open source plugins created by the Pelican team and the community. + We are also working on adding a plugin marketplace to the [Hub](https://hub.pelican.dev). + + +## Create a Plugin + + + You **need** at least basic [PHP](https://php.net), [Laravel](https://laravel.com) and [Filament](https://filamentphp.com) knowledge to create plugins! + + +Run `php artisan p:plugin:make` inside your panel directory (`/var/www/pelican` by default) to create the basic files and folders for your new plugin. This will create the `plugin.json`, the main plugin file, a config file and a service provider. + + + Visit our [Discord](https://discord.gg/pelican-panel) if you need any help when creating plugins! + + +### Plugin Structure + +The two most important files of your plugin are the `plugin.json` (which contains general information like name, version etc.) and the main plugin file. (where you can load Filament resources, pages etc.) +While the `plugin.json` is in the root of your plugin folder, the main plugin file is in `src`. + +In general, the plugin folder tries to mimic the structure of a normal Laravel/FilamentPHP project. For example, translations go into `lang` and migrations are in `database/migrations`. +Everything is auto-discovered and service providers, artisan commands, migrations etc. are automatically registered. + +The only exception to a normal Laravel project is the `app` folder, it is called `src` instead. Besides that, is still follows the same sub-structure, e.g. models go in `src/Models` and service providers are in `src/Providers`. + + + The plugin id and the plugin root folder name need to match! + + +#### plugin.json + +Most of the fields inside the `plugin.json` are generated when running the [artisan command](#create-a-plugin). + +| Field | Required | Description | +| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| id | Yes | Unique identifier of the plugin. Has to match with the plugin root folder name! | +| name | Yes | Display name of the plugin. | +| author | Yes | Display name of the plugin creator. | +| version | No | Version of the plugin. Ideally, this should follow [semantic versioning](https://semver.org/). When not set it will assume version 1.0.0. | +| description | No | Short description of the plugin. | +| category | Yes | Either `plugin`, `theme` or `language`. | +| update_url | No | URL that will be fetched when checking for updates. See [below](#update-url) for more information. | +| namespace | Yes | The php namespace for the plugin files. | +| class | Yes | The name of the plugin main class. | +| panels | No | Array of [FilamentPHP panel](#filament-panels) ids the plugin should be loaded on. When not set the plugin will be loaded on all panels. | +| panel_version | No | The panel version required for the plugin. See [below](#panel-version) for more information. | +| composer_packages | No | Array of additional composer packages. Key is the package name and value is the version. See [below](#additional-composer-dependencies) for more information. | + + + You can ignore the `meta` fields. Those are used internally to keep track of the plugin status and load order. + + +#### Update URL + +In your `plugin.json` you can specify an `update_url` that is fetched when checking for plugin updates. +When not set, there will be no update checking for the plugin. + +It needs to point directly to a json file with the following format: + +```json title="update.json" +{ + "panel_version_1": { + "version": "1.0.0", + "download_url": "..." + }, + "panel_version_2": { + "version": "1.1.0", + "download_url": "..." + } +} +``` + +If you don't have panel-version-specific plugin versions, you can also use `*` as panel version wildcard: + +```json title="update.json" +{ + "*": { + "version": "1.1.0", + "download_url": "..." + } +} +``` + +You can also mix the wildcard with real panel versions. This way the wildcard will be used as fallback when the users panel version isn't specified in the update json. + +Example: + +```json title="update.json" +{ + "panel_version_1": { + "version": "1.0.0", + "download_url": "..." + }, + "*": { + "version": "1.1.0", + "download_url": "..." + } +} +``` + +#### Additional composer dependencies + +If you need additional composer packages as dependency for your plugin, you can add the package name to the `composer_packages` array in your `plugin.json`. + +Example: + +```json +"composer_packages": { + "package/one": "^1.0", + "package/two": "^1.0" +} +``` + +#### Panel version + +In your `plugin.json` you can specify a `panel_version` that is used for compatibility checks. +When not set, the plugin will be loaded regardless of the panel version. + +You can add a `^` in front of the version to make it a minimum constraint instead of a strict one, e.g. `1.2.0` means "_only_ version 1.2.0" while `^1.2.0` means "version 1.2.0 _or higher_". + +### Filament Resources + +To simply register Filament resources, pages or widgets you can add the following snippet to the `register` function in your main plugin file: + +```php {3-5} +$id = str($panel->getId())->title(); + +$panel->discoverPages(plugin_path($this->getId(), "src/Filament/$id/Pages"), "\\Filament\\$id\\Pages"); +$panel->discoverResources(plugin_path($this->getId(), "src/Filament/$id/Resources"), "\\Filament\\$id\\Resources"); +$panel->discoverWidgets(plugin_path($this->getId(), "src/Filament/$id/Widgets"), "\\Filament\\$id\\Widgets"); +``` + +Make sure to replace `` with your actual namespace. + +### Filament Panels + +By default there are three Filament panels: + +1. `admin` (Admin Area) +2. `app` (Server list) +3. `server` (Client Area, with `Server` model as [tenant](https://filamentphp.com/docs/4.x/users/tenancy)) + +#### App panel + +The `app` panel is solely used for the server list and has no navigation. +If you want to expand the `app` panel, you can re-enable the navigation by adding the following snippet to the `register` function in your main plugin file: + +```php +if ($panel->getId() === 'app') { + // Optional: "Embed" the server list. This will add a Servers navigation item for the server list. + // ServerResource::embedServerList(); + + // Re-enable navigation + $panel->navigation(true); + + // By default the topbar is always used. With proper navigation we want to conditionally enable it based on the users customization + $panel->topbar(function () { + $navigationType = user()?->getCustomization(CustomizationKey::TopNavigation); + + return $navigationType === 'topbar' || $navigationType === 'mixed' || $navigationType === true; + }); + + // Rebuild component cache + $panel->clearCachedComponents(); +} +``` + +### Translations + +Normally, translations will be registered under the namespace of your plugin. +This means you need to prefix translation keys with your plugin id when using `trans()`, e.g. `trans('myplugin::strings.test')` would look for `test` inside `plugins/myplugin/lang/strings.php`. + +If your plugin adds new default languages to the panel you need to set the plugin category to `language`. This way translations will be registered without any namespace. + +### Views + +Views are automatically registered under the namespace of your plugin. +This means you need to prefix view-strings with your plugin id, e.g. `myplugin::my-view` would look for `plugins/myplugin/resources/views/my-view.blade.php`. + +### Seeder + +If present, a seeder with the name of your plugin will be automatically registered. It needs to be named `Seeder` (e.g. `MyPluginSeeder`) and put inside your plugins `database/Seeder` folder, e.g. `plugins/myplugin/database/Seeder/MyPluginSeeder.php`. +This seeder will be automatically called when a user installs the plugin or when seeders are run in general, e.g. when using `php artisan db:seed` or `php artisan migrate --seed`. + +### Plugin Settings + +Your main plugin class can implement the `HasPluginSettings` interface to conveniently add a settings page to your plugin. +The settings page can be accessed on the plugin list on the panel. Your plugin needs to provide the form and how the data should be saved. + +If you want to save the settings in the `.env` file you can use the `EnvironmentWriterTrait`. Example: + +```php +use EnvironmentWriterTrait; + +public function getSettingsForm(): array +{ + return [ + TextInput::make('test_input') + ->required() + ->default(fn () => config('myplugin.test_input')), + Toggle::make('test_toggle') + ->inline(false) + ->default(fn () => config('myplugin.test_toggle')), + ]; +} + +public function saveSettings(array $data): void +{ + $this->writeToEnvironment([ + 'MYPLUGIN_TEST_INPUT' => $data['test_input'], + 'MYPLUGIN_TEST_TOGGLE' => $data['test_toggle'], + ]); + + Notification::make() + ->title('Settings saved') + ->success() + ->send(); +} +``` + + + When adding new environment variables make sure to prefix them with your plugin id, e.g. `MYPLUGIN_TEST_INPUT` and not just `TEST_INPUT`. + + +### Modify existing resources + +Most of the default panel resources and pages have static methods to register custom classes or to change existing ones. You can call these static methods inside the `register` function of a service provider. + +Examples: + +```php title="MyPluginProvider.php" +public function register(): void +{ + // Add new relation manager to UserResource + UserResource::registerCustomRelations(MyCustomRelationManager::class); + + // Add new widget to Console page + Console::registerCustomWidgets(ConsoleWidgetPosition::AboveConsole, [MyCustomWidget::class]); + + // Add new header action to Server list page + ListServers::registerCustomHeaderActions(HeaderActionPosition::Before, MyCustomAction::make()); +} +``` + +For more information you can check the ["CanModify" and "CanCustomize" traits in `./app/Traits/Filament`](https://github.com/pelican-dev/panel/tree/main/app/Traits/Filament). + +### Add new permissions + +#### Admin Roles + +To add new admin role permissions you can use `Role::registerCustomPermissions` and `Role::registerCustomDefaultPermissions`. + +You can call these static methods inside the `register` function of a service provider. Examples: + +```php title="MyPluginProvider.php" +public function register(): void +{ + // Register default permissions: viewList, view, create, update, delete + Role::registerCustomDefaultPermissions('myModel'); + + // Register custom permissions + Role::registerCustomPermissions([ + 'myModel' => [ + 'customPerm1', + 'customPerm2' + ] + ]); + + // Optionally, register an icon for a custom model + Role::registerCustomModelIcon('myModel', 'tabler-star'); + + // You can also add new permissions to existing models + Role::registerCustomPermissions([ + 'server' => [ + 'custom', + ] + ]); +} +``` + +#### Subuser + +To add new subuser permissions you can use `Subuser::registerCustomPermissions`. + +You can call this static method inside the `register` function of a service provider. Examples: + +```php title="MyPluginProvider.php" +public function register(): void +{ + // Register new subuser permissions (call with icon and hidden param) + Subuser::registerCustomPermissions('custom', ['customPerm1', 'customPerm2'], 'tabler-star', false); + + // You can also add new permissions to existing permission groups (call without icon and hidden param) + Subuser::registerCustomPermissions('console', ['custom']); +} +``` + +## Publish a Plugin + + + While Pelican is still in beta, please do **not** sell plugins. Also, if possible, please make them open source. + During the beta we still add new features and change existing ones. So keeping your plugins open helps us to improve the plugin system! + + +To publish a plugin you only need to do two things: + +1. Open your `plugin.json` and remove the `meta` part. +2. Zip up your whole plugin folder. + +And that's it! Now you can take the zip file and share it with the world! + + + We are currently working on adding a plugin marketplace to the [Hub](https://hub.pelican.dev). + Until then you can publish your plugins on our [plugins repo](https://github.com/pelican-dev/plugins). You can also share them in the `#plugins` channel in our [Discord](https://discord.gg/pelican-panel). + + + + You can license your plugin code under whatever license you want. You do not have to use the same license as the panel! + You also don't have to open source your plugin code. But if you do want to open source it we recommend [MIT](https://choosealicense.com/licenses/mit) or [GPL v3](https://choosealicense.com/licenses/gpl-3.0) as license. + \ No newline at end of file diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 7126aedd..41cae8fa 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -108,6 +108,7 @@ const config: Config = { "ini", "sql", "yaml", + "php", ], }, } satisfies Preset.ThemeConfig, @@ -136,7 +137,7 @@ const config: Config = { ], tailwindPlugin, ], - future:{ + future: { experimental_faster: true, v4: true } diff --git a/sidebars.ts b/sidebars.ts index e1d56ee8..6d8c5777 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -31,6 +31,7 @@ const sidebars: SidebarsConfig = { 'panel/advanced/mysql', 'panel/advanced/artisan', 'panel/advanced/docker', + 'panel/advanced/plugins', ] } ],