Les plugins natifs peuvent étendre le panneau d’administration avec des pages React personnalisées et des widgets de tableau de bord — les plugins sandboxés décrivent leur interface utilisateur comme Block Kit à la place, car l’envoi de JavaScript de plugin dans l’administration casserait l’isolation du sandbox.
Si votre plugin ne nécessite qu’un formulaire de paramètres, le formulaire admin.settingsSchema généré automatiquement (voir Votre premier plugin natif) couvre la plupart des cas sans écrire de React. Optez pour des composants personnalisés lorsque vous avez besoin d’une interface utilisateur plus riche que ce que settingsSchema fournit.
Point d’entrée d’administration
Les plugins avec interface utilisateur d’administration exportent des objets pages et widgets depuis un point d’entrée admin:
import { SEOSettingsPage } from "./components/SEOSettingsPage";
import { SEODashboardWidget } from "./components/SEODashboardWidget";
export const widgets = {
"seo-overview": SEODashboardWidget,
};
export const pages = {
"/settings": SEOSettingsPage,
};
Configurez le point d’entrée dans package.json:
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}
Référencez-le depuis definePlugin():
definePlugin({
id: "seo",
version: "1.0.0",
admin: {
entry: "@my-org/plugin-seo/admin",
pages: [{ path: "/settings", label: "SEO Settings", icon: "settings" }],
widgets: [{ id: "seo-overview", title: "SEO Overview", size: "half" }],
},
});
Le descripteur nécessite un adminEntry correspondant pour qu’EmDash sache où trouver les composants au moment de la compilation:
adminEntry: "@my-org/plugin-seo/admin",
Pages d’administration
Les pages d’administration sont des composants React qui se montent sous /_emdash/admin/plugins/<plugin-id>/<path>.
Définition de page
Déclarez chaque page sous admin.pages avec un chemin, une étiquette et une icône:
admin: {
pages: [
{
path: "/settings",
label: "Settings",
icon: "settings",
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
],
}
Composant de page
Le composant suivant lit et enregistre les paramètres via le hook de l’API du plugin:
import { useState, useEffect } from "react";
import { usePluginAPI } from "@emdash-cms/admin";
export function SettingsPage() {
const api = usePluginAPI();
const [settings, setSettings] = useState<Record<string, unknown>>({});
const [saving, setSaving] = useState(false);
useEffect(() => {
api.get("settings").then(setSettings);
}, []);
const handleSave = async () => {
setSaving(true);
await api.post("settings/save", settings);
setSaving(false);
};
return (
<div>
<h1>Plugin Settings</h1>
<label>
Site Title
<input
type="text"
value={(settings.siteTitle as string) || ""}
onChange={(e) => setSettings({ ...settings, siteTitle: e.target.value })}
/>
</label>
<button onClick={handleSave} disabled={saving}>
{saving ? "Saving..." : "Save Settings"}
</button>
</div>
);
}
Hook de l’API du plugin
usePluginAPI() appelle les routes de votre plugin avec le préfixe d’ID de plugin et l’en-tête CSRF X-EmDash-Request: 1 ajouté automatiquement:
import { usePluginAPI } from "@emdash-cms/admin";
function MyComponent() {
const api = usePluginAPI();
const data = await api.get("status"); // GET /_emdash/api/plugins/<id>/status
await api.post("settings/save", { enabled: true }); // POST with JSON body
const result = await api.get("history?limit=50"); // query params supported
}
Widgets de tableau de bord
Les widgets apparaissent sur le tableau de bord d’administration et fournissent des informations en un coup d’œil.
Définition de widget
Déclarez chaque widget sous admin.widgets avec un id, un titre et une taille:
admin: {
widgets: [
{
id: "seo-overview",
title: "SEO Overview",
size: "half", // "full" | "half" | "third"
},
],
}
Composant de widget
Le composant suivant récupère ses données au montage et affiche un résumé compact:
import { useState, useEffect } from "react";
import { usePluginAPI } from "@emdash-cms/admin";
export function SEOWidget() {
const api = usePluginAPI();
const [data, setData] = useState({ score: 0, issues: [] });
useEffect(() => {
api.get("analyze").then(setData);
}, []);
return (
<div className="widget-content">
<div className="score">{data.score}%</div>
<ul>
{data.issues.map((issue, i) => (
<li key={i}>{(issue as { message: string }).message}</li>
))}
</ul>
</div>
);
}
Tailles de widget
| Size | Description |
|---|---|
full | Largeur complète du tableau de bord |
half | Moitié de la largeur du tableau de bord |
third | Un tiers de la largeur du tableau de bord |
Les widgets s’ajustent automatiquement en fonction de la largeur de l’écran.
Structure d’exportation
Le point d’entrée d’administration exporte deux objets:
import { SettingsPage } from "./components/SettingsPage";
import { ReportsPage } from "./components/ReportsPage";
import { StatusWidget } from "./components/StatusWidget";
import { OverviewWidget } from "./components/OverviewWidget";
export const pages = {
"/settings": SettingsPage,
"/reports": ReportsPage,
};
export const widgets = {
status: StatusWidget,
overview: OverviewWidget,
};
Utilisation des composants d’administration
EmDash fournit des composants prédéfinis pour les motifs courants:
import {
Card,
Button,
Input,
Select,
Toggle,
Table,
Pagination,
Alert,
Loading,
} from "@emdash-cms/admin";
function SettingsPage() {
return (
<Card title="Settings">
<Input label="API Key" type="password" />
<Toggle label="Enabled" defaultChecked />
<Button variant="primary">Save</Button>
</Card>
);
}
Interface utilisateur de paramètres auto-générée
Si votre plugin ne nécessite qu’un formulaire de paramètres, utilisez admin.settingsSchema sans composants personnalisés:
admin: {
settingsSchema: {
apiKey: { type: "secret", label: "API Key" },
enabled: { type: "boolean", label: "Enabled", default: true },
},
},
EmDash génère automatiquement une page de paramètres. Optez pour des pages React personnalisées uniquement lorsque vous avez besoin d’un comportement au-delà d’un formulaire de base.
Navigation
Les pages du plugin apparaissent dans la barre latérale d’administration sous le nom du plugin. L’ordre correspond au tableau admin.pages, comme indiqué ci-dessous:
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // first
{ path: "/history", label: "History", icon: "history" }, // second
{ path: "/reports", label: "Reports", icon: "chart" }, // third
],
}
Configuration de build
Les composants d’administration nécessitent un point d’entrée de build séparé. La configuration de bundler suivante construit à la fois le serveur et les points d’entrée d’administration:
tsdown
export default {
entry: {
index: "src/index.ts",
admin: "src/admin.tsx",
},
format: "esm",
dts: true,
external: ["react", "react-dom", "emdash", "@emdash-cms/admin"],
}; tsup
export default {
entry: ["src/index.ts", "src/admin.tsx"],
format: "esm",
dts: true,
external: ["react", "react-dom", "emdash", "@emdash-cms/admin"],
}; Gardez React et EmDash admin comme dépendances externes pour éviter d’empaqueter des doublons.
Activer/désactiver le plugin
Lorsqu’un plugin est désactivé dans l’administration:
- Les liens de la barre latérale sont masqués.
- Les widgets du tableau de bord ne sont pas rendus.
- Les pages d’administration retournent 404.
- Les hooks backend s’exécutent toujours (pour la sécurité des données).
Les plugins peuvent vérifier leur état activé:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");
Exemple complet
Le plugin suivant définit une page de tableau de bord, une page de paramètres et un widget, avec les points d’entrée de runtime et d’administration dans des fichiers séparés. Le fichier src/index.ts contient le descripteur et le runtime:
import { definePlugin } from "emdash";
import type { PluginDescriptor } from "emdash";
export function analyticsPlugin(): PluginDescriptor {
return {
id: "analytics",
version: "1.0.0",
format: "native",
entrypoint: "@my-org/plugin-analytics",
adminEntry: "@my-org/plugin-analytics/admin",
adminPages: [
{ path: "/dashboard", label: "Dashboard", icon: "chart" },
{ path: "/settings", label: "Settings", icon: "settings" },
],
adminWidgets: [{ id: "events-today", title: "Events Today", size: "third" }],
};
}
export function createPlugin() {
return definePlugin({
id: "analytics",
version: "1.0.0",
capabilities: ["network:request"],
allowedHosts: ["api.analytics.example.com"],
storage: {
events: { indexes: ["type", "createdAt"] },
},
admin: {
entry: "@my-org/plugin-analytics/admin",
settingsSchema: {
trackingId: { type: "string", label: "Tracking ID" },
enabled: { type: "boolean", label: "Enabled", default: true },
},
pages: [
{ path: "/dashboard", label: "Dashboard", icon: "chart" },
{ path: "/settings", label: "Settings", icon: "settings" },
],
widgets: [{ id: "events-today", title: "Events Today", size: "third" }],
},
routes: {
stats: {
handler: async (ctx) => {
const today = new Date().toISOString().split("T")[0];
const count = await ctx.storage.events.count({
createdAt: { gte: today },
});
return { today: count };
},
},
},
});
}
export default createPlugin;
Le fichier src/admin.tsx mappe les chemins de page et les IDs de widget à leurs composants React:
import { EventsWidget } from "./components/EventsWidget";
import { DashboardPage } from "./components/DashboardPage";
import { SettingsPage } from "./components/SettingsPage";
export const widgets = {
"events-today": EventsWidget,
};
export const pages = {
"/dashboard": DashboardPage,
"/settings": SettingsPage,
};