Add container create page from the containers list with always-pull.
Release rolling / release (push) Successful in 8m24s
Release rolling / release (push) Successful in 8m24s
Adds a dedicated blank create form (name, image, always pull, ports, auto-remove, advanced settings) separate from template Deploy, reuses deployContainer with pull-if-missing and publish-all-ports support.
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
/**
|
||||
* Add container page — blank create form (separate from Deploy templates).
|
||||
* Field IDs use the addc- prefix. Submit goes through deployContainerWithSteps.
|
||||
*/
|
||||
import {
|
||||
showStatusIndicator,
|
||||
hideStatusIndicator,
|
||||
showAlert,
|
||||
} from './uiUtils.js'
|
||||
import { presentError } from '../client/errors.js'
|
||||
|
||||
let portSeq = 0
|
||||
let volSeq = 0
|
||||
let envSeq = 0
|
||||
let labelSeq = 0
|
||||
let dnsSeq = 0
|
||||
let hostSeq = 0
|
||||
let capSeq = 0
|
||||
|
||||
function el(id) {
|
||||
return document.getElementById(id)
|
||||
}
|
||||
|
||||
function val(id) {
|
||||
return el(id)?.value?.trim?.() || ''
|
||||
}
|
||||
|
||||
function checked(id) {
|
||||
return Boolean(el(id)?.checked)
|
||||
}
|
||||
|
||||
function numOrNull(id, parse = parseFloat) {
|
||||
const raw = el(id)?.value
|
||||
if (raw === '' || raw == null) return null
|
||||
const n = parse(raw)
|
||||
return Number.isFinite(n) && n > 0 ? n : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear dynamic rows and reset primary fields.
|
||||
*/
|
||||
export function resetAddContainerForm() {
|
||||
for (const id of [
|
||||
'addc-ports',
|
||||
'addc-volumes',
|
||||
'addc-env',
|
||||
'addc-labels',
|
||||
'addc-dns',
|
||||
'addc-extra-hosts',
|
||||
'addc-capabilities',
|
||||
]) {
|
||||
const c = el(id)
|
||||
if (c) c.innerHTML = ''
|
||||
}
|
||||
const form = el('add-container-form')
|
||||
if (form) form.reset()
|
||||
// Defaults after reset
|
||||
const always = el('addc-always-pull')
|
||||
if (always) always.checked = true
|
||||
const detach = el('addc-detach')
|
||||
if (detach) detach.checked = true
|
||||
const net = el('addc-network-mode')
|
||||
if (net) net.value = 'bridge'
|
||||
const restart = el('addc-restart-policy')
|
||||
if (restart) restart.value = 'no'
|
||||
toggleAddcCustomNetwork()
|
||||
}
|
||||
|
||||
export function addcAddPort(portData = null) {
|
||||
const container = el('addc-ports')
|
||||
if (!container) return
|
||||
const id = `addc-port-${++portSeq}`
|
||||
let hostPort = ''
|
||||
let containerPort = ''
|
||||
let protocol = 'tcp'
|
||||
if (portData != null) {
|
||||
const portStr = String(portData).trim()
|
||||
if (portStr.includes(':')) {
|
||||
const [h, rest] = portStr.split(':')
|
||||
hostPort = h.trim()
|
||||
if (rest.includes('/')) {
|
||||
const [c, p] = rest.split('/')
|
||||
containerPort = c.trim()
|
||||
protocol = p?.toLowerCase() === 'udp' ? 'udp' : 'tcp'
|
||||
} else containerPort = rest.trim()
|
||||
} else if (portStr.includes('/')) {
|
||||
const [c, p] = portStr.split('/')
|
||||
containerPort = c.trim()
|
||||
protocol = p?.toLowerCase() === 'udp' ? 'udp' : 'tcp'
|
||||
} else if (portStr) containerPort = portStr
|
||||
}
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item port-mapping-item'
|
||||
item.dataset.addcRow = id
|
||||
item.innerHTML = `
|
||||
<div class="port-mapping-fields">
|
||||
<div class="port-field-group">
|
||||
<label class="port-field-label">Host Port</label>
|
||||
<input type="number" class="form-control bg-dark text-white port-host-input" placeholder="8080" min="1" max="65535" value="${escapeAttr(hostPort)}">
|
||||
</div>
|
||||
<div class="port-connector"><i class="fas fa-arrow-right"></i></div>
|
||||
<div class="port-field-group">
|
||||
<label class="port-field-label">Container Port</label>
|
||||
<input type="number" class="form-control bg-dark text-white port-container-input" placeholder="80" min="1" max="65535" value="${escapeAttr(containerPort)}" required>
|
||||
</div>
|
||||
<div class="port-field-group" style="max-width:6rem">
|
||||
<label class="port-field-label">Protocol</label>
|
||||
<select class="form-select bg-dark text-white port-protocol-input">
|
||||
<option value="tcp" ${protocol === 'tcp' ? 'selected' : ''}>TCP</option>
|
||||
<option value="udp" ${protocol === 'udp' ? 'selected' : ''}>UDP</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger align-self-end" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>`
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function addcAddVolume() {
|
||||
const container = el('addc-volumes')
|
||||
if (!container) return
|
||||
const id = `addc-vol-${++volSeq}`
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item volume-mount-item'
|
||||
item.dataset.addcRow = id
|
||||
item.innerHTML = `
|
||||
<div class="volume-mount-fields">
|
||||
<div class="volume-field-group" style="max-width:8rem">
|
||||
<label class="volume-field-label">Type</label>
|
||||
<select class="form-select bg-dark text-white volume-type-input">
|
||||
<option value="bind">Bind</option>
|
||||
<option value="named">Named volume</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="volume-field-group volume-host-wrap flex-grow-1">
|
||||
<label class="volume-field-label volume-src-label">Host path</label>
|
||||
<input type="text" class="form-control bg-dark text-white volume-host-input" placeholder="/path/on/host or volume-name">
|
||||
</div>
|
||||
<div class="volume-field-group flex-grow-1">
|
||||
<label class="volume-field-label">Container path</label>
|
||||
<input type="text" class="form-control bg-dark text-white volume-container-input" placeholder="/data" required>
|
||||
</div>
|
||||
<div class="volume-field-group" style="max-width:6rem">
|
||||
<label class="volume-field-label">Mode</label>
|
||||
<select class="form-select bg-dark text-white volume-mode-input">
|
||||
<option value="rw">RW</option>
|
||||
<option value="ro">RO</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger align-self-end" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>`
|
||||
const typeSel = item.querySelector('.volume-type-input')
|
||||
const srcLabel = item.querySelector('.volume-src-label')
|
||||
typeSel?.addEventListener('change', () => {
|
||||
if (srcLabel) srcLabel.textContent = typeSel.value === 'named' ? 'Volume name' : 'Host path'
|
||||
})
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function addcAddEnv() {
|
||||
const container = el('addc-env')
|
||||
if (!container) return
|
||||
const id = `addc-env-${++envSeq}`
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item mb-2 d-flex gap-2 align-items-center'
|
||||
item.dataset.addcRow = id
|
||||
item.innerHTML = `
|
||||
<input type="text" class="form-control bg-dark text-white" placeholder="KEY" data-env-key="${id}" style="flex:0 0 36%">
|
||||
<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" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>`
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function addcAddLabel() {
|
||||
const container = el('addc-labels')
|
||||
if (!container) return
|
||||
const id = `addc-label-${++labelSeq}`
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item d-flex gap-2 align-items-center mb-2'
|
||||
item.dataset.addcRow = 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" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>`
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function addcAddDns() {
|
||||
const container = el('addc-dns')
|
||||
if (!container) return
|
||||
const id = `addc-dns-${++dnsSeq}`
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item d-flex gap-2 align-items-center mb-2'
|
||||
item.dataset.addcRow = 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" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>`
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function addcAddExtraHost() {
|
||||
const container = el('addc-extra-hosts')
|
||||
if (!container) return
|
||||
const id = `addc-host-${++hostSeq}`
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item d-flex gap-2 align-items-center mb-2'
|
||||
item.dataset.addcRow = id
|
||||
item.innerHTML = `
|
||||
<input type="text" class="form-control bg-dark text-white" placeholder="hostname:ip" data-host-id="${id}">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>`
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function addcAddCapability() {
|
||||
const container = el('addc-capabilities')
|
||||
if (!container) return
|
||||
const id = `addc-cap-${++capSeq}`
|
||||
const item = document.createElement('div')
|
||||
item.className = 'array-item d-flex gap-2 align-items-center mb-2'
|
||||
item.dataset.addcRow = 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" data-addc-remove title="Remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>`
|
||||
item.querySelector('[data-addc-remove]')?.addEventListener('click', () => item.remove())
|
||||
container.appendChild(item)
|
||||
}
|
||||
|
||||
export function toggleAddcCustomNetwork() {
|
||||
const mode = val('addc-network-mode') || 'bridge'
|
||||
const wrap = el('addc-custom-network-wrap')
|
||||
const label = el('addc-custom-network-label')
|
||||
const hint = el('addc-custom-network-hint')
|
||||
const input = el('addc-custom-network')
|
||||
if (!wrap) return
|
||||
if (mode === 'host' || mode === 'none') {
|
||||
wrap.style.display = 'none'
|
||||
return
|
||||
}
|
||||
wrap.style.display = ''
|
||||
if (mode === 'container') {
|
||||
if (label) label.textContent = 'Container ID / name'
|
||||
if (hint) hint.textContent = 'Share the network stack of this container'
|
||||
if (input) input.placeholder = 'container-name-or-id'
|
||||
} else {
|
||||
if (label) label.textContent = 'Attach to network'
|
||||
if (hint) hint.textContent = 'Named network to join after create (optional)'
|
||||
if (input) input.placeholder = 'optional network name'
|
||||
}
|
||||
}
|
||||
|
||||
function collectPorts() {
|
||||
const container = el('addc-ports')
|
||||
if (!container) return []
|
||||
const ports = []
|
||||
container.querySelectorAll('.port-mapping-item').forEach((item) => {
|
||||
const host = item.querySelector('.port-host-input')?.value.trim() || ''
|
||||
const cPort = item.querySelector('.port-container-input')?.value.trim() || ''
|
||||
const protocol = item.querySelector('.port-protocol-input')?.value || 'tcp'
|
||||
if (!cPort) return
|
||||
ports.push(host ? `${host}:${cPort}/${protocol}` : `${cPort}/${protocol}`)
|
||||
})
|
||||
return ports
|
||||
}
|
||||
|
||||
function collectVolumes() {
|
||||
const container = el('addc-volumes')
|
||||
if (!container) return []
|
||||
const volumes = []
|
||||
container.querySelectorAll('.volume-mount-item').forEach((item) => {
|
||||
const src = item.querySelector('.volume-host-input')?.value.trim() || ''
|
||||
const dest = item.querySelector('.volume-container-input')?.value.trim() || ''
|
||||
const mode = item.querySelector('.volume-mode-input')?.value || 'rw'
|
||||
if (!src || !dest) return
|
||||
volumes.push(`${src}:${dest}:${mode}`)
|
||||
})
|
||||
return volumes
|
||||
}
|
||||
|
||||
function collectEnv() {
|
||||
const container = el('addc-env')
|
||||
if (!container) return []
|
||||
const env = []
|
||||
container.querySelectorAll('[data-env-key]').forEach((keyInput) => {
|
||||
const name = keyInput.value.trim()
|
||||
if (!name) return
|
||||
const id = keyInput.getAttribute('data-env-key')
|
||||
const valueInput = container.querySelector(`[data-env-value="${id}"]`)
|
||||
env.push({ name, value: valueInput?.value ?? '' })
|
||||
})
|
||||
return env
|
||||
}
|
||||
|
||||
function collectLabels() {
|
||||
const container = el('addc-labels')
|
||||
if (!container) return {}
|
||||
/** @type {Record<string, string>} */
|
||||
const labels = {}
|
||||
container.querySelectorAll('[data-label-id]').forEach((input) => {
|
||||
const raw = input.value.trim()
|
||||
if (!raw || !raw.includes('=')) return
|
||||
const eq = raw.indexOf('=')
|
||||
const k = raw.slice(0, eq).trim()
|
||||
const v = raw.slice(eq + 1)
|
||||
if (k) labels[k] = v
|
||||
})
|
||||
return labels
|
||||
}
|
||||
|
||||
function collectSimpleList(containerId, attr) {
|
||||
const container = el(containerId)
|
||||
if (!container) return []
|
||||
const out = []
|
||||
container.querySelectorAll(`[${attr}]`).forEach((input) => {
|
||||
const v = input.value.trim()
|
||||
if (v) out.push(v)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Build deployContainer payload from the add-container form.
|
||||
* @returns {object}
|
||||
*/
|
||||
export function collectAddContainerForm() {
|
||||
const networkMode = val('addc-network-mode') || 'bridge'
|
||||
const customNet = val('addc-custom-network')
|
||||
const labels = collectLabels()
|
||||
let customNetwork = null
|
||||
if (customNet && networkMode !== 'host' && networkMode !== 'none') {
|
||||
customNetwork = customNet
|
||||
}
|
||||
|
||||
const data = {
|
||||
source: 'add-container',
|
||||
containerName: val('addc-name'),
|
||||
image: val('addc-image'),
|
||||
alwaysPull: checked('addc-always-pull'),
|
||||
publishAllPorts: checked('addc-publish-all'),
|
||||
command: val('addc-command') || null,
|
||||
entrypoint: val('addc-entrypoint') || null,
|
||||
workingDir: val('addc-workdir') || null,
|
||||
networkMode,
|
||||
customNetwork,
|
||||
hostname: val('addc-hostname') || null,
|
||||
domainname: val('addc-domainname') || null,
|
||||
ports: collectPorts(),
|
||||
dns: collectSimpleList('addc-dns', 'data-dns-id'),
|
||||
extraHosts: collectSimpleList('addc-extra-hosts', 'data-host-id'),
|
||||
volumes: collectVolumes(),
|
||||
env: collectEnv(),
|
||||
labels: Object.keys(labels).length ? labels : null,
|
||||
user: val('addc-user') || null,
|
||||
privileged: checked('addc-privileged'),
|
||||
readonlyRootfs: checked('addc-readonly-rootfs'),
|
||||
capabilities: collectSimpleList('addc-capabilities', 'data-cap-id'),
|
||||
restartPolicy: val('addc-restart-policy') || 'no',
|
||||
restartMaxRetries: parseInt(el('addc-restart-max-retries')?.value, 10) || null,
|
||||
autoRemove: checked('addc-auto-remove'),
|
||||
tty: checked('addc-tty'),
|
||||
stdinOpen: checked('addc-stdin-open'),
|
||||
init: checked('addc-init'),
|
||||
cpuLimit: numOrNull('addc-cpu-limit'),
|
||||
memoryLimit: numOrNull('addc-memory-limit', (x) => parseInt(x, 10)),
|
||||
memoryReservation: numOrNull('addc-memory-reservation', (x) => parseInt(x, 10)),
|
||||
shmSize: numOrNull('addc-shm-size', (x) => parseInt(x, 10)),
|
||||
healthCmd: val('addc-health-cmd') || null,
|
||||
logDriver: val('addc-log-driver') || null,
|
||||
}
|
||||
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (
|
||||
data[key] === null ||
|
||||
data[key] === '' ||
|
||||
(Array.isArray(data[key]) && data[key].length === 0)
|
||||
) {
|
||||
delete data[key]
|
||||
}
|
||||
})
|
||||
// Keep boolean alwaysPull even when false
|
||||
data.alwaysPull = checked('addc-always-pull')
|
||||
data.publishAllPorts = checked('addc-publish-all')
|
||||
data.source = 'add-container'
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} payload
|
||||
*/
|
||||
async function confirmReplaceIfNeeded(payload) {
|
||||
if (payload.replace) return payload
|
||||
if (typeof window.peardockOps?.askUserConfirm !== 'function') return payload
|
||||
// Server returns conflict; caller may also pre-check. Keep replace off until conflict.
|
||||
return payload
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit create form via job stepper.
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function submitAddContainer() {
|
||||
let payload = collectAddContainerForm()
|
||||
if (!payload.containerName || !payload.image) {
|
||||
throw new Error(
|
||||
'Container name and image are required — How to fix: fill both fields before deploying.'
|
||||
)
|
||||
}
|
||||
|
||||
// Reuse template deploy replace helper when available
|
||||
if (typeof window.deployDockerContainer === 'function') {
|
||||
// Prefer shared path for name-conflict handling + job tray
|
||||
const result = await window.deployDockerContainer(payload)
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof window.peardockOps?.deployContainerWithSteps !== 'function') {
|
||||
throw new Error('Deploy helpers are not ready')
|
||||
}
|
||||
|
||||
payload = await confirmReplaceIfNeeded(payload)
|
||||
try {
|
||||
const job = await window.peardockOps.deployContainerWithSteps(payload)
|
||||
return {
|
||||
success: true,
|
||||
viaJob: true,
|
||||
message:
|
||||
job?.result?.message ||
|
||||
`Container "${payload.containerName}" created from image "${payload.image}"`,
|
||||
id: job?.result?.id,
|
||||
}
|
||||
} catch (err) {
|
||||
if (
|
||||
err?.code === 'CONTAINER_NAME_CONFLICT' ||
|
||||
/already exists|name.*already|already in use/i.test(String(err?.message || ''))
|
||||
) {
|
||||
let replace = false
|
||||
if (window.peardockOps?.askUserConfirm) {
|
||||
replace = await window.peardockOps.askUserConfirm(
|
||||
'Container name already in use',
|
||||
`A container named "${payload.containerName}" already exists. Replace it? This stops and removes the old container, then creates a new one with your settings.`,
|
||||
{ confirmLabel: 'Replace', danger: true, icon: 'fa-redo' }
|
||||
)
|
||||
}
|
||||
if (replace) {
|
||||
const job = await window.peardockOps.deployContainerWithSteps({
|
||||
...payload,
|
||||
replace: true,
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
viaJob: true,
|
||||
message:
|
||||
job?.result?.message ||
|
||||
`Container "${payload.containerName}" replaced successfully`,
|
||||
id: job?.result?.id,
|
||||
replaced: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err && typeof err === 'object') err.viaJob = true
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire buttons and network mode toggle once.
|
||||
*/
|
||||
export function initAddContainerPage() {
|
||||
const form = el('add-container-form')
|
||||
if (!form || form.dataset.addcBound === '1') return
|
||||
form.dataset.addcBound = '1'
|
||||
|
||||
el('addc-network-mode')?.addEventListener('change', toggleAddcCustomNetwork)
|
||||
el('addc-add-port')?.addEventListener('click', () => addcAddPort())
|
||||
el('addc-add-volume')?.addEventListener('click', () => addcAddVolume())
|
||||
el('addc-add-env')?.addEventListener('click', () => addcAddEnv())
|
||||
el('addc-add-label')?.addEventListener('click', () => addcAddLabel())
|
||||
el('addc-add-dns')?.addEventListener('click', () => addcAddDns())
|
||||
el('addc-add-extra-host')?.addEventListener('click', () => addcAddExtraHost())
|
||||
el('addc-add-capability')?.addEventListener('click', () => addcAddCapability())
|
||||
|
||||
el('addc-cancel-btn')?.addEventListener('click', () => {
|
||||
if (typeof window.navigateToView === 'function') window.navigateToView('containers')
|
||||
})
|
||||
|
||||
el('addc-reset-btn')?.addEventListener('click', () => resetAddContainerForm())
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
await handleAddContainerSubmit()
|
||||
})
|
||||
|
||||
el('addc-deploy-btn')?.addEventListener('click', async (e) => {
|
||||
e.preventDefault()
|
||||
await handleAddContainerSubmit()
|
||||
})
|
||||
|
||||
toggleAddcCustomNetwork()
|
||||
}
|
||||
|
||||
async function handleAddContainerSubmit() {
|
||||
const btn = el('addc-deploy-btn')
|
||||
if (btn) btn.disabled = true
|
||||
try {
|
||||
showStatusIndicator('Creating container…')
|
||||
const result = await submitAddContainer()
|
||||
// Job drawer owns success feedback when viaJob
|
||||
if (!result?.viaJob) {
|
||||
showAlert('success', result?.message || 'Container created')
|
||||
}
|
||||
resetAddContainerForm()
|
||||
if (typeof window.navigateToView === 'function') {
|
||||
window.navigateToView('containers')
|
||||
}
|
||||
if (typeof window.sendCommand === 'function') {
|
||||
window.sendCommand('listContainers')
|
||||
}
|
||||
} catch (err) {
|
||||
if (err?.code === 'DEPLOY_CANCELLED') return
|
||||
// Job tray owns multi-line error when viaJob
|
||||
if (!err?.viaJob) {
|
||||
presentError(err, 'deployContainer', { showAlert })
|
||||
}
|
||||
} finally {
|
||||
if (btn) btn.disabled = false
|
||||
hideStatusIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttr(s) {
|
||||
return String(s ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
}
|
||||
|
||||
// Window exports for inline handlers if needed
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addcAddPort = addcAddPort
|
||||
window.addcAddVolume = addcAddVolume
|
||||
window.addcAddEnv = addcAddEnv
|
||||
window.addcAddLabel = addcAddLabel
|
||||
window.addcAddDns = addcAddDns
|
||||
window.addcAddExtraHost = addcAddExtraHost
|
||||
window.addcAddCapability = addcAddCapability
|
||||
window.resetAddContainerForm = resetAddContainerForm
|
||||
window.collectAddContainerForm = collectAddContainerForm
|
||||
window.submitAddContainer = submitAddContainer
|
||||
window.initAddContainerPage = initAddContainerPage
|
||||
window.toggleAddcCustomNetwork = toggleAddcCustomNetwork
|
||||
}
|
||||
|
||||
export default {
|
||||
initAddContainerPage,
|
||||
resetAddContainerForm,
|
||||
collectAddContainerForm,
|
||||
submitAddContainer,
|
||||
}
|
||||
Reference in New Issue
Block a user