diff --git a/web/src/pages/MainPage.tsx b/web/src/pages/MainPage.tsx index 763aea9..105cb11 100644 --- a/web/src/pages/MainPage.tsx +++ b/web/src/pages/MainPage.tsx @@ -63,8 +63,11 @@ function MainPage() { const [isAuthChecking, setIsAuthChecking] = useState(true); // 新增:认证检查状态 const [isPlaybookDialogOpen, setIsPlaybookDialogOpen] = useState(false); const [playbookTarget, setPlaybookTarget] = useState<'selected' | 'all' | null>(null); - const [useKeyAuth, setUseKeyAuth] = useState(false); - + const [useKeyAuth, setUseKeyAuth] = useState(() => { + const saved = localStorage.getItem('useKeyAuth'); + return saved ? JSON.parse(saved) : false; + }); + const navigate = useNavigate(); const { isAuthenticated } = useAuth(); @@ -73,7 +76,7 @@ function MainPage() { const checkAuth = () => { try { const isLocalAuth = authStorage.getAuth(); - + // 如果既没有React context认证也没有localStorage认证,则跳转到登录页 if (!isAuthenticated && !isLocalAuth) { navigate('/login'); @@ -87,12 +90,12 @@ function MainPage() { // 立即检查认证状态 const isAuthed = checkAuth(); - + // 只有通过了认证检查,才执行后续的数据加载 if (isAuthed) { fetchHosts(); } - + // 完成认证检查 setIsAuthChecking(false); }, [isAuthenticated, navigate]); @@ -101,6 +104,11 @@ function MainPage() { fetchHosts(); }, []); + // 持久化密钥认证开关状态 + useEffect(() => { + localStorage.setItem('useKeyAuth', JSON.stringify(useKeyAuth)); + }, [useKeyAuth]); + const fetchHosts = async () => { setIsLoadingHosts(true); try { @@ -136,8 +144,8 @@ function MainPage() { const handleAddHosts = async () => { if (!batchInput.trim()) { - toast.error("错误", { description: "请输入主机信息" }); - return; + toast.error("错误", { description: "请输入主机信息" }); + return; } const lines = batchInput.trim().split('\n'); const hostsData: Omit[] = []; @@ -146,55 +154,60 @@ function MainPage() { const formatString = useKeyAuth ? "'备注 地址 用户名 端口'" : "'备注 地址 用户名 端口 密码'"; lines.forEach((line, index) => { - if (line.trim() === '') return; - const parts = line.trim().split(/\s+/); - if (parts.length !== expectedParts) { - errors.push(`第${index + 1}行:格式错误,应为 ${formatString}`); + if (line.trim() === '') return; + const parts = line.trim().split(/\s+/); + if (parts.length !== expectedParts) { + errors.push(`第${index + 1}行:格式错误,应为 ${formatString}`); + } else { + const [comment, address, username, portStr, password] = parts; + const port = parseInt(portStr, 10); + if (isNaN(port)) { + errors.push(`第${index + 1}行:端口号 '${portStr}' 无效`); } else { - const [comment, address, username, portStr, password] = parts; - const port = parseInt(portStr, 10); - if (isNaN(port)) { - errors.push(`第${index + 1}行:端口号 '${portStr}' 无效`); - } else { - hostsData.push({ - comment, - address, - username, - port, - password: useKeyAuth ? '' : password - }); - } + hostsData.push({ + comment, + address, + username, + port, + password: useKeyAuth ? '' : password + }); } + } }); if (errors.length > 0) { - errors.forEach(err => toast.error("输入错误", { description: err })); - return; + errors.forEach(err => toast.error("输入错误", { description: err })); + return; } if (hostsData.length === 0) { - toast.error("错误", { description: "未找到有效的主机信息" }); - return; + toast.error("错误", { description: "未找到有效的主机信息" }); + return; } setIsAddingHost(true); try { - // 对每个主机数据进行处理 - const processedHostsData = hostsData.map(host => prepareHostData(host, undefined, useKeyAuth)); - const response = await api.post('/api/hosts/batch', processedHostsData); - toast.success("成功", { description: response.data.message || `成功添加 ${response.data.count} 台主机` }); - setBatchInput(''); - fetchHosts(); - setIsBatchAddOpen(false); // Close dialog on success + // 对每个主机数据进行处理 + const processedHostsData = hostsData.map(host => prepareHostData(host, undefined, useKeyAuth)); + const response = await api.post('/api/hosts/batch', processedHostsData); + toast.success("成功", { description: response.data.message || `成功添加 ${response.data.count} 台主机` }); + setBatchInput(''); + fetchHosts(); + setIsBatchAddOpen(false); // Close dialog on success } catch (error) { - console.error('Failed to add hosts:', error); - toast.error("添加主机失败", { - description: error instanceof Error ? error.message : (error as any).response?.data?.error || "发生未知错误", - }); + console.error('Failed to add hosts:', error); + toast.error("添加主机失败", { + description: error instanceof Error ? error.message : (error as any).response?.data?.error || "发生未知错误", + }); } finally { - setIsAddingHost(false); + setIsAddingHost(false); } }; const handleEditHost = (host: Host) => { - setEditingHost(host); + // 确保编辑时有默认的认证方式 + const hostWithDefaults = { + ...host, + auth_method: host.auth_method || 'password' + }; + setEditingHost(hostWithDefaults); }; const handleSaveEdit = async (editedHost: Host) => { @@ -203,7 +216,7 @@ function MainPage() { try { // 处理主机数据,特别是密码字段 const dataToSend = prepareHostData(editedHost, editingHost, editedHost.auth_method === 'key'); - + await api.put(`/api/hosts/${editingHost.id}`, dataToSend); toast.success("成功", { description: "主机信息已更新" }); setEditingHost(null); @@ -308,13 +321,13 @@ function MainPage() { const openTerminal = (hostId: number) => { // 打开新窗口 const terminalWindow = window.open(`/terminal/${hostId}`, `terminal_${hostId}`, 'width=800,height=600'); - + // 确保新窗口成功打开 if (!terminalWindow) { toast.error('无法打开终端', { description: '请允许浏览器打开弹出窗口' }); return; } - + // 等待新窗口加载完成 const sendAuthInfo = () => { try { @@ -322,7 +335,7 @@ function MainPage() { const token = authStorage.getToken(); const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 5); // 5小时过期时间 - + // 如果terminalWindow可用且已加载完成,发送认证信息 if (terminalWindow && terminalWindow.document.readyState === 'complete') { localStorage.setItem('isAuthenticated', 'true'); @@ -330,7 +343,7 @@ function MainPage() { if (token) { localStorage.setItem('token', token); } - + // 尝试向新窗口发送消息,以便它可以检测认证状态 terminalWindow.postMessage({ type: 'AUTH_INFO', @@ -338,7 +351,7 @@ function MainPage() { authExpiresAt: expiresAt.toISOString(), token: token }, '*'); - + // 移除敏感日志 } else { // 如果窗口未完成加载,稍后再试 @@ -349,7 +362,7 @@ function MainPage() { toast.error('无法连接到终端', { description: '认证信息传递失败' }); } }; - + // 开始尝试发送认证信息 setTimeout(sendAuthInfo, 500); }; @@ -360,8 +373,8 @@ function MainPage() { return; } if (target === 'all' && hosts.length === 0) { - toast.error("错误", { description: "没有主机可供上传" }); - return; + toast.error("错误", { description: "没有主机可供上传" }); + return; } setUploadTarget(target); setIsUploadDialogOpen(true); @@ -397,7 +410,7 @@ function MainPage() { setPlaybookTarget(target); setIsPlaybookDialogOpen(true); }; - + const handlePlaybookComplete = () => { console.log("Playbook execution complete"); }; @@ -510,13 +523,13 @@ function MainPage() { 批量添加主机 每行输入一台主机信息,格式: - {useKeyAuth - ? "备注 地址 用户 端口" + {useKeyAuth + ? "备注 地址 用户 端口" : "备注 地址 用户 端口 SSH密码"} 。例如:
- {useKeyAuth - ? "us 192.168.1.1 root 22" + {useKeyAuth + ? "us 192.168.1.1 root 22" : "us 192.168.1.2 root 22 yourpassword"}
@@ -540,16 +553,25 @@ function MainPage() { -
- - -
+ + +
+ + +
+
+ +

{useKeyAuth ? '新添加的主机将使用SSH密钥认证' : '新添加的主机将使用密码认证'}

+
+
- @@ -558,8 +580,8 @@ function MainPage() { - @@ -568,9 +590,9 @@ function MainPage() { - +

检查所有主机的连通性

@@ -670,29 +692,42 @@ function MainPage() { {/* Form fields remain the same */}
- setEditingHost({...editingHost, comment: e.target.value})} className="col-span-3" /> + setEditingHost({ ...editingHost, comment: e.target.value })} className="col-span-3" />
- setEditingHost({...editingHost, address: e.target.value})} className="col-span-3" /> + setEditingHost({ ...editingHost, address: e.target.value })} className="col-span-3" />
- setEditingHost({...editingHost, username: e.target.value})} className="col-span-3" /> + setEditingHost({ ...editingHost, username: e.target.value })} className="col-span-3" />
- setEditingHost({...editingHost, port: parseInt(e.target.value, 10) || 22})} className="col-span-3" /> + setEditingHost({ ...editingHost, port: parseInt(e.target.value, 10) || 22 })} className="col-span-3" />
- - setEditingHost({...editingHost, password: e.target.value})} className="col-span-3" /> + +
+ setEditingHost({ ...editingHost, auth_method: checked ? 'key' : 'password' })} + /> + +
+ {editingHost.auth_method === 'password' && ( +
+ + setEditingHost({ ...editingHost, password: e.target.value })} className="col-span-3" /> +
+ )}
)} - +