'use client'; import { useEffect, useState, useRef } from 'react'; import { Upload, Trash2, Power, AlertTriangle, Loader2, Package, Save, Shield } from 'lucide-react'; import type { SettingsPolicy } from '@/lib/admin/types'; import { DEFAULT_POLICY } from '@/lib/admin/types'; interface PluginEntry { id: string; name: string; version: string; author: string; description: string; type: string; enabled: boolean; permissions: string[]; installedAt: string; updatedAt: string; } export default function AdminPluginsPage() { const [plugins, setPlugins] = useState([]); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const fileInputRef = useRef(null); const [policy, setPolicy] = useState({ ...DEFAULT_POLICY }); const [policyDirty, setPolicyDirty] = useState(false); const [savingPolicy, setSavingPolicy] = useState(false); useEffect(() => { fetchPlugins(); fetchPolicy(); }, []); async function fetchPolicy() { try { const res = await fetch('/api/admin/policy'); if (res.ok) { const data = await res.json(); setPolicy(data); } } catch { /* ignore */ } } function togglePluginsEnabled() { setPolicy(prev => ({ ...prev, features: { ...prev.features, pluginsEnabled: !prev.features.pluginsEnabled }, })); setPolicyDirty(true); setMessage(null); } async function handleSavePolicy() { setSavingPolicy(true); setMessage(null); try { const res = await fetch('/api/admin/policy', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(policy), }); if (res.ok) { setMessage({ type: 'success', text: 'Plugin policy saved. Users will see changes on next login.' }); setPolicyDirty(false); } else { const data = await res.json(); setMessage({ type: 'error', text: data.error || 'Failed to save policy' }); } } catch { setMessage({ type: 'error', text: 'Failed to save policy' }); } finally { setSavingPolicy(false); } } async function fetchPlugins() { setLoading(true); try { const res = await fetch('/api/admin/plugins'); if (res.ok) setPlugins(await res.json()); } finally { setLoading(false); } } async function handleUpload(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setUploading(true); setMessage(null); const formData = new FormData(); formData.append('file', file); try { const res = await fetch('/api/admin/plugins', { method: 'POST', body: formData, }); const data = await res.json(); if (res.ok) { const warnings = data.warnings?.length ? ` (${data.warnings.length} warning(s))` : ''; setMessage({ type: 'success', text: `Plugin "${data.plugin.name}" installed${warnings}` }); await fetchPlugins(); } else { setMessage({ type: 'error', text: data.error || 'Upload failed' }); } } catch { setMessage({ type: 'error', text: 'Upload failed' }); } finally { setUploading(false); if (fileInputRef.current) fileInputRef.current.value = ''; } } async function togglePlugin(id: string, enabled: boolean) { setMessage(null); const res = await fetch('/api/admin/plugins', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, enabled }), }); if (res.ok) { setPlugins(prev => prev.map(p => p.id === id ? { ...p, enabled } : p)); } else { const data = await res.json(); setMessage({ type: 'error', text: data.error || 'Update failed' }); } } async function deletePlugin(id: string, name: string) { if (!confirm(`Remove plugin "${name}"? This cannot be undone.`)) return; setMessage(null); const res = await fetch('/api/admin/plugins', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }), }); if (res.ok) { setPlugins(prev => prev.filter(p => p.id !== id)); setMessage({ type: 'success', text: `Plugin "${name}" removed` }); } else { const data = await res.json(); setMessage({ type: 'error', text: data.error || 'Delete failed' }); } } if (loading) { return
Loading...
; } const pluginsEnabled = policy.features.pluginsEnabled ?? true; return (

Plugins

Manage plugins and plugin policy for all users

{policyDirty && ( )}
{message && (
{message.text}
)} {/* Plugin Policy */}

Plugin Policy

Control plugin availability for users

Plugins Enabled

Allow the plugin system to load and run plugins for users

{/* Deployed Plugins */}

Deployed Plugins

Admin-uploaded plugins for all users

{plugins.length === 0 ? (

No plugins installed

Upload a plugin ZIP file to get started

) : (
{plugins.map(plugin => (
{plugin.name} v{plugin.version} {plugin.enabled ? 'Enabled' : 'Disabled'}
{plugin.description && (

{plugin.description}

)}
by {plugin.author} · {plugin.type} · installed {new Date(plugin.installedAt).toLocaleDateString()}
{plugin.permissions.length > 0 && (
Permissions: {plugin.permissions.join(', ')}
)}
))}
)}
); }