@@ -19,7 +19,6 @@ import {
19
19
LabeledRow ,
20
20
MarkdownInput ,
21
21
TextInput ,
22
- ErrorDisplay ,
23
22
} from "@cocalc/frontend/components" ;
24
23
import { StudentProjectUpgrades } from "./upgrades" ;
25
24
import { CourseActions } from "../actions" ;
@@ -34,6 +33,7 @@ import { KUCALC_ON_PREMISES } from "@cocalc/util/db-schema/site-defaults";
34
33
import { EnvironmentVariablesConfig } from "./envvars-config" ;
35
34
import StudentPay from "./student-pay" ;
36
35
//import Mirror from "./mirror";
36
+ import ShowError from "@cocalc/frontend/components/error" ;
37
37
38
38
interface Props {
39
39
name : string ;
@@ -44,81 +44,10 @@ interface Props {
44
44
45
45
export const ConfigurationPanel : React . FC < Props > = React . memo (
46
46
( { name, project_id, settings, configuring_projects } ) => {
47
- const [ email_body_error , set_email_body_error ] = useState <
48
- string | undefined
49
- > ( undefined ) ;
50
-
51
47
const actions = useActions < CourseActions > ( { name } ) ;
52
- const store = useStore < CourseStore > ( { name } ) ;
53
48
const is_commercial = useTypedRedux ( "customize" , "is_commercial" ) ;
54
49
const kucalc = useTypedRedux ( "customize" , "kucalc" ) ;
55
50
56
- /*
57
- * Custom invitation email body
58
- */
59
-
60
- const check_email_body = debounce (
61
- ( value ) => {
62
- const allow_urls : boolean = redux
63
- . getStore ( "projects" )
64
- . allow_urls_in_emails ( project_id ) ;
65
- if ( ! allow_urls && contains_url ( value ) ) {
66
- set_email_body_error (
67
- "URLs in emails are not allowed for free trial projects. Please upgrade or delete the URL. This is an anti-spam measure." ,
68
- ) ;
69
- } else {
70
- set_email_body_error ( undefined ) ;
71
- }
72
- } ,
73
- 500 ,
74
- { leading : true , trailing : true } ,
75
- ) ;
76
-
77
- function render_email_body_error ( ) {
78
- if ( email_body_error == null ) return ;
79
- return < ErrorDisplay error = { email_body_error } /> ;
80
- }
81
-
82
- function render_email_invite_body ( ) {
83
- const template_instr =
84
- " Also, {title} will be replaced by the title of the course and {name} by your name." ;
85
- return (
86
- < Card
87
- title = {
88
- < >
89
- < Icon name = "envelope" /> Email Invitation
90
- </ >
91
- }
92
- >
93
- < div
94
- style = { {
95
- border : "1px solid lightgrey" ,
96
- padding : "10px" ,
97
- borderRadius : "5px" ,
98
- } }
99
- >
100
- { render_email_body_error ( ) }
101
- < MarkdownInput
102
- persist_id = { name + "email-invite-body" }
103
- attach_to = { name }
104
- rows = { 6 }
105
- default_value = { store . get_email_invite ( ) }
106
- on_save = { ( body ) => actions . configuration . set_email_invite ( body ) }
107
- save_disabled = { email_body_error != null }
108
- on_change = { check_email_body }
109
- on_cancel = { ( ) => set_email_body_error ( undefined ) }
110
- />
111
- </ div >
112
- < hr />
113
- < span style = { { color : "#666" } } >
114
- If you add a student to this course using their email address, and
115
- they do not have a CoCalc account, then they will receive this email
116
- invitation. { template_instr }
117
- </ span >
118
- </ Card >
119
- ) ;
120
- }
121
-
122
51
function render_require_institute_pay ( ) {
123
52
if ( ! is_commercial ) return ;
124
53
return (
@@ -165,32 +94,6 @@ export const ConfigurationPanel: React.FC<Props> = React.memo(
165
94
) ;
166
95
}
167
96
168
- function render_disable_students ( ) {
169
- return (
170
- < DisableStudentCollaboratorsPanel
171
- checked = { ! ! settings . get ( "allow_collabs" ) }
172
- on_change = { ( val ) => actions . configuration . set_allow_collabs ( val ) }
173
- />
174
- ) ;
175
- }
176
-
177
- function render_student_project_functionality ( ) {
178
- const functionality =
179
- settings . get ( "student_project_functionality" ) ?. toJS ( ) ?? { } ;
180
- return (
181
- < CustomizeStudentProjectFunctionality
182
- functionality = { functionality }
183
- onChange = { async ( opts ) =>
184
- await actions . configuration . set_student_project_functionality ( opts )
185
- }
186
- />
187
- ) ;
188
- }
189
-
190
- function render_nbgrader ( ) {
191
- return < Nbgrader name = { name } /> ;
192
- }
193
-
194
97
return (
195
98
< div className = "smc-vfill" style = { { overflowY : "scroll" } } >
196
99
< Row >
@@ -207,14 +110,19 @@ export const ConfigurationPanel: React.FC<Props> = React.memo(
207
110
name = { name }
208
111
/>
209
112
< br />
210
- { render_email_invite_body ( ) }
113
+ < EmailInvitation
114
+ actions = { actions }
115
+ redux = { redux }
116
+ project_id = { project_id }
117
+ name = { name }
118
+ />
211
119
< br />
212
- { render_nbgrader ( ) }
120
+ < Nbgrader name = { name } />
213
121
</ Col >
214
122
< Col md = { 12 } style = { { padding : "15px" } } >
215
- { render_disable_students ( ) }
123
+ < CollaboratorPolicy settings = { settings } actions = { actions } />
216
124
< br />
217
- { render_student_project_functionality ( ) }
125
+ < RestrictStudentProjects settings = { settings } actions = { actions } />
218
126
< br />
219
127
< StudentProjectSoftwareEnvironment
220
128
actions = { actions . configuration }
@@ -224,14 +132,16 @@ export const ConfigurationPanel: React.FC<Props> = React.memo(
224
132
/>
225
133
< br />
226
134
< Parallel name = { name } />
227
- < DatastoreConfig
228
- actions = { actions . configuration }
229
- datastore = { settings . get ( "datastore" ) }
135
+ < NetworkFilesystem
136
+ actions = { actions }
137
+ settings = { settings }
138
+ project_id = { project_id }
230
139
/>
231
140
< br />
232
- < EnvironmentVariablesConfig
233
- actions = { actions . configuration }
234
- envvars = { settings . get ( "envvars" ) }
141
+ < EnvVariables
142
+ actions = { actions }
143
+ settings = { settings }
144
+ project_id = { project_id }
235
145
/>
236
146
{ /*<br />
237
147
<Mirror
@@ -291,3 +201,127 @@ export function TitleAndDescription({ actions, settings, name }) {
291
201
</ Card >
292
202
) ;
293
203
}
204
+
205
+ export function EmailInvitation ( { actions, redux, project_id, name } ) {
206
+ const [ error , setError ] = useState < string > ( "" ) ;
207
+ const store = useStore < CourseStore > ( { name } ) ;
208
+
209
+ const check_email_body = debounce (
210
+ ( value ) => {
211
+ const allow_urls : boolean = redux
212
+ . getStore ( "projects" )
213
+ . allow_urls_in_emails ( project_id ) ;
214
+ if ( ! allow_urls && contains_url ( value ) ) {
215
+ setError (
216
+ "URLs in emails are not allowed for free trial projects. Please upgrade or delete the URL. This is an anti-spam measure." ,
217
+ ) ;
218
+ } else {
219
+ setError ( "" ) ;
220
+ }
221
+ } ,
222
+ 500 ,
223
+ { leading : true , trailing : true } ,
224
+ ) ;
225
+
226
+ const template_instr =
227
+ " Also, {title} will be replaced by the title of the course and {name} by your name." ;
228
+ return (
229
+ < Card
230
+ title = {
231
+ < >
232
+ < Icon name = "envelope" /> Email Invitation
233
+ </ >
234
+ }
235
+ >
236
+ < div
237
+ style = { {
238
+ border : "1px solid lightgrey" ,
239
+ padding : "10px" ,
240
+ borderRadius : "5px" ,
241
+ } }
242
+ >
243
+ < ShowError error = { error } />
244
+ < MarkdownInput
245
+ persist_id = { name + "email-invite-body" }
246
+ attach_to = { name }
247
+ rows = { 6 }
248
+ default_value = { store . get_email_invite ( ) }
249
+ on_save = { ( body ) => actions . configuration . set_email_invite ( body ) }
250
+ save_disabled = { ! ! error }
251
+ on_change = { check_email_body }
252
+ on_cancel = { ( ) => setError ( "" ) }
253
+ />
254
+ </ div >
255
+ < hr />
256
+ < span style = { { color : "#666" } } >
257
+ If you add a student to this course using their email address, and they
258
+ do not have a CoCalc account, then they will receive this email
259
+ invitation. { template_instr }
260
+ </ span >
261
+ </ Card >
262
+ ) ;
263
+ }
264
+
265
+ export function CollaboratorPolicy ( { settings, actions } ) {
266
+ return (
267
+ < DisableStudentCollaboratorsPanel
268
+ checked = { ! ! settings . get ( "allow_collabs" ) }
269
+ on_change = { ( val ) => actions . configuration . set_allow_collabs ( val ) }
270
+ />
271
+ ) ;
272
+ }
273
+
274
+ export function RestrictStudentProjects ( { settings, actions } ) {
275
+ const functionality =
276
+ settings . get ( "student_project_functionality" ) ?. toJS ( ) ?? { } ;
277
+ return (
278
+ < CustomizeStudentProjectFunctionality
279
+ functionality = { functionality }
280
+ onChange = { async ( opts ) =>
281
+ await actions . configuration . set_student_project_functionality ( opts )
282
+ }
283
+ />
284
+ ) ;
285
+ }
286
+
287
+ export function NetworkFilesystem ( {
288
+ settings,
289
+ actions,
290
+ project_id,
291
+ close,
292
+ } : {
293
+ settings ;
294
+ actions ;
295
+ project_id ;
296
+ close ?;
297
+ } ) {
298
+ return (
299
+ < DatastoreConfig
300
+ actions = { actions . configuration }
301
+ datastore = { settings . get ( "datastore" ) }
302
+ project_id = { project_id }
303
+ close = { close }
304
+ />
305
+ ) ;
306
+ }
307
+
308
+ export function EnvVariables ( {
309
+ settings,
310
+ actions,
311
+ project_id,
312
+ close,
313
+ } : {
314
+ settings ;
315
+ actions ;
316
+ project_id ;
317
+ close ?;
318
+ } ) {
319
+ return (
320
+ < EnvironmentVariablesConfig
321
+ actions = { actions . configuration }
322
+ envvars = { settings . get ( "envvars" ) }
323
+ project_id = { project_id }
324
+ close = { close }
325
+ />
326
+ ) ;
327
+ }
0 commit comments