forked from snxraven/peardock
add indicators for tasks
This commit is contained in:
parent
ebe25cbe1c
commit
e33023e250
254
app.js
254
app.js
@ -20,9 +20,94 @@ const connections = {};
|
|||||||
window.openTerminals = {};
|
window.openTerminals = {};
|
||||||
let activePeer = null;
|
let activePeer = null;
|
||||||
window.activePeer = null; // Expose to other modules
|
window.activePeer = null; // Expose to other modules
|
||||||
|
hideStatusIndicator();
|
||||||
|
|
||||||
|
|
||||||
|
function waitForPeerResponse(expectedMessageFragment, timeout = 900000) {
|
||||||
|
console.log(`[DEBUG] Waiting for peer response with fragment: "${expectedMessageFragment}"`);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
window.handlePeerResponse = (response) => {
|
||||||
|
console.log(`[DEBUG] Received response: ${JSON.stringify(response)}`);
|
||||||
|
if (response && response.success && response.message.includes(expectedMessageFragment)) {
|
||||||
|
console.log(`[DEBUG] Expected response received: ${response.message}`);
|
||||||
|
resolve(response);
|
||||||
|
} else if (Date.now() - startTime > timeout) {
|
||||||
|
console.warn('[WARN] Timeout while waiting for peer response');
|
||||||
|
reject(new Error('Timeout waiting for peer response'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Timeout fallback
|
||||||
|
setTimeout(() => {
|
||||||
|
console.warn('[WARN] Timed out waiting for response');
|
||||||
|
reject(new Error('Timed out waiting for peer response'));
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Initialize the app
|
// Initialize the app
|
||||||
console.log('[INFO] Client app initialized');
|
console.log('[INFO] Client app initialized');
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const statusIndicator = document.getElementById('status-indicator');
|
||||||
|
if (statusIndicator) {
|
||||||
|
statusIndicator.remove();
|
||||||
|
console.log('[INFO] Status indicator removed from DOM on load');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Show Status Indicator
|
||||||
|
// Modify showStatusIndicator to recreate it dynamically
|
||||||
|
function showStatusIndicator(message = 'Processing...') {
|
||||||
|
const statusIndicator = document.createElement('div');
|
||||||
|
statusIndicator.id = 'status-indicator';
|
||||||
|
statusIndicator.className = 'position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-dark bg-opacity-75';
|
||||||
|
statusIndicator.innerHTML = `
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-light" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 text-light">${message}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(statusIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideStatusIndicator() {
|
||||||
|
const statusIndicator = document.getElementById('status-indicator');
|
||||||
|
if (statusIndicator) {
|
||||||
|
console.log('[DEBUG] Hiding status indicator');
|
||||||
|
statusIndicator.remove();
|
||||||
|
} else {
|
||||||
|
console.error('[ERROR] Status indicator element not found!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Show Alert
|
||||||
|
function showAlert(type, message) {
|
||||||
|
const alertContainer = document.getElementById('alert-container');
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
||||||
|
alert.role = 'alert';
|
||||||
|
alert.innerHTML = `
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
`;
|
||||||
|
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
|
||||||
|
setTimeout(() => {
|
||||||
|
alert.classList.remove('show');
|
||||||
|
setTimeout(() => alert.remove(), 300); // Bootstrap's fade-out transition duration
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
// Collapse Sidebar Functionality
|
// Collapse Sidebar Functionality
|
||||||
const collapseSidebarBtn = document.getElementById('collapse-sidebar-btn');
|
const collapseSidebarBtn = document.getElementById('collapse-sidebar-btn');
|
||||||
@ -38,10 +123,20 @@ function handlePeerData(data, topicId, peer) {
|
|||||||
const response = JSON.parse(data.toString());
|
const response = JSON.parse(data.toString());
|
||||||
console.log(`[DEBUG] Received data from peer (topic: ${topicId}): ${JSON.stringify(response)}`);
|
console.log(`[DEBUG] Received data from peer (topic: ${topicId}): ${JSON.stringify(response)}`);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error(`[ERROR] Server error: ${response.error}`);
|
||||||
|
showAlert('danger', response.error);
|
||||||
|
hideStatusIndicator();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.type === 'containers') {
|
if (response.type === 'containers') {
|
||||||
if (window.activePeer === peer) {
|
if (window.activePeer === peer) {
|
||||||
renderContainers(response.data);
|
renderContainers(response.data);
|
||||||
}
|
}
|
||||||
|
} else if (response.type === 'stats') {
|
||||||
|
console.log(`[DEBUG] Updating stats for container: ${response.data.id}`);
|
||||||
|
updateContainerStats(response.data); // Call the stats update function
|
||||||
} else if (response.type === 'terminalOutput') {
|
} else if (response.type === 'terminalOutput') {
|
||||||
appendTerminalOutput(response.data, response.containerId, response.encoding);
|
appendTerminalOutput(response.data, response.containerId, response.encoding);
|
||||||
} else if (response.type === 'containerConfig') {
|
} else if (response.type === 'containerConfig') {
|
||||||
@ -49,13 +144,14 @@ function handlePeerData(data, topicId, peer) {
|
|||||||
window.inspectContainerCallback(response.data);
|
window.inspectContainerCallback(response.data);
|
||||||
window.inspectContainerCallback = null; // Reset the callback
|
window.inspectContainerCallback = null; // Reset the callback
|
||||||
}
|
}
|
||||||
} else if (response.type === 'stats') {
|
}
|
||||||
updateContainerStats(response.data);
|
|
||||||
} else if (response.error) {
|
if (typeof window.handlePeerResponse === 'function') {
|
||||||
console.error(`[ERROR] Server error: ${response.error}`);
|
window.handlePeerResponse(response);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[ERROR] Failed to parse data from peer (topic: ${topicId}): ${err.message}`);
|
console.error(`[ERROR] Failed to process peer data: ${err.message}`);
|
||||||
|
showAlert('danger', 'Failed to process peer data.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +443,7 @@ function renderContainers(containers) {
|
|||||||
// Add event listener for duplicate button
|
// Add event listener for duplicate button
|
||||||
const duplicateBtn = row.querySelector('.action-duplicate');
|
const duplicateBtn = row.querySelector('.action-duplicate');
|
||||||
duplicateBtn.addEventListener('click', () => openDuplicateModal(container));
|
duplicateBtn.addEventListener('click', () => openDuplicateModal(container));
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,63 +453,120 @@ function addActionListeners(row, container) {
|
|||||||
const removeBtn = row.querySelector('.action-remove');
|
const removeBtn = row.querySelector('.action-remove');
|
||||||
const terminalBtn = row.querySelector('.action-terminal');
|
const terminalBtn = row.querySelector('.action-terminal');
|
||||||
|
|
||||||
startBtn.addEventListener('click', () => {
|
// Start Button
|
||||||
|
startBtn.addEventListener('click', async () => {
|
||||||
|
showStatusIndicator(`Starting container "${container.Names[0]}"...`);
|
||||||
sendCommand('startContainer', { id: container.Id });
|
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');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ERROR] Failed to start container:', error.message);
|
||||||
|
showAlert('danger', error.message || 'Failed to start container.');
|
||||||
|
} finally {
|
||||||
|
console.log('[DEBUG] Hiding status indicator in startBtn finally block');
|
||||||
|
hideStatusIndicator();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stopBtn.addEventListener('click', () => {
|
stopBtn.addEventListener('click', async () => {
|
||||||
|
showStatusIndicator(`Stopping container "${container.Names[0]}"...`);
|
||||||
sendCommand('stopContainer', { id: container.Id });
|
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');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ERROR] Failed to stop container:', error.message);
|
||||||
|
showAlert('danger', error.message || 'Failed to stop container.');
|
||||||
|
} finally {
|
||||||
|
console.log('[DEBUG] Hiding status indicator in stopBtn finally block');
|
||||||
|
hideStatusIndicator();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
removeBtn.addEventListener('click', () => {
|
|
||||||
// Show the delete confirmation modal
|
// Remove Button
|
||||||
|
removeBtn.addEventListener('click', async () => {
|
||||||
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
||||||
deleteModal.show();
|
deleteModal.show();
|
||||||
|
|
||||||
// Handle confirmation button
|
|
||||||
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
||||||
confirmDeleteBtn.onclick = () => {
|
confirmDeleteBtn.onclick = async () => {
|
||||||
console.log(`[INFO] Deleting container: ${container.Id}`);
|
deleteModal.hide();
|
||||||
|
|
||||||
|
showStatusIndicator(`Deleting container "${container.Names[0]}"...`);
|
||||||
|
|
||||||
// Check if the container has active terminals
|
// Check if the container has active terminals
|
||||||
if (window.openTerminals[container.Id]) {
|
if (window.openTerminals[container.Id]) {
|
||||||
console.log(`[INFO] Closing active terminals for container: ${container.Id}`);
|
console.log(`[INFO] Closing active terminals for container: ${container.Id}`);
|
||||||
window.openTerminals[container.Id].forEach((terminalId) => {
|
window.openTerminals[container.Id].forEach((terminalId) => {
|
||||||
try {
|
try {
|
||||||
cleanUpTerminal(terminalId); // Use your terminal.js cleanup logic
|
cleanUpTerminal(terminalId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[ERROR] Failed to clean up terminal ${terminalId}: ${err.message}`);
|
console.error(`[ERROR] Failed to clean up terminal ${terminalId}: ${err.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
delete window.openTerminals[container.Id]; // Remove from open terminals
|
delete window.openTerminals[container.Id];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the terminal modal is active and hide it
|
// Hide the terminal modal if it is active
|
||||||
const terminalModal = document.getElementById('terminal-modal');
|
const terminalModal = document.getElementById('terminal-modal');
|
||||||
if (terminalModal.style.display === 'flex') {
|
if (terminalModal.style.display === 'flex') {
|
||||||
console.log(`[INFO] Hiding terminal modal for container: ${container.Id}`);
|
console.log(`[INFO] Hiding terminal modal for container: ${container.Id}`);
|
||||||
terminalModal.style.display = 'none';
|
terminalModal.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the removeContainer command
|
|
||||||
sendCommand('removeContainer', { id: container.Id });
|
sendCommand('removeContainer', { id: container.Id });
|
||||||
deleteModal.hide(); // Close the delete confirmation modal
|
|
||||||
|
|
||||||
// Remove the container row from the container list
|
const expectedMessageFragment = `Container ${container.Id} removed`;
|
||||||
const row = containerList.querySelector(`tr[data-container-id="${container.Id}"]`);
|
|
||||||
if (row) {
|
try {
|
||||||
row.remove();
|
const response = await waitForPeerResponse(expectedMessageFragment);
|
||||||
console.log(`[INFO] Removed container row for container ID: ${container.Id}`);
|
console.log('[DEBUG] Remove container response:', response);
|
||||||
|
|
||||||
|
showAlert('success', response.message);
|
||||||
|
|
||||||
|
// Refresh the container list to update states
|
||||||
|
sendCommand('listContainers');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ERROR] Failed to delete container:', error.message);
|
||||||
|
showAlert('danger', error.message || `Failed to delete container "${container.Names[0]}".`);
|
||||||
|
} finally {
|
||||||
|
console.log('[DEBUG] Hiding status indicator in removeBtn finally block');
|
||||||
|
hideStatusIndicator();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
terminalBtn.addEventListener('click', () => {
|
terminalBtn.addEventListener('click', () => {
|
||||||
|
console.log(`[DEBUG] Opening terminal for container ID: ${container.Id}`);
|
||||||
|
try {
|
||||||
startTerminal(container.Id, container.Names[0] || container.Id);
|
startTerminal(container.Id, container.Names[0] || container.Id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[ERROR] Failed to start terminal for container ${container.Id}: ${error.message}`);
|
||||||
|
showAlert('danger', `Failed to start terminal: ${error.message}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Function to update container statistics
|
// Function to update container statistics
|
||||||
function updateContainerStats(stats) {
|
function updateContainerStats(stats) {
|
||||||
console.log(`[DEBUG] Updating stats for container ID: ${stats.id}`);
|
console.log(`[DEBUG] Updating stats for container ID: ${stats.id}`);
|
||||||
@ -444,30 +598,41 @@ function updateContainerStats(stats) {
|
|||||||
function openDuplicateModal(container) {
|
function openDuplicateModal(container) {
|
||||||
console.log(`[INFO] Opening Duplicate Modal for container: ${container.Id}`);
|
console.log(`[INFO] Opening Duplicate Modal for container: ${container.Id}`);
|
||||||
|
|
||||||
// Send a command to get container configurations
|
showStatusIndicator('Fetching container configuration...');
|
||||||
|
|
||||||
|
// Send a command to inspect the container
|
||||||
sendCommand('inspectContainer', { id: container.Id });
|
sendCommand('inspectContainer', { id: container.Id });
|
||||||
|
|
||||||
// Listen for the response
|
// Listen for the inspectContainer response
|
||||||
window.inspectContainerCallback = (config) => {
|
window.inspectContainerCallback = (config) => {
|
||||||
|
hideStatusIndicator();
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
alert('Failed to retrieve container configuration.');
|
console.error('[ERROR] Failed to retrieve container configuration.');
|
||||||
|
showAlert('danger', 'Failed to retrieve container configuration.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("TESTER: " + config.HostConfig?.CpusetCpus)
|
console.log(`[DEBUG] Retrieved container configuration: ${JSON.stringify(config)}`);
|
||||||
let CPUs = config.HostConfig?.CpusetCpus.split(",");
|
|
||||||
|
// Parse configuration and populate the modal fields
|
||||||
|
try {
|
||||||
|
const CPUs = config.HostConfig?.CpusetCpus?.split(',') || [];
|
||||||
|
|
||||||
// Populate the modal fields with the current configurations
|
|
||||||
document.getElementById('container-name').value = config.Name.replace(/^\//, '');
|
document.getElementById('container-name').value = config.Name.replace(/^\//, '');
|
||||||
document.getElementById('container-hostname').value = config.Config.Hostname.replace(/^\//, '');
|
document.getElementById('container-hostname').value = config.Config.Hostname || '';
|
||||||
document.getElementById('container-image').value = config.Config.Image;
|
document.getElementById('container-image').value = config.Config.Image || '';
|
||||||
document.getElementById('container-netmode').value = config.HostConfig?.NetworkMode;
|
document.getElementById('container-netmode').value = config.HostConfig?.NetworkMode || '';
|
||||||
document.getElementById('container-cpu').value = CPUs.length;
|
document.getElementById('container-cpu').value = CPUs.length || 0;
|
||||||
document.getElementById('container-memory').value = Math.round(config.HostConfig?.Memory / (1024 * 1024));
|
document.getElementById('container-memory').value = Math.round(config.HostConfig?.Memory / (1024 * 1024)) || 0;
|
||||||
document.getElementById('container-config').value = JSON.stringify(config, null, 2);
|
document.getElementById('container-config').value = JSON.stringify(config, null, 2);
|
||||||
|
|
||||||
// Show the modal
|
// Show the duplicate modal
|
||||||
duplicateModal.show();
|
duplicateModal.show();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[ERROR] Failed to populate modal fields: ${error.message}`);
|
||||||
|
showAlert('danger', 'Failed to populate container configuration fields.');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,6 +640,9 @@ function openDuplicateModal(container) {
|
|||||||
// Handle the Duplicate Container Form Submission
|
// Handle the Duplicate Container Form Submission
|
||||||
duplicateContainerForm.addEventListener('submit', (e) => {
|
duplicateContainerForm.addEventListener('submit', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
duplicateModal.hide();
|
||||||
|
|
||||||
|
showStatusIndicator('Duplicating container...');
|
||||||
|
|
||||||
const name = document.getElementById('container-name').value.trim();
|
const name = document.getElementById('container-name').value.trim();
|
||||||
const hostname = document.getElementById('container-hostname').value.trim();
|
const hostname = document.getElementById('container-hostname').value.trim();
|
||||||
@ -488,23 +656,21 @@ duplicateContainerForm.addEventListener('submit', (e) => {
|
|||||||
try {
|
try {
|
||||||
config = JSON.parse(configJSON);
|
config = JSON.parse(configJSON);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Invalid JSON in configuration.');
|
hideStatusIndicator();
|
||||||
|
showAlert('danger', 'Invalid JSON in configuration.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[INFO] Sending duplicateContainer command for name: ${name}`);
|
|
||||||
|
|
||||||
// Send the duplicate command to the server
|
|
||||||
sendCommand('duplicateContainer', { name, image, hostname, netmode, cpu, memory, config });
|
sendCommand('duplicateContainer', { name, image, hostname, netmode, cpu, memory, config });
|
||||||
|
|
||||||
// Close the modal
|
// Simulate delay for the demo
|
||||||
duplicateModal.hide();
|
|
||||||
|
|
||||||
// Trigger container list update after a short delay
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('[INFO] Fetching updated container list after duplication');
|
hideStatusIndicator();
|
||||||
|
showAlert('success', 'Container duplicated successfully!');
|
||||||
|
|
||||||
|
// Refresh container list
|
||||||
sendCommand('listContainers');
|
sendCommand('listContainers');
|
||||||
}, 2000); // Wait for duplication to complete
|
}, 2000); // Simulated processing time
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
51
index.html
51
index.html
@ -120,7 +120,6 @@
|
|||||||
#terminal-modal .header {
|
#terminal-modal .header {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
/* Change cursor to indicate drag is possible */
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -165,6 +164,30 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#status-indicator {
|
||||||
|
display: none; /* Ensure it's hidden by default */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
z-index: 1050;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status-indicator .spinner-border {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status-indicator p {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -222,7 +245,7 @@
|
|||||||
<input type="text" class="form-control" id="container-name" required>
|
<input type="text" class="form-control" id="container-name" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="container-image" class="form-label">Hostname</label>
|
<label for="container-hostname" class="form-label">Hostname</label>
|
||||||
<input type="text" class="form-control" id="container-hostname" required>
|
<input type="text" class="form-control" id="container-hostname" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -230,7 +253,7 @@
|
|||||||
<input type="text" class="form-control" id="container-image" required>
|
<input type="text" class="form-control" id="container-image" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="container-image" class="form-label">Net Mode</label>
|
<label for="container-netmode" class="form-label">Net Mode</label>
|
||||||
<input type="text" class="form-control" id="container-netmode" required>
|
<input type="text" class="form-control" id="container-netmode" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -241,7 +264,6 @@
|
|||||||
<label for="container-memory" class="form-label">Memory (MB)</label>
|
<label for="container-memory" class="form-label">Memory (MB)</label>
|
||||||
<input type="number" class="form-control" id="container-memory" required>
|
<input type="number" class="form-control" id="container-memory" required>
|
||||||
</div>
|
</div>
|
||||||
<!-- Container Configuration as JSON -->
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="container-config" class="form-label">Container Configuration (JSON)</label>
|
<label for="container-config" class="form-label">Container Configuration (JSON)</label>
|
||||||
<textarea class="form-control" id="container-config" rows="10" required></textarea>
|
<textarea class="form-control" id="container-config" rows="10" required></textarea>
|
||||||
@ -264,7 +286,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="terminal-container"></div>
|
<div id="terminal-container"></div>
|
||||||
<div id="terminal-resize-handle"></div> <!-- Resize handle -->
|
<div id="terminal-resize-handle"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Delete Confirmation Modal -->
|
<!-- Delete Confirmation Modal -->
|
||||||
@ -286,15 +308,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tray"></div>
|
<!-- Status Indicator Overlay -->
|
||||||
|
<div id="status-indicator"
|
||||||
|
class="position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-dark bg-opacity-75"
|
||||||
|
style="display: none; z-index: 1050;">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-light" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 text-light">Processing...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alert Container -->
|
||||||
|
<div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3"
|
||||||
|
style="z-index: 1051; max-width: 90%;"></div>
|
||||||
|
|
||||||
<!-- xterm.js -->
|
<!-- xterm.js -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
|
||||||
|
|
||||||
<!-- Bootstrap JS for Modal Functionality -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Your App JS -->
|
<!-- Your App JS -->
|
||||||
<script type="module" src="app.js"></script>
|
<script type="module" src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user