PHPackages                             2lenet/dashboard2-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. 2lenet/dashboard2-bundle

ActiveSymfony-bundle[Admin Panels](/categories/admin)

2lenet/dashboard2-bundle
========================

Dashboard bundle 2, widgets

2.6.1(1mo ago)413.3k↓25.5%24MITPHPPHP ^8.4CI failing

Since Jun 24Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/2lenet/DashboardBundle)[ Packagist](https://packagist.org/packages/2lenet/dashboard2-bundle)[ RSS](/packages/2lenet-dashboard2-bundle/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (10)Dependencies (60)Versions (80)Used By (4)

This bundle provides a dashboard with customizable widgets.

### Table of contents

[](#table-of-contents)

- [Installation](#installation)
    - [Creating widgets](#creating-widgets)
- [Recipes](#recipes)
    - [Troubleshooting](#troubleshooting)
    - [Templating](#templating)
    - [Widget configuration](#widget-configuration)
    - [Widget cache](#widget-cache)
    - [Widget roles](#widget-roles)
- [Static dashboard](#static-dashboard)
- [Understand the data structure](#understand-the-data-structure)

Installation
============

[](#installation)

`composer require 2lenet/dashboard2-bundle`

Add this to routes.yaml

```
dashboard_widgets:
    resource: "@LleDashboardBundle/Resources/config/routes.yaml"
```

You will also need to update your database to have the widgets table.

```
php bin/console make:migration
php bin/console doctrine:migrations:migrate

```

> ⚠️ Do not forget to check your migration file !

Creating widgets
----------------

[](#creating-widgets)

### With the maker:

[](#with-the-maker)

`php bin/console make:widget`

Just provide a short name for your widget and the maker will generate the class and the template for you.

If you want a widget for your workflow, you should use this maker : `php bin/console make:workflow-widget`

### If you prefer to do it yourself :

[](#if-you-prefer-to-do-it-yourself-)

Create a class that extends `AbstractWidget` and fill in the methods.

```
use Lle\DashboardBundle\Widgets\AbstractWidget;
```

MethodDescriptionrender**Mandatory.** Return a string that will be the widget content.getNameGet the widget title that will appear in the header. It will be translated by default.supportsIf this method returns false, the users won't be able to see or add it.supportsAjaxNOT SUPPORTED YETRecipes
=======

[](#recipes)

Troubleshooting
---------------

[](#troubleshooting)

Why don't I see my widget ?!

- Check the [roles](#widget-roles).
- Check your network tab; maybe the widget is returning a 500.
- Try to clear the cache.

How do I get the logged user ?!

- Use $this-&gt;security-&gt;getUser().

Why is the dashboard ugly/not working ?!

- `bin/console asset:install`.

Why do I get a 404 ?

- RTFM. Add the routes as specified above.

When I add a widget, they appear *very* far in the bottom ?!

- The widgets are added below the most bottom existing widget. You may have a widget that does not appear.

*Feel free to add more*

Templating
----------

[](#templating)

A base template exists :

```
{% extends '@LleDashboard/widget/base_widget.html.twig' %}
```

To easily render a template, you can use the twig() method. It will automatically add a "widget" variable that contains your type.

Example :

```
public function render()
{
    return $this->twig("widget/pasta_widget.html.twig", [
        "data" => $data,
    ]);
}
```

Note that base template uses Bootstrap 5 cards. Various blocks exists to override the base template.

If you want to hide the header of a widget, and only show it on hover : you must add the following lines in the template of your widget :

```
{% block widget_class %}
    card-simplified
{% endblock %}
```

By default, there is a button to export a widget as PDF. You can remove this feature :

Example :

```
public function render()
{
    return $this->twig("widget/pasta_widget.html.twig", [
        "data" => $data,
        "exportable" => false
    ]);
}
```

You can define two parameters to configure your export : orientation (portrait or landscape) and format (a4, a3, a2, ...)

Example :

```
public function render()
{
    return $this->twig("widget/pasta_widget.html.twig", [
        "data" => $data,
        "exportable" => [
            "orientation" => "landscape",
            "format" => "a3"
        ],
    ]);
}
```

Widget configuration
--------------------

[](#widget-configuration)

Each widget is individually configurable. The property "config" in the widgets is a JSON field where you can put anything you like. By default, this field is used by the configuration form.

If you want to add a configuration form, you can use the createForm() method, which works like the Controller one. Then, you need to pass the form as a variable named `config_form` to the template.

Example:

```
public function render()
{
    $form = $this->createForm(InterventionWidgetType::class);

    return $this->twig("widget/cake_widget.html.twig", [
        "data" => $data,
        "config_form" => $form->createView()
    ]);
}
```

```
class InterventionWidgetType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('etat', ChoiceType::class, [
                'choices' => $yourChoices
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            // Configure your form options here
        ]);
    }
}
```

The result of the form will overwrite the config property, in a JSON format.

To retrieve your form value in the widget : `$this->getConfig("etat");`

Widget cache
------------

[](#widget-cache)

Widgets are cached for 5 minutes, to avoid doing calculations everytime, especially for big charts.
The cache is based on a cache key, if the value of the key changes, the cache is refreshed, whether 5 minutes have passed or not.

You can change the timeout and the cache key with the following :

```
public function getCacheKey(): string
{
    return $this->getId() . "_" .md5($this->config);
}

public function getCacheTimeout(): int
{
    return 300;
}
```

If you want to disable the cache for a widget, just make sure that getCacheTimeout returns 0.

Widget roles
------------

[](#widget-roles)

Widgets have roles on them, generated from the name.
Example : PostIt =&gt; ROLE\_DASHBOARD\_POST\_IT

If you want to change this behaviour, simply override supports(), or add a voter.

Add/configure a chartJS widget
------------------------------

[](#addconfigure-a-chartjs-widget)

Once configured, this widget allows the user to obtain a chart based on the application provided charts configuration. To do this, you need to create the different possible configurations and generate the data accordingly.

First, implements the ChartProviderInterface on your class (Repository, Service, ...). Then add the `getChart` and `getChartList` methods.

The `getChartList` method is used to list the provided charts configurations usable by the widget The `getChart` method return a ChartModel ( from symfonyUx Chart bundle ).

#### `getDataConf`:

[](#getdataconf)

This method return an array with the differents configurations.

```
public function getChartList(): array
{
    return [
        'COUNTSOMETHING-DAY-30',
        'COUNTSOMETHING-DAY-60',
        'COUNTSOMETHING-MONTH-12',
        'COUNTSOMETHING-MONTH-24',
        'SUMSOMETHING-DAY-30',
        'SUMSOMETHING-DAY-60',
        'SUMSOMETHING-MONTH-12',
        'SUMSOMETHING-YEAR-1'
    ];
}
```

#### `getChart`:

[](#getchart)

For this method, you will receive selected chart key.

Next, you need to create/return a chart model ( \\Symfony\\UX\\Chartjs\\Model\\Chart )

A full example with a table KPI and KPI Value to graph arbitrary datas:

```
    public function getChartList(): array
    {
        $qb = $this->createQueryBuilder('kv');
        $qb->join("kv.kpi", "k");
        $qb->distinct();
        $qb->select('k.code');
        $codes = [];

        foreach ($qb->getQuery()->execute() as $code) {
            $codes[] = $code["code"];
        }

        return $codes;
    }
    public function getChart(string $confKey): Chart
    {
        $labels = [];
        $values = [];
        foreach ($this->getData($confKey) as $row) {
            $labels[] = $row['date'];
            $values[] = $row['value'];
        }

        $chart = $this->chartBuilder->createChart(Chart::TYPE_BAR);
        $chart->setData([
            'labels' => $labels,
            'datasets' => [
                [
                    'label' => $confKey,
                    'backgroundColor' => 'rgb(255, 99, 132, .4)',
                    'borderColor' => 'rgb(255, 99, 132)',
                    'data' => $values,
                    'tension' => 0.4,
                ],
            ],
        ]);
        $chart->setOptions([
            'maintainAspectRatio' => false,
        ]);

        return $chart;
    }

    public function getData(string $confKey): array
    {
        $qb = $this->createQueryBuilder('kv');
        $qb->join("kv.kpi", "k");
        $qb->select('SUM(kv.value) as value');
        $qb->where("k.code = :kpi");
        $qb->setParameter("kpi", $confKey);

        $qb
            ->addSelect("CONCAT(WEEK(kv.date), '-', YEAR(kv.date)) as date")
            ->groupBy('date');

        return $qb->getQuery()->getResult();
    }
```

Static dashboard
================

[](#static-dashboard)

The bundle provides a static dashboard mode that displays a fixed list of widgets without any database storage or user customization (no drag, no add/remove, no per-user config). The list of widgets to render is supplied by a service that the application must implement.

Setup
-----

[](#setup)

### 1. Route

[](#1-route)

Point your home route (or any route you want) to `StaticDashboardController::staticDashboard`:

```
# config/routes/dashboard.yaml
home:
    path: /
    controller: Lle\DashboardBundle\Controller\StaticDashboardController::staticDashboard
dashboard:
    resource: "@LleDashboardBundle/Resources/config/routes.yaml"
```

The static dashboard is also available out of the box at `/dashboard/static`.

### 2. Implement the static widget provider (mandatory)

[](#2-implement-the-static-widget-provider-mandatory)

The controller does not know which widgets to display: it delegates that to a service implementing `Lle\DashboardBundle\Contracts\StaticWidgetProviderInterface`:

```
namespace Lle\DashboardBundle\Contracts;

interface StaticWidgetProviderInterface
{
    public function getMyWidgets(): array;

    public function getWidget(string $index): ?WidgetTypeInterface;
}
```

`getMyWidgets()` returns the ordered list of widget instances to render. `getWidget($index)` resolves a single widget by the **array key** used in `getMyWidgets()` — that key is the `static_index` passed to the ajax refresh route.

Inject `iterable $widgetTypes` (Symfony tagged iterator) to receive every widget type defined in the project: widgets are auto-tagged with `lle_dashboard.widget` because they implement `WidgetTypeInterface`. Then pick the ones you want to display, optionally overriding their config (title, etc.) via `setConfig()`.

Real example from this project (`src/Service/Dashboard/StaticWidgetProvider.php`):

```
namespace App\Service\Dashboard;

use App\Widget\DossierWorkflow;
use App\Widget\MonitoringBoxes;
use App\Widget\QuotaSms;
use App\Widget\StatsDayWidget;
use App\Widget\StatsWidget;
use App\Widget\SuiviTelechargement;
use Lle\DashboardBundle\Contracts\StaticWidgetProviderInterface;
use Lle\DashboardBundle\Contracts\WidgetTypeInterface;
use Lle\DashboardBundle\Widgets\AbstractWidget;

class StaticWidgetProvider implements StaticWidgetProviderInterface
{
    /** @var array */
    protected array $widgetTypes = [];

    /** @var array */
    protected array $widgets = [];

    public function __construct(iterable $widgetTypes)
    {
        /** @var WidgetTypeInterface $widgetType */
        foreach ($widgetTypes as $widgetType) {
            if ($widgetType->getType()) {
                $this->widgetTypes[$widgetType->getType()] = $widgetType;
            }
        }

        $this->widgets = [
            "workflow" => $this->buildWidget(DossierWorkflow::class, ['title' => 'Dossier Workflow']),
            "boxs" => $this->buildWidget(MonitoringBoxes::class, ['title' => 'Monitoring Boxes']),
            "quotaSms" => $this->buildWidget(QuotaSms::class, ['title' => 'Quota SMS']),
            "suivisTelechargement" => $this->buildWidget(SuiviTelechargement::class, ['title' => 'Suivis Téléchargement']),
            "statsDay" => $this->buildWidget(StatsDayWidget::class, ['title' => 'Stats par jours']),
            "statsAnnuelle" => $this->buildWidget(StatsWidget::class, ['title' => 'Stats']),
        ];
    }

    public function getWidgetType(string $widgetType): ?WidgetTypeInterface
    {
        if (array_key_exists($widgetType, $this->widgetTypes)) {
            return clone $this->widgetTypes[$widgetType];
        }

        return null;
    }

    private function buildWidget(string $class, array $config): AbstractWidget
    {
        $type = self::classToType($class);
        $widget = $this->widgetTypes[$type] ?? null;

        if (!$widget instanceof AbstractWidget) {
            throw new \RuntimeException(sprintf('Widget type "%s" is not registered or does not extend AbstractWidget.', $type));
        }

        $clone = clone $widget;
        $clone->setConfig($config);

        return $clone;
    }

    public function getMyWidgets(): array
    {
        return $this->widgets;
    }

    public function getWidget(string $index): ?WidgetTypeInterface
    {
        return $this->widgets[$index] ?? null;
    }

    private static function classToType(string $class): string
    {
        return str_replace('\\', '_', $class) . '_widget';
    }
}
```

A few things worth noting:

- The `buildWidget()` helper centralizes the lookup-clone-configure logic. It returns `AbstractWidget` (not `WidgetTypeInterface`) because `setConfig()` lives on `AbstractWidget` — typing it that way keeps PHPStan happy and lets callers chain `setConfig()` directly.
- It throws a `RuntimeException` if the widget type is missing (typo in the FQCN, widget not tagged, etc.) rather than silently returning `null` and crashing later on `->setConfig()`. Failing fast at construction time gives a clear stack trace instead of a confusing "method on null" error.
- The instances stored in `$this->widgets` are **clones**, so the per-widget `setConfig()` does not mutate the shared widget type registered in the container.
- The array keys (`"workflow"`, `"boxs"`, ...) are stable identifiers used as `static_index` by the ajax refresh route. Don't rename them lightly if the dashboard is already in production.
- `setConfig(['title' => '...'])` lets you display the same widget type several times with different titles, without subclassing.

### 3. Declare your provider to the bundle

[](#3-declare-your-provider-to-the-bundle)

The bundle exposes a `static_widget_provider` configuration key. Set it to your implementation FQCN: the bundle aliases `StaticWidgetProviderInterface` to that service so the controller can autowire it.

```
# config/packages/lle_dashboard.yaml
lle_dashboard:
    static_widget_provider: App\Service\Dashboard\StaticWidgetProvider
```

If this key is left to its default, autowiring of `StaticWidgetProviderInterface` will fail and the static dashboard route will not work.

### 4. Widget sizing

[](#4-widget-sizing)

In static mode, widgets are laid out in a Bootstrap grid. The default CSS class is computed from `getWidth()` (the GridStack column count maps to `col-md-{width}`).

You can override this per widget by implementing `getStaticCssClass()`. For example, `DossierWorkflow` in this project takes the full row:

```
// src/Widget/DossierWorkflow.php
public function getStaticCssClass(): string
{
    return 'col-12 col-md-12';
}
```

### 5. Custom static rendering

[](#5-custom-static-rendering)

By default, `renderStatic()` calls `render()`. Override it in your widget to provide a different rendering in static mode:

```
public function renderStatic(): string
{
    return $this->twig('widget/my_widget_static.html.twig', [
        'data' => $this->getData(),
    ]);
}
```

The ajax refresh route `/dashboard/render_static_widget/{static_index}` calls `getWidget($static_index)` on your provider, then `renderStatic()` on the returned widget. With the provider above, `static_index` is `"workflow"`, `"boxs"`, etc. — the keys of `$this->widgets`.

Understand the data structure
=============================

[](#understand-the-data-structure)

Widget Entity &lt;--&gt; Widget Type &lt;--&gt; DashboardController

A WidgetType (eg. PostItWidget) is simply a *definition* that will be used by the controller.
When an user adds a widget, it will create a distinct entity.
A widget may have multiple entities for the same type. For example, an user may have multiple post-its with different contents.

Some widgets do not have an user\_id filled in. They are the default widgets, which may only be created by the super admin (using the buttons in the dashboard)

###  Health Score

61

—

FairBetter than 98% of packages

Maintenance90

Actively maintained with recent releases

Popularity31

Limited adoption so far

Community23

Small or concentrated contributor base

Maturity85

Battle-tested with a long release history

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~47 days

Recently: every ~17 days

Total

39

Last Release

47d ago

PHP version history (2 changes)2.0.0PHP &gt;=7.2

2.5.0PHP ^8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/b715c92f6219caaeecb8f1a8d511ebfa17f05c2b868875dbefaaf95c0dd0df91?d=identicon)[sebheitzmann](/maintainers/sebheitzmann)

---

Top Contributors

[![valentin-helies](https://avatars.githubusercontent.com/u/89925635?v=4)](https://github.com/valentin-helies "valentin-helies (66 commits)")[![sebheitzmann](https://avatars.githubusercontent.com/u/2197902?v=4)](https://github.com/sebheitzmann "sebheitzmann (65 commits)")[![SpaghettiBolognaise](https://avatars.githubusercontent.com/u/31731763?v=4)](https://github.com/SpaghettiBolognaise "SpaghettiBolognaise (28 commits)")[![TheoHab](https://avatars.githubusercontent.com/u/93769453?v=4)](https://github.com/TheoHab "TheoHab (3 commits)")[![iBast](https://avatars.githubusercontent.com/u/77158590?v=4)](https://github.com/iBast "iBast (2 commits)")[![Antoine68](https://avatars.githubusercontent.com/u/38207637?v=4)](https://github.com/Antoine68 "Antoine68 (1 commits)")[![emilieD68](https://avatars.githubusercontent.com/u/19471750?v=4)](https://github.com/emilieD68 "emilieD68 (1 commits)")

---

Tags

dashboardsymfonysymfony-bundledashboard

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/2lenet-dashboard2-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/2lenet-dashboard2-bundle/health.svg)](https://phpackages.com/packages/2lenet-dashboard2-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M387](/packages/easycorp-easyadmin-bundle)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1616.4k14](/packages/2lenet-crudit-bundle)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.1k17.8k](/packages/prestashop-prestashop)[pimcore/pimcore

Content &amp; Product Management Framework (CMS/PIM/E-Commerce)

3.8k3.8M508](/packages/pimcore-pimcore)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9421.6k61](/packages/open-dxp-opendxp)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1189.8k](/packages/rcsofttech-audit-trail-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
