"use client"; import { useEffect, useState, useCallback } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button, buttonVariants } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; import { Switch } from "@/components/ui/switch"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Puzzle, Search, RefreshCw, MoreHorizontal, Power, RotateCcw, Upload, } from "lucide-react"; import { toast } from "sonner"; interface Plugin { id: string; name: string; version: string | null; description: string | null; isEnabled: boolean; jarFile: string | null; installedAt: number; } export default function PluginsPage() { const [plugins, setPlugins] = useState([]); const [jarFiles, setJarFiles] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [actionLoading, setActionLoading] = useState(null); const fetchPlugins = useCallback(async () => { try { const res = await fetch("/api/plugins"); if (res.ok) { const data = await res.json(); setPlugins(data.plugins); setJarFiles(data.jarFiles); } } finally { setLoading(false); } }, []); useEffect(() => { fetchPlugins(); }, [fetchPlugins]); const handleAction = async ( name: string, action: "enable" | "disable" | "reload", ) => { setActionLoading(`${name}-${action}`); try { const res = await fetch(`/api/plugins?action=${action}&name=${encodeURIComponent(name)}`, { method: "POST", }); if (!res.ok) throw new Error((await res.json()).error); const labels = { enable: "enabled", disable: "disabled", reload: "reloaded" }; toast.success(`${name} ${labels[action]}`); fetchPlugins(); } catch (err) { toast.error(err instanceof Error ? err.message : "Action failed"); } finally { setActionLoading(null); } }; const filtered = plugins.filter((p) => p.name.toLowerCase().includes(search.toLowerCase()), ); const enabledCount = plugins.filter((p) => p.isEnabled).length; return (

Plugins

Manage your server plugins

{enabledCount} / {plugins.length} active Upload Plugin
{/* Search */}
setSearch(e.target.value)} className="pl-9 bg-zinc-900 border-zinc-700 text-white placeholder:text-zinc-500 focus:border-emerald-500/50" />
{/* Plugins grid */} {loading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : filtered.length === 0 ? (

{plugins.length === 0 ? "No plugins installed" : "No plugins match your search"}

) : (
{filtered.map((plugin) => (

{plugin.name}

{plugin.version && (

v{plugin.version}

)}
handleAction( plugin.name, plugin.isEnabled ? "disable" : "enable", ) } className="text-zinc-300 focus:text-white focus:bg-zinc-800" disabled={!!actionLoading} > {plugin.isEnabled ? "Disable" : "Enable"} handleAction(plugin.name, "reload")} className="text-zinc-300 focus:text-white focus:bg-zinc-800" disabled={!plugin.isEnabled || !!actionLoading} > Reload
{plugin.description && (

{plugin.description}

)}
{plugin.isEnabled ? "Enabled" : "Disabled"} handleAction(plugin.name, checked ? "enable" : "disable") } className="scale-75" />
))}
)} {/* Jar files not in DB */} {jarFiles.filter( (jar) => !plugins.find((p) => p.jarFile === jar || p.name + ".jar" === jar), ).length > 0 && ( Jar files detected (not yet in database)
{jarFiles.map((jar) => ( {jar} ))}
)}
); }