Skip to content

Commit e1e9862

Browse files
committed
Squashed commit of the following:
commit 9e1418c Author: wzrdx <128477299+wzrdx@users.noreply.github.com> Date: Thu Oct 23 13:52:48 2025 +0300 feat: Format ports from array in deeploy utils commit e9da986 Author: wzrdx <128477299+wzrdx@users.noreply.github.com> Date: Thu Oct 23 12:55:33 2025 +0300 chore: Update schema to use array, refactor PortMappingSection to use fields commit 71c02de Author: wzrdx <128477299+wzrdx@users.noreply.github.com> Date: Thu Oct 23 12:36:08 2025 +0300 refactor: Move ports schema to genericAppDeploymentSchema and separate PortMappingSection from AppParametersSection
1 parent f8fe411 commit e1e9862

File tree

10 files changed

+252
-139
lines changed

10 files changed

+252
-139
lines changed

src/components/create-job/secondary-plugins/GenericSecondaryPluginSections.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ConfigSectionTitle from '@components/job/config/ConfigSectionTitle';
22
import DynamicEnvSection from '@shared/jobs/DynamicEnvSection';
33
import FileVolumesSection from '@shared/jobs/FileVolumesSection';
44
import KeyValueEntriesSection from '@shared/jobs/KeyValueEntriesSection';
5+
import PortMappingSection from '@shared/PortMappingSection';
56
import AppParametersSection from '../sections/AppParametersSection';
67
import PluginEnvVariablesSection from '../sections/PluginEnvVariablesSection';
78
import PoliciesSection from '../sections/PoliciesSection';
@@ -12,6 +13,9 @@ export default function GenericSecondaryPluginSections({ name }: { name: string
1213
<ConfigSectionTitle title="App Parameters" />
1314
<AppParametersSection baseName={name} />
1415

16+
<ConfigSectionTitle title="Port Mapping" />
17+
<PortMappingSection baseName={name} />
18+
1519
<PluginEnvVariablesSection baseName={name} />
1620

1721
<ConfigSectionTitle title="Dynamic ENV Variables" />

src/components/create-job/secondary-plugins/NativeInputsSection.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ConfigSectionTitle from '@components/job/config/ConfigSectionTitle';
22
import { PLUGIN_SIGNATURE_TYPES } from '@data/pluginSignatureTypes';
33
import CustomParametersSection from '@shared/jobs/native/CustomParametersSection';
44
import NativeAppIdentitySection from '@shared/jobs/native/NativeAppIdentitySection';
5+
import PortMappingSection from '@shared/PortMappingSection';
56
import { useFormContext } from 'react-hook-form';
67
import AppParametersSection from '../sections/AppParametersSection';
78

@@ -19,6 +20,9 @@ export default function NativeInputsSection({ index }: { index: number }) {
1920
<ConfigSectionTitle title="App Parameters" />
2021
<AppParametersSection baseName={name} />
2122

23+
<ConfigSectionTitle title="Port Mapping" />
24+
<PortMappingSection baseName={name} />
25+
2226
<ConfigSectionTitle title="Custom Parameters" />
2327
<CustomParametersSection baseName={name} />
2428
</div>

src/components/create-job/secondary-plugins/SecondaryPluginsCard.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ export default function SecondaryPluginsCard() {
9494
crVisibility: CR_VISIBILITY_OPTIONS[0],
9595
crUsername: '',
9696
crPassword: '',
97-
ports: {},
9897
},
9998
...TUNNELING_DEFAULTS,
10099
...GENERIC_PLUGIN_DEFAULTS,
@@ -115,7 +114,6 @@ export default function SecondaryPluginsCard() {
115114
{ command: 'npm run build' },
116115
{ command: 'npm run start' },
117116
],
118-
ports: {},
119117
},
120118
...TUNNELING_DEFAULTS,
121119
...GENERIC_PLUGIN_DEFAULTS,

src/components/create-job/sections/AppParametersSection.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { BOOLEAN_TYPES } from '@data/booleanTypes';
22
import InputWithLabel from '@shared/InputWithLabel';
33
import NumberInputWithLabel from '@shared/NumberInputWithLabel';
4-
import PortMappingSection from '@shared/PortMappingSection';
54
import SelectWithLabel from '@shared/SelectWithLabel';
65
import { useFormContext } from 'react-hook-form';
76

87
export default function AppParametersSection({ baseName = 'deployment' }: { baseName?: string }) {
98
const { watch, trigger } = useFormContext();
109

1110
const enableTunneling: (typeof BOOLEAN_TYPES)[number] = watch(`${baseName}.enableTunneling`);
12-
const deploymentType = watch(`${baseName}.deploymentType.type`);
1311

1412
return (
1513
<div className="col gap-4">
@@ -35,11 +33,6 @@ export default function AppParametersSection({ baseName = 'deployment' }: { base
3533
<InputWithLabel name={`${baseName}.tunnelingToken`} label="Tunnel Token" placeholder="Starts with 'ey'" />
3634
</div>
3735
)}
38-
39-
{/* TODO: Check if deploying generic plugin/job */}
40-
{(deploymentType === 'container' || deploymentType === 'worker') && (
41-
<PortMappingSection name={`${baseName}.deploymentType.ports`} label="Port Mapping" />
42-
)}
4336
</div>
4437
);
4538
}

src/components/create-job/steps/deployment/GenericDeployment.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import EnvVariablesCard from '@shared/jobs/EnvVariablesCard';
88
import FileVolumesSection from '@shared/jobs/FileVolumesSection';
99
import KeyValueEntriesSection from '@shared/jobs/KeyValueEntriesSection';
1010
import TargetNodesCard from '@shared/jobs/target-nodes/TargetNodesCard';
11+
import PortMappingSection from '@shared/PortMappingSection';
1112

1213
function GenericDeployment({ isEditingJob }: { isEditingJob?: boolean }) {
1314
return (
@@ -26,6 +27,10 @@ function GenericDeployment({ isEditingJob }: { isEditingJob?: boolean }) {
2627
<AppParametersSection />
2728
</SlateCard>
2829

30+
<SlateCard title="Port Mapping">
31+
<PortMappingSection />
32+
</SlateCard>
33+
2934
<EnvVariablesCard />
3035

3136
<SlateCard title="Dynamic ENV Variables">

src/lib/deeploy-utils.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ import {
2323
ServiceJobDeployment,
2424
ServiceJobSpecifications,
2525
} from '@typedefs/deeploys';
26-
import { GenericSecondaryPlugin, NativeSecondaryPlugin, SecondaryPluginType } from '@typedefs/steps/deploymentStepTypes';
26+
import {
27+
GenericSecondaryPlugin,
28+
NativeSecondaryPlugin,
29+
PortMappingEntry,
30+
SecondaryPluginType,
31+
} from '@typedefs/steps/deploymentStepTypes';
2732
import { addDays, addHours, differenceInDays, differenceInHours } from 'date-fns';
2833
import _ from 'lodash';
2934
import { FieldValues, UseFieldArrayAppend, UseFieldArrayRemove } from 'react-hook-form';
@@ -170,12 +175,23 @@ export const formatFileVolumes = (fileVolumes: { name: string; mountingPoint: st
170175
return formatted;
171176
};
172177

173-
export const formatContainerResources = (containerOrWorkerType: ContainerOrWorkerType, ports?: Record<string, string>) => {
174-
return {
178+
export const formatContainerResources = (containerOrWorkerType: ContainerOrWorkerType, portsArray: Array<PortMappingEntry>) => {
179+
const baseResources: { cpu: number; memory: string; ports?: Record<string, number> } = {
175180
cpu: containerOrWorkerType.cores,
176181
memory: `${containerOrWorkerType.ram}g`,
177-
...(ports && Object.keys(ports).length > 0 && { ports }),
178182
};
183+
184+
if (portsArray.length > 0) {
185+
const ports = {};
186+
187+
portsArray.forEach((port) => {
188+
ports[port.hostPort.toString()] = port.containerPort;
189+
});
190+
191+
baseResources.ports = ports;
192+
}
193+
194+
return baseResources;
179195
};
180196

181197
export const formatNodes = (targetNodes: { address: string }[]): string[] => {
@@ -246,7 +262,7 @@ export const formatGenericPluginConfigAndSignature = (
246262
resources: {
247263
cpu: number;
248264
memory: string;
249-
ports?: Record<string, string>;
265+
ports?: Record<string, number>;
250266
},
251267
plugin: GenericSecondaryPlugin,
252268
) => {
@@ -311,7 +327,7 @@ export const formatGenericJobPayload = (
311327
const spareNodes = formatNodes(deployment.spareNodes);
312328

313329
const { pluginConfig, pluginSignature } = formatGenericPluginConfigAndSignature(
314-
formatContainerResources(containerType, deployment.deploymentType.ports),
330+
formatContainerResources(containerType, deployment.ports),
315331
deployment,
316332
);
317333

@@ -351,7 +367,7 @@ export const formatNativeJobPayload = (
351367
}
352368
});
353369

354-
const nodeResources = formatContainerResources(workerType, undefined);
370+
const nodeResources = formatContainerResources(workerType, []);
355371
const targetNodes = formatNodes(deployment.targetNodes);
356372
const targetNodesCount = formatTargetNodesCount(targetNodes, specifications.targetNodesCount);
357373

@@ -438,7 +454,7 @@ export const formatServiceJobPayload = (
438454
deployment: ServiceJobDeployment,
439455
) => {
440456
const jobTags = formatJobTags(specifications);
441-
const containerResources = formatContainerResources(containerType, undefined);
457+
const containerResources = formatContainerResources(containerType, []);
442458
const targetNodes = formatNodes(deployment.targetNodes);
443459
const spareNodes = formatNodes(deployment.spareNodes);
444460

src/schemas/steps/deployment.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,6 @@ const validations = {
4040
.max(256, 'Value cannot exceed 256 characters')
4141
.regex(/^https?:\/\/.+/, 'Must be a valid URI'),
4242

43-
// optionalUri: z
44-
// .string()
45-
// .refine((val) => val === '' || val.length >= 2, 'Value must be at least 2 characters')
46-
// .refine((val) => val === '' || val.length <= 256, 'Value cannot exceed 256 characters')
47-
// .refine((val) => val === '' || /^https?:\/\/.+/.test(val), 'Must be a valid URI'),
48-
4943
optionalUri: z
5044
.union([
5145
z.literal(''),
@@ -66,6 +60,32 @@ const validations = {
6660
.max(65535, 'Value cannot exceed 65535'),
6761
]),
6862

63+
ports: z
64+
.array(
65+
z.object({
66+
hostPort: z
67+
.number()
68+
.int('Value must be a whole number')
69+
.min(1, 'Value must be at least 1')
70+
.max(65535, 'Value cannot exceed 65535'),
71+
containerPort: z
72+
.number()
73+
.int('Value must be a whole number')
74+
.min(1, 'Value must be at least 1')
75+
.max(65535, 'Value cannot exceed 65535'),
76+
}),
77+
)
78+
.refine(
79+
(entries) => {
80+
const hostPorts = entries.map((entry) => entry.hostPort);
81+
return hostPorts.length === new Set(hostPorts).size;
82+
},
83+
{
84+
message: 'Duplicate host ports are not allowed',
85+
},
86+
)
87+
.default([]),
88+
6989
envVars: getKeyValueEntriesArraySchema(),
7090
dynamicEnvVars: z
7191
.array(dynamicEnvEntrySchema)
@@ -197,6 +217,7 @@ export const applyCustomPluginSignatureRefinements = (schema) => {
197217
};
198218

199219
const baseDeploymentSchema = z.object({
220+
// Target Nodes
200221
autoAssign: z.boolean(),
201222
targetNodes: z.array(nodeSchema).refine(
202223
(nodes) => {
@@ -221,9 +242,12 @@ const baseDeploymentSchema = z.object({
221242
},
222243
),
223244
allowReplicationInTheWild: z.boolean(),
245+
246+
// Tunneling
224247
enableTunneling: z.enum(BOOLEAN_TYPES, { required_error: 'Value is required' }),
225-
tunnelingLabel: getOptionalStringSchema(64),
248+
port: validations.port,
226249
tunnelingToken: getOptionalStringSchema(512),
250+
tunnelingLabel: getOptionalStringSchema(64),
227251
});
228252

229253
const containerDeploymentTypeSchema = z.object({
@@ -233,7 +257,6 @@ const containerDeploymentTypeSchema = z.object({
233257
crVisibility: z.enum(CR_VISIBILITY_OPTIONS, { required_error: 'Value is required' }),
234258
crUsername: z.union([getStringSchema(3, 128), z.literal('')]).optional(),
235259
crPassword: z.union([getStringSchema(3, 256), z.literal('')]).optional(),
236-
ports: z.record(z.string(), z.string()).optional(),
237260
});
238261

239262
const workerDeploymentTypeSchema = z.object({
@@ -266,15 +289,14 @@ const workerDeploymentTypeSchema = z.object({
266289
message: 'Duplicate commands are not allowed',
267290
},
268291
),
269-
ports: z.record(z.string(), z.string()).optional(),
270292
});
271293

272294
export const deploymentTypeSchema = z.discriminatedUnion('type', [containerDeploymentTypeSchema, workerDeploymentTypeSchema]);
273295

274296
const genericAppDeploymentSchemaWihtoutRefinements = baseDeploymentSchema.extend({
275297
jobAlias: validations.jobAlias,
276298
deploymentType: deploymentTypeSchema,
277-
port: validations.port,
299+
ports: validations.ports,
278300
envVars: validations.envVars,
279301
dynamicEnvVars: validations.dynamicEnvVars,
280302
volumes: validations.volumes,
@@ -296,6 +318,9 @@ const baseGenericSecondaryPluginSchema = z.object({
296318
enableTunneling: z.enum(BOOLEAN_TYPES, { required_error: 'Value is required' }),
297319
tunnelingToken: getOptionalStringSchema(512),
298320

321+
// Ports
322+
ports: validations.ports,
323+
299324
// Deployment type
300325
deploymentType: deploymentTypeSchema,
301326

@@ -324,6 +349,9 @@ const baseNativeSecondaryPluginSchema = z.object({
324349
enableTunneling: z.enum(BOOLEAN_TYPES, { required_error: 'Value is required' }),
325350
tunnelingToken: getOptionalStringSchema(512),
326351

352+
// Ports
353+
ports: validations.ports,
354+
327355
// Custom Parameters
328356
customParams: validations.customParams,
329357
});
@@ -341,7 +369,6 @@ const nativeAppDeploymentSchemaWihtoutRefinements = baseDeploymentSchema.extend(
341369
jobAlias: validations.jobAlias,
342370
pluginSignature: validations.pluginSignature,
343371
customPluginSignature: getOptionalStringSchema(128),
344-
port: validations.port,
345372
customParams: validations.customParams,
346373
pipelineParams: validations.pipelineParams,
347374
pipelineInputType: z.enum(PIPELINE_INPUT_TYPES, { required_error: 'Value is required' }),
@@ -356,7 +383,6 @@ export const nativeAppDeploymentSchema = applyCustomPluginSignatureRefinements(
356383

357384
const serviceAppDeploymentSchemaWihtoutRefinements = baseDeploymentSchema.extend({
358385
jobAlias: validations.jobAlias,
359-
port: validations.port,
360386
enableTunneling: z.enum(BOOLEAN_TYPES, { required_error: 'Value is required' }),
361387
tunnelingToken: getOptionalStringSchema(512),
362388
inputs: validations.envVars,

0 commit comments

Comments
 (0)