peardock/libs/dockerTerminal.js

213 lines
7.0 KiB
JavaScript
Raw Normal View History

2024-12-01 22:01:07 -05:00
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
// DOM Elements
const dockerTerminalModal = document.getElementById('docker-terminal-modal');
const dockerTerminalTitle = document.getElementById('docker-terminal-title');
const dockerTerminalContainer = document.getElementById('docker-terminal-container');
const dockerKillTerminalBtn = document.getElementById('docker-kill-terminal-btn');
// Terminal variables
let dockerTerminalSession = null;
2024-12-01 23:48:26 -05:00
/**
* Initialize and start the Docker CLI terminal.
* @param {string} connectionId - Unique ID for the connection.
* @param {Object} peer - Active peer object for communication.
*/
2024-12-01 22:01:07 -05:00
function startDockerTerminal(connectionId, peer) {
2024-12-01 22:14:26 -05:00
if (!peer) {
console.error('[ERROR] No active peer for Docker CLI terminal.');
return;
2024-12-01 22:01:07 -05:00
}
2024-12-01 22:14:26 -05:00
if (dockerTerminalSession) {
console.log('[INFO] Docker CLI terminal session already exists.');
return;
2024-12-01 22:01:07 -05:00
}
2024-12-01 22:14:26 -05:00
2024-12-01 23:48:26 -05:00
// Verify DOM elements
const dockerTerminalContainer = document.getElementById('docker-terminal-container');
const dockerTerminalTitle = document.getElementById('docker-terminal-title');
const dockerTerminalModal = document.getElementById('dockerTerminalModal');
const dockerKillTerminalBtn = document.getElementById('docker-kill-terminal-btn');
if (!dockerTerminalContainer || !dockerTerminalTitle || !dockerTerminalModal || !dockerKillTerminalBtn) {
console.error('[ERROR] Missing required DOM elements for Docker CLI terminal.');
return;
}
// Initialize the xterm.js terminal
2024-12-01 22:14:26 -05:00
const xterm = new Terminal({
cursorBlink: true,
theme: { background: '#000000', foreground: '#ffffff' },
});
const fitAddon = new FitAddon();
xterm.loadAddon(fitAddon);
2024-12-01 23:48:26 -05:00
// Prepare the terminal container
2024-12-01 22:14:26 -05:00
dockerTerminalContainer.innerHTML = ''; // Clear previous content
xterm.open(dockerTerminalContainer);
fitAddon.fit();
dockerTerminalSession = { xterm, fitAddon, connectionId, peer };
2024-12-01 23:48:26 -05:00
// Buffer to accumulate user input
let inputBuffer = '';
2024-12-01 22:14:26 -05:00
2024-12-01 23:48:26 -05:00
// Handle terminal input
2024-12-01 22:14:26 -05:00
xterm.onData((input) => {
2024-12-01 23:48:26 -05:00
if (input === '\r') {
// User pressed Enter
const fullCommand = prependDockerCommand(inputBuffer.trim());
if (fullCommand) {
console.log(`[DEBUG] Sending Docker CLI command: ${fullCommand}`);
2024-12-01 22:14:26 -05:00
peer.write(
JSON.stringify({
2024-12-01 23:48:26 -05:00
command: 'dockerCommand',
2024-12-01 22:14:26 -05:00
connectionId,
2024-12-01 23:48:26 -05:00
data: fullCommand,
2024-12-01 22:14:26 -05:00
})
);
2024-12-01 23:48:26 -05:00
xterm.write('\r\n'); // Move to the next line
2024-12-01 22:14:26 -05:00
} else {
2024-12-01 23:48:26 -05:00
xterm.write('\r\n[ERROR] Invalid command. Please check your input.\r\n');
2024-12-01 22:14:26 -05:00
}
inputBuffer = ''; // Clear the buffer after processing
2024-12-01 23:48:26 -05:00
} else if (input === '\u007F') {
// Handle backspace
2024-12-01 22:14:26 -05:00
if (inputBuffer.length > 0) {
2024-12-01 23:48:26 -05:00
inputBuffer = inputBuffer.slice(0, -1); // Remove last character
xterm.write('\b \b'); // Erase character from display
2024-12-01 22:14:26 -05:00
}
} else {
2024-12-01 23:48:26 -05:00
// Append input to buffer and display it
inputBuffer += input;
xterm.write(input);
2024-12-01 22:14:26 -05:00
}
});
2024-12-01 23:48:26 -05:00
// Handle peer data
2024-12-01 22:14:26 -05:00
peer.on('data', (data) => {
2024-12-01 23:48:26 -05:00
console.log('[DEBUG] Received data event');
2024-12-01 22:14:26 -05:00
try {
const response = JSON.parse(data.toString());
2024-12-01 23:48:26 -05:00
if (response.connectionId === connectionId) {
const decodedData = decodeResponseData(response.data, response.encoding);
if (response.type === 'dockerOutput') {
xterm.write(`${decodedData.trim()}\r\n`);
} else if (response.type === 'terminalErrorOutput') {
xterm.write(`\r\n[ERROR] ${decodedData.trim()}\r\n`);
}
2024-12-01 22:14:26 -05:00
}
} catch (error) {
console.error(`[ERROR] Failed to parse response from peer: ${error.message}`);
}
});
2024-12-01 23:48:26 -05:00
// Update the terminal modal and title
2024-12-01 22:14:26 -05:00
dockerTerminalTitle.textContent = `Docker CLI Terminal: ${connectionId}`;
2024-12-01 23:48:26 -05:00
const modalInstance = new bootstrap.Modal(dockerTerminalModal);
modalInstance.show();
2024-12-01 22:14:26 -05:00
2024-12-01 23:48:26 -05:00
// Attach event listener for Kill Terminal button
2024-12-01 22:14:26 -05:00
dockerKillTerminalBtn.onclick = () => {
cleanUpDockerTerminal();
};
}
2024-12-01 23:48:26 -05:00
/**
* Prepend 'docker' to the command if it's missing.
* @param {string} command - Command string entered by the user.
* @returns {string|null} - Full Docker command or null if invalid.
*/
function prependDockerCommand(command) {
// Disallow dangerous operators
if (/[\|;&`]/.test(command)) {
console.warn('[WARN] Invalid characters detected in command.');
return null;
}
// Prepend 'docker' if not already present
if (!command.startsWith('docker ')) {
return `docker ${command}`;
}
return command;
}
/**
* Decode response data from Base64 or return as-is if not encoded.
* @param {string} data - Response data from the server.
* @param {string} encoding - Encoding type (e.g., 'base64').
* @returns {string} - Decoded or plain data.
*/
function decodeResponseData(data, encoding) {
if (encoding === 'base64') {
try {
return atob(data); // Decode Base64 data
} catch (error) {
console.error(`[ERROR] Failed to decode Base64 data: ${error.message}`);
return '[ERROR] Command failed.';
}
2024-12-01 22:01:07 -05:00
}
2024-12-01 23:48:26 -05:00
return data; // Return plain data if not encoded
}
2024-12-01 22:01:07 -05:00
2024-12-01 23:48:26 -05:00
/**
* Clean up the Docker CLI terminal session.
*/
2024-12-01 22:01:07 -05:00
function cleanUpDockerTerminal() {
2024-12-01 23:48:26 -05:00
console.log('[INFO] Cleaning up Docker Terminal...');
// Retrieve the required DOM elements
const dockerTerminalContainer = document.getElementById('docker-terminal-container');
const dockerTerminalModal = document.getElementById('dockerTerminalModal');
if (!dockerTerminalContainer || !dockerTerminalModal) {
console.error('[ERROR] Required DOM elements not found for cleaning up the Docker Terminal.');
return;
}
// Dispose of the terminal session if it exists
2024-12-01 22:14:26 -05:00
if (dockerTerminalSession) {
2024-12-01 23:48:26 -05:00
if (dockerTerminalSession.xterm) {
dockerTerminalSession.xterm.dispose();
}
dockerTerminalSession = null; // Reset the session object
}
// Clear the terminal content
dockerTerminalContainer.innerHTML = '';
// Use Bootstrap API to hide the modal
const modalInstance = bootstrap.Modal.getInstance(dockerTerminalModal);
if (modalInstance) {
modalInstance.hide();
} else {
console.warn('[WARNING] Modal instance not found. Falling back to manual close.');
dockerTerminalModal.style.display = 'none';
}
// Ensure lingering backdrops are removed
const backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
2024-12-01 22:14:26 -05:00
}
2024-12-01 23:48:26 -05:00
// Restore the body's scroll behavior
document.body.classList.remove('modal-open');
document.body.style.paddingRight = '';
console.log('[INFO] Docker CLI terminal session cleanup completed.');
2024-12-01 22:01:07 -05:00
}
2024-12-01 22:14:26 -05:00
2024-12-01 23:48:26 -05:00
// Attach event listener for Kill Terminal button (redundant safety check)
2024-12-01 22:01:07 -05:00
dockerKillTerminalBtn.onclick = () => {
cleanUpDockerTerminal();
};
// Export functions
2024-12-01 23:48:26 -05:00
export { startDockerTerminal, cleanUpDockerTerminal, dockerTerminalSession };