diff --git a/app.js b/app.js
index b0d6c77..e1ccba2 100644
--- a/app.js
+++ b/app.js
@@ -380,14 +380,31 @@ function addConnection(topicHex) {
connectionItem.className = 'list-group-item d-flex align-items-center justify-content-between';
connectionItem.dataset.topicId = topicId;
connectionItem.innerHTML = `
-
- ${topicId}
-
-
- `;
+
+ ${topicId}
+
+
+
+
+
+`;
+// Add Docker Terminal button event listener
+connectionItem.querySelector('.docker-terminal-btn').addEventListener('click', (e) => {
+ e.stopPropagation();
+ const connection = connections[topicId];
+ if (connection && connection.peer) {
+ import('./dockerTerminal.js').then(({ startDockerTerminal }) => {
+ startDockerTerminal(topicId, connection.peer);
+ });
+ } else {
+ console.error('[ERROR] No active peer for Docker CLI terminal.');
+ }
+})
connectionItem.querySelector('span').addEventListener('click', () => switchConnection(topicId));
connectionItem.querySelector('.disconnect-btn').addEventListener('click', (e) => {
e.stopPropagation();
diff --git a/index.html b/index.html
index 83ed5eb..5ae7e0e 100644
--- a/index.html
+++ b/index.html
@@ -515,6 +515,19 @@
+
+
+
diff --git a/libs/dockerTerminal.js b/libs/dockerTerminal.js
new file mode 100644
index 0000000..31758da
--- /dev/null
+++ b/libs/dockerTerminal.js
@@ -0,0 +1,100 @@
+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;
+
+// Start Docker CLI terminal session
+function startDockerTerminal(connectionId, peer) {
+ if (!peer) {
+ console.error('[ERROR] No active peer for Docker CLI terminal.');
+ return;
+ }
+
+ if (dockerTerminalSession) {
+ console.log(`[INFO] Docker CLI terminal session already exists for connection: ${connectionId}`);
+ return;
+ }
+
+ console.log(`[INFO] Starting Docker CLI terminal for connection: ${connectionId}`);
+
+ const xterm = new Terminal({
+ cursorBlink: true,
+ theme: { background: '#1a1a1a', foreground: '#ffffff' },
+ });
+ const fitAddon = new FitAddon();
+ xterm.loadAddon(fitAddon);
+
+ dockerTerminalContainer.innerHTML = ''; // Clear previous content
+ xterm.open(dockerTerminalContainer);
+ fitAddon.fit();
+
+ dockerTerminalSession = { xterm, fitAddon, connectionId, peer };
+
+ xterm.onData((input) => {
+ const sanitizedInput = sanitizeDockerCommand(input.trim());
+ if (sanitizedInput) {
+ console.log(`[DEBUG] Sending Docker CLI command: ${sanitizedInput}`);
+ peer.write(
+ JSON.stringify({
+ type: 'dockerCommand',
+ data: sanitizedInput,
+ })
+ );
+ } else {
+ xterm.write('\r\n[ERROR] Invalid command. Only Docker CLI commands are allowed.\r\n');
+ }
+ });
+
+ peer.on('data', (data) => {
+ try {
+ const response = JSON.parse(data.toString());
+ if (response.type === 'dockerOutput' && response.connectionId === connectionId) {
+ xterm.write(response.data);
+ }
+ } catch (error) {
+ console.error(`[ERROR] Failed to parse response from peer: ${error.message}`);
+ }
+ });
+
+ dockerTerminalTitle.textContent = `Docker CLI Terminal: ${connectionId}`;
+ dockerTerminalModal.style.display = 'flex';
+}
+
+// Sanitize input to ensure only Docker CLI commands are allowed
+function sanitizeDockerCommand(command) {
+ const allowedCommands = ['docker', 'docker-compose'];
+ const parts = command.split(/\s+/);
+ const baseCommand = parts[0];
+
+ if (allowedCommands.includes(baseCommand)) {
+ return command; // Valid Docker command
+ }
+
+ return null; // Invalid command
+}
+
+// Clean up Docker CLI terminal session
+function cleanUpDockerTerminal() {
+ if (dockerTerminalSession) {
+ dockerTerminalSession.xterm.dispose();
+ dockerTerminalSession = null;
+ dockerTerminalContainer.innerHTML = '';
+ dockerTerminalModal.style.display = 'none';
+ console.log('[INFO] Docker CLI terminal session cleaned up.');
+ }
+}
+
+// Handle Kill Terminal button
+dockerKillTerminalBtn.onclick = () => {
+ cleanUpDockerTerminal();
+};
+
+// Export functions
+export { startDockerTerminal, cleanUpDockerTerminal };
diff --git a/server/server.js b/server/server.js
index 0f9aa3b..45377dd 100644
--- a/server/server.js
+++ b/server/server.js
@@ -55,7 +55,7 @@ swarm.on('connection', (peer) => {
if (!(parsedData.command === 'stats' && Object.keys(parsedData.args).length === 0)) {
console.log(`[DEBUG] Received data from peer: ${JSON.stringify(parsedData)}`);
}
- let response;
+ let response;
switch (parsedData.command) {
case 'listContainers':
@@ -107,7 +107,44 @@ swarm.on('connection', (peer) => {
await duplicateContainer(name, image, hostname, netmode, cpu, memoryInMB, dupConfig, peer);
return; // Response is handled within the duplicateContainer function
+ case 'dockerCommand':
+ console.log(`[INFO] Executing Docker CLI command: ${parsedData.data}`);
+ try {
+ const exec = spawn('sh', ['-c', parsedData.data]);
+ exec.stdout.on('data', (output) => {
+ peer.write(
+ JSON.stringify({
+ type: 'dockerOutput',
+ connectionId: parsedData.connectionId,
+ data: output.toString(),
+ })
+ );
+ });
+
+ exec.stderr.on('data', (error) => {
+ peer.write(
+ JSON.stringify({
+ type: 'dockerOutput',
+ connectionId: parsedData.connectionId,
+ data: `[ERROR] ${error.toString()}`,
+ })
+ );
+ });
+
+ exec.on('close', (code) => {
+ peer.write(
+ JSON.stringify({
+ type: 'dockerOutput',
+ connectionId: parsedData.connectionId,
+ data: `[INFO] Command exited with code ${code}\n`,
+ })
+ );
+ });
+ } catch (error) {
+ peer.write(JSON.stringify({ error: `Failed to execute command: ${error.message}` }));
+ }
+ break;
case 'startContainer':
console.log(`[INFO] Handling 'startContainer' command for container: ${parsedData.args.id}`);
await docker.getContainer(parsedData.args.id).start();