Skip to content

Commit 4362056

Browse files
authored
Services UX improvements, public/private service with default port mapping suggested (#35)
* fix: Services small UX fixes and improvements * feat: Services changes for isPublicService and ports mapping * feat: Suggest default port mapping when using a private service * chore: Format job payload, handle editing running services * fix: Service tunneling payload
1 parent f06d3f6 commit 4362056

File tree

12 files changed

+141
-67
lines changed

12 files changed

+141
-67
lines changed

scripts/add-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ const printSummary = (service: Service) => {
694694
console.log(`Description: ${colorValue(service.description)}`);
695695
console.log(`Image: ${colorValue(service.image)}`);
696696
console.log(`Port: ${colorValue(service.port)}`);
697-
console.log(`Logo: ${colorValue(service.logo)}`);
697+
console.log(`Logo filename (in src/assets/services): ${colorValue(service.logo)}`);
698698
console.log(`Color: ${colorValue(service.color)}`);
699699
console.log(`Plugin signature: ${colorValue(service.pluginSignature)}`);
700700
console.log(`Tunnel engine: ${colorValue(service.tunnelEngine)}`);

src/components/auth/LoginCard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ export default function LoginCard({ hasOracles }: { hasOracles: boolean }) {
145145
try {
146146
const [appsResult, secretsResult] = await Promise.allSettled([getApps(request), getSecrets(request)]);
147147

148+
console.log('Login', { appsResult, secretsResult });
149+
148150
if (appsResult.status === 'rejected') {
149151
throw appsResult.reason;
150152
}

src/components/create-job/JobFormWrapper.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ function JobFormWrapper({ projectName, draftJobsCount }) {
120120
},
121121
deployment: {
122122
...getBaseSchemaDefaults().deployment,
123+
ports: [],
124+
isPublicService: true,
123125
envVars: [],
124126
inputs: [],
125127
},

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

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import AppParametersSection from '@components/create-job/sections/AppParametersSection';
2+
import { BOOLEAN_TYPES } from '@data/booleanTypes';
23
import services, { Service } from '@data/services';
34
import { Button } from '@heroui/button';
5+
import { Checkbox } from '@heroui/checkbox';
46
import { createTunnel } from '@lib/api/tunnels';
57
import { DeploymentContextType } from '@lib/contexts/deployment/context';
68
import { useDeploymentContext } from '@lib/contexts/deployment/hook';
@@ -12,6 +14,7 @@ import InputWithLabel from '@shared/InputWithLabel';
1214
import DeeployInfoTag from '@shared/jobs/DeeployInfoTag';
1315
import ServiceInputsSection from '@shared/jobs/ServiceInputsSection';
1416
import TargetNodesCard from '@shared/jobs/target-nodes/TargetNodesCard';
17+
import PortMappingSection from '@shared/PortMappingSection';
1518
import { useEffect, useState } from 'react';
1619
import { useFormContext } from 'react-hook-form';
1720
import { toast } from 'react-hot-toast';
@@ -27,10 +30,13 @@ function ServiceDeployment({ isEditingRunningJob }: { isEditingRunningJob?: bool
2730

2831
const serviceId: number = watch('serviceId');
2932
const alias: string = watch('deployment.jobAlias');
33+
const isPublicService: boolean = watch('deployment.isPublicService');
34+
const ports = watch('deployment.ports');
3035

3136
const service: Service = services.find((service) => service.id === serviceId)!;
3237

3338
const [isCreatingTunnel, setCreatingTunnel] = useState<boolean>(false);
39+
const [generatedPortMapping, setGeneratedPortMapping] = useState<boolean>(false);
3440

3541
useEffect(() => {
3642
if (!alias || alias === '') {
@@ -42,6 +48,31 @@ function ServiceDeployment({ isEditingRunningJob }: { isEditingRunningJob?: bool
4248
setValue('deployment.port', service.port);
4349
}, [service]);
4450

51+
useEffect(() => {
52+
if (!isPublicService && !generatedPortMapping) {
53+
const hasPorts = Array.isArray(ports) && ports.length > 0;
54+
if (!hasPorts) {
55+
const hostPort = 32000 + Math.floor(Math.random() * 700) + 1;
56+
setValue('deployment.ports', [{ hostPort, containerPort: service.port }], {
57+
shouldDirty: true,
58+
shouldValidate: true,
59+
});
60+
setGeneratedPortMapping(true);
61+
}
62+
}
63+
}, [isPublicService, ports, service.port, setValue]);
64+
65+
useEffect(() => {
66+
setValue('deployment.enableTunneling', isPublicService ? BOOLEAN_TYPES[0] : BOOLEAN_TYPES[1], {
67+
shouldDirty: true,
68+
shouldValidate: true,
69+
});
70+
71+
if (isPublicService) {
72+
setValue('deployment.ports', [], { shouldDirty: true });
73+
}
74+
}, [isPublicService, setValue]);
75+
4576
const onGenerateTunnel = async () => {
4677
if (!tunnelingSecrets) {
4778
throw new Error('No tunneling secrets found.');
@@ -90,43 +121,66 @@ function ServiceDeployment({ isEditingRunningJob }: { isEditingRunningJob?: bool
90121
<TargetNodesCard isEditingRunningJob={isEditingRunningJob} />
91122

92123
<SlateCard
93-
title="Tunneling"
124+
title="Service Parameters"
94125
label={
95-
<Button
96-
className="h-[34px]"
97-
color="primary"
98-
size="sm"
99-
onPress={onGenerateTunnel}
100-
isLoading={isCreatingTunnel}
101-
isDisabled={!tunnelingSecrets}
102-
>
103-
<div className="row gap-1.5">
104-
<RiCodeSSlashLine className="text-base" />
105-
<div className="compact">Generate Tunnel</div>
106-
</div>
107-
</Button>
126+
isPublicService && (
127+
<Button
128+
className="h-[34px]"
129+
color="primary"
130+
size="sm"
131+
onPress={onGenerateTunnel}
132+
isLoading={isCreatingTunnel}
133+
isDisabled={!tunnelingSecrets || isEditingRunningJob}
134+
>
135+
<div className="row gap-1.5">
136+
<RiCodeSSlashLine className="text-base" />
137+
<div className="compact">Generate Tunnel</div>
138+
</div>
139+
</Button>
140+
)
108141
}
109142
>
110-
{!tunnelingSecrets && (
111-
<DeeployInfoTag
112-
text={
113-
<>
114-
Please add your{' '}
115-
<Link to={routePath.tunnels} className="text-primary font-medium hover:opacity-70">
116-
Cloudflare secrets
117-
</Link>{' '}
118-
to enable tunnel generation.
119-
</>
120-
}
121-
/>
122-
)}
123-
124-
<AppParametersSection
125-
enablePort={false}
126-
isCreatingTunnel={isCreatingTunnel}
127-
enableTunnelingLabel
128-
forceTunnelingEnabled
129-
/>
143+
<div className="col gap-4">
144+
{!isEditingRunningJob && (
145+
<Checkbox
146+
isSelected={isPublicService}
147+
onValueChange={(value) => setValue('deployment.isPublicService', value, { shouldDirty: true })}
148+
>
149+
<div className="compact">Public Service</div>
150+
</Checkbox>
151+
)}
152+
153+
{isPublicService && !tunnelingSecrets && (
154+
<DeeployInfoTag
155+
text={
156+
<>
157+
Please add your{' '}
158+
<Link to={routePath.tunnels} className="text-primary font-medium hover:opacity-70">
159+
Cloudflare secrets
160+
</Link>{' '}
161+
to enable tunnel generation.
162+
</>
163+
}
164+
/>
165+
)}
166+
167+
{isPublicService ? (
168+
<AppParametersSection
169+
enablePort={false}
170+
isCreatingTunnel={isCreatingTunnel}
171+
enableTunnelingLabel={service.tunnelEngine === 'ngrok'}
172+
forceTunnelingEnabled
173+
/>
174+
) : (
175+
<div className="col gap-2">
176+
<DeeployInfoTag
177+
text={<>Add a port mapping of type HOST_PORT to SERVICE_PORT ({service.port}).</>}
178+
/>
179+
180+
<PortMappingSection />
181+
</div>
182+
)}
183+
</div>
130184
</SlateCard>
131185

132186
{service?.inputs?.length > 0 && <ServiceInputsSection inputs={service.inputs} />}

src/components/draft/DraftEditFormWrapper.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ export default function DraftEditFormWrapper({
167167
deployment: {
168168
...baseDefaults.deployment,
169169
inputs: cloneDeep(deployment.inputs),
170+
ports: cloneDeep(deployment.ports ?? []),
171+
isPublicService: deployment.isPublicService ?? true,
170172
serviceReplica: deployment.serviceReplica ?? '',
171173
},
172174
} as z.infer<typeof jobSchema>;

src/components/edit-job/JobEditFormWrapper.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ export default function JobEditFormWrapper({
215215
...getBaseSchemaDefaults().deployment,
216216
tunnelingLabel: jobConfig.NGROK_EDGE_LABEL || '',
217217
inputs: getEnvVars(jobConfig),
218+
ports: getPortMappings(jobConfig),
219+
isPublicService: !!(jobConfig.CLOUDFLARE_TOKEN || jobConfig.NGROK_AUTH_TOKEN),
218220
},
219221
});
220222

src/components/job/config/JobPluginsSection.tsx

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,26 @@ export default function JobPluginsSection({ job }: { job: RunningJobWithResource
4848
<div className="flex items-start justify-between">
4949
<div className="text-lg font-semibold">Plugins</div>
5050

51-
<div className="w-[240px]">
52-
<StyledSelect
53-
selectedKeys={[pluginConfig.signature]}
54-
onSelectionChange={(keys) => {
55-
const selectedKey = Array.from(keys)[0] as string;
56-
setPluginConfig(pluginConfigs.find((config) => config.signature === selectedKey)!);
57-
}}
58-
placeholder="Select a plugin"
59-
>
60-
{pluginConfigs.map((item) => (
61-
<SelectItem key={item.signature} textValue={item.signature}>
62-
<div className="row gap-2 py-1">
63-
<div className="font-medium">{item.signature}</div>
64-
</div>
65-
</SelectItem>
66-
))}
67-
</StyledSelect>
68-
</div>
51+
{job.resources.jobType !== JobType.Service && (
52+
<div className="w-[240px]">
53+
<StyledSelect
54+
selectedKeys={[pluginConfig.signature]}
55+
onSelectionChange={(keys) => {
56+
const selectedKey = Array.from(keys)[0] as string;
57+
setPluginConfig(pluginConfigs.find((config) => config.signature === selectedKey)!);
58+
}}
59+
placeholder="Select a plugin"
60+
>
61+
{pluginConfigs.map((item) => (
62+
<SelectItem key={item.signature} textValue={item.signature}>
63+
<div className="row gap-2 py-1">
64+
<div className="font-medium">{item.signature}</div>
65+
</div>
66+
</SelectItem>
67+
))}
68+
</StyledSelect>
69+
</div>
70+
)}
6971
</div>
7072

7173
<div className="col gap-4">

src/lib/deeploy-utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,10 @@ export const formatServiceJobPayload = (
536536
deployment: ServiceJobDeployment,
537537
) => {
538538
const jobTags = formatJobTags(specifications);
539-
const containerResources = formatContainerResources(serviceContainerType, []);
539+
const containerResources = formatContainerResources(
540+
serviceContainerType,
541+
deployment.isPublicService ? [] : deployment.ports,
542+
);
540543
const targetNodes = formatNodes(deployment.targetNodes);
541544
const spareNodes = formatNodes(deployment.spareNodes);
542545

@@ -558,7 +561,7 @@ export const formatServiceJobPayload = (
558561

559562
// Tunneling
560563
PORT: formatPort(service.port),
561-
TUNNEL_ENGINE_ENABLED: true, // Tunneling is always enabled for services
564+
TUNNEL_ENGINE_ENABLED: deployment.isPublicService,
562565
TUNNEL_ENGINE: service.tunnelEngine,
563566

564567
// Variables

src/schemas/steps/deployment.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,9 @@ export const nativeAppDeploymentSchema = applyCustomPluginSignatureRefinements(
398398

399399
const serviceAppDeploymentSchemaWihtoutRefinements = baseDeploymentSchema.extend({
400400
inputs: validations.envVars,
401-
serviceReplica: nodeSchema.shape.address.optional(),
401+
ports: validations.ports,
402+
isPublicService: z.boolean().default(true),
403+
// serviceReplica: nodeSchema.shape.address.optional(),
402404
});
403405

404406
export const serviceAppDeploymentSchema = applyTunnelingRefinements(serviceAppDeploymentSchemaWihtoutRefinements);

src/shared/jobs/DeeploySuccessAlert.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,20 @@ export default function DeeploySuccessAlert({
3737
<div>{item.text}</div>
3838
<div></div>
3939
<div>{item.serverAlias}</div>
40-
<div></div>
4140

42-
{item.tunnelURL && (
43-
<Link
44-
to={`https://${item.tunnelURL}`}
45-
target="_blank"
46-
className="row gap-1 text-[13px] text-green-500 hover:opacity-70"
47-
>
48-
<span className="font-roboto-mono">{item.tunnelURL}</span>
49-
<RiExternalLinkLine className="mb-px text-[14px]" />
50-
</Link>
41+
{!!item.tunnelURL && (
42+
<>
43+
<div></div>
44+
45+
<Link
46+
to={`https://${item.tunnelURL}`}
47+
target="_blank"
48+
className="row gap-1 text-[13px] text-green-500 hover:opacity-70"
49+
>
50+
<span className="font-roboto-mono">{item.tunnelURL}</span>
51+
<RiExternalLinkLine className="mb-px text-[14px]" />
52+
</Link>
53+
</>
5154
)}
5255
</div>
5356
))}

0 commit comments

Comments
 (0)