diff --git a/src/ui/Desktop/Apps/File Manager/FileManager.tsx b/src/ui/Desktop/Apps/File Manager/FileManager.tsx index 9d8d013a..e4a6a25d 100644 --- a/src/ui/Desktop/Apps/File Manager/FileManager.tsx +++ b/src/ui/Desktop/Apps/File Manager/FileManager.tsx @@ -1,34 +1,8 @@ -import React, { useState, useEffect, useRef } from "react"; -import { FileManagerLeftSidebar } from "@/ui/Desktop/Apps/File Manager/FileManagerLeftSidebar.tsx"; -import { FileManagerHomeView } from "@/ui/Desktop/Apps/File Manager/FileManagerHomeView.tsx"; -import { FileManagerFileEditor } from "@/ui/Desktop/Apps/File Manager/FileManagerFileEditor.tsx"; -import { FileManagerOperations } from "@/ui/Desktop/Apps/File Manager/FileManagerOperations.tsx"; +import React from "react"; import { FileManagerModern } from "@/ui/Desktop/Apps/File Manager/FileManagerModern.tsx"; -import { Button } from "@/components/ui/button.tsx"; -import { FIleManagerTopNavbar } from "@/ui/Desktop/Apps/File Manager/FIleManagerTopNavbar.tsx"; -import { cn } from "@/lib/utils.ts"; -import { Save, RefreshCw, Settings, Trash2, Grid3X3, Sidebar } from "lucide-react"; -import { toast } from "sonner"; -import { useTranslation } from "react-i18next"; -import { - getFileManagerRecent, - getFileManagerPinned, - getFileManagerShortcuts, - addFileManagerRecent, - removeFileManagerRecent, - addFileManagerPinned, - removeFileManagerPinned, - addFileManagerShortcut, - removeFileManagerShortcut, - readSSHFile, - writeSSHFile, - getSSHStatus, - connectSSH, -} from "@/ui/main-axios.ts"; -import type { SSHHost, Tab } from "../../../types/index.js"; +import type { SSHHost } from "../../../types/index.js"; export function FileManager({ - onSelectView, initialHost = null, onClose, }: { @@ -37,722 +11,10 @@ export function FileManager({ initialHost?: SSHHost | null; onClose?: () => void; }): React.ReactElement { - const { t } = useTranslation(); - const [tabs, setTabs] = useState([]); - const [activeTab, setActiveTab] = useState("home"); - const [recent, setRecent] = useState([]); - const [pinned, setPinned] = useState([]); - const [shortcuts, setShortcuts] = useState([]); - - const [currentHost, setCurrentHost] = useState(null); - const [isSaving, setIsSaving] = useState(false); - - const [showOperations, setShowOperations] = useState(false); - const [currentPath, setCurrentPath] = useState("/"); - const [useModernView, setUseModernView] = useState(true); // 默认使用现代视图 - - const [deletingItem, setDeletingItem] = useState(null); - - const sidebarRef = useRef(null); - - useEffect(() => { - if (initialHost && (!currentHost || currentHost.id !== initialHost.id)) { - setCurrentHost(initialHost); - setTimeout(() => { - try { - const path = initialHost.defaultPath || "/"; - if (sidebarRef.current && sidebarRef.current.openFolder) { - sidebarRef.current.openFolder(initialHost, path); - } - } catch (e) {} - }, 0); - } - }, [initialHost]); - - useEffect(() => { - if (currentHost) { - fetchHomeData(); - } else { - setRecent([]); - setPinned([]); - setShortcuts([]); - } - }, [currentHost]); - - useEffect(() => { - if (activeTab === "home" && currentHost) { - const interval = setInterval(() => { - fetchHomeData(); - }, 2000); - - return () => clearInterval(interval); - } - }, [activeTab, currentHost]); - - async function fetchHomeData() { - if (!currentHost) return; - - try { - const homeDataPromise = Promise.all([ - getFileManagerRecent(currentHost.id), - getFileManagerPinned(currentHost.id), - getFileManagerShortcuts(currentHost.id), - ]); - - const timeoutPromise = new Promise((_, reject) => - setTimeout( - () => reject(new Error(t("fileManager.fetchHomeDataTimeout"))), - 15000, - ), - ); - - const [recentRes, pinnedRes, shortcutsRes] = (await Promise.race([ - homeDataPromise, - timeoutPromise, - ])) as [any, any, any]; - - const recentWithPinnedStatus = (recentRes || []).map((file) => ({ - ...file, - type: "file", - isPinned: (pinnedRes || []).some( - (pinnedFile) => - pinnedFile.path === file.path && pinnedFile.name === file.name, - ), - })); - - const pinnedWithType = (pinnedRes || []).map((file) => ({ - ...file, - type: "file", - })); - - setRecent(recentWithPinnedStatus); - setPinned(pinnedWithType); - setShortcuts( - (shortcutsRes || []).map((shortcut) => ({ - ...shortcut, - type: "directory", - })), - ); - } catch (err: any) { - const { toast } = await import("sonner"); - toast.error(t("fileManager.failedToFetchHomeData")); - - if (onClose) { - onClose(); - } - } - } - - const formatErrorMessage = (err: any, defaultMessage: string): string => { - if (typeof err === "object" && err !== null && "response" in err) { - const axiosErr = err as any; - if (axiosErr.response?.status === 403) { - return `${t("fileManager.permissionDenied")}. ${defaultMessage}. ${t("fileManager.checkDockerLogs")}.`; - } else if (axiosErr.response?.status === 500) { - const backendError = - axiosErr.response?.data?.error || - t("fileManager.internalServerError"); - return `${t("fileManager.serverError")} (500): ${backendError}. ${t("fileManager.checkDockerLogs")}.`; - } else if (axiosErr.response?.data?.error) { - const backendError = axiosErr.response.data.error; - return `${axiosErr.response?.status ? `${t("fileManager.error")} ${axiosErr.response.status}: ` : ""}${backendError}. ${t("fileManager.checkDockerLogs")}.`; - } else { - return `${t("fileManager.requestFailed")} ${axiosErr.response?.status || t("fileManager.unknown")}. ${t("fileManager.checkDockerLogs")}.`; - } - } else if (err instanceof Error) { - return `${err.message}. ${t("fileManager.checkDockerLogs")}.`; - } else { - return `${defaultMessage}. ${t("fileManager.checkDockerLogs")}.`; - } - }; - - const handleOpenFile = async (file: any) => { - const tabId = file.path; - - if (!tabs.find((t) => t.id === tabId)) { - const currentSshSessionId = currentHost?.id.toString(); - - setTabs([ - ...tabs, - { - id: tabId, - title: file.name, - fileName: file.name, - content: "", - filePath: file.path, - isSSH: true, - sshSessionId: currentSshSessionId, - loading: true, - }, - ]); - try { - const res = await readSSHFile(currentSshSessionId, file.path); - setTabs((tabs) => - tabs.map((t) => - t.id === tabId - ? { - ...t, - content: res.content, - loading: false, - error: undefined, - } - : t, - ), - ); - await addFileManagerRecent({ - name: file.name, - path: file.path, - isSSH: true, - sshSessionId: currentSshSessionId, - hostId: currentHost?.id, - }); - } catch (err: any) { - const errorMessage = formatErrorMessage( - err, - t("fileManager.cannotReadFile"), - ); - toast.error(errorMessage); - setTabs((tabs) => - tabs.map((t) => (t.id === tabId ? { ...t, loading: false } : t)), - ); - } - } - setActiveTab(tabId); - }; - - const handleRemoveRecent = async (file: any) => { - try { - await removeFileManagerRecent({ - name: file.name, - path: file.path, - isSSH: true, - sshSessionId: file.sshSessionId, - hostId: currentHost?.id, - }); - fetchHomeData(); - } catch (err) {} - }; - - const handlePinFile = async (file: any) => { - try { - await addFileManagerPinned({ - name: file.name, - path: file.path, - isSSH: true, - sshSessionId: file.sshSessionId, - hostId: currentHost?.id, - }); - if (sidebarRef.current && sidebarRef.current.fetchFiles) { - sidebarRef.current.fetchFiles(); - } - } catch (err) {} - }; - - const handleUnpinFile = async (file: any) => { - try { - await removeFileManagerPinned({ - name: file.name, - path: file.path, - isSSH: true, - sshSessionId: file.sshSessionId, - hostId: currentHost?.id, - }); - if (sidebarRef.current && sidebarRef.current.fetchFiles) { - sidebarRef.current.fetchFiles(); - } - } catch (err) {} - }; - - const handleOpenShortcut = async (shortcut: any) => { - if (sidebarRef.current?.isOpeningShortcut) { - return; - } - - if (sidebarRef.current && sidebarRef.current.openFolder) { - try { - sidebarRef.current.isOpeningShortcut = true; - - const normalizedPath = shortcut.path.startsWith("/") - ? shortcut.path - : `/${shortcut.path}`; - - await sidebarRef.current.openFolder(currentHost, normalizedPath); - } catch (err) { - } finally { - if (sidebarRef.current) { - sidebarRef.current.isOpeningShortcut = false; - } - } - } else { - } - }; - - const handleAddShortcut = async (folderPath: string) => { - try { - const name = folderPath.split("/").pop() || folderPath; - await addFileManagerShortcut({ - name, - path: folderPath, - isSSH: true, - sshSessionId: currentHost?.id.toString(), - hostId: currentHost?.id, - }); - } catch (err) {} - }; - - const handleRemoveShortcut = async (shortcut: any) => { - try { - await removeFileManagerShortcut({ - name: shortcut.name, - path: shortcut.path, - isSSH: true, - sshSessionId: currentHost?.id.toString(), - hostId: currentHost?.id, - }); - } catch (err) {} - }; - - const closeTab = (tabId: string | number) => { - const idx = tabs.findIndex((t) => t.id === tabId); - const newTabs = tabs.filter((t) => t.id !== tabId); - setTabs(newTabs); - if (activeTab === tabId) { - if (newTabs.length > 0) setActiveTab(newTabs[Math.max(0, idx - 1)].id); - else setActiveTab("home"); - } - }; - - const setTabContent = (tabId: string | number, content: string) => { - setTabs((tabs) => - tabs.map((t) => - t.id === tabId - ? { - ...t, - content, - dirty: true, - error: undefined, - success: undefined, - } - : t, - ), - ); - }; - - const handleSave = async (tab: Tab) => { - if (isSaving) { - return; - } - - setIsSaving(true); - - try { - if (!tab.sshSessionId) { - throw new Error(t("fileManager.noSshSessionId")); - } - - if (!tab.filePath) { - throw new Error(t("fileManager.noFilePath")); - } - - if (!currentHost?.id) { - throw new Error(t("fileManager.noCurrentHost")); - } - - try { - const statusPromise = getSSHStatus(tab.sshSessionId); - const statusTimeoutPromise = new Promise((_, reject) => - setTimeout( - () => reject(new Error(t("fileManager.sshStatusCheckTimeout"))), - 10000, - ), - ); - - const status = (await Promise.race([ - statusPromise, - statusTimeoutPromise, - ])) as { connected: boolean }; - - if (!status.connected) { - const connectPromise = connectSSH(tab.sshSessionId, { - hostId: currentHost.id, - ip: currentHost.ip, - port: currentHost.port, - username: currentHost.username, - password: currentHost.password, - sshKey: currentHost.key, - keyPassword: currentHost.keyPassword, - authType: currentHost.authType, - credentialId: currentHost.credentialId, - userId: currentHost.userId, - }); - const connectTimeoutPromise = new Promise((_, reject) => - setTimeout( - () => reject(new Error(t("fileManager.sshReconnectionTimeout"))), - 15000, - ), - ); - - await Promise.race([connectPromise, connectTimeoutPromise]); - } - } catch (statusErr) {} - - const savePromise = writeSSHFile( - tab.sshSessionId, - tab.filePath, - tab.content, - ); - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => { - reject(new Error(t("fileManager.saveOperationTimeout"))); - }, 30000), - ); - - const result = await Promise.race([savePromise, timeoutPromise]); - setTabs((tabs) => - tabs.map((t) => - t.id === tab.id - ? { - ...t, - loading: false, - } - : t, - ), - ); - - if (result?.toast) { - toast[result.toast.type](result.toast.message); - } else { - toast.success(t("fileManager.fileSavedSuccessfully")); - } - - Promise.allSettled([ - (async () => { - try { - await addFileManagerRecent({ - name: tab.fileName, - path: tab.filePath, - isSSH: true, - sshSessionId: tab.sshSessionId, - hostId: currentHost.id, - }); - } catch (recentErr) {} - })(), - ]).then(() => {}); - } catch (err) { - let errorMessage = formatErrorMessage( - err, - t("fileManager.cannotSaveFile"), - ); - - if ( - errorMessage.includes("timed out") || - errorMessage.includes("timeout") - ) { - errorMessage = t("fileManager.saveTimeout"); - } - - toast.error(`${t("fileManager.failedToSaveFile")}: ${errorMessage}`); - setTabs((tabs) => - tabs.map((t) => - t.id === tab.id - ? { - ...t, - loading: false, - } - : t, - ), - ); - } finally { - setIsSaving(false); - } - }; - - const handleHostChange = (_host: SSHHost | null) => {}; - - const handleOperationComplete = () => { - if (sidebarRef.current && sidebarRef.current.fetchFiles) { - sidebarRef.current.fetchFiles(); - } - }; - - const handleSuccess = (message: string) => { - toast.success(message); - }; - - const handleError = (error: string) => { - toast.error(error); - }; - - const updateCurrentPath = (newPath: string) => { - setCurrentPath(newPath); - }; - - const handleDeleteFromSidebar = (item: any) => { - setDeletingItem(item); - }; - - const performDelete = async (item: any) => { - if (!currentHost?.id) return; - - try { - const { deleteSSHItem } = await import("@/ui/main-axios.ts"); - const response = await deleteSSHItem( - currentHost.id.toString(), - item.path, - item.type === "directory", - ); - - if (response?.toast) { - toast[response.toast.type](response.toast.message); - } else { - toast.success( - `${item.type === "directory" ? t("fileManager.folder") : t("fileManager.file")} ${t("fileManager.deletedSuccessfully")}`, - ); - } - - setDeletingItem(null); - handleOperationComplete(); - } catch (error: any) { - handleError( - error?.response?.data?.error || t("fileManager.failedToDeleteItem"), - ); - } - }; - - if (!currentHost) { - if (useModernView) { - return ( - - ); - } - - return ( -
-
- {})} - onOpenFile={handleOpenFile} - tabs={tabs} - ref={sidebarRef} - host={initialHost as SSHHost} - onOperationComplete={handleOperationComplete} - onError={handleError} - onSuccess={handleSuccess} - onPathChange={updateCurrentPath} - /> -
-
-
-

- {t("fileManager.connectToServer")} -

-

- {t("fileManager.selectServerToEdit")} -

-
-
-
- ); - } - - // 如果使用现代视图且有主机连接,显示现代文件管理器 - if (useModernView && currentHost) { - return ( -
- {/* 视图切换按钮 */} -
- -
- -
- ); - } - return ( -
-
- {})} - onOpenFile={handleOpenFile} - tabs={tabs} - ref={sidebarRef} - host={currentHost as SSHHost} - onOperationComplete={handleOperationComplete} - onError={handleError} - onSuccess={handleSuccess} - onPathChange={updateCurrentPath} - onDeleteItem={handleDeleteFromSidebar} - /> -
-
-
-
- ({ id: t.id, title: t.title }))} - activeTab={activeTab} - setActiveTab={setActiveTab} - closeTab={closeTab} - onHomeClick={() => { - setActiveTab("home"); - }} - /> -
-
- {/* 添加现代视图切换按钮 */} - -
- -
- -
-
-
-
-
-
- {activeTab === "home" ? ( - - ) : ( - (() => { - const tab = tabs.find((t) => t.id === activeTab); - if (!tab) return null; - return ( -
-
- - setTabContent(tab.id, content) - } - /> -
-
- ); - })() - )} -
- {showOperations && ( -
- -
- )} -
-
- - {deletingItem && ( -
-
- -
-
-

- - {t("fileManager.confirmDelete")} -

-

- {t("fileManager.confirmDeleteMessage", { - name: deletingItem.name, - })} - {deletingItem.type === "directory" && - ` ${t("fileManager.deleteDirectoryWarning")}`} -

-

- {t("fileManager.actionCannotBeUndone")} -

-
- - -
-
-
-
- )} -
+ ); -} +} \ No newline at end of file