Ir al contenido

Interfaz de administración

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 de administración.

Los plugins con interfaz de usuario de administración exportan componentes desde un punto de entrada admin:

src/admin.tsx
import { SEOSettingsPage } from "../../plugins/components/SEOSettingsPage";
import { SEODashboardWidget } from "../../plugins/components/SEODashboardWidget";
// Widgets del panel
export const widgets = {
"seo-overview": SEODashboardWidget,
};
// Paginas de administracion
export const pages = {
"/settings": SEOSettingsPage,
};

Configura el punto de entrada en package.json:

package.json
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}

Haz referencia a él en tu definición del plugin:

src/index.ts
definePlugin({
id: "seo",
version: "1.0.0",
admin: {
entry: "@my-org/plugin-seo/admin",
pages: [{ path: "/settings", label: "Ajustes SEO", icon: "settings" }],
widgets: [{ id: "seo-overview", title: "Resumen SEO", size: "half" }],
},
});

Las páginas de administración son componentes de React que reciben el contexto del plugin mediante hooks.

Define las páginas en admin.pages:

admin: {
pages: [
{
path: "/settings", // Ruta URL relativa a la base del plugin
label: "Ajustes", // Etiqueta de la barra lateral
icon: "settings", // Nombre del icono (opcional)
},
{
path: "/reports",
label: "Informes",
icon: "chart",
},
];
}

Las páginas se montan en /_emdash/admin/plugins/<plugin-id>/<path>.

src/components/SettingsPage.tsx
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>Configuración del Plugin</h1>
<label>
Título del Sitio
<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 })}
/>
Habilitado
</label>
<button onClick={handleSave} disabled={saving}>
{saving ? "Guardando..." : "Guardar ajustes"}
</button>
</div>
);
}

Usa usePluginAPI() para llamar a las rutas de tu plugin:

import { usePluginAPI } from "@emdash-cms/admin";
function MyComponent() {
const api = usePluginAPI();
// Solicitud GET a la ruta del plugin
const data = await api.get("status");
// Solicitud POST con cuerpo
await api.post("settings/save", { enabled: true });
// Con parámetros de URL
const result = await api.get("history?limit=50");
}

El hook agrega automáticamente el prefijo del ID del plugin a las URLs de las rutas.

Los widgets aparecen en el panel de administración y muestran información de un vistazo.

Define los widgets en admin.widgets:

admin: {
widgets: [
{
id: "seo-overview", // ID único del widget
title: "Resumen SEO", // Título del widget (opcional)
size: "half", // "full" | "half" | "third"
},
];
}
src/components/SEOWidget.tsx
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ñoDescripción
fullAncho completo del dashboard
halfMitad del ancho del dashboard
thirdUn tercio del ancho del dashboard

Los widgets se ajustan automáticamente según el ancho de la pantalla.

El punto de entrada de administración exporta dos objetos:

src/admin.tsx
import { SettingsPage } from "../../plugins/components/SettingsPage";
import { ReportsPage } from "../../plugins/components/ReportsPage";
import { StatusWidget } from "../../plugins/components/StatusWidget";
import { OverviewWidget } from "../../plugins/components/OverviewWidget";
// Páginas indexadas por ruta
export const pages = {
"/settings": SettingsPage,
"/reports": ReportsPage,
};
// Widgets indexados por ID
export const widgets = {
status: StatusWidget,
overview: OverviewWidget,
};

EmDash proporciona componentes preconstruidos para patrones comunes:

import {
Card,
Button,
Input,
Select,
Toggle,
Table,
Pagination,
Alert,
Loading
} from "@emdash-cms/admin";
function SettingsPage() {
return (
<Card title="Ajustes">
<Input label="Clave API" type="password" />
<Toggle label="Activado" defaultChecked />
<Button variant="primary">Guardar</Button>
</Card>
);
}

Interfaz de configuración generada automáticamente

Sección titulada «Interfaz de configuración generada automáticamente»

Si tu plugin solo necesita un formulario de configuración, usa admin.settingsSchema sin componentes personalizados:

admin: {
settingsSchema: {
apiKey: { type: "secret", label: "Clave API" },
enabled: { type: "boolean", label: "Activado", default: true }
}
}

EmDash genera una página de configuración automáticamente. Agrega páginas personalizadas solo para funcionalidades más allá de la configuración básica.

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.

admin: {
pages: [
{ path: "/settings", label: "Ajustes", icon: "settings" }, // Primero
{ path: "/history", label: "Historial", icon: "history" }, // Segundo
{ path: "/reports", label: "Informes", icon: "chart" }, // Tercero
];
}

Los componentes de administración necesitan un punto de entrada de compilación separado. Configura tu bundler:

tsdown.config.ts
export default {
entry: {
index: "src/index.ts",
admin: "src/admin.tsx"
},
format: "esm",
dts: true,
external: ["react", "react-dom", "emdash", "@emdash-cms/admin"]
};

Mantén React y @emdash-cms/admin como dependencias externas para evitar duplicados en el bundle.

Cuando un plugin está deshabilitado en la administración:

  • 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 aún se ejecutan (por seguridad de datos)

Los plugins pueden verificar su estado de habilitación:

const enabled = await ctx.kv.get<boolean>("_emdash:enabled");

Ejemplo: interfaz de administración completa

Sección titulada «Ejemplo: interfaz de administración completa»
src/index.ts
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: "ID de seguimiento" },
enabled: { type: "boolean", label: "Activado", default: true },
},
pages: [
{ path: "/dashboard", label: "Panel", icon: "chart" },
{ path: "/settings", label: "Ajustes", icon: "settings" },
],
widgets: [{ id: "events-today", title: "Eventos de hoy", 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 };
},
},
},
});
src/admin.tsx
import { EventsWidget } from "../../plugins/components/EventsWidget";
import { DashboardPage } from "../../plugins/components/DashboardPage";
import { SettingsPage } from "../../plugins/components/SettingsPage";
export const widgets = {
"events-today": EventsWidget,
};
export const pages = {
"/dashboard": DashboardPage,
"/settings": SettingsPage,
};