Eigene Widgets für das Magento-2-Admin-Dashboard bauen
AI generated
M2
di.xml
Magento 2 · Admin Dashboard
Eigene Widgets für das Admin-Dashboard
KPI-Kacheln, Chart.js-Integration und eine Cache-Strategie für teure Abfragen

Das Standard-Dashboard von Magento zeigt Umsatz, Bestseller und wiederkehrende Kunden, für fachliche KPIs wie Lagerbestand-Warnungen oder offene RMA-Anfragen fehlt dagegen jede vorgefertigte Kachel. Wer die Dashboard-Architektur, die Datenaggregation und eine saubere Cache-Strategie versteht, kann eigene Widgets bauen, die auch bei großen Datenmengen nicht jedes Mal die komplette Datenbank neu befragen.

12 Min. Lesezeit Dashboard-Tabs KPI-Kacheln Chart.js Cache-Strategie AJAX-Refresh

1. Wie das Magento-Dashboard intern aufgebaut ist

Das Admin-Dashboard basiert auf dem Modul Magento_Dashboard und rendert seine Tabs und Kacheln über klassische Blöcke, nicht über UI-Components. Die Haupt-Seite Magento\Dashboard\Block\Grids fungiert als Container, der über getChildNames die einzelnen Tabs, Diagrammbereich, Bestseller, zuletzt georderte Produkte, iteriert und rendert. Für ein eigenes Widget bedeutet das: Es reicht nicht, einfach einen weiteren Block irgendwo zu registrieren, das Widget muss als Kind-Block explizit in die bestehende Dashboard-Layout-Struktur eingehängt werden.

Layout-technisch bedeutet das eine Erweiterung von adminhtml_dashboard_index.xml um ein eigenes referenceBlock auf dashboard.grids, mit einem block-Element, das auf die eigene Widget-Klasse zeigt. Diese Klasse erbt üblicherweise von Magento\Backend\Block\Template, in komplexeren Fällen mit eigener Datenaggregation von Magento\Backend\Block\Dashboard\AbstractDashboardWidget, das bereits einige Konventionen für Zeitraum-Filter und Chart-Daten vorgibt.

2. Eigenes Widget per Layout-XML registrieren

Die Registrierung selbst ist reine Layout-Konfiguration und kommt ohne Plugins oder Preferences aus, was dem Grundsatz entspricht, Erweiterungen möglichst additiv statt invasiv umzusetzen. Ein eigenes Template rendert die Kachel mit Kennzahl, Trend-Pfeil und optional einem kleinen Sparkline-Chart, während die eigentliche Datenermittlung komplett im PHP-Block liegt und nicht im Template selbst berechnet wird.

Wichtig ist die richtige Positionierung über sortOrder innerhalb des Dashboard-Containers, da neue Kacheln sonst unvorhersehbar zwischen bestehenden Standard-Widgets erscheinen. Für ein Lagerbestand-Warnungs-Widget bietet sich eine Platzierung direkt neben der Bestseller-Kachel an, da beide fachlich eng mit dem aktuellen Sortiment zusammenhängen.


<!-- app/code/Mironsoft/DashboardWidgets/view/adminhtml/layout/adminhtml_dashboard_index.xml -->
<referenceBlock name="dashboard.grids">
    <block class="Mironsoft\DashboardWidgets\Block\Widget\StockAlerts"
           name="dashboard.widget.stock_alerts"
           template="Mironsoft_DashboardWidgets::widget/stock_alerts.phtml"
           after="dashboard.bestsellers"/>
</referenceBlock>

3. Datenaggregation für die Lagerbestand-Warnung

Die eigentliche Kennzahl, wie viele Produkte den definierten Mindestbestand unterschreiten, wird nicht per Live-Collection-Iteration im Block berechnet, sondern über eine gezielte Aggregations-Query gegen die Bestandstabelle. Für Magento mit dem Modul Magento_InventorySalesApi bedeutet das eine Abfrage gegen die Source-Item-Tabellen statt der alten, mittlerweile veralteten cataloginventory_stock_item-Struktur allein, da mehrere Lagerorte sonst nicht korrekt berücksichtigt werden.

