diff --git a/lib/controllers/file-controller.js b/lib/controllers/file-controller.js index 83e1ab5..e5e7075 100644 --- a/lib/controllers/file-controller.js +++ b/lib/controllers/file-controller.js @@ -4,6 +4,7 @@ import { listServerFiles, removeServerItem, uploadServerItem, + moveServerItems, } from "../k8s/server-files.js"; import { sendError } from "../util/ExpressClientError.js"; import { checkAuthorization } from "../database/queries/server-queries.js"; @@ -79,3 +80,18 @@ export async function getItem(req, res) { }) .catch(sendError(res)); } + +export async function moveItems(req, res) { + const serverSpec = req.body; + if (!serverSpec.id) return res.status(400).send("Server id missing!"); + if (!serverSpec.destination) + return res.status(400).send("Destination required!"); + if (!serverSpec.origin) return res.status(400).send("Origin required!"); + if (!serverSpec.files || !Array.isArray(serverSpec.files)) + return res.status(400).send("Files required!"); + const authorized = await checkAuthorization(serverSpec.id, req.cairoId); + if (!authorized) return res.sendStatus(403); + moveServerItems(serverSpec) + .then(() => res.sendStatus(200)) + .catch(sendError(res)); +} diff --git a/lib/k8s/server-files.js b/lib/k8s/server-files.js index ce29a02..efa32d8 100644 --- a/lib/k8s/server-files.js +++ b/lib/k8s/server-files.js @@ -86,12 +86,22 @@ export async function uploadServerItem(serverSpec, file) { }).catch(handleError); } -export async function getServerItem(serverSpec, writableStream) { +export async function getServerItem(serverSpec) { const { path } = serverSpec; - const ds = new Transform({ transform: (c, e, cb) => cb(null, c) }); + const ds = new Transform({ transform: (c, _e, cb) => cb(null, c) }); pathSecurityCheck(path); const ftpTransfer = useServerFtp(serverSpec, async (c) => { await c.downloadTo(ds, path); }).catch(handleError); return { ds, ftpTransfer }; } + +export async function moveServerItems(serverSpec) { + const { destination, origin, files } = serverSpec; + useServerFtp(serverSpec, async (c) => + Promise.all( + files.map((f) => c.rename(`${origin}/${f}`, `${destination}/${f}`)), + ), + ).catch(handleError); + return files; +} diff --git a/lib/routes/files-route.js b/lib/routes/files-route.js index 1092a81..d82f780 100644 --- a/lib/routes/files-route.js +++ b/lib/routes/files-route.js @@ -6,6 +6,7 @@ import { listFiles, uploadItem, getItem, + moveItems, } from "../controllers/file-controller.js"; import cairoAuthMiddleware from "./middlewares/auth-middleware.js"; @@ -18,6 +19,7 @@ router.post("/list", listFiles); router.post("/folder", createFolder); router.delete("/item", deleteItem); router.post("/item", getItem); +router.post("/move", moveItems); router.post("/upload", multerMiddleware.single("file"), uploadItem); export default router; diff --git a/lib/storage/s3-integration.js b/lib/storage/s3-integration.js deleted file mode 100644 index 34ed5d5..0000000 --- a/lib/storage/s3-integration.js +++ /dev/null @@ -1,34 +0,0 @@ -import multer from "multer"; -import multerS3 from "multer-s3"; -import AWS from "aws-sdk"; - -// Environment Variables -const { - MCL_S3_ENDPOINT: s3Endpoint, - MCL_S3_ACCESS_KEY_ID: s3KeyId, - MCL_S3_ACCESS_KEY: s3Key, -} = process.env; - -export const mcl = "mcl"; - -export const s3 = new AWS.S3({ - endpoint: s3Endpoint, - accessKeyId: s3KeyId, - secretAccessKey: s3Key, - sslEnabled: true, - s3ForcePathStyle: true, -}); - -const storage = multerS3({ - s3, - bucket, - contentType: multerS3.AUTO_CONTENT_TYPE, - metadata: (req, file, cb) => { - cb(null, { fieldName: file.fieldname }); - }, - key: (req, file, cb) => { - cb(null, Date.now().toString()); - }, -}); - -export const upload = multer({ storage }); diff --git a/src/components/files/ChonkyStyledFileBrowser.jsx b/src/components/files/ChonkyStyledFileBrowser.jsx deleted file mode 100644 index 3273684..0000000 --- a/src/components/files/ChonkyStyledFileBrowser.jsx +++ /dev/null @@ -1,42 +0,0 @@ -// ChonkyFullFileBrowser.tsx -import { forwardRef, memo } from "react"; -import { - StylesProvider, - createGenerateClassName, -} from "@material-ui/core/styles"; - -import { - FileBrowser, - FileList, - FileContextMenu, - FileNavbar, - FileToolbar, - setChonkyDefaults, - FileBrowserHandle, - FileBrowserProps, -} from "chonky"; - -import { ChonkyIconFA } from "chonky-icon-fontawesome"; - -setChonkyDefaults({ iconComponent: ChonkyIconFA }); - -const muiJSSClassNameGenerator = createGenerateClassName({ - // Seed property is used to add a prefix classes generated by material ui. - seed: "chonky", -}); - -export default memo( - forwardRef((props, ref) => { - const { onScroll } = props; - return ( - - - - - - - - - ); - }), -); diff --git a/src/components/files/MineclusterFiles.jsx b/src/components/files/MineclusterFiles.jsx index e41652e..c853707 100644 --- a/src/components/files/MineclusterFiles.jsx +++ b/src/components/files/MineclusterFiles.jsx @@ -1,5 +1,8 @@ import { useState, useEffect, useMemo, useRef } from "react"; import Box from "@mui/material/Box"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; + import { FileBrowser, FileContextMenu, @@ -17,7 +20,7 @@ import { deleteServerItem, getServerItem, } from "@mcl/queries"; -import { previewServerItem } from "../../util/queries"; +import { moveServerItems, previewServerItem } from "../../util/queries"; import { cairoAuthHeader } from "@mcl/util/auth.js"; import { supportedFileTypes } from "./FilePreview.jsx"; @@ -32,6 +35,7 @@ export default function MineclusterFiles(props) { ChonkyActions.DownloadFiles, ChonkyActions.CopyFiles, ChonkyActions.DeleteFiles, + ChonkyActions.MoveFiles, ], [], ); @@ -132,6 +136,15 @@ export default function MineclusterFiles(props) { ); } + function moveFile(movePayload) { + const { files: filePayload, destination: destinationPayload } = movePayload; + if (!destinationPayload.isDir || filePayload.length === 0) return; + const files = filePayload.map((f) => f.name); + const dest = destinationPayload.id; + const origin = dirStack.join("/"); + moveServerItems(serverId, files, dest, origin).then(updateFiles); + } + function fileClick(chonkyEvent) { const { id: clickEvent, payload } = chonkyEvent; if (clickEvent === "open_parent_folder") return openParentFolder(); @@ -141,6 +154,7 @@ export default function MineclusterFiles(props) { return downloadFiles(chonkyEvent.state.selectedFilesForAction); if (clickEvent === "delete_files") return deleteItems(chonkyEvent.state.selectedFilesForAction); + if (clickEvent === "move_files") return moveFile(payload); if (clickEvent !== "open_files") return; // console.log(clickEvent); openItem(payload); } diff --git a/src/util/queries.js b/src/util/queries.js index 14a7035..f2d65b1 100644 --- a/src/util/queries.js +++ b/src/util/queries.js @@ -64,6 +64,13 @@ export const createServerFolder = async (serverId, path) => export const deleteServerItem = async (serverId, path, isDir) => fetchApiCore("/files/item", { id: serverId, path, isDir }, "DELETE"); +export const moveServerItems = async (serverId, files, destination, origin) => + fetchApiCore( + "/files/move", + { id: serverId, files, destination, origin }, + "POST", + ); + export async function previewServerItem(serverId, path) { const resp = await fetchApiCore("/files/item", { id: serverId, path }); if (resp.status !== 200) return console.log("AHHHH");