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