import { Boxes, Eye, Filter, PlugZap, Search, Sparkles } from "lucide-react"; import { type ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { GamePluginStatus, MarketplacePluginFilterRequest, MarketplacePluginResponse, MarketplacePluginStateAction } from "../api/types"; import { ConfirmDialog, ManagementDialog } from "../components/OperationControls"; import { PluginLifecycleWorkbench } from "../components/PluginLifecycleWorkbench"; import { RuntimeDLLExtensionsPanel } from "../components/RuntimeDLLExtensionsPanel"; import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews"; import { PageFrame } from "../components/PageFrame"; import type { PageComponentProps } from "../contracts/page"; import { isPlatformAdmin } from "../contracts/workspace"; import { cx } from "../utils/classes"; type ListState = "loading" | "ready" | "error"; type StatusFilter = GamePluginStatus | "all"; interface PluginsPageInitialState { listState?: ListState; listError?: string; plugins?: MarketplacePluginResponse[]; selectedId?: string; detail?: MarketplacePluginResponse | null; usingFallback?: boolean; actionResult?: { status: "succeeded" | "failed"; label: string } | null; } interface PluginsPageProps extends Partial { initialState?: PluginsPageInitialState; } const statusFilters: Array<{ id: StatusFilter; label: string }> = [ { id: "all", label: "全部" }, { id: "installed", label: "已安装" }, { id: "disabled", label: "停用" }, { id: "invalid", label: "异常" }, { id: "updating", label: "更新中" } ]; export function PluginsPage({ initialState, session, operations }: PluginsPageProps = {}) { const [listState, setListState] = useState(initialState?.listState ?? "loading"); const [listError, setListError] = useState(initialState?.listError ?? ""); const [plugins, setPlugins] = useState(initialState?.plugins ?? []); const [selectedId, setSelectedId] = useState(initialState?.selectedId ?? ""); const [detail, setDetail] = useState(initialState?.detail ?? null); const [detailPending, setDetailPending] = useState(false); const [detailError, setDetailError] = useState(""); const [keyword, setKeyword] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [serverType, setServerType] = useState(""); const [capability, setCapability] = useState(""); const [actionPending, setActionPending] = useState(null); const [confirmAction, setConfirmAction] = useState(null); const [actionResult, setActionResult] = useState<{ status: "succeeded" | "failed"; label: string } | null>(initialState?.actionResult ?? null); const [usingFallback, setUsingFallback] = useState(initialState?.usingFallback ?? false); const refresh = useCallback(async () => { setListState("loading"); setListError(""); const filter: MarketplacePluginFilterRequest = { keyword: keyword.trim() || undefined, status: statusFilter, serverType: serverType.trim() || undefined, capability: capability.trim() || undefined }; if (initialState?.plugins) { setListState(initialState.listState ?? "ready"); setListError(initialState.listError ?? ""); setPlugins(initialState.plugins); setSelectedId(initialState.selectedId ?? ""); setDetail(initialState.detail ?? null); setUsingFallback(initialState.usingFallback ?? false); return; } try { const response = await platformApiClient.listMarketplacePlugins(filter); setPlugins(response.items); setSelectedId((current) => (current && response.items.some((plugin) => plugin.id === current) ? current : "")); setUsingFallback(false); setListState("ready"); } catch (error) { setListState("error"); setListError(error instanceof Error ? error.message : "插件市场加载失败"); setPlugins([]); setDetail(null); } }, [capability, initialState, keyword, serverType, statusFilter]); useEffect(() => { void refresh(); }, [refresh]); useEffect(() => { if (!selectedId) { setDetail(null); setDetailError(""); return; } const selectedSummary = plugins.find((plugin) => plugin.id === selectedId) ?? null; if (usingFallback) { setDetail(selectedSummary); setDetailError(""); return; } setDetailPending(true); setDetailError(""); platformApiClient .getMarketplacePlugin(selectedId) .then((plugin) => setDetail(plugin)) .catch((error: unknown) => { setDetail(selectedSummary); setDetailError(error instanceof Error ? error.message : "插件详情加载失败"); }) .finally(() => setDetailPending(false)); }, [plugins, selectedId, usingFallback]); const retryDetail = useCallback(async () => { if (!selectedId || usingFallback) { return; } setDetailPending(true); setDetailError(""); try { const plugin = await platformApiClient.getMarketplacePlugin(selectedId); setDetail(plugin); } catch (error) { setDetailError(error instanceof Error ? error.message : "插件详情加载失败"); } finally { setDetailPending(false); } }, [selectedId, usingFallback]); const serverTypes = useMemo(() => unique(plugins.map((plugin) => plugin.serverType)), [plugins]); const capabilities = useMemo(() => unique(plugins.flatMap((plugin) => [...plugin.capabilities, ...plugin.bridgeActions])), [plugins]); const installedCount = plugins.filter((plugin) => plugin.status === "installed").length; const bridgeActionCount = plugins.reduce((sum, plugin) => sum + plugin.bridgeActions.length, 0); const invalidCount = plugins.filter((plugin) => plugin.status === "invalid").length; function updateSelect(event: ChangeEvent) { const { name, value } = event.target; if (name === "serverType") { setServerType(value); } else if (name === "capability") { setCapability(value); } } async function changeState(action: MarketplacePluginStateAction) { if (!detail || usingFallback) { return; } const intent = `${stateActionLabel(action)}插件`; const operationId = operations?.begin({ intent, targetKind: "plugin", targetId: detail.id, requester: session?.displayName }); setActionPending(action); setActionResult(null); try { const updated = await platformApiClient.setMarketplacePluginState(detail.id, { action }); setDetail(updated); setPlugins((current) => current.map((plugin) => (plugin.id === updated.id ? updated : plugin))); setActionResult({ status: "succeeded", label: `${updated.name} 已${stateActionLabel(action)}` }); if (operationId) operations?.succeed(operationId, `${updated.name} 已${stateActionLabel(action)}`); setConfirmAction(null); } catch (error) { const reason = error instanceof Error ? error.message : "状态更新失败"; setActionResult({ status: "failed", label: reason }); if (operationId) operations?.fail(operationId, reason, operationId); } finally { setActionPending(null); } } function requestStateChange(action: MarketplacePluginStateAction) { if (!canManage || usingFallback || actionPending !== null) { return; } setActionResult(null); setConfirmAction(action); } const canManage = Boolean(session && isPlatformAdmin(session)); return (
0 ? "warning" : "success" } ]} />
{usingFallback && } {!canManage && !usingFallback && } {actionResult && } {listState === "loading" && } {listState === "error" && void refresh()} />} {listState === "ready" && plugins.length === 0 && ( } title="暂无匹配插件" description="调整搜索、状态、服务器类型或能力筛选后再试。" actionLabel="清除筛选" onAction={() => { setKeyword(""); setStatusFilter("all"); setServerType(""); setCapability(""); }} /> )} {plugins.length > 0 && (
{plugins.map((plugin) => (

{plugin.name}

{plugin.id}
{statusLabel(plugin.status)}

{plugin.description || plugin.serverDisplayName || plugin.serverType}

版本
{plugin.version}
服务器类型
{plugin.serverType}
能力
{plugin.capabilities.slice(0, 3).join(", ") || "--"}
桥接
{plugin.bridgeActions.slice(0, 3).join(", ") || "--"}
))}
)} { if (actionPending === null) { setSelectedId(""); setDetail(null); setDetailError(""); setConfirmAction(null); } }}>
{detailPending && } {detailError && void retryDetail()} compact />} {detail && }
{ if (actionPending === null) setConfirmAction(null); }} onConfirm={() => { if (confirmAction) void changeState(confirmAction); }} />
); } interface PluginDetailProps { plugin: MarketplacePluginResponse; actionPending: MarketplacePluginStateAction | null; actionsDisabled: boolean; onAction: (action: MarketplacePluginStateAction) => void; } function PluginDetail({ plugin, actionPending, actionsDisabled, onAction }: PluginDetailProps) { return (

{plugin.name}

{plugin.manifestRef}
{statusLabel(plugin.status)}
能力
{plugin.capabilities.join(", ") || "--"}
桥接动作
{plugin.bridgeActions.join(", ") || "--"}
插件页面
{plugin.pages.map((page) => `${page.title}(${page.key})`).join(", ") || "--"}
校验
{plugin.validationViolations?.length ? plugin.validationViolations.join(", ") : "manifest validated"}
); } function DetailStat({ label, value }: { label: string; value: string }) { return ( {label} {value} ); } function unique(values: string[]): string[] { return Array.from(new Set(values.filter(Boolean))).sort((left, right) => left.localeCompare(right)); } function statusClass(status: GamePluginStatus): string { if (status === "installed") { return "status-active"; } if (status === "invalid") { return "status-error"; } return "status-disabled"; } function statusLabel(status: string): string { return status === "installed" ? "已安装" : status === "disabled" ? "停用" : status === "invalid" ? "异常" : status === "updating" ? "更新中" : status; } function stateActionLabel(action: MarketplacePluginStateAction): string { if (action === "install") { return "安装"; } return action === "disable" ? "停用" : "启用"; } function pluginStateChangeDescription(plugin: MarketplacePluginResponse, action: MarketplacePluginStateAction): string { const description = `将对 ${plugin.name} 执行“${stateActionLabel(action)}”。平台会返回持久状态,失败时保留当前状态并允许重试。`; if (action !== "install" && action !== "enable") { return description; } const extensions = plugin.runtimeProfiles?.dllExtensions ?? []; if (extensions.length === 0) { return description; } const releases = extensions.map((extension) => [extension.releaseHost, extension.releaseFilename].filter(Boolean).join(" / ") || extension.displayName).join(";"); return `${description} UE4SS DLL 声明:Windows amd64 的 SCUM 启动前会校验并按固定发布下载或更新 ${releases};Run 不直接执行 DLL,Linux 会拒绝启动。`; }