I plugin nativi possono estendere il pannello di amministrazione con pagine React personalizzate e widget del dashboard — i plugin sandbox descrivono la loro UI come Block Kit invece, perché l’invio di JavaScript del plugin nell’admin violerebbe l’isolamento del sandbox.
Se il tuo plugin ha bisogno solo di un modulo di impostazioni, il modulo admin.settingsSchema generato automaticamente (vedi Il tuo primo plugin nativo) copre la maggior parte dei casi senza scrivere alcun React. Ricorri a componenti personalizzati quando hai bisogno di un’interfaccia utente più ricca di quella che settingsSchema fornisce.
Punto di ingresso dell’amministrazione
I plugin con UI di amministrazione esportano oggetti pages e widgets da un punto di ingresso admin:
import { SEOSettingsPage } from "./components/SEOSettingsPage";
import { SEODashboardWidget } from "./components/SEODashboardWidget";
export const widgets = {
"seo-overview": SEODashboardWidget,
};
export const pages = {
"/settings": SEOSettingsPage,
};
Configura il punto di ingresso in package.json:
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}
Referenzialo da 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" }],
},
});
Il descrittore necessita di un adminEntry corrispondente affinché EmDash sappia dove trovare i componenti al momento della compilazione:
adminEntry: "@my-org/plugin-seo/admin",
Pagine di amministrazione
Le pagine di amministrazione sono componenti React montati sotto /_emdash/admin/plugins/<plugin-id>/<path>.
Definizione della pagina
admin: {
pages: [
{
path: "/settings",
label: "Settings",
icon: "settings",
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
],
}
Componente della pagina
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 dell’API del plugin
usePluginAPI() chiama le route del tuo plugin con il prefisso dell’ID del plugin e l’header CSRF X-EmDash-Request: 1 aggiunti automaticamente:
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 con corpo JSON
const result = await api.get("history?limit=50"); // parametri di query supportati
}
Widget del dashboard
I widget appaiono sul dashboard di amministrazione e forniscono informazioni a colpo d’occhio.
Definizione del widget
admin: {
widgets: [
{
id: "seo-overview",
title: "SEO Overview",
size: "half", // "full" | "half" | "third"
},
],
}
Componente del 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>
);
}
Dimensioni del widget
| Size | Description |
|---|---|
full | Larghezza completa del dashboard |
half | Metà larghezza del dashboard |
third | Un terzo della larghezza del dashboard |
I widget si adattano automaticamente in base alla larghezza dello schermo.
Struttura di esportazione
Il punto di ingresso dell’amministrazione esporta due oggetti:
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,
};
Utilizzo di componenti di amministrazione
EmDash fornisce componenti predefiniti per modelli comuni:
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>
);
}
UI di impostazioni generata automaticamente
Se il tuo plugin ha bisogno solo di un modulo di impostazioni, usa admin.settingsSchema senza componenti personalizzati:
admin: {
settingsSchema: {
apiKey: { type: "secret", label: "API Key" },
enabled: { type: "boolean", label: "Enabled", default: true },
},
},
EmDash genera automaticamente una pagina di impostazioni. Ricorri a pagine React personalizzate solo quando hai bisogno di un comportamento oltre un modulo di base.
Navigazione
Le pagine del plugin appaiono nella barra laterale di amministrazione sotto il nome del plugin. L’ordine corrisponde all’array admin.pages.
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // primo
{ path: "/history", label: "History", icon: "history" }, // secondo
{ path: "/reports", label: "Reports", icon: "chart" }, // terzo
],
}
Configurazione di compilazione
I componenti di amministrazione necessitano di un punto di ingresso di compilazione separato. Configura il tuo 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"],
}; Mantieni React ed EmDash Admin come dipendenze esterne per evitare di raggruppare duplicati.
Abilitazione/disabilitazione del plugin
Quando un plugin è disabilitato nell’amministrazione:
- I link della barra laterale sono nascosti.
- I widget del dashboard non vengono renderizzati.
- Le pagine di amministrazione restituiscono 404.
- Gli hook del backend vengono comunque eseguiti (per la sicurezza dei dati).
I plugin possono verificare il loro stato abilitato:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");
Esempio completo
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,
};