import { useEffect, useState, useCallback } from 'react'; import { Table, Button, Space, Tag, message, Upload, Modal, Input, Drawer, Descriptions, Popconfirm, Form, Tabs, theme, } from 'antd'; import { UploadOutlined, PlayCircleOutlined, PauseCircleOutlined, CloudDownloadOutlined, DeleteOutlined, ReloadOutlined, HeartOutlined, SettingOutlined, } from '@ant-design/icons'; import type { PluginInfo, PluginStatus, PluginSchemaResponse } from '../api/plugins'; import { listPlugins, uploadPlugin, installPlugin, enablePlugin, disablePlugin, uninstallPlugin, purgePlugin, getPluginHealth, getPluginSchema, updatePluginConfig, } from '../api/plugins'; import PluginSettingsForm from '../components/PluginSettingsForm'; const STATUS_CONFIG: Record = { uploaded: { color: '#475569', label: '已上传' }, installed: { color: '#2563EB', label: '已安装' }, enabled: { color: '#059669', label: '已启用' }, running: { color: '#059669', label: '运行中' }, disabled: { color: '#dc2626', label: '已禁用' }, uninstalled: { color: '#9333EA', label: '已卸载' }, }; export default function PluginAdmin() { const [plugins, setPlugins] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [uploadModalOpen, setUploadModalOpen] = useState(false); const [manifestText, setManifestText] = useState(''); const [wasmFile, setWasmFile] = useState(null); const [detailPlugin, setDetailPlugin] = useState(null); const [schemaData, setSchemaData] = useState(null); const [healthDetail, setHealthDetail] = useState | null>(null); const [actionLoading, setActionLoading] = useState(null); const { token } = theme.useToken(); const fetchPlugins = useCallback(async (p = page) => { setLoading(true); try { const result = await listPlugins(p); setPlugins(result.data); setTotal(result.total); } catch { message.error('加载插件列表失败'); } setLoading(false); }, [page]); useEffect(() => { fetchPlugins(); }, [fetchPlugins]); // 打开详情时加载 schema(含 settings) useEffect(() => { if (!detailPlugin) { setSchemaData(null); return; } getPluginSchema(detailPlugin.id) .then(setSchemaData) .catch(() => setSchemaData(null)); }, [detailPlugin]); const handleUpload = async () => { if (!wasmFile || !manifestText.trim()) { message.warning('请选择 WASM 文件并填写 Manifest'); return; } try { await uploadPlugin(wasmFile, manifestText); message.success('插件上传成功'); setUploadModalOpen(false); setWasmFile(null); setManifestText(''); fetchPlugins(); } catch { message.error('插件上传失败'); } }; const handleAction = async (id: string, action: () => Promise, label: string) => { setActionLoading(id); try { await action(); message.success(`${label}成功`); fetchPlugins(); if (detailPlugin?.id === id) { setDetailPlugin(null); } } catch { message.error(`${label}失败`); } setActionLoading(null); }; const handleHealthCheck = async (id: string) => { try { const result = await getPluginHealth(id); setHealthDetail(result.details); } catch { message.error('健康检查失败'); } }; const getActions = (record: PluginInfo) => { const id = record.id; const btns: React.ReactNode[] = []; switch (record.status) { case 'uploaded': btns.push( , ); break; case 'installed': btns.push( , ); break; case 'enabled': case 'running': btns.push( , ); break; case 'disabled': btns.push( , , ); break; } return btns; }; const columns = [ { title: '名称', dataIndex: 'name', key: 'name', width: 180 }, { title: '版本', dataIndex: 'version', key: 'version', width: 80 }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: PluginStatus) => { const cfg = STATUS_CONFIG[status] || { color: '#475569', label: status }; return {cfg.label}; }, }, { title: '作者', dataIndex: 'author', key: 'author', width: 120 }, { title: '描述', dataIndex: 'description', key: 'description', ellipsis: true, }, { title: '操作', key: 'action', width: 320, render: (_: unknown, record: PluginInfo) => ( {getActions(record)} handleAction(record.id, async () => { await purgePlugin(record.id); return record; }, '清除')} > ), }, ]; return (
setPage(p), showTotal: (t) => `共 ${t} 个插件`, }} /> setUploadModalOpen(false)} okText="上传" width={600} >
{ setWasmFile(file); return false; }} maxCount={1} accept=".wasm" fileList={[]} onRemove={() => setWasmFile(null)} > setManifestText(e.target.value)} placeholder="[metadata] id = "my-plugin" name = "我的插件" version = "0.1.0"" />
{ setDetailPlugin(null); setHealthDetail(null); setSchemaData(null); }} width={500} > {detailPlugin && ( {detailPlugin.id} {detailPlugin.name} {detailPlugin.version} {STATUS_CONFIG[detailPlugin.status]?.label || detailPlugin.status} {detailPlugin.author || '-'} {detailPlugin.description || '-'} {detailPlugin.installed_at || '-'} {detailPlugin.enabled_at || '-'} {detailPlugin.entities.length}
{healthDetail && (
                          {JSON.stringify(healthDetail, null, 2)}
                        
)}
), }, ...(schemaData?.settings ? [ { key: 'settings', label: ( 配置 ), children: ( } recordVersion={detailPlugin.record_version} onSave={async (config, version) => { const updated = await updatePluginConfig( detailPlugin.id, config, version, ); setDetailPlugin({ ...detailPlugin, ...updated }); }} readOnly={detailPlugin.status !== 'enabled' && detailPlugin.status !== 'running'} /> ), }, ] : []), ]} /> )}
); }