dev-1.7.0 #294
@@ -65,7 +65,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
const [files, setFiles] = useState<FileItem[]>([]);
|
const [files, setFiles] = useState<FileItem[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [sshSessionId, setSshSessionId] = useState<string | null>(null);
|
const [sshSessionId, setSshSessionId] = useState<string | null>(null);
|
||||||
|
const [currentRequestId, setCurrentRequestId] = useState<number>(0);
|
||||||
|
const [isReconnecting, setIsReconnecting] = useState<boolean>(false);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [lastRefreshTime, setLastRefreshTime] = useState<number>(0);
|
||||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||||
const [pinnedFiles, setPinnedFiles] = useState<Set<string>>(new Set());
|
const [pinnedFiles, setPinnedFiles] = useState<Set<string>>(new Set());
|
||||||
const [sidebarRefreshTrigger, setSidebarRefreshTrigger] = useState(0);
|
const [sidebarRefreshTrigger, setSidebarRefreshTrigger] = useState(0);
|
||||||
@@ -144,7 +147,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
// 文件列表更新
|
// 文件列表更新
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sshSessionId) {
|
if (sshSessionId) {
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
}
|
}
|
||||||
}, [sshSessionId, currentPath]);
|
}, [sshSessionId, currentPath]);
|
||||||
|
|
||||||
@@ -226,60 +229,61 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Generate unique request ID to prevent race conditions
|
||||||
|
const requestId = Date.now();
|
||||||
|
setCurrentRequestId(requestId);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
console.log("Loading directory:", path, "with session ID:", sshSessionId);
|
|
||||||
|
|
||||||
// 首先检查SSH连接状态
|
|
||||||
try {
|
try {
|
||||||
const status = await getSSHStatus(sshSessionId);
|
console.log(`[${requestId}] Loading directory:`, path);
|
||||||
console.log("SSH connection status:", status);
|
|
||||||
|
|
||||||
if (!status.connected) {
|
const response = await listSSHFiles(sshSessionId, path);
|
||||||
console.log("SSH not connected, attempting to reconnect...");
|
|
||||||
await initializeSSHConnection();
|
// Only process response if this is still the latest request
|
||||||
return; // 重连后会触发useEffect重新加载目录
|
if (requestId !== currentRequestId) {
|
||||||
}
|
console.log(`[${requestId}] Request outdated, ignoring response`);
|
||||||
} catch (statusError) {
|
|
||||||
console.log("Failed to get SSH status, attempting to reconnect...");
|
|
||||||
await initializeSSHConnection();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await listSSHFiles(sshSessionId, path);
|
console.log(`[${requestId}] Directory response received:`, response);
|
||||||
console.log("Directory response from backend:", response);
|
|
||||||
|
|
||||||
// 处理新的返回格式 { files: FileItem[], path: string }
|
|
||||||
const files = Array.isArray(response) ? response : response?.files || [];
|
const files = Array.isArray(response) ? response : response?.files || [];
|
||||||
console.log("Directory contents loaded:", files.length, "items");
|
|
||||||
console.log(
|
|
||||||
"Files with sizes:",
|
|
||||||
files.map((f) => ({ name: f.name, size: f.size, type: f.type })),
|
|
||||||
);
|
|
||||||
|
|
||||||
setFiles(files);
|
setFiles(files);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
} catch (error: any) {
|
|
||||||
console.error("Failed to load directory:", error);
|
|
||||||
|
|
||||||
// 如果是连接错误,尝试重连
|
console.log(`[${requestId}] Directory loaded successfully:`, files.length, "items");
|
||||||
if (
|
} catch (error: any) {
|
||||||
error.message?.includes("connection") ||
|
// Only handle error if this is still the latest request
|
||||||
error.message?.includes("established")
|
if (requestId !== currentRequestId) {
|
||||||
) {
|
console.log(`[${requestId}] Request outdated, ignoring error`);
|
||||||
console.log("Connection error detected, attempting to reconnect...");
|
return;
|
||||||
await initializeSSHConnection();
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
t("fileManager.failedToLoadDirectory") +
|
|
||||||
": " +
|
|
||||||
(error.message || error),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error(`[${requestId}] Failed to load directory:`, error);
|
||||||
|
toast.error(
|
||||||
|
t("fileManager.failedToLoadDirectory") + ": " + (error.message || error)
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
// Only clear loading if this is still the latest request
|
||||||
|
if (requestId === currentRequestId) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖刷新函数 - 防止疯狂点击
|
||||||
|
function handleRefreshDirectory() {
|
||||||
|
const now = Date.now();
|
||||||
|
const DEBOUNCE_MS = 500; // 500ms防抖
|
||||||
|
|
||||||
|
if (now - lastRefreshTime < DEBOUNCE_MS) {
|
||||||
|
console.log("Refresh ignored - too frequent");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastRefreshTime(now);
|
||||||
|
loadDirectory(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
function handleFilesDropped(fileList: FileList) {
|
function handleFilesDropped(fileList: FileList) {
|
||||||
if (!sshSessionId) {
|
if (!sshSessionId) {
|
||||||
@@ -352,7 +356,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
toast.success(
|
toast.success(
|
||||||
t("fileManager.fileUploadedSuccessfully", { name: file.name }),
|
t("fileManager.fileUploadedSuccessfully", { name: file.name }),
|
||||||
);
|
);
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (
|
if (
|
||||||
error.message?.includes("connection") ||
|
error.message?.includes("connection") ||
|
||||||
@@ -455,7 +459,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
toast.success(
|
toast.success(
|
||||||
t("fileManager.itemsDeletedSuccessfully", { count: files.length }),
|
t("fileManager.itemsDeletedSuccessfully", { count: files.length }),
|
||||||
);
|
);
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (
|
if (
|
||||||
@@ -807,7 +811,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 刷新文件列表
|
// 刷新文件列表
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
|
||||||
// 清空剪贴板(剪切操作后,复制操作保留剪贴板内容)
|
// 清空剪贴板(剪切操作后,复制操作保留剪贴板内容)
|
||||||
@@ -931,7 +935,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 刷新文件列表
|
// 刷新文件列表
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(`撤销操作失败: ${error.message || "Unknown error"}`);
|
toast.error(`撤销操作失败: ${error.message || "Unknown error"}`);
|
||||||
console.error("Undo failed:", error);
|
console.error("Undo failed:", error);
|
||||||
@@ -942,16 +946,16 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
setEditingFile(file);
|
setEditingFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保SSH连接有效
|
// 确保SSH连接有效 - 简化版本,防止并发重连
|
||||||
async function ensureSSHConnection() {
|
async function ensureSSHConnection() {
|
||||||
if (!sshSessionId || !currentHost) return;
|
if (!sshSessionId || !currentHost || isReconnecting) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await getSSHStatus(sshSessionId);
|
const status = await getSSHStatus(sshSessionId);
|
||||||
console.log("SSH connection status:", status);
|
|
||||||
|
|
||||||
if (!status.connected) {
|
if (!status.connected && !isReconnecting) {
|
||||||
console.log("SSH not connected, attempting to reconnect...");
|
setIsReconnecting(true);
|
||||||
|
console.log("SSH disconnected, reconnecting...");
|
||||||
|
|
||||||
await connectSSH(sshSessionId, {
|
await connectSSH(sshSessionId, {
|
||||||
hostId: currentHost.id,
|
hostId: currentHost.id,
|
||||||
@@ -969,8 +973,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
console.log("SSH reconnection successful");
|
console.log("SSH reconnection successful");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("SSH connection check/reconnect failed:", error);
|
console.log("SSH reconnection failed:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setIsReconnecting(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,7 +1047,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
|
|
||||||
// 清除编辑状态
|
// 清除编辑状态
|
||||||
setEditingFile(null);
|
setEditingFile(null);
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Rename failed with error:", {
|
console.error("Rename failed with error:", {
|
||||||
error,
|
error,
|
||||||
@@ -1177,7 +1183,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
toast.success(
|
toast.success(
|
||||||
`成功移动了 ${successCount} 个项目到 ${targetFolder.name}`,
|
`成功移动了 ${successCount} 个项目到 ${targetFolder.name}`,
|
||||||
);
|
);
|
||||||
loadDirectory(currentPath);
|
handleRefreshDirectory();
|
||||||
clearSelection(); // 清除选中状态
|
clearSelection(); // 清除选中状态
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1602,7 +1608,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadDirectory(currentPath)}
|
onClick={handleRefreshDirectory}
|
||||||
className="h-9"
|
className="h-9"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-4 h-4" />
|
<RefreshCw className="w-4 h-4" />
|
||||||
@@ -1637,7 +1643,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
currentPath={currentPath}
|
currentPath={currentPath}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onPathChange={setCurrentPath}
|
onPathChange={setCurrentPath}
|
||||||
onRefresh={() => loadDirectory(currentPath)}
|
onRefresh={handleRefreshDirectory}
|
||||||
onUpload={handleFilesDropped}
|
onUpload={handleFilesDropped}
|
||||||
onDownload={(files) => files.forEach(handleDownloadFile)}
|
onDownload={(files) => files.forEach(handleDownloadFile)}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
@@ -1684,7 +1690,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
}}
|
}}
|
||||||
onNewFolder={handleCreateNewFolder}
|
onNewFolder={handleCreateNewFolder}
|
||||||
onNewFile={handleCreateNewFile}
|
onNewFile={handleCreateNewFile}
|
||||||
onRefresh={() => loadDirectory(currentPath)}
|
onRefresh={handleRefreshDirectory}
|
||||||
hasClipboard={!!clipboard}
|
hasClipboard={!!clipboard}
|
||||||
onDragToDesktop={() => handleDragToDesktop(contextMenu.files)}
|
onDragToDesktop={() => handleDragToDesktop(contextMenu.files)}
|
||||||
onOpenTerminal={(path) => handleOpenTerminal(path)}
|
onOpenTerminal={(path) => handleOpenTerminal(path)}
|
||||||
|
|||||||
Reference in New Issue
Block a user