Der Block selbst bleibt schlank und delegiert die eigentliche Aggregation an eine eigene, injizierbare Service-Klasse, die unabhängig vom Dashboard auch in einem CLI-Command oder einer Benachrichtigung wiederverwendet werden kann. Diese Trennung zahlt sich aus, sobald dieselbe Kennzahl später auch als E-Mail-Report oder als eigene Admin-Notification gebraucht wird.


<?php

declare(strict_types=1);

namespace Mironsoft\DashboardWidgets\Model;

use Magento\Framework\App\ResourceConnection;

/**
 * Aggregiert die Anzahl der Produkte, deren Lagerbestand
 * unter dem konfigurierten Mindestbestand liegt.
 */
class StockAlertAggregator
{
    /**
     * @param ResourceConnection $resourceConnection Zugriff auf die Datenbankverbindung
     */
    public function __construct(
        private readonly ResourceConnection $resourceConnection,
    ) {
    }

    /**
     * Zaehlt Produkte unterhalb des Mindestbestands je Lagerort.
     *
     * @param int $threshold Mindestbestand-Schwelle
     * @return int Anzahl betroffener Produkte
     */
    public function countBelowThreshold(int $threshold): int
    {
        $connection = $this->resourceConnection->getConnection();
        $select = $connection->select()
            ->from(
                ['source_item' => $this->resourceConnection->getTableName('inventory_source_item')],
                [new \Zend_Db_Expr('COUNT(DISTINCT source_item.sku)')]
            )
            ->where('source_item.quantity < ?', $threshold)
            ->where('source_item.status = 1');

        return (int) $connection->fetchOne($select);
    }
}

4. Offene RMA-Anfragen als zweite KPI-Kachel

Läuft im Shop ein eigenes RMA-Modul mit eigener Entity, liegt die Zählung offener Rücksendeanfragen näher an einer klassischen Collection-Abfrage mit addFieldToFilter auf den Statuswert, da hier keine mehreren Lagerorte, sondern ein einfacher Status pro Datensatz relevant ist. Entscheidend ist auch hier, das Zählen nicht über getSize einer vollständig geladenen Collection zu erledigen, sondern über eine reine Count-Query, um unnötig große Ergebnismengen zu vermeiden.

Für eine aussagekräftige Kachel reicht die reine Anzahl selten aus, ein Vergleich zum Vorwochenwert liefert erst den fachlich relevanten Trend. Dafür wird dieselbe Aggregations-Logik mit einem zusätzlichen Zeitfenster-Parameter zweimal aufgerufen, einmal für den aktuellen und einmal für den vorherigen Zeitraum, und die Differenz als Trend-Pfeil im Template dargestellt.

5. Chart.js im Admin-Kontext einbinden

Magento bringt Chart.js für das Standard-Verkaufsdiagramm bereits als Bundle-Abhängigkeit mit, ein eigenes Widget kann diese vorhandene Bibliothek über requirejs-config.js referenzieren, statt eine zusätzliche Kopie einzubinden. Der Verweis erfolgt über ein map-Alias auf die vorhandene Chart.js-Instanz, wodurch ein zusätzliches Laden derselben Bibliothek und eine unnötige Vergrößerung des Admin-JavaScript-Bundles vermieden wird.

Das eigentliche Rendering des Sparkline-Charts erfolgt über eine kleine Knockout- oder reine RequireJS-Komponente, die die vom Server per data-Attribut übergebenen Zahlenreihen entgegennimmt und beim DOM-Ready-Event initialisiert. Nach jedem eingebetteten Inline-Script-Block ist gemäß den Projektkonventionen registerInlineScript des HyväCsp-ViewModels aufzurufen, auch im Admin-Kontext gilt eine vergleichbare CSP-Härtung, weshalb Inline-Scripts sauber über die vorgesehenen Mechanismen statt per direktem script-Tag eingebunden werden sollten.


