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.
Inhaltsverzeichnis
- 1. Wie das Magento-Dashboard intern aufgebaut ist
- 2. Eigenes Widget per Layout-XML registrieren
- 3. Datenaggregation für die Lagerbestand-Warnung
- 4. Offene RMA-Anfragen als zweite KPI-Kachel
- 5. Chart.js im Admin-Kontext einbinden
- 6. Cache-Strategie für teure Dashboard-Abfragen
- 7. AJAX-Refresh-Pattern für einzelne Kacheln
- 8. ACL und Berechtigungen für eigene Dashboard-Widgets
- 9. Testing und Performance-Monitoring
- 10. Zusammenfassung
- 11. FAQ
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.