11import AppParametersSection from '@components/create-job/sections/AppParametersSection' ;
2+ import { BOOLEAN_TYPES } from '@data/booleanTypes' ;
23import services , { Service } from '@data/services' ;
34import { Button } from '@heroui/button' ;
5+ import { Checkbox } from '@heroui/checkbox' ;
46import { createTunnel } from '@lib/api/tunnels' ;
57import { DeploymentContextType } from '@lib/contexts/deployment/context' ;
68import { useDeploymentContext } from '@lib/contexts/deployment/hook' ;
@@ -12,6 +14,7 @@ import InputWithLabel from '@shared/InputWithLabel';
1214import DeeployInfoTag from '@shared/jobs/DeeployInfoTag' ;
1315import ServiceInputsSection from '@shared/jobs/ServiceInputsSection' ;
1416import TargetNodesCard from '@shared/jobs/target-nodes/TargetNodesCard' ;
17+ import PortMappingSection from '@shared/PortMappingSection' ;
1518import { useEffect , useState } from 'react' ;
1619import { useFormContext } from 'react-hook-form' ;
1720import { 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 } /> }
0 commit comments