mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
feat(storage): add Kingsoft KS3 storage policy (#285)
* feat:添加金山KS3存储策略 * fix:pr question * fix:question
This commit is contained in:
parent
d896d1f165
commit
b8dd0fcd9d
|
|
@ -847,6 +847,7 @@
|
|||
"cos": "Tencent Cloud COS",
|
||||
"onedrive": "OneDrive",
|
||||
"s3": "S3 Compatible",
|
||||
"ks3": "Kingsoft Cloud S3",
|
||||
"obs": "Huawei Cloud OBS",
|
||||
"load_balance": "Load Balance",
|
||||
"childPolicy": "Child Storage Policy",
|
||||
|
|
@ -1011,7 +1012,9 @@
|
|||
"driverRootDes": "Choose where to save files in your OneDrive account. Changing this option will make existing files in the storage policy inaccessible.",
|
||||
"saveToDefaultOneDrive": "Save files to default OneDrive driver",
|
||||
"saveToSharePoint": "Save files to SharePoint",
|
||||
"sharePointUrlDes": "Enter the SharePoint site URL. After losing focus, the system will automatically convert it to the correct driver identifier."
|
||||
"sharePointUrlDes": "Enter the SharePoint site URL. After losing focus, the system will automatically convert it to the correct driver identifier.",
|
||||
"ks3selectRegionDes": "Enter the region code of the storage bucket, e.g. <0>BEIJING</0> .",
|
||||
"ks3EndpointPathStyle": "Select the format of the KS3 Endpoint address."
|
||||
},
|
||||
"node": {
|
||||
"slave": "slave",
|
||||
|
|
|
|||
|
|
@ -848,6 +848,7 @@
|
|||
"cos": "Tencent COS",
|
||||
"onedrive": "OneDrive",
|
||||
"s3": "S3互換",
|
||||
"ks3": "金山雲 KS3",
|
||||
"obs": "Huawei Cloud OBS",
|
||||
"load_balance": "負荷分散",
|
||||
"childPolicy": "子ストレージポリシー",
|
||||
|
|
@ -1000,7 +1001,9 @@
|
|||
"driverRootDes": "OneDriveアカウント内でファイルを保存する場所を選択してください。このオプションを変更すると、ストレージポリシーに既に存在するファイルにアクセスできなくなる可能性があります。",
|
||||
"saveToDefaultOneDrive": "ファイルをデフォルトのOneDriveドライブに保存",
|
||||
"saveToSharePoint": "SharePointにファイルを保存",
|
||||
"sharePointUrlDes": "SharePointサイトのURLを入力してください。フォーカスが外れると、システムが自動的に正しいドライブ識別子に変換します。"
|
||||
"sharePointUrlDes": "SharePointサイトのURLを入力してください。フォーカスが外れると、システムが自動的に正しいドライブ識別子に変換します。",
|
||||
"ks3selectRegionDes": "バケットが存在するリージョンコードを入力してください(例:<0>BEIJING</0>)。",
|
||||
"ks3EndpointPathStyle": "パス形式エンドポイントの強制使用を選択してください。"
|
||||
},
|
||||
"node": {
|
||||
"slave": "スレーブ",
|
||||
|
|
|
|||
|
|
@ -847,6 +847,7 @@
|
|||
"cos": "腾讯云 COS",
|
||||
"onedrive": "OneDrive",
|
||||
"s3": "S3 兼容",
|
||||
"ks3": "金山云 KS3",
|
||||
"obs": "华为云 OBS",
|
||||
"load_balance": "负载均衡",
|
||||
"childPolicy": "子存储策略",
|
||||
|
|
@ -999,7 +1000,9 @@
|
|||
"driverRootDes": "选择在 OneDrive 账户中保存文件的位置。更改此选项会导致存储策略中已有文件无法访问。",
|
||||
"saveToDefaultOneDrive": "保存文件到默认 OneDrive 驱动器",
|
||||
"saveToSharePoint": "保存文件到 SharePoint",
|
||||
"sharePointUrlDes": "输入 SharePoint 站点 URL。失去焦点后,系统将自动转换为正确的驱动器标识。"
|
||||
"sharePointUrlDes": "输入 SharePoint 站点 URL。失去焦点后,系统将自动转换为正确的驱动器标识。",
|
||||
"ks3selectRegionDes": "输入存储桶所在的区域代码,如 <0>BEIJING</0>。",
|
||||
"ks3EndpointPathStyle": "选择是否强制使用路径格式 Endpoint。"
|
||||
},
|
||||
"node": {
|
||||
"slave": "从机",
|
||||
|
|
|
|||
|
|
@ -844,6 +844,7 @@
|
|||
"cos": "騰訊雲 COS",
|
||||
"onedrive": "OneDrive",
|
||||
"s3": "S3 相容",
|
||||
"ks3": "金山雲 KS3",
|
||||
"obs": "華為雲 OBS",
|
||||
"load_balance": "負載均衡",
|
||||
"childPolicy": "子儲存策略",
|
||||
|
|
@ -996,7 +997,9 @@
|
|||
"driverRootDes": "選擇在 OneDrive 賬戶中儲存檔案的位置。更改此選項會導致儲存策略中已有檔案無法訪問。",
|
||||
"saveToDefaultOneDrive": "儲存檔案到預設 OneDrive 驅動器",
|
||||
"saveToSharePoint": "儲存檔案到 SharePoint",
|
||||
"sharePointUrlDes": "輸入 SharePoint 站點 URL。失去焦點後,系統將自動轉換為正確的驅動器標識。"
|
||||
"sharePointUrlDes": "輸入 SharePoint 站點 URL。失去焦點後,系統將自動轉換為正確的驅動器標識。",
|
||||
"ks3selectRegionDes": "輸入儲存桶所在的區域程式碼,如 <0>BEIJING</0>。",
|
||||
"ks3EndpointPathStyle": "選擇是否強制使用路徑格式 Endpoint。"
|
||||
},
|
||||
"node": {
|
||||
"slave": "從機",
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -99,6 +99,7 @@ export enum PolicyType {
|
|||
cos = "cos",
|
||||
upyun = "upyun",
|
||||
s3 = "s3",
|
||||
ks3 = "ks3",
|
||||
obs = "obs",
|
||||
load_balance = "load_balance",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ const BasicInfoSection = () => {
|
|||
values.type === PolicyType.obs ||
|
||||
values.type === PolicyType.qiniu ||
|
||||
values.type === PolicyType.s3 ||
|
||||
values.type === PolicyType.ks3 ||
|
||||
values.type === PolicyType.upyun
|
||||
);
|
||||
}, [values.type]);
|
||||
|
|
@ -93,7 +94,8 @@ const BasicInfoSection = () => {
|
|||
values.type === PolicyType.oss ||
|
||||
values.type === PolicyType.cos ||
|
||||
values.type === PolicyType.obs ||
|
||||
values.type === PolicyType.s3
|
||||
values.type === PolicyType.s3 ||
|
||||
values.type === PolicyType.ks3
|
||||
);
|
||||
}, [values.type]);
|
||||
|
||||
|
|
@ -345,7 +347,7 @@ const BasicInfoSection = () => {
|
|||
<NoMarginHelperText>{t("policy.thisIsACustomDomainDes")}</NoMarginHelperText>
|
||||
</>
|
||||
)}
|
||||
{values.type === PolicyType.s3 && (
|
||||
{(values.type === PolicyType.s3 || values.type === PolicyType.ks3) && (
|
||||
<>
|
||||
<FormControlLabel
|
||||
sx={{ mt: 1, mb: -1 }}
|
||||
|
|
@ -364,7 +366,13 @@ const BasicInfoSection = () => {
|
|||
label={t("policy.usePathEndpoint")}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
<Trans i18nKey="policy.s3EndpointPathStyle" ns="dashboard" components={[<Code />]} />
|
||||
<Trans
|
||||
i18nKey={
|
||||
values.type === PolicyType.s3 ? "policy.s3EndpointPathStyle" : "policy.ks3EndpointPathStyle"
|
||||
}
|
||||
ns="dashboard"
|
||||
components={[<Code />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -405,11 +413,15 @@ const BasicInfoSection = () => {
|
|||
</SettingForm>
|
||||
</>
|
||||
)}
|
||||
{values.type == PolicyType.s3 && (
|
||||
{(values.type === PolicyType.s3 || values.type === PolicyType.ks3) && (
|
||||
<SettingForm title={t("policy.s3Region")} lgWidth={5}>
|
||||
<DenseFilledTextField fullWidth required value={values.settings?.region} onChange={onS3RegionChange} />
|
||||
<NoMarginHelperText>
|
||||
<Trans i18nKey="policy.selectRegionDes" ns="dashboard" components={[<Code />]} />
|
||||
<Trans
|
||||
i18nKey={values.type === PolicyType.s3 ? "policy.selectRegionDes" : "policy.ks3selectRegionDes"}
|
||||
ns="dashboard"
|
||||
components={[<Code />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</SettingForm>
|
||||
)}
|
||||
|
|
@ -464,7 +476,7 @@ const BasicInfoSection = () => {
|
|||
</SecondaryButton>
|
||||
</SettingForm>
|
||||
)}
|
||||
{values.type === PolicyType.s3 && (
|
||||
{(values.type === PolicyType.s3 || values.type === PolicyType.ks3) && (
|
||||
<SettingForm title={t("policy.batchDeleteSize")} lgWidth={5}>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const MediaMetadataSection = () => {
|
|||
);
|
||||
|
||||
const noNativeExtractor = useMemo(() => {
|
||||
return values.type === PolicyType.s3 || values.type === PolicyType.onedrive;
|
||||
return values.type === PolicyType.s3 || values.type === PolicyType.ks3 || values.type === PolicyType.onedrive;
|
||||
}, [values.type]);
|
||||
|
||||
if (values.type === PolicyType.local) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import OssWizard from "./Wizards/OSS/OssWizard";
|
|||
import QiniuWizard from "./Wizards/Qiniu/QiniuWizard";
|
||||
import RemoteWizard from "./Wizards/Remote/RemoteWizard";
|
||||
import S3Wizard from "./Wizards/S3/S3Wizard";
|
||||
import KS3Wizard from "./Wizards/KS3/KS3Wizard";
|
||||
import UpyunWizard from "./Wizards/Upyun/UpyunWizard";
|
||||
|
||||
export const PageQuery = "page";
|
||||
|
|
@ -102,6 +103,22 @@ export const PolicyPropsMap: Record<PolicyType, PolicyProps> = {
|
|||
chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB
|
||||
chunkSizeDes: "policy.chunkSizeDesS3",
|
||||
},
|
||||
[PolicyType.ks3]: {
|
||||
name: "policy.ks3",
|
||||
img: "/static/img/ks3.png",
|
||||
wizardSize: "sm",
|
||||
wizard: KS3Wizard,
|
||||
bucketName: "policy.bucketName",
|
||||
bucketType: "policy.bucketType",
|
||||
endpointName: "policy.policyEndpoint",
|
||||
endpointDes: <Trans i18nKey="policy.ks3selectRegionDes" ns="dashboard" components={[<Code />]} />,
|
||||
akName: "Access Key",
|
||||
skName: "Secret Key",
|
||||
corsExposedHeaders: ["ETag"],
|
||||
chunkSizeMin: 5 * 1024 * 1024, //5MB
|
||||
chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB
|
||||
chunkSizeDes: "policy.chunkSizeDesS3",
|
||||
},
|
||||
[PolicyType.cos]: {
|
||||
name: "policy.cos",
|
||||
img: "/static/img/cos.png",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
import { Button, Checkbox, Collapse, FormControl, FormControlLabel, Stack } from "@mui/material";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useRef, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { createStoragePolicyCors } from "../../../../../api/api";
|
||||
import { StoragePolicy } from "../../../../../api/dashboard";
|
||||
import { PolicyType } from "../../../../../api/explorer";
|
||||
import { useAppDispatch } from "../../../../../redux/hooks";
|
||||
import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar";
|
||||
import { DenseFilledTextField, SecondaryButton } from "../../../../Common/StyledComponents";
|
||||
import SettingForm from "../../../../Pages/Setting/SettingForm";
|
||||
import { Code } from "../../../Common/Code";
|
||||
import { EndpointInput } from "../../../Common/EndpointInput";
|
||||
import { NoMarginHelperText } from "../../../Settings/Settings";
|
||||
import { AddWizardProps } from "../../AddWizardDialog";
|
||||
import BucketACLInput from "../../EditStoragePolicy/BucketACLInput";
|
||||
import BucketCorsTable from "../../EditStoragePolicy/BucketCorsTable";
|
||||
|
||||
const KS3Wizard = ({ onSubmit }: AddWizardProps) => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const dispatch = useAppDispatch();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [corsAdded, setCorsAdded] = useState(false);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [policy, setPolicy] = useState<StoragePolicy>({
|
||||
id: 0,
|
||||
node_id: 0,
|
||||
name: "",
|
||||
type: PolicyType.ks3,
|
||||
is_private: true,
|
||||
dir_name_rule: "uploads/{uid}/{path}",
|
||||
settings: {
|
||||
chunk_size: 25 << 20,
|
||||
media_meta_generator_proxy: true,
|
||||
thumb_generator_proxy: true,
|
||||
},
|
||||
file_name_rule: "{uuid}_{originname}",
|
||||
edges: {},
|
||||
});
|
||||
|
||||
const hamdleCreateCors = () => {
|
||||
if (!formRef.current?.checkValidity()) {
|
||||
formRef.current?.reportValidity();
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
dispatch(createStoragePolicyCors({ policy }))
|
||||
.then(() => {
|
||||
enqueueSnackbar(t("policy.corsPolicyAdded"), { variant: "success", action: DefaultCloseAction });
|
||||
setCorsAdded(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!formRef.current?.checkValidity()) {
|
||||
formRef.current?.reportValidity();
|
||||
return;
|
||||
}
|
||||
onSubmit(policy);
|
||||
};
|
||||
|
||||
return (
|
||||
<form ref={formRef} onSubmit={handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
<SettingForm title={t("policy.name")} lgWidth={12}>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
required
|
||||
value={policy.name}
|
||||
onChange={(e) => setPolicy({ ...policy, name: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>{t("policy.policyName")}</NoMarginHelperText>
|
||||
</SettingForm>
|
||||
<SettingForm title={t("policy.bucketName")} lgWidth={12}>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
required
|
||||
value={policy.bucket_name}
|
||||
onChange={(e) => setPolicy({ ...policy, bucket_name: e.target.value })}
|
||||
/>
|
||||
</SettingForm>
|
||||
<SettingForm title={t("policy.bucketType")} lgWidth={12}>
|
||||
<FormControl fullWidth>
|
||||
<BucketACLInput
|
||||
phraseVariant={policy.type}
|
||||
value={policy.is_private ?? false}
|
||||
onChange={(value) => setPolicy({ ...policy, is_private: value })}
|
||||
/>
|
||||
<NoMarginHelperText>{t("policy.bucketTypeDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
<SettingForm title={t("policy.policyEndpoint")} lgWidth={12}>
|
||||
<EndpointInput
|
||||
fullWidth
|
||||
required
|
||||
value={policy.server}
|
||||
enforceProtocol
|
||||
onChange={(e) => setPolicy({ ...policy, server: e.target.value })}
|
||||
variant={"outlined"}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
<Trans i18nKey="policy.s3EndpointDes" ns="dashboard" components={[<Code />]} />
|
||||
</NoMarginHelperText>
|
||||
<FormControlLabel
|
||||
sx={{ mt: 1, mb: -1 }}
|
||||
slotProps={{
|
||||
typography: {
|
||||
variant: "body2",
|
||||
},
|
||||
}}
|
||||
control={
|
||||
<Checkbox
|
||||
size={"small"}
|
||||
checked={policy.settings?.s3_path_style ?? false}
|
||||
onChange={(e) =>
|
||||
setPolicy({
|
||||
...policy,
|
||||
settings: { ...policy.settings, s3_path_style: e.target.checked ? true : undefined },
|
||||
})
|
||||
}
|
||||
/>
|
||||
}
|
||||
label={t("policy.usePathEndpoint")}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
<Trans i18nKey="policy.ks3EndpointPathStyle" ns="dashboard" components={[<Code />]} />
|
||||
</NoMarginHelperText>
|
||||
</SettingForm>
|
||||
<SettingForm title={t("policy.s3Region")} lgWidth={12}>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
required
|
||||
value={policy.settings?.region}
|
||||
onChange={(e) => setPolicy({ ...policy, settings: { ...policy.settings, region: e.target.value } })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
<Trans i18nKey="policy.ks3selectRegionDes" ns="dashboard" components={[<Code />]} />
|
||||
</NoMarginHelperText>
|
||||
</SettingForm>
|
||||
<SettingForm title={t("policy.accessCredential")} lgWidth={12}>
|
||||
<FormControl fullWidth>
|
||||
<DenseFilledTextField
|
||||
placeholder="Access Key"
|
||||
fullWidth
|
||||
required
|
||||
value={policy.access_key}
|
||||
onChange={(e) => setPolicy({ ...policy, access_key: e.target.value })}
|
||||
/>
|
||||
<DenseFilledTextField
|
||||
placeholder="Secret Key"
|
||||
sx={{ mt: 1 }}
|
||||
fullWidth
|
||||
required
|
||||
value={policy.secret_key}
|
||||
onChange={(e) => setPolicy({ ...policy, secret_key: e.target.value })}
|
||||
/>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
<SettingForm title={t("policy.corsSettingStep")} lgWidth={12}>
|
||||
<FormControl fullWidth>
|
||||
<BucketCorsTable exposedHeaders={["ETag"]} />
|
||||
<NoMarginHelperText>{t("policy.ossCORSDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
<Collapse in={!corsAdded} sx={{ mt: 1 }}>
|
||||
<Button loading={loading} variant="contained" color="primary" sx={{ mr: 1 }} onClick={hamdleCreateCors}>
|
||||
{t("policy.letCloudreveHelpMe")}
|
||||
</Button>
|
||||
<SecondaryButton variant="contained" onClick={() => setCorsAdded(true)}>
|
||||
{t("policy.addedManually")}
|
||||
</SecondaryButton>
|
||||
</Collapse>
|
||||
</SettingForm>
|
||||
</Stack>
|
||||
<Collapse in={corsAdded}>
|
||||
<Button variant="contained" color="primary" sx={{ mt: 2 }} onClick={handleSubmit}>
|
||||
{t("policy.create")}
|
||||
</Button>
|
||||
</Collapse>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default KS3Wizard;
|
||||
|
|
@ -13,6 +13,7 @@ import ResumeHint from "./uploader/placeholder";
|
|||
import Qiniu from "./uploader/qiniu";
|
||||
import Remote from "./uploader/remote";
|
||||
import S3 from "./uploader/s3";
|
||||
import KS3 from "./uploader/ks3";
|
||||
import Upyun from "./uploader/upyun";
|
||||
import {
|
||||
cleanupResumeCtx,
|
||||
|
|
@ -130,6 +131,8 @@ export default class UploadManager {
|
|||
return new Upyun(task, this);
|
||||
case PolicyType.s3:
|
||||
return new S3(task, this);
|
||||
case PolicyType.ks3:
|
||||
return new KS3(task, this);
|
||||
case PolicyType.obs:
|
||||
return new OBS(task, this);
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ const resumePolicy = [
|
|||
PolicyType.oss,
|
||||
PolicyType.onedrive,
|
||||
PolicyType.s3,
|
||||
PolicyType.ks3,
|
||||
];
|
||||
const deleteUploadSessionDelay = 500;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import Chunk, { ChunkInfo } from "./chunk";
|
||||
import { s3LikeFinishUpload, s3LikeUploadCallback, s3LikeUploadChunk } from "../api";
|
||||
import { Status } from "./base";
|
||||
import { PolicyType } from "../../../../api/explorer.ts";
|
||||
|
||||
export default class KS3 extends Chunk {
|
||||
protected async uploadChunk(chunkInfo: ChunkInfo) {
|
||||
const etag = await s3LikeUploadChunk(
|
||||
this.task.session?.upload_urls[chunkInfo.index]!,
|
||||
chunkInfo,
|
||||
(p) => {
|
||||
this.updateChunkProgress(p.loaded, chunkInfo.index);
|
||||
},
|
||||
this.cancelToken.token,
|
||||
);
|
||||
|
||||
this.task.chunkProgress[chunkInfo.index].etag = etag;
|
||||
}
|
||||
|
||||
protected async afterUpload(): Promise<any> {
|
||||
this.logger.info(`Finishing multipart upload...`);
|
||||
this.transit(Status.finishing);
|
||||
await s3LikeFinishUpload(this.task.session!.completeURL, false, this.task.chunkProgress, this.cancelToken.token);
|
||||
|
||||
this.logger.info(`Sending S3-like upload callback...`);
|
||||
return s3LikeUploadCallback(this.task.session!.session_id, this.task.session!.callback_secret, PolicyType.ks3);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue