新增启动命令警告

This commit is contained in:
小朱
2025-08-09 18:16:51 +08:00
parent 16c6f06c69
commit 80afa37a67
2 changed files with 195 additions and 6 deletions

View File

@@ -0,0 +1,147 @@
import React, { useState, useEffect } from 'react'
import { X, AlertTriangle, Terminal, Play } from 'lucide-react'
interface ConfirmStartDialogProps {
isOpen: boolean
instanceName: string
startCommand: string
onConfirm: () => void
onCancel: () => void
}
export const ConfirmStartDialog: React.FC<ConfirmStartDialogProps> = ({
isOpen,
instanceName,
startCommand,
onConfirm,
onCancel
}) => {
const [isVisible, setIsVisible] = useState(false)
const [isAnimating, setIsAnimating] = useState(false)
const [isClosing, setIsClosing] = useState(false)
useEffect(() => {
if (isOpen) {
setIsClosing(false)
setIsVisible(true)
setTimeout(() => setIsAnimating(true), 10)
} else {
setIsAnimating(false)
setIsClosing(true)
setTimeout(() => setIsVisible(false), 300)
}
}, [isOpen])
const handleCancel = () => {
setIsAnimating(false)
setIsClosing(true)
setTimeout(() => {
onCancel()
}, 300)
}
const handleConfirm = () => {
setIsAnimating(false)
setIsClosing(true)
setTimeout(() => {
onConfirm()
}, 300)
}
if (!isVisible) return null
// 检测启动命令是否为none
const isCommandSuspicious = startCommand === 'none'
return (
<div className="fixed inset-0 z-[60] flex items-center justify-center">
{/* 背景遮罩 */}
<div
className={`absolute inset-0 bg-black/50 ${
isClosing ? 'animate-fade-out' : isAnimating ? 'animate-fade-in' : 'opacity-0'
}`}
onClick={handleCancel}
/>
{/* 对话框内容 */}
<div className={`relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 p-6 ${
isClosing ? 'animate-scale-out' : isAnimating ? 'animate-scale-in' : 'opacity-0 scale-95'
}`}>
{/* 关闭按钮 */}
<button
onClick={handleCancel}
className="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
>
<X className="w-5 h-5" />
</button>
{/* 标题和图标 */}
<div className="flex items-center space-x-3 mb-4">
<div className="flex-shrink-0">
<AlertTriangle className="w-8 h-8 text-yellow-500" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
</h3>
</div>
</div>
{/* 实例信息 */}
<div className="mb-6">
<p className="text-gray-600 dark:text-gray-300 mb-3">
<span className="font-semibold text-gray-900 dark:text-white">"{instanceName}"</span>
</p>
{/* 启动命令信息 */}
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3 mb-4">
<div className="flex items-center space-x-2 mb-2">
<Terminal className="w-4 h-4 text-gray-500" />
<span className="text-sm font-medium text-gray-700 dark:text-gray-300"></span>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 font-mono break-all bg-gray-100 dark:bg-gray-600 p-2 rounded">
{startCommand}
</p>
</div>
{/* 警告信息 */}
<div className="border border-yellow-200 dark:border-yellow-800 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-3">
<div className="flex items-start space-x-2">
<AlertTriangle className="w-4 h-4 text-yellow-500 mt-0.5 flex-shrink-0" />
<div className="flex-1">
<p className="text-sm text-yellow-800 dark:text-yellow-200 font-medium mb-1">
</p>
<ul className="text-xs text-yellow-700 dark:text-yellow-300 space-y-1">
<li> "none"</li>
</ul>
<p className="text-xs text-yellow-700 dark:text-yellow-300 mt-2">
</p>
</div>
</div>
</div>
</div>
{/* 操作按钮 */}
<div className="flex justify-end space-x-3">
<button
onClick={handleCancel}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-colors"
>
</button>
<button
onClick={handleConfirm}
className="flex items-center space-x-2 px-4 py-2 text-sm font-medium text-white bg-yellow-600 border border-transparent rounded-md hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 transition-colors"
>
<Play className="w-4 h-4" />
<span></span>
</button>
</div>
</div>
</div>
)
}
export default ConfirmStartDialog

