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/cloud-filesystem/cloud-filesystems.tsx
Views: 687
1
/*
2
Component that shows a list of all cloud file systems:
3
4
- in a project
5
- associated to an account
6
*/
7
8
import { useEffect, useRef, useState } from "react";
9
import { editCloudFilesystem, getCloudFilesystems } from "./api";
10
import useCounter from "@cocalc/frontend/app-framework/counter-hook";
11
import ShowError from "@cocalc/frontend/components/error";
12
import type { CloudFilesystem as CloudFilesystemType } from "@cocalc/util/db-schema/cloud-filesystems";
13
import { Button, Spin } from "antd";
14
import CreateCloudFilesystem from "./create";
15
import CloudFilesystem from "./cloud-filesystem";
16
import { Icon } from "@cocalc/frontend/components/icon";
17
import { A } from "@cocalc/frontend/components/A";
18
import RefreshButton from "@cocalc/frontend/components/refresh";
19
import { cmp } from "@cocalc/util/misc";
20
import {
21
SortableList,
22
SortableItem,
23
DragHandle,
24
} from "@cocalc/frontend/components/sortable-list";
25
// import {
26
// get_local_storage,
27
// set_local_storage,
28
// } from "@cocalc/frontend/misc/local-storage";
29
import { useTypedRedux } from "@cocalc/frontend/app-framework";
30
31
export type CloudFilesystems = {
32
[id: number]: CloudFilesystemType;
33
};
34
35
interface Props {
36
// if not given, shows global list across all projects you collab on
37
project_id?: string;
38
}
39
40
export default function CloudFilesystems({ project_id }: Props) {
41
const { val: counter, inc: refresh } = useCounter();
42
const [error, setError] = useState<string>("");
43
const [refreshing, setRefreshing] = useState<boolean>(false);
44
const [cloudFilesystems, setCloudFilesystems] =
45
useState<CloudFilesystems | null>(null);
46
const scheduledRefresh = useRef<boolean>(false);
47
48
// todo -- other sorts later
49
// const [sortBy, setSortBy] = useState<
50
// "id" | "title" | "custom" | "edited" | "state"
51
// >((get_local_storage(`cloudfs-${project_id}`) ?? "custom") as any);
52
const sortBy: string = "custom";
53
54
const [ids, setIds] = useState<number[]>([]);
55
const account_id = useTypedRedux("account", "account_id");
56
57
const updateIds = (cloudFilesystems: CloudFilesystems | null) => {
58
if (cloudFilesystems == null) {
59
setIds([]);
60
return;
61
}
62
const c = Object.values(cloudFilesystems);
63
c.sort((x, y) => {
64
const d = -cmp(x.position ?? 0, y.position ?? 0);
65
if (d) return d;
66
return -cmp(x.id ?? 0, y.id ?? 0);
67
});
68
const ids = c.map(({ id }) => id);
69
setIds(ids);
70
};
71
72
useEffect(() => {
73
(async () => {
74
try {
75
setRefreshing(true);
76
const cloudFilesystems: CloudFilesystems = {};
77
for (const cloudFilesystem of await getCloudFilesystems({
78
project_id,
79
})) {
80
cloudFilesystems[cloudFilesystem.id] = cloudFilesystem;
81
}
82
setCloudFilesystems(cloudFilesystems);
83
updateIds(cloudFilesystems);
84
85
if (!scheduledRefresh.current) {
86
// if a file system is currently being deleted, we refresh
87
// again in 30s.
88
for (const { deleting } of Object.values(cloudFilesystems)) {
89
if (deleting) {
90
setTimeout(() => {
91
scheduledRefresh.current = false;
92
refresh();
93
}, 30000);
94
scheduledRefresh.current = true;
95
break;
96
}
97
}
98
}
99
} catch (err) {
100
setError(`${err}`);
101
} finally {
102
setRefreshing(false);
103
}
104
})();
105
}, [counter]);
106
107
if (cloudFilesystems == null) {
108
return <Spin />;
109
}
110
111
const renderItem = (id) => {
112
const cloudFilesystem = cloudFilesystems[id];
113
114
return (
115
<div style={{ display: "flex" }}>
116
{sortBy == "custom" && account_id == cloudFilesystem.account_id && (
117
<div
118
style={{
119
fontSize: "20px",
120
color: "#888",
121
display: "flex",
122
justifyContent: "center",
123
flexDirection: "column",
124
marginRight: "5px",
125
}}
126
>
127
<DragHandle id={id} />
128
</div>
129
)}
130
<CloudFilesystem
131
style={{ marginBottom: "10px" }}
132
key={`${id}`}
133
cloudFilesystem={cloudFilesystem}
134
refresh={refresh}
135
showProject={project_id == null}
136
editable={account_id == cloudFilesystem.account_id}
137
/>
138
</div>
139
);
140
};
141
142
const v: JSX.Element[] = [];
143
for (const id of ids) {
144
v.push(
145
<SortableItem key={`${id}`} id={id}>
146
{renderItem(id)}
147
</SortableItem>,
148
);
149
}
150
151
return (
152
<div>
153
<RefreshButton
154
refresh={refresh}
155
style={{ position: "absolute", right: 0 }}
156
refreshing={refreshing}
157
/>
158
<h2 style={{ textAlign: "center" }}>Cloud File Systems</h2>
159
<div style={{ textAlign: "center" }}>
160
<Button
161
href="https://youtu.be/zYoldE2yS3I"
162
target="_new"
163
style={{ marginRight: "15px" }}
164
>
165
<Icon name="youtube" style={{ color: "red" }} />
166
Short Demo
167
</Button>
168
<Button
169
href="https://youtu.be/uk5eA5piQEo"
170
target="_new"
171
style={{ marginRight: "15px" }}
172
>
173
<Icon name="youtube" style={{ color: "red" }} />
174
Long Demo
175
</Button>
176
<Button
177
href="https://doc.cocalc.com/cloud_file_system.html"
178
target="_new"
179
>
180
<Icon name="external-link" />
181
Docs
182
</Button>
183
</div>
184
<p
185
style={{
186
maxWidth: "700px",
187
margin: "15px auto",
188
fontSize: "11pt",
189
color: "#666",
190
}}
191
>
192
<A href="https://doc.cocalc.com/cloud_file_system.html">
193
CoCalc Cloud File Systems{" "}
194
</A>
195
are scalable distributed POSIX shared file systems with fast local
196
caching. Use them simultaneously from all compute servers in this
197
project. There are no limits on how much data you can store. You do not
198
specify the size of a cloud file system in advance. The cost per GB is
199
typically much less than a compute server disk, but you pay network
200
usage and operations.
201
</p>
202
203
<div style={{ margin: "5px 0" }}>
204
{project_id
205
? ""
206
: "All Cloud File Systems you own across your projects are listed below."}
207
</div>
208
<ShowError error={error} setError={setError} />
209
{project_id != null && cloudFilesystems != null && (
210
<CreateCloudFilesystem
211
project_id={project_id}
212
cloudFilesystems={cloudFilesystems}
213
refresh={refresh}
214
/>
215
)}
216
<SortableList
217
disabled={sortBy != "custom"}
218
items={ids}
219
Item={({ id }) => renderItem(id)}
220
onDragStop={(oldIndex, newIndex) => {
221
let position;
222
if (newIndex == ids.length - 1) {
223
const last = cloudFilesystems[ids[ids.length - 1]];
224
// putting it at the bottom, so subtract 1 from very bottom position
225
position = (last.position ?? last.id) - 1;
226
} else {
227
// putting it above what was at position newIndex.
228
if (newIndex == 0) {
229
// very top
230
const first = cloudFilesystems[ids[0]];
231
// putting it at the bottom, so subtract 1 from very bottom position
232
position = (first.position ?? first.id) + 1;
233
} else {
234
// not at the very top: between two
235
let x, y;
236
if (newIndex > oldIndex) {
237
x = cloudFilesystems[ids[newIndex]];
238
y = cloudFilesystems[ids[newIndex + 1]];
239
} else {
240
x = cloudFilesystems[ids[newIndex - 1]];
241
y = cloudFilesystems[ids[newIndex]];
242
}
243
244
const x0 = x.position ?? x.id;
245
const y0 = y.position ?? y.id;
246
// TODO: yes, positions could get too close like with compute servers
247
position = (x0 + y0) / 2;
248
}
249
}
250
// update UI
251
const id = ids[oldIndex];
252
const cur = { ...cloudFilesystems[id], position };
253
const cloudFilesystems1 = { ...cloudFilesystems, [id]: cur };
254
setCloudFilesystems(cloudFilesystems1);
255
updateIds(cloudFilesystems1);
256
// update Database
257
(async () => {
258
try {
259
await editCloudFilesystem({ id, position });
260
} catch (err) {
261
console.warn(err);
262
}
263
})();
264
}}
265
>
266
{v}
267
</SortableList>
268
</div>
269
);
270
}
271
272