diff --git a/app.js b/app.js
index 47e26d7..eb1382f 100644
--- a/app.js
+++ b/app.js
@@ -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}
-
+ ${message}
+
`;
+
+ // 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 = `
-
${name} |
- ${image} |
- ${container.State} |
- 0 |
- 0 |
- ${ipAddress} |
-
-
-
-
-
-
- |
- `;
+ ${name} |
+ ${image} |
+ ${container.State} |
+ 0 |
+ 0 |
+ ${ipAddress} |
+
+
+
+
+
+
+
+ |
+`;
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
diff --git a/index.html b/index.html
index c7527ef..e4402a3 100644
--- a/index.html
+++ b/index.html
@@ -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);
+ }
}
diff --git a/server/server.js b/server/server.js
index 851b3ee..802bdec 100644
--- a/server/server.js
+++ b/server/server.js
@@ -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 });