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