/laravel-pilot

A customizable administration panel for Laravel

Primary LanguageJavaScript

Pilot: Laravel Administration panel

Table of contents

Requirements

  • Laravel v5.3+
  • PHP v7.0.0+

Quickstart

Setup

  1. Install package
  2. Add this to composer.json in the autoload.psr-4 hash (just below "App\\": "app/"): "VivienLN\\Pilot\\": "vendor/vivienln/pilot" (required until the package is on packagist)
  3. In config/app.php, add this in the providers array: VivienLN\Pilot\PilotServiceProvider::class,
  4. In a shell, execute composer dump-autoload
  5. In a shell, execute php artisan pilot:install. This will copy configuration files and assets to your main directory, run migrations and create empty policies if you need so.
  6. Configure pilot in config/pilot.php and policies (see below)

Basic configuration

All configuration is done within the config/pilot.php file.

The most basic setup is done by providing model class names such as follow:

return [
    'models' => [
        ['model' => 'App\Post'],
        ['model' => 'App\Category'],
    ],
];

This will, by default, show all columns of this models and use models and columns names for display. However, you still need to define policies to setup user permissions (see below).

Note: If you use config cache, do not forget to refresh it: php artisan config:cache

Note: model is the only required key to make Pilot aware of your models, but you can customize many more things in each array. See Documentation for more information.

Users and roles

Each user can have one or more roles that define what permissions they have. By defaut, a super-admin role is created.

Note: When you install pilot, 2 tables are created to handle roles: pilot_rolesand pilot_role_user.

Grant permissions to a user

To associate a user with a role, use the pilot:grant command:

php artisan pilot:grant super-admin 1

The parameters are:

  1. The slug of the role to give the user(s)
  2. The user id

Deny permissions to a user

The pilot:deny command let you do the exact opposite, removing a role from a user. The parameters are the same.

Authorization Policies

Pilot uses the Laravel Policies to determine whether or not a user can do an action. If you are not familiar with policies, read the official documentation about policies.

For each model you want to use with Pilot, you must create and declare a Policy. When installing pilot, you can automatically generate policies.

If you do not want to use this feature, or juste want to add policies later, you can still use artisan: php artisan make:policy MyModelPolicy --model=MyModel

In any case, you must register your policies in app/Providers/AuthServiceProvider.php, in the $providers array.

Note: You need to declare models in the Pilot config file (see below) and setup policies to browse them in pilot. Others are simply ignored.

Supported policy methods

The following methods are recognized by Pilot and will be automatically used:

  • view(User $user, Model $model) Return whether the user can view the model. Models user cannot view are excluded from the table view.
  • create(User $user) Return whether the user can create a new model.
  • update(User $user, Model $model) Return whether the user can edit the model. If the user can view the model but not update it, the form will be shown but disabled.
  • delete(User $user, Model $model) Return whether the user can delete the model. //TODO: used??
  • list(User $user) Return whether the user can access the table view listing the instances of the model. Excludes the ones he/she cannot view.

Note: The view, create, update, and delete methods are automatically added when you use Artisan to generate a policy class. Only the list method must be manually added.

Note: If a method is not defined, no user will have the right to perform the action.

Do not forget you can use the before method.

Policies examples

Let the user update only the posts they created. They still can view other posts but not update them (form fields are greyed-out).

public function update(User $user, Post $post)
{
	return $user->id === $post->user_id;
}

Let the user view only the posts they created. Others post will not be listed in the table view.

public function view(User $user, Post $post)
{
	return $user->id === $post->user_id;
}

Allow the user to view and edit all their posts, and hide unpublished posts from other users.

public function view(User $user, Post $post)
{
	return $user->id === $post->user_id;
}

public function view(User $user, Post $post)
{
	return $user->id === $post->user_id || $post->is_published;
}

Note: If a user have the update permission, but not the view permission, they will still be unable to create or update an item, because they won't have access to the form.

Documentation

All configuration is done within the config/pilot.php file, in the form of an array.

To better understand what follows, you must know what pages are used in Pilot. Here is the list:

  • index: The dashboard of the admin panel.
  • table: For one model, the list of all the instances in database.
  • edit: The edit/create form of a model.

Routes Prefix

You can set a custom prefix for all the administration routes, with the prefix key:

    'prefix' => 'admin',

The default value is admin, meaning you'll access the panel with /admin/.

Title

The title key lets you provide a custom HTML <title> for the admin panel pages.

    'title' => 'My awesome panel',

Upload

[TODO]

Models

The models array is the most important part of the configuration.

