@@ -381,7 +410,7 @@ export function ContainerFormPage() {
];
return (
-
);
}
diff --git a/create-a-container/client/src/pages/jobs/JobDetailPage.tsx b/create-a-container/client/src/pages/jobs/JobDetailPage.tsx
index 9d30fd22..21b8c15a 100644
--- a/create-a-container/client/src/pages/jobs/JobDetailPage.tsx
+++ b/create-a-container/client/src/pages/jobs/JobDetailPage.tsx
@@ -11,6 +11,7 @@ import {
import { ArrowLeft, Terminal } from 'lucide-react';
import { ButtonLink } from '@/components/ButtonLink';
import { ApiError } from '@/lib/api';
+import { useCurrentSiteId } from '@/lib/currentSite';
import { keys, queries } from '@/lib/queries';
import type { JobStatusRow } from '@/lib/types';
@@ -37,6 +38,10 @@ function statusVariant(s: string): 'default' | 'success' | 'warning' | 'danger'
export function JobDetailPage() {
const { id } = useParams<{ id: string }>();
+ // /jobs/:id has no parent list route — Back returns to the current site's
+ // containers (where jobs are launched from), or the sites list as a fallback.
+ const currentSiteId = useCurrentSiteId();
+ const backTo = currentSiteId ? `/sites/${currentSiteId}/containers` : '/sites';
const { data: job, isLoading, error, refetch } = useQuery({
queryKey: keys.job(id!),
queryFn: () => queries.getJob(id!),
@@ -113,7 +118,9 @@ export function JobDetailPage() {
subtitle={job.command}
icon={
}
actions={
-
}>Back
+
}>
+ {currentSiteId ? 'Back to containers' : 'Back to sites'}
+
}
/>
diff --git a/create-a-container/openapi.v1.yaml b/create-a-container/openapi.v1.yaml
index c88603a7..982a8485 100644
--- a/create-a-container/openapi.v1.yaml
+++ b/create-a-container/openapi.v1.yaml
@@ -837,7 +837,7 @@ paths:
put:
operationId: update_container
tags: [Containers]
- summary: Update services/env/entrypoint; enqueues a restart job when needed (owner/admin)
+ summary: Update services/env/entrypoint; enqueues a restart job only when explicitly requested (owner/admin)
requestBody:
content:
application/json:
@@ -860,7 +860,7 @@ paths:
items: { $ref: '#/components/schemas/EnvVar' }
description: Full replacement set. Omitting it clears all user env vars (unless the request is restart-only).
entrypoint: { type: string, nullable: true, description: Omitting/blank clears the entrypoint (unless the request is restart-only) }
- restart: { type: boolean, description: 'true forces a restart even with no config changes; alone, it performs a restart-only request' }
+ restart: { type: boolean, description: 'A restart job is enqueued only when true — config changes alone never restart the container (they apply on the next restart); alone, it performs a restart-only request' }
responses:
'200':
description: Updated, optional restart job
@@ -875,6 +875,7 @@ paths:
containerId: { type: integer }
jobId: { type: integer, nullable: true, description: 'Restart job id, when a restart was enqueued' }
dnsWarnings: { type: array, items: { type: string } }
+ pendingRestart: { type: boolean, description: 'true when env/entrypoint changes were saved but no restart was requested — they apply on the next restart' }
message: { type: string }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { description: 'forbidden — only the owner/admin may edit (collaborators have a read-only view); non-admins may not reassign ownership', content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
diff --git a/create-a-container/routers/api/v1/containers.js b/create-a-container/routers/api/v1/containers.js
index 8d1d676b..7e93de6c 100644
--- a/create-a-container/routers/api/v1/containers.js
+++ b/create-a-container/routers/api/v1/containers.js
@@ -642,7 +642,10 @@ router.put(
const ownerChanged = newOwnerUsername !== null && newOwnerUsername !== container.username;
const envChanged = !isRestartOnly && container.environmentVars !== envVarsJson;
const entrypointChanged = !isRestartOnly && container.entrypoint !== newEntrypoint;
- const needsRestart = forceRestart || envChanged || entrypointChanged;
+ // Never restart implicitly (issue #449): a restart job is enqueued only
+ // when the caller explicitly asks for one. Saved env/entrypoint changes
+ // are applied by reconfigure-container.js on the next restart.
+ const needsRestart = forceRestart;
let restartJob = null;
const dnsWarnings = [];
@@ -770,11 +773,17 @@ router.put(
}
}
+ const pendingRestart = !restartJob && (envChanged || entrypointChanged);
return ok(res, {
containerId: container.id,
jobId: restartJob ? restartJob.id : null,
dnsWarnings,
- message: restartJob ? 'Container is restarting' : 'Container updated',
+ pendingRestart,
+ message: restartJob
+ ? 'Container is restarting'
+ : pendingRestart
+ ? 'Container updated — changes take effect on the next restart'
+ : 'Container updated',
});
}),
);
diff --git a/create-a-container/routers/api/v1/index.js b/create-a-container/routers/api/v1/index.js
index eb83aa46..c6144cc7 100644
--- a/create-a-container/routers/api/v1/index.js
+++ b/create-a-container/routers/api/v1/index.js
@@ -43,7 +43,7 @@ const { isOidcEnabled } = require('../../../utils/oidc');
const { Setting } = require('../../../models');
router.get(
'/health',
- asyncHandler(async (_req, res) => {
+ asyncHandler(async (req, res) => {
// The banner is cosmetic — never let a DB hiccup fail the health check.
let banner = null;
try {
@@ -56,6 +56,8 @@ router.get(
isDev: process.env.NODE_ENV !== 'production',
oidcEnabled: isOidcEnabled(),
banner,
+ // Cached at startup in app.locals (see app.js); the SPA footer shows it.
+ version: req.app.locals.versionInfo ?? null,
});
}),
);