'use client'; import React, { useState, useEffect } from 'react'; import { EdgeFunction } from '@/lib/vfs/types'; import { Plus, Loader2, AlertCircle, Code2, MoreVertical, Pencil, Trash2, ToggleLeft, ToggleRight, Copy, ExternalLink, CheckCircle2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { FunctionEditor } from './function-editor'; import { cn } from '@/lib/utils'; import type { FunctionsDataProvider } from './data-providers'; interface FunctionsManagerProps { deploymentId?: string; dataProvider?: FunctionsDataProvider; hideRuntimeFeatures?: boolean; workspaceId?: string; } export function FunctionsManager({ deploymentId, dataProvider, hideRuntimeFeatures, workspaceId }: FunctionsManagerProps) { const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api'; const [functions, setFunctions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editingFunction, setEditingFunction] = useState(null); const [isCreating, setIsCreating] = useState(false); const [copiedUrl, setCopiedUrl] = useState(null); useEffect(() => { loadFunctions(); }, [deploymentId, dataProvider]); const loadFunctions = async () => { try { setLoading(true); setError(null); if (dataProvider) { setFunctions(await dataProvider.list()); } else if (deploymentId) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/functions`); if (!res.ok) { const data = await res.json(); throw new Error(data.error || 'Failed to load functions'); } const data = await res.json(); setFunctions(data.functions); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load functions'); } finally { setLoading(false); } }; const toggleEnabled = async (fn: EdgeFunction) => { try { if (dataProvider) { await dataProvider.toggle(fn.id, !fn.enabled); } else if (deploymentId) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/functions/${fn.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !fn.enabled }), }); if (!res.ok) throw new Error('Failed to update function'); } else { return; } await loadFunctions(); } catch (err) { console.error('Failed to toggle function:', err); } }; const deleteFunction = async (fn: EdgeFunction) => { if (!confirm(`Delete function "${fn.name}"? This cannot be undone.`)) return; try { if (dataProvider) { await dataProvider.remove(fn.id); } else if (deploymentId) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/functions/${fn.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error('Failed to delete function'); } else { return; } await loadFunctions(); } catch (err) { console.error('Failed to delete function:', err); } }; const copyUrl = (fn: EdgeFunction) => { if (!deploymentId) return; const url = `${window.location.origin}${apiBase}/deployments/${deploymentId}/functions/${fn.name}`; navigator.clipboard.writeText(url); setCopiedUrl(fn.id); setTimeout(() => setCopiedUrl(null), 2000); }; const handleSave = async (data: Partial) => { try { if (dataProvider) { await dataProvider.save(editingFunction?.id || null, data); } else if (!deploymentId) { throw new Error('No deployment ID available'); } else if (editingFunction) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/functions/${editingFunction.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'Failed to update function'); } } else { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/functions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'Failed to create function'); } } setEditingFunction(null); setIsCreating(false); await loadFunctions(); } catch (err) { throw err; } }; if (loading) { return (
); } if (error) { return (

{error}

); } return (

Edge Functions

{functions.length === 0 ? (

No edge functions yet

Create your first API endpoint

) : (
{functions.map(fn => (
{fn.name} {fn.method} {!fn.enabled && ( disabled )}
{fn.description && (

{fn.description}

)}
Timeout: {fn.timeoutMs / 1000}s {!hideRuntimeFeatures && deploymentId && ( )}
setEditingFunction(fn)}> Edit toggleEnabled(fn)}> {fn.enabled ? ( <> Disable ) : ( <> Enable )} {!hideRuntimeFeatures && deploymentId && ( window.open(`${apiBase}/deployments/${deploymentId}/functions/${fn.name}`, '_blank')} > Open in Browser )} deleteFunction(fn)} className="text-destructive" > Delete
))}
)}
{/* Function Editor Dialog */} {(isCreating || editingFunction) && ( { setIsCreating(false); setEditingFunction(null); }} onSave={handleSave} /> )}
); }