Add restart to actions

This commit is contained in:
Raven Scott 2024-11-29 23:30:18 -05:00
parent 964c6c60f3
commit 4f13281873
3 changed files with 148 additions and 41 deletions

105
app.js
View File

@ -105,27 +105,31 @@ function hideStatusIndicator() {
// Show Alert
function showAlert(type, message) {
const alertContainer = document.getElementById('alert-container');
// Create alert element
const alert = document.createElement('div');
alert.className = `alert alert-${type} alert-dismissible fade show`;
alert.role = 'alert';
alert.className = `alert ${type}`;
alert.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<span>${message}</span>
<button class="close-btn" aria-label="Close">&times;</button>
`;
// Add close button functionality
const closeButton = alert.querySelector('.close-btn');
closeButton.addEventListener('click', () => {
alert.remove(); // Remove alert on close
});
// Append alert to container
alertContainer.appendChild(alert);
// Automatically hide the status indicator for errors or success
if (type === 'danger' || type === 'success') {
hideStatusIndicator();
}
// Automatically remove the alert after 5 seconds
// Automatically remove alert after 5 seconds
setTimeout(() => {
alert.classList.remove('show');
setTimeout(() => alert.remove(), 300); // Bootstrap's fade-out transition duration
alert.remove();
}, 5000);
}
// Collapse Sidebar Functionality
const collapseSidebarBtn = document.getElementById('collapse-sidebar-btn');
collapseSidebarBtn.addEventListener('click', () => {
@ -454,30 +458,33 @@ function renderContainers(containers) {
const row = document.createElement('tr');
row.dataset.containerId = containerId; // Store container ID for reference
row.innerHTML = `
<td>${name}</td>
<td>${image}</td>
<td>${container.State}</td>
<td class="cpu">0</td>
<td class="memory">0</td>
<td class="ip-address">${ipAddress}</td>
<td>
<button class="btn btn-success btn-sm action-start" ${container.State === 'running' ? 'disabled' : ''}>
<i class="fas fa-play"></i>
</button>
<button class="btn btn-warning btn-sm action-stop" ${container.State !== 'running' ? 'disabled' : ''}>
<i class="fas fa-stop"></i>
</button>
<button class="btn btn-danger btn-sm action-remove">
<i class="fas fa-trash"></i>
</button>
<button class="btn btn-primary btn-sm action-terminal" ${container.State !== 'running' ? 'disabled' : ''}>
<i class="fas fa-terminal"></i>
</button>
<button class="btn btn-secondary btn-sm action-duplicate">
<i class="fas fa-clone"></i>
</button>
</td>
`;
<td>${name}</td>
<td>${image}</td>
<td>${container.State}</td>
<td class="cpu">0</td>
<td class="memory">0</td>
<td class="ip-address">${ipAddress}</td>
<td>
<button class="btn btn-success btn-sm action-start" ${container.State === 'running' ? 'disabled' : ''}>
<i class="fas fa-play"></i>
</button>
<button class="btn btn-warning btn-sm action-stop" ${container.State !== 'running' ? 'disabled' : ''}>
<i class="fas fa-stop"></i>
</button>
<button class="btn btn-danger btn-sm action-remove">
<i class="fas fa-trash"></i>
</button>
<button class="btn btn-primary btn-sm action-terminal" ${container.State !== 'running' ? 'disabled' : ''}>
<i class="fas fa-terminal"></i>
</button>
<button class="btn btn-secondary btn-sm action-duplicate">
<i class="fas fa-clone"></i>
</button>
<button class="btn btn-info btn-sm action-restart" ${container.State !== 'running' ? 'disabled' : ''}>
<i class="fas fa-redo"></i>
</button>
</td>
`;
containerList.appendChild(row);
// Add event listeners for action buttons
@ -494,6 +501,7 @@ function addActionListeners(row, container) {
const stopBtn = row.querySelector('.action-stop');
const removeBtn = row.querySelector('.action-remove');
const terminalBtn = row.querySelector('.action-terminal');
const restartBtn = row.querySelector('.action-restart');
// Start Button
startBtn.addEventListener('click', async () => {
@ -541,6 +549,31 @@ function addActionListeners(row, container) {
hideStatusIndicator();
}
});
// Restart Button
restartBtn.addEventListener('click', async () => {
showStatusIndicator(`Restarting container "${container.Names[0]}"...`);
sendCommand('restartContainer', { id: container.Id });
const expectedMessageFragment = `Container ${container.Id} restarted`;
try {
const response = await waitForPeerResponse(expectedMessageFragment);
console.log('[DEBUG] Restart container response:', response);
showAlert('success', response.message);
// Refresh the container list to update states
sendCommand('listContainers');
} catch (error) {
console.error('[ERROR] Failed to restart container:', error.message);
showAlert('danger', error.message || 'Failed to restart container.');
} finally {
console.log('[DEBUG] Hiding status indicator in restartBtn finally block');
hideStatusIndicator();
}
});
// Remove Button

View File

@ -226,6 +226,74 @@
#dashboard.hidden {
display: none !important; /* Hide the dashboard completely when not needed */
}
#alert-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1055; /* Ensure it overlays other elements */
display: flex;
flex-direction: column-reverse; /* Stack alerts upwards */
gap: 10px; /* Add space between alerts */
}
.alert {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 80%; /* Ensure the alert doesn't stretch too wide */
padding: 12px 20px; /* Adjust padding for a more balanced look */
background-color: #2b2b2b; /* Slightly lighter background for better contrast */
color: #e0e0e0; /* Softer white text for readability */
border-radius: 6px; /* Round corners for a modern look */
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5); /* Add subtle shadow for depth */
font-family: Arial, sans-serif;
font-size: 14px; /* Adjust font size for readability */
animation: fadeIn 0.3s ease-out, fadeOut 4.5s ease-in forwards;
white-space: nowrap; /* Prevent text wrapping */
overflow: hidden; /* Hide overflow for long text */
text-overflow: ellipsis; /* Add ellipsis for overflowed text */
}
.alert.success {
border-left: 6px solid #28a745; /* Green border for success */
}
.alert.danger {
border-left: 6px solid #dc3545; /* Red border for danger */
}
.alert .close-btn {
background: none;
border: none;
color: #e0e0e0; /* Use the same text color for consistency */
font-size: 16px;
cursor: pointer;
margin-left: 15px; /* Space between text and close button */
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
90% {
opacity: 0.3;
}
100% {
opacity: 0;
transform: translateY(10px);
}
}
</style>
</head>

