import { useState } from "react"; import { FileBrowserWidget } from "@/modules/storage"; import { AclEditor } from "@/components/admin/AclEditor"; import { Card } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { T, translate } from "@/i18n"; import { Button } from "@/components/ui/button"; import { RefreshCw, Database, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { useAuth } from "@/hooks/useAuth"; import { supabase } from "@/integrations/supabase/client"; import { useEffect } from "react"; import { Progress } from "@/components/ui/progress"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; export default function StorageManager() { // Default to 'root' mount, but we could allow selecting mounts if there are multiple. // For now assuming 'root' is the main VFS. const [mount, setMount] = useState("home"); const [currentPath, setCurrentPath] = useState("/"); const [selectedPath, setSelectedPath] = useState(null); const [indexing, setIndexing] = useState(false); const [indexProgress, setIndexProgress] = useState(0); const [indexCountMsg, setIndexCountMsg] = useState(""); const [fullText, setFullText] = useState(false); const { session } = useAuth(); // The ACL editor should show permissions for the SELECTED file if any, // otherwise for the current folder. const targetPath = selectedPath || currentPath; useEffect(() => { if (!indexing) return; let eventSource: EventSource | undefined; let isMounted = true; const setupStream = async () => { const { data: sessionData } = await supabase.auth.getSession(); const token = sessionData.session?.access_token || session?.access_token; const streamUrl = token ? `${import.meta.env.VITE_SERVER_IMAGE_API_URL}/api/stream?token=${encodeURIComponent(token)}` : `${import.meta.env.VITE_SERVER_IMAGE_API_URL}/api/stream`; eventSource = new EventSource(streamUrl); eventSource.addEventListener('system', (e: any) => { if (!isMounted) return; try { const payload = JSON.parse(e.data); if (payload.type === 'vfs-index' && payload.data) { const matchPath = targetPath.replace(/^\//, ''); const payloadTarget = (payload.data.targetPath || '').replace(/^\//, ''); if (payload.data.mount === mount && payloadTarget === matchPath) { setIndexProgress(payload.data.progress); setIndexCountMsg(`Indexing: ${payload.data.indexedCount} / ${payload.data.total}`); } } } catch (err) { } }); }; setupStream(); return () => { isMounted = false; if (eventSource) eventSource.close(); }; }, [indexing, mount, targetPath, session]); const handleClearIndex = async () => { if (!confirm("Are you sure you want to clear the index for this path? This will not delete actual files.")) return; try { setIndexing(true); const cleanPath = targetPath.replace(/^\//, ''); // Remove leading slash const endpoint = mount === 'home' ? `${import.meta.env.VITE_SERVER_IMAGE_API_URL}/api/vfs/admin/index/${cleanPath}` : `${import.meta.env.VITE_SERVER_IMAGE_API_URL}/api/vfs/admin/index/${mount}/${cleanPath}`; const res = await fetch(endpoint.replace(/\/$/, ''), { method: 'DELETE', headers: { 'Authorization': `Bearer ${session?.access_token || ''}` } }); if (!res.ok) { const text = await res.text(); throw new Error(text); } const data = await res.json(); toast.success(translate("Index cleared"), { description: data.message }); } catch (err: any) { toast.error(translate("Failed to clear index"), { description: err.message }); } finally { setIndexing(false); } }; const handleIndex = async () => { try { setIndexing(true); setIndexProgress(0); setIndexCountMsg("Preparing indexing batch..."); const cleanPath = targetPath.replace(/^\//, ''); // Remove leading slash const endpoint = mount === 'home' ? `${import.meta.env.VITE_SERVER_IMAGE_API_URL}/api/vfs/admin/index/${cleanPath}?fullText=${fullText}` : `${import.meta.env.VITE_SERVER_IMAGE_API_URL}/api/vfs/admin/index/${mount}/${cleanPath}?fullText=${fullText}`; const res = await fetch(endpoint.replace(/\/$/, ''), { // ensure no trailing slash if path was empty method: 'POST', headers: { 'Authorization': `Bearer ${session?.access_token || ''}` } }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'Failed to index mount'); } const data = await res.json(); toast.success(translate("Indexing completed"), { description: data.message }); } catch (err: any) { toast.error(translate("Failed to index mount"), { description: err.message }); } finally { // keep it somewhat visible for a second if extremely fast setTimeout(() => { setIndexing(false); setIndexProgress(0); setIndexCountMsg(""); }, 1000); } }; return (
{/* Left Pane: File Browser */}

File Browser

{ setMount(m); setCurrentPath('/'); setSelectedPath(null); }} onPathChange={(p) => { setCurrentPath(p); setSelectedPath(null); // Clear selection when navigating }} onSelect={setSelectedPath} viewMode="list" mode="simple" canChangeMount={true} allowFileViewer={false} allowDownload={true} allowPreview={false} allowFileUpload={true} allowFileDelete={true} allowFileMove={true} allowFileRename={true} allowFolderCreate={true} allowFolderDelete={true} allowFolderMove={true} allowFolderRename={true} showToolbar={true} glob="*.*" sortBy="name" />
{/* Right Pane: ACL Editor */}

Permissions & Tasks

Selected Path
{mount}:{targetPath}
Database Synchronization
setFullText(checked as boolean)} disabled={indexing} />
{indexing && (
{indexCountMsg || `${indexProgress}%`}
)}
); }