View File

@@ -24,6 +24,7 @@ import { Instance, CreateInstanceRequest } from '@/types'
import { useNotificationStore } from '@/stores/notificationStore'
import apiClient from '@/utils/api'
import { ConfirmDeleteDialog } from '@/components/ConfirmDeleteDialog'
import { ConfirmStartDialog } from '@/components/ConfirmStartDialog'
import { CreateConfigDialog } from '@/components/CreateConfigDialog'
import SearchableSelect from '@/components/SearchableSelect'
import RconConsole from '@/components/RconConsole'
@@ -77,6 +78,8 @@ const InstanceManagerPage: React.FC = () => {
const [editingInstance, setEditingInstance] = useState<Instance | null>(null)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [instanceToDelete, setInstanceToDelete] = useState<Instance | null>(null)
const [showStartConfirmDialog, setShowStartConfirmDialog] = useState(false)
const [instanceToStart, setInstanceToStart] = useState<Instance | null>(null)
const [showCreateConfigDialog, setShowCreateConfigDialog] = useState(false)
const [createConfigInfo, setCreateConfigInfo] = useState<{
instanceId: string
@@ -607,8 +610,23 @@ const InstanceManagerPage: React.FC = () => {
setTimeout(() => setInstallModalAnimating(true), 10)
}
// 启动实例
const handleStartInstance = async (instance: Instance) => {
// 检查启动命令并显示确认对话框
const handleStartInstance = (instance: Instance) => {
// 检测启动命令是否为none
const isCommandNone = instance.startCommand === 'none'
if (isCommandNone) {
// 显示确认对话框
setInstanceToStart(instance)
setShowStartConfirmDialog(true)
} else {
// 直接启动
performStartInstance(instance)
}
}
// 实际执行启动实例的函数
const performStartInstance = async (instance: Instance) => {
try {
const response = await apiClient.startInstance(instance.id)
if (response.success) {
@@ -617,7 +635,7 @@ const InstanceManagerPage: React.FC = () => {
title: '启动成功',
message: `实例 "${instance.name}" 正在启动`
})
// 如果返回了终端会话ID使用sessionId参数跳转到终端页面
if (response.data?.terminalSessionId) {
navigate(`/terminal?sessionId=${response.data.terminalSessionId}&instance=${instance.id}&cwd=${encodeURIComponent(instance.workingDirectory)}`)
@@ -625,12 +643,12 @@ const InstanceManagerPage: React.FC = () => {
// 兼容旧版本使用instance参数
navigate(`/terminal?instance=${instance.id}&cwd=${encodeURIComponent(instance.workingDirectory)}`)
}
fetchInstances()
}
} catch (error: any) {
console.error('启动实例失败:', error)
// 获取具体的错误消息
let errorMessage = '无法启动实例'
if (error.message) {
@@ -638,7 +656,7 @@ const InstanceManagerPage: React.FC = () => {
} else if (error.error) {
errorMessage = error.error
}
addNotification({
type: 'error',
title: '启动失败',
@@ -647,6 +665,21 @@ const InstanceManagerPage: React.FC = () => {
}
}
// 确认启动实例
const handleConfirmStart = () => {
if (instanceToStart) {
performStartInstance(instanceToStart)
setShowStartConfirmDialog(false)
setInstanceToStart(null)
}
}
// 取消启动实例
const handleCancelStart = () => {
setShowStartConfirmDialog(false)
setInstanceToStart(null)
}
// 停止实例
const handleStopInstance = async (instance: Instance) => {
try {
@@ -1935,6 +1968,15 @@ const InstanceManagerPage: React.FC = () => {
onCancel={handleCancelDelete}
/>
{/* 启动确认对话框 */}
<ConfirmStartDialog
isOpen={showStartConfirmDialog}
instanceName={instanceToStart?.name || ''}
startCommand={instanceToStart?.startCommand || ''}
onConfirm={handleConfirmStart}
onCancel={handleCancelStart}
/>
{/* 启动命令帮助模态框 */}
{showStartCommandHelpModal && (
<div className={`fixed inset-0 bg-black/50 flex items-center justify-center z-50 transition-opacity duration-300 ${