diff --git a/app.js b/app.js
index 8d3e893..112184d 100644
--- a/app.js
+++ b/app.js
@@ -338,6 +338,13 @@ function handlePeerData(data, topicId, peer) {
window.inspectContainerCallback = null; // Reset the callback
}
break;
+
+ case 'logs':
+ console.log('[INFO] Handling logs output...');
+ if (window.handleLogOutput) {
+ window.handleLogOutput(response);
+ }
+ break;
default:
console.warn(`[WARN] Unhandled response type: ${response.type}`);
@@ -403,40 +410,40 @@ function addConnection(topicHex) {
`;
-// Add Docker Terminal button event listener
-connectionItem.querySelector('.docker-terminal-btn')?.addEventListener('click', (event) => {
- event.stopPropagation();
+ // Add Docker Terminal button event listener
+ connectionItem.querySelector('.docker-terminal-btn')?.addEventListener('click', (event) => {
+ event.stopPropagation();
- console.log('[DEBUG] Docker terminal button clicked.');
+ console.log('[DEBUG] Docker terminal button clicked.');
- if (!topicId) {
- console.error('[ERROR] Missing topicId. Cannot proceed.');
- return;
- }
-
- const connection = connections[topicId];
- console.log(`[DEBUG] Retrieved connection for topicId: ${topicId}`, connection);
-
- if (connection && connection.peer) {
- try {
- console.log(`[DEBUG] Starting Docker terminal for topicId: ${topicId}`);
- startDockerTerminal(topicId, connection.peer);
-
- const dockerTerminalModal = document.getElementById('dockerTerminalModal');
- if (dockerTerminalModal) {
- const modalInstance = new bootstrap.Modal(dockerTerminalModal);
- modalInstance.show();
- console.log('[DEBUG] Docker Terminal modal displayed.');
- } else {
- console.error('[ERROR] Docker Terminal modal not found in the DOM.');
- }
- } catch (error) {
- console.error(`[ERROR] Failed to start Docker CLI terminal for topicId: ${topicId}`, error);
+ if (!topicId) {
+ console.error('[ERROR] Missing topicId. Cannot proceed.');
+ return;
}
- } else {
- console.warn(`[WARNING] No active peer found for topicId: ${topicId}. Unable to start Docker CLI terminal.`);
- }
-});
+
+ const connection = connections[topicId];
+ console.log(`[DEBUG] Retrieved connection for topicId: ${topicId}`, connection);
+
+ if (connection && connection.peer) {
+ try {
+ console.log(`[DEBUG] Starting Docker terminal for topicId: ${topicId}`);
+ startDockerTerminal(topicId, connection.peer);
+
+ const dockerTerminalModal = document.getElementById('dockerTerminalModal');
+ if (dockerTerminalModal) {
+ const modalInstance = new bootstrap.Modal(dockerTerminalModal);
+ modalInstance.show();
+ console.log('[DEBUG] Docker Terminal modal displayed.');
+ } else {
+ console.error('[ERROR] Docker Terminal modal not found in the DOM.');
+ }
+ } catch (error) {
+ console.error(`[ERROR] Failed to start Docker CLI terminal for topicId: ${topicId}`, error);
+ }
+ } else {
+ console.warn(`[WARNING] No active peer found for topicId: ${topicId}. Unable to start Docker CLI terminal.`);
+ }
+ });
connectionItem.querySelector('span').addEventListener('click', () => switchConnection(topicId));
@@ -640,7 +647,7 @@ function switchConnection(topicId) {
resetContainerList();
console.log(`[INFO] Switched to connection: ${topicId}`);
-
+
// Start the stats interval
startStatsInterval();
@@ -688,33 +695,37 @@ function renderContainers(containers, topicId) {
const row = document.createElement('tr');
row.dataset.containerId = containerId; // Store container ID for reference
row.innerHTML = `
-
${name} |
- ${image} |
- ${container.State || 'Unknown'} |
- 0 |
- 0 |
- ${ipAddress} |
-
-
-
-
-
-
-
- |
- `;
+ ${name} |
+ ${image} |
+ ${container.State || 'Unknown'} |
+ 0 |
+ 0 |
+ ${ipAddress} |
+
+
+
+
+
+
+
+
+
+ |
+`;
containerList.appendChild(row);
// Add event listener for duplicate button
const duplicateBtn = row.querySelector('.action-duplicate');
@@ -737,18 +748,18 @@ function addActionListeners(row, container) {
startBtn.addEventListener('click', async () => {
showStatusIndicator(`Starting container "${container.Names[0]}"...`);
sendCommand('startContainer', { id: container.Id });
-
+
const expectedMessageFragment = `Container ${container.Id} started`;
-
+
try {
const response = await waitForPeerResponse(expectedMessageFragment);
console.log('[DEBUG] Start container response:', response);
-
+
showAlert('success', response.message);
-
+
// Refresh the container list to update states
sendCommand('listContainers');
-
+
// Restart stats interval
startStatsInterval();
} catch (error) {
@@ -759,23 +770,23 @@ function addActionListeners(row, container) {
hideStatusIndicator();
}
});
-
+
stopBtn.addEventListener('click', async () => {
showStatusIndicator(`Stopping container "${container.Names[0]}"...`);
sendCommand('stopContainer', { id: container.Id });
-
+
const expectedMessageFragment = `Container ${container.Id} stopped`;
-
+
try {
const response = await waitForPeerResponse(expectedMessageFragment);
console.log('[DEBUG] Stop container response:', response);
-
+
showAlert('success', response.message);
-
+
// Refresh the container list to update states
sendCommand('listContainers');
-
+
// Restart stats interval
startStatsInterval();
} catch (error) {
@@ -786,7 +797,7 @@ function addActionListeners(row, container) {
hideStatusIndicator();
}
});
-
+
// Restart Button
@@ -813,6 +824,35 @@ function addActionListeners(row, container) {
}
});
+ const logsBtn = row.querySelector('.action-logs');
+ logsBtn.addEventListener('click', () => openLogModal(container.Id));
+
+ function openLogModal(containerId) {
+ console.log(`[INFO] Opening logs modal for container: ${containerId}`);
+
+ const modal = new bootstrap.Modal(document.getElementById('logsModal'));
+ const logContainer = document.getElementById('logs-container');
+
+ // Clear any existing logs
+ logContainer.innerHTML = '';
+
+ // Request previous logs
+ sendCommand('logs', { id: containerId });
+
+ // Listen for logs
+ window.handleLogOutput = (logData) => {
+ const logLine = atob(logData.data); // Decode base64 logs
+ const logElement = document.createElement('pre');
+ logElement.textContent = logLine;
+ logContainer.appendChild(logElement);
+
+ // Scroll to the bottom
+ logContainer.scrollTop = logContainer.scrollHeight;
+ };
+
+ // Show the modal
+ modal.show();
+ }
// Remove Button
removeBtn.addEventListener('click', async () => {
diff --git a/index.html b/index.html
index 7dc0969..dbab3f7 100644
--- a/index.html
+++ b/index.html
@@ -531,6 +531,20 @@
+
+
diff --git a/server/server.js b/server/server.js
index ead7bb9..335d3ec 100644
--- a/server/server.js
+++ b/server/server.js
@@ -162,6 +162,37 @@ swarm.on('connection', (peer) => {
}
break;
+
+ case 'logs':
+ console.log(`[INFO] Handling 'logs' command for container: ${parsedData.args.id}`);
+ const logsContainer = docker.getContainer(parsedData.args.id);
+ const logsStream = await logsContainer.logs({
+ stdout: true,
+ stderr: true,
+ tail: 100, // Fetch the last 100 log lines
+ follow: true, // Stream live logs
+ });
+
+ logsStream.on('data', (chunk) => {
+ peer.write(
+ JSON.stringify({
+ type: 'logs',
+ data: chunk.toString('base64'), // Send base64 encoded logs
+ })
+ );
+ });
+
+ logsStream.on('end', () => {
+ console.log(`[INFO] Log stream ended for container: ${parsedData.args.id}`);
+ });
+
+ logsStream.on('error', (err) => {
+ console.error(`[ERROR] Log stream error for container ${parsedData.args.id}: ${err.message}`);
+ peer.write(JSON.stringify({ error: `Log stream error: ${err.message}` }));
+ });
+
+ break;
+
case 'duplicateContainer':
console.log('[INFO] Handling \'duplicateContainer\' command');
const { name, image, hostname, netmode, cpu, memory, config: dupConfig } = parsedData.args;