管理パネルUI
プラグインは、カスタムページやダッシュボードウィジェットで管理パネルを拡張できます。これらは、コアの管理機能と共にレンダリングされるReactコンポーネントです。
管理エントリーポイント
Section titled “管理エントリーポイント”管理UIを持つプラグインは、adminエントリーポイントからコンポーネントをエクスポートします:
import { SEOSettingsPage } from "../../plugins/components/SEOSettingsPage";import { SEODashboardWidget } from "../../plugins/components/SEODashboardWidget";
// Dashboard widgetsexport const widgets = { "seo-overview": SEODashboardWidget,};
// Admin pagesexport const pages = { "/settings": SEOSettingsPage,};package.jsonでエントリーポイントを設定します:
json title="package.json"{ "exports": { ".": "./dist/index.js", "./admin": "./dist/admin.js" }}プラグイン定義で参照します:
typescript title="src/index.ts"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" }], },});管理ページは、フックを介してプラグインコンテキストを受け取るReactコンポーネントです。
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", }, ];}ページは/_emdash/admin/plugins/<plugin-id>/<path>にマウントされます。
ページコンポーネント
Section titled “ページコンポーネント”typescript title="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>プラグイン設定</h1>
<label> サイトタイトル <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 })} /> 有効 </label>
<button onClick={handleSave} disabled={saving}> {saving ? "Saving..." : "Save Settings"} </button> </div> );}プラグインAPIフック
Section titled “プラグインAPIフック”usePluginAPI()を使用してプラグインのルートを呼び出します:
import { usePluginAPI } from "@emdash-cms/admin";
function MyComponent() { const api = usePluginAPI();
// プラグインルートへのGETリクエスト const data = await api.get("status");
// ボディ付きPOSTリクエスト await api.post("settings/save", { enabled: true });
// URLパラメータ付き const result = await api.get("history?limit=50");}このフックは、ルートURLに自動的にプラグインIDプレフィックスを追加します。
ダッシュボードウィジェット
Section titled “ダッシュボードウィジェット”ウィジェットは管理ダッシュボードに表示され、一目で情報を提供します。
ウィジェット定義
Section titled “ウィジェット定義”admin.widgetsでウィジェットを定義します:
admin: { widgets: [ { id: "seo-overview", // Unique widget ID title: "SEO Overview", // Widget title (optional) size: "half", // "full" | "half" | "third" }, ];}ウィジェットコンポーネント
Section titled “ウィジェットコンポーネント”typescript title="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> );}ウィジェットサイズ
Section titled “ウィジェットサイズ”| サイズ | 説明 |
|---|---|
full | ダッシュボード全体の幅 |
half | ダッシュボードの半分の幅 |
third | ダッシュボードの1/3の幅 |
ウィジェットは画面幅に基づいて自動的に折り返されます。
エクスポート構造
Section titled “エクスポート構造”管理エントリーポイントは2つのオブジェクトをエクスポートします:
typescript title="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";
// Pages keyed by pathexport const pages = { "/settings": SettingsPage, "/reports": ReportsPage,};
// Widgets keyed by IDexport const widgets = { status: StatusWidget, overview: OverviewWidget,};管理コンポーネントの使用
Section titled “管理コンポーネントの使用”EmDashは一般的なパターン用に事前構築されたコンポーネントを提供します:
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
Section titled “自動生成される設定UI”プラグインが設定フォームのみを必要とする場合は、カスタムコンポーネントなしでadmin.settingsSchemaを使用します:
admin: { settingsSchema: { apiKey: { type: "secret", label: "API Key" }, enabled: { type: "boolean", label: "Enabled", default: true } }}EmDashは設定ページを自動的に生成します。基本的な設定以外の機能にはカスタムページのみを追加してください。
ナビゲーション
Section titled “ナビゲーション”プラグインページは、プラグイン名の下にある管理サイドバーに表示されます。順序は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 ];}管理コンポーネントには別のビルドエントリーポイントが必要です。バンドラーを設定します:
typescript title="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"]};typescript title="tsup.config.ts"export default { entry: ["src/index.ts", "src/admin.tsx"], format: "esm", dts: true, external: ["react", "react-dom", "emdash", "@emdash-cms/admin"]};重複バンドルを避けるため、ReactとEmDash管理を外部依存関係として保持してください。
プラグインの有効化/無効化
Section titled “プラグインの有効化/無効化”プラグインが管理パネルで無効化された場合:
- サイドバーリンクは非表示になります
- ダッシュボードウィジェットはレンダリングされません
- 管理ページは404を返します
- バックエンドフックは(データ安全性のために)引き続き実行されます
プラグインは有効状態を確認できます:
const enabled = await ctx.kv.get<boolean>("_emdash:enabled");例:完全な管理UI
Section titled “例:完全な管理UI”typescript title="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: "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 }; }, }, },});typescript title="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,};