// app/code/Mironsoft/DashboardWidgets/view/adminhtml/web/js/stock-alerts-sparkline.js
define(['jquery', 'chartjsAdmin'], function ($, Chart) {
    'use strict';

    return function (config, element) {
        var ctx = element.getContext('2d');

        new Chart(ctx, {
            type: 'line',
            data: {
                labels: config.labels,
                datasets: [{
                    data: config.values,
                    borderColor: '#f26322',
                    fill: false,
                    tension: 0.3
                }]
            },
            options: {
                plugins: { legend: { display: false } },
                scales: { x: { display: false }, y: { display: false } }
            }
        });
    };
});

6. Cache-Strategie für teure Dashboard-Abfragen

Eine Aggregations-Query über mehrere Millionen Bestandszeilen bei jedem Dashboard-Aufruf neu auszuführen, ist selbst mit gutem Index spürbar langsam, sobald mehrere Admin-Nutzer das Dashboard gleichzeitig öffnen. Der Block liest die Kennzahl deshalb nicht direkt aus der Aggregations-Klasse, sondern über eine Zwischenschicht, die das Ergebnis mit einem eigenen Cache-Tag im konfigurierten Cache-Backend, meist Redis, für einen begrenzten Zeitraum vorhält.

Eine TTL von fünf bis fünfzehn Minuten ist für die meisten KPI-Kacheln ein guter Kompromiss zwischen Aktualität und Datenbank-Last, da Admin-Nutzer selten sekundengenaue Werte benötigen. Wichtig ist außerdem ein eigener, sprechender Cache-Tag statt der Wiederverwendung eines bestehenden Magento-Tags, damit die Kachel gezielt und ohne Seiteneffekte auf andere Cache-Bereiche invalidiert werden kann, etwa unmittelbar nach einem manuellen Bestand-Import.


<?php

declare(strict_types=1);

namespace Mironsoft\DashboardWidgets\Model;

use Magento\Framework\App\CacheInterface;
use Magento\Framework\Serialize\SerializerInterface;

/**
 * Cached-Wrapper um teure Dashboard-Aggregationen mit kurzer TTL.
 */
class CachedStockAlertProvider
{
    private const CACHE_TAG = 'mironsoft_dashboard_stock_alerts';
    private const CACHE_LIFETIME = 600;

    /**
     * @param StockAlertAggregator $aggregator Fuehrt die eigentliche Aggregation durch
     * @param CacheInterface $cache Konfiguriertes Cache-Backend
     * @param SerializerInterface $serializer Serialisiert das Cache-Ergebnis
     */
    public function __construct(
        private readonly StockAlertAggregator $aggregator,
        private readonly CacheInterface $cache,
        private readonly SerializerInterface $serializer,
    ) {
    }

    /**
     * Liefert die Anzahl betroffener Produkte, aus dem Cache falls vorhanden.
     *
     * @param int $threshold Mindestbestand-Schwelle
     * @return int
     */
    public function getCount(int $threshold): int
    {
        $cacheKey = self::CACHE_TAG . '_' . $threshold;
        $cached = $this->cache->load($cacheKey);

        if ($cached !== false) {
            return (int) $this->serializer->unserialize($cached);
        }

        $count = $this->aggregator->countBelowThreshold($threshold);
        $this->cache->save(
            $this->serializer->serialize($count),
            $cacheKey,
            [self::CACHE_TAG],
            self::CACHE_LIFETIME
        );

        return $count;
    }
}

7. AJAX-Refresh-Pattern für einzelne Kacheln

Statt die gesamte Dashboard-Seite bei jeder Aktualisierung neu zu laden, lohnt sich für einzelne Kacheln ein eigener, schlanker Controller, der ausschließlich die aktuelle Kennzahl als JSON zurückgibt. Ein kleines JavaScript-Intervall auf Basis von setInterval oder, sauberer, ein Alpine-artiger Polling-Mechanismus fragt diesen Endpunkt in einem konfigurierbaren Abstand ab und aktualisiert nur den Zahlenwert und den Trend-Pfeil im DOM, ohne die gesamte Seite neu zu rendern.

