Path: blob/master/src/packages/frontend/compute/cloud-filesystem/cloud-filesystems.tsx
1503 views
/*1Component that shows a list of all cloud file systems:23- in a project4- associated to an account5*/67import { useEffect, useRef, useState } from "react";8import { editCloudFilesystem, getCloudFilesystems } from "./api";9import useCounter from "@cocalc/frontend/app-framework/counter-hook";10import ShowError from "@cocalc/frontend/components/error";11import type { CloudFilesystem as CloudFilesystemType } from "@cocalc/util/db-schema/cloud-filesystems";12import { Button, Spin } from "antd";13import CreateCloudFilesystem from "./create";14import CloudFilesystem from "./cloud-filesystem";15import { Icon } from "@cocalc/frontend/components/icon";16import { A } from "@cocalc/frontend/components/A";17import RefreshButton from "@cocalc/frontend/components/refresh";18import { cmp } from "@cocalc/util/misc";19import {20SortableList,21SortableItem,22DragHandle,23} from "@cocalc/frontend/components/sortable-list";24// import {25// get_local_storage,26// set_local_storage,27// } from "@cocalc/frontend/misc/local-storage";28import { useTypedRedux } from "@cocalc/frontend/app-framework";2930export type CloudFilesystems = {31[id: number]: CloudFilesystemType;32};3334interface Props {35// if not given, shows global list across all projects you collab on36project_id?: string;37noTitle?: boolean;38}3940export default function CloudFilesystems({ project_id, noTitle }: Props) {41const { val: counter, inc: refresh } = useCounter();42const [error, setError] = useState<string>("");43const [refreshing, setRefreshing] = useState<boolean>(false);44const [cloudFilesystems, setCloudFilesystems] =45useState<CloudFilesystems | null>(null);46const scheduledRefresh = useRef<boolean>(false);4748// todo -- other sorts later49// const [sortBy, setSortBy] = useState<50// "id" | "title" | "custom" | "edited" | "state"51// >((get_local_storage(`cloudfs-${project_id}`) ?? "custom") as any);52const sortBy: string = "custom";5354const [ids, setIds] = useState<number[]>([]);55const account_id = useTypedRedux("account", "account_id");5657const updateIds = (cloudFilesystems: CloudFilesystems | null) => {58if (cloudFilesystems == null) {59setIds([]);60return;61}62const c = Object.values(cloudFilesystems);63c.sort((x, y) => {64const d = -cmp(x.position ?? 0, y.position ?? 0);65if (d) return d;66return -cmp(x.id ?? 0, y.id ?? 0);67});68const ids = c.map(({ id }) => id);69setIds(ids);70};7172useEffect(() => {73(async () => {74try {75setRefreshing(true);76const cloudFilesystems: CloudFilesystems = {};77for (const cloudFilesystem of await getCloudFilesystems({78project_id,79})) {80cloudFilesystems[cloudFilesystem.id] = cloudFilesystem;81}82setCloudFilesystems(cloudFilesystems);83updateIds(cloudFilesystems);8485if (!scheduledRefresh.current) {86// if a file system is currently being deleted, we refresh87// again in 30s.88for (const { deleting } of Object.values(cloudFilesystems)) {89if (deleting) {90setTimeout(() => {91scheduledRefresh.current = false;92refresh();93}, 30000);94scheduledRefresh.current = true;95break;96}97}98}99} catch (err) {100setError(`${err}`);101} finally {102setRefreshing(false);103}104})();105}, [counter]);106107if (cloudFilesystems == null) {108return <Spin />;109}110111const renderItem = (id) => {112const cloudFilesystem = cloudFilesystems[id];113114return (115<div style={{ display: "flex" }}>116{sortBy == "custom" && account_id == cloudFilesystem.account_id && (117<div118style={{119fontSize: "20px",120color: "#888",121display: "flex",122justifyContent: "center",123flexDirection: "column",124marginRight: "5px",125}}126>127<DragHandle id={id} />128</div>129)}130<CloudFilesystem131style={{ marginBottom: "10px" }}132key={`${id}`}133cloudFilesystem={cloudFilesystem}134refresh={refresh}135showProject={project_id == null}136editable={account_id == cloudFilesystem.account_id}137/>138</div>139);140};141142const v: React.JSX.Element[] = [];143for (const id of ids) {144v.push(145<SortableItem key={`${id}`} id={id}>146{renderItem(id)}147</SortableItem>,148);149}150151return (152<div>153<RefreshButton154refresh={refresh}155style={{ float: "right" }}156refreshing={refreshing}157/>158{!noTitle && <h2 style={{ textAlign: "center" }}>Cloud File Systems</h2>}159<div160style={{161margin: "15px auto 30px auto",162fontSize: "11pt",163color: "#666",164}}165>166<A href="https://doc.cocalc.com/cloud_file_system.html">167CoCalc Cloud File Systems{" "}168</A>169are scalable distributed POSIX shared file systems with fast local170caching. Use them simultaneously from all compute servers in this171project. There are no limits on how much data you can store. You do not172specify the size of a cloud file system in advance. The cost per GB is173typically much less than a compute server disk, but you pay network174usage and operations.175<div style={{ float: "right" }}>176<Button177href="https://youtu.be/zYoldE2yS3I"178target="_new"179type="link"180style={{ marginRight: "15px" }}181>182<Icon name="youtube" style={{ color: "red" }} />183Short Demo184</Button>185<Button186href="https://youtu.be/uk5eA5piQEo"187target="_new"188type="link"189style={{ marginRight: "15px" }}190>191<Icon name="youtube" style={{ color: "red" }} />192Long Demo193</Button>194<Button195href="https://doc.cocalc.com/cloud_file_system.html"196target="_new"197type="link"198>199<Icon name="external-link" />200Docs201</Button>202</div>203</div>204205<div style={{ margin: "5px 0" }}>206{project_id207? ""208: "All Cloud File Systems you own across your projects are listed below."}209</div>210<ShowError error={error} setError={setError} />211{project_id != null && cloudFilesystems != null && (212<CreateCloudFilesystem213project_id={project_id}214cloudFilesystems={cloudFilesystems}215refresh={refresh}216/>217)}218<SortableList219disabled={sortBy != "custom"}220items={ids}221Item={({ id }) => renderItem(id)}222onDragStop={(oldIndex, newIndex) => {223let position;224if (newIndex == ids.length - 1) {225const last = cloudFilesystems[ids[ids.length - 1]];226// putting it at the bottom, so subtract 1 from very bottom position227position = (last.position ?? last.id) - 1;228} else {229// putting it above what was at position newIndex.230if (newIndex == 0) {231// very top232const first = cloudFilesystems[ids[0]];233// putting it at the bottom, so subtract 1 from very bottom position234position = (first.position ?? first.id) + 1;235} else {236// not at the very top: between two237let x, y;238if (newIndex > oldIndex) {239x = cloudFilesystems[ids[newIndex]];240y = cloudFilesystems[ids[newIndex + 1]];241} else {242x = cloudFilesystems[ids[newIndex - 1]];243y = cloudFilesystems[ids[newIndex]];244}245246const x0 = x.position ?? x.id;247const y0 = y.position ?? y.id;248// TODO: yes, positions could get too close like with compute servers249position = (x0 + y0) / 2;250}251}252// update UI253const id = ids[oldIndex];254const cur = { ...cloudFilesystems[id], position };255const cloudFilesystems1 = { ...cloudFilesystems, [id]: cur };256setCloudFilesystems(cloudFilesystems1);257updateIds(cloudFilesystems1);258// update Database259(async () => {260try {261await editCloudFilesystem({ id, position });262} catch (err) {263console.warn(err);264}265})();266}}267>268{v}269</SortableList>270</div>271);272}273274275