add container log view
This commit is contained in:
parent
3e37359e61
commit
77122a58b7
182
app.js
182
app.js
@ -338,6 +338,13 @@ function handlePeerData(data, topicId, peer) {
|
|||||||
window.inspectContainerCallback = null; // Reset the callback
|
window.inspectContainerCallback = null; // Reset the callback
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'logs':
|
||||||
|
console.log('[INFO] Handling logs output...');
|
||||||
|
if (window.handleLogOutput) {
|
||||||
|
window.handleLogOutput(response);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(`[WARN] Unhandled response type: ${response.type}`);
|
console.warn(`[WARN] Unhandled response type: ${response.type}`);
|
||||||
@ -403,40 +410,40 @@ function addConnection(topicHex) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add Docker Terminal button event listener
|
// Add Docker Terminal button event listener
|
||||||
connectionItem.querySelector('.docker-terminal-btn')?.addEventListener('click', (event) => {
|
connectionItem.querySelector('.docker-terminal-btn')?.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
console.log('[DEBUG] Docker terminal button clicked.');
|
console.log('[DEBUG] Docker terminal button clicked.');
|
||||||
|
|
||||||
if (!topicId) {
|
if (!topicId) {
|
||||||
console.error('[ERROR] Missing topicId. Cannot proceed.');
|
console.error('[ERROR] Missing topicId. Cannot proceed.');
|
||||||
return;
|
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);
|
|
||||||
}
|
}
|
||||||
} 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));
|
connectionItem.querySelector('span').addEventListener('click', () => switchConnection(topicId));
|
||||||
@ -640,7 +647,7 @@ function switchConnection(topicId) {
|
|||||||
resetContainerList();
|
resetContainerList();
|
||||||
|
|
||||||
console.log(`[INFO] Switched to connection: ${topicId}`);
|
console.log(`[INFO] Switched to connection: ${topicId}`);
|
||||||
|
|
||||||
// Start the stats interval
|
// Start the stats interval
|
||||||
startStatsInterval();
|
startStatsInterval();
|
||||||
|
|
||||||
@ -688,33 +695,37 @@ function renderContainers(containers, topicId) {
|
|||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.dataset.containerId = containerId; // Store container ID for reference
|
row.dataset.containerId = containerId; // Store container ID for reference
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${name}</td>
|
<td>${name}</td>
|
||||||
<td>${image}</td>
|
<td>${image}</td>
|
||||||
<td>${container.State || 'Unknown'}</td>
|
<td>${container.State || 'Unknown'}</td>
|
||||||
<td class="cpu">0</td>
|
<td class="cpu">0</td>
|
||||||
<td class="memory">0</td>
|
<td class="memory">0</td>
|
||||||
<td class="ip-address">${ipAddress}</td>
|
<td class="ip-address">${ipAddress}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-success btn-sm action-start" ${container.State === 'running' ? 'disabled' : ''}>
|
<button class="btn btn-success btn-sm action-start" ${container.State === 'running' ? 'disabled' : ''}>
|
||||||
<i class="fas fa-play"></i>
|
<i class="fas fa-play"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-info btn-sm action-restart" ${container.State !== 'running' ? 'disabled' : ''}>
|
<button class="btn btn-info btn-sm action-restart" ${container.State !== 'running' ? 'disabled' : ''}>
|
||||||
<i class="fas fa-redo"></i>
|
<i class="fas fa-redo"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-warning btn-sm action-stop" ${container.State !== 'running' ? 'disabled' : ''}>
|
<button class="btn btn-warning btn-sm action-stop" ${container.State !== 'running' ? 'disabled' : ''}>
|
||||||
<i class="fas fa-stop"></i>
|
<i class="fas fa-stop"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger btn-sm action-remove">
|
<button class="btn btn-dark btn-sm action-logs">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-binoculars"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary btn-sm action-terminal" ${container.State !== 'running' ? 'disabled' : ''}>
|
<button class="btn btn-danger btn-sm action-remove">
|
||||||
<i class="fas fa-terminal"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary btn-sm action-duplicate">
|
<button class="btn btn-primary btn-sm action-terminal" ${container.State !== 'running' ? 'disabled' : ''}>
|
||||||
<i class="fas fa-clone"></i>
|
<i class="fas fa-terminal"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
<button class="btn btn-secondary btn-sm action-duplicate">
|
||||||
`;
|
<i class="fas fa-clone"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
containerList.appendChild(row);
|
containerList.appendChild(row);
|
||||||
// Add event listener for duplicate button
|
// Add event listener for duplicate button
|
||||||
const duplicateBtn = row.querySelector('.action-duplicate');
|
const duplicateBtn = row.querySelector('.action-duplicate');
|
||||||
@ -737,18 +748,18 @@ function addActionListeners(row, container) {
|
|||||||
startBtn.addEventListener('click', async () => {
|
startBtn.addEventListener('click', async () => {
|
||||||
showStatusIndicator(`Starting container "${container.Names[0]}"...`);
|
showStatusIndicator(`Starting container "${container.Names[0]}"...`);
|
||||||
sendCommand('startContainer', { id: container.Id });
|
sendCommand('startContainer', { id: container.Id });
|
||||||
|
|
||||||
const expectedMessageFragment = `Container ${container.Id} started`;
|
const expectedMessageFragment = `Container ${container.Id} started`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await waitForPeerResponse(expectedMessageFragment);
|
const response = await waitForPeerResponse(expectedMessageFragment);
|
||||||
console.log('[DEBUG] Start container response:', response);
|
console.log('[DEBUG] Start container response:', response);
|
||||||
|
|
||||||
showAlert('success', response.message);
|
showAlert('success', response.message);
|
||||||
|
|
||||||
// Refresh the container list to update states
|
// Refresh the container list to update states
|
||||||
sendCommand('listContainers');
|
sendCommand('listContainers');
|
||||||
|
|
||||||
// Restart stats interval
|
// Restart stats interval
|
||||||
startStatsInterval();
|
startStatsInterval();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -759,23 +770,23 @@ function addActionListeners(row, container) {
|
|||||||
hideStatusIndicator();
|
hideStatusIndicator();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
stopBtn.addEventListener('click', async () => {
|
stopBtn.addEventListener('click', async () => {
|
||||||
showStatusIndicator(`Stopping container "${container.Names[0]}"...`);
|
showStatusIndicator(`Stopping container "${container.Names[0]}"...`);
|
||||||
sendCommand('stopContainer', { id: container.Id });
|
sendCommand('stopContainer', { id: container.Id });
|
||||||
|
|
||||||
const expectedMessageFragment = `Container ${container.Id} stopped`;
|
const expectedMessageFragment = `Container ${container.Id} stopped`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await waitForPeerResponse(expectedMessageFragment);
|
const response = await waitForPeerResponse(expectedMessageFragment);
|
||||||
console.log('[DEBUG] Stop container response:', response);
|
console.log('[DEBUG] Stop container response:', response);
|
||||||
|
|
||||||
showAlert('success', response.message);
|
showAlert('success', response.message);
|
||||||
|
|
||||||
// Refresh the container list to update states
|
// Refresh the container list to update states
|
||||||
sendCommand('listContainers');
|
sendCommand('listContainers');
|
||||||
|
|
||||||
// Restart stats interval
|
// Restart stats interval
|
||||||
startStatsInterval();
|
startStatsInterval();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -786,7 +797,7 @@ function addActionListeners(row, container) {
|
|||||||
hideStatusIndicator();
|
hideStatusIndicator();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Restart Button
|
// 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
|
// Remove Button
|
||||||
removeBtn.addEventListener('click', async () => {
|
removeBtn.addEventListener('click', async () => {
|
||||||
|
14
index.html
14
index.html
@ -531,6 +531,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="logsModal" tabindex="-1" aria-labelledby="logsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content bg-dark text-white">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="logsModalLabel">Container Logs</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="logs-container" style="max-height: 70vh; overflow-y: auto; font-family: monospace; background: black; padding: 10px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Alert Container -->
|
<!-- Alert Container -->
|
||||||
<div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3"
|
<div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3"
|
||||||
style="z-index: 1051; max-width: 90%;"></div>
|
style="z-index: 1051; max-width: 90%;"></div>
|
||||||
|
@ -162,6 +162,37 @@ swarm.on('connection', (peer) => {
|
|||||||
}
|
}
|
||||||
break;
|
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':
|
case 'duplicateContainer':
|
||||||
console.log('[INFO] Handling \'duplicateContainer\' command');
|
console.log('[INFO] Handling \'duplicateContainer\' command');
|
||||||
const { name, image, hostname, netmode, cpu, memory, config: dupConfig } = parsedData.args;
|
const { name, image, hostname, netmode, cpu, memory, config: dupConfig } = parsedData.args;
|
||||||
|
Loading…
Reference in New Issue
Block a user