View File

@ -60,12 +60,12 @@ swarm.on('connection', (peer) => {
console.log('[INFO] Handling \'listContainers\' command');
try {
const containers = await docker.listContainers({ all: true });
const detailedContainers = await Promise.all(
containers.map(async (container) => {
try {
const details = await docker.getContainer(container.Id).inspect();
// Safely access the IP address
let ipAddress = 'No IP Assigned';
if (details.NetworkSettings && details.NetworkSettings.Networks) {
@ -74,7 +74,7 @@ swarm.on('connection', (peer) => {
ipAddress = networks[0].IPAddress;
}
}
return { ...container, ipAddress }; // Add IP address to container data
} catch (error) {
console.error(`[ERROR] Failed to inspect container ${container.Id}: ${error.message}`);
@ -82,14 +82,14 @@ swarm.on('connection', (peer) => {
}
})
);
response = { type: 'containers', data: detailedContainers };
} catch (error) {
console.error(`[ERROR] Failed to list containers: ${error.message}`);
response = { error: 'Failed to list containers' };
}
break;
case 'inspectContainer':
console.log(`[INFO] Handling 'inspectContainer' command for container: ${parsedData.args.id}`);
const container = docker.getContainer(parsedData.args.id);
@ -118,6 +118,12 @@ swarm.on('connection', (peer) => {
response = { success: true, message: `Container ${parsedData.args.id} stopped` };
break;
case 'restartContainer':
console.log(`[INFO] Handling 'restartContainer' command for container: ${parsedData.args.id}`);
await docker.getContainer(parsedData.args.id).restart();
response = { success: true, message: `Container ${parsedData.args.id} restarted` };
break;
case 'removeContainer':
console.log(`[INFO] Handling 'removeContainer' command for container: ${parsedData.args.id}`);
await docker.getContainer(parsedData.args.id).remove({ force: true });