import { useEffect, useMemo, useState } from "react"; import { App as AntdApp, Button, Form, Input, Layout, message, Modal, Progress, Space, Table, Tabs, Tag, Typography, Upload, } from "antd"; import { api, clearToken, getToken, setToken } from "../api/client"; const { Header, Content } = Layout; const { Text } = Typography; const taskColor = { queued: "default", running: "processing", success: "success", failed: "error", }; export function App() { const [token, setTokenState] = useState(getToken()); const [pwd, setPwd] = useState(""); const [currentPath, setCurrentPath] = useState("."); const [entries, setEntries] = useState([]); const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(false); const [downloadOpen, setDownloadOpen] = useState(false); const [extractOpen, setExtractOpen] = useState(false); const [apiMsg, contextHolder] = message.useMessage(); async function loadFiles(path = currentPath) { setLoading(true); try { const data = await api(`/api/fs/list?path=${encodeURIComponent(path)}`); setEntries(data.entries || []); setCurrentPath(path); } catch (err) { apiMsg.error(String(err.message || err)); } finally { setLoading(false); } } async function loadTasks() { try { const data = await api("/api/tasks?limit=100"); setTasks(data.tasks || []); } catch (err) { if (String(err.message || "").includes("401")) { clearToken(); setTokenState(""); } } } useEffect(() => { if (!token) return; loadFiles("."); loadTasks(); const timer = setInterval(loadTasks, 2000); return () => clearInterval(timer); }, [token]); const columns = useMemo( () => [ { title: "Name", dataIndex: "name", render: (_, row) => row.isDir ? ( loadFiles(row.path)}>{row.name} ) : ( {row.name} ), }, { title: "Type", dataIndex: "isDir", render: (v) => (v ? "Directory" : "File"), }, { title: "Size", dataIndex: "size", }, { title: "Action", render: (_, row) => ( {!row.isDir && ( )} ), }, ], [currentPath] ); if (!token) { return ( {contextHolder}

FastFileViewer Login

setPwd(e.target.value)} onPressEnter={async () => { try { const data = await api("/api/login", { method: "POST", body: JSON.stringify({ password: pwd }), }); setToken(data.token); setTokenState(data.token); setPwd(""); } catch (err) { apiMsg.error("Login failed"); } }} />
); } return ( {contextHolder}
FastFileViewer
Current: {currentPath} { try { const form = new FormData(); form.append("dir", currentPath); form.append("file", file); await api("/api/fs/upload", { method: "POST", body: form, }); onSuccess?.("ok"); loadFiles(currentPath); } catch (err) { onError?.(err); } }} > row.path} columns={columns} dataSource={entries} loading={loading} pagination={false} /> ), }, { key: "tasks", label: "Tasks", children: (
row.id} dataSource={tasks} pagination={false} columns={[ { title: "ID", dataIndex: "id" }, { title: "Type", dataIndex: "type" }, { title: "Status", dataIndex: "status", render: (v) => {v}, }, { title: "Progress", dataIndex: "progress", render: (v) => , }, { title: "Source", dataIndex: "source" }, { title: "Target", dataIndex: "targetPath" }, { title: "Error", dataIndex: "error" }, ]} /> ), }, ]} /> setDownloadOpen(false)} footer={null} >
{ await api("/api/tasks/download", { method: "POST", body: JSON.stringify(values), }); setDownloadOpen(false); loadTasks(); }} >
setExtractOpen(false)} footer={null} >
{ await api("/api/tasks/extract", { method: "POST", body: JSON.stringify(values), }); setExtractOpen(false); loadTasks(); }} >
); }