重写ssh前端组件,通用化
This commit is contained in:
parent
1cddee8d42
commit
d83a87b259
@ -57,8 +57,6 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
|
|
||||||
// 初始化 Terminal 实例
|
// 初始化 Terminal 实例
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(`[Terminal ${id}] 组件挂载 - 使用实例管理器`);
|
|
||||||
|
|
||||||
const manager = TerminalInstanceManager.getInstance();
|
const manager = TerminalInstanceManager.getInstance();
|
||||||
|
|
||||||
// 获取或创建实例(Manager 内部会处理连接策略的创建和复用)
|
// 获取或创建实例(Manager 内部会处理连接策略的创建和复用)
|
||||||
@ -100,15 +98,11 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
const currentState = instance.getState();
|
const currentState = instance.getState();
|
||||||
if (currentState.status === 'disconnected' || currentState.status === 'error') {
|
if (currentState.status === 'disconnected' || currentState.status === 'error') {
|
||||||
console.log(`[Terminal ${id}] 开始连接`);
|
|
||||||
instance.connect();
|
instance.connect();
|
||||||
} else {
|
|
||||||
console.log(`[Terminal ${id}] 实例已连接,跳过connect调用,当前状态: ${currentState.status}`);
|
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
console.log(`[Terminal ${id}] 组件卸载 - 只unmount,不销毁实例`);
|
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
instance.unmount();
|
instance.unmount();
|
||||||
@ -122,23 +116,14 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const fitAddon = instanceRef.current?.getFitAddon();
|
const fitAddon = instanceRef.current?.getFitAddon();
|
||||||
if (fitAddon && instanceRef.current) {
|
if (fitAddon && instanceRef.current) {
|
||||||
// 检查各层容器尺寸
|
|
||||||
if (wrapperRef.current && terminalRef.current) {
|
|
||||||
console.log(`[Terminal ${id}] 📦 Wrapper尺寸: ${wrapperRef.current.clientWidth}px × ${wrapperRef.current.clientHeight}px`);
|
|
||||||
console.log(`[Terminal ${id}] 📦 Content尺寸: ${terminalRef.current.clientWidth}px × ${terminalRef.current.clientHeight}px`);
|
|
||||||
}
|
|
||||||
|
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
// 发送新的尺寸给后端
|
// 发送新的尺寸给后端
|
||||||
const xterm = instanceRef.current.getXTerm();
|
const xterm = instanceRef.current.getXTerm();
|
||||||
const cols = xterm.cols;
|
const cols = xterm.cols;
|
||||||
const rows = xterm.rows;
|
const rows = xterm.rows;
|
||||||
console.log(`[Terminal ${id}] FitAddon计算结果: ${cols} cols x ${rows} rows`);
|
|
||||||
instanceRef.current.sendResize(cols, rows);
|
instanceRef.current.sendResize(cols, rows);
|
||||||
// 重要:让XTerm获取焦点,否则无法输入
|
// 重要:让XTerm获取焦点,否则无法输入
|
||||||
xterm.focus();
|
xterm.focus();
|
||||||
const hasFocus = xterm.textarea?.matches(':focus');
|
|
||||||
console.log(`[Terminal ${id}] Tab activated, resized: ${cols}x${rows}, focused: ${hasFocus}`);
|
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@ -147,20 +132,10 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 监听窗口大小变化,自动调整终端尺寸
|
// 监听窗口大小变化,自动调整终端尺寸
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
console.log(`[Terminal ${id}] 🔄 Window resize event`);
|
|
||||||
if (wrapperRef.current && terminalRef.current) {
|
|
||||||
console.log(`[Terminal ${id}] 📦 Resize - Wrapper: ${wrapperRef.current.clientWidth}px × ${wrapperRef.current.clientHeight}px`);
|
|
||||||
console.log(`[Terminal ${id}] 📦 Resize - Content: ${terminalRef.current.clientWidth}px × ${terminalRef.current.clientHeight}px`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fitAddon = instanceRef.current?.getFitAddon();
|
const fitAddon = instanceRef.current?.getFitAddon();
|
||||||
if (fitAddon) {
|
if (fitAddon) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
if (instanceRef.current) {
|
|
||||||
const xterm = instanceRef.current.getXTerm();
|
|
||||||
console.log(`[Terminal ${id}] 🔄 Resize后: ${xterm.cols} cols × ${xterm.rows} rows`);
|
|
||||||
}
|
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -65,7 +65,6 @@ const TerminalSplitNodeComponent: React.FC<TerminalSplitNodeProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="h-full w-full flex flex-col bg-white dark:bg-gray-900"
|
className="h-full w-full flex flex-col bg-white dark:bg-gray-900"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log(`[Group Click] Activating group: ${node.id}`);
|
|
||||||
onFocus(node.id);
|
onFocus(node.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -82,7 +81,6 @@ const TerminalSplitNodeComponent: React.FC<TerminalSplitNodeProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log(`[Tab Click] groupId: ${node.id}, tabId: ${tab.id}`);
|
|
||||||
onTabClick(node.id, tab.id);
|
onTabClick(node.id, tab.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -104,7 +102,6 @@ const TerminalSplitNodeComponent: React.FC<TerminalSplitNodeProps> = ({
|
|||||||
className="flex items-center justify-center w-8 h-8 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
|
className="flex items-center justify-center w-8 h-8 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log(`[+ Button Click] Creating tab in group: ${node.id}`);
|
|
||||||
onNewTab(node.id); // 直接传递groupId
|
onNewTab(node.id); // 直接传递groupId
|
||||||
}}
|
}}
|
||||||
title="新建终端 (Cmd+T)"
|
title="新建终端 (Cmd+T)"
|
||||||
|
|||||||
@ -168,16 +168,22 @@ export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
|
|||||||
<>
|
<>
|
||||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
||||||
<Select value={currentTheme} onValueChange={onThemeChange}>
|
<Select value={currentTheme} onValueChange={onThemeChange}>
|
||||||
<SelectTrigger className={compact ? "h-7 w-[160px] text-xs" : "h-7 w-[200px] text-xs"}>
|
<SelectTrigger className={compact ? "h-7 w-[120px] text-xs" : "h-7 w-[200px] text-xs"}>
|
||||||
<Palette className="h-3.5 w-3.5 mr-1" />
|
<Palette className="h-3.5 w-3.5 mr-1" />
|
||||||
<SelectValue placeholder="选择主题" />
|
<SelectValue placeholder="主题" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="z-[9999]">
|
<SelectContent className="z-[9999]">
|
||||||
{themes.map((theme) => (
|
{themes.map((theme) => {
|
||||||
|
// 紧凑模式:只显示英文名(去掉括号和中文)
|
||||||
|
const displayLabel = compact
|
||||||
|
? theme.label.split('(')[0].split('(')[0].trim()
|
||||||
|
: theme.label;
|
||||||
|
return (
|
||||||
<SelectItem key={theme.name} value={theme.name}>
|
<SelectItem key={theme.name} value={theme.name}>
|
||||||
{theme.label}
|
{displayLabel}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -82,8 +82,6 @@ export class TerminalInstance {
|
|||||||
this.connectionStrategy = config.connection;
|
this.connectionStrategy = config.connection;
|
||||||
this.setupConnectionListeners();
|
this.setupConnectionListeners();
|
||||||
this.setupTerminalListeners();
|
this.setupTerminalListeners();
|
||||||
|
|
||||||
console.log(`[TerminalInstance ${config.id}] Created`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,10 +101,6 @@ export class TerminalInstance {
|
|||||||
// 只在第一次或切换容器时调用 open
|
// 只在第一次或切换容器时调用 open
|
||||||
if (!this.mounted) {
|
if (!this.mounted) {
|
||||||
this.xterm.open(container);
|
this.xterm.open(container);
|
||||||
console.log(`[TerminalInstance ${this.config.id}] XTerm opened in container`);
|
|
||||||
} else if (this.currentContainer === container) {
|
|
||||||
// 已经挂载到同一个容器,只需要调整尺寸
|
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Already mounted to same container, skipping open`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentContainer = container;
|
this.currentContainer = container;
|
||||||
@ -119,10 +113,8 @@ export class TerminalInstance {
|
|||||||
const cols = this.xterm.cols;
|
const cols = this.xterm.cols;
|
||||||
const rows = this.xterm.rows;
|
const rows = this.xterm.rows;
|
||||||
this.connectionStrategy.resize(cols, rows);
|
this.connectionStrategy.resize(cols, rows);
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Initial resize sent: ${cols}x${rows}`);
|
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Mounted to DOM`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,14 +127,12 @@ export class TerminalInstance {
|
|||||||
this.currentContainer = null;
|
this.currentContainer = null;
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
|
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Unmounted from DOM`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接到服务器
|
* 连接到服务器
|
||||||
*/
|
*/
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Connecting...`);
|
|
||||||
await this.connectionStrategy.connect();
|
await this.connectionStrategy.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +140,6 @@ export class TerminalInstance {
|
|||||||
* 断开连接
|
* 断开连接
|
||||||
*/
|
*/
|
||||||
disconnect(): void {
|
disconnect(): void {
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Disconnecting...`);
|
|
||||||
this.connectionStrategy.disconnect();
|
this.connectionStrategy.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +244,6 @@ export class TerminalInstance {
|
|||||||
*/
|
*/
|
||||||
showAudit(companyName: string, customMessage?: string): boolean {
|
showAudit(companyName: string, customMessage?: string): boolean {
|
||||||
if (this.auditShown) {
|
if (this.auditShown) {
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Audit already shown, skipping`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +260,6 @@ export class TerminalInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.auditShown = true;
|
this.auditShown = true;
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Audit warning displayed`);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fitAddon.fit();
|
this.fitAddon.fit();
|
||||||
@ -286,7 +273,6 @@ export class TerminalInstance {
|
|||||||
*/
|
*/
|
||||||
resetAudit(): void {
|
resetAudit(): void {
|
||||||
this.auditShown = false;
|
this.auditShown = false;
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Audit state reset`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,7 +286,6 @@ export class TerminalInstance {
|
|||||||
* 销毁实例(完全清理)
|
* 销毁实例(完全清理)
|
||||||
*/
|
*/
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
console.log(`[TerminalInstance ${this.config.id}] Disposing...`);
|
|
||||||
|
|
||||||
// 清理连接
|
// 清理连接
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
|
|||||||
@ -18,7 +18,6 @@ export class TerminalInstanceManager {
|
|||||||
private connectionStrategies: Map<string, BaseConnectionStrategy> = new Map();
|
private connectionStrategies: Map<string, BaseConnectionStrategy> = new Map();
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
console.log('[TerminalInstanceManager] Initialized');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,17 +39,14 @@ export class TerminalInstanceManager {
|
|||||||
getOrCreate(tabId: string, config: TerminalInstanceCreateConfig): TerminalInstance {
|
getOrCreate(tabId: string, config: TerminalInstanceCreateConfig): TerminalInstance {
|
||||||
// 如果实例已存在,直接返回
|
// 如果实例已存在,直接返回
|
||||||
if (this.instances.has(tabId)) {
|
if (this.instances.has(tabId)) {
|
||||||
console.log(`[TerminalInstanceManager] Reusing instance for tab: ${tabId}`);
|
|
||||||
return this.instances.get(tabId)!;
|
return this.instances.get(tabId)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建连接策略(每个 Tab 独立的策略)
|
// 创建连接策略(每个 Tab 独立的策略)
|
||||||
console.log(`[TerminalInstanceManager] Creating connection strategy for tab: ${tabId}`);
|
|
||||||
const connectionStrategy = this.createConnectionStrategy(config.connectionConfig);
|
const connectionStrategy = this.createConnectionStrategy(config.connectionConfig);
|
||||||
this.connectionStrategies.set(tabId, connectionStrategy);
|
this.connectionStrategies.set(tabId, connectionStrategy);
|
||||||
|
|
||||||
// 创建新实例
|
// 创建新实例
|
||||||
console.log(`[TerminalInstanceManager] Creating new instance for tab: ${tabId}`);
|
|
||||||
const instance = new TerminalInstance({
|
const instance = new TerminalInstance({
|
||||||
id: config.id,
|
id: config.id,
|
||||||
connection: connectionStrategy,
|
connection: connectionStrategy,
|
||||||
@ -106,7 +102,6 @@ export class TerminalInstanceManager {
|
|||||||
destroy(tabId: string): void {
|
destroy(tabId: string): void {
|
||||||
const instance = this.instances.get(tabId);
|
const instance = this.instances.get(tabId);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
console.log(`[TerminalInstanceManager] Destroying instance for tab: ${tabId}`);
|
|
||||||
instance.dispose();
|
instance.dispose();
|
||||||
this.instances.delete(tabId);
|
this.instances.delete(tabId);
|
||||||
}
|
}
|
||||||
@ -114,7 +109,6 @@ export class TerminalInstanceManager {
|
|||||||
// 同时销毁连接策略
|
// 同时销毁连接策略
|
||||||
const strategy = this.connectionStrategies.get(tabId);
|
const strategy = this.connectionStrategies.get(tabId);
|
||||||
if (strategy && 'dispose' in strategy) {
|
if (strategy && 'dispose' in strategy) {
|
||||||
console.log(`[TerminalInstanceManager] Destroying connection strategy for tab: ${tabId}`);
|
|
||||||
(strategy as any).dispose();
|
(strategy as any).dispose();
|
||||||
}
|
}
|
||||||
this.connectionStrategies.delete(tabId);
|
this.connectionStrategies.delete(tabId);
|
||||||
@ -124,17 +118,14 @@ export class TerminalInstanceManager {
|
|||||||
* 销毁所有实例(通常用于应用卸载时)
|
* 销毁所有实例(通常用于应用卸载时)
|
||||||
*/
|
*/
|
||||||
destroyAll(): void {
|
destroyAll(): void {
|
||||||
console.log('[TerminalInstanceManager] Destroying all instances');
|
this.instances.forEach((instance) => {
|
||||||
this.instances.forEach((instance, tabId) => {
|
|
||||||
console.log(`[TerminalInstanceManager] Destroying instance: ${tabId}`);
|
|
||||||
instance.dispose();
|
instance.dispose();
|
||||||
});
|
});
|
||||||
this.instances.clear();
|
this.instances.clear();
|
||||||
|
|
||||||
// 同时销毁所有连接策略
|
// 销毁所有连接策略
|
||||||
this.connectionStrategies.forEach((strategy, tabId) => {
|
this.connectionStrategies.forEach((strategy) => {
|
||||||
if ('dispose' in strategy) {
|
if ('dispose' in strategy) {
|
||||||
console.log(`[TerminalInstanceManager] Destroying connection strategy: ${tabId}`);
|
|
||||||
(strategy as any).dispose();
|
(strategy as any).dispose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -34,7 +34,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
*/
|
*/
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
console.log('[SSHConnectionStrategy] Already connected');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +44,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsUrl = `${protocol}//${window.location.host}/api/v1/server-ssh/connect/${this.sshConfig.serverId}?token=${this.sshConfig.token}`;
|
const wsUrl = `${protocol}//${window.location.host}/api/v1/server-ssh/connect/${this.sshConfig.serverId}?token=${this.sshConfig.token}`;
|
||||||
|
|
||||||
console.log('[SSHConnectionStrategy] 连接WebSocket:', wsUrl);
|
|
||||||
await this.connectWebSocket(wsUrl);
|
await this.connectWebSocket(wsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +57,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
this.ws = ws;
|
this.ws = ws;
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log('🔗 SSH WebSocket connected:', wsUrl);
|
|
||||||
this.notifyStatusChange('connected');
|
this.notifyStatusChange('connected');
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
resolve();
|
resolve();
|
||||||
@ -77,7 +74,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
console.log('[SSHConnectionStrategy] WebSocket closed');
|
|
||||||
|
|
||||||
// 只有在已连接状态下关闭才尝试重连
|
// 只有在已连接状态下关闭才尝试重连
|
||||||
if (this.status === 'connected' && this.config.autoReconnect) {
|
if (this.status === 'connected' && this.config.autoReconnect) {
|
||||||
@ -109,7 +105,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.notifyStatusChange('disconnected');
|
this.notifyStatusChange('disconnected');
|
||||||
console.log('[SSHConnectionStrategy] Disconnected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,7 +139,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
console.log(`[SSHConnectionStrategy] Resize sent: ${cols}x${rows}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +183,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
const interval = this.config.reconnectInterval ?? 3000;
|
const interval = this.config.reconnectInterval ?? 3000;
|
||||||
|
|
||||||
if (this.reconnectAttempts >= maxAttempts) {
|
if (this.reconnectAttempts >= maxAttempts) {
|
||||||
console.log('[SSHConnectionStrategy] Max reconnect attempts reached');
|
|
||||||
this.notifyStatusChange('error');
|
this.notifyStatusChange('error');
|
||||||
this.notifyError('连接断开,已达到最大重连次数');
|
this.notifyError('连接断开,已达到最大重连次数');
|
||||||
return;
|
return;
|
||||||
@ -198,8 +191,6 @@ export class SSHConnectionStrategy extends BaseConnectionStrategy {
|
|||||||
this.reconnectAttempts++;
|
this.reconnectAttempts++;
|
||||||
this.notifyStatusChange('reconnecting');
|
this.notifyStatusChange('reconnecting');
|
||||||
|
|
||||||
console.log(`[SSHConnectionStrategy] Reconnecting... (${this.reconnectAttempts}/${maxAttempts})`);
|
|
||||||
|
|
||||||
this.reconnectTimer = setTimeout(() => {
|
this.reconnectTimer = setTimeout(() => {
|
||||||
this.connect().catch(error => {
|
this.connect().catch(error => {
|
||||||
console.error('[SSHConnectionStrategy] Reconnect failed:', error);
|
console.error('[SSHConnectionStrategy] Reconnect failed:', error);
|
||||||
|
|||||||
@ -206,23 +206,18 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
const splitInGroup = useCallback((groupId?: string) => {
|
const splitInGroup = useCallback((groupId?: string) => {
|
||||||
// 如果提供了groupId就用它,否则用activeGroupId
|
// 如果提供了groupId就用它,否则用activeGroupId
|
||||||
const targetGroupId = groupId || activeGroupId;
|
const targetGroupId = groupId || activeGroupId;
|
||||||
console.log(`[splitInGroup] Target groupId: ${targetGroupId}`);
|
|
||||||
|
|
||||||
const targetGroup = findGroup(layout.root, targetGroupId);
|
const targetGroup = findGroup(layout.root, targetGroupId);
|
||||||
if (!targetGroup) {
|
if (!targetGroup) {
|
||||||
console.log('[splitInGroup] Target group not found');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[splitInGroup] Target group: ${targetGroup.id}, tabs count: ${targetGroup.tabs.length}`);
|
|
||||||
|
|
||||||
const activeTab = targetGroup.tabs.find(t => t.id === targetGroup.activeTabId);
|
const activeTab = targetGroup.tabs.find(t => t.id === targetGroup.activeTabId);
|
||||||
if (!activeTab) {
|
if (!activeTab) {
|
||||||
console.log('[splitInGroup] No active tab in target group');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[splitInGroup] Creating new tab based on: ${activeTab.serverName}`);
|
|
||||||
|
|
||||||
const newTab: TerminalTab = {
|
const newTab: TerminalTab = {
|
||||||
id: `tab-${Date.now()}`,
|
id: `tab-${Date.now()}`,
|
||||||
@ -243,20 +238,16 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
// 更新activeGroupId
|
// 更新activeGroupId
|
||||||
setActiveGroupId(targetGroupId);
|
setActiveGroupId(targetGroupId);
|
||||||
|
|
||||||
console.log(`[splitInGroup] New tab created: ${newTab.id} in group: ${targetGroup.id}`);
|
|
||||||
}, [activeGroupId, findGroup, layout.root]);
|
}, [activeGroupId, findGroup, layout.root]);
|
||||||
|
|
||||||
// 切换标签页
|
// 切换标签页
|
||||||
const switchTab = useCallback((groupId: string, tabId: string) => {
|
const switchTab = useCallback((groupId: string, tabId: string) => {
|
||||||
console.log(`[switchTab] Switching to group: ${groupId}, tab: ${tabId}`);
|
|
||||||
|
|
||||||
// 先设置activeGroupId,确保后续操作使用正确的组
|
// 先设置activeGroupId,确保后续操作使用正确的组
|
||||||
setActiveGroupId(groupId);
|
setActiveGroupId(groupId);
|
||||||
|
|
||||||
setLayout(prev => {
|
setLayout(prev => {
|
||||||
const group = findGroup(prev.root, groupId);
|
const group = findGroup(prev.root, groupId);
|
||||||
if (!group) {
|
if (!group) {
|
||||||
console.log(`[switchTab] Group not found: ${groupId}`);
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +266,6 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
// 销毁 Terminal 实例
|
// 销毁 Terminal 实例
|
||||||
const manager = TerminalInstanceManager.getInstance();
|
const manager = TerminalInstanceManager.getInstance();
|
||||||
manager.destroy(tabId);
|
manager.destroy(tabId);
|
||||||
console.log(`[useSplitView] 销毁 Terminal 实例: ${tabId}`);
|
|
||||||
|
|
||||||
setLayout(prev => {
|
setLayout(prev => {
|
||||||
const group = findGroup(prev.root, groupId);
|
const group = findGroup(prev.root, groupId);
|
||||||
@ -313,7 +303,6 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
const manager = TerminalInstanceManager.getInstance();
|
const manager = TerminalInstanceManager.getInstance();
|
||||||
group.tabs.forEach(tab => {
|
group.tabs.forEach(tab => {
|
||||||
manager.destroy(tab.id);
|
manager.destroy(tab.id);
|
||||||
console.log(`[useSplitView] 关闭组,销毁 Terminal 实例: ${tab.id}`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +313,6 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
if (!parent) {
|
if (!parent) {
|
||||||
// 根节点 且 只有一个Tab,关闭整个窗口
|
// 根节点 且 只有一个Tab,关闭整个窗口
|
||||||
if (group && group.tabs.length === 1) {
|
if (group && group.tabs.length === 1) {
|
||||||
console.log('[useSplitView] 最后一个Tab,关闭整个窗口');
|
|
||||||
onWindowClose?.();
|
onWindowClose?.();
|
||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
@ -413,7 +401,6 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
|
|
||||||
// 关闭所有Tab(窗口关闭时调用)
|
// 关闭所有Tab(窗口关闭时调用)
|
||||||
const closeAll = useCallback(() => {
|
const closeAll = useCallback(() => {
|
||||||
console.log('[useSplitView] 关闭所有Tab,断开所有连接');
|
|
||||||
const manager = TerminalInstanceManager.getInstance();
|
const manager = TerminalInstanceManager.getInstance();
|
||||||
|
|
||||||
// 收集所有Tab ID
|
// 收集所有Tab ID
|
||||||
@ -430,10 +417,8 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
// 销毁所有Terminal实例
|
// 销毁所有Terminal实例
|
||||||
allTabIds.forEach(tabId => {
|
allTabIds.forEach(tabId => {
|
||||||
manager.destroy(tabId);
|
manager.destroy(tabId);
|
||||||
console.log(`[useSplitView] 已销毁Terminal实例: ${tabId}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[useSplitView] 共关闭 ${allTabIds.length} 个Tab`);
|
|
||||||
}, [layout.root]);
|
}, [layout.root]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user