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