Los plugins nativos pueden extender el panel de administración con páginas React personalizadas y widgets del panel de control — los plugins en sandbox describen su interfaz de usuario como Block Kit en su lugar, porque enviar JavaScript del plugin al administrador rompería el aislamiento del sandbox.
Si su plugin solo necesita un formulario de configuración, el formulario admin.settingsSchema generado automáticamente (consulte Su primer plugin nativo) cubre la mayoría de los casos sin escribir ningún React. Recurra a componentes personalizados cuando necesite una interfaz de usuario más rica que la que proporciona settingsSchema.
Punto de entrada de administración
Los plugins con interfaz de usuario de administración exportan objetos pages y widgets desde un punto de entrada admin:
import { SEOSettingsPage } from "./components/SEOSettingsPage";
import { SEODashboardWidget } from "./components/SEODashboardWidget";
export const widgets = {
"seo-overview": SEODashboardWidget,
};
export const pages = {
"/settings": SEOSettingsPage,
};
Configure el punto de entrada en package.json:
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}
Referencie desde 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" }],
},
});
El descriptor necesita un adminEntry coincidente para que EmDash sepa dónde encontrar los componentes en tiempo de compilación:
adminEntry: "@my-org/plugin-seo/admin",
Páginas de administración
Las páginas de administración son componentes React que se montan bajo /_emdash/admin/plugins/<plugin-id>/<path>.
Definición de página
Declare cada página bajo admin.pages con una ruta, etiqueta e icono:
admin: {
pages: [
{
path: "/settings",
label: "Settings",
icon: "settings",
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
],
}
Componente de página
El siguiente componente lee y guarda la configuración a través del hook de la 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 de la API del plugin
usePluginAPI() llama a las rutas de su plugin con el prefijo del ID del plugin y el encabezado CSRF X-EmDash-Request: 1 añadido automáticamente:
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 del panel de control
Los widgets aparecen en el panel de control de administración y proporcionan información de un vistazo.
Definición de widget
Declare cada widget bajo admin.widgets con un id, título y tamaño:
admin: {
widgets: [
{
id: "seo-overview",
title: "SEO Overview",
size: "half", // "full" | "half" | "third"
},
],
}
Componente de widget
El siguiente componente recupera sus datos al montarse y renderiza un resumen compacto:
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>
);
}
Tamaños de widget
| Size | Description |
|---|---|
full | Ancho completo del panel |
half | Mitad del ancho del panel |
third | Un tercio del ancho del panel |
Los widgets se ajustan automáticamente según el ancho de la pantalla.
Estructura de exportación
El punto de entrada de administración exporta dos objetos:
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,
};
Uso de componentes de administración
EmDash proporciona componentes prediseñados para patrones comunes:
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>
);
}
Interfaz de usuario de configuración autogenerada
Si su plugin solo necesita un formulario de configuración, use admin.settingsSchema sin componentes personalizados:
admin: {
settingsSchema: {
apiKey: { type: "secret", label: "API Key" },
enabled: { type: "boolean", label: "Enabled", default: true },
},
},
EmDash genera automáticamente una página de configuración. Recurra a páginas React personalizadas solo cuando necesite un comportamiento más allá de un formulario básico.
Navegación
Las páginas del plugin aparecen en la barra lateral de administración bajo el nombre del plugin. El orden coincide con el array admin.pages, como se muestra a continuación:
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // first
{ path: "/history", label: "History", icon: "history" }, // second
{ path: "/reports", label: "Reports", icon: "chart" }, // third
],
}
Configuración de compilación
Los componentes de administración necesitan un punto de entrada de compilación separado. La siguiente configuración del empaquetador compila tanto el servidor como los puntos de entrada de administración:
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"],
}; Mantenga React y EmDash admin como dependencias externas para evitar empaquetar duplicados.
Habilitar/deshabilitar plugin
Cuando un plugin está deshabilitado en el administrador:
- Los enlaces de la barra lateral están ocultos.
- Los widgets del panel de control no se renderizan.
- Las páginas de administración devuelven 404.
- Los hooks del backend aún se ejecutan (por seguridad de los datos).
Los plugins pueden verificar su estado habilitado:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");
Ejemplo completo
El siguiente plugin define una página de panel de control, una página de configuración y un widget, con los puntos de entrada de tiempo de ejecución y administración en archivos separados. El archivo src/index.ts contiene el descriptor y el tiempo de ejecución:
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;
El archivo src/admin.tsx mapea rutas de página e IDs de widget a sus componentes 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,
};