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/compute/doc-status.tsx
Views: 687
1
/*
2
This is a component that should be placed at the top of a document to help
3
the user when they have requested their document run on a given compute
4
server. It does the following:
5
6
- If id is as requested and is the project, do nothing.
7
8
- If id is as requested and is not the project, draw line in color of that compute server.
9
10
- If not where we want to be, defines how close via a percentage
11
12
- If compute server not running:
13
- if exists and you own it, prompts user to start it and also shows the
14
compute server's component so they can do so.
15
- if not exists (or deleted), say so
16
- if owned by somebody else, say so
17
*/
18
19
import Inline from "./inline";
20
import { useTypedRedux } from "@cocalc/frontend/app-framework";
21
import { Alert, Button, Progress, Space, Spin, Tooltip } from "antd";
22
import type { ComputeServerUserInfo } from "@cocalc/util/db-schema/compute-servers";
23
import ComputeServer from "./compute-server";
24
import { useEffect, useState } from "react";
25
import { Icon } from "@cocalc/frontend/components";
26
import SyncButton from "./sync-button";
27
import { avatar_fontcolor } from "@cocalc/frontend/account/avatar/font-color";
28
import { DisplayImage } from "./select-image";
29
import Menu from "./menu";
30
31
interface Props {
32
project_id: string;
33
id: number;
34
requestedId: number;
35
noSync?: boolean;
36
standalone?: boolean;
37
}
38
39
export function ComputeServerDocStatus({
40
project_id,
41
id,
42
requestedId,
43
noSync,
44
standalone,
45
}: Props) {
46
const [showDetails, setShowDetails] = useState<boolean | null>(null);
47
const computeServers = useTypedRedux({ project_id }, "compute_servers");
48
const account_id = useTypedRedux("account", "account_id");
49
50
useEffect(() => {
51
// if the id or requestedId changes, need to reset to default behavior
52
// regarding what is shown.
53
setShowDetails(null);
54
}, [id, requestedId]);
55
56
const requestedServer = computeServers?.get(`${requestedId}`);
57
const syncExclude = requestedServer?.getIn([
58
"configuration",
59
"excludeFromSync",
60
]);
61
const excludeFromSync =
62
syncExclude?.includes("~") || syncExclude?.includes(".");
63
const syncState = requestedServer?.getIn([
64
"detailed_state",
65
"filesystem-sync",
66
]);
67
68
// show sync errors
69
useEffect(() => {
70
if (syncState?.get("extra")) {
71
setShowDetails(true);
72
}
73
}, [syncState?.get("extra")]);
74
75
if (id == 0 && requestedId == 0) {
76
return null;
77
}
78
79
if (computeServers == null) {
80
return null;
81
}
82
83
const topBar = (progress) => (
84
<div
85
style={{
86
display: "flex",
87
borderBottom:
88
!standalone && requestedServer != null && !showDetails
89
? "1px solid #ccc"
90
: undefined,
91
...(standalone
92
? { border: "1px solid #ddd", borderRadius: "5px" }
93
: undefined),
94
}}
95
>
96
{progress == 100 && !noSync && (
97
<SyncButton
98
type="text"
99
disabled={excludeFromSync}
100
style={{
101
marginLeft: "-3px",
102
float: "right",
103
width: "90px",
104
}}
105
size="small"
106
compute_server_id={id}
107
project_id={project_id}
108
time={syncState?.get("time")}
109
syncing={
110
requestedServer?.get("state") == "running" &&
111
!syncState?.get("extra") &&
112
(syncState?.get("progress") ?? 100) <
113
80 /* 80 because the last per for read cache is not sync and sometimes gets stuck */
114
}
115
>
116
Sync
117
</SyncButton>
118
)}
119
{progress < 100 && (
120
<Tooltip title={"Make sure the compute server is running."}>
121
<div
122
onClick={() => {
123
setShowDetails(showDetails === true ? false : true);
124
}}
125
style={{
126
whiteSpace: "nowrap",
127
padding: "2.5px 5px",
128
background: "darkred",
129
color: "white",
130
height: "24px",
131
}}
132
>
133
NOT CONNECTED
134
</div>
135
</Tooltip>
136
)}
137
<Tooltip
138
mouseEnterDelay={0.9}
139
title={
140
<>
141
{progress == 100 ? "Running on " : "Opening on "}{" "}
142
<Inline id={requestedId} computeServer={requestedServer} />.
143
</>
144
}
145
>
146
<div
147
onClick={() => {
148
setShowDetails(showDetails === true ? false : true);
149
}}
150
style={{
151
height: "24px",
152
cursor: "pointer",
153
padding: "2px 5px",
154
background: requestedServer?.get("color") ?? "#fff",
155
color: avatar_fontcolor(requestedServer?.get("color") ?? "#fff"),
156
width: "100%",
157
overflow: "hidden",
158
textAlign: "center",
159
}}
160
>
161
{progress < 100 ? `${progress}% - ` : ""}
162
<div style={{ display: "inline-block" }}>
163
<div style={{ display: "flex" }}>
164
<div
165
style={{
166
maxWidth: "30ex",
167
textOverflow: "ellipsis",
168
overflow: "hidden",
169
whiteSpace: "nowrap",
170
marginRight: "5px",
171
}}
172
>
173
{requestedServer?.get("title") ?? "Loading..."}
174
</div>
175
(Id: {requestedServer?.get("project_specific_id")})
176
</div>
177
</div>
178
<DisplayImage
179
style={{
180
marginLeft: "10px",
181
borderLeft: "1px solid black",
182
paddingLeft: "10px",
183
}}
184
configuration={requestedServer?.get("configuration")?.toJS()}
185
/>
186
</div>
187
</Tooltip>
188
<Menu
189
fontSize={"13pt"}
190
size="small"
191
style={{ marginTop: "1px", height: "10px" }}
192
id={requestedId}
193
project_id={project_id}
194
/>
195
</div>
196
);
197
198
const server: ComputeServerUserInfo | undefined = computeServers
199
?.get(`${requestedId}`)
200
?.toJS();
201
const { progress, message, status } = getProgress(
202
server,
203
account_id,
204
id,
205
requestedId,
206
);
207
if (!showDetails) {
208
if (showDetails == null && progress < 100) {
209
setShowDetails(true);
210
}
211
return topBar(progress);
212
}
213
214
return (
215
<div
216
className="smc-vfill"
217
style={{ flex: 3, minHeight: "300px", background: "white" }}
218
>
219
<div>{topBar(progress)}</div>
220
<div
221
className="smc-vfill"
222
style={{
223
border: `1px solid #ccc`,
224
borderRadius: "5px",
225
margin: "15px",
226
padding: "5px",
227
boxShadow: "rgba(33, 33, 33, 0.5) 1px 5px 7px",
228
marginTop: "0px",
229
overflow: "auto",
230
}}
231
>
232
<div
233
style={{
234
textAlign: "center",
235
}}
236
>
237
<Space style={{ width: "100%", margin: "15px 0" }}>
238
<Button
239
size="large"
240
type="text"
241
onClick={() => setShowDetails(false)}
242
>
243
<Icon name="times" /> Hide
244
</Button>
245
<Progress
246
type="circle"
247
trailColor="#e6f4ff"
248
percent={progress}
249
strokeWidth={14}
250
size={42}
251
/>
252
<Alert
253
style={{ margin: "0 15px" }}
254
type="info"
255
message={
256
<>
257
{message}{" "}
258
{progress < 100 && status != "exception" ? (
259
<Spin style={{ marginLeft: "15px" }} />
260
) : undefined}
261
</>
262
}
263
/>
264
</Space>
265
</div>
266
{server != null && (
267
<ComputeServer
268
editable={account_id == server.account_id}
269
server={server}
270
/>
271
)}
272
</div>
273
</div>
274
);
275
}
276
277
function getProgress(
278
server: ComputeServerUserInfo | undefined,
279
account_id,
280
id,
281
requestedId,
282
): {
283
progress: number;
284
message: string;
285
status: "exception" | "active" | "normal" | "success";
286
} {
287
if (requestedId == 0) {
288
return {
289
progress: 50,
290
message: "Moving back to project...",
291
status: "active",
292
};
293
}
294
if (server == null) {
295
return {
296
progress: 0,
297
message: "Server does not exist. Please select a different server.",
298
status: "exception",
299
};
300
}
301
if (server.deleted) {
302
return {
303
progress: 0,
304
message:
305
"Server was deleted. Please select a different server or undelete it.",
306
status: "exception",
307
};
308
}
309
310
if (
311
server.account_id != account_id &&
312
server.state != "running" &&
313
server.state != "starting"
314
) {
315
return {
316
progress: 0,
317
message:
318
"This is not your compute server, and it is not running. Only the owner of a compute server can start it.",
319
status: "exception",
320
};
321
}
322
323
// below here it isn't our server, it is running.
324
325
if (server.state == "deprovisioned") {
326
return {
327
progress: 0,
328
message:
329
"Please start the compute server by clicking the Start button below.",
330
status: "exception",
331
};
332
}
333
334
if (server.state == "off") {
335
return {
336
progress: 10,
337
message:
338
"Please start the compute server by clicking the Start button below.",
339
status: "exception",
340
};
341
}
342
if (server.state == "suspended") {
343
return {
344
progress: 15,
345
message:
346
"Please resume the compute server by clicking the Resume button below.",
347
status: "exception",
348
};
349
}
350
351
if (server.state != "starting" && server.state != "running") {
352
return {
353
progress: 25,
354
message: "Please start the compute server.",
355
status: "exception",
356
};
357
}
358
359
if (server.state == "starting") {
360
return {
361
progress: 40,
362
message: "Compute server is starting.",
363
status: "active",
364
};
365
}
366
367
// below it is running
368
369
const computeIsLive = server.detailed_state?.compute?.state == "ready";
370
if (computeIsLive) {
371
if (id == requestedId) {
372
return {
373
progress: 100,
374
message: "Compute server is fully connected!",
375
status: "success",
376
};
377
} else {
378
return {
379
progress: 90,
380
message:
381
"Compute server is connected and should attach to this file soon...",
382
status: "success",
383
};
384
}
385
}
386
const filesystemIsLive =
387
server.detailed_state?.["filesystem-sync"]?.state == "ready";
388
const computeIsRecent = isRecent(server.detailed_state?.compute?.time);
389
const filesystemIsRecent = isRecent(
390
server.detailed_state?.["filesystem-sync"]?.time,
391
);
392
if (filesystemIsRecent) {
393
return {
394
progress: 70,
395
message: "Waiting for filesystem to connect.",
396
status: "normal",
397
};
398
}
399
if (filesystemIsLive) {
400
if (computeIsRecent) {
401
return {
402
progress: 80,
403
message: "Waiting for compute to connect.",
404
status: "normal",
405
};
406
}
407
}
408
409
return {
410
progress: 50,
411
message:
412
"Compute server is running, but filesystem and compute components aren't connected. Waiting...",
413
status: "active",
414
};
415
}
416
417
function isRecent(expire = 0) {
418
return Date.now() - expire < 60 * 1000;
419
}
420
421