diff --git a/apps/web-dashboard/src/app/plugins/page.tsx b/apps/web-dashboard/src/app/plugins/page.tsx new file mode 100644 index 0000000..039e538 --- /dev/null +++ b/apps/web-dashboard/src/app/plugins/page.tsx @@ -0,0 +1,357 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Puzzle, Download, Power, PowerOff, Settings, Trash2, Search, Plus } from "lucide-react"; +import { api, Plugin } from "@/lib/api"; + +export default function PluginsPage() { + const [plugins, setPlugins] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [showInstallForm, setShowInstallForm] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [filterEnabled, setFilterEnabled] = useState(undefined); + const [actionLoading, setActionLoading] = useState(null); + const [installForm, setInstallForm] = useState({ + name: "", + version: "", + config: "{}" + }); + + useEffect(() => { + loadPlugins(); + }, [filterEnabled]); + + const loadPlugins = async () => { + try { + setLoading(true); + const pluginsResponse = await api.plugins.list(filterEnabled); + setPlugins(pluginsResponse.plugins); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load plugins"); + } finally { + setLoading(false); + } + }; + + const handleInstallPlugin = async () => { + try { + setActionLoading('install'); + const config = installForm.config ? JSON.parse(installForm.config) : {}; + await api.plugins.install({ + name: installForm.name, + version: installForm.version, + config + }); + + // Reset form + setInstallForm({ name: "", version: "", config: "{}" }); + setShowInstallForm(false); + await loadPlugins(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to install plugin"); + } finally { + setActionLoading(null); + } + }; + + const handleTogglePlugin = async (pluginId: number, enabled: boolean) => { + try { + setActionLoading(`toggle-${pluginId}`); + if (enabled) { + await api.plugins.enable(pluginId); + } else { + await api.plugins.disable(pluginId); + } + await loadPlugins(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to toggle plugin"); + } finally { + setActionLoading(null); + } + }; + + const handleUninstallPlugin = async (pluginId: number) => { + if (!confirm("Are you sure you want to uninstall this plugin?")) return; + + try { + setActionLoading(`uninstall-${pluginId}`); + await api.plugins.uninstall(pluginId); + await loadPlugins(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to uninstall plugin"); + } finally { + setActionLoading(null); + } + }; + + const filteredPlugins = plugins.filter(plugin => + plugin.name.toLowerCase().includes(searchTerm.toLowerCase()) || + plugin.description.toLowerCase().includes(searchTerm.toLowerCase()) || + plugin.author.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(6)].map((_, i) => ( +
+
+
+ ))} +
+
+ ); + } + + if (error) { + return ( +
+
+

Error loading plugins

+

{error}

+
+
+ ); + } + + return ( +
+
+
+ +
+

Plugins

+

Plugin marketplace and management.

+
+
+ +
+ + {showInstallForm && ( + + + Install New Plugin + + +
+
+ + setInstallForm({ ...installForm, name: e.target.value })} + placeholder="e.g., rate-limiter" + className="mt-1 block w-full h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> +
+
+ + setInstallForm({ ...installForm, version: e.target.value })} + placeholder="e.g., v1.0.0" + className="mt-1 block w-full h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> +
+
+ +