@@ -2,7 +2,7 @@ import TextField from "@mui/material/TextField";
2
2
import * as TypesGen from "api/typesGenerated" ;
3
3
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete" ;
4
4
import { FormikContextType , useFormik } from "formik" ;
5
- import { FC , useEffect , useState } from "react" ;
5
+ import { FC , useEffect , useReducer , useState } from "react" ;
6
6
import {
7
7
getFormHelpers ,
8
8
nameValidator ,
@@ -29,11 +29,18 @@ import {
29
29
import { ExternalAuth } from "./ExternalAuth" ;
30
30
import { ErrorAlert } from "components/Alert/ErrorAlert" ;
31
31
import { Stack } from "components/Stack/Stack" ;
32
- import { type ExternalAuthPollingState } from "./CreateWorkspacePage" ;
32
+ import {
33
+ CreateWorkspaceMode ,
34
+ type ExternalAuthPollingState ,
35
+ } from "./CreateWorkspacePage" ;
33
36
import { useSearchParams } from "react-router-dom" ;
34
37
import { CreateWSPermissions } from "./permissions" ;
38
+ import { useTheme } from "@emotion/react" ;
39
+ import { Margins } from "components/Margins/Margins" ;
40
+ import { Alert } from "components/Alert/Alert" ;
35
41
36
42
export interface CreateWorkspacePageViewProps {
43
+ mode : CreateWorkspaceMode ;
37
44
error : unknown ;
38
45
defaultName : string ;
39
46
defaultOwner : TypesGen . User ;
@@ -54,6 +61,7 @@ export interface CreateWorkspacePageViewProps {
54
61
}
55
62
56
63
export const CreateWorkspacePageView : FC < CreateWorkspacePageViewProps > = ( {
64
+ mode,
57
65
error,
58
66
defaultName,
59
67
defaultOwner,
@@ -112,133 +120,143 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
112
120
) ;
113
121
114
122
return (
115
- < FullPageHorizontalForm title = "New workspace" onCancel = { onCancel } >
116
- < HorizontalForm onSubmit = { form . handleSubmit } >
117
- { Boolean ( error ) && < ErrorAlert error = { error } /> }
118
- { /* General info */ }
119
- < FormSection
120
- title = "General"
121
- description = "The template and name of your new workspace."
122
- >
123
- < FormFields >
124
- < SelectedTemplate template = { template } />
125
- { versionId && versionId !== template . active_version_id && (
126
- < Stack spacing = { 1 } className = { styles . hasDescription } >
127
- < TextField
128
- disabled
129
- fullWidth
130
- value = { versionId }
131
- label = "Version ID"
132
- />
133
- < span className = { styles . description } >
134
- This parameter has been preset, and cannot be modified.
135
- </ span >
136
- </ Stack >
137
- ) }
138
- < TextField
139
- { ...getFieldHelpers ( "name" ) }
140
- disabled = { creatingWorkspace }
141
- onChange = { onChangeTrimmed ( form ) }
142
- autoFocus
143
- fullWidth
144
- label = "Workspace Name"
145
- />
146
- </ FormFields >
147
- </ FormSection >
123
+ < >
124
+ { mode === "duplicate" && < DuplicateWarningMessage /> }
148
125
149
- { permissions . createWorkspaceForUser && (
126
+ < FullPageHorizontalForm title = "New workspace" onCancel = { onCancel } >
127
+ < HorizontalForm onSubmit = { form . handleSubmit } >
128
+ { Boolean ( error ) && < ErrorAlert error = { error } /> }
129
+ { /* General info */ }
150
130
< FormSection
151
- title = "Workspace Owner "
152
- description = "Only admins can create workspace for other users ."
131
+ title = "General "
132
+ description = "The template and name of your new workspace ."
153
133
>
154
134
< FormFields >
155
- < UserAutocomplete
156
- value = { owner }
157
- onChange = { ( user ) => {
158
- setOwner ( user ?? defaultOwner ) ;
159
- } }
160
- label = "Owner"
161
- size = "medium"
135
+ < SelectedTemplate template = { template } />
136
+ { versionId && versionId !== template . active_version_id && (
137
+ < Stack spacing = { 1 } className = { styles . hasDescription } >
138
+ < TextField
139
+ disabled
140
+ fullWidth
141
+ value = { versionId }
142
+ label = "Version ID"
143
+ />
144
+ < span className = { styles . description } >
145
+ This parameter has been preset, and cannot be modified.
146
+ </ span >
147
+ </ Stack >
148
+ ) }
149
+ < TextField
150
+ { ...getFieldHelpers ( "name" ) }
151
+ disabled = { creatingWorkspace }
152
+ onChange = { onChangeTrimmed ( form ) }
153
+ autoFocus
154
+ fullWidth
155
+ label = "Workspace Name"
162
156
/>
163
157
</ FormFields >
164
158
</ FormSection >
165
- ) }
166
159
167
- { externalAuth && externalAuth . length > 0 && (
168
- < FormSection
169
- title = "External Authentication"
170
- description = "This template requires authentication to external services."
171
- >
172
- < FormFields >
173
- { externalAuth . map ( ( auth ) => (
174
- < ExternalAuth
175
- key = { auth . id }
176
- authenticateURL = { auth . authenticate_url }
177
- authenticated = { auth . authenticated }
178
- externalAuthPollingState = { externalAuthPollingState }
179
- startPollingExternalAuth = { startPollingExternalAuth }
180
- displayName = { auth . display_name }
181
- displayIcon = { auth . display_icon }
182
- error = { externalAuthErrors [ auth . id ] }
160
+ { permissions . createWorkspaceForUser && (
161
+ < FormSection
162
+ title = "Workspace Owner"
163
+ description = "Only admins can create workspace for other users."
164
+ >
165
+ < FormFields >
166
+ < UserAutocomplete
167
+ value = { owner }
168
+ onChange = { ( user ) => {
169
+ setOwner ( user ?? defaultOwner ) ;
170
+ } }
171
+ label = "Owner"
172
+ size = "medium"
183
173
/>
184
- ) ) }
185
- </ FormFields >
186
- </ FormSection >
187
- ) }
174
+ </ FormFields >
175
+ </ FormSection >
176
+ ) }
188
177
189
- { parameters && (
190
- < >
191
- < MutableTemplateParametersSection
192
- templateParameters = { parameters }
193
- getInputProps = { ( parameter , index ) => {
194
- return {
195
- ...getFieldHelpers (
196
- "rich_parameter_values[" + index + "].value" ,
197
- ) ,
198
- onChange : async ( value ) => {
199
- await form . setFieldValue ( "rich_parameter_values." + index , {
200
- name : parameter . name ,
201
- value : value ,
202
- } ) ;
203
- } ,
204
- disabled :
205
- disabledParamsList ?. includes (
206
- parameter . name . toLowerCase ( ) . replace ( / / g, "_" ) ,
207
- ) || creatingWorkspace ,
208
- } ;
209
- } }
210
- />
211
- < ImmutableTemplateParametersSection
212
- templateParameters = { parameters }
213
- classes = { { root : styles . warningSection } }
214
- getInputProps = { ( parameter , index ) => {
215
- return {
216
- ...getFieldHelpers (
217
- "rich_parameter_values[" + index + "].value" ,
218
- ) ,
219
- onChange : async ( value ) => {
220
- await form . setFieldValue ( "rich_parameter_values." + index , {
221
- name : parameter . name ,
222
- value : value ,
223
- } ) ;
224
- } ,
225
- disabled :
226
- disabledParamsList ?. includes (
227
- parameter . name . toLowerCase ( ) . replace ( / / g, "_" ) ,
228
- ) || creatingWorkspace ,
229
- } ;
230
- } }
231
- />
232
- </ >
233
- ) }
178
+ { externalAuth && externalAuth . length > 0 && (
179
+ < FormSection
180
+ title = "External Authentication"
181
+ description = "This template requires authentication to external services."
182
+ >
183
+ < FormFields >
184
+ { externalAuth . map ( ( auth ) => (
185
+ < ExternalAuth
186
+ key = { auth . id }
187
+ authenticateURL = { auth . authenticate_url }
188
+ authenticated = { auth . authenticated }
189
+ externalAuthPollingState = { externalAuthPollingState }
190
+ startPollingExternalAuth = { startPollingExternalAuth }
191
+ displayName = { auth . display_name }
192
+ displayIcon = { auth . display_icon }
193
+ error = { externalAuthErrors [ auth . id ] }
194
+ />
195
+ ) ) }
196
+ </ FormFields >
197
+ </ FormSection >
198
+ ) }
234
199
235
- < FormFooter
236
- onCancel = { onCancel }
237
- isLoading = { creatingWorkspace }
238
- submitLabel = "Create Workspace"
239
- />
240
- </ HorizontalForm >
241
- </ FullPageHorizontalForm >
200
+ { parameters && (
201
+ < >
202
+ < MutableTemplateParametersSection
203
+ templateParameters = { parameters }
204
+ getInputProps = { ( parameter , index ) => {
205
+ return {
206
+ ...getFieldHelpers (
207
+ "rich_parameter_values[" + index + "].value" ,
208
+ ) ,
209
+ onChange : async ( value ) => {
210
+ await form . setFieldValue (
211
+ "rich_parameter_values." + index ,
212
+ {
213
+ name : parameter . name ,
214
+ value : value ,
215
+ } ,
216
+ ) ;
217
+ } ,
218
+ disabled :
219
+ disabledParamsList ?. includes (
220
+ parameter . name . toLowerCase ( ) . replace ( / / g, "_" ) ,
221
+ ) || creatingWorkspace ,
222
+ } ;
223
+ } }
224
+ />
225
+ < ImmutableTemplateParametersSection
226
+ templateParameters = { parameters }
227
+ classes = { { root : styles . warningSection } }
228
+ getInputProps = { ( parameter , index ) => {
229
+ return {
230
+ ...getFieldHelpers (
231
+ "rich_parameter_values[" + index + "].value" ,
232
+ ) ,
233
+ onChange : async ( value ) => {
234
+ await form . setFieldValue (
235
+ "rich_parameter_values." + index ,
236
+ {
237
+ name : parameter . name ,
238
+ value : value ,
239
+ } ,
240
+ ) ;
241
+ } ,
242
+ disabled :
243
+ disabledParamsList ?. includes (
244
+ parameter . name . toLowerCase ( ) . replace ( / / g, "_" ) ,
245
+ ) || creatingWorkspace ,
246
+ } ;
247
+ } }
248
+ />
249
+ </ >
250
+ ) }
251
+
252
+ < FormFooter
253
+ onCancel = { onCancel }
254
+ isLoading = { creatingWorkspace }
255
+ submitLabel = "Create Workspace"
256
+ />
257
+ </ HorizontalForm >
258
+ </ FullPageHorizontalForm >
259
+ </ >
242
260
) ;
243
261
} ;
244
262
@@ -279,6 +297,31 @@ const useExternalAuthVerification = (
279
297
} ;
280
298
} ;
281
299
300
+ function DuplicateWarningMessage ( ) {
301
+ const [ isDismissed , dismiss ] = useReducer ( ( ) => true , false ) ;
302
+ const theme = useTheme ( ) ;
303
+
304
+ if ( isDismissed ) {
305
+ return null ;
306
+ }
307
+
308
+ // Setup looks a little hokey (having an Alert already fully configured to
309
+ // listen to dismissals, on top of more dismissal state), but relying solely
310
+ // on the Alert API wouldn't get rid of the div and horizontal margin helper
311
+ // after the dismiss happens. Not using CSS margins because those can be a
312
+ // style maintenance nightmare over time
313
+ return (
314
+ < div css = { { paddingTop : theme . spacing ( 6 ) } } >
315
+ < Margins size = "medium" >
316
+ < Alert severity = "warning" dismissible onDismiss = { dismiss } >
317
+ Duplicating a workspace only copies its parameters. No state from the
318
+ old workspace is copied over.
319
+ </ Alert >
320
+ </ Margins >
321
+ </ div >
322
+ ) ;
323
+ }
324
+
282
325
const useStyles = makeStyles ( ( theme ) => ( {
283
326
hasDescription : {
284
327
paddingBottom : theme . spacing ( 2 ) ,
0 commit comments