Fix SSH connection issues and enable no-extension file editing

- Add SSH connection checking with auto-reconnection in FileWindow
- Fix missing sshHost parameter passing from FileManagerModern
- Enable editing for files without extensions (.bashrc, Dockerfile, etc.)
- Add proper error handling for SSH connection failures
- Fix onContentChange callback for real-time text editing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZacharyZcR
2025-09-16 17:41:08 +08:00
parent e364c20213
commit 0497ff139e
2 changed files with 88 additions and 6 deletions

View File

@@ -350,6 +350,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
windowId={windowId} windowId={windowId}
file={file} file={file}
sshSessionId={sshSessionId} sshSessionId={sshSessionId}
sshHost={currentHost}
initialX={offsetX} initialX={offsetX}
initialY={offsetY} initialY={offsetY}
/> />

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { DraggableWindow } from './DraggableWindow'; import { DraggableWindow } from './DraggableWindow';
import { FileViewer } from './FileViewer'; import { FileViewer } from './FileViewer';
import { useWindowManager } from './WindowManager'; import { useWindowManager } from './WindowManager';
import { downloadSSHFile, readSSHFile, writeSSHFile } from '@/ui/main-axios'; import { downloadSSHFile, readSSHFile, writeSSHFile, getSSHStatus, connectSSH } from '@/ui/main-axios';
import { toast } from 'sonner'; import { toast } from 'sonner';
interface FileItem { interface FileItem {
@@ -16,10 +16,25 @@ interface FileItem {
group?: string; group?: string;
} }
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
password?: string;
key?: string;
keyPassword?: string;
authType: 'password' | 'key';
credentialId?: number;
userId?: number;
}
interface FileWindowProps { interface FileWindowProps {
windowId: string; windowId: string;
file: FileItem; file: FileItem;
sshSessionId: string; sshSessionId: string;
sshHost: SSHHost;
initialX?: number; initialX?: number;
initialY?: number; initialY?: number;
} }
@@ -28,6 +43,7 @@ export function FileWindow({
windowId, windowId,
file, file,
sshSessionId, sshSessionId,
sshHost,
initialX = 100, initialX = 100,
initialY = 100 initialY = 100
}: FileWindowProps) { }: FileWindowProps) {
@@ -39,6 +55,39 @@ export function FileWindow({
const currentWindow = windows.find(w => w.id === windowId); const currentWindow = windows.find(w => w.id === windowId);
// 确保SSH连接有效
const ensureSSHConnection = async () => {
try {
// 首先检查SSH连接状态
const status = await getSSHStatus(sshSessionId);
console.log('SSH connection status:', status);
if (!status.connected) {
console.log('SSH not connected, attempting to reconnect...');
// 重新建立连接
await connectSSH(sshSessionId, {
hostId: sshHost.id,
ip: sshHost.ip,
port: sshHost.port,
username: sshHost.username,
password: sshHost.password,
sshKey: sshHost.key,
keyPassword: sshHost.keyPassword,
authType: sshHost.authType,
credentialId: sshHost.credentialId,
userId: sshHost.userId
});
console.log('SSH reconnection successful');
}
} catch (error) {
console.log('SSH connection check/reconnect failed:', error);
// 即使连接失败也尝试继续让具体的API调用报错
throw error;
}
};
// 加载文件内容 // 加载文件内容
useEffect(() => { useEffect(() => {
const loadFileContent = async () => { const loadFileContent = async () => {
@@ -46,6 +95,10 @@ export function FileWindow({
try { try {
setIsLoading(true); setIsLoading(true);
// 确保SSH连接有效
await ensureSSHConnection();
const response = await readSSHFile(sshSessionId, file.path); const response = await readSSHFile(sshSessionId, file.path);
setContent(response.content || ''); setContent(response.content || '');
@@ -57,28 +110,46 @@ export function FileWindow({
]; ];
const extension = file.name.split('.').pop()?.toLowerCase(); const extension = file.name.split('.').pop()?.toLowerCase();
setIsEditable(editableExtensions.includes(extension || '')); const hasNoExtension = !file.name.includes('.') || file.name.startsWith('.');
// 已知可编辑扩展名或无后缀文件默认可编辑(按文本处理)
setIsEditable(editableExtensions.includes(extension || '') || hasNoExtension);
} catch (error: any) { } catch (error: any) {
console.error('Failed to load file:', error); console.error('Failed to load file:', error);
toast.error(`Failed to load file: ${error.message || 'Unknown error'}`);
// 如果是连接错误,提供更明确的错误信息
if (error.message?.includes('connection') || error.message?.includes('established')) {
toast.error(`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`);
} else {
toast.error(`Failed to load file: ${error.message || 'Unknown error'}`);
}
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
loadFileContent(); loadFileContent();
}, [file, sshSessionId]); }, [file, sshSessionId, sshHost]);
// 保存文件 // 保存文件
const handleSave = async (newContent: string) => { const handleSave = async (newContent: string) => {
try { try {
setIsLoading(true); setIsLoading(true);
// 确保SSH连接有效
await ensureSSHConnection();
await writeSSHFile(sshSessionId, file.path, newContent); await writeSSHFile(sshSessionId, file.path, newContent);
setContent(newContent); setContent(newContent);
toast.success('File saved successfully'); toast.success('File saved successfully');
} catch (error: any) { } catch (error: any) {
console.error('Failed to save file:', error); console.error('Failed to save file:', error);
toast.error(`Failed to save file: ${error.message || 'Unknown error'}`);
// 如果是连接错误,提供更明确的错误信息
if (error.message?.includes('connection') || error.message?.includes('established')) {
toast.error(`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`);
} else {
toast.error(`Failed to save file: ${error.message || 'Unknown error'}`);
}
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -87,6 +158,9 @@ export function FileWindow({
// 下载文件 // 下载文件
const handleDownload = async () => { const handleDownload = async () => {
try { try {
// 确保SSH连接有效
await ensureSSHConnection();
const response = await downloadSSHFile(sshSessionId, file.path); const response = await downloadSSHFile(sshSessionId, file.path);
if (response?.content) { if (response?.content) {
@@ -112,7 +186,13 @@ export function FileWindow({
} }
} catch (error: any) { } catch (error: any) {
console.error('Failed to download file:', error); console.error('Failed to download file:', error);
toast.error(`Failed to download file: ${error.message || 'Unknown error'}`);
// 如果是连接错误,提供更明确的错误信息
if (error.message?.includes('connection') || error.message?.includes('established')) {
toast.error(`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`);
} else {
toast.error(`Failed to download file: ${error.message || 'Unknown error'}`);
}
} }
}; };
@@ -158,6 +238,7 @@ export function FileWindow({
content={content} content={content}
isLoading={isLoading} isLoading={isLoading}
isEditable={isEditable} isEditable={isEditable}
onContentChange={(newContent) => setContent(newContent)}
onSave={handleSave} onSave={handleSave}
onDownload={handleDownload} onDownload={handleDownload}
/> />