Os plugins nativos podem estender o painel de administração com páginas React personalizadas e widgets do painel — plugins em sandbox descrevem sua interface de usuário como Block Kit em vez disso, porque enviar JavaScript do plugin para o administrador quebraria o isolamento do sandbox.
Se o seu plugin precisa apenas de um formulário de configurações, o formulário admin.settingsSchema gerado automaticamente (veja Seu primeiro plugin nativo) cobre a maioria dos casos sem escrever nenhum React. Recorra a componentes personalizados quando você precisar de uma interface de usuário mais rica do que settingsSchema fornece.
Ponto de entrada de administração
Plugins com interface de usuário de administração exportam objetos pages e widgets de um ponto 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 o ponto de entrada em package.json:
{
"exports": {
".": "./dist/index.js",
"./admin": "./dist/admin.js"
}
}
Referencie de 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" }],
},
});
O descritor precisa de um adminEntry correspondente para que o EmDash saiba onde encontrar os componentes no momento da compilação:
adminEntry: "@my-org/plugin-seo/admin",
Páginas de administração
As páginas de administração são componentes React que são montados sob /_emdash/admin/plugins/<plugin-id>/<path>.
Definição de página
Declare cada página sob admin.pages com um caminho, rótulo e ícone:
admin: {
pages: [
{
path: "/settings",
label: "Settings",
icon: "settings",
},
{
path: "/reports",
label: "Reports",
icon: "chart",
},
],
}
Componente de página
O seguinte componente lê e salva configurações através do hook da API do 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 da API do plugin
usePluginAPI() chama as rotas do seu plugin com o prefixo do ID do plugin e o cabeçalho CSRF X-EmDash-Request: 1 adicionado 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
}
Widgets do painel
Os widgets aparecem no painel de administração e fornecem informações de relance.
Definição de widget
Declare cada widget sob admin.widgets com um id, título e tamanho:
admin: {
widgets: [
{
id: "seo-overview",
title: "SEO Overview",
size: "half", // "full" | "half" | "third"
},
],
}
Componente de widget
O seguinte componente busca seus dados na montagem e renderiza um resumo 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>
);
}
Tamanhos de widget
| Size | Description |
|---|---|
full | Largura completa do painel |
half | Metade da largura do painel |
third | Um terço da largura do painel |
Os widgets se ajustam automaticamente com base na largura da tela.
Estrutura de exportação
O ponto de entrada de administração exporta dois 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,
};
Usando componentes de administração
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>
);
}
Interface de usuário de configurações gerada automaticamente
Se o seu plugin precisa apenas 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 uma página de configurações automaticamente. Recorra a páginas React personalizadas apenas quando você precisar de um comportamento além de um formulário básico.
Navegação
As páginas do plugin aparecem na barra lateral de administração sob o nome do plugin. A ordem corresponde ao array admin.pages, conforme mostrado abaixo:
admin: {
pages: [
{ path: "/settings", label: "Settings", icon: "settings" }, // first
{ path: "/history", label: "History", icon: "history" }, // second
{ path: "/reports", label: "Reports", icon: "chart" }, // third
],
}
Configuração de build
Os componentes de administração precisam de um ponto de entrada de build separado. A seguinte configuração do bundler constrói tanto o servidor quanto os pontos de entrada de administração:
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 o React e o EmDash admin como dependências externas para evitar empacotar duplicatas.
Ativar/desativar plugin
Quando um plugin é desativado no administrador:
- Os links da barra lateral ficam ocultos.
- Os widgets do painel não são renderizados.
- As páginas de administração retornam 404.
- Os hooks do backend ainda são executados (para segurança dos dados).
Os plugins podem verificar seu estado ativado:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");
Exemplo completo
O seguinte plugin define uma página de painel, uma página de configurações e um widget, com os pontos de entrada de runtime e administração em arquivos separados. O arquivo src/index.ts contém o descritor e o 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;
O arquivo src/admin.tsx mapeia caminhos de página e ids de widget para seus 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,
};