Os plugins podem estender o painel de administração com páginas personalizadas e widgets do painel. São componentes React que renderizam junto com as funcionalidades core do admin.
Ponto de entrada do admin
Os plugins com UI de admin exportam componentes a partir de um ponto de entrada admin:
import { SEOSettingsPage } from "./components/SEOSettingsPage";
import { SEODashboardWidget } from "./components/SEODashboardWidget";
// Widgets do painel
export const widgets = {
"seo-overview": SEODashboardWidget,
};
// Páginas de admin
export const pages = {
"/settings": SEOSettingsPage,
};
Configure o ponto de entrada no package.json:
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}
Referencie-o na definição do 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 admin
As páginas de admin são componentes React que recebem o contexto do plugin via hooks.
Definição de página
Defina páginas em admin.pages:
admin: {
pages: [
{
path: "/settings", // Caminho URL (relativo à base do plugin)
label: "Settings", // Rótulo na barra lateral
icon: "settings", // Nome do ícone (opcional)
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
];
}
As páginas montam-se em /_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 da API do plugin
Use usePluginAPI() para chamar as rotas do plugin:
import { usePluginAPI } from "@emdash-cms/admin";
function MyComponent() {
const api = usePluginAPI();
// Pedido GET para rota do plugin
const data = await api.get("status");
// Pedido POST com corpo
await api.post("settings/save", { enabled: true });
// Com parâmetros de URL
const result = await api.get("history?limit=50");
}
O hook adiciona automaticamente o prefixo do ID do plugin aos URLs das rotas.
Widgets do painel
Os widgets aparecem no painel de administração e fornecem informação de relance.
Definição de widget
Defina widgets em admin.widgets:
admin: {
widgets: [
{
id: "seo-overview", // ID único do widget
title: "SEO Overview", // Título do widget (opcional)
size: "half", // "full" | "half" | "third"
},
];
}
Componente de 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>
);
}
Tamanhos de widget
| Tamanho | Descrição |
|---|---|
full | Largura total do painel |
half | Metade da largura do painel |
third | Um terço da largura do painel |
Os widgets ajustam-se automaticamente conforme a largura do ecrã.
Estrutura de exportação
O ponto de entrada admin exporta dois objetos:
import { SettingsPage } from "./components/SettingsPage";
import { ReportsPage } from "./components/ReportsPage";
import { StatusWidget } from "./components/StatusWidget";
import { OverviewWidget } from "./components/OverviewWidget";
// Páginas indexadas por caminho
export const pages = {
"/settings": SettingsPage,
"/reports": ReportsPage,
};
// Widgets indexados por ID
export const widgets = {
status: StatusWidget,
overview: OverviewWidget,
};
Usar componentes de admin
O EmDash fornece componentes pré-construídos para padrões comuns:
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 configurações gerada automaticamente
Se o plugin só precisa de um formulário de configurações, use admin.settingsSchema sem componentes personalizados:
admin: {
settingsSchema: {
apiKey: { type: "secret", label: "API Key" },
enabled: { type: "boolean", label: "Enabled", default: true }
}
}
O EmDash gera automaticamente uma página de configurações. Adicione páginas personalizadas apenas para funcionalidades além de configurações básicas.
Navegação
As páginas de plugin aparecem na barra lateral do admin sob o nome do plugin. A ordem corresponde ao array admin.pages.
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // Primeiro
{ path: "/history", label: "History", icon: "history" }, // Segundo
{ path: "/reports", label: "Reports", icon: "chart" }, // Terceiro
];
}
Configuração de build
Os componentes de admin precisam de um ponto de entrada de build separado. Configure o 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"]
}; Mantenha React e EmDash admin como dependências externas para evitar duplicação no bundle.
Ativar/desativar plugin
Quando um plugin é desativado no admin:
- Os links na barra lateral ficam ocultos
- Os widgets do painel não são renderizados
- As páginas de admin devolvem 404
- Os hooks do backend continuam a executar (por segurança dos dados)
Os plugins podem verificar o seu estado de ativação:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");
Exemplo: UI de admin 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,
};