From a9f044a93d7273b6f130de7304e6429544f2af70 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 14 Jun 2022 20:25:45 +0800 Subject: [PATCH] feat: add remote storage policy --- public/locales/en-US/dashboard.json | 46 +++- public/locales/zh-CN/dashboard.json | 46 +++- src/component/Admin/Dialogs/MagicVar.js | 51 +++-- .../Admin/Policy/Guid/RemoteGuide.js | 214 ++++++++++-------- 4 files changed, 232 insertions(+), 125 deletions(-) diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json index af33533..3432038 100644 --- a/public/locales/en-US/dashboard.json +++ b/public/locales/en-US/dashboard.json @@ -314,6 +314,50 @@ "policyName": "Storage policy name", "finish": "Finish", "furtherActions": "To use this storage policy, go to the user group setting page and bind this storage policy for the appropriate user group.", - "backToList": "Back to storage policy list" + "backToList": "Back to storage policy list", + "magicVar": { + "fileNameMagicVar": "File name magic variables", + "pathMagicVar": "Path magic variables", + "variable": "Variable", + "description": "Description", + "example": "Example", + "16digitsRandomString": "16 digits random string", + "8digitsRandomString": "8 digits random string", + "secondTimestamp": "Timestamp", + "nanoTimestamp": "Nano timestamp", + "uid": "User ID", + "originalFileName": "Original file name", + "extension": "File extension name", + "uuidV4": "UUID V4", + "date": "Date", + "dateAndTime": "Date and time", + "year": "Year", + "month": "Month", + "day": "Day", + "hour": "Hour", + "minute": "Minute", + "second": "Second", + "userUploadPath": "Upload path" + }, + "storageNode": "Storage node", + "communicationOK": "通信正常", + "editRemoteStoragePolicy": "Edit remote storage policy", + "addRemoteStoragePolicy": "Add remote storage policy", + "remoteDescription": "The remote storage policy allows you to use a server that is also running Cloudreve as the slave storage node, and users' upload and download traffic are directly transmitted over HTTP.", + "remoteCopyBinaryDescription": "Copy the Cloudreve executable with the same version as master to the server you want to use as a slave storage node.", + "remoteSecretDescription": "The following is the randomly generated slave secret, usually no need to change. If you have customization requirement, you can fill in your own secret into the following field.", + "remoteSecret": "Slave node secret", + "modifyRemoteConfig": "Modify the Cloudreve config file on slave node.", + "addRemoteConfigDes": " Create a new <0>conf.ini file in the same directory as the slave Cloudreve, fill in the slave configuration, and start/restart the slave Cloudreve. The following is an example configuration for your slave Cloudreve, where the secret section is pre-filled in for you as generated in the previous step.", + "remoteConfigDifference": "The configuration file format on the slave side is roughly the same as the master side, with the following differences:", + "remoteConfigDifference1": "The <1>mode field under the <0>System section must be changed to <2>slave.", + "remoteConfigDifference2": "You must specify the <1>Secret field under the <0>Slave section, whose value is the secret filled in or generated in step 2.", + "remoteConfigDifference3": "The cross-origin configuration, i.e. the contents of the <0>CORS field, must be enabled, as described in the example above or in the official documentation. If the configuration is not correct, users will not be able to upload files to the slave node via the web browser.", + "inputRemoteAddress": "Enter slave node address.", + "inputRemoteAddressDes": "If HTTPS is enabled on the master, the slave also needs to enable it and fill in the address with HTTPS protocol below.", + "remoteAddress": "Slave node address", + "testCommunicationDes": "After completing the above steps, you can test if the communication is working by clicking the test button below.", + "testCommunication": "Test slave communication", + "pathMagicVarDesRemote": "Enter the physical path to the folder you want to store files. Either absolute or relative (relative to slave Cloudreve executable) path is supported. You can use magic variables in the path, which will be automatically replaced with the corresponding values when the file is uploaded; see <0>List of path magic variables for available magic variables." } } \ No newline at end of file diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json index 06790e2..ab96544 100644 --- a/public/locales/zh-CN/dashboard.json +++ b/public/locales/zh-CN/dashboard.json @@ -313,6 +313,50 @@ "policyName": "存储策略名", "finish": "完成", "furtherActions": "要使用此存储策略,请到用户组管理页面,为相应用户组绑定此存储策略。", - "backToList": "返回存储策略列表" + "backToList": "返回存储策略列表", + "magicVar": { + "fileNameMagicVar": "文件名魔法变量", + "pathMagicVar": "路径魔法变量", + "variable": "魔法变量", + "description": "描述", + "example": "示例", + "16digitsRandomString": "16 位随机字符", + "8digitsRandomString": "8 位随机字符", + "secondTimestamp": "秒级时间戳", + "nanoTimestamp": "纳秒级时间戳", + "uid": "用户 ID", + "originalFileName": "原始文件名", + "extension": "文件扩展名", + "uuidV4": "UUID V4", + "date": "日期", + "dateAndTime": "日期时间", + "year": "年份", + "month": "月份", + "day": "日", + "hour": "小时", + "minute": "分钟", + "second": "秒", + "userUploadPath": "用户上传路径" + }, + "storageNode": "存储端配置", + "communicationOK": "通信正常", + "editRemoteStoragePolicy": "修改从机存储策略", + "addRemoteStoragePolicy": "添加从机存储策略", + "remoteDescription": "从机存储策略允许你使用同样运行了 Cloudreve 的服务器作为存储端, 用户上传下载流量通过 HTTP 直传。", + "remoteCopyBinaryDescription": "将和主站相同版本的 Cloudreve 程序拷贝至要作为从机的服务器上。", + "remoteSecretDescription": "下方为系统为您随机生成的从机端密钥,一般无需改动,如果有自定义需求,可将您的密钥填入下方:", + "remoteSecret": "从机密钥", + "modifyRemoteConfig": "修改从机配置文件。", + "addRemoteConfigDes": " 在从机端 Cloudreve 的同级目录下新建 <0>conf.ini 文件,填入从机配置,启动/重启从机端 Cloudreve。以下为一个可供参考的配置例子,其中密钥部分已帮您填写为上一步所生成的。", + "remoteConfigDifference": "从机端配置文件格式大致与主站端相同,区别在于:", + "remoteConfigDifference1": "<0>System 分区下的 <1>mode 字段必须更改为 <2>slave。", + "remoteConfigDifference2": "必须指定 <0>Slave 分区下的 <1>Secret 字段,其值为第二步里填写或生成的密钥。", + "remoteConfigDifference3": "必须启动跨域配置,即 <0>CORS 字段的内容,具体可参考上文范例或官方文档。如果配置不正确,用户将无法通过 Web 端向从机上传文件。", + "inputRemoteAddress": "填写从机地址。", + "inputRemoteAddressDes": "如果主站启用了 HTTPS,从机也需要启用,并在下方填入 HTTPS 协议的地址。", + "remoteAddress": "从机地址", + "testCommunicationDes": "完成以上步骤后,你可以点击下方的测试按钮测试通信是否正常。", + "testCommunication": "测试从机通信", + "pathMagicVarDesRemote": "请在下方输入文件的存储目录路径,可以为绝对路径或相对路径(相对于 从机的 Cloudreve)。路径中可以使用魔法变量,文件在上传时会自动替换这些变量为相应值; 可用魔法变量可参考 <0>路径魔法变量列表。" } } \ No newline at end of file diff --git a/src/component/Admin/Dialogs/MagicVar.js b/src/component/Admin/Dialogs/MagicVar.js index 1cd2120..11130b5 100644 --- a/src/component/Admin/Dialogs/MagicVar.js +++ b/src/component/Admin/Dialogs/MagicVar.js @@ -10,107 +10,110 @@ import TableContainer from "@material-ui/core/TableContainer"; import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; import React from "react"; +import { useTranslation } from "react-i18next"; const magicVars = [ { value: "{randomkey16}", - des: "16位随机字符", + des: "16digitsRandomString", example: "N6IimT5XZP324ACK", fileOnly: false, }, { value: "{randomkey8}", - des: "8位随机字符", + des: "8digitsRandomString", example: "gWz78q30", fileOnly: false, }, { value: "{timestamp}", - des: "秒级时间戳", + des: "secondTimestamp", example: "1582692933", fileOnly: false, }, { value: "{timestamp_nano}", - des: "纳秒级时间戳", + des: "nanoTimestamp", example: "1582692933231834600", fileOnly: false, }, { value: "{uid}", - des: "用户ID", + des: "uid", example: "1", fileOnly: false, }, { value: "{originname}", - des: "原始文件名", + des: "originalFileName", example: "MyPico.mp4", fileOnly: true, }, { value: "{ext}", - des: "文件扩展名", + des: "extension", example: ".jpg", fileOnly: true, }, { value: "{uuid}", - des: "UUID V4", + des: "uuidV4", example: "31f0a770-659d-45bf-a5a9-166c06f33281", fileOnly: true, }, { value: "{date}", - des: "日期", + des: "date", example: "20060102", fileOnly: false, }, { value: "{datetime}", - des: "日期时间", + des: "dateAndTime", example: "20060102150405", fileOnly: false, }, { value: "{year}", - des: "年份", + des: "year", example: "2006", fileOnly: false, }, { value: "{month}", - des: "月份", + des: "month", example: "01", fileOnly: false, }, { value: "{day}", - des: "日", + des: "day", example: "02", fileOnly: false, }, { value: "{hour}", - des: "小时", + des: "hour", example: "15", fileOnly: false, }, { value: "{minute}", - des: "分钟", + des: "minute", example: "04", fileOnly: false, }, { value: "{second}", - des: "秒", + des: "second", example: "05", fileOnly: false, }, ]; export default function MagicVar({ isFile, open, onClose, isSlave }) { + const { t } = useTranslation("dashboard", { keyPrefix: "policy.magicVar" }); + const { t: tCommon } = useTranslation("common"); return ( - {isFile ? "文件名魔法变量" : "路径魔法变量"} + {isFile ? t("fileNameMagicVar") : t("pathMagicVar")} - 魔法变量 - 描述 - 示例 + {t("variable")} + {t("description")} + {t("example")} @@ -142,7 +145,7 @@ export default function MagicVar({ isFile, open, onClose, isSlave }) { > {m.value} - {m.des} + {t(m.des)} {m.example} ); @@ -153,8 +156,8 @@ export default function MagicVar({ isFile, open, onClose, isSlave }) { {"{path}"} - 用户上传路径 - /我的文件/学习资料/ + {t("userUploadPath")} + /MyFile/Documents/ )} @@ -163,7 +166,7 @@ export default function MagicVar({ isFile, open, onClose, isSlave }) { diff --git a/src/component/Admin/Policy/Guid/RemoteGuide.js b/src/component/Admin/Policy/Guid/RemoteGuide.js index df087c6..437b439 100644 --- a/src/component/Admin/Policy/Guid/RemoteGuide.js +++ b/src/component/Admin/Policy/Guid/RemoteGuide.js @@ -22,6 +22,7 @@ import { getNumber, randomStr } from "../../../../utils"; import DomainInput from "../../Common/DomainInput"; import SizeInput from "../../Common/SizeInput"; import MagicVar from "../../Dialogs/MagicVar"; +import { Trans, useTranslation } from "react-i18next"; const useStyles = makeStyles((theme) => ({ stepContent: { @@ -90,28 +91,29 @@ const useStyles = makeStyles((theme) => ({ const steps = [ { - title: "存储端配置", + title: "storageNode", optional: false, }, { - title: "上传路径", + title: "storagePathStep", optional: false, }, { - title: "直链设置", + title: "sourceLinkStep", optional: false, }, { - title: "上传设置", + title: "uploadSettingStep", optional: false, }, { - title: "完成", + title: "finishStep", optional: false, }, ]; export default function RemoteGuide(props) { + const { t } = useTranslation("dashboard", { keyPrefix: "policy" }); const classes = useStyles(); const history = useHistory(); @@ -179,7 +181,7 @@ export default function RemoteGuide(props) { secret: policy.SecretKey, }) .then(() => { - ToggleSnackbar("top", "right", "通信正常", "success"); + ToggleSnackbar("top", "right", t("communicationOK"), "success"); }) .catch((error) => { ToggleSnackbar("top", "right", error.message, "error"); @@ -229,7 +231,7 @@ export default function RemoteGuide(props) { ToggleSnackbar( "top", "right", - "存储策略已" + (props.policy ? "保存" : "添加"), + props.policy ? t("policySaved") : t("policyAdded"), "success" ); setActiveStep(5); @@ -247,7 +249,9 @@ export default function RemoteGuide(props) { return (
- {props.policy ? "修改" : "添加"}从机存储策略 + {props.policy + ? t("editRemoteStoragePolicy") + : t("addRemoteStoragePolicy")} {steps.map((label, index) => { @@ -255,7 +259,9 @@ export default function RemoteGuide(props) { const labelProps = {}; if (label.optional) { labelProps.optional = ( - 可选 + + {t("optional")} + ); } if (isStepSkipped(index)) { @@ -263,7 +269,9 @@ export default function RemoteGuide(props) { } return ( - {label.title} + + {t(label.title)} + ); })} @@ -278,8 +286,7 @@ export default function RemoteGuide(props) { }} > - 从机存储策略允许你使用同样运行了 Cloudreve - 的服务器作为存储端, 用户上传下载流量通过 HTTP 直传。 + {t("remoteDescription")}
@@ -288,8 +295,7 @@ export default function RemoteGuide(props) {
- 将和主站相同版本的 Cloudreve - 程序拷贝至要作为从机的服务器上。 + {t("remoteCopyBinaryDescription")}
@@ -300,13 +306,12 @@ export default function RemoteGuide(props) {
- 下方为系统为您随机生成的从机端密钥,一般无需改动,如果有自定义需求, - 可将您的密钥填入下方: + {t("remoteSecretDescription")}
- 从机密钥 + {t("remoteSecret")}
- 修改从机配置文件。 + {t("modifyRemoteConfig")}
- 在从机端 Cloudreve 的同级目录下新建 - conf.ini - 文件,填入从机配置,启动/重启从机端 Cloudreve。 - 以下为一个可供参考的配置例子,其中密钥部分已帮您填写为上一步所生成的。 + ]} + />
                                 [System]
@@ -355,24 +361,41 @@ export default function RemoteGuide(props) {
                                 AllowHeaders = *
- 从机端配置文件格式大致与主站端相同,区别在于: + {t("remoteConfigDifference")}
  • - System - 分区下的 - mode - 字段必须更改为slave + , + , + , + ]} + />
  • - 必须指定Slave分区下的 - Secret - 字段,其值为第二步里填写或生成的密钥。 + , + , + ]} + />
  • - 必须启动跨域配置,即CORS - 字段的内容, - 具体可参考上文范例或官方文档。如果配置不正确,用户将无法通过 - Web 端向从机上传文件。 + ]} + />
@@ -385,16 +408,14 @@ export default function RemoteGuide(props) {
- 填写从机地址。 + {t("inputRemoteAddress")}
- 如果主站启用了 - HTTPS,从机也需要启用,并在下方填入 HTTPS - 协议的地址。 + {t("inputRemoteAddressDes")}
- 从机地址 + {t("remoteAddress")}
- 完成以上步骤后,你可以点击下方的测试按钮测试通信是否正常。 + {t("testCommunicationDes")}
@@ -436,7 +457,7 @@ export default function RemoteGuide(props) { variant={"contained"} color={"primary"} > - 下一步 + {t("next")}
@@ -456,25 +477,23 @@ export default function RemoteGuide(props) {
- 请在下方输入文件的存储目录路径,可以为绝对路径或相对路径(相对于 - 从机的 - Cloudreve)。路径中可以使用魔法变量,文件在上传时会自动替换这些变量为相应值; - 可用魔法变量可参考{" "} - { - e.preventDefault(); - setMagicVar("path"); - }} - > - 路径魔法变量列表 - {" "} - 。 + setMagicVar("file")} + />, + ]} + />
- 存储目录 + {t("pathOfFolderToStoreFiles")} } - label="开启重命名" + label={t("autoRenameStoredFile")} /> } - label="不开启" + label={t("keepOriginalFileName")} /> @@ -537,7 +556,7 @@ export default function RemoteGuide(props) {
- 命名规则 + {t("renameRule")} setActiveStep(0)} > - 上一步 + {t("back")}
@@ -588,9 +607,9 @@ export default function RemoteGuide(props) {
- 是否允许获取文件永久直链? + {t("enableGettingPermanentSourceLink")}
- 开启后,用户可以请求获得能直接访问到文件内容的直链,适用于图床应用或自用。 + {t("enableGettingPermanentSourceLinkDes")}
@@ -608,14 +627,14 @@ export default function RemoteGuide(props) { control={ } - label="允许" + label={t("allowed")} /> } - label="禁止" + label={t("forbidden")} /> @@ -630,10 +649,9 @@ export default function RemoteGuide(props) {
- 是否要对下载/直链使用 CDN? + {t("useCDN")}
- 开启后,用户访问文件时的 URL - 中的域名部分会被替换为 CDN 域名。 + {t("useCDNDes")}
@@ -659,14 +677,14 @@ export default function RemoteGuide(props) { control={ } - label="使用" + label={t("use")} /> } - label="不使用" + label={t("notUse")} /> @@ -681,7 +699,7 @@ export default function RemoteGuide(props) {
- 选择协议并填写 CDN 域名 + {t("cdnDomain")}
@@ -692,7 +710,7 @@ export default function RemoteGuide(props) { policy.IsOriginLinkEnable === "true" && useCDN === "true" } - label={"CDN 前缀"} + label={t("cdnPrefix")} />
@@ -706,7 +724,7 @@ export default function RemoteGuide(props) { className={classes.button} onClick={() => setActiveStep(1)} > - 上一步 + {t("back")} {" "}
@@ -734,7 +752,7 @@ export default function RemoteGuide(props) {
- 是否限制上传的单文件大小? + {t("limitFileSize")}
@@ -766,14 +784,14 @@ export default function RemoteGuide(props) { control={ } - label="限制" + label={t("limit")} /> } - label="不限制" + label={t("notLimit")} /> @@ -788,7 +806,7 @@ export default function RemoteGuide(props) {
- 输入限制: + {t("enterSizeLimit")}
@@ -811,7 +829,7 @@ export default function RemoteGuide(props) {
- 是否限制上传文件扩展名? + {t("limitFileExt")}
@@ -851,14 +869,14 @@ export default function RemoteGuide(props) { control={ } - label="限制" + label={t("limit")} /> } - label="不限制" + label={t("notLimit")} /> @@ -875,13 +893,12 @@ export default function RemoteGuide(props) {
- 输入允许上传的文件扩展名,多个请以半角逗号 , - 隔开 + {t("enterFileExt")}
- 扩展名列表 + {t("extList")}
- 请指定分片上传时的分片大小,填写为 0 - 表示不使用分片上传。 + {t("chunkSizeLabel")}
- 启用分片上传后,用户上传的文件将会被切分成分片逐个上传到存储端,当上传中断后,用户可以选择从上次上传的分片后继续开始上传。 + {t("chunkSizeDes")}
@@ -932,7 +948,7 @@ export default function RemoteGuide(props) { className={classes.button} onClick={() => setActiveStep(2)} > - 上一步 + {t("back")} {" "}
@@ -952,12 +968,12 @@ export default function RemoteGuide(props) {
- 最后一步,为此存储策略命名: + {t("nameThePolicy")}
- 存储策略名 + {t("policyName")} setActiveStep(3)} > - 上一步 + {t("back")} {" "}
@@ -992,10 +1008,10 @@ export default function RemoteGuide(props) { <>
- 存储策略已{props.policy ? "保存" : "添加"}! + {props.policy ? t("policySaved") : t("policyAdded")} - 要使用此存储策略,请到用户组管理页面,为相应用户组绑定此存储策略。 + {t("furtherActions")}
@@ -1004,7 +1020,7 @@ export default function RemoteGuide(props) { className={classes.button} onClick={() => history.push("/admin/policy")} > - 返回存储策略列表 + {t("backToList")}