diff --git a/src/backend/ssh/file-manager.ts b/src/backend/ssh/file-manager.ts
index bc1aebe2..25ccc0bd 100644
--- a/src/backend/ssh/file-manager.ts
+++ b/src/backend/ssh/file-manager.ts
@@ -443,33 +443,87 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
sshConn.lastActive = Date.now();
+ // First check file size to prevent loading huge files
+ const MAX_READ_SIZE = 10 * 1024 * 1024; // 10MB - same as frontend limit
const escapedPath = filePath.replace(/'/g, "'\"'\"'");
- sshConn.client.exec(`cat '${escapedPath}'`, (err, stream) => {
- if (err) {
- fileLogger.error("SSH readFile error:", err);
- return res.status(500).json({ error: err.message });
+
+ // Get file size first
+ sshConn.client.exec(`stat -c%s '${escapedPath}' 2>/dev/null || wc -c < '${escapedPath}'`, (sizeErr, sizeStream) => {
+ if (sizeErr) {
+ fileLogger.error("SSH file size check error:", sizeErr);
+ return res.status(500).json({ error: sizeErr.message });
}
- let data = "";
- let errorData = "";
+ let sizeData = "";
+ let sizeErrorData = "";
- stream.on("data", (chunk: Buffer) => {
- data += chunk.toString();
+ sizeStream.on("data", (chunk: Buffer) => {
+ sizeData += chunk.toString();
});
- stream.stderr.on("data", (chunk: Buffer) => {
- errorData += chunk.toString();
+ sizeStream.stderr.on("data", (chunk: Buffer) => {
+ sizeErrorData += chunk.toString();
});
- stream.on("close", (code) => {
- if (code !== 0) {
- fileLogger.error(
- `SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`,
- );
- return res.status(500).json({ error: `Command failed: ${errorData}` });
+ sizeStream.on("close", (sizeCode) => {
+ if (sizeCode !== 0) {
+ fileLogger.error(`File size check failed: ${sizeErrorData}`);
+ return res.status(500).json({ error: `Cannot check file size: ${sizeErrorData}` });
}
- res.json({ content: data, path: filePath });
+ const fileSize = parseInt(sizeData.trim(), 10);
+
+ if (isNaN(fileSize)) {
+ fileLogger.error("Invalid file size response:", sizeData);
+ return res.status(500).json({ error: "Cannot determine file size" });
+ }
+
+ // Check if file is too large
+ if (fileSize > MAX_READ_SIZE) {
+ fileLogger.warn("File too large for reading", {
+ operation: "file_read",
+ sessionId,
+ filePath,
+ fileSize,
+ maxSize: MAX_READ_SIZE,
+ });
+ return res.status(400).json({
+ error: `File too large to open in editor. Maximum size is ${MAX_READ_SIZE / 1024 / 1024}MB, file is ${(fileSize / 1024 / 1024).toFixed(2)}MB. Use download instead.`,
+ fileSize,
+ maxSize: MAX_READ_SIZE,
+ tooLarge: true
+ });
+ }
+
+ // File size is acceptable, proceed with reading
+ sshConn.client.exec(`cat '${escapedPath}'`, (err, stream) => {
+ if (err) {
+ fileLogger.error("SSH readFile error:", err);
+ return res.status(500).json({ error: err.message });
+ }
+
+ let data = "";
+ let errorData = "";
+
+ stream.on("data", (chunk: Buffer) => {
+ data += chunk.toString();
+ });
+
+ stream.stderr.on("data", (chunk: Buffer) => {
+ errorData += chunk.toString();
+ });
+
+ stream.on("close", (code) => {
+ if (code !== 0) {
+ fileLogger.error(
+ `SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`,
+ );
+ return res.status(500).json({ error: `Command failed: ${errorData}` });
+ }
+
+ res.json({ content: data, path: filePath });
+ });
+ });
});
});
});
diff --git a/src/ui/Desktop/Apps/File Manager/FileManagerGrid.tsx b/src/ui/Desktop/Apps/File Manager/FileManagerGrid.tsx
index c988febd..5c65d07e 100644
--- a/src/ui/Desktop/Apps/File Manager/FileManagerGrid.tsx
+++ b/src/ui/Desktop/Apps/File Manager/FileManagerGrid.tsx
@@ -69,11 +69,11 @@ const getFileIcon = (file: FileItem, viewMode: 'grid' | 'list' = 'grid') => {
const iconClass = viewMode === 'grid' ? "w-8 h-8" : "w-6 h-6";
if (file.type === 'directory') {
- return ;
+ return ;
case 'json':
case 'xml':
case 'yaml':
@@ -128,9 +128,9 @@ const getFileIcon = (file: FileItem, viewMode: 'grid' | 'list' = 'grid') => {
case 'ini':
case 'conf':
case 'config':
- return
+
{t("fileManager.dragFilesToUpload")}
{ // 阻止文件选择事件 @@ -748,7 +748,7 @@ export function FileManagerGrid({
)} {file.type === 'link' && file.linkTarget && ( -+
→ {file.linkTarget}
)} @@ -770,8 +770,8 @@ export function FileManagerGrid({ data-file-path={file.path} className={cn( "flex items-center gap-3 p-2 rounded cursor-pointer transition-all", - "hover:bg-dark-hover", - isSelected && "bg-blue-500/20" + "hover:bg-accent hover:text-accent-foreground", + isSelected && "bg-primary/20" )} onClick={(e) => handleFileClick(file, e)} onContextMenu={(e) => { @@ -796,7 +796,7 @@ export function FileManagerGrid({ onKeyDown={handleEditKeyDown} onBlur={handleEditConfirm} className={cn( - "flex-1 min-w-0 max-w-[200px] rounded-md border border-input bg-background px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none", + "flex-1 min-w-0 max-w-[200px] rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none", "text-foreground placeholder:text-muted-foreground", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]" )} @@ -804,7 +804,7 @@ export function FileManagerGrid({ /> ) : ({ // 阻止文件选择事件 @@ -818,7 +818,7 @@ export function FileManagerGrid({
)} {file.type === 'link' && file.linkTarget && ( -+
→ {file.linkTarget}
)} @@ -855,7 +855,7 @@ export function FileManagerGrid({ {/* 框选矩形 */} {isSelecting && selectionRect && (This file is {formatFileSize(file.size)} in size, which may cause performance issues when opened as text.
{isTooLarge ? ( -+
File is too large (> 10MB) and cannot be opened as text for security reasons.