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/bucket.tsx
Views: 687
1
import { getCity, getDataStoragePriceRange } from "./util";
2
import { Alert, Checkbox, Select, Spin } from "antd";
3
import {
4
GOOGLE_CLOUD_BUCKET_STORAGE_CLASSES,
5
GOOGLE_CLOUD_BUCKET_STORAGE_CLASSES_DESC,
6
GOOGLE_CLOUD_MULTIREGIONS,
7
GOOGLE_CLOUD_REGIONS,
8
DEFAULT_CONFIGURATION,
9
} from "@cocalc/util/db-schema/cloud-filesystems";
10
import { useEffect, useMemo, useState } from "react";
11
import { A, Icon } from "@cocalc/frontend/components";
12
import { EXTERNAL, NO_CHANGE } from "./create";
13
import { getRecentRegions } from "./regions";
14
import { currency } from "@cocalc/util/misc";
15
import { markup } from "@cocalc/util/compute/cloud/google-cloud/compute-cost";
16
import { useGoogleCloudPriceData } from "@cocalc/frontend/compute/api";
17
import { filterOption } from "@cocalc/frontend/compute/util";
18
19
export function BucketStorageClass({ configuration, setConfiguration }) {
20
const [priceData] = useGoogleCloudPriceData();
21
22
if (priceData == null) {
23
return <Spin />;
24
}
25
return (
26
<div style={{ marginTop: "10px" }}>
27
<b style={{ fontSize: "13pt" }}>
28
<A href="https://cloud.google.com/storage/docs/storage-classes">
29
{EXTERNAL} Storage Class
30
</A>
31
</b>
32
<br />
33
The bucket storage class determines how much it costs to store and access
34
your data, but has minimal impact on speed. You can change this later, but
35
the change only impacts newly saved data. With nearline, coldline and
36
archive storage classes, you have to pay to store any data for at least 30
37
days, 90 days, and 1 year, respectively.
38
<Alert
39
style={{ margin: "10px" }}
40
showIcon
41
type="info"
42
message={`Recommendation: Autoclass`}
43
description={
44
<>
45
Unless you understand why a different choice is better for your
46
data, use{" "}
47
<A href="https://cloud.google.com/storage/docs/autoclass">
48
autoclass
49
</A>
50
, because the management fee is minimal, there is no extra early
51
delete fee, and data you don't touch gets automatically stored
52
efficiently, down to a few dollars per TERABYTE after a year. Data
53
you frequently access costs the same as standard storage. The
54
monthly management fee is only{" "}
55
{currency(
56
markup({
57
cost: 2.5,
58
priceData,
59
}),
60
)}{" "}
61
per million blocks (there are 65,536 blocks of size 16 MB in 1 TB of
62
data).
63
</>
64
}
65
/>
66
<Select
67
style={{ width: "100%", marginTop: "5px", height: "auto" }}
68
options={GOOGLE_CLOUD_BUCKET_STORAGE_CLASSES.map(
69
(bucket_storage_class) => {
70
const { min, max } = getDataStoragePriceRange({
71
...configuration,
72
priceData,
73
bucket_storage_class,
74
});
75
return {
76
value: bucket_storage_class,
77
key: bucket_storage_class,
78
label: (
79
<div>
80
<div>
81
{GOOGLE_CLOUD_BUCKET_STORAGE_CLASSES_DESC[
82
bucket_storage_class
83
]?.desc ?? bucket_storage_class}
84
</div>
85
<div style={{ fontFamily: "monospace" }}>
86
{min ? currency(min, 5) : null}
87
{min != max && min && max ? ` - ${currency(max, 5)}` : null}
88
{min && max ? " per GB per month at rest" : null}
89
</div>
90
</div>
91
),
92
};
93
},
94
)}
95
value={configuration.bucket_storage_class}
96
onChange={(bucket_storage_class) =>
97
setConfiguration({ ...configuration, bucket_storage_class })
98
}
99
/>
100
{configuration.bucket_storage_class.includes("auto") && (
101
<Alert
102
style={{ margin: "10px 0" }}
103
showIcon
104
type="warning"
105
message={
106
<>
107
<A href="https://cloud.google.com/storage/docs/autoclass">
108
Autoclass buckets
109
</A>{" "}
110
incur a monthly management fee of{" "}
111
{currency(
112
markup({
113
cost: 2.5,
114
priceData,
115
}),
116
)}{" "}
117
for every million objects stored in them.
118
</>
119
}
120
/>
121
)}
122
</div>
123
);
124
}
125
126
export function BucketLocation({ configuration, setConfiguration }) {
127
const [multiregion, setMultiregion] = useState<boolean>(
128
configuration.bucket_location &&
129
!configuration.bucket_location?.includes("-"),
130
);
131
const [priceData] = useGoogleCloudPriceData();
132
133
const [recentRegions, setRecentRegions] = useState<string[] | null>(null);
134
useEffect(() => {
135
if (!configuration.project_id) return;
136
(async () => {
137
const recent = await getRecentRegions(configuration.project_id);
138
setRecentRegions(recent);
139
})();
140
}, [configuration.project_id]);
141
142
useEffect(() => {
143
if (!configuration.bucket_location && recentRegions != null) {
144
let bucket_location;
145
if (multiregion) {
146
if (recentRegions[0]?.startsWith("europe")) {
147
bucket_location = "eu";
148
} else if (recentRegions[0]?.startsWith("asia")) {
149
bucket_location = "asia";
150
} else {
151
bucket_location = "us";
152
}
153
} else {
154
bucket_location =
155
recentRegions[0] ?? DEFAULT_CONFIGURATION.bucket_location;
156
}
157
setConfiguration({
158
...configuration,
159
bucket_location,
160
});
161
}
162
}, [recentRegions, configuration.bucket_location]);
163
164
const options = useMemo(() => {
165
let regions = multiregion
166
? GOOGLE_CLOUD_MULTIREGIONS
167
: GOOGLE_CLOUD_REGIONS;
168
169
if (multiregion) {
170
if (recentRegions?.[0]?.startsWith("europe")) {
171
regions = ["eu"].concat(regions.filter((x) => x != "eu"));
172
} else if (recentRegions?.[0]?.startsWith("asia")) {
173
regions = ["asia"].concat(regions.filter((x) => x != "asia"));
174
}
175
}
176
const options = regions.map((region) => {
177
let location;
178
const { min, max } = getDataStoragePriceRange({
179
...configuration,
180
priceData,
181
bucket_location: region,
182
});
183
if (multiregion) {
184
location = `${region.toUpperCase()} (Multiregion)`;
185
} else {
186
location = region;
187
}
188
const city = getCity({ region, priceData });
189
const label = (
190
<div style={{ display: "flex" }}>
191
<div style={{ flex: 0.6 }}>
192
{location} ({city})
193
</div>
194
<div style={{ flex: 1, fontFamily: "monospace" }}>
195
{min ? currency(min, 5) : null}
196
{min != max && min && max ? ` - ${currency(max, 5)}` : null}
197
{min && max ? " / GB / month at rest" : null}
198
</div>
199
</div>
200
);
201
return {
202
value: region,
203
label,
204
key: region,
205
price: { min, max },
206
search: `${city} ${location}`,
207
};
208
});
209
if (!multiregion && (recentRegions?.length ?? 0) > 0) {
210
const z = new Set(recentRegions);
211
const m: { [region: string]: any } = {};
212
for (const x of options) {
213
if (z.has(x.value)) {
214
m[x.value] = x;
215
}
216
}
217
const recent: any[] = [];
218
for (const region of recentRegions ?? []) {
219
recent.push({ ...m[region], key: `recent-${region}` });
220
}
221
222
return [
223
{
224
label: "Your Recent Compute Servers are in These Regions",
225
options: recent,
226
},
227
{ label: "All Regions", options },
228
];
229
}
230
return options as any[];
231
}, [multiregion, priceData, configuration.bucket_storage_class]);
232
233
return (
234
<div style={{ marginTop: "10px" }}>
235
<b style={{ fontSize: "13pt", color: "#666" }}>
236
<A href="https://cloud.google.com/storage/docs/locations">
237
{EXTERNAL} Location
238
</A>
239
</b>
240
{NO_CHANGE}
241
You can use your cloud file system from any compute server in the world,
242
in any cloud or self hosted. However, data transfer and operations are{" "}
243
<b>faster and cheaper</b> when the file system and compute server are in
244
the same region. <br />
245
<div style={{ display: "flex", margin: "10px 0" }}>
246
<Select
247
showSearch
248
style={{ flex: 1, width: "300px", marginTop: "5px" }}
249
options={options}
250
value={configuration.bucket_location}
251
onChange={(bucket_location) => {
252
setConfiguration({ ...configuration, bucket_location });
253
setMultiregion(!bucket_location?.includes("-"));
254
}}
255
optionFilterProp="children"
256
filterOption={filterOption}
257
/>
258
<div
259
style={{
260
display: "flex",
261
textAlign: "center",
262
alignItems: "center",
263
justifyContent: "center",
264
padding: "0 15px",
265
}}
266
>
267
<Checkbox
268
onChange={(e) => {
269
if (e.target.checked) {
270
setMultiregion(true);
271
setConfiguration({
272
...configuration,
273
bucket_location: "",
274
});
275
} else {
276
setMultiregion(false);
277
setConfiguration({
278
...configuration,
279
bucket_location: "",
280
});
281
}
282
}}
283
checked={multiregion}
284
>
285
<Icon name="global" /> Multiregion
286
</Checkbox>
287
</div>
288
</div>
289
</div>
290
);
291
}
292
293