mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-17 08:14:25 +00:00
chore: prepare for PR
This commit is contained in:
1 parent
201998638a
commit
5c5f67f48b
11 files changed
+513
-134
No files matched your search
+61
-26
@@ -2,6 +2,16 @@ import { Terminal } from '@xterm/xterm';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
|
||||
const terminalDebugEnabled = import.meta.env.DEV;
|
||||
|
||||
function logTerminal(level, message, ...context) {
|
||||
if (!terminalDebugEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console[level](message, ...context);
|
||||
}
|
||||
|
||||
export function initializeTerminalComponent() {
|
||||
function terminalData() {
|
||||
return {
|
||||
@@ -30,6 +40,8 @@ export function initializeTerminalComponent() {
|
||||
pingTimeoutId: null,
|
||||
heartbeatMissed: 0,
|
||||
maxHeartbeatMisses: 3,
|
||||
// Command buffering for race condition prevention
|
||||
pendingCommand: null,
|
||||
// Resize handling
|
||||
resizeObserver: null,
|
||||
resizeTimeout: null,
|
||||
@@ -120,6 +132,7 @@ export function initializeTerminalComponent() {
|
||||
this.checkIfProcessIsRunningAndKillIt();
|
||||
this.clearAllTimers();
|
||||
this.connectionState = 'disconnected';
|
||||
this.pendingCommand = null;
|
||||
if (this.socket) {
|
||||
this.socket.close(1000, 'Client cleanup');
|
||||
}
|
||||
@@ -154,6 +167,7 @@ export function initializeTerminalComponent() {
|
||||
this.pendingWrites = 0;
|
||||
this.paused = false;
|
||||
this.commandBuffer = '';
|
||||
this.pendingCommand = null;
|
||||
|
||||
// Notify parent component that terminal disconnected
|
||||
this.$wire.dispatch('terminalDisconnected');
|
||||
@@ -188,7 +202,7 @@ export function initializeTerminalComponent() {
|
||||
|
||||
initializeWebSocket() {
|
||||
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
|
||||
console.log('[Terminal] WebSocket already connecting/connected, skipping');
|
||||
logTerminal('log', '[Terminal] WebSocket already connecting/connected, skipping');
|
||||
return; // Already connecting or connected
|
||||
}
|
||||
|
||||
@@ -197,7 +211,7 @@ export function initializeTerminalComponent() {
|
||||
|
||||
// Ensure terminal config is available
|
||||
if (!window.terminalConfig) {
|
||||
console.warn('[Terminal] Terminal config not available, using defaults');
|
||||
logTerminal('warn', '[Terminal] Terminal config not available, using defaults');
|
||||
window.terminalConfig = {};
|
||||
}
|
||||
|
||||
@@ -223,7 +237,7 @@ export function initializeTerminalComponent() {
|
||||
}
|
||||
|
||||
const url = `${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}`
|
||||
console.log(`[Terminal] Attempting connection to: ${url}`);
|
||||
logTerminal('log', `[Terminal] Attempting connection to: ${url}`);
|
||||
|
||||
try {
|
||||
this.socket = new WebSocket(url);
|
||||
@@ -232,7 +246,7 @@ export function initializeTerminalComponent() {
|
||||
const timeoutMs = this.reconnectAttempts === 0 ? 15000 : this.connectionTimeout;
|
||||
this.connectionTimeoutId = setTimeout(() => {
|
||||
if (this.connectionState === 'connecting') {
|
||||
console.error(`[Terminal] Connection timeout after ${timeoutMs}ms`);
|
||||
logTerminal('error', `[Terminal] Connection timeout after ${timeoutMs}ms`);
|
||||
this.socket.close();
|
||||
this.handleConnectionError('Connection timeout');
|
||||
}
|
||||
@@ -244,13 +258,13 @@ export function initializeTerminalComponent() {
|
||||
this.socket.onclose = this.handleSocketClose.bind(this);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Terminal] Failed to create WebSocket:', error);
|
||||
logTerminal('error', '[Terminal] Failed to create WebSocket:', error);
|
||||
this.handleConnectionError(`Failed to create WebSocket connection: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
handleSocketOpen() {
|
||||
console.log('[Terminal] WebSocket connection established. Cool cool cool cool cool cool.');
|
||||
logTerminal('log', '[Terminal] WebSocket connection established.');
|
||||
this.connectionState = 'connected';
|
||||
this.reconnectAttempts = 0;
|
||||
this.heartbeatMissed = 0;
|
||||
@@ -262,6 +276,12 @@ export function initializeTerminalComponent() {
|
||||
this.connectionTimeoutId = null;
|
||||
}
|
||||
|
||||
// Flush any buffered command from before WebSocket was ready
|
||||
if (this.pendingCommand) {
|
||||
this.sendMessage(this.pendingCommand);
|
||||
this.pendingCommand = null;
|
||||
}
|
||||
|
||||
// Start ping timeout monitoring
|
||||
this.resetPingTimeout();
|
||||
|
||||
@@ -270,16 +290,16 @@ export function initializeTerminalComponent() {
|
||||
},
|
||||
|
||||
handleSocketError(error) {
|
||||
console.error('[Terminal] WebSocket error:', error);
|
||||
console.error('[Terminal] WebSocket state:', this.socket ? this.socket.readyState : 'No socket');
|
||||
console.error('[Terminal] Connection attempt:', this.reconnectAttempts + 1);
|
||||
logTerminal('error', '[Terminal] WebSocket error:', error);
|
||||
logTerminal('error', '[Terminal] WebSocket state:', this.socket ? this.socket.readyState : 'No socket');
|
||||
logTerminal('error', '[Terminal] Connection attempt:', this.reconnectAttempts + 1);
|
||||
this.handleConnectionError('WebSocket error occurred');
|
||||
},
|
||||
|
||||
handleSocketClose(event) {
|
||||
console.warn(`[Terminal] WebSocket connection closed. Code: ${event.code}, Reason: ${event.reason || 'No reason provided'}`);
|
||||
console.log('[Terminal] Was clean close:', event.code === 1000);
|
||||
console.log('[Terminal] Connection attempt:', this.reconnectAttempts + 1);
|
||||
logTerminal('warn', `[Terminal] WebSocket connection closed. Code: ${event.code}, Reason: ${event.reason || 'No reason provided'}`);
|
||||
logTerminal('log', '[Terminal] Was clean close:', event.code === 1000);
|
||||
logTerminal('log', '[Terminal] Connection attempt:', this.reconnectAttempts + 1);
|
||||
|
||||
this.connectionState = 'disconnected';
|
||||
this.clearAllTimers();
|
||||
@@ -297,7 +317,7 @@ export function initializeTerminalComponent() {
|
||||
},
|
||||
|
||||
handleConnectionError(reason) {
|
||||
console.error(`[Terminal] Connection error: ${reason} (attempt ${this.reconnectAttempts + 1})`);
|
||||
logTerminal('error', `[Terminal] Connection error: ${reason} (attempt ${this.reconnectAttempts + 1})`);
|
||||
this.connectionState = 'disconnected';
|
||||
|
||||
// Only dispatch error to UI after a few failed attempts to avoid immediate error on page load
|
||||
@@ -310,7 +330,7 @@ export function initializeTerminalComponent() {
|
||||
|
||||
scheduleReconnect() {
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
console.error('[Terminal] Max reconnection attempts reached');
|
||||
logTerminal('error', '[Terminal] Max reconnection attempts reached');
|
||||
this.message = '(connection failed - max retries exceeded)';
|
||||
return;
|
||||
}
|
||||
@@ -323,7 +343,7 @@ export function initializeTerminalComponent() {
|
||||
this.maxReconnectDelay
|
||||
);
|
||||
|
||||
console.warn(`[Terminal] Scheduling reconnect attempt ${this.reconnectAttempts + 1} in ${delay}ms`);
|
||||
logTerminal('warn', `[Terminal] Scheduling reconnect attempt ${this.reconnectAttempts + 1} in ${delay}ms`);
|
||||
|
||||
this.reconnectInterval = setTimeout(() => {
|
||||
this.reconnectAttempts++;
|
||||
@@ -335,17 +355,21 @@ export function initializeTerminalComponent() {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(JSON.stringify(message));
|
||||
} else {
|
||||
console.warn('[Terminal] WebSocket not ready, message not sent:', message);
|
||||
logTerminal('warn', '[Terminal] WebSocket not ready, message not sent:', message);
|
||||
}
|
||||
},
|
||||
|
||||
sendCommandWhenReady(message) {
|
||||
if (this.isWebSocketReady()) {
|
||||
this.sendMessage(message);
|
||||
} else {
|
||||
this.pendingCommand = message;
|
||||
}
|
||||
},
|
||||
|
||||
handleSocketMessage(event) {
|
||||
logTerminal('log', '[Terminal] Received WebSocket message:', event.data);
|
||||
|
||||
// Handle pong responses
|
||||
if (event.data === 'pong') {
|
||||
this.heartbeatMissed = 0;
|
||||
@@ -354,6 +378,10 @@ export function initializeTerminalComponent() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.term?._initialized && event.data !== 'pty-ready') {
|
||||
logTerminal('warn', '[Terminal] Received message before PTY initialization:', event.data);
|
||||
}
|
||||
|
||||
if (event.data === 'pty-ready') {
|
||||
if (!this.term._initialized) {
|
||||
this.term.open(document.getElementById('terminal'));
|
||||
@@ -398,17 +426,24 @@ export function initializeTerminalComponent() {
|
||||
|
||||
// Notify parent component that terminal disconnected
|
||||
this.$wire.dispatch('terminalDisconnected');
|
||||
} else if (
|
||||
typeof event.data === 'string' &&
|
||||
(event.data.startsWith('Unauthorized:') || event.data.startsWith('Invalid SSH command:'))
|
||||
) {
|
||||
logTerminal('error', '[Terminal] Backend rejected terminal startup:', event.data);
|
||||
this.$wire.dispatch('error', event.data);
|
||||
this.terminalActive = false;
|
||||
} else {
|
||||
try {
|
||||
this.pendingWrites++;
|
||||
this.term.write(event.data, (err) => {
|
||||
if (err) {
|
||||
console.error('[Terminal] Write error:', err);
|
||||
logTerminal('error', '[Terminal] Write error:', err);
|
||||
}
|
||||
this.flowControlCallback();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Terminal] Write operation failed:', error);
|
||||
logTerminal('error', '[Terminal] Write operation failed:', error);
|
||||
this.pendingWrites = Math.max(0, this.pendingWrites - 1);
|
||||
}
|
||||
}
|
||||
@@ -483,10 +518,10 @@ export function initializeTerminalComponent() {
|
||||
clearTimeout(this.pingTimeoutId);
|
||||
this.pingTimeoutId = null;
|
||||
}
|
||||
console.log('[Terminal] Tab hidden, pausing heartbeat monitoring');
|
||||
logTerminal('log', '[Terminal] Tab hidden, pausing heartbeat monitoring');
|
||||
} else if (wasVisible === false) {
|
||||
// Tab is now visible again
|
||||
console.log('[Terminal] Tab visible, resuming connection management');
|
||||
logTerminal('log', '[Terminal] Tab visible, resuming connection management');
|
||||
|
||||
if (this.wasConnectedBeforeHidden && this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
// Send immediate ping to verify connection is still alive
|
||||
@@ -508,10 +543,10 @@ export function initializeTerminalComponent() {
|
||||
|
||||
this.pingTimeoutId = setTimeout(() => {
|
||||
this.heartbeatMissed++;
|
||||
console.warn(`[Terminal] Ping timeout - missed ${this.heartbeatMissed}/${this.maxHeartbeatMisses}`);
|
||||
logTerminal('warn', `[Terminal] Ping timeout - missed ${this.heartbeatMissed}/${this.maxHeartbeatMisses}`);
|
||||
|
||||
if (this.heartbeatMissed >= this.maxHeartbeatMisses) {
|
||||
console.error('[Terminal] Too many missed heartbeats, closing connection');
|
||||
logTerminal('error', '[Terminal] Too many missed heartbeats, closing connection');
|
||||
this.socket.close(1001, 'Heartbeat timeout');
|
||||
}
|
||||
}, this.pingTimeout);
|
||||
@@ -553,7 +588,7 @@ export function initializeTerminalComponent() {
|
||||
|
||||
// Check if dimensions are valid
|
||||
if (height <= 0 || width <= 0) {
|
||||
console.warn('[Terminal] Invalid wrapper dimensions, retrying...', { height, width });
|
||||
logTerminal('warn', '[Terminal] Invalid wrapper dimensions, retrying...', { height, width });
|
||||
setTimeout(() => this.resizeTerminal(), 100);
|
||||
return;
|
||||
}
|
||||
@@ -562,7 +597,7 @@ export function initializeTerminalComponent() {
|
||||
|
||||
if (!charSize.height || !charSize.width) {
|
||||
// Fallback values if char size not available yet
|
||||
console.warn('[Terminal] Character size not available, retrying...');
|
||||
logTerminal('warn', '[Terminal] Character size not available, retrying...');
|
||||
setTimeout(() => this.resizeTerminal(), 100);
|
||||
return;
|
||||
}
|
||||
@@ -583,10 +618,10 @@ export function initializeTerminalComponent() {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn('[Terminal] Invalid calculated dimensions:', { rows, cols, height, width, charSize });
|
||||
logTerminal('warn', '[Terminal] Invalid calculated dimensions:', { rows, cols, height, width, charSize });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Terminal] Resize error:', error);
|
||||
logTerminal('error', '[Terminal] Resize error:', error);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
<div>No containers are running or terminal access is disabled on this server.</div>
|
||||
@else
|
||||
<form class="w-96 min-w-fit flex gap-2 items-end" wire:submit="$dispatchSelf('connectToContainer')"
|
||||
x-data="{ autoConnected: false }" x-init="if ({{ count($containers) }} === 1 && !autoConnected) {
|
||||
x-data="{ autoConnected: false }"
|
||||
x-on:terminal-websocket-ready.window="if ({{ count($containers) }} === 1 && !autoConnected) {
|
||||
autoConnected = true;
|
||||
$nextTick(() => $wire.dispatchSelf('connectToContainer'));
|
||||
}">
|
||||
|
||||
Reference in new issue
Block a user