Pages d'administration React et widgets

Sur cette page

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

SizeDescription
fullLargeur complète du tableau de bord
halfMoitié de largeur du tableau de bord
thirdUn 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.

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,
};