/** * 快速连接 — 尽量像 ssh 命令一样简单 * 支持:host | user@host | user@host:port | host:port */ import { useEffect, useState } from 'react' import { Zap, ArrowRight, AlertCircle } from 'lucide-react' import { useTabStore } from '../../stores/tabStore' import Modal from '../common/Modal' import Button from '../common/Button' interface QuickConnectDialogProps { isOpen: boolean onClose: () => void initialTarget?: string } /** 解析 user@host:port / host:port / host */ // eslint-disable-next-line react-refresh/only-export-components export function parseSshTarget(raw: string): { host: string port: number username: string } | null { const s = raw.trim() if (!s) return null // strip optional ssh:// const cleaned = s.replace(/^ssh:\/\//i, '') let username = 'root' let hostPort = cleaned const at = cleaned.lastIndexOf('@') if (at > 0) { username = cleaned.slice(0, at).trim() || 'root' hostPort = cleaned.slice(at + 1).trim() } // IPv6 in brackets [2001:db8::1]:22 let host = hostPort let port = 22 const bracket = /^\[([^\]]+)\](?::(\d+))?$/.exec(hostPort) if (bracket) { host = bracket[1] if (bracket[2]) port = parseInt(bracket[2], 10) || 22 } else { // last :port — avoid splitting bare IPv6 without brackets const colon = hostPort.lastIndexOf(':') if (colon > 0 && hostPort.indexOf(':') === colon) { const maybePort = hostPort.slice(colon + 1) if (/^\d+$/.test(maybePort)) { host = hostPort.slice(0, colon) port = parseInt(maybePort, 10) || 22 } } } host = host.trim() if (!host || !username) return null return { host, port, username } } export default function QuickConnectDialog({ isOpen, onClose, initialTarget = '' }: QuickConnectDialogProps) { const { addTab } = useTabStore() const [target, setTarget] = useState('') const [password, setPassword] = useState('') const [isConnecting, setIsConnecting] = useState(false) const [error, setError] = useState(null) useEffect(() => { if (!isOpen) return setTarget(initialTarget) setPassword('') setError(null) setIsConnecting(false) }, [initialTarget, isOpen]) const handleSubmit = async () => { const parsed = parseSshTarget(target) if (!parsed) { setError('请输入主机,例如 root@192.168.1.10 或 opsbase.cn:5555') return } setIsConnecting(true) setError(null) try { addTab({ title: `${parsed.username}@${parsed.host}`, type: 'terminal', connectionConfig: { host: parsed.host, port: parsed.port, username: parsed.username, password: password || undefined, }, }) onClose() setTarget('') setPassword('') } catch (err: unknown) { setError((err instanceof Error ? err.message : String(err)) || '连接失败') } finally { setIsConnecting(false) } } return ( } size="md" showFooter={false} >
{ e.preventDefault() void handleSubmit() }} className="space-y-4" >
setTarget(e.target.value)} placeholder="root@192.168.1.10:22" className="ui-input font-mono" autoComplete="off" spellCheck={false} />

支持 host、user@host、user@host:port

setPassword(e.target.value)} placeholder="••••••••" className="ui-input" autoComplete="current-password" />
{error && (
{error}
)}
) }