Again, the simplest pilot.php configuration file is as follow:

return [
    'models' => [
        ['model' => 'App\Post'],
        ['model' => 'App\Category'],
    ],
];

Each sub arrays of models can contain many variables to customize further how Pilot interacts with your models.

See below for more information. Each of the following properties can be added to the model arrays.

columns

Array (default: all columns)

For each model, you can provide a columns array, specifying which properties/columns will be shown and editable. You can simply specifies columns in the array:

'models' => [
    [
        'model' => 'App\Post',
        'columns' => ['id', 'title', 'category'],
    ],
    ['model' => 'App\Category'],
],

Or ou can pass more parameters for each column, by defining an array with the column name as key, ie.:

'models' => [
    [
        'model' => 'App\Post',
        'columns' => [
            'id'=> ['display' => '#'],
            'title',
            'category',
        ],
    ],
    ['model' => 'App\Category'],
],

Continue reading for all the properties of columns.

display

String (default: property name) How the column/property will be displayed in admin panel.

editable

Boolean (default: true) If false, the input will be disabled, but it will be displayed. This is especially usefull for id fields.

filters

Array or String (default: [])

In a filters array, you can pass pre-defined filters as string, that will be applied when displaying data in the table view. Some filters accept a parameter, in which case they are passed with the syntax filter:parameter.

  • limit:<limit>: The maximum number of characters to display; adds an ellipsis ("...") if needed.
  • asset:<width>: Displays an image with asset(<value>) as src attribute. You can provide a width to resize it.
  • link: Displays property as a link to the edit view.

Example:

'models' => [
    [
        'model' => 'App\Post',
        'columns' => [
            'title'=> [
                'display' => 'Title',
                'filters' => ['limit:10', 'link'],
            ],
            'category',
            'id',
        ],
    ],
]

Note: You can obviously set multiple filters, but note that they are applied in the order of the array, so for example ['link', 'limit:10'] will put a link around the text and then cut it at 10 characters. In this case you should write ['limit:10', 'link']

Note: You can pass filters as a String if there is only one filter.

filters_before and filters_after

Callable (default: null)

If you need further customization, you can use on or more filter hooks:

  • filters_before
  • filters_after

Use them as keys in the model array, and a custom callback as value, ie:

'models' => [
    [
        'model' => 'App\Post',
        'columns' => [
            'title'=> [
                'display' => 'Title',
                'filters' => 'limit:10',
                'filters_before' => function($item, $value, $reflector) { 
                    // your code 
                }
            ],
            'category',
            'id',
        ],
    ],
]

As the names imply, filter_before and filter_after will be called respectively before and after the filters defined by filters.

Both functions accept the following parameters (in this order):

  • $item (Model): The current item whose value is filtered
  • $value (String): The value being filtered. May have been altered by previous filters in the case of filter_after
  • $reflector An instance of VivienLN\Pilot\ModelReflector used for the current model

Both functions must return the filtered value as a string.

Note: value is initially filtered through PHP strip_tags() (before all filters and hooks), but the final output will be shown as HTML.

related_attribute

String (default: null)

When the "column" is actually a related model, use this property to define which attribute of the related model must be displayed.

with

Array (default: [])

Used for eager loading. Specify the relationships to load along the models, for the table view.

'models' => [
    [
        'model' => 'App\Post',
        'with' => ['author', 'categories'],
    ],
]
show_in_table

Boolean (default: true) Whether the column will be shown in table view. This lets you hide from this view some property with lengthy content (for example a post text).

Edit/Create form customization

For each column, you can specify with the form key which form element will be used to edit the value. These are the available values:

  • text (default)
  • textarea
  • checkbox
  • select:option1,option2,...
  • select_multiple:option1,option2,...

Note: If your column corresponds to a related model, you do not need to define the form property.

[TODO] Edit/Create form validation
[TODO] Edit/Create form messages

Relationships and custom attributes

In the columns array, you can use relationships, or even custom attributes.

In the end, the attribute simply should be callable, eg:

'columns' => [
    'foo'=> [
        ...
    ],
],

Will automatically call $yourModel->foo(), no matter how this method is defined. However, there are some things to know to use relations.

Relationships

If you have defined a relationship, when displaying its value, by default Laravel will show the whole related model as a JSON string.

Use the related_attribute property on the column array to specify which attribute will be used.

Note: Currently, in filters and hooks, the $item is still the current model (not the related model). This means that, for example, the link filter will NOT link to the related model. Note: Many To Many relationships models will be displayed separated by commas. Note: You do not need to define the form attribute for related models. Pilot will automatically use a select / multiple select, using the related_attribute for values.

