diff --git a/web/src/components/FileUplaod/index.module.scss b/web/src/components/FileUplaod/index.module.scss index 3e465beafd..f308dee996 100644 --- a/web/src/components/FileUplaod/index.module.scss +++ b/web/src/components/FileUplaod/index.module.scss @@ -1,6 +1,6 @@ .formFileUpload { height: 16rem; - width: 28rem; + width: 29rem; max-width: 100%; text-align: center; position: relative; diff --git a/web/src/components/FileUplaod/index.tsx b/web/src/components/FileUplaod/index.tsx index e6ec1ee1cb..f043182041 100644 --- a/web/src/components/FileUplaod/index.tsx +++ b/web/src/components/FileUplaod/index.tsx @@ -1,16 +1,26 @@ -import React from "react"; +import React, { useEffect } from "react"; import clsx from "clsx"; import styles from "./index.module.scss"; // drag drop file component -function FileUpload(props: { onUpload: (files: any) => void }) { - const { onUpload = () => {} } = props; +function FileUpload(props: { onUpload: (files: any) => void, uploadType: "file" | "folder" }) { + const { onUpload = () => {}, uploadType } = props; // drag state const [dragActive, setDragActive] = React.useState(false); // ref const inputRef = React.useRef(null); + useEffect(() => { + if (uploadType === "folder") { + inputRef.current.setAttribute("webkitdirectory", ""); + inputRef.current.setAttribute("directory", ""); + } else { + inputRef.current.removeAttribute("webkitdirectory"); + inputRef.current.removeAttribute("directory"); + } + }, [uploadType]); + // handle drag events const handleDrag = function (e: any) { e.preventDefault(); @@ -57,7 +67,6 @@ function FileUpload(props: { onUpload: (files: any) => void }) { type="file" className={styles.inputFileUpload} multiple={true} - // @ts-ignore onChange={handleChange} /> diff --git a/web/src/hooks/useAwsS3.ts b/web/src/hooks/useAwsS3.ts index 618b0aec06..4f45d0337b 100644 --- a/web/src/hooks/useAwsS3.ts +++ b/web/src/hooks/useAwsS3.ts @@ -27,9 +27,18 @@ function useAwsS3() { Delimiter: "/", }) .promise(); - return res.Contents || []; + + const files = res.Contents || [] + const dirs = res.CommonPrefixes || [] + return [...files, ...dirs]; }; + + const getFileUrl = (bucket: string, key: string) => { + const res = s3.getSignedUrl('getObject', { Bucket: bucket, Key: key }) + return res + } + const uploadFile = async (bucketName: string, key: string, body: any, { contentType }: any) => { const res = await s3 .putObject({ Bucket: bucketName, Key: key, ContentType: contentType, Body: body }) @@ -37,7 +46,12 @@ function useAwsS3() { return res; }; - return { s3, getList, uploadFile }; + const deleteFile = async (bucket: string, key: string) => { + const res = await s3.deleteObject({ Bucket: bucket, Key: key }).promise() + return res + } + + return { s3, getList, uploadFile, getFileUrl, deleteFile }; } export default useAwsS3; diff --git a/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx b/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx index 4e1f50fc7e..53654b47ce 100644 --- a/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx +++ b/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx @@ -24,6 +24,7 @@ import { import IconWrap from "@/components/IconWrap"; import { useBucketCreateMutation, useBucketUpdateMutation } from "../../service"; +import useStorageStore from "../../store"; import { TBucket } from "@/apis/typing"; import useGlobalStore from "@/pages/globalStore"; @@ -31,6 +32,7 @@ import useGlobalStore from "@/pages/globalStore"; function CreateBucketModal(props: { storage?: TBucket }) { const { isOpen, onOpen, onClose } = useDisclosure(); const { t } = useTranslation(); + const store = useStorageStore((store) => store); const { storage } = props; @@ -65,6 +67,7 @@ function CreateBucketModal(props: { storage?: TBucket }) { }); if (!res.error) { + store.setCurrentStorage(res.data); showSuccess("update success."); onClose(); } @@ -72,6 +75,7 @@ function CreateBucketModal(props: { storage?: TBucket }) { res = await bucketCreateMutation.mutateAsync(values); if (!res.error) { + store.setCurrentStorage(res.data); showSuccess("create success."); onClose(); } @@ -89,7 +93,7 @@ function CreateBucketModal(props: { storage?: TBucket }) { setFocus("shortName"); }, 0); }} - tooltip="创建 Bucket" + tooltip={isEdit ? "编辑 Bucket" : "创建 Bucket" } > {isEdit ? : } @@ -97,7 +101,7 @@ function CreateBucketModal(props: { storage?: TBucket }) { - Create Bucket + {isEdit ? "编辑 Bucket" : "创建 Bucket" } diff --git a/web/src/pages/app/storages/mods/CreateFolderModal/index.tsx b/web/src/pages/app/storages/mods/CreateFolderModal/index.tsx index 4ba04d412a..e9ea27b532 100644 --- a/web/src/pages/app/storages/mods/CreateFolderModal/index.tsx +++ b/web/src/pages/app/storages/mods/CreateFolderModal/index.tsx @@ -16,10 +16,14 @@ import { } from "@chakra-ui/react"; import { t } from "i18next"; +import useStorageStore, { TFile } from "../../store"; + function CreateModal() { const { isOpen, onOpen, onClose } = useDisclosure(); + const { prefix, setPrefix } = useStorageStore(); + - const { register, setFocus, handleSubmit } = useForm<{ name: string }>(); + const { register, setFocus, handleSubmit } = useForm<{ prefix: string }>(); return ( <> @@ -28,7 +32,7 @@ function CreateModal() { onClick={() => { onOpen(); setTimeout(() => { - setFocus("name"); + setFocus("prefix"); }, 0); }} > @@ -44,9 +48,9 @@ function CreateModal() { - 文件夹名称 + 文件夹名称 { - console.log("submit"); + onClick={handleSubmit((value) => { + setPrefix(prefix + value.prefix + "/"); + onClose(); })} > {t("Common.Dialog.Confirm")} diff --git a/web/src/pages/app/storages/mods/FileList/index.tsx b/web/src/pages/app/storages/mods/FileList/index.tsx index 8f36043f31..bd15d274eb 100644 --- a/web/src/pages/app/storages/mods/FileList/index.tsx +++ b/web/src/pages/app/storages/mods/FileList/index.tsx @@ -1,45 +1,67 @@ -import { DeleteIcon, DownloadIcon } from "@chakra-ui/icons"; +import { useEffect, useState } from "react"; +import { DeleteIcon, DownloadIcon, ViewIcon } from "@chakra-ui/icons"; import { HStack, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react"; import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; +import ConfirmButton from "@/components/ConfirmButton"; import IconWrap from "@/components/IconWrap"; import PanelHeader from "@/components/Panel/Header"; -import { formatDate } from "@/utils/format"; +import { formatDate, formatSize } from "@/utils/format"; import useStorageStore, { TFile } from "../../store"; import CreateFolderModal from "../CreateFolderModal"; import HostingDrawer from "../HostingDrawer"; +import PathLink from "../PathLink"; import UploadButton from "../UploadButton"; import useAwsS3 from "@/hooks/useAwsS3"; import RightPanel from "@/pages/app/mods/RightPanel"; export default function FileList() { - const { getList } = useAwsS3(); + const { getList, getFileUrl, deleteFile } = useAwsS3(); - const { currentStorage } = useStorageStore(); + const { currentStorage, prefix, setPrefix } = useStorageStore(); const bucketName = currentStorage?.metadata.name; const query = useQuery( - ["fileList", bucketName, "/"], - () => getList(bucketName, { marker: "", prefix: "/" }), + ["fileList", bucketName], + () => getList(bucketName, { marker: "", prefix }), { enabled: !!bucketName, }, ); + useEffect(() => { + query.refetch(); + }, [bucketName, prefix]); + + const viewAppFile = (file: TFile) => { + if (file.Prefix) { + changeDirectory(file); + return + } + + window.open(getFileUrl(bucketName!, file.Key), "_blank"); + } + + const changeDirectory = (file: TFile) => { + setPrefix(file.Prefix!) + } + return ( - + query.refetch()} /> - + + {/* */} 容量: {currentStorage?.spec.storage} - 已用: {currentStorage?.status.capacity.storage} - 文件数: {currentStorage?.status.capacity.objectCount} + 已用: {currentStorage?.status?.capacity?.storage} + 文件数: {currentStorage?.status?.capacity?.objectCount}
@@ -47,8 +69,8 @@ export default function FileList() { - + {(query?.data || []).map((file: TFile) => { return ( - - - - - + + + + + ); diff --git a/web/src/pages/app/storages/mods/PathLink/index.tsx b/web/src/pages/app/storages/mods/PathLink/index.tsx new file mode 100644 index 0000000000..e4989cd482 --- /dev/null +++ b/web/src/pages/app/storages/mods/PathLink/index.tsx @@ -0,0 +1,38 @@ +import useStorageStore from "../../store"; + +function PathLink() { + const { currentStorage, prefix, setPrefix } = useStorageStore(); + const bucketName = currentStorage?.metadata.name || ""; + + const strs = prefix?.split("/").filter((s) => s !== ""); + + const paths = strs?.map((s, i) => { + return { + name: s, + path: strs[i - 1] ? `${strs[i - 1]}/${s}/` : `/${s}/`, + }; + }); + + paths?.unshift({ + name: bucketName, + path: "/", + }); + + + const changeDirectory = (path: string) => { + setPrefix(path); + }; + + return ( +
+ {paths?.map((p) => ( + + changeDirectory(p.path)}>{p.name} + / + + ))} +
+ ); +} + +export default PathLink; diff --git a/web/src/pages/app/storages/mods/StorageListPanel/index.tsx b/web/src/pages/app/storages/mods/StorageListPanel/index.tsx index e14c6f3742..b7edf2b224 100644 --- a/web/src/pages/app/storages/mods/StorageListPanel/index.tsx +++ b/web/src/pages/app/storages/mods/StorageListPanel/index.tsx @@ -17,8 +17,8 @@ export default function StorageListPanel() { const bucketListQuery = useBucketListQuery({ onSuccess(data) { - if (data?.data?.items?.length) { - store.setCurrentStorage(data?.data?.items[0]); + if (data?.data?.items?.length) { + if (!store.currentStorage) store.setCurrentStorage(data?.data?.items[0]); } }, }); @@ -43,6 +43,7 @@ export default function StorageListPanel() { className="group" onClick={() => { store.setCurrentStorage(storage); + store.setPrefix("/") }} >
diff --git a/web/src/pages/app/storages/mods/UploadButton/index.tsx b/web/src/pages/app/storages/mods/UploadButton/index.tsx index 25408a9aca..c03cdc9af5 100644 --- a/web/src/pages/app/storages/mods/UploadButton/index.tsx +++ b/web/src/pages/app/storages/mods/UploadButton/index.tsx @@ -19,13 +19,14 @@ import FileUpload from "@/components/FileUplaod"; import useStorageStore from "../../store"; import useAwsS3 from "@/hooks/useAwsS3"; +import useGlobalStore from "@/pages/globalStore"; -function UploadButton() { +function UploadButton({onUploadSuccess}: {onUploadSuccess: () => void}) { const { isOpen, onOpen, onClose } = useDisclosure(); - - const { currentStorage } = useStorageStore(); - + const { currentStorage, prefix } = useStorageStore(); + const { showSuccess } = useGlobalStore(); const { uploadFile } = useAwsS3(); + const [uploadType, setUploadType] = React.useState<"file" | "folder">("file"); return (
@@ -34,26 +35,38 @@ function UploadButton() { 上传 - 上传文件 - 上传文件夹 + { + setUploadType("file"); + onOpen(); + }}>上传文件 + { + setUploadType("folder"); + onOpen(); + }}>上传文件夹 - Upload File + Upload {uploadType === "file" ? "File" : "Folder"}
{ + console.log(files); for (let i = 0; i < files.length; i++) { - await uploadFile(currentStorage?.metadata.name!, "/" + files[i].name, files[i], { - contentType: files[i].type, + const file = files[i]; + const fileName = file.webkitRelativePath ? file.webkitRelativePath : file.name; + await uploadFile(currentStorage?.metadata.name!, prefix + fileName, file, { + contentType: file.type, }); } - console.log("success"); - return files; + + onUploadSuccess(); + onClose(); + showSuccess("上传成功"); }} />
diff --git a/web/src/pages/app/storages/store.ts b/web/src/pages/app/storages/store.ts index 7aee465a73..883085e228 100644 --- a/web/src/pages/app/storages/store.ts +++ b/web/src/pages/app/storages/store.ts @@ -15,11 +15,14 @@ export type TFile = { DisplayName: string; ID: string; }; + Prefix?: string; }; type State = { currentStorage?: TBucket; setCurrentStorage: (currentStorage: TBucket) => void; + prefix?: string; + setPrefix: (prefix: string) => void; }; const useStorageStore = create()( @@ -30,6 +33,10 @@ const useStorageStore = create()( set((state) => { state.currentStorage = currentStorage; }), + prefix: "/", + setPrefix: (prefix) => set((state) => { + state.prefix = prefix; + }), })), ), ); diff --git a/web/src/utils/format.ts b/web/src/utils/format.ts index e7ee1ed0db..0639401c64 100644 --- a/web/src/utils/format.ts +++ b/web/src/utils/format.ts @@ -6,3 +6,13 @@ export function formatDate( ) { return dayjs(date).format(format); } + +export function formatSize(size: number) { + const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + let i = 0; + while (size >= 1024) { + size /= 1024; + i++; + } + return size.toFixed(2) + " " + units[i]; +}
.. 文件路径文件类型 大小 更新时间 @@ -59,18 +81,33 @@ export default function FileList() {
jpg{file.Key}{file.Size}{formatDate(file.LastModified)}
+ {file.Prefix ? + changeDirectory(file)}>{bucketName + '/' + file.Prefix} + : bucketName + '/' + file.Key} + --{file.Size ? formatSize(file.Size) : '--'}{file.LastModified ? formatDate(file.LastModified) : '--'} - {}}> - - - {}}> - + viewAppFile(file)}> + + { + await deleteFile(bucketName!, file.Key); + query.refetch(); + }} + headerText={String(t("Delete"))} + bodyText={'确认要删除文件吗?'} + > + + + +