CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/course/configuration/configuration-panel.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Card, Col, Row, Spin } from "antd";
7
import { debounce } from "lodash";
8
import { FormattedMessage, useIntl } from "react-intl";
9
10
import {
11
redux,
12
useActions,
13
useState,
14
useStore,
15
useTypedRedux,
16
} from "@cocalc/frontend/app-framework";
17
import {
18
Icon,
19
LabeledRow,
20
MarkdownInput,
21
TextInput,
22
} from "@cocalc/frontend/components";
23
import ShowError from "@cocalc/frontend/components/error";
24
import { course } from "@cocalc/frontend/i18n";
25
import { KUCALC_ON_PREMISES } from "@cocalc/util/db-schema/site-defaults";
26
import { contains_url } from "@cocalc/util/misc";
27
import { CourseActions } from "../actions";
28
import { CourseSettingsRecord, CourseStore } from "../store";
29
import ConfigurationCopying from "./configuration-copying";
30
import { CustomizeStudentProjectFunctionality } from "./customize-student-project-functionality";
31
import { DatastoreConfig } from "./datastore-config";
32
import { DisableStudentCollaboratorsPanel } from "./disable-collaborators";
33
import { EnvironmentVariablesConfig } from "./envvars-config";
34
import { Nbgrader } from "./nbgrader";
35
import { Parallel } from "./parallel";
36
import StudentPay from "./student-pay";
37
import { StudentProjectSoftwareEnvironment } from "./student-project-software-environment";
38
import { StudentProjectUpgrades } from "./upgrades";
39
import { COLORS } from "@cocalc/util/theme";
40
41
interface Props {
42
name: string;
43
project_id: string;
44
settings: CourseSettingsRecord;
45
configuring_projects?: boolean;
46
}
47
48
export function ConfigurationPanel({
49
name,
50
project_id,
51
settings,
52
configuring_projects,
53
}: Props) {
54
const actions = useActions<CourseActions>({ name });
55
56
return (
57
<div
58
className="smc-vfill"
59
style={{
60
overflowY: "scroll",
61
}}
62
>
63
<Row>
64
<Col md={12} style={{ padding: "15px 15px 15px 0" }}>
65
<UpgradeConfiguration
66
name={name}
67
settings={settings}
68
configuring_projects={configuring_projects}
69
actions={actions}
70
/>
71
<br />
72
<TitleAndDescription
73
actions={actions}
74
settings={settings}
75
name={name}
76
/>
77
<br />
78
<EmailInvitation
79
actions={actions}
80
redux={redux}
81
project_id={project_id}
82
name={name}
83
/>
84
<br />
85
<Nbgrader name={name} />
86
</Col>
87
<Col md={12} style={{ padding: "15px" }}>
88
<CollaboratorPolicy settings={settings} actions={actions} />
89
<br />
90
<RestrictStudentProjects settings={settings} actions={actions} />
91
<br />
92
<ConfigureSoftwareEnvironment
93
actions={actions}
94
settings={settings}
95
project_id={project_id}
96
/>
97
<br />
98
<Parallel name={name} />
99
<NetworkFilesystem
100
actions={actions}
101
settings={settings}
102
project_id={project_id}
103
/>
104
<br />
105
<EnvVariables
106
actions={actions}
107
settings={settings}
108
project_id={project_id}
109
/>
110
<br />
111
<ConfigurationCopying
112
actions={actions}
113
settings={settings}
114
project_id={project_id}
115
/>
116
</Col>
117
</Row>
118
</div>
119
);
120
}
121
122
export function UpgradeConfiguration({
123
name,
124
settings,
125
configuring_projects,
126
actions,
127
}) {
128
const is_commercial = useTypedRedux("customize", "is_commercial");
129
const kucalc = useTypedRedux("customize", "kucalc");
130
131
function render_require_institute_pay() {
132
if (!is_commercial) return;
133
return (
134
<>
135
<StudentProjectUpgrades
136
name={name}
137
is_onprem={false}
138
is_commercial={is_commercial}
139
institute_pay={settings?.get("institute_pay")}
140
student_pay={settings?.get("student_pay")}
141
site_license_id={settings?.get("site_license_id")}
142
site_license_strategy={settings?.get("site_license_strategy")}
143
shared_project_id={settings?.get("shared_project_id")}
144
disabled={configuring_projects}
145
settings={settings}
146
actions={actions.configuration}
147
/>
148
<br />
149
</>
150
);
151
}
152
153
/**
154
* OnPrem instances support licenses to be distributed to all student projects.
155
*/
156
function render_onprem_upgrade_projects() {
157
if (is_commercial || kucalc !== KUCALC_ON_PREMISES) {
158
return;
159
}
160
return (
161
<>
162
<StudentProjectUpgrades
163
name={name}
164
is_onprem={true}
165
is_commercial={false}
166
site_license_id={settings?.get("site_license_id")}
167
site_license_strategy={settings?.get("site_license_strategy")}
168
shared_project_id={settings?.get("shared_project_id")}
169
disabled={configuring_projects}
170
settings={settings}
171
actions={actions.configuration}
172
/>
173
<br />
174
</>
175
);
176
}
177
178
return (
179
<Card
180
title={
181
<>
182
<Icon name="gears" />{" "}
183
<FormattedMessage
184
id="course.configuration.configure_upgrades.title"
185
defaultMessage={"Configure Upgrades"}
186
/>
187
</>
188
}
189
>
190
{is_commercial && <StudentPay actions={actions} settings={settings} />}
191
{render_require_institute_pay()}
192
{render_onprem_upgrade_projects()}
193
</Card>
194
);
195
}
196
197
export function TitleAndDescription({ actions, settings, name }) {
198
const intl = useIntl();
199
if (settings == null) {
200
return <Spin />;
201
}
202
return (
203
<Card
204
title={
205
<>
206
<Icon name="header" />{" "}
207
{intl.formatMessage(course.title_and_description_label)}
208
</>
209
}
210
>
211
<LabeledRow label="Title">
212
<TextInput
213
text={settings.get("title") ?? ""}
214
on_change={(title) => actions.configuration.set_title(title)}
215
/>
216
</LabeledRow>
217
<LabeledRow label="Description">
218
<MarkdownInput
219
persist_id={name + "course-description"}
220
attach_to={name}
221
rows={6}
222
default_value={settings.get("description")}
223
on_save={(desc) => actions.configuration.set_description(desc)}
224
/>
225
</LabeledRow>
226
<hr />
227
<span style={{ color: COLORS.GRAY_M }}>
228
<FormattedMessage
229
id="course.configuration.title_and_description.info"
230
defaultMessage={`Set the course title and description here.
231
When you change the title or description,
232
the corresponding title and description of each student project will be updated.
233
The description is set to this description,
234
and the title is set to the student name followed by this title.
235
Use the description to provide additional information about the course,
236
e.g., a link to the main course website.`}
237
/>
238
</span>
239
</Card>
240
);
241
}
242
243
export function EmailInvitation({ actions, redux, project_id, name }) {
244
const intl = useIntl();
245
const [error, setError] = useState<string>("");
246
const store = useStore<CourseStore>({ name });
247
248
const check_email_body = debounce(
249
(value) => {
250
const allow_urls: boolean = redux
251
.getStore("projects")
252
.allow_urls_in_emails(project_id);
253
if (!allow_urls && contains_url(value)) {
254
setError(
255
intl.formatMessage({
256
id: "course.configuration.email_invitation.url_error",
257
defaultMessage:
258
"URLs in emails are not allowed for free trial projects. Please upgrade or delete the URL. This is an anti-spam measure.",
259
}),
260
);
261
} else {
262
setError("");
263
}
264
},
265
500,
266
{ leading: true, trailing: true },
267
);
268
269
return (
270
<Card
271
title={
272
<>
273
<Icon name="envelope" />{" "}
274
{intl.formatMessage(course.email_invitation_label)}
275
</>
276
}
277
>
278
<div
279
style={{
280
border: "1px solid lightgrey",
281
padding: "10px",
282
borderRadius: "5px",
283
}}
284
>
285
<ShowError error={error} />
286
<MarkdownInput
287
persist_id={name + "email-invite-body"}
288
attach_to={name}
289
rows={6}
290
default_value={store.get_email_invite()}
291
on_save={(body) => actions.configuration.set_email_invite(body)}
292
save_disabled={!!error}
293
on_change={check_email_body}
294
on_cancel={() => setError("")}
295
/>
296
</div>
297
<hr />
298
<span style={{ color: COLORS.GRAY_M }}>
299
<FormattedMessage
300
id="course.configuration.email_invitation.info"
301
defaultMessage={`If you add a student to this course using their email address,
302
and they do not have a CoCalc account, then they will receive this email invitation.
303
Also, "{title}" will be replaced by the title of the course and "{name}" by your name.`}
304
description={`Email invitations for students in an online course. Do not change {name} and {title} since they are variables.`}
305
values={{
306
// the curly brackets are replaced by the variable with brackets, since we also use this for template vars.
307
title: "{title}",
308
name: "{name}",
309
}}
310
/>
311
</span>
312
</Card>
313
);
314
}
315
316
export function CollaboratorPolicy({ settings, actions }) {
317
return (
318
<DisableStudentCollaboratorsPanel
319
checked={!!settings.get("allow_collabs")}
320
on_change={(val) => actions.configuration.set_allow_collabs(val)}
321
/>
322
);
323
}
324
325
export function RestrictStudentProjects({ settings, actions }) {
326
const functionality =
327
settings.get("student_project_functionality")?.toJS() ?? {};
328
return (
329
<CustomizeStudentProjectFunctionality
330
functionality={functionality}
331
onChange={async (opts) =>
332
await actions.configuration.set_student_project_functionality(opts)
333
}
334
/>
335
);
336
}
337
338
export function NetworkFilesystem({
339
settings,
340
actions,
341
project_id,
342
close,
343
}: {
344
settings;
345
actions;
346
project_id;
347
close?;
348
}) {
349
return (
350
<DatastoreConfig
351
actions={actions.configuration}
352
datastore={settings.get("datastore")}
353
project_id={project_id}
354
close={close}
355
/>
356
);
357
}
358
359
export function EnvVariables({
360
settings,
361
actions,
362
project_id,
363
close,
364
}: {
365
settings;
366
actions;
367
project_id;
368
close?;
369
}) {
370
return (
371
<EnvironmentVariablesConfig
372
actions={actions.configuration}
373
envvars={settings.get("envvars")}
374
project_id={project_id}
375
close={close}
376
/>
377
);
378
}
379
380
export function ConfigureSoftwareEnvironment({
381
actions,
382
settings,
383
project_id,
384
close,
385
}: {
386
actions;
387
settings;
388
project_id;
389
close?;
390
}) {
391
return (
392
<StudentProjectSoftwareEnvironment
393
actions={actions.configuration}
394
software_image={settings.get("custom_image")}
395
course_project_id={project_id}
396
inherit_compute_image={settings.get("inherit_compute_image")}
397
close={close}
398
/>
399
);
400
}
401
402