update duplicate

This commit is contained in:
Raven Scott
2025-11-24 15:27:10 -05:00
parent 50857e4c02
commit 6171cddfa3
3 changed files with 1265 additions and 65 deletions
+113 -34
View File
@@ -2,7 +2,7 @@ import Hyperswarm from 'hyperswarm';
import b4a from 'b4a'; import b4a from 'b4a';
import { startTerminal, appendTerminalOutput } from './libs/terminal.js'; import { startTerminal, appendTerminalOutput } from './libs/terminal.js';
import { startDockerTerminal, cleanUpDockerTerminal } from './libs/dockerTerminal.js'; import { startDockerTerminal, cleanUpDockerTerminal } from './libs/dockerTerminal.js';
import { fetchTemplates, displayTemplateList, openDeployModal } from './libs/templateDeploy.js'; import { fetchTemplates, displayTemplateList, openDeployModal, collectDuplicateFormData, populateDuplicateForm } from './libs/templateDeploy.js';
import { showContainerSkeleton, createProgressBar, updateProgressBar, removeProgressBar, showOperationStatus } from './libs/loadingStates.js'; import { showContainerSkeleton, createProgressBar, updateProgressBar, removeProgressBar, showOperationStatus } from './libs/loadingStates.js';
import { closeAllModals, showStatusIndicator, hideStatusIndicator, showAlert } from './libs/uiUtils.js'; import { closeAllModals, showStatusIndicator, hideStatusIndicator, showAlert } from './libs/uiUtils.js';
@@ -1396,17 +1396,37 @@ function openDuplicateModal(container) {
console.log(`[DEBUG] Retrieved container configuration: ${JSON.stringify(config)}`); console.log(`[DEBUG] Retrieved container configuration: ${JSON.stringify(config)}`);
// Parse configuration and populate the modal fields // Parse configuration and populate the accordion form
try { try {
const CPUs = config.HostConfig?.CpusetCpus?.split(',') || []; // Clear the form first
const form = document.getElementById('duplicate-container-form');
if (form) form.reset();
document.getElementById('container-name').value = config.Name.replace(/^\//, ''); // Populate all fields using the helper function
document.getElementById('container-hostname').value = config.Config.Hostname || ''; populateDuplicateForm(config);
document.getElementById('container-image').value = config.Config.Image || '';
document.getElementById('container-netmode').value = config.HostConfig?.NetworkMode || ''; // Set up network mode change handler for duplicate modal
document.getElementById('container-cpu').value = CPUs.length || 0; const networkMode = document.getElementById('duplicate-network-mode');
document.getElementById('container-memory').value = Math.round(config.HostConfig?.Memory / (1024 * 1024)) || 0; const customNetworkContainer = document.getElementById('duplicate-custom-network-container');
document.getElementById('container-config').value = JSON.stringify(config, null, 2); if (networkMode && customNetworkContainer) {
// Remove existing listeners by cloning and replacing
const newNetworkMode = networkMode.cloneNode(true);
networkMode.parentNode.replaceChild(newNetworkMode, networkMode);
newNetworkMode.addEventListener('change', (e) => {
if (e.target.value === 'container') {
customNetworkContainer.style.display = 'block';
const input = customNetworkContainer.querySelector('input');
if (input) input.placeholder = 'container-name';
} else if (e.target.value !== 'host' && e.target.value !== 'none' && e.target.value !== 'bridge') {
customNetworkContainer.style.display = 'block';
const input = customNetworkContainer.querySelector('input');
if (input) input.placeholder = 'network-name';
} else {
customNetworkContainer.style.display = 'none';
}
});
}
// Show the duplicate modal // Show the duplicate modal
duplicateModal.show(); duplicateModal.show();
@@ -1419,39 +1439,98 @@ function openDuplicateModal(container) {
// Handle the Duplicate Container Form Submission // Handle the Duplicate Container Form Submission
duplicateContainerForm.addEventListener('submit', (e) => { duplicateContainerForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
duplicateModal.hide();
showStatusIndicator('Duplicating container...'); let formData;
const name = document.getElementById('container-name').value.trim();
const hostname = document.getElementById('container-hostname').value.trim();
const image = document.getElementById('container-image').value.trim();
const netmode = document.getElementById('container-netmode').value.trim();
const cpu = document.getElementById('container-cpu').value.trim();
const memory = document.getElementById('container-memory').value.trim();
const configJSON = document.getElementById('container-config').value.trim();
let config;
try { try {
config = JSON.parse(configJSON); formData = collectDuplicateFormData();
} catch (err) { } catch (collectError) {
hideStatusIndicator(); console.error('[ERROR] Failed to collect duplicate form data:', collectError);
showAlert('danger', 'Invalid JSON in configuration.'); showAlert('danger', 'Failed to collect form data. Check console for details.');
return; return;
} }
sendCommand('duplicateContainer', { name, image, hostname, netmode, cpu, memory, config }); // Validate required fields
if (!formData.containerName || !formData.image) {
showAlert('danger', 'Container name and image are required.');
return;
}
// Simulate delay for the demo try {
setTimeout(() => { showStatusIndicator('Duplicating container...');
// Use deployContainer command with the collected form data
// This reuses the same deployment logic
const originalHandler = window.handlePeerResponse;
let timeoutId = null;
let isResolved = false;
const duplicateHandler = (response) => {
if (isResolved) {
if (typeof originalHandler === 'function') {
originalHandler(response);
}
return;
}
const isDuplicateResponse =
(response.success && response.message && response.message.includes('deployed successfully')) ||
(response.error && (response.message || response.error).includes('deploy') || response.error.includes('Container'));
if (isDuplicateResponse) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
window.handlePeerResponse = originalHandler;
isResolved = true;
duplicateModal.hide();
if (response.success && response.message && response.message.includes('deployed successfully')) {
hideStatusIndicator(); hideStatusIndicator();
showAlert('success', 'Container duplicated successfully!'); showAlert('success', `Container "${formData.containerName}" duplicated successfully!`);
// Refresh container list
sendCommand('listContainers'); sendCommand('listContainers');
}, 2000); // Simulated processing time } else if (response.error) {
hideStatusIndicator();
const errorMessage = typeof response.error === 'string'
? response.error
: (response.error?.message || response.error?.toString() || 'Unknown error');
showAlert('danger', errorMessage);
}
} else {
if (typeof originalHandler === 'function') {
originalHandler(response);
}
}
};
window.handlePeerResponse = duplicateHandler;
if (typeof window.sendCommand === 'function') {
window.sendCommand('deployContainer', formData);
} else {
window.handlePeerResponse = originalHandler;
hideStatusIndicator();
showAlert('danger', 'sendCommand is not available. Please ensure app.js is loaded.');
return;
}
timeoutId = setTimeout(() => {
if (!isResolved) {
window.handlePeerResponse = originalHandler;
isResolved = true;
hideStatusIndicator();
duplicateModal.hide();
showAlert('danger', 'Duplication timed out. No response from server.');
}
}, 60000);
} catch (error) {
hideStatusIndicator();
console.error('[ERROR] Failed to duplicate container:', error);
showAlert('danger', error.message || 'Failed to duplicate container. Check console for details.');
}
}); });
+419 -18
View File
@@ -708,43 +708,444 @@
<!-- Duplicate Container Modal --> <!-- Duplicate Container Modal -->
<div class="modal fade" id="duplicateModal" tabindex="-1" aria-labelledby="duplicateModalLabel" aria-hidden="true"> <div class="modal fade" id="duplicateModal" tabindex="-1" aria-labelledby="duplicateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white"> <div class="modal-content bg-dark text-white">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="duplicateModalLabel">Duplicate Container</h5> <h5 class="modal-title" id="duplicateModalLabel">Duplicate Container</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<form id="duplicate-container-form"> <form id="duplicate-container-form">
<div class="mb-3"> <!-- Basic Settings (Always Visible) -->
<label for="container-name" class="form-label">Container Name</label> <div class="mb-4">
<input type="text" class="form-control" id="container-name" required> <h6 class="text-primary mb-3"><i class="fas fa-cog"></i> Basic Settings</h6>
<div class="row">
<div class="col-md-6 mb-3">
<label for="duplicate-container-name" class="form-label">Container Name <span class="text-danger">*</span></label>
<input type="text" id="duplicate-container-name" class="form-control bg-dark text-white" placeholder="my-container" required>
<small class="text-muted">Alphanumeric, dashes, and underscores only</small>
</div>
<div class="col-md-6 mb-3">
<label for="duplicate-image" class="form-label">Image <span class="text-danger">*</span></label>
<input type="text" id="duplicate-image" class="form-control bg-dark text-white" placeholder="nginx:latest" required>
<small class="text-muted">Docker image name and tag</small>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="duplicate-command" class="form-label">Command Override</label>
<input type="text" id="duplicate-command" class="form-control bg-dark text-white" placeholder="Override default command">
<small class="text-muted">Override container CMD</small>
</div>
<div class="col-md-4 mb-3">
<label for="duplicate-entrypoint" class="form-label">Entrypoint Override</label>
<input type="text" id="duplicate-entrypoint" class="form-control bg-dark text-white" placeholder="Override default entrypoint">
<small class="text-muted">Override container ENTRYPOINT</small>
</div>
<div class="col-md-4 mb-3">
<label for="duplicate-workdir" class="form-label">Working Directory</label>
<input type="text" id="duplicate-workdir" class="form-control bg-dark text-white" placeholder="/app">
<small class="text-muted">Container working directory</small>
</div>
</div>
</div>
<!-- Accordion for Advanced Settings -->
<div class="accordion accordion-flush" id="duplicateAccordion">
<!-- Networking -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateNetworkingCollapse">
<i class="fas fa-network-wired me-2"></i> Networking
</button>
</h2>
<div id="duplicateNetworkingCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-network-mode" class="form-label">Network Mode</label>
<select id="duplicate-network-mode" class="form-select bg-dark text-white">
<option value="bridge">Bridge</option>
<option value="host">Host</option>
<option value="none">None</option>
<option value="container">Container</option>
</select>
</div>
<div class="col-md-6" id="duplicate-custom-network-container" style="display: none;">
<label for="duplicate-custom-network" class="form-label">Custom Network</label>
<input type="text" id="duplicate-custom-network" class="form-control bg-dark text-white" placeholder="network-name">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-hostname" class="form-label">Hostname</label>
<input type="text" id="duplicate-hostname" class="form-control bg-dark text-white" placeholder="container-hostname">
</div>
<div class="col-md-6">
<label for="duplicate-domainname" class="form-label">Domain Name</label>
<input type="text" id="duplicate-domainname" class="form-control bg-dark text-white" placeholder="example.com">
</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="container-hostname" class="form-label">Hostname</label> <label class="form-label">Port Mappings</label>
<input type="text" class="form-control" id="container-hostname" required> <div id="duplicate-ports-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicatePortMapping()">
<i class="fas fa-plus"></i> Add Port
</button>
<small class="text-muted d-block mt-1">Format: host:container/protocol (e.g., 8080:80/tcp)</small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="container-image" class="form-label">Image</label> <label class="form-label">DNS Servers</label>
<input type="text" class="form-control" id="container-image" required> <div id="duplicate-dns-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateDnsServer()">
<i class="fas fa-plus"></i> Add DNS Server
</button>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="container-netmode" class="form-label">Net Mode</label> <label class="form-label">Extra Hosts</label>
<input type="text" class="form-control" id="container-netmode" required> <div id="duplicate-extra-hosts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateExtraHost()">
<i class="fas fa-plus"></i> Add Host
</button>
<small class="text-muted d-block mt-1">Format: hostname:ip (e.g., example.com:127.0.0.1)</small>
</div>
</div>
</div>
</div>
<!-- Volumes & Storage -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateVolumesCollapse">
<i class="fas fa-hdd me-2"></i> Volumes & Storage
</button>
</h2>
<div id="duplicateVolumesCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Volume Mounts</label>
<div id="duplicate-volumes-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateVolumeMount()">
<i class="fas fa-plus"></i> Add Volume
</button>
<small class="text-muted d-block mt-1">Format: /host/path:/container/path:mode (mode: ro/rw)</small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="container-cpu" class="form-label">CPU Count</label> <label class="form-label">Tmpfs Mounts</label>
<input type="number" class="form-control" id="container-cpu" required> <div id="duplicate-tmpfs-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateTmpfsMount()">
<i class="fas fa-plus"></i> Add Tmpfs
</button>
<small class="text-muted d-block mt-1">Format: /path:size (e.g., /tmp:100m)</small>
</div>
</div>
</div>
</div>
<!-- Resources -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateResourcesCollapse">
<i class="fas fa-server me-2"></i> Resources
</button>
</h2>
<div id="duplicateResourcesCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-4">
<label for="duplicate-cpu-limit" class="form-label">CPU Limit (cores)</label>
<input type="number" id="duplicate-cpu-limit" class="form-control bg-dark text-white" placeholder="2" step="0.1" min="0">
<small class="text-muted">Number of CPU cores</small>
</div>
<div class="col-md-4">
<label for="duplicate-cpu-reservation" class="form-label">CPU Reservation</label>
<input type="number" id="duplicate-cpu-reservation" class="form-control bg-dark text-white" placeholder="1" step="0.1" min="0">
</div>
<div class="col-md-4">
<label for="duplicate-cpu-shares" class="form-label">CPU Shares</label>
<input type="number" id="duplicate-cpu-shares" class="form-control bg-dark text-white" placeholder="1024" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="duplicate-memory-limit" class="form-label">Memory Limit (MB)</label>
<input type="number" id="duplicate-memory-limit" class="form-control bg-dark text-white" placeholder="512" min="0">
</div>
<div class="col-md-4">
<label for="duplicate-memory-reservation" class="form-label">Memory Reservation (MB)</label>
<input type="number" id="duplicate-memory-reservation" class="form-control bg-dark text-white" placeholder="256" min="0">
</div>
<div class="col-md-4">
<label for="duplicate-memory-swap" class="form-label">Memory Swap (MB)</label>
<input type="number" id="duplicate-memory-swap" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="container-memory" class="form-label">Memory (MB)</label> <label class="form-label">Device Mappings</label>
<input type="number" class="form-control" id="container-memory" required> <div id="duplicate-devices-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateDeviceMapping()">
<i class="fas fa-plus"></i> Add Device
</button>
<small class="text-muted d-block mt-1">Format: /host/device:/container/device:permissions</small>
</div>
</div>
</div>
</div>
<!-- Environment & Labels -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateEnvLabelsCollapse">
<i class="fas fa-tags me-2"></i> Environment & Labels
</button>
</h2>
<div id="duplicateEnvLabelsCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Environment Variables</label>
<div id="duplicate-env"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateEnvVar()">
<i class="fas fa-plus"></i> Add Variable
</button>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="container-config" class="form-label">Container Configuration (JSON)</label> <label class="form-label">Labels</label>
<textarea class="form-control" id="container-config" rows="10" required></textarea> <div id="duplicate-labels-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateLabel()">
<i class="fas fa-plus"></i> Add Label
</button>
<small class="text-muted d-block mt-1">Format: key=value</small>
</div>
</div>
</div>
</div>
<!-- Security -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateSecurityCollapse">
<i class="fas fa-shield-alt me-2"></i> Security
</button>
</h2>
<div id="duplicateSecurityCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-user" class="form-label">User/UID</label>
<input type="text" id="duplicate-user" class="form-control bg-dark text-white" placeholder="1000 or username">
</div>
<div class="col-md-6">
<label for="duplicate-group" class="form-label">Group/GID</label>
<input type="text" id="duplicate-group" class="form-control bg-dark text-white" placeholder="1000 or groupname">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-privileged">
<label class="form-check-label" for="duplicate-privileged">Privileged Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-readonly-rootfs">
<label class="form-check-label" for="duplicate-readonly-rootfs">Read-only Root Filesystem</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Capabilities</label>
<div id="duplicate-capabilities-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateCapability()">
<i class="fas fa-plus"></i> Add Capability
</button>
<small class="text-muted d-block mt-1">e.g., NET_ADMIN, SYS_ADMIN</small>
</div>
<div class="mb-3">
<label class="form-label">Security Options</label>
<div id="duplicate-security-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateSecurityOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
<small class="text-muted d-block mt-1">e.g., apparmor=profile, seccomp=unconfined</small>
</div>
</div>
</div>
</div>
<!-- Runtime & Behavior -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateRuntimeCollapse">
<i class="fas fa-play-circle me-2"></i> Runtime & Behavior
</button>
</h2>
<div id="duplicateRuntimeCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-restart-policy" class="form-label">Restart Policy</label>
<select id="duplicate-restart-policy" class="form-select bg-dark text-white">
<option value="no">No</option>
<option value="always">Always</option>
<option value="on-failure">On Failure</option>
<option value="unless-stopped">Unless Stopped</option>
</select>
</div>
<div class="col-md-6">
<label for="duplicate-restart-max-retries" class="form-label">Max Retries (on-failure)</label>
<input type="number" id="duplicate-restart-max-retries" class="form-control bg-dark text-white" placeholder="10" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-auto-remove">
<label class="form-check-label" for="duplicate-auto-remove">Auto Remove</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-tty">
<label class="form-check-label" for="duplicate-tty">TTY</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-stdin-open">
<label class="form-check-label" for="duplicate-stdin-open">Interactive (Stdin Open)</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-detach">
<label class="form-check-label" for="duplicate-detach">Detach Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-init">
<label class="form-check-label" for="duplicate-init">Init Process</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Health & Logging -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateHealthCollapse">
<i class="fas fa-heartbeat me-2"></i> Health & Logging
</button>
</h2>
<div id="duplicateHealthCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label for="duplicate-health-cmd" class="form-label">Health Check Command</label>
<input type="text" id="duplicate-health-cmd" class="form-control bg-dark text-white" placeholder="CMD-SHELL curl -f http://localhost/ || exit 1">
</div>
<div class="row mb-3">
<div class="col-md-3">
<label for="duplicate-health-interval" class="form-label">Interval (s)</label>
<input type="number" id="duplicate-health-interval" class="form-control bg-dark text-white" placeholder="30" min="1">
</div>
<div class="col-md-3">
<label for="duplicate-health-timeout" class="form-label">Timeout (s)</label>
<input type="number" id="duplicate-health-timeout" class="form-control bg-dark text-white" placeholder="10" min="1">
</div>
<div class="col-md-3">
<label for="duplicate-health-retries" class="form-label">Retries</label>
<input type="number" id="duplicate-health-retries" class="form-control bg-dark text-white" placeholder="3" min="0">
</div>
<div class="col-md-3">
<label for="duplicate-health-start-period" class="form-label">Start Period (s)</label>
<input type="number" id="duplicate-health-start-period" class="form-control bg-dark text-white" placeholder="0" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-log-driver" class="form-label">Logging Driver</label>
<select id="duplicate-log-driver" class="form-select bg-dark text-white">
<option value="">Default</option>
<option value="json-file">JSON File</option>
<option value="syslog">Syslog</option>
<option value="journald">Journald</option>
<option value="gelf">GELF</option>
<option value="fluentd">Fluentd</option>
<option value="awslogs">AWS Logs</option>
<option value="none">None</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Logging Options</label>
<div id="duplicate-log-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateLogOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Advanced -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateAdvancedCollapse">
<i class="fas fa-cogs me-2"></i> Advanced
</button>
</h2>
<div id="duplicateAdvancedCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Sysctls</label>
<div id="duplicate-sysctls-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateSysctl()">
<i class="fas fa-plus"></i> Add Sysctl
</button>
<small class="text-muted d-block mt-1">Format: key=value (e.g., net.ipv4.ip_forward=1)</small>
</div>
<div class="mb-3">
<label class="form-label">Ulimits</label>
<div id="duplicate-ulimits-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateUlimit()">
<i class="fas fa-plus"></i> Add Ulimit
</button>
<small class="text-muted d-block mt-1">Format: name=soft:hard (e.g., nofile=1024:2048)</small>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duplicate-oom-kill-disable">
<label class="form-check-label" for="duplicate-oom-kill-disable">Disable OOM Killer</label>
</div>
</div>
<div class="col-md-6">
<label for="duplicate-pids-limit" class="form-label">PIDs Limit</label>
<input type="number" id="duplicate-pids-limit" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label for="duplicate-shm-size" class="form-label">Shared Memory Size (MB)</label>
<input type="number" id="duplicate-shm-size" class="form-control bg-dark text-white" placeholder="64" min="0">
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-clone"></i> Duplicate Container
</button>
</div> </div>
<button type="submit" class="btn btn-primary">Deploy Duplicate</button>
</form> </form>
</div> </div>
</div> </div>
+721 -1
View File
@@ -831,5 +831,725 @@ deployForm.addEventListener('submit', async (e) => {
// Initialize templates on load // Initialize templates on load
document.addEventListener('DOMContentLoaded', fetchTemplates); document.addEventListener('DOMContentLoaded', fetchTemplates);
// Duplicate modal array management functions
let duplicatePortCounter = 0;
let duplicateVolumeCounter = 0;
let duplicateEnvCounter = 0;
let duplicateLabelCounter = 0;
let duplicateDnsCounter = 0;
let duplicateExtraHostCounter = 0;
let duplicateDeviceCounter = 0;
let duplicateCapabilityCounter = 0;
let duplicateSecurityOptCounter = 0;
let duplicateLogOptCounter = 0;
let duplicateSysctlCounter = 0;
let duplicateUlimitCounter = 0;
let duplicateTmpfsCounter = 0;
function addDuplicatePortMapping() {
const container = document.getElementById('duplicate-ports-container');
if (!container) return;
const id = `duplicate-port-${duplicatePortCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="8080:80/tcp" data-port-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateVolumeMount() {
const container = document.getElementById('duplicate-volumes-container');
if (!container) return;
const id = `duplicate-volume-${duplicateVolumeCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="/host/path:/container/path:ro" data-volume-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateTmpfsMount() {
const container = document.getElementById('duplicate-tmpfs-container');
if (!container) return;
const id = `duplicate-tmpfs-${duplicateTmpfsCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="/tmp:100m" data-tmpfs-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateEnvVar() {
const container = document.getElementById('duplicate-env');
if (!container) return;
const id = `duplicate-env-${duplicateEnvCounter++}`;
const item = document.createElement('div');
item.className = 'array-item mb-2';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="KEY" data-env-key="${id}" style="flex: 0 0 40%;">
<input type="text" class="form-control bg-dark text-white" placeholder="value" data-env-value="${id}" style="flex: 1;">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateLabel() {
const container = document.getElementById('duplicate-labels-container');
if (!container) return;
const id = `duplicate-label-${duplicateLabelCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="key=value" data-label-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateDnsServer() {
const container = document.getElementById('duplicate-dns-container');
if (!container) return;
const id = `duplicate-dns-${duplicateDnsCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="8.8.8.8" data-dns-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateExtraHost() {
const container = document.getElementById('duplicate-extra-hosts-container');
if (!container) return;
const id = `duplicate-host-${duplicateExtraHostCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="example.com:127.0.0.1" data-host-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateDeviceMapping() {
const container = document.getElementById('duplicate-devices-container');
if (!container) return;
const id = `duplicate-device-${duplicateDeviceCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="/dev/ttyUSB0:/dev/ttyUSB0:rwm" data-device-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateCapability() {
const container = document.getElementById('duplicate-capabilities-container');
if (!container) return;
const id = `duplicate-cap-${duplicateCapabilityCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="NET_ADMIN" data-cap-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateSecurityOpt() {
const container = document.getElementById('duplicate-security-opts-container');
if (!container) return;
const id = `duplicate-secopt-${duplicateSecurityOptCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="apparmor=profile" data-secopt-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateLogOpt() {
const container = document.getElementById('duplicate-log-opts-container');
if (!container) return;
const id = `duplicate-logopt-${duplicateLogOptCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="max-size=10m" data-logopt-key="${id}" style="flex: 0 0 40%;">
<input type="text" class="form-control bg-dark text-white" placeholder="value" data-logopt-value="${id}" style="flex: 1;">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateSysctl() {
const container = document.getElementById('duplicate-sysctls-container');
if (!container) return;
const id = `duplicate-sysctl-${duplicateSysctlCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="net.ipv4.ip_forward=1" data-sysctl-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
function addDuplicateUlimit() {
const container = document.getElementById('duplicate-ulimits-container');
if (!container) return;
const id = `duplicate-ulimit-${duplicateUlimitCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
item.id = id;
item.innerHTML = `
<input type="text" class="form-control bg-dark text-white" placeholder="nofile=1024:2048" data-ulimit-id="${id}">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeArrayItem('${id}')">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(item);
}
// Make duplicate functions globally available
window.addDuplicatePortMapping = addDuplicatePortMapping;
window.addDuplicateVolumeMount = addDuplicateVolumeMount;
window.addDuplicateTmpfsMount = addDuplicateTmpfsMount;
window.addDuplicateEnvVar = addDuplicateEnvVar;
window.addDuplicateLabel = addDuplicateLabel;
window.addDuplicateDnsServer = addDuplicateDnsServer;
window.addDuplicateExtraHost = addDuplicateExtraHost;
window.addDuplicateDeviceMapping = addDuplicateDeviceMapping;
window.addDuplicateCapability = addDuplicateCapability;
window.addDuplicateSecurityOpt = addDuplicateSecurityOpt;
window.addDuplicateLogOpt = addDuplicateLogOpt;
window.addDuplicateSysctl = addDuplicateSysctl;
window.addDuplicateUlimit = addDuplicateUlimit;
// Collect duplicate form data (similar to collectFormData but for duplicate modal)
function collectDuplicateFormData() {
const data = {
containerName: document.getElementById('duplicate-container-name')?.value.trim() || '',
image: document.getElementById('duplicate-image')?.value.trim() || '',
command: document.getElementById('duplicate-command')?.value.trim() || null,
entrypoint: document.getElementById('duplicate-entrypoint')?.value.trim() || null,
workingDir: document.getElementById('duplicate-workdir')?.value.trim() || null,
// Networking
networkMode: document.getElementById('duplicate-network-mode')?.value || 'bridge',
customNetwork: (() => {
const mode = document.getElementById('duplicate-network-mode')?.value;
const customNet = document.getElementById('duplicate-custom-network')?.value.trim();
if ((mode === 'container' || (mode && mode !== 'bridge' && mode !== 'host' && mode !== 'none')) && customNet) {
return customNet;
}
return null;
})(),
hostname: document.getElementById('duplicate-hostname')?.value.trim() || null,
domainname: document.getElementById('duplicate-domainname')?.value.trim() || null,
ports: collectArrayItems('duplicate-ports-container', 'data-port-id'),
dns: collectArrayItems('duplicate-dns-container', 'data-dns-id'),
extraHosts: collectArrayItems('duplicate-extra-hosts-container', 'data-host-id'),
// Volumes
volumes: collectArrayItems('duplicate-volumes-container', 'data-volume-id'),
tmpfs: collectArrayItems('duplicate-tmpfs-container', 'data-tmpfs-id'),
// Resources
cpuLimit: parseFloat(document.getElementById('duplicate-cpu-limit')?.value) || null,
cpuReservation: parseFloat(document.getElementById('duplicate-cpu-reservation')?.value) || null,
cpuShares: parseInt(document.getElementById('duplicate-cpu-shares')?.value) || null,
memoryLimit: parseInt(document.getElementById('duplicate-memory-limit')?.value) || null,
memoryReservation: parseInt(document.getElementById('duplicate-memory-reservation')?.value) || null,
memorySwap: parseInt(document.getElementById('duplicate-memory-swap')?.value) || null,
devices: collectArrayItems('duplicate-devices-container', 'data-device-id'),
// Environment & Labels
env: collectDuplicateEnvVars(),
labels: collectDuplicateLabels(),
// Security
user: document.getElementById('duplicate-user')?.value.trim() || null,
group: document.getElementById('duplicate-group')?.value.trim() || null,
privileged: document.getElementById('duplicate-privileged')?.checked || false,
readonlyRootfs: document.getElementById('duplicate-readonly-rootfs')?.checked || false,
capabilities: collectArrayItems('duplicate-capabilities-container', 'data-cap-id'),
securityOpts: collectArrayItems('duplicate-security-opts-container', 'data-secopt-id'),
// Runtime
restartPolicy: document.getElementById('duplicate-restart-policy')?.value || 'no',
restartMaxRetries: parseInt(document.getElementById('duplicate-restart-max-retries')?.value) || null,
autoRemove: document.getElementById('duplicate-auto-remove')?.checked || false,
tty: document.getElementById('duplicate-tty')?.checked || false,
stdinOpen: document.getElementById('duplicate-stdin-open')?.checked || false,
detach: document.getElementById('duplicate-detach')?.checked !== false,
init: document.getElementById('duplicate-init')?.checked || false,
// Health & Logging
healthCmd: document.getElementById('duplicate-health-cmd')?.value.trim() || null,
healthInterval: parseInt(document.getElementById('duplicate-health-interval')?.value) || null,
healthTimeout: parseInt(document.getElementById('duplicate-health-timeout')?.value) || null,
healthRetries: parseInt(document.getElementById('duplicate-health-retries')?.value) || null,
healthStartPeriod: parseInt(document.getElementById('duplicate-health-start-period')?.value) || null,
logDriver: document.getElementById('duplicate-log-driver')?.value || null,
logOpts: collectDuplicateLogOpts(),
// Advanced
sysctls: collectDuplicateSysctls(),
ulimits: collectDuplicateUlimits(),
oomKillDisable: document.getElementById('duplicate-oom-kill-disable')?.checked || false,
pidsLimit: parseInt(document.getElementById('duplicate-pids-limit')?.value) || null,
shmSize: parseInt(document.getElementById('duplicate-shm-size')?.value) || null,
};
// Remove null/empty values
Object.keys(data).forEach(key => {
if (data[key] === null || data[key] === '' || (Array.isArray(data[key]) && data[key].length === 0)) {
delete data[key];
}
});
return data;
}
function collectDuplicateEnvVars() {
const container = document.getElementById('duplicate-env');
if (!container) return [];
const envVars = [];
container.querySelectorAll('[data-env-key]').forEach(keyInput => {
const key = keyInput.value.trim();
const valueInput = container.querySelector(`[data-env-value="${keyInput.getAttribute('data-env-key')}"]`);
const value = valueInput?.value.trim() || '';
if (key) {
envVars.push({ name: key, value });
}
});
return envVars;
}
function collectDuplicateLabels() {
const items = collectArrayItems('duplicate-labels-container', 'data-label-id');
const labels = {};
items.forEach(item => {
const [key, value] = item.split('=');
if (key && value) {
labels[key.trim()] = value.trim();
}
});
return Object.keys(labels).length > 0 ? labels : null;
}
function collectDuplicateLogOpts() {
const container = document.getElementById('duplicate-log-opts-container');
if (!container) return null;
const opts = {};
container.querySelectorAll('[data-logopt-key]').forEach(keyInput => {
const key = keyInput.value.trim();
const valueInput = container.querySelector(`[data-logopt-value="${keyInput.getAttribute('data-logopt-key')}"]`);
const value = valueInput?.value.trim() || '';
if (key) {
opts[key] = value;
}
});
return Object.keys(opts).length > 0 ? opts : null;
}
function collectDuplicateSysctls() {
const items = collectArrayItems('duplicate-sysctls-container', 'data-sysctl-id');
const sysctls = {};
items.forEach(item => {
const [key, value] = item.split('=');
if (key && value) {
sysctls[key.trim()] = value.trim();
}
});
return Object.keys(sysctls).length > 0 ? sysctls : null;
}
function collectDuplicateUlimits() {
const items = collectArrayItems('duplicate-ulimits-container', 'data-ulimit-id');
const ulimits = [];
items.forEach(item => {
const [name, limits] = item.split('=');
if (name && limits) {
const [soft, hard] = limits.split(':');
ulimits.push({
Name: name.trim(),
Soft: soft ? parseInt(soft) : null,
Hard: hard ? parseInt(hard) : null
});
}
});
return ulimits.length > 0 ? ulimits : null;
}
// Populate duplicate form from container config
function populateDuplicateForm(config) {
if (!config) return;
// Reset counters
duplicatePortCounter = duplicateVolumeCounter = duplicateEnvCounter = duplicateLabelCounter = duplicateDnsCounter = 0;
duplicateExtraHostCounter = duplicateDeviceCounter = duplicateCapabilityCounter = duplicateSecurityOptCounter = 0;
duplicateLogOptCounter = duplicateSysctlCounter = duplicateUlimitCounter = duplicateTmpfsCounter = 0;
// Clear all array containers
['duplicate-ports-container', 'duplicate-volumes-container', 'duplicate-env', 'duplicate-labels-container',
'duplicate-dns-container', 'duplicate-extra-hosts-container', 'duplicate-devices-container',
'duplicate-capabilities-container', 'duplicate-security-opts-container', 'duplicate-log-opts-container',
'duplicate-sysctls-container', 'duplicate-ulimits-container', 'duplicate-tmpfs-container'].forEach(id => {
const container = document.getElementById(id);
if (container) container.innerHTML = '';
});
// Basic settings
const nameEl = document.getElementById('duplicate-container-name');
if (nameEl) nameEl.value = config.Name ? config.Name.replace(/^\//, '') : '';
const imageEl = document.getElementById('duplicate-image');
if (imageEl) imageEl.value = config.Config?.Image || '';
const commandEl = document.getElementById('duplicate-command');
if (commandEl && config.Config?.Cmd) {
commandEl.value = Array.isArray(config.Config.Cmd) ? config.Config.Cmd.join(' ') : config.Config.Cmd;
}
const entrypointEl = document.getElementById('duplicate-entrypoint');
if (entrypointEl && config.Config?.Entrypoint) {
entrypointEl.value = Array.isArray(config.Config.Entrypoint) ? config.Config.Entrypoint.join(' ') : config.Config.Entrypoint;
}
const workdirEl = document.getElementById('duplicate-workdir');
if (workdirEl) workdirEl.value = config.Config?.WorkingDir || '';
// Networking
const networkModeEl = document.getElementById('duplicate-network-mode');
if (networkModeEl && config.HostConfig?.NetworkMode) {
const netMode = config.HostConfig.NetworkMode;
if (netMode.startsWith('container:')) {
networkModeEl.value = 'container';
const customNetEl = document.getElementById('duplicate-custom-network');
if (customNetEl) {
customNetEl.value = netMode.replace('container:', '');
document.getElementById('duplicate-custom-network-container').style.display = 'block';
}
} else {
networkModeEl.value = netMode;
}
}
const hostnameEl = document.getElementById('duplicate-hostname');
if (hostnameEl) hostnameEl.value = config.Config?.Hostname || '';
const domainnameEl = document.getElementById('duplicate-domainname');
if (domainnameEl) domainnameEl.value = config.Config?.Domainname || '';
// Port bindings
if (config.HostConfig?.PortBindings) {
Object.entries(config.HostConfig.PortBindings).forEach(([containerPort, bindings]) => {
if (bindings && bindings.length > 0) {
const binding = bindings[0];
const portStr = binding.HostPort ? `${binding.HostPort}:${containerPort}` : containerPort;
addDuplicatePortMapping();
const container = document.getElementById('duplicate-ports-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = portStr;
}
});
}
// DNS
if (config.HostConfig?.Dns && Array.isArray(config.HostConfig.Dns)) {
config.HostConfig.Dns.forEach(dns => {
addDuplicateDnsServer();
const container = document.getElementById('duplicate-dns-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = dns;
});
}
// Extra hosts
if (config.HostConfig?.ExtraHosts && Array.isArray(config.HostConfig.ExtraHosts)) {
config.HostConfig.ExtraHosts.forEach(host => {
addDuplicateExtraHost();
const container = document.getElementById('duplicate-extra-hosts-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = host;
});
}
// Volumes
if (config.HostConfig?.Binds && Array.isArray(config.HostConfig.Binds)) {
config.HostConfig.Binds.forEach(bind => {
addDuplicateVolumeMount();
const container = document.getElementById('duplicate-volumes-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = bind;
});
}
// Tmpfs
if (config.HostConfig?.Tmpfs && typeof config.HostConfig.Tmpfs === 'object') {
Object.entries(config.HostConfig.Tmpfs).forEach(([path, opts]) => {
addDuplicateTmpfsMount();
const container = document.getElementById('duplicate-tmpfs-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = opts ? `${path}:${opts}` : path;
});
}
// Resources
if (config.HostConfig?.NanoCpus) {
const cpuLimitEl = document.getElementById('duplicate-cpu-limit');
if (cpuLimitEl) cpuLimitEl.value = config.HostConfig.NanoCpus / 1000000000;
}
if (config.HostConfig?.CpuQuota) {
const cpuReservationEl = document.getElementById('duplicate-cpu-reservation');
if (cpuReservationEl) cpuReservationEl.value = config.HostConfig.CpuQuota / 1000000000;
}
if (config.HostConfig?.CpuShares) {
const cpuSharesEl = document.getElementById('duplicate-cpu-shares');
if (cpuSharesEl) cpuSharesEl.value = config.HostConfig.CpuShares;
}
if (config.HostConfig?.Memory) {
const memoryLimitEl = document.getElementById('duplicate-memory-limit');
if (memoryLimitEl) memoryLimitEl.value = Math.round(config.HostConfig.Memory / (1024 * 1024));
}
if (config.HostConfig?.MemoryReservation) {
const memoryReservationEl = document.getElementById('duplicate-memory-reservation');
if (memoryReservationEl) memoryReservationEl.value = Math.round(config.HostConfig.MemoryReservation / (1024 * 1024));
}
if (config.HostConfig?.MemorySwap !== undefined) {
const memorySwapEl = document.getElementById('duplicate-memory-swap');
if (memorySwapEl) memorySwapEl.value = config.HostConfig.MemorySwap === -1 ? -1 : Math.round(config.HostConfig.MemorySwap / (1024 * 1024));
}
// Devices
if (config.HostConfig?.Devices && Array.isArray(config.HostConfig.Devices)) {
config.HostConfig.Devices.forEach(device => {
const deviceStr = `${device.PathOnHost}:${device.PathInContainer}:${device.CgroupPermissions || 'rwm'}`;
addDuplicateDeviceMapping();
const container = document.getElementById('duplicate-devices-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = deviceStr;
});
}
// Environment variables
if (config.Config?.Env && Array.isArray(config.Config.Env)) {
config.Config.Env.forEach(envStr => {
const [name, ...valueParts] = envStr.split('=');
const value = valueParts.join('=');
addDuplicateEnvVar();
const container = document.getElementById('duplicate-env');
const items = container.querySelectorAll('.array-item');
const lastItem = items[items.length - 1];
if (lastItem) {
const keyInput = lastItem.querySelector('[data-env-key]');
const valueInput = lastItem.querySelector('[data-env-value]');
if (keyInput) keyInput.value = name || '';
if (valueInput) valueInput.value = value || '';
}
});
}
// Labels
if (config.Config?.Labels && typeof config.Config.Labels === 'object') {
Object.entries(config.Config.Labels).forEach(([key, value]) => {
addDuplicateLabel();
const container = document.getElementById('duplicate-labels-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = `${key}=${value}`;
});
}
// Security
const userEl = document.getElementById('duplicate-user');
if (userEl) userEl.value = config.Config?.User || '';
const groupEl = document.getElementById('duplicate-group');
if (groupEl && config.HostConfig?.GroupAdd && config.HostConfig.GroupAdd.length > 0) {
groupEl.value = config.HostConfig.GroupAdd[0];
}
const privilegedEl = document.getElementById('duplicate-privileged');
if (privilegedEl) privilegedEl.checked = config.HostConfig?.Privileged || false;
const readonlyRootfsEl = document.getElementById('duplicate-readonly-rootfs');
if (readonlyRootfsEl) readonlyRootfsEl.checked = config.HostConfig?.ReadonlyRootfs || false;
// Capabilities
if (config.HostConfig?.CapAdd && Array.isArray(config.HostConfig.CapAdd)) {
config.HostConfig.CapAdd.forEach(cap => {
addDuplicateCapability();
const container = document.getElementById('duplicate-capabilities-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = cap;
});
}
// Security options
if (config.HostConfig?.SecurityOpt && Array.isArray(config.HostConfig.SecurityOpt)) {
config.HostConfig.SecurityOpt.forEach(opt => {
addDuplicateSecurityOpt();
const container = document.getElementById('duplicate-security-opts-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = opt;
});
}
// Runtime
const restartPolicyEl = document.getElementById('duplicate-restart-policy');
if (restartPolicyEl && config.HostConfig?.RestartPolicy) {
restartPolicyEl.value = config.HostConfig.RestartPolicy.Name || 'no';
const restartMaxRetriesEl = document.getElementById('duplicate-restart-max-retries');
if (restartMaxRetriesEl && config.HostConfig.RestartPolicy.MaximumRetryCount) {
restartMaxRetriesEl.value = config.HostConfig.RestartPolicy.MaximumRetryCount;
}
}
const autoRemoveEl = document.getElementById('duplicate-auto-remove');
if (autoRemoveEl) autoRemoveEl.checked = config.HostConfig?.AutoRemove || false;
const ttyEl = document.getElementById('duplicate-tty');
if (ttyEl) ttyEl.checked = config.Config?.Tty || false;
const stdinOpenEl = document.getElementById('duplicate-stdin-open');
if (stdinOpenEl) stdinOpenEl.checked = config.Config?.OpenStdin || false;
const detachEl = document.getElementById('duplicate-detach');
if (detachEl) detachEl.checked = config.Config?.AttachStdin === false;
const initEl = document.getElementById('duplicate-init');
if (initEl) initEl.checked = config.HostConfig?.Init || false;
// Health check
if (config.Config?.Healthcheck) {
const healthCmdEl = document.getElementById('duplicate-health-cmd');
if (healthCmdEl && config.Config.Healthcheck.Test) {
const test = config.Config.Healthcheck.Test;
if (Array.isArray(test)) {
healthCmdEl.value = test.join(' ');
} else {
healthCmdEl.value = test;
}
}
const healthIntervalEl = document.getElementById('duplicate-health-interval');
if (healthIntervalEl && config.Config.Healthcheck.Interval) {
healthIntervalEl.value = Math.round(config.Config.Healthcheck.Interval / 1000000000);
}
const healthTimeoutEl = document.getElementById('duplicate-health-timeout');
if (healthTimeoutEl && config.Config.Healthcheck.Timeout) {
healthTimeoutEl.value = Math.round(config.Config.Healthcheck.Timeout / 1000000000);
}
const healthRetriesEl = document.getElementById('duplicate-health-retries');
if (healthRetriesEl && config.Config.Healthcheck.Retries) {
healthRetriesEl.value = config.Config.Healthcheck.Retries;
}
const healthStartPeriodEl = document.getElementById('duplicate-health-start-period');
if (healthStartPeriodEl && config.Config.Healthcheck.StartPeriod) {
healthStartPeriodEl.value = Math.round(config.Config.Healthcheck.StartPeriod / 1000000000);
}
}
// Logging
if (config.HostConfig?.LogConfig) {
const logDriverEl = document.getElementById('duplicate-log-driver');
if (logDriverEl) logDriverEl.value = config.HostConfig.LogConfig.Type || '';
if (config.HostConfig.LogConfig.Config && typeof config.HostConfig.LogConfig.Config === 'object') {
Object.entries(config.HostConfig.LogConfig.Config).forEach(([key, value]) => {
addDuplicateLogOpt();
const container = document.getElementById('duplicate-log-opts-container');
const items = container.querySelectorAll('.array-item');
const lastItem = items[items.length - 1];
if (lastItem) {
const keyInput = lastItem.querySelector('[data-logopt-key]');
const valueInput = lastItem.querySelector('[data-logopt-value]');
if (keyInput) keyInput.value = key;
if (valueInput) valueInput.value = value || '';
}
});
}
}
// Advanced
if (config.HostConfig?.Sysctls && typeof config.HostConfig.Sysctls === 'object') {
Object.entries(config.HostConfig.Sysctls).forEach(([key, value]) => {
addDuplicateSysctl();
const container = document.getElementById('duplicate-sysctls-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = `${key}=${value}`;
});
}
if (config.HostConfig?.Ulimits && Array.isArray(config.HostConfig.Ulimits)) {
config.HostConfig.Ulimits.forEach(ulimit => {
const ulimitStr = `${ulimit.Name}=${ulimit.Soft || ''}:${ulimit.Hard || ''}`;
addDuplicateUlimit();
const container = document.getElementById('duplicate-ulimits-container');
const lastInput = container?.lastElementChild?.querySelector('input');
if (lastInput) lastInput.value = ulimitStr;
});
}
const oomKillDisableEl = document.getElementById('duplicate-oom-kill-disable');
if (oomKillDisableEl) oomKillDisableEl.checked = config.HostConfig?.OomKillDisable || false;
const pidsLimitEl = document.getElementById('duplicate-pids-limit');
if (pidsLimitEl && config.HostConfig?.PidsLimit !== undefined) {
pidsLimitEl.value = config.HostConfig.PidsLimit === 0 ? -1 : config.HostConfig.PidsLimit;
}
const shmSizeEl = document.getElementById('duplicate-shm-size');
if (shmSizeEl && config.HostConfig?.ShmSize) {
shmSizeEl.value = Math.round(config.HostConfig.ShmSize / (1024 * 1024));
}
}
// Export required functions // Export required functions
export { fetchTemplates, displayTemplateList, openDeployModal }; export { fetchTemplates, displayTemplateList, openDeployModal, collectDuplicateFormData, populateDuplicateForm };