diff --git a/create-a-container/client/src/lib/types.ts b/create-a-container/client/src/lib/types.ts index 85701ab3..9258582d 100644 --- a/create-a-container/client/src/lib/types.ts +++ b/create-a-container/client/src/lib/types.ts @@ -54,6 +54,11 @@ export interface ServiceTransport { id: number; protocol: 'tcp' | 'udp'; externalPort: number; + tls: boolean; + backendTls: boolean; + externalHostname: string | null; + externalDomainId: number | null; + domain?: string; } export interface ServiceDns { id: number; diff --git a/create-a-container/client/src/pages/containers/ContainerFormPage.tsx b/create-a-container/client/src/pages/containers/ContainerFormPage.tsx index 87ec7ebe..85b7f81b 100644 --- a/create-a-container/client/src/pages/containers/ContainerFormPage.tsx +++ b/create-a-container/client/src/pages/containers/ContainerFormPage.tsx @@ -41,6 +41,7 @@ const SERVICE_TYPES = [ { value: 'http', label: 'HTTP' }, { value: 'https', label: 'HTTPS (backend TLS)' }, { value: 'tcp', label: 'TCP' }, + { value: 'tls', label: 'TLS (backend TLS)' }, { value: 'udp', label: 'UDP' }, { value: 'srv', label: 'DNS (SRV record)' }, ]; @@ -48,12 +49,14 @@ const SERVICE_TYPES = [ const serviceSchema = z .object({ id: z.number().optional(), - type: z.enum(['http', 'https', 'tcp', 'udp', 'srv']), + type: z.enum(['http', 'https', 'tcp', 'tls', 'udp', 'srv']), internalPort: z.string(), + externalPort: z.number().optional(), externalHostname: z.string().optional(), externalDomainId: z.string().optional(), dnsName: z.string().optional(), authRequired: z.boolean().optional(), + tls: z.boolean().optional(), deleted: z.boolean().optional(), }) .refine( @@ -66,6 +69,18 @@ const serviceSchema = z message: 'An external domain is required for HTTP services', path: ['externalDomainId'], }, + ) + .refine( + (s) => + s.deleted || + s.id !== undefined || // existing services are immutable here + (s.type !== 'tcp' && s.type !== 'tls') || + !s.tls || + !!s.externalDomainId, + { + message: 'An external domain is required when TLS termination is enabled', + path: ['externalDomainId'], + }, ); const envVarSchema = z.object({ key: z.string(), value: z.string() }); @@ -161,12 +176,21 @@ export function ContainerFormPage() { ? s.httpService?.backendProtocol === 'https' ? 'https' : 'http' - : (s.transportService?.protocol ?? 'tcp'), + : s.transportService?.backendTls + ? 'tls' + : (s.transportService?.protocol ?? 'tcp'), internalPort: String(s.internalPort), - externalHostname: s.httpService?.externalHostname || '', - externalDomainId: s.httpService ? String(s.httpService.externalDomainId) : '', + externalPort: s.transportService?.externalPort, + externalHostname: + s.httpService?.externalHostname || s.transportService?.externalHostname || '', + externalDomainId: s.httpService + ? String(s.httpService.externalDomainId) + : s.transportService?.externalDomainId + ? String(s.transportService.externalDomainId) + : '', dnsName: s.dnsService?.dnsName || '', authRequired: !!s.httpService?.authRequired, + tls: !!s.transportService?.tls, deleted: false, })), environmentVars: Object.entries(container.environmentVars || {}).map(([key, value]) => ({ @@ -215,6 +239,7 @@ export function ContainerFormPage() { externalDomainId: '', dnsName: '', authRequired: false, + tls: false, deleted: false, }); added += 1; @@ -252,14 +277,16 @@ export function ContainerFormPage() { return; } if (s.deleted) return; + const externalHostname = s.externalHostname?.trim(); servicesObj[`s${idx}`] = { id: s.id, type: s.type, internalPort: s.internalPort ? parseInt(s.internalPort, 10) : undefined, - externalHostname: s.externalHostname, + externalHostname: externalHostname ? externalHostname : undefined, externalDomainId: s.externalDomainId ? parseInt(s.externalDomainId, 10) : undefined, dnsName: s.dnsName, authRequired: s.authRequired, + tls: s.tls, }; }); const payload = { @@ -502,6 +529,7 @@ export function ContainerFormPage() { externalDomainId: defaultExternalDomainId, dnsName: '', authRequired: false, + tls: false, deleted: false, }) } @@ -524,7 +552,7 @@ export function ContainerFormPage() {
{isExisting && (

- Existing service — only Require authentication can be changed. Delete and re-add to modify other fields. + Existing service — the type cannot be changed. Delete and re-add to change it.

)}
@@ -552,7 +580,6 @@ export function ContainerFormPage() { label="Internal port" type="number" inputMode="numeric" - readOnly={isExisting} {...register(`services.${idx}.internalPort`)} />