/** * Plugin Manager Component * * Provides UI for managing installed plugins: * - List installed plugins with state indicators * - Install plugins from local paths * - Enable/disable plugins * - Configure plugin settings * - Uninstall plugins */ import { useState, useEffect, useCallback } from "react"; import { Package, Settings, Trash2, Plus, X, RefreshCw, RotateCcw } from "lucide-react"; import { fetchPlugins, installPlugin, enablePlugin, disablePlugin, uninstallPlugin, fetchPluginSettings, updatePluginSettings, reloadPlugin } from "../api"; import type { PluginInstallation } from "@fusion/core"; import type { ToastType } from "../hooks/useToast"; interface PluginManagerProps { addToast: (message: string, type?: ToastType) => void; projectId?: string; } const STATE_COLORS: Record = { started: "var(--color-success, #22c55e)", loaded: "var(--color-warning, #eab308)", error: "var(--color-error, #ef4444)", stopped: "var(--color-muted, #6b7280)", installed: "var(--color-info, #3b82f6)", }; export function PluginManager({ addToast, projectId }: PluginManagerProps) { const [plugins, setPlugins] = useState([]); const [loading, setLoading] = useState(true); const [showInstall, setShowInstall] = useState(false); const [installPath, setInstallPath] = useState(""); const [installing, setInstalling] = useState(false); const [reloadingPluginId, setReloadingPluginId] = useState(null); const [selectedPlugin, setSelectedPlugin] = useState(null); const [pluginSettings, setPluginSettings] = useState>({}); const [settingsLoading, setSettingsLoading] = useState(false); const loadPlugins = useCallback(async () => { try { setLoading(true); const data = await fetchPlugins(projectId); setPlugins(data); } catch (err) { addToast(`Failed to load plugins: ${err instanceof Error ? err.message : String(err)}`, "error"); } finally { setLoading(false); } }, [projectId, addToast]); useEffect(() => { loadPlugins(); }, [loadPlugins]); const handleInstall = async () => { if (!installPath.trim()) { addToast("Please enter a plugin path", "error"); return; } try { setInstalling(true); await installPlugin({ path: installPath }, projectId); addToast("Plugin installed successfully", "success"); setShowInstall(false); setInstallPath(""); await loadPlugins(); } catch (err) { addToast(`Failed to install plugin: ${err instanceof Error ? err.message : String(err)}`, "error"); } finally { setInstalling(false); } }; const handleEnable = async (plugin: PluginInstallation) => { try { await enablePlugin(plugin.id, projectId); addToast(`${plugin.name} enabled`, "success"); await loadPlugins(); } catch (err) { addToast(`Failed to enable plugin: ${err instanceof Error ? err.message : String(err)}`, "error"); } }; const handleDisable = async (plugin: PluginInstallation) => { try { await disablePlugin(plugin.id, projectId); addToast(`${plugin.name} disabled`, "success"); await loadPlugins(); } catch (err) { addToast(`Failed to disable plugin: ${err instanceof Error ? err.message : String(err)}`, "error"); } }; const handleReload = async (plugin: PluginInstallation) => { try { setReloadingPluginId(plugin.id); await reloadPlugin(plugin.id, projectId); addToast(`${plugin.name} reloaded`, "success"); await loadPlugins(); } catch (err) { addToast(`Failed to reload plugin: ${err instanceof Error ? err.message : String(err)}`, "error"); } finally { setReloadingPluginId(null); } }; const handleUninstall = async (plugin: PluginInstallation) => { if (!confirm(`Are you sure you want to uninstall "${plugin.name}"?`)) { return; } try { await uninstallPlugin(plugin.id, projectId); addToast(`${plugin.name} uninstalled`, "success"); await loadPlugins(); setSelectedPlugin(null); } catch (err) { addToast(`Failed to uninstall plugin: ${err instanceof Error ? err.message : String(err)}`, "error"); } }; const handleSelectPlugin = async (plugin: PluginInstallation) => { setSelectedPlugin(plugin); try { setSettingsLoading(true); const settings = await fetchPluginSettings(plugin.id, projectId); setPluginSettings(settings); } catch { setPluginSettings({}); } finally { setSettingsLoading(false); } }; const handleSaveSettings = async () => { if (!selectedPlugin) return; try { await updatePluginSettings(selectedPlugin.id, pluginSettings, projectId); addToast("Settings saved", "success"); } catch (err) { addToast(`Failed to save settings: ${err instanceof Error ? err.message : String(err)}`, "error"); } }; // Plugin detail view if (selectedPlugin) { return (

{selectedPlugin.name}

{selectedPlugin.state}
{selectedPlugin.description && (

{selectedPlugin.description}

)} {selectedPlugin.author && (

By {selectedPlugin.author}

)} {selectedPlugin.homepage && (

{selectedPlugin.homepage}

)}

Version {selectedPlugin.version}

Settings

{settingsLoading ? (

Loading...

) : selectedPlugin.settingsSchema ? (
{Object.entries(selectedPlugin.settingsSchema).map(([key, schema]) => (
{schema.type === "string" && ( setPluginSettings({ ...pluginSettings, [key]: e.target.value })} placeholder={schema.description} /> )} {schema.type === "number" && ( setPluginSettings({ ...pluginSettings, [key]: Number(e.target.value) })} /> )} {schema.type === "boolean" && ( )} {schema.type === "enum" && ( )} {schema.description && !schema.required && ( {schema.description} )}
))}
) : (

No configurable settings.

)}
{selectedPlugin.state === "started" && ( )} {selectedPlugin.enabled ? ( ) : ( )}
); } // Plugin list view return (

Plugins

{showInstall && (
setInstallPath(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleInstall()} />
)} {loading ? (
Loading plugins...
) : plugins.length === 0 ? (

No plugins installed.

Install a plugin to get started.

) : (
{plugins.map((plugin) => (
{plugin.name} v{plugin.version} {plugin.state}
{plugin.state === "started" && ( )}
))}
)}
); }