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/student-project-software-environment.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 { Alert, Button, Card, Divider, Radio, Space } from "antd";
7
import { useEffect, useState } from "react";
8
import { FormattedMessage, useIntl } from "react-intl";
9
10
import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";
11
import { A, Icon, Markdown } from "@cocalc/frontend/components";
12
import {
13
ComputeImage,
14
ComputeImages,
15
} from "@cocalc/frontend/custom-software/init";
16
import {
17
SoftwareEnvironment,
18
SoftwareEnvironmentState,
19
} from "@cocalc/frontend/custom-software/selector";
20
import {
21
compute_image2basename,
22
is_custom_image,
23
} from "@cocalc/frontend/custom-software/util";
24
import { HelpEmailLink } from "@cocalc/frontend/customize";
25
import { labels } from "@cocalc/frontend/i18n";
26
import { SoftwareImageDisplay } from "@cocalc/frontend/project/settings/software-image-display";
27
import {
28
KUCALC_COCALC_COM,
29
KUCALC_ON_PREMISES,
30
} from "@cocalc/util/db-schema/site-defaults";
31
import { ConfigurationActions } from "./actions";
32
33
const CSI_HELP =
34
"https://doc.cocalc.com/software.html#custom-software-environment";
35
36
interface Props {
37
actions: ConfigurationActions;
38
course_project_id: string;
39
software_image?: string;
40
inherit_compute_image?: boolean;
41
close?;
42
}
43
44
export function StudentProjectSoftwareEnvironment({
45
actions,
46
course_project_id,
47
software_image,
48
inherit_compute_image,
49
close,
50
}: Props) {
51
const intl = useIntl();
52
const customize_kucalc = useTypedRedux("customize", "kucalc");
53
const customize_software = useTypedRedux("customize", "software");
54
const software_envs = customize_software.get("environments");
55
const dflt_compute_img = customize_software.get("default");
56
57
// by default, we inherit the software image from the project where this course is run from
58
const inherit = inherit_compute_image ?? true;
59
const [state, set_state] = useState<SoftwareEnvironmentState>({});
60
const [changing, set_changing] = useState(false);
61
62
function handleChange(state): void {
63
set_state(state);
64
}
65
const current_environment = <SoftwareImageDisplay image={software_image} />;
66
67
const custom_images: ComputeImages | undefined = useTypedRedux(
68
"compute_images",
69
"images",
70
);
71
72
function on_inherit_change(inherit: boolean) {
73
if (inherit) {
74
// we have to get the compute image name from the course project
75
const projects_store = redux.getStore("projects");
76
const course_project_compute_image = projects_store.getIn([
77
"project_map",
78
course_project_id,
79
"compute_image",
80
]);
81
actions.set_inherit_compute_image(course_project_compute_image);
82
} else {
83
actions.set_inherit_compute_image();
84
}
85
}
86
87
useEffect(() => {
88
if (inherit) {
89
set_changing(false);
90
}
91
}, [inherit]);
92
93
function csi_warning() {
94
return (
95
<Alert
96
type={"warning"}
97
message={
98
<>
99
<strong>Warning:</strong> Do not change a custom image once there is
100
already one setup and deployed!
101
</>
102
}
103
description={
104
"The associated user files will not be updated and the software environment changes might break the functionality of existing files."
105
}
106
/>
107
);
108
}
109
110
function render_controls_body() {
111
if (!changing) {
112
return (
113
<Button onClick={() => set_changing(true)} disabled={changing}>
114
{intl.formatMessage(labels.change)}...
115
</Button>
116
);
117
} else {
118
return (
119
<div>
120
<SoftwareEnvironment
121
onChange={handleChange}
122
default_image={software_image}
123
/>
124
{state.image_type === "custom" && csi_warning()}
125
<br />
126
<Space>
127
<Button
128
onClick={() => {
129
set_changing(false);
130
}}
131
>
132
{intl.formatMessage(labels.cancel)}
133
</Button>
134
<Button
135
disabled={
136
state.image_type === "custom" && state.image_selected == null
137
}
138
type="primary"
139
onClick={async () => {
140
set_changing(false);
141
await actions.set_software_environment(state);
142
close?.();
143
}}
144
>
145
{intl.formatMessage(labels.save)}
146
</Button>
147
</Space>
148
</div>
149
);
150
}
151
}
152
153
function render_controls() {
154
if (inherit) return;
155
return (
156
<>
157
<Divider orientation="left">
158
{intl.formatMessage(labels.configuration)}
159
</Divider>
160
{render_controls_body()}
161
</>
162
);
163
}
164
165
function render_description() {
166
const img_id = software_image ?? dflt_compute_img;
167
let descr: string | undefined;
168
if (is_custom_image(img_id)) {
169
if (custom_images == null) return;
170
const base_id = compute_image2basename(img_id);
171
const img: ComputeImage | undefined = custom_images.get(base_id);
172
if (img != null) {
173
descr = img.get("desc");
174
}
175
} else {
176
const img = software_envs.get(img_id);
177
if (img != null) {
178
descr = `<i>(${img.get("descr")})</i>`;
179
}
180
}
181
if (descr) {
182
return (
183
<Markdown
184
style={{
185
display: "block",
186
maxHeight: "200px",
187
overflowY: "auto",
188
marginTop: "10px",
189
marginBottom: "10px",
190
}}
191
value={descr}
192
/>
193
);
194
}
195
}
196
197
function render_custom_info() {
198
if (software_image != null && is_custom_image(software_image)) return;
199
return (
200
<p>
201
<FormattedMessage
202
id="course.student-project-software-environment.help"
203
defaultMessage={`If you need additional software or a fully <A>customized software environment</A>,
204
please contact {help}.`}
205
values={{
206
help: <HelpEmailLink />,
207
A: (c) => <A href={CSI_HELP}>{c}</A>,
208
}}
209
/>
210
</p>
211
);
212
}
213
214
function render_inherit() {
215
// We use fontWeight: "normal" below because otherwise the default
216
// of bold for the entire label is a bit much for such a large label.
217
return (
218
<Radio.Group
219
onChange={(e) => on_inherit_change(e.target.value)}
220
value={inherit}
221
>
222
<Radio style={{ fontWeight: "normal" }} value={true}>
223
<FormattedMessage
224
id="course.student-project-software-environment.inherit.true"
225
defaultMessage={`<strong>Inherit</strong> student projects software environments from this teacher project`}
226
/>
227
</Radio>
228
<Radio style={{ fontWeight: "normal" }} value={false}>
229
<FormattedMessage
230
id="course.student-project-software-environment.inherit.false"
231
defaultMessage={`<strong>Explicitly</strong> specify student project software environments`}
232
/>
233
</Radio>
234
</Radio.Group>
235
);
236
}
237
238
// this selector only make sense for cocalc.com and cocalc-onprem
239
if (
240
customize_kucalc !== KUCALC_COCALC_COM &&
241
customize_kucalc !== KUCALC_ON_PREMISES
242
)
243
return null;
244
245
return (
246
<Card
247
title={
248
<>
249
<Icon name="laptop" />{" "}
250
{intl.formatMessage(labels.software_environment)}:{" "}
251
{current_environment}
252
</>
253
}
254
>
255
<p>
256
<FormattedMessage
257
id="course.student-project-software-environment.status"
258
defaultMessage={`Student projects will use the following software environment: <em>{env}</em>`}
259
values={{
260
em: (c) => <em>{c}</em>,
261
env: current_environment,
262
}}
263
/>
264
</p>
265
{render_description()}
266
{render_custom_info()}
267
{render_inherit()}
268
{render_controls()}
269
</Card>
270
);
271
}
272
273