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