Der Controller selbst greift auf denselben gecachten Provider zurück wie das initiale Server-Rendering, sodass Cache-Strategie und Datenquelle an genau einer Stelle gepflegt werden. Ein zu kurzes Polling-Intervall, etwa alle fünf Sekunden, ergibt bei einer Cache-TTL von zehn Minuten wenig Sinn und erzeugt nur unnötige Requests, das Intervall sollte deshalb an die tatsächliche Cache-Lebensdauer angepasst werden.

8. ACL und Berechtigungen für eigene Dashboard-Widgets

Nicht jeder Admin-Nutzer sollte automatisch jede KPI-Kachel sehen, insbesondere wenn RMA-Zahlen oder Lagerbestand-Details fachlich sensibel sind. Der Block prüft deshalb vor dem Rendern über AuthorizationInterface, ob die aktuelle Admin-Rolle die passende ACL-Resource besitzt, und gibt bei fehlender Berechtigung schlicht keinen Inhalt zurück, statt die Kachel leer oder mit einer Fehlermeldung anzuzeigen.

Die ACL-Resource selbst wird wie bei jedem neuen Modul in acl.xml deklariert und im system.xml-Menü als eigener Konfigurationsbereich für die Widget-Einstellungen, etwa die Schwellenwerte für die Lagerbestand-Warnung, referenziert. Diese Kopplung stellt sicher, dass sowohl die Sichtbarkeit des Widgets als auch der Zugriff auf dessen Konfiguration konsistent über dieselbe Berechtigungsstruktur gesteuert werden.

9. Testing und Performance-Monitoring

Für die Aggregations-Klasse selbst lohnt sich ein Integrationstest, der die erzeugte SQL-Query gegen eine bekannte Testdatenmenge prüft, statt nur das Rendering des Templates zu testen. So werden Regressionen in der Zählweise, etwa nach einer Änderung an der Inventory-Struktur, frühzeitig erkannt, bevor sie als falsche Kennzahl im Live-Dashboard auffallen.

Für den produktiven Betrieb lohnt sich außerdem ein Blick in den MySQL Slow Query Log nach dem Deployment eines neuen Widgets, insbesondere wenn die zugrunde liegende Tabelle mehrere Millionen Zeilen umfasst. Ein fehlender Index auf der Filterspalte fällt im lokalen Testsystem mit wenigen tausend Testzeilen oft nicht auf, wird aber auf einem Produktivkatalog schnell zum spürbaren Performance-Problem für alle gleichzeitig eingeloggten Admin-Nutzer.

Widget-Typ Datenquelle Aktualisierung Empfohlene Cache-TTL
Lagerbestand-Warnung inventory_source_item Aggregation AJAX-Polling 5-10 Minuten
Offene RMA-Anfragen eigene RMA-Collection mit Status-Filter AJAX-Polling 2-5 Minuten
Umsatz-Sparkline (custom Zeitraum) sales_order Aggregation beim Seitenaufruf 10-15 Minuten
Fehlgeschlagene Bulk-Operationen OperationRepositoryInterface AJAX-Polling 1-2 Minuten
Neue Kundenregistrierungen customer_entity Aggregation beim Seitenaufruf 10-15 Minuten

Mironsoft

Magento-Entwicklung, Modul-Beratung und Systemarchitektur

Magento-Projekt, das eine zweite Meinung oder erfahrene Umsetzung braucht?

Wir entwickeln individuelle Magento-Module, beraten bei Architekturentscheidungen und übernehmen komplexe Umsetzungen, von der Service-Contract-Planung bis zum produktionsreifen Deployment.

Architektur-Beratung

Modul- und Systemarchitektur vor der Umsetzung fundiert durchdenken lassen.

Custom-Modul-Entwicklung

Individuelle Magento-Module nach Best Practices sauber umsetzen.

Code-Review & Audit

Bestehende Module auf Performance, Sicherheit und Wartbarkeit prüfen lassen.

10. Zusammenfassung

Eigene Dashboard-Widgets in Magento 2: Das Wichtigste auf einen Blick

Architektur

Eigene Blöcke via referenceBlock auf dashboard.grids in adminhtml_dashboard_index.xml einhängen.

Datenaggregation

