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 décrivent leur interface utilisateur en tant que Block Kit à la place, car l’envoi de JavaScript de plugin dans l’admin briserait l’isolation du sandbox.
Si votre plugin n’a besoin que d’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. Utilisez 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 montés sous /_emdash/admin/plugins/<plugin-id>/<path>.
Définition de page
admin: {
pages: [
{
path: "/settings",
label: "Settings",
icon: "settings",
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
],
}
Composant de page
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 d’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 avec corps JSON
const result = await api.get("history?limit=50"); // paramètres de requête supportés
}
Widgets du 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
admin: {
widgets: [
{
id: "seo-overview",
title: "SEO Overview",
size: "half", // "full" | "half" | "third"
},
],
}
Composant de widget
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 largeur du tableau de bord |
third | Un tiers de 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 de composants d’administration
EmDash fournit des composants pré-construits pour les modèles 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 générée automatiquement
Si votre plugin n’a besoin que d’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. N’utilisez des pages React personnalisées que lorsque vous avez besoin d’un comportement au-delà d’un formulaire de base.
Navigation
Les pages de plugin apparaissent dans la barre latérale d’administration sous le nom du plugin. L’ordre correspond au tableau admin.pages.
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // premier
{ path: "/history", label: "History", icon: "history" }, // deuxième
{ path: "/reports", label: "Reports", icon: "chart" }, // troisième
],
}
Configuration de compilation
Les composants d’administration nécessitent un point d’entrée de compilation séparé. Configurez votre bundler :
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 de regrouper des doublons.
Activation/désactivation du 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 du 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
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;
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,
};