From 885f97d366d2d43463309fe45e277fd2c980aa3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=9C=B1?= <10714957+xiao-zhu245@user.noreply.gitee.com> Date: Wed, 9 Jul 2025 14:04:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AE=9E=E4=BE=8B=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/ConfirmDeleteDialog.tsx | 118 +++++++++++++ client/src/pages/InstanceManagerPage.tsx | 158 +++++++++++++++--- server/src/routes/files.ts | 2 +- 3 files changed, 257 insertions(+), 21 deletions(-) create mode 100644 client/src/components/ConfirmDeleteDialog.tsx diff --git a/client/src/components/ConfirmDeleteDialog.tsx b/client/src/components/ConfirmDeleteDialog.tsx new file mode 100644 index 0000000..6a47144 --- /dev/null +++ b/client/src/components/ConfirmDeleteDialog.tsx @@ -0,0 +1,118 @@ +import React from 'react' +import { X, AlertTriangle, Folder, Trash2 } from 'lucide-react' + +interface ConfirmDeleteDialogProps { + isOpen: boolean + instanceName: string + workingDirectory: string + onConfirm: (deleteDirectory: boolean) => void + onCancel: () => void +} + +export const ConfirmDeleteDialog: React.FC = ({ + isOpen, + instanceName, + workingDirectory, + onConfirm, + onCancel +}) => { + const [deleteDirectory, setDeleteDirectory] = React.useState(false) + + if (!isOpen) return null + + const handleConfirm = () => { + onConfirm(deleteDirectory) + } + + return ( +
+ {/* 背景遮罩 */} +
+ + {/* 对话框内容 */} +
+ {/* 关闭按钮 */} + + + {/* 标题和图标 */} +
+
+ +
+
+

+ 确认删除实例 +

+
+
+ + {/* 实例信息 */} +
+

+ 确定要删除实例 "{instanceName}" 吗? +

+ + {/* 工作目录信息 */} +
+
+ + 工作目录: +
+

+ {workingDirectory} +

+
+ + {/* 删除目录选项 */} +
+ +
+
+ + {/* 操作按钮 */} +
+ + +
+
+
+ ) +} + +export default ConfirmDeleteDialog \ No newline at end of file diff --git a/client/src/pages/InstanceManagerPage.tsx b/client/src/pages/InstanceManagerPage.tsx index 569e736..d3b7bc7 100644 --- a/client/src/pages/InstanceManagerPage.tsx +++ b/client/src/pages/InstanceManagerPage.tsx @@ -19,6 +19,7 @@ import { import { Instance, CreateInstanceRequest } from '@/types' import { useNotificationStore } from '@/stores/notificationStore' import apiClient from '@/utils/api' +import { ConfirmDeleteDialog } from '@/components/ConfirmDeleteDialog' const InstanceManagerPage: React.FC = () => { const navigate = useNavigate() @@ -27,6 +28,8 @@ const InstanceManagerPage: React.FC = () => { const [loading, setLoading] = useState(true) const [showCreateModal, setShowCreateModal] = useState(false) const [editingInstance, setEditingInstance] = useState(null) + const [showDeleteDialog, setShowDeleteDialog] = useState(false) + const [instanceToDelete, setInstanceToDelete] = useState(null) const [formData, setFormData] = useState({ name: '', description: '', @@ -74,12 +77,21 @@ const InstanceManagerPage: React.FC = () => { resetForm() fetchInstances() } - } catch (error) { + } catch (error: any) { console.error('创建实例失败:', error) + + // 获取具体的错误消息 + let errorMessage = '无法创建实例' + if (error.message) { + errorMessage = error.message + } else if (error.error) { + errorMessage = error.error + } + addNotification({ type: 'error', title: '创建失败', - message: '无法创建实例' + message: errorMessage }) } } @@ -100,12 +112,21 @@ const InstanceManagerPage: React.FC = () => { resetForm() fetchInstances() } - } catch (error) { + } catch (error: any) { console.error('更新实例失败:', error) + + // 获取具体的错误消息 + let errorMessage = '无法更新实例' + if (error.message) { + errorMessage = error.message + } else if (error.error) { + errorMessage = error.error + } + addNotification({ type: 'error', title: '更新失败', - message: '无法更新实例' + message: errorMessage }) } } @@ -131,12 +152,21 @@ const InstanceManagerPage: React.FC = () => { fetchInstances() } - } catch (error) { + } catch (error: any) { console.error('启动实例失败:', error) + + // 获取具体的错误消息 + let errorMessage = '无法启动实例' + if (error.message) { + errorMessage = error.message + } else if (error.error) { + errorMessage = error.error + } + addNotification({ type: 'error', title: '启动失败', - message: '无法启动实例' + message: errorMessage }) } } @@ -153,40 +183,108 @@ const InstanceManagerPage: React.FC = () => { }) fetchInstances() } - } catch (error) { + } catch (error: any) { console.error('停止实例失败:', error) + + // 获取具体的错误消息 + let errorMessage = '无法停止实例' + if (error.message) { + errorMessage = error.message + } else if (error.error) { + errorMessage = error.error + } + addNotification({ type: 'error', title: '停止失败', - message: '无法停止实例' + message: errorMessage }) } } // 删除实例 - const handleDeleteInstance = async (instance: Instance) => { - if (!confirm(`确定要删除实例 "${instance.name}" 吗?`)) return + const handleDeleteInstance = (instance: Instance) => { + setInstanceToDelete(instance) + setShowDeleteDialog(true) + } + + // 确认删除实例 + const handleConfirmDelete = async (deleteDirectory: boolean) => { + if (!instanceToDelete) return + + setShowDeleteDialog(false) try { - const response = await apiClient.deleteInstance(instance.id) + const response = await apiClient.deleteInstance(instanceToDelete.id) if (response.success) { - addNotification({ - type: 'success', - title: '删除成功', - message: `实例 "${instance.name}" 已删除` - }) + // 如果用户选择删除目录,发送删除目录的请求 + if (deleteDirectory) { + try { + // 调用删除目录的API + const deleteResponse = await fetch('/api/files/delete', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + paths: [instanceToDelete.workingDirectory] + }) + }) + + if (!deleteResponse.ok) { + const errorData = await deleteResponse.json() + throw new Error(errorData.message || '删除目录失败') + } + + addNotification({ + type: 'success', + title: '删除成功', + message: `实例 "${instanceToDelete.name}" 已删除,工作目录也已删除` + }) + } catch (dirError: any) { + addNotification({ + type: 'warning', + title: '目录删除失败', + message: `实例已删除,但无法删除工作目录: ${dirError.message || '未知错误'}` + }) + } + } else { + addNotification({ + type: 'success', + title: '删除成功', + message: `实例 "${instanceToDelete.name}" 已删除` + }) + } + fetchInstances() } - } catch (error) { + } catch (error: any) { console.error('删除实例失败:', error) + + // 获取具体的错误消息 + let errorMessage = '无法删除实例' + if (error.message) { + errorMessage = error.message + } else if (error.error) { + errorMessage = error.error + } + addNotification({ type: 'error', title: '删除失败', - message: '无法删除实例' + message: errorMessage }) + } finally { + setInstanceToDelete(null) } } + // 取消删除 + const handleCancelDelete = () => { + setShowDeleteDialog(false) + setInstanceToDelete(null) + } + // 打开文件目录 const handleOpenDirectory = (instance: Instance) => { navigate(`/files?path=${encodeURIComponent(instance.workingDirectory)}`) @@ -387,14 +485,25 @@ const InstanceManagerPage: React.FC = () => {
@@ -517,6 +626,15 @@ const InstanceManagerPage: React.FC = () => {
)} + + {/* 删除确认对话框 */} + ) } diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index 586359f..d8171f2 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -302,7 +302,7 @@ router.delete('/delete', async (req: Request, res: Response) => { const stats = await fs.stat(filePath) if (stats.isDirectory()) { - await fs.rmdir(filePath, { recursive: true }) + await fs.rm(filePath, { recursive: true, force: true }) } else { await fs.unlink(filePath) }