diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json index 19ed7a0..700862d 100644 --- a/public/locales/en-US/dashboard.json +++ b/public/locales/en-US/dashboard.json @@ -828,6 +828,15 @@ "onedrive": "OneDrive", "s3": "S3 Compatible", "obs": "Huawei Cloud OBS", + "load_balance": "Load Balance", + "childPolicy": "Child Storage Policy", + "childPolicyDes": "Select the child storage policies to add to the load balance pool.", + "weight": "Weight", + "addTargetPolicy": "Add Child Policy", + "selectPolicies": "Select Policies", + "selectPoliciesDes": "Select storage policies to add to the load balance pool.", + "loadBalanceDes": "When using the load balanced storage policy, new uploads will be randomly distributed to different child storage policies based on weight.", + "xChildPolicies": "{{count}} child storage policies", "refresh": "Refresh", "delete": "Delete", "edit": "Edit", diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json index a3f7c92..c6003cc 100644 --- a/public/locales/ja-JP/dashboard.json +++ b/public/locales/ja-JP/dashboard.json @@ -829,6 +829,15 @@ "onedrive": "OneDrive", "s3": "S3互換", "obs": "Huawei Cloud OBS", + "load_balance": "負荷分散", + "childPolicy": "子ストレージポリシー", + "childPolicyDes": "負荷分散に追加する子ストレージポリシーを選択してください。", + "weight": "Weight重み", + "addTargetPolicy": "子ストレージポリシーを追加", + "selectPolicies": "ストレージポリシーを選択", + "selectPoliciesDes": "負荷分散に追加するストレージポリシーを選択してください。", + "loadBalanceDes": "負荷分散ストレージポリシーを使用する場合、新規アップロードは重みに基づいてランダムに異なる子ストレージポリシーに分散されます。", + "xChildPolicies": "{{count}} 子ストレージポリシー", "refresh": "更新", "delete": "削除", "edit": "編集", diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json index 2f6f252..40fdd04 100644 --- a/public/locales/zh-CN/dashboard.json +++ b/public/locales/zh-CN/dashboard.json @@ -829,6 +829,15 @@ "onedrive": "OneDrive", "s3": "S3 兼容", "obs": "华为云 OBS", + "load_balance": "负载均衡", + "childPolicy": "子存储策略", + "childPolicyDes": "选择你要添加到负载均衡中的子存储策略。", + "weight": "权重", + "addTargetPolicy": "添加子存储策略", + "selectPolicies": "选择策略", + "selectPoliciesDes": "选择要添加到负载均衡的存储策略。", + "loadBalanceDes": "使用负载均衡存储策略时,新上传的文件会根据权重随机分配到不同的子存储策略中。", + "xChildPolicies": "{{count}} 个子存储策略", "refresh": "刷新", "delete": "删除", "edit": "编辑", diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json index a64228f..36d5695 100644 --- a/public/locales/zh-TW/dashboard.json +++ b/public/locales/zh-TW/dashboard.json @@ -825,6 +825,15 @@ "onedrive": "OneDrive", "s3": "S3 相容", "obs": "華為雲 OBS", + "load_balance": "負載均衡", + "childPolicy": "子儲存策略", + "childPolicyDes": "選擇你要添加到負載均衡中的子儲存策略。", + "weight": "權重", + "addTargetPolicy": "添加子儲存策略", + "selectPolicies": "選擇儲存策略", + "selectPoliciesDes": "選擇要添加到負載均衡的儲存策略。", + "loadBalanceDes": "使用負載均衡儲存策略時,新上傳的文件會根據權重隨機分配到不同的子儲存策略中。", + "xChildPolicies": "{{count}} 子儲存策略", "refresh": "重新整理", "delete": "刪除", "edit": "編輯", diff --git a/public/static/img/lb.svg b/public/static/img/lb.svg new file mode 100644 index 0000000..8a9132c --- /dev/null +++ b/public/static/img/lb.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/api/explorer.ts b/src/api/explorer.ts index 0fa09f5..d42303f 100644 --- a/src/api/explorer.ts +++ b/src/api/explorer.ts @@ -99,6 +99,7 @@ export enum PolicyType { upyun = "upyun", s3 = "s3", obs = "obs", + load_balance = "load_balance", } export interface StoragePolicy { diff --git a/src/component/Admin/StoragePolicy/SelectProvider.tsx b/src/component/Admin/StoragePolicy/SelectProvider.tsx index fe3bc5c..d339985 100644 --- a/src/component/Admin/StoragePolicy/SelectProvider.tsx +++ b/src/component/Admin/StoragePolicy/SelectProvider.tsx @@ -15,6 +15,9 @@ import { SecondaryButton } from "../../Common/StyledComponents"; import DraggableDialog from "../../Dialogs/DraggableDialog"; import Open from "../../Icons/Open"; import { PolicyPropsMap } from "./StoragePolicySetting"; +import { useState } from "react"; +import ProDialog from "../Common/ProDialog.tsx"; +import { ProChip } from "../../Pages/Setting/SettingForm.tsx"; export interface SelectProviderProps { open: boolean; @@ -30,6 +33,7 @@ const StyledCard = styled(Card)(({ theme }) => ({ const SelectProvider = ({ open, onClose, onSelect }: SelectProviderProps) => { const { t } = useTranslation("dashboard"); + const [proOpen, setProOpen] = useState(false); return ( { maxWidth: "sm", }} > + setProOpen(false)} /> {Object.values(PolicyType).map((type) => ( - onSelect(type)}> + (PolicyPropsMap[type].pro ? setProOpen(true) : onSelect(type))} + > {t(PolicyPropsMap[type].name)} + {PolicyPropsMap[type].pro && } diff --git a/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx b/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx index 97a83b7..76a7308 100644 --- a/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx +++ b/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx @@ -60,6 +60,7 @@ export interface PolicyProps { credentialDes?: React.ReactNode; corsExposedHeaders?: string[]; endpointNotEnforcePrefix?: boolean; + pro?: boolean; } export const PolicyPropsMap: Record = { @@ -70,6 +71,12 @@ export const PolicyPropsMap: Record = { wizard: LocalWizard, chunkSizeDes: "policy.chunkSizeDes", }, + [PolicyType.load_balance]: { + name: "policy.load_balance", + img: "/static/img/lb.svg", + wizardSize: "sm", + pro: true, + }, [PolicyType.remote]: { name: "policy.remote", img: "/static/img/remote.png", diff --git a/src/component/Uploader/Uploader.tsx b/src/component/Uploader/Uploader.tsx index 96b533b..3d363d4 100644 --- a/src/component/Uploader/Uploader.tsx +++ b/src/component/Uploader/Uploader.tsx @@ -2,19 +2,19 @@ import dayjs from "dayjs"; import { useSnackbar } from "notistack"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { ContextMenuTypes } from "../../redux/fileManagerSlice.ts"; import { closeUploadTaskList, openUploadTaskList, setUploadProgress } from "../../redux/globalStateSlice.ts"; import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; import { refreshFileList, updateUserCapacity } from "../../redux/thunks/filemanager.ts"; import SessionManager, { UserSettings } from "../../session"; +import useActionDisplayOpt from "../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import { FileManagerIndex } from "../FileManager/FileManager.tsx"; import UploadManager, { SelectType } from "./core"; import { UploaderError } from "./core/errors"; import Base, { Status } from "./core/uploader/base.ts"; import { DropFileBackground } from "./DropFile.tsx"; import PasteUploadDialog from "./PasteUploadDialog.tsx"; import TaskList from "./Popup/TaskList.tsx"; -import useActionDisplayOpt from "../FileManager/ContextMenu/useActionDisplayOpt.ts"; -import { ContextMenuTypes } from "../../redux/fileManagerSlice.ts"; -import { FileManagerIndex } from "../FileManager/FileManager.tsx"; let totalProgressCollector: NodeJS.Timeout | null = null; let lastProgressStart = -1; diff --git a/src/component/Uploader/core/errors/index.ts b/src/component/Uploader/core/errors/index.ts index 19ee33b..7a54d12 100644 --- a/src/component/Uploader/core/errors/index.ts +++ b/src/component/Uploader/core/errors/index.ts @@ -1,8 +1,8 @@ -import { OneDriveError, QiniuError, UpyunError } from "../types"; -import i18next from "../../../../i18n"; -import { AppError, Response } from "../../../../api/request.ts"; import { StoragePolicy } from "../../../../api/explorer.ts"; +import { AppError, Response } from "../../../../api/request.ts"; +import i18next from "../../../../i18n"; import { sizeToString } from "../../../../util"; +import { OneDriveError, QiniuError, UpyunError } from "../types"; export enum UploaderErrorName { InvalidFile = "InvalidFile", @@ -31,6 +31,7 @@ export enum UploaderErrorName { FailedFinishOSSUpload = "FailedFinishOSSUpload", FailedFinishQiniuUpload = "FailedFinishQiniuUpload", FailedTransformResponse = "FailedTransformResponse", + LoadBalancePolicyNoAvailable = "LoadBalancePolicyNoAvailable", } const RETRY_ERROR_LIST = [ diff --git a/src/component/Uploader/core/index.ts b/src/component/Uploader/core/index.ts index a1a4d9a..baf429c 100644 --- a/src/component/Uploader/core/index.ts +++ b/src/component/Uploader/core/index.ts @@ -165,14 +165,14 @@ export default class UploadManager { // 选择文件 public select = (dst: string, type = SelectType.File): Promise => { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { if (this.policy == undefined) { this.logger.warn(`Calling file selector while no policy is set`); throw new UploaderError(UploaderErrorName.NoPolicySelected, "No policy selected."); } - this.fileInput.onchange = (ev: Event) => this.addFiles(ev, dst, resolve); - this.directoryInput.onchange = (ev: Event) => this.addFiles(ev, dst, resolve); + this.fileInput.onchange = (ev: Event) => this.addFiles(ev, dst, resolve, reject); + this.directoryInput.onchange = (ev: Event) => this.addFiles(ev, dst, resolve, reject); this.fileInput.value = ""; this.directoryInput.value = ""; type == SelectType.File ? this.fileInput.click() : this.directoryInput.click(); @@ -197,8 +197,8 @@ export default class UploadManager { if (!this.currentPath) { return; } - const uploaders = await new Promise((resolve) => - this.addFiles(files, this.currentPath ?? defaultPath, resolve, getName), + const uploaders = await new Promise((resolve, reject) => + this.addFiles(files, this.currentPath ?? defaultPath, resolve, reject, getName), ); this.o.onProactiveFileAdded && this.o.onProactiveFileAdded(uploaders); }; @@ -207,6 +207,7 @@ export default class UploadManager { ev: Event | File[], dst: string, resolve: (value: Base[] | PromiseLike) => void, + reject: (reason?: any) => void, getName?: (file: File) => string, ) => { let files: File[] = []; @@ -221,22 +222,25 @@ export default class UploadManager { } if (files.length > 0) { - resolve( - files.map( - (file): Base => - this.dispatchUploader({ - type: TaskType.file, - policy: this.policy as StoragePolicy, - dst: getDirectoryUploadDst(dst, file), - file: file, - size: file.size, - overwrite: this.overwrite, - name: getName ? getName(file) : file.name, - chunkProgress: [], - resumed: false, - }), - ), - ); + let uploaders: Base[] = []; + try { + uploaders = files.map((file): Base => { + return this.dispatchUploader({ + type: TaskType.file, + policy: this.policy as StoragePolicy, + dst: getDirectoryUploadDst(dst, file), + file: file, + size: file.size, + overwrite: this.overwrite, + name: getName ? getName(file) : file.name, + chunkProgress: [], + resumed: false, + }); + }); + resolve(uploaders); + } catch (e) { + reject(e); + } } }; @@ -248,7 +252,9 @@ export default class UploadManager { if (containFile) { this.o.onDropLeave && this.o.onDropLeave(e); const items = await getAllFileEntries(e.dataTransfer!.items); - const uploaders = await new Promise((resolve) => this.addFiles(items, this.currentPath, resolve)); + const uploaders = await new Promise((resolve, reject) => + this.addFiles(items, this.currentPath as string, resolve, reject), + ); this.o.onProactiveFileAdded && this.o.onProactiveFileAdded(uploaders); } };