Custom attributes

Just define a dynamic attribute getter on your model, eg:

public function getFooAttribute()
{
    return 'bar';
}

You can then use it in the columns array of your model.

icon

String (default: "list")

For each model, you can customize which icon will be used.

You can use:

  • One of the default icons by using its name (see list below)
  • A custom image url (use the asset() helper)

Note: Any SVG icon will be inserted as inline svg, to allow control from CSS.

The available default icons are:

  • box
  • box2
  • calendar
  • chart
  • checkmark
  • comment
  • dashboard
  • down
  • edit
  • eye
  • file
  • folder
  • gear
  • heart
  • left
  • link
  • list
  • lock
  • mail
  • mail2
  • off
  • gamepad
  • pin
  • remove
  • right
  • rocket
  • search
  • star
  • tag
  • trash
  • up
  • user

Usage example:

[
    'model' => 'App\Post',
    'icon' => 'rocket',
    ...
]

per_page

Integer (default: 25)

Pilot uses pagination in table view, with LengthAwarePaginator. With per_page you can customize the page size for each model.

scopes

Array (default: null)

From Laravel documentation:

Local scopes allow you to define common sets of constraints that you may easily re-use throughout your application. For example, you may need to frequently retrieve all users that are considered "popular".

You can configure Pilot to add tabs on top of the table view, each tab corresponding to a given scope.

To do so, add in your model array a scopes array which will contain scope names as keys, and tab texts as values.

[
    'model' => 'App\Post',
    'scopes' => [
        'published' => 'Published'
    ],
    ...
]

This will add a "Published" tab on top on the table view. This tab will call $model->published(), that will look for a scopePublished() method on your model. It is up to you to implement it such as described in Laravel documentation.

When you setup at least one scope, a "All" tab will automatically be added at the beginning and showing by default.

Customize views and controllers

Views used by Pilot

When you install Pilot, all the needed views are published to your app resources/views/vendor/pilot folder.

You can edit these views as much as you want:

  • layout: The layout of the Pilot administration panel
  • index: The admin Dashboard
  • table: The table listing all entries of one model
  • edit: The edit/create form for one model
  • partials/edit/: Views used in the edit/creation form, one for each input type
  • partials/table/: Views used in the table view

Note: When overriding default views, be careful of Auth checks (@can and @cannot directives). Even if the controller will block unauthorized actions, you may not want to leave links and buttons leading to 403 errors.

Variables passed to the views

Here is a list of all the variables used in the views.

  • To all views:
    • $pilot (VivienLN\Pilot\Pilot): The Pilot object
    • $user (App\User): The current user
    • $title (String): The title of the page, dynamically generated by controller
  • layout
    • No additional variables
  • index
    • No additional variables
  • table
    • $items (Illuminate\Database\Eloquent\Collection): A collection of all the models displayed in table
    • $slug (String): The slug of the current Model class, used by reflectors and routes
    • $reflector (VivienLN\Pilot\ModelReflector): A reflector for the current Model class
    • $scopes (Array): All the scopes available for the current Model class
    • $scope (String): Current scope (can be null; see Models > scopes
  • edit
    • $model (Illuminate\Database\Eloquent\Model): The current Model being edited
    • $slug (String): The slug of the current Model, used by reflectors and routes
    • $reflector (VivienLN\Pilot\ModelReflector): A reflector for the current Model class
    • $canSave (Boolean): Flag that defines if a user can save (update model) or not.

Custom routes

You can set custom admin routes in your main routes file. Just remember to use the RedirectGuest middleware provided by Pilot to redirect non-authenticated users:

['middleware' => [VivienLN\Pilot\Middleware\RedirectGuest::class]

You can of course create a controller extending PilotController to keep and add features.

Configuration example

This is a sample pilot.php file, combining all the options see above.

return [
    'prefix' => 'admin123456',
    'models' => [
        ['model' => 'App\Category'],
        ['model' => 'App\Tag'],
        [
            'model' => 'App\Post',
            'display' => 'Blog posts',
            'slug' => 'blog_post',
            'with' => ['category', 'tags'],
            'columns' => [
                'id' => [
                    'display' => '#'
                ],
                'picture' => [
                    'display' => 'Picture',
                    'filters' => 'asset:120',
                ],
                'title' => [
                    'display' => 'Titre',
                    'filters' => ['limit:10', 'link'],
                ],
                'is_published' => [
                    'show_in_table' => false,
                ],
            ],
        ],
    ],
];