Fix Template Parsing
Release rolling / release (push) Has been cancelled

This commit is contained in:
Raven Scott
2026-07-16 04:04:32 -04:00
parent 223c7118c9
commit fb8f1c4fa4
4 changed files with 811 additions and 106 deletions
+165 -98
View File
@@ -1,6 +1,10 @@
// Import dependencies first (ES6 imports must be at top)
import { closeAllModals, showStatusIndicator, hideStatusIndicator, updateStatusIndicator, showAlert } from './uiUtils.js';
import { fetchMergedTemplates, getTemplateListUrls } from '../client/templateLists.js';
import {
isStackTemplate,
resolveTemplateForDeploy,
} from '../client/templateResolve.js';
// DOM Elements - Lazy loaded (initialized when modal opens)
let templateList = null;
@@ -11,6 +15,40 @@ let templates = [];
let searchInputListenerSetup = false; // Track if search input listener is set up
let formSubmitListenerSetup = false; // Track if form submit listener is set up
/**
* Active form root (view or modal). Both UIs reuse the same element ids, so
* document.getElementById always hits the *first* (view) form and leaves the
* modal empty — scope all deploy-* lookups here.
* @type {ParentNode|null}
*/
let formScope = null;
/**
* @param {ParentNode|null|undefined} root
*/
export function setDeployFormScope(root) {
formScope = root || null;
}
/**
* Scoped lookup for deploy form controls (prefer active form root).
* @param {string} id
* @returns {HTMLElement|null}
*/
function deployEl(id) {
const key = String(id || '');
if (!key) return null;
if (formScope && typeof formScope.querySelector === 'function') {
try {
const el = formScope.querySelector(`#${key}`);
if (el) return /** @type {HTMLElement} */ (el);
} catch {
// ignore invalid selector
}
}
return document.getElementById(key);
}
// Array item counters for unique IDs
let portCounter = 0;
let volumeCounter = 0;
@@ -140,6 +178,7 @@ function setupFormSubmitListener() {
deployForm.addEventListener('submit', async (e) => {
e.preventDefault();
setDeployFormScope(deployForm);
let formData;
try {
@@ -296,7 +335,7 @@ function displayTemplateList(templates) {
// Array management functions
function addPortMapping(portData = null) {
const container = document.getElementById('deploy-ports-container');
const container = deployEl('deploy-ports-container');
if (!container) {
console.error('[ERROR] Ports container not found - element with id "deploy-ports-container" does not exist');
return;
@@ -471,7 +510,7 @@ function validatePortMapping(portId) {
}
function addVolumeMount(volumeData = null) {
const container = document.getElementById('deploy-volumes-container');
const container = deployEl('deploy-volumes-container');
if (!container) return;
const id = `volume-${volumeCounter++}`;
const item = document.createElement('div');
@@ -681,7 +720,7 @@ function validateVolumeMount(volumeId) {
}
function addTmpfsMount() {
const container = document.getElementById('deploy-tmpfs-container');
const container = deployEl('deploy-tmpfs-container');
const id = `tmpfs-${tmpfsCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -905,7 +944,7 @@ function createEnvVarInput(envVar, id) {
}
function addEnvVar(envVar = null) {
const container = document.getElementById('deploy-env');
const container = deployEl('deploy-env');
const id = `env-${envCounter++}`;
const item = document.createElement('div');
item.className = 'array-item mb-2';
@@ -939,7 +978,7 @@ function addEnvVar(envVar = null) {
}
function addLabel() {
const container = document.getElementById('deploy-labels-container');
const container = deployEl('deploy-labels-container');
const id = `label-${labelCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -955,7 +994,7 @@ function addLabel() {
}
function addDnsServer() {
const container = document.getElementById('deploy-dns-container');
const container = deployEl('deploy-dns-container');
const id = `dns-${dnsCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -971,7 +1010,7 @@ function addDnsServer() {
}
function addExtraHost() {
const container = document.getElementById('deploy-extra-hosts-container');
const container = deployEl('deploy-extra-hosts-container');
const id = `host-${extraHostCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -987,7 +1026,7 @@ function addExtraHost() {
}
function addDeviceMapping() {
const container = document.getElementById('deploy-devices-container');
const container = deployEl('deploy-devices-container');
const id = `device-${deviceCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -1003,7 +1042,7 @@ function addDeviceMapping() {
}
function addCapability() {
const container = document.getElementById('deploy-capabilities-container');
const container = deployEl('deploy-capabilities-container');
const id = `cap-${capabilityCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -1019,7 +1058,7 @@ function addCapability() {
}
function addSecurityOpt() {
const container = document.getElementById('deploy-security-opts-container');
const container = deployEl('deploy-security-opts-container');
const id = `secopt-${securityOptCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -1035,7 +1074,7 @@ function addSecurityOpt() {
}
function addLogOpt() {
const container = document.getElementById('deploy-log-opts-container');
const container = deployEl('deploy-log-opts-container');
const id = `logopt-${logOptCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -1052,7 +1091,7 @@ function addLogOpt() {
}
function addSysctl() {
const container = document.getElementById('deploy-sysctls-container');
const container = deployEl('deploy-sysctls-container');
const id = `sysctl-${sysctlCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -1068,7 +1107,7 @@ function addSysctl() {
}
function addUlimit() {
const container = document.getElementById('deploy-ulimits-container');
const container = deployEl('deploy-ulimits-container');
const id = `ulimit-${ulimitCounter++}`;
const item = document.createElement('div');
item.className = 'array-item';
@@ -1613,8 +1652,8 @@ window.updateSliderRange = updateSliderRange;
// Network mode change handler
document.addEventListener('DOMContentLoaded', () => {
const networkMode = document.getElementById('deploy-network-mode');
const customNetworkContainer = document.getElementById('deploy-custom-network-container');
const networkMode = deployEl('deploy-network-mode');
const customNetworkContainer = deployEl('deploy-custom-network-container');
if (networkMode && customNetworkContainer) {
networkMode.addEventListener('change', (e) => {
if (e.target.value === 'container') {
@@ -1643,24 +1682,24 @@ document.addEventListener('DOMContentLoaded', () => {
// Collect all form data
function collectFormData() {
const data = {
containerName: document.getElementById('deploy-container-name')?.value.trim() || '',
image: document.getElementById('deploy-image')?.value.trim() || '',
command: document.getElementById('deploy-command')?.value.trim() || null,
entrypoint: document.getElementById('deploy-entrypoint')?.value.trim() || null,
workingDir: document.getElementById('deploy-workdir')?.value.trim() || null,
containerName: deployEl('deploy-container-name')?.value.trim() || '',
image: deployEl('deploy-image')?.value.trim() || '',
command: deployEl('deploy-command')?.value.trim() || null,
entrypoint: deployEl('deploy-entrypoint')?.value.trim() || null,
workingDir: deployEl('deploy-workdir')?.value.trim() || null,
// Networking
networkMode: document.getElementById('deploy-network-mode')?.value || 'bridge',
networkMode: deployEl('deploy-network-mode')?.value || 'bridge',
customNetwork: (() => {
const mode = document.getElementById('deploy-network-mode')?.value || 'bridge';
const customNet = document.getElementById('deploy-custom-network')?.value.trim();
const mode = deployEl('deploy-network-mode')?.value || 'bridge';
const customNet = deployEl('deploy-custom-network')?.value.trim();
if (!customNet) return null;
// host/none: no extra attach; container: peer container name; bridge: optional user network
if (mode === 'host' || mode === 'none') return null;
return customNet;
})(),
hostname: document.getElementById('deploy-hostname')?.value.trim() || null,
domainname: document.getElementById('deploy-domainname')?.value.trim() || null,
hostname: deployEl('deploy-hostname')?.value.trim() || null,
domainname: deployEl('deploy-domainname')?.value.trim() || null,
ports: collectPortMappings(),
dns: collectArrayItems('deploy-dns-container', 'data-dns-id'),
extraHosts: collectArrayItems('deploy-extra-hosts-container', 'data-host-id'),
@@ -1672,23 +1711,23 @@ function collectFormData() {
// Resources
// Range sliders use 0 for “unset / unlimited”
cpuLimit: (() => {
const v = parseFloat(document.getElementById('deploy-cpu-limit')?.value)
const v = parseFloat(deployEl('deploy-cpu-limit')?.value)
return Number.isFinite(v) && v > 0 ? v : null
})(),
cpuReservation: (() => {
const v = parseFloat(document.getElementById('deploy-cpu-reservation')?.value)
const v = parseFloat(deployEl('deploy-cpu-reservation')?.value)
return Number.isFinite(v) && v > 0 ? v : null
})(),
cpuShares: parseInt(document.getElementById('deploy-cpu-shares')?.value) || null,
cpuShares: parseInt(deployEl('deploy-cpu-shares')?.value) || null,
memoryLimit: (() => {
const v = parseInt(document.getElementById('deploy-memory-limit')?.value, 10)
const v = parseInt(deployEl('deploy-memory-limit')?.value, 10)
return Number.isFinite(v) && v > 0 ? v : null
})(),
memoryReservation: (() => {
const v = parseInt(document.getElementById('deploy-memory-reservation')?.value, 10)
const v = parseInt(deployEl('deploy-memory-reservation')?.value, 10)
return Number.isFinite(v) && v > 0 ? v : null
})(),
memorySwap: parseInt(document.getElementById('deploy-memory-swap')?.value) || null,
memorySwap: parseInt(deployEl('deploy-memory-swap')?.value) || null,
devices: collectArrayItems('deploy-devices-container', 'data-device-id'),
// Environment & Labels
@@ -1696,37 +1735,37 @@ function collectFormData() {
labels: collectLabels(),
// Security
user: document.getElementById('deploy-user')?.value.trim() || null,
group: document.getElementById('deploy-group')?.value.trim() || null,
privileged: document.getElementById('deploy-privileged')?.checked || false,
readonlyRootfs: document.getElementById('deploy-readonly-rootfs')?.checked || false,
user: deployEl('deploy-user')?.value.trim() || null,
group: deployEl('deploy-group')?.value.trim() || null,
privileged: deployEl('deploy-privileged')?.checked || false,
readonlyRootfs: deployEl('deploy-readonly-rootfs')?.checked || false,
capabilities: collectArrayItems('deploy-capabilities-container', 'data-cap-id'),
securityOpts: collectArrayItems('deploy-security-opts-container', 'data-secopt-id'),
// Runtime
restartPolicy: document.getElementById('deploy-restart-policy')?.value || 'no',
restartMaxRetries: parseInt(document.getElementById('deploy-restart-max-retries')?.value) || null,
autoRemove: document.getElementById('deploy-auto-remove')?.checked || false,
tty: document.getElementById('deploy-tty')?.checked || false,
stdinOpen: document.getElementById('deploy-stdin-open')?.checked || false,
detach: document.getElementById('deploy-detach')?.checked !== false, // Default true
init: document.getElementById('deploy-init')?.checked || false,
restartPolicy: deployEl('deploy-restart-policy')?.value || 'no',
restartMaxRetries: parseInt(deployEl('deploy-restart-max-retries')?.value) || null,
autoRemove: deployEl('deploy-auto-remove')?.checked || false,
tty: deployEl('deploy-tty')?.checked || false,
stdinOpen: deployEl('deploy-stdin-open')?.checked || false,
detach: deployEl('deploy-detach')?.checked !== false, // Default true
init: deployEl('deploy-init')?.checked || false,
// Health & Logging
healthCmd: document.getElementById('deploy-health-cmd')?.value.trim() || null,
healthInterval: parseInt(document.getElementById('deploy-health-interval')?.value) || null,
healthTimeout: parseInt(document.getElementById('deploy-health-timeout')?.value) || null,
healthRetries: parseInt(document.getElementById('deploy-health-retries')?.value) || null,
healthStartPeriod: parseInt(document.getElementById('deploy-health-start-period')?.value) || null,
logDriver: document.getElementById('deploy-log-driver')?.value || null,
healthCmd: deployEl('deploy-health-cmd')?.value.trim() || null,
healthInterval: parseInt(deployEl('deploy-health-interval')?.value) || null,
healthTimeout: parseInt(deployEl('deploy-health-timeout')?.value) || null,
healthRetries: parseInt(deployEl('deploy-health-retries')?.value) || null,
healthStartPeriod: parseInt(deployEl('deploy-health-start-period')?.value) || null,
logDriver: deployEl('deploy-log-driver')?.value || null,
logOpts: collectLogOpts(),
// Advanced
sysctls: collectSysctls(),
ulimits: collectUlimits(),
oomKillDisable: document.getElementById('deploy-oom-kill-disable')?.checked || false,
pidsLimit: parseInt(document.getElementById('deploy-pids-limit')?.value) || null,
shmSize: parseInt(document.getElementById('deploy-shm-size')?.value) || null,
oomKillDisable: deployEl('deploy-oom-kill-disable')?.checked || false,
pidsLimit: parseInt(deployEl('deploy-pids-limit')?.value) || null,
shmSize: parseInt(deployEl('deploy-shm-size')?.value) || null,
};
// Remove null/empty values
@@ -1752,7 +1791,7 @@ function collectArrayItems(containerId, dataAttr) {
// Collect port mappings from structured inputs
function collectPortMappings() {
const container = document.getElementById('deploy-ports-container');
const container = deployEl('deploy-ports-container');
if (!container) return [];
const ports = [];
@@ -1777,7 +1816,7 @@ function collectPortMappings() {
// Collect volume mounts from structured inputs
function collectVolumeMounts() {
const container = document.getElementById('deploy-volumes-container');
const container = deployEl('deploy-volumes-container');
if (!container) return [];
const volumes = [];
@@ -1814,7 +1853,7 @@ function collectVolumeMounts() {
}
function collectEnvVars() {
const container = document.getElementById('deploy-env');
const container = deployEl('deploy-env');
if (!container) return [];
const envVars = [];
@@ -1877,7 +1916,7 @@ function collectLabels() {
}
function collectLogOpts() {
const container = document.getElementById('deploy-log-opts-container');
const container = deployEl('deploy-log-opts-container');
if (!container) return null;
const opts = {};
container.querySelectorAll('[data-logopt-key]').forEach(keyInput => {
@@ -1922,9 +1961,9 @@ function collectUlimits() {
// Update preview
function updatePreview() {
const previewContainer = document.getElementById('deploy-preview-container');
const preview = document.getElementById('deploy-preview');
const showPreview = document.getElementById('deploy-show-preview')?.checked;
const previewContainer = deployEl('deploy-preview-container');
const preview = deployEl('deploy-preview');
const showPreview = deployEl('deploy-show-preview')?.checked;
if (!previewContainer || !preview) return;
@@ -2030,9 +2069,9 @@ function portainerVolumeToString(volume, nameHint = 'data') {
* @param {object} template
*/
function applyTemplateNetwork(template) {
const modeEl = document.getElementById('deploy-network-mode');
const customEl = document.getElementById('deploy-custom-network');
const customWrap = document.getElementById('deploy-custom-network-container');
const modeEl = deployEl('deploy-network-mode');
const customEl = deployEl('deploy-custom-network');
const customWrap = deployEl('deploy-custom-network-container');
if (!modeEl) return;
const raw =
@@ -2089,13 +2128,12 @@ function populateDeployFormFromTemplate(template, opts = {}) {
logOptCounter = sysctlCounter = ulimitCounter = tmpfsCounter = 0;
const typeNum = Number(template.type);
const isStack =
typeNum === 2 ||
typeNum === 3 ||
(!template.image &&
(template.repository?.url || template.repository?.URL || template.repository));
const catalogIsStack = isStackTemplate(template) || typeNum === 2 || typeNum === 3;
// After compose resolve, treat as deployable container when image is present
const hasImage = Boolean(String(template.image || template.Image || '').trim());
const isStack = catalogIsStack && !hasImage && !template._composeResolved;
// Clear dynamic lists first
// Clear dynamic lists first (scoped to active form root)
[
'deploy-ports-container',
'deploy-volumes-container',
@@ -2111,7 +2149,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
'deploy-ulimits-container',
'deploy-tmpfs-container',
].forEach((id) => {
const el = document.getElementById(id);
const el = deployEl(id);
if (el) el.innerHTML = '';
});
@@ -2123,7 +2161,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
// Container name
if (!opts.skipName) {
const nameEl = document.getElementById('deploy-container-name');
const nameEl = deployEl('deploy-container-name');
if (nameEl) {
const preferred = String(template.name || template.title || nameHint)
.toLowerCase()
@@ -2135,7 +2173,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Image (+ optional registry prefix when image is bare and registry set)
const imageEl = document.getElementById('deploy-image');
const imageEl = deployEl('deploy-image');
if (imageEl) {
let image = String(template.image || template.Image || '').trim();
const registry = String(template.registry || template.Registry || '').trim();
@@ -2144,15 +2182,22 @@ function populateDeployFormFromTemplate(template, opts = {}) {
image = `${registry.replace(/\/$/, '')}/${image}`;
}
imageEl.value = image;
if (!image && isStack) {
if (!image && catalogIsStack) {
warnings.push(
'Compose/stack template has no single image — deploy via Stacks with the compose file from the repository.'
template._composeWarning ||
'Compose/stack template has no single image — could not resolve compose from the repository. Deploy the full stack via Stacks, or pick a container (type 1) template.'
);
} else if (template._composeResolved && template._composeService) {
warnings.push(
`Resolved image from compose service “${template._composeService}” (multi-service stacks may need Stacks for full deploy).`
);
} else if (template._composeWarning) {
warnings.push(template._composeWarning);
}
}
// Command
const cmdEl = document.getElementById('deploy-command');
const cmdEl = deployEl('deploy-command');
if (cmdEl) {
const cmd = template.command ?? template.Command ?? template.cmd;
if (Array.isArray(cmd)) cmdEl.value = cmd.join(' ');
@@ -2161,7 +2206,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Entrypoint (rare in Portainer templates)
const epEl = document.getElementById('deploy-entrypoint');
const epEl = deployEl('deploy-entrypoint');
if (epEl) {
const ep = template.entrypoint ?? template.Entrypoint;
if (Array.isArray(ep)) epEl.value = ep.join(' ');
@@ -2170,17 +2215,17 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Working dir
const wdEl = document.getElementById('deploy-workdir');
const wdEl = deployEl('deploy-workdir');
if (wdEl) {
wdEl.value = template.workingDir || template.working_dir || template.WorkingDir || '';
}
// Hostname / domain
const hostnameEl = document.getElementById('deploy-hostname');
const hostnameEl = deployEl('deploy-hostname');
if (hostnameEl) {
hostnameEl.value = template.hostname || template.Hostname || '';
}
const domainEl = document.getElementById('deploy-domainname');
const domainEl = deployEl('deploy-domainname');
if (domainEl) {
domainEl.value = template.domainname || template.domainName || template.Domainname || '';
}
@@ -2190,8 +2235,8 @@ function populateDeployFormFromTemplate(template, opts = {}) {
// Interactive / TTY
const interactive = template.interactive === true || template.Interactive === true;
const ttyEl = document.getElementById('deploy-tty');
const stdinEl = document.getElementById('deploy-stdin-open');
const ttyEl = deployEl('deploy-tty');
const stdinEl = deployEl('deploy-stdin-open');
if (ttyEl) ttyEl.checked = interactive || template.tty === true || template.Tty === true;
if (stdinEl) {
stdinEl.checked =
@@ -2199,7 +2244,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Privileged
const privEl = document.getElementById('deploy-privileged');
const privEl = deployEl('deploy-privileged');
if (privEl) {
privEl.checked =
template.privileged === true ||
@@ -2208,7 +2253,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Read-only rootfs
const roEl = document.getElementById('deploy-readonly-rootfs');
const roEl = deployEl('deploy-readonly-rootfs');
if (roEl) {
roEl.checked =
template.readonly === true ||
@@ -2217,7 +2262,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Restart policy
const rpEl = document.getElementById('deploy-restart-policy');
const rpEl = deployEl('deploy-restart-policy');
if (rpEl) {
const rp =
template.restart_policy ||
@@ -2305,7 +2350,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
for (const pair of pairs) {
addLabel();
const container = document.getElementById('deploy-labels-container');
const container = deployEl('deploy-labels-container');
const last = container?.lastElementChild?.querySelector('input');
if (last) last.value = pair;
}
@@ -2318,7 +2363,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
for (const h of hosts) {
if (h == null || h === '') continue;
addExtraHost();
const container = document.getElementById('deploy-extra-hosts-container');
const container = deployEl('deploy-extra-hosts-container');
const last = container?.lastElementChild?.querySelector('input');
if (last) last.value = String(h);
}
@@ -2330,7 +2375,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
for (const d of devicesRaw) {
if (!d) continue;
addDeviceMapping();
const container = document.getElementById('deploy-devices-container');
const container = deployEl('deploy-devices-container');
const last = container?.lastElementChild?.querySelector('input');
if (last) {
last.value =
@@ -2347,7 +2392,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
for (const c of caps) {
if (!c) continue;
addCapability();
const container = document.getElementById('deploy-capabilities-container');
const container = deployEl('deploy-capabilities-container');
const last = container?.lastElementChild?.querySelector('input');
if (last) last.value = String(c);
}
@@ -2358,7 +2403,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
if (sysctls && typeof sysctls === 'object' && !Array.isArray(sysctls)) {
for (const [k, v] of Object.entries(sysctls)) {
addSysctl();
const container = document.getElementById('deploy-sysctls-container');
const container = deployEl('deploy-sysctls-container');
const last = container?.lastElementChild?.querySelector('input');
if (last) last.value = `${k}=${v}`;
}
@@ -2374,7 +2419,7 @@ function populateDeployFormFromTemplate(template, opts = {}) {
}
// Open deploy modal and populate the form dynamically
function openDeployModal(template) {
async function openDeployModal(template) {
if (!template || typeof template !== 'object') {
console.error('[ERROR] Invalid template provided to openDeployModal:', template);
showAlert('danger', 'Invalid template data. Please try again.');
@@ -2383,6 +2428,10 @@ function openDeployModal(template) {
// Initialize template deployer lazily (DOM elements, modal, event listeners)
initTemplateDeployer();
// Scope all deploy-* lookups to the modal form (not the hidden deploy-view form)
const form = document.getElementById('deploy-form');
setDeployFormScope(form);
// Store current template for validation
currentTemplate = template;
@@ -2393,11 +2442,21 @@ function openDeployModal(template) {
deployTitle.textContent = `Deploy ${template.title || template.name || 'Template'}`;
}
// Clear form fields (modal form — may share ids with deploy-view; first id wins)
const form = document.getElementById('deploy-form') || document.getElementById('deploy-view-form');
if (form) form.reset();
const result = populateDeployFormFromTemplate(template);
// Resolve stack compose → primary service image/ports/volumes/env when catalog omits image
let resolved = template;
if (!String(template.image || template.Image || '').trim() && isStackTemplate(template)) {
showStatusIndicator(`Loading compose for ${template.title || template.name || 'template'}`);
try {
resolved = await resolveTemplateForDeploy(template);
} finally {
hideStatusIndicator();
}
}
currentTemplate = resolved;
const result = populateDeployFormFromTemplate(resolved);
if (result.isStack) {
showAlert(
'warning',
@@ -2405,7 +2464,10 @@ function openDeployModal(template) {
'This is a Compose/stack template. Use Stacks to deploy the compose file from its repository.'
);
} else if (result.warnings.length) {
showAlert('warning', result.warnings.join(' '));
showAlert(
resolved._composeResolved ? 'info' : 'warning',
result.warnings.join(' ')
);
}
if (templateDeployModal) {
@@ -2416,14 +2478,17 @@ function openDeployModal(template) {
modalElement.addEventListener(
'shown.bs.modal',
() => {
const portsContainer = document.getElementById('deploy-ports-container');
const empty =
setDeployFormScope(document.getElementById('deploy-form'));
const portsContainer = deployEl('deploy-ports-container');
const imageEl = deployEl('deploy-image');
const emptyPorts =
!portsContainer ||
portsContainer.querySelectorAll('.port-mapping-item').length === 0;
const emptyImage = !imageEl?.value?.trim();
const hasPorts =
Array.isArray(template.ports) && template.ports.length > 0;
if (empty && hasPorts) {
populateDeployFormFromTemplate(template, { skipName: true });
Array.isArray(resolved.ports) && resolved.ports.length > 0;
if ((emptyPorts && hasPorts) || (emptyImage && resolved.image)) {
populateDeployFormFromTemplate(resolved, { skipName: true });
}
},
{ once: true }
@@ -2510,7 +2575,7 @@ function validateFormData(data) {
// Validate environment variables
if (data.env && Array.isArray(data.env)) {
const envContainer = document.getElementById('deploy-env');
const envContainer = deployEl('deploy-env');
if (envContainer && currentTemplate && currentTemplate.env) {
// Create a map of template env vars for validation
const templateEnvMap = {};
@@ -4073,3 +4138,5 @@ export {
window.collectDuplicateFormData = collectDuplicateFormData;
window.populateDuplicateForm = populateDuplicateForm;
window.populateDeployFormFromTemplate = populateDeployFormFromTemplate;
window.setDeployFormScope = setDeployFormScope;
window.resolveTemplateForDeploy = resolveTemplateForDeploy;