I plugin nativi possono estendere il pannello di amministrazione con pagine React personalizzate e widget della dashboard — i plugin sandboxed descrivono invece la loro interfaccia utente come Block Kit, perché l’invio di JavaScript del plugin nell’amministratore violerebbe l’isolamento della sandbox.
Se il tuo plugin necessita 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 React. Ricorri ai componenti personalizzati quando hai bisogno di un’interfaccia utente più ricca di quella fornita da settingsSchema.
Punto di ingresso dell’amministrazione
I plugin con interfaccia utente 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"
}
}
Fai riferimento 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 in modo che 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 che vengono montati sotto /_emdash/admin/plugins/<plugin-id>/<path>.
Definizione della pagina
Dichiara ogni pagina sotto admin.pages con un percorso, un’etichetta e un’icona:
admin: {
pages: [
{
path: "/settings",
label: "Settings",
icon: "settings",
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
],
}
Componente della pagina
Il seguente componente legge e salva le impostazioni tramite l’hook dell’API del 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 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 aggiunto 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 with JSON body
const result = await api.get("history?limit=50"); // query params supported
}
Widget della dashboard
I widget appaiono sulla dashboard di amministrazione e forniscono informazioni a colpo d’occhio.
Definizione del widget
Dichiara ogni widget sotto admin.widgets con un id, un titolo e una dimensione:
admin: {
widgets: [
{
id: "seo-overview",
title: "SEO Overview",
size: "half", // "full" | "half" | "third"
},
],
}
Componente del widget
Il seguente componente recupera i suoi dati al montaggio e rende un riepilogo compatto:
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 dei widget
| Size | Description |
|---|---|
full | Larghezza completa della dashboard |
half | Metà della larghezza della dashboard |
third | Un terzo della larghezza della 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 dei componenti di amministrazione
EmDash fornisce componenti predefiniti per pattern 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>
);
}
Interfaccia utente delle impostazioni auto-generata
Se il tuo plugin necessita 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 alle 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, come mostrato di seguito:
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // first
{ path: "/history", label: "History", icon: "history" }, // second
{ path: "/reports", label: "Reports", icon: "chart" }, // third
],
}
Configurazione di build
I componenti di amministrazione necessitano di un punto di ingresso di build separato. La seguente configurazione del bundler compila sia il server che i punti di ingresso di amministrazione:
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 impacchettare duplicati.
Abilitare/disabilitare il plugin
Quando un plugin è disabilitato nell’amministrazione:
- I collegamenti della barra laterale sono nascosti.
- I widget della 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
Il seguente plugin definisce una pagina dashboard, una pagina di impostazioni e un widget, con i punti di ingresso di runtime e amministrazione in file separati. Il file src/index.ts contiene il descrittore e il 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;
Il file src/admin.tsx mappa i percorsi delle pagine e gli ID dei widget ai loro componenti 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,
};