Los plugins pueden ampliar el panel de administración con páginas personalizadas y widgets del panel. Son componentes de React que se renderizan junto con la funcionalidad principal del admin.
Punto de entrada del admin
Los plugins con UI de administración exportan componentes desde un punto de entrada admin:
import { SEOSettingsPage } from "./components/SEOSettingsPage";
import { SEODashboardWidget } from "./components/SEODashboardWidget";
// Dashboard widgets
export const widgets = {
"seo-overview": SEODashboardWidget,
};
// Admin pages
export const pages = {
"/settings": SEOSettingsPage,
};
Configura el punto de entrada en package.json:
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}
Referencia en la definición del plugin:
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" }],
},
});
Páginas de administración
Las páginas de administración son componentes de React que reciben el contexto del plugin mediante hooks.
Definición de página
Define las páginas en admin.pages:
admin: {
pages: [
{
path: "/settings", // URL path (relative to plugin base)
label: "Settings", // Sidebar label
icon: "settings", // Icon name (optional)
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
];
}
Las páginas se montan en /_emdash/admin/plugins/<plugin-id>/<path>.
Componente de página
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 || ""}
onChange={(e) => setSettings({ ...settings, siteTitle: e.target.value })}
/>
</label>
<label>
<input
type="checkbox"
checked={settings.enabled ?? true}
onChange={(e) => setSettings({ ...settings, enabled: e.target.checked })}
/>
Enabled
</label>
<button onClick={handleSave} disabled={saving}>
{saving ? "Saving..." : "Save Settings"}
</button>
</div>
);
}
Hook de la API del plugin
Usa usePluginAPI() para llamar a las rutas de tu plugin:
import { usePluginAPI } from "@emdash-cms/admin";
function MyComponent() {
const api = usePluginAPI();
// GET request to plugin route
const data = await api.get("status");
// POST request with body
await api.post("settings/save", { enabled: true });
// With URL parameters
const result = await api.get("history?limit=50");
}
El hook añade automáticamente el prefijo del ID del plugin a las URLs de las rutas.
Widgets del panel
Los widgets aparecen en el panel de administración y ofrecen información de un vistazo.
Definición del widget
Define los widgets en admin.widgets:
admin: {
widgets: [
{
id: "seo-overview", // Unique widget ID
title: "SEO Overview", // Widget title (optional)
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.message}</li>
))}
</ul>
</div>
);
}
Tamaños de widget
| Tamaño | Descripción |
|---|---|
full | Ancho completo del panel |
half | Mitad del ancho del panel |
third | Un tercio del ancho del panel |
Los widgets se reordenan automáticamente según el ancho de pantalla.
Estructura de exportación
El punto de entrada del admin exporta dos objetos:
import { SettingsPage } from "./components/SettingsPage";
import { ReportsPage } from "./components/ReportsPage";
import { StatusWidget } from "./components/StatusWidget";
import { OverviewWidget } from "./components/OverviewWidget";
// Pages keyed by path
export const pages = {
"/settings": SettingsPage,
"/reports": ReportsPage,
};
// Widgets keyed by ID
export const widgets = {
status: StatusWidget,
overview: OverviewWidget,
};
Uso de componentes de administración
EmDash proporciona componentes predefinidos para patrones habituales:
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 de ajustes generada automáticamente
Si tu plugin solo necesita un formulario de ajustes, usa admin.settingsSchema sin componentes personalizados:
admin: {
settingsSchema: {
apiKey: { type: "secret", label: "API Key" },
enabled: { type: "boolean", label: "Enabled", default: true }
}
}
EmDash genera la página de ajustes automáticamente. Añade páginas personalizadas solo para funcionalidad más allá de los ajustes básicos.
Navegación
Las páginas del plugin aparecen en la barra lateral del admin bajo el nombre del plugin. El orden coincide con el array admin.pages.
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 build
Los componentes de administración necesitan un punto de entrada de build separado. Configura tu 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"]
}; Mantén React y EmDash admin como dependencias externas para evitar duplicar bundles.
Activar/desactivar plugin
Cuando un plugin está desactivado en el admin:
- Los enlaces de la barra lateral están ocultos
- Los widgets del panel no se renderizan
- Las páginas de administración devuelven 404
- Los hooks del backend siguen ejecutándose (por seguridad de los datos)
Los plugins pueden comprobar si están activados:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");
Ejemplo: admin UI completa
import { definePlugin } from "emdash";
export default definePlugin({
id: "analytics",
version: "1.0.0",
capabilities: ["network:fetch"],
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 };
},
},
},
});
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,
};