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:
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
// 如果是连接错误,提供更明确的错误信息
|
||||||
|
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'}`);
|
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);
|
||||||
|
|
||||||
|
// 如果是连接错误,提供更明确的错误信息
|
||||||
|
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'}`);
|
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,8 +186,14 @@ export function FileWindow({
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Failed to download file:', error);
|
console.error('Failed to download file:', 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'}`);
|
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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user