Add container create page from the containers list with always-pull.
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:
Raven Scott
2026-07-15 14:27:05 -04:00
parent bbf0607aaf
commit b185ea7a55
9 changed files with 1055 additions and 24 deletions
+58
View File
@@ -3790,3 +3790,61 @@ option {
display: none;
}
}
/* Add container page */
.add-container-page .page-header {
margin-bottom: 1.25rem;
}
.add-container-page .addc-back-link {
font-size: 0.85rem;
text-decoration: none;
}
.add-container-page .addc-back-link:hover {
color: var(--accent-primary, #2dd4bf) !important;
}
.add-container-form .info-card {
background: rgba(15, 23, 42, 0.65);
border: 1px solid rgba(51, 65, 85, 0.85);
border-radius: 12px;
padding: 1.15rem 1.25rem;
}
.add-container-form .form-check-input:checked {
background-color: var(--pd-accent-primary, #2dd4bf);
border-color: var(--pd-accent-primary, #2dd4bf);
}
.add-container-form .accordion-button:not(.collapsed) {
color: var(--pd-accent-primary, #2dd4bf);
box-shadow: none;
}
.add-container-form .accordion-button:focus {
box-shadow: none;
border-color: rgba(45, 212, 191, 0.35);
}
.add-container-form .port-mapping-fields,
.add-container-form .volume-mount-fields {
display: flex;
flex-wrap: wrap;
gap: 0.65rem;
align-items: flex-end;
width: 100%;
}
.add-container-form .port-field-group,
.add-container-form .volume-field-group {
flex: 1 1 6rem;
min-width: 0;
}
.add-container-form .port-connector {
align-self: center;
color: var(--text-muted, #9aa8bc);
padding-bottom: 0.35rem;
}
.addc-sticky-actions {
position: sticky;
bottom: 0.75rem;
z-index: 5;
padding: 0.75rem 1rem;
background: rgba(15, 17, 23, 0.92);
border: 1px solid rgba(51, 65, 85, 0.7);
border-radius: 12px;
backdrop-filter: blur(8px);
}
+18 -4
View File
@@ -345,11 +345,16 @@ export async function deployContainerWithSteps(args) {
/** @type {{ pulledOk: boolean }} */
const state = { pulledOk: false }
const replace = args.replace === true
const fromAdd = args.source === 'add-container'
const alwaysPull = args.alwaysPull !== false
const jobLabel = replace
? `Replace ${args.containerName || 'container'}`
: fromAdd
? `Create ${args.containerName || 'container'}`
: `Deploy ${args.containerName || 'container'}`
return runJob(
replace
? `Replace ${args.containerName || 'container'}`
: `Deploy ${args.containerName || 'container'}`,
jobLabel,
[
{
id: 'validate',
@@ -362,7 +367,9 @@ export async function deployContainerWithSteps(args) {
}
log(`Name=${args.containerName}`)
log(`Image=${args.image}`)
log(`Always pull image: ${alwaysPull ? 'yes' : 'no (local if present)'}`)
if (replace) log('Mode: replace existing container with same name')
if (args.publishAllPorts) log('Publish all exposed ports: yes')
if (args.ports?.length) log(`Ports: ${args.ports.join(', ')}`)
if (args.volumes?.length) log(`Volumes: ${args.volumes.join(', ')}`)
if (args.networkMode) log(`Network mode: ${args.networkMode}`)
@@ -371,13 +378,19 @@ export async function deployContainerWithSteps(args) {
},
{
id: 'pull',
label: 'Pull image (if needed)',
label: alwaysPull ? 'Pull image' : 'Resolve image (local or pull)',
run: async ({ log }) => {
if (args.skipPull) {
log('Skip pull (using local image)')
state.pulledOk = true
return
}
// alwaysPull=false: let the server decide (local if present, else pull)
if (!alwaysPull) {
log('Always pull is off — server will use a local image if available')
state.pulledOk = false
return
}
log(`Pulling ${args.image}`)
try {
await manager.request(Methods.pullImage, { image: args.image })
@@ -406,6 +419,7 @@ export async function deployContainerWithSteps(args) {
const payload = {
...args,
skipPull: state.pulledOk === true,
alwaysPull,
replace: replace === true,
}
try {