239 lines
7.2 KiB
JavaScript
239 lines
7.2 KiB
JavaScript
import { Terminal } from 'xterm';
|
|
import { FitAddon } from 'xterm-addon-fit';
|
|
|
|
// DOM Elements
|
|
const terminalModal = document.getElementById('terminal-modal');
|
|
const terminalTitle = document.getElementById('terminal-title');
|
|
const terminalContainer = document.getElementById('terminal-container');
|
|
const tray = document.getElementById('tray');
|
|
const terminalHeader = document.querySelector('#terminal-modal .header');
|
|
|
|
// Terminal variables
|
|
let terminalSessions = {}; // Track terminal sessions per containerId
|
|
let activeContainerId = null; // Currently displayed containerId
|
|
|
|
// Variables for resizing
|
|
let isResizing = false;
|
|
let startY = 0;
|
|
let startHeight = 0;
|
|
|
|
// Kill Terminal button functionality
|
|
document.getElementById('kill-terminal-btn').onclick = () => {
|
|
killActiveTerminal();
|
|
};
|
|
|
|
// Kill the active terminal session
|
|
function killActiveTerminal() {
|
|
const containerId = activeContainerId;
|
|
|
|
if (containerId && terminalSessions[containerId]) {
|
|
console.log(`[INFO] Killing terminal session for container: ${containerId}`);
|
|
|
|
// Send kill command to server
|
|
window.sendCommand('killTerminal', { containerId });
|
|
|
|
// Clean up terminal session
|
|
cleanUpTerminal(containerId);
|
|
|
|
// Hide the terminal modal if this was the active session
|
|
if (activeContainerId === containerId) {
|
|
terminalModal.style.display = 'none';
|
|
activeContainerId = null;
|
|
}
|
|
} else {
|
|
console.error('[ERROR] No terminal session found to kill.');
|
|
}
|
|
}
|
|
|
|
// Start resizing when the mouse is down on the header
|
|
terminalHeader.addEventListener('mousedown', (e) => {
|
|
isResizing = true;
|
|
startY = e.clientY; // Track the initial Y position
|
|
startHeight = terminalModal.offsetHeight; // Track the initial height
|
|
document.body.style.cursor = 'ns-resize'; // Change cursor to indicate resizing
|
|
e.preventDefault(); // Prevent text selection
|
|
});
|
|
|
|
// Resize the modal while dragging
|
|
document.addEventListener('mousemove', (e) => {
|
|
if (isResizing) {
|
|
const deltaY = startY - e.clientY; // Calculate how much the mouse moved
|
|
const newHeight = Math.min(
|
|
Math.max(startHeight + deltaY, 150), // Minimum height: 150px
|
|
window.innerHeight * 0.9 // Maximum height: 90% of viewport height
|
|
);
|
|
|
|
terminalModal.style.height = `${newHeight}px`; // Set new height
|
|
terminalContainer.style.height = `${newHeight - 40}px`; // Adjust terminal container height
|
|
|
|
const activeSession = terminalSessions[activeContainerId];
|
|
if (activeSession) {
|
|
setTimeout(() => activeSession.fitAddon.fit(), 10); // Adjust terminal content
|
|
}
|
|
}
|
|
});
|
|
|
|
// Stop resizing when the mouse is released
|
|
document.addEventListener('mouseup', () => {
|
|
if (isResizing) {
|
|
isResizing = false;
|
|
document.body.style.cursor = 'default'; // Reset cursor
|
|
}
|
|
});
|
|
|
|
// Start terminal session
|
|
function startTerminal(containerId, containerName) {
|
|
if (!window.activePeer) {
|
|
console.error('[ERROR] No active peer for terminal.');
|
|
return;
|
|
}
|
|
|
|
if (terminalSessions[containerId]) {
|
|
console.log(`[INFO] Terminal session already exists for container: ${containerId}`);
|
|
if (activeContainerId !== containerId || terminalModal.style.display === 'none') {
|
|
switchTerminal(containerId);
|
|
} else {
|
|
console.log(`[INFO] Terminal for container ${containerId} is already active`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
console.log(`[INFO] Creating new terminal session for container: ${containerId}`);
|
|
|
|
const xterm = new Terminal({
|
|
cursorBlink: true,
|
|
theme: { background: '#000000', foreground: '#ffffff' },
|
|
});
|
|
const fitAddon = new FitAddon();
|
|
xterm.loadAddon(fitAddon);
|
|
|
|
const terminalDiv = document.createElement('div');
|
|
terminalDiv.style.width = '100%';
|
|
terminalDiv.style.height = '100%';
|
|
terminalDiv.style.display = 'none'; // Initially hidden
|
|
terminalContainer.appendChild(terminalDiv);
|
|
|
|
xterm.open(terminalDiv);
|
|
|
|
const onDataDisposable = xterm.onData((data) => {
|
|
console.log(`[DEBUG] Sending terminal input for container ${containerId}: ${data}`);
|
|
window.activePeer.write(
|
|
JSON.stringify({
|
|
type: 'terminalInput',
|
|
containerId,
|
|
data: btoa(data),
|
|
encoding: 'base64',
|
|
})
|
|
);
|
|
});
|
|
|
|
terminalSessions[containerId] = {
|
|
xterm,
|
|
fitAddon,
|
|
onDataDisposable,
|
|
output: '',
|
|
name: containerName,
|
|
resizeListener: null,
|
|
container: terminalDiv,
|
|
};
|
|
|
|
console.log(`[INFO] Starting terminal for container: ${containerId}`);
|
|
window.activePeer.write(
|
|
JSON.stringify({ command: 'startTerminal', args: { containerId } })
|
|
);
|
|
|
|
switchTerminal(containerId);
|
|
}
|
|
|
|
// Switch to a terminal session
|
|
function switchTerminal(containerId) {
|
|
const session = terminalSessions[containerId];
|
|
if (!session) {
|
|
console.error(`[ERROR] No terminal session found for container: ${containerId}`);
|
|
return;
|
|
}
|
|
|
|
if (activeContainerId && activeContainerId !== containerId) {
|
|
const currentSession = terminalSessions[activeContainerId];
|
|
if (currentSession) {
|
|
if (currentSession.resizeListener) {
|
|
window.removeEventListener('resize', currentSession.resizeListener);
|
|
currentSession.resizeListener = null;
|
|
}
|
|
currentSession.container.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
session.container.style.display = 'block';
|
|
setTimeout(() => session.fitAddon.fit(), 10);
|
|
|
|
terminalTitle.dataset.containerId = containerId;
|
|
terminalTitle.textContent = `Container Terminal: ${session.name}`;
|
|
terminalModal.style.display = 'flex';
|
|
activeContainerId = containerId;
|
|
|
|
removeFromTray(containerId);
|
|
|
|
console.log(`[INFO] Switched to terminal for container: ${containerId}`);
|
|
|
|
if (session.resizeListener) {
|
|
window.removeEventListener('resize', session.resizeListener);
|
|
}
|
|
session.resizeListener = () => session.fitAddon.fit();
|
|
window.addEventListener('resize', session.resizeListener);
|
|
}
|
|
|
|
// Append terminal output
|
|
function appendTerminalOutput(data, containerId, encoding) {
|
|
const session = terminalSessions[containerId];
|
|
if (!session) {
|
|
console.error(`[ERROR] No terminal session found for container: ${containerId}`);
|
|
return;
|
|
}
|
|
|
|
let outputData;
|
|
if (encoding === 'base64') {
|
|
outputData = atob(data);
|
|
} else {
|
|
outputData = data;
|
|
}
|
|
|
|
session.xterm.write(outputData);
|
|
}
|
|
|
|
// Remove terminal from tray
|
|
function removeFromTray(containerId) {
|
|
const trayItem = document.querySelector(`.tray-item[data-id="${containerId}"]`);
|
|
if (trayItem) {
|
|
trayItem.remove();
|
|
}
|
|
}
|
|
|
|
// Clean up terminal session
|
|
function cleanUpTerminal(containerId) {
|
|
const session = terminalSessions[containerId];
|
|
if (session) {
|
|
session.xterm.dispose();
|
|
session.onDataDisposable.dispose();
|
|
if (session.resizeListener) {
|
|
window.removeEventListener('resize', session.resizeListener);
|
|
}
|
|
session.container.parentNode.removeChild(session.container);
|
|
delete terminalSessions[containerId];
|
|
console.log(`[INFO] Cleaned up terminal for container: ${containerId}`);
|
|
} else {
|
|
console.error(`[ERROR] No terminal session to clean up for container: ${containerId}`);
|
|
}
|
|
}
|
|
|
|
// Clean up all terminals
|
|
function cleanUpAllTerminals() {
|
|
Object.keys(terminalSessions).forEach(cleanUpTerminal);
|
|
terminalModal.style.display = 'none';
|
|
activeContainerId = null;
|
|
console.log('[INFO] All terminal sessions cleaned up.');
|
|
}
|
|
|
|
// Expose functions to app.js
|
|
export { startTerminal, appendTerminalOutput, cleanUpAllTerminals };
|