Gezielte Count- und Aggregations-Queries statt vollständig geladener Collections nutzen.

Visualisierung

Vorhandene Chart.js-Instanz über requirejs-config.js referenzieren statt neu einbinden.

Betrieb

Kurze Cache-TTL mit eigenem Cache-Tag, AJAX-Refresh statt komplettem Seiten-Reload, ACL-geprüftes Rendering.

11. FAQ: Eigene Dashboard-Widgets in Magento 2: Das Wichtigste auf einen Blick

1Kann ein eigenes Dashboard-Widget über UI-Components statt über klassische Blöcke gebaut werden?
Das Standard-Dashboard basiert vollständig auf klassischen Blöcken, ein UI-Component-Widget lässt sich zwar technisch parallel einbinden, fügt sich aber nicht sauber in die bestehende getChildNames-Iteration ein und wird deshalb in der Praxis selten so umgesetzt.
2Warum erscheint mein Widget an der falschen Stelle im Dashboard?
Meist fehlt eine explizite sortOrder- oder after-Angabe im referenceBlock, ohne diese Angabe entscheidet die Reihenfolge der Layout-XML-Verarbeitung, die sich zwischen Magento-Versionen unterscheiden kann.
3Muss jede Dashboard-Kachel zwingend gecacht werden?
Nein, für sehr schnelle Abfragen mit gutem Index und geringem Datenvolumen ist ein Cache oft unnötige Komplexität, ab spürbarer Query-Laufzeit oder bei mehreren gleichzeitig aktiven Admin-Nutzern lohnt sich ein Cache aber deutlich.
4Wie wird der Cache nach einem relevanten Datenupdate invalidiert?
Über den eigenen Cache-Tag lässt sich der Bereich gezielt per cache:clean mit dem Tag-Namen leeren, zusätzlich kann ein Observer auf das relevante Event, etwa einen Import-Abschluss, den Cache-Eintrag proaktiv löschen.
5Ist Chart.js im Admin-Bereich zwingend die richtige Wahl für eigene Diagramme?
Da Magento Chart.js bereits für das Standard-Verkaufsdiagramm mitbringt, spart die Wiederverwendung eine zusätzliche Abhängigkeit, für sehr spezielle Diagrammtypen kann eine andere Bibliothek dennoch sinnvoller sein.
6Wie oft sollte ein AJAX-Polling-Intervall eine Kachel aktualisieren?
Das Intervall sollte an die Cache-TTL der zugrunde liegenden Daten gekoppelt sein, ein kürzeres Intervall erzeugt nur zusätzliche Requests ohne neue Informationen, da der Cache ohnehin denselben Wert zurückgibt.
7Wie wird verhindert, dass sensible KPI-Daten für alle Admin-Rollen sichtbar sind?
Der Block prüft vor dem Rendern über AuthorizationInterface die passende ACL-Resource und gibt ohne Berechtigung keinen Inhalt zurück, die Zuweisung erfolgt wie bei jedem Modul über acl.xml.
8Können mehrere Dashboard-Widgets dieselbe Aggregations-Klasse teilen?
Ja, das ist sogar empfehlenswert, eine schlanke Service-Klasse für die Aggregation lässt sich sowohl im Dashboard-Block als auch in einem CLI-Command oder einer E-Mail-Benachrichtigung wiederverwenden.
9Was passiert mit dem Widget, wenn die zugrunde liegende Tabelle sehr groß wird?
Ohne passenden Index auf der Filterspalte wird die Aggregations-Query mit wachsender Tabellengröße zunehmend langsamer, ein Blick in den Slow Query Log nach dem Deployment hilft, dieses Problem frühzeitig zu erkennen.
10Lohnt sich ein eigenes Dashboard-Widget für eine KPI, die nur einmal täglich relevant ist?
In diesem Fall ist ein Cron-generierter Report per E-Mail oder ein Eintrag im Admin-Notification-Bereich oft die passendere Lösung, ein Live-Dashboard-Widget mit häufigem Polling lohnt sich vor allem für Kennzahlen, die sich im Tagesverlauf tatsächlich ändern.