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/customize-student-project-functionality.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 { Button, Card, Checkbox } from "antd";
7
import { isEqual } from "lodash";
8
import { useEffect, useRef, useState } from "react";
9
import { defineMessage, FormattedMessage, useIntl } from "react-intl";
10
11
import {
12
redux,
13
useIsMountedRef,
14
useTypedRedux,
15
} from "@cocalc/frontend/app-framework";
16
import { Icon, Paragraph, Tip } from "@cocalc/frontend/components";
17
import { course, IntlMessage, labels } from "@cocalc/frontend/i18n";
18
import { R_IDE } from "@cocalc/util/consts/ui";
19
import type { StudentProjectFunctionality } from "@cocalc/util/db-schema/projects";
20
21
export type { StudentProjectFunctionality };
22
23
interface Option {
24
name: string;
25
title: IntlMessage;
26
description: IntlMessage;
27
isCoCalcCom?: boolean;
28
notImplemented?: boolean;
29
}
30
31
const OPTIONS: Option[] = [
32
{
33
name: "disableActions",
34
title: defineMessage({
35
id: "course.customize-student-project-functionality.disableActions.title",
36
defaultMessage: "Disable file actions",
37
}),
38
description: defineMessage({
39
id: "course.customize-student-project-functionality.disableActions.description",
40
defaultMessage:
41
"Make it so students can't delete, download, copy, publish, etc., files in their project. See the Disable Publish sharing option below if you just want to disable publishing.",
42
}),
43
},
44
{
45
name: "disableJupyterToggleReadonly",
46
title: defineMessage({
47
id: "course.customize-student-project-functionality.disableJupyterToggleReadonly.title",
48
defaultMessage:
49
"Disable toggling whether cells are editable or deletable",
50
}),
51
description: defineMessage({
52
id: "course.customize-student-project-functionality.disableJupyterToggleReadonly.description",
53
defaultMessage:
54
"Make it so that in Jupyter notebooks, students can't toggle whether cells are editable or deletable, and also disables the RAW Json Editor and the Jupyter command list dialog. If you set this, you should probably disable all of the JupyterLab and Jupyter classic options too.",
55
}),
56
},
57
{
58
name: "disableJupyterClassicServer",
59
title: defineMessage({
60
id: "course.customize-student-project-functionality.disableJupyterClassicServer.title",
61
defaultMessage: "Disable Jupyter Classic notebook server",
62
}),
63
description: defineMessage({
64
id: "course.customize-student-project-functionality.disableJupyterClassicServer.description",
65
defaultMessage:
66
"Disable the user interface for running a Jupyter classic server in student projects. This is important, since Jupyter classic provides its own extensive download and edit functionality; moreover, you may want to disable Jupyter classic to reduce confusion if you don't plan to use it.",
67
}),
68
},
69
{
70
name: "disableJupyterLabServer",
71
title: defineMessage({
72
id: "course.customize-student-project-functionality.disableJupyterLabServer.title",
73
defaultMessage: "Disable JupyterLab notebook server",
74
}),
75
description: defineMessage({
76
id: "course.customize-student-project-functionality.disableJupyterLabServer.description",
77
defaultMessage:
78
"Disable the user interface for running a JupyterLab server in student projects. This is important, since JupyterLab it provides its own extensive download and edit functionality; moreover, you may want to disable JupyterLab to reduce confusion if you don't plan to use it.",
79
}),
80
},
81
{
82
name: "disableVSCodeServer",
83
title: defineMessage({
84
id: "course.customize-student-project-functionality.disableVSCodeServer.title",
85
defaultMessage: "Disable VS Code IDE Server",
86
}),
87
description: defineMessage({
88
id: "course.customize-student-project-functionality.disableVSCodeServer.description",
89
defaultMessage:
90
"Disable the VS Code IDE Server, which lets you run VS Code in a project with one click.",
91
}),
92
},
93
{
94
name: "disablePlutoServer",
95
title: defineMessage({
96
id: "course.customize-student-project-functionality.disablePlutoServer.title",
97
defaultMessage: "Disable Pluto Julia notebook server",
98
}),
99
description: defineMessage({
100
id: "course.customize-student-project-functionality.disablePlutoServer.description",
101
defaultMessage:
102
"Disable the user interface for running a pluto server in student projects. Pluto lets you run Julia notebooks from a project.",
103
}),
104
},
105
{
106
name: "disableRServer",
107
title: defineMessage({
108
id: "course.customize-student-project-functionality.disableRServer.title",
109
defaultMessage: "{R_IDE}",
110
}),
111
description: defineMessage({
112
id: "course.customize-student-project-functionality.disableRServer.description",
113
defaultMessage: `Disable the user interface for running the {R_IDE} server in student projects. This is an IDE for coding in R.`,
114
}),
115
},
116
{
117
name: "disableTerminals",
118
title: defineMessage({
119
id: "course.customize-student-project-functionality.disableTerminals.title",
120
defaultMessage: "Disable command line terminal",
121
}),
122
description: defineMessage({
123
id: "course.customize-student-project-functionality.disableTerminals.description",
124
defaultMessage:
125
"Disables opening or running command line terminals in student projects.",
126
}),
127
},
128
{
129
name: "disableUploads",
130
title: defineMessage({
131
id: "course.customize-student-project-functionality.disableUploads.title",
132
defaultMessage: "Disable file uploads",
133
}),
134
description: defineMessage({
135
id: "course.customize-student-project-functionality.disableUploads.description",
136
defaultMessage:
137
"Blocks uploading files to the student project via drag-n-drop or the Upload button.",
138
}),
139
},
140
{
141
name: "disableLibrary",
142
title: defineMessage({
143
id: "course.customize-student-project-functionality.disableLibrary.title",
144
defaultMessage: "Disable Library",
145
}),
146
description: defineMessage({
147
id: "course.customize-student-project-functionality.disableLibrary.description",
148
defaultMessage:
149
"In the file explorer there is a library button for browsing and copying books and tutorials into a project. Disable this to simplify the interface.",
150
}),
151
},
152
{
153
name: "disableCollaborators",
154
title: defineMessage({
155
id: "course.customize-student-project-functionality.disableCollaborators.title",
156
defaultMessage: "Disable adding or removing collaborators",
157
}),
158
description: defineMessage({
159
id: "course.customize-student-project-functionality.disableCollaborators.description",
160
defaultMessage:
161
"Removes the user interface for adding or removing collaborators from student projects.",
162
}),
163
},
164
// {
165
// notImplemented: true,
166
// name: "disableAPI",
167
// title: "Disable API keys",
168
// description:
169
// "Makes it so the HTTP API is blocked from accessing the student project. A student might use the API to get around various other restrictions.",
170
// },
171
{
172
name: "disableNetwork",
173
title: defineMessage({
174
id: "course.customize-student-project-functionality.disableNetwork.title",
175
defaultMessage: "Disable outgoing network access",
176
}),
177
description: defineMessage({
178
id: "course.customize-student-project-functionality.disableNetwork.description",
179
defaultMessage:
180
"Blocks all outgoing network connections from the student projects.",
181
}),
182
isCoCalcCom: true,
183
},
184
{
185
name: "disableNetworkWarningBanner",
186
title: defineMessage({
187
id: "course.customize-student-project-functionality.disableNetworkWarningBanner.title",
188
defaultMessage: "Disable outgoing network access warning banner",
189
}),
190
description: defineMessage({
191
id: "course.customize-student-project-functionality.disableNetworkWarningBanner.description",
192
defaultMessage:
193
"Disables the banner at the top of the screen that warns students that network access is disabled.",
194
}),
195
isCoCalcCom: true,
196
},
197
{
198
name: "disableSSH",
199
title: defineMessage({
200
id: "course.customize-student-project-functionality.disableSSH.title",
201
defaultMessage: "Disable SSH access to project",
202
}),
203
description: defineMessage({
204
id: "course.customize-student-project-functionality.disableSSH.description",
205
defaultMessage: "Makes any attempt to ssh to a student project fail.",
206
}),
207
isCoCalcCom: true,
208
},
209
{
210
name: "disableChatGPT",
211
title: defineMessage({
212
id: "course.customize-student-project-functionality.disableChatGPT.title",
213
defaultMessage: "Disable all AI integration (ChatGPT & co.)",
214
}),
215
description: defineMessage({
216
id: "course.customize-student-project-functionality.disableChatGPT.description",
217
defaultMessage:
218
"Remove *all* AI integrations (ChatGPT & co.) from the student projects. This is a hint for honest students, since of course students can still use copy/paste to accomplish the same thing.",
219
}),
220
},
221
{
222
name: "disableSomeChatGPT",
223
title: defineMessage({
224
id: "course.customize-student-project-functionality.disableSomeChatGPT.title",
225
defaultMessage: "Disable some AI integration (ChatGPT & co.)",
226
}),
227
description: defineMessage({
228
id: "course.customize-student-project-functionality.disableSomeChatGPT.description",
229
defaultMessage:
230
"Disable AI integration (ChatGPT & co.) except that 'Help me fix' and 'Explain' buttons. Use this if you only want the students to use AI assistance to get unstuck.",
231
}),
232
},
233
{
234
name: "disableSharing",
235
title: defineMessage({
236
id: "course.customize-student-project-functionality.disableSharing.title",
237
defaultMessage: "Disable Public sharing",
238
}),
239
description: defineMessage({
240
id: "course.customize-student-project-functionality.disableSharing.description",
241
defaultMessage:
242
"Disable public sharing of files from the student projects. This is a hint for honest students, since of course students can still download files or even copy them to another project and share them. This does not change the share status of any files that are currently shared.",
243
}),
244
},
245
] as const;
246
247
interface Props {
248
functionality: StudentProjectFunctionality;
249
onChange: (StudentProjectFunctionality) => Promise<void>;
250
}
251
252
export function CustomizeStudentProjectFunctionality({
253
functionality,
254
onChange,
255
}: Props) {
256
const intl = useIntl();
257
const isCoCalcCom = useTypedRedux("customize", "is_cocalc_com");
258
const [state, setState] =
259
useState<StudentProjectFunctionality>(functionality);
260
const [saving, setSaving] = useState<boolean>(false);
261
262
function onChangeState(obj: StudentProjectFunctionality) {
263
const newState = { ...state };
264
for (const key in obj) {
265
newState[key] = obj[key];
266
}
267
setState(newState);
268
}
269
270
const isMountedRef = useIsMountedRef();
271
272
const lastFunctionalityRef =
273
useRef<StudentProjectFunctionality>(functionality);
274
useEffect(() => {
275
if (isEqual(functionality, lastFunctionalityRef.current)) {
276
return;
277
}
278
// some sort of upstream change
279
lastFunctionalityRef.current = functionality;
280
setState(functionality);
281
}, [functionality]);
282
283
function renderOption(option: Option) {
284
const { name } = option;
285
const description = intl.formatMessage(option.description, { R_IDE });
286
287
let title = intl.formatMessage(option.title, { R_IDE });
288
if (option.notImplemented) {
289
const msg = intl.formatMessage(labels.not_implemented).toUpperCase();
290
title += ` (${msg})`;
291
}
292
293
return (
294
<Tip key={name} title={title} tip={description}>
295
<Checkbox
296
disabled={saving}
297
checked={state[name]}
298
onChange={(e) =>
299
onChangeState({
300
[name]: (e.target as any).checked,
301
})
302
}
303
>
304
{title}
305
</Checkbox>
306
<br />
307
</Tip>
308
);
309
}
310
311
const options: JSX.Element[] = [];
312
for (const option of OPTIONS) {
313
if (option.isCoCalcCom && !isCoCalcCom) continue;
314
options.push(renderOption(option));
315
}
316
317
const title = intl.formatMessage(course.restrict_student_projects);
318
319
return (
320
<Card
321
title={
322
<>
323
<Icon name="lock" /> {title}
324
</>
325
}
326
>
327
<Paragraph type="secondary">
328
<FormattedMessage
329
id="course.customize-student-project-functionality.description"
330
defaultMessage={`Check any of the boxes below
331
to remove the corresponding functionality from all student projects.
332
Hover over an option for more information about what it disables.
333
This is useful to reduce student confusion and keep the students more focused,
334
e.g., during an exam.
335
<i>
336
Do not gain a false sense of security and expect these to prevent all forms of cheating.
337
</i>`}
338
/>
339
</Paragraph>
340
<hr />
341
<div
342
style={{
343
border: "1px solid lightgrey",
344
padding: "10px",
345
borderRadius: "5px",
346
}}
347
>
348
{options}
349
<div style={{ marginTop: "8px" }}>
350
<Button
351
type="primary"
352
disabled={saving || isEqual(functionality, state)}
353
onClick={async () => {
354
setSaving(true);
355
await onChange(state);
356
if (isMountedRef.current) {
357
setSaving(false);
358
}
359
}}
360
>
361
{intl.formatMessage(labels.save_changes)}
362
</Button>
363
</div>
364
</div>
365
</Card>
366
);
367
}
368
369
export function completeStudentProjectFunctionality(
370
x: StudentProjectFunctionality,
371
) {
372
const y = { ...x };
373
for (const { name } of OPTIONS) {
374
if (y[name] == null) {
375
y[name] = false;
376
}
377
}
378
return y;
379
}
380
381
// NOTE: we allow project_id to be undefined for convenience since some clients
382
// were written with that unlikely assumption on their knowledge of project_id.
383
type Hook = (project_id?: string) => StudentProjectFunctionality;
384
export const useStudentProjectFunctionality: Hook = (project_id?: string) => {
385
const project_map = useTypedRedux("projects", "project_map") as any;
386
const [state, setState] = useState<StudentProjectFunctionality>(
387
project_map
388
?.getIn([project_id ?? "", "course", "student_project_functionality"])
389
?.toJS() ?? {},
390
);
391
useEffect(() => {
392
setState(
393
project_map
394
?.getIn([project_id ?? "", "course", "student_project_functionality"])
395
?.toJS() ?? {},
396
);
397
return;
398
}, [
399
project_map?.getIn([
400
project_id ?? "",
401
"course",
402
"student_project_functionality",
403
]),
404
]);
405
406
return state;
407
};
408
409
// Getting the information known right now about studnet project functionality.
410
// Similar to the above hook, but just a point in time snapshot. Use this
411
// for old components that haven't been converted to react hooks yet.
412
export function getStudentProjectFunctionality(
413
project_id?: string,
414
): StudentProjectFunctionality {
415
return (
416
redux
417
.getStore("projects")
418
?.getIn([
419
"project_map",
420
project_id ?? "",
421
"course",
422
"student_project_functionality",
423
])
424
?.toJS() ?? {}
425
);
426
}
427
428