Configurations guide for node aria2

This commit is contained in:
HFO4 2021-10-31 09:32:50 +08:00
parent b190979ca5
commit 9be016c2bf
3 changed files with 655 additions and 57 deletions

View File

@ -0,0 +1,429 @@
import { lighten, makeStyles } from "@material-ui/core/styles";
import React, { useCallback, useState } from "react";
import Typography from "@material-ui/core/Typography";
import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../../../../actions";
import Link from "@material-ui/core/Link";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import Input from "@material-ui/core/Input";
import RadioGroup from "@material-ui/core/RadioGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Radio from "@material-ui/core/Radio";
import Collapse from "@material-ui/core/Collapse";
import Button from "@material-ui/core/Button";
import Alert from "@material-ui/lab/Alert";
import Box from "@material-ui/core/Box";
import FormHelperText from "@material-ui/core/FormHelperText";
import API from "../../../../middleware/Api";
const useStyles = makeStyles((theme) => ({
stepContent: {
padding: "16px 32px 16px 32px",
},
form: {
maxWidth: 400,
marginTop: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
subStepContainer: {
display: "flex",
marginBottom: 20,
padding: 10,
transition: theme.transitions.create("background-color", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
"&:focus-within": {
backgroundColor: theme.palette.background.default,
},
},
stepNumber: {
width: 20,
height: 20,
backgroundColor: lighten(theme.palette.secondary.light, 0.2),
color: theme.palette.secondary.contrastText,
textAlign: "center",
borderRadius: " 50%",
},
stepNumberContainer: {
marginRight: 10,
},
stepFooter: {
marginTop: 32,
},
button: {
marginRight: theme.spacing(1),
},
viewButtonLabel: { textTransform: "none" },
"@global": {
code: {
color: "rgba(0, 0, 0, 0.87)",
display: "inline-block",
padding: "2px 6px",
fontFamily:
' Consolas, "Liberation Mono", Menlo, Courier, monospace',
borderRadius: "2px",
backgroundColor: "rgba(255,229,100,0.1)",
},
pre: {
margin: "24px 0",
padding: "12px 18px",
overflow: "auto",
direction: "ltr",
borderRadius: "4px",
backgroundColor: "#272c34",
color: "#fff",
fontFamily:
' Consolas, "Liberation Mono", Menlo, Courier, monospace',
},
},
}));
export default function Aria2RPC(props) {
const classes = useStyles();
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const testAria2 = () => {
setLoading(true);
API.post("/admin/node/aria2/test", {
type: props.node.Type,
server: props.node.Server,
secret: props.node.SlaveKey,
rpc: props.node.Aria2Options.Server,
token: props.node.Aria2Options.Token,
})
.then((response) => {
ToggleSnackbar(
"top",
"right",
"连接成功Aria2 版本为:" + response.data,
"success"
);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
const mode = props.node.Type === 0 ? "从机" : "主机";
return (
<form
className={classes.stepContent}
onSubmit={(e) => {
e.preventDefault();
props.onSubmit(e);
}}
>
<Alert severity="info" style={{ marginBottom: 10 }}>
<Typography variant="body2">
Cloudreve 的离线下载功能由{" "}
<Link href={"https://aria2.github.io/"} target={"_blank"}>
Aria2
</Link>{" "}
驱动如需使用请在目标节点服务器上以和运行 Cloudreve
相同的用户身份启动 Aria2 并在 Aria2 的配置文件中开启 RPC
服务
<Box component="span" fontWeight="fontWeightBold">
Aria2 需要和{mode} Cloudreve 进程共用相同的文件系统
</Box>{" "}
更多信息及指引请参考文档的{" "}
<Link
href={"https://docs.cloudreve.org/use/aria2"}
target={"_blank"}
>
离线下载
</Link>{" "}
章节
</Typography>
</Alert>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>1</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
{props.node.Type === 0
? "是否需要此节点接管离线下载任务?"
: "是否需要主机接管离线下载任务?"}
<br />
{props.node.Type === 0
? "开启后,用户的离线下载请求可以被分流到此节点处理。"
: "开启后,用户的离线下载请求可以被分流到主机处理。"}
</Typography>
<div className={classes.form}>
<FormControl required component="fieldset">
<RadioGroup
required
value={props.node.Aria2Enabled}
onChange={props.handleTextChange(
"Aria2Enabled"
)}
row
>
<FormControlLabel
value={"true"}
control={<Radio color={"primary"} />}
label="启用"
/>
<FormControlLabel
value={"false"}
control={<Radio color={"primary"} />}
label="关闭"
/>
</RadioGroup>
</FormControl>
</div>
</div>
</div>
<Collapse in={props.node.Aria2Enabled === "true"}>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>2</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
{props.node.Type === 0
? " 在目标节点服务器上与节点 "
: "在与 "}
Cloudreve 进程相同的文件系统环境下启动 Aria2
进程在启动 Aria2 需要在其配置文件中启用 RPC
服务并设定 RPC
Secret以便后续使用以下为一个供参考的配置
</Typography>
<pre>
# 启用 RPC 服务
<br />
enable-rpc=true
<br />
# RPC 监听端口
<br />
rpc-listen-port=6800
<br />
# RPC 授权令牌可自行设定
<br />
rpc-secure={props.node.Aria2Options.Token}
<br />
</pre>
<Alert severity="info" style={{ marginBottom: 10 }}>
<Typography variant="body2">
推荐在日常启动流程中先启动 Aria2再启动节点
Cloudreve这样节点 Cloudreve 可以向 Aria2
订阅事件通知下载状态变更处理更及时当然如果没有这一流程节点
Cloudreve 也会通过轮询追踪任务状态
</Typography>
</Alert>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>3</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
在下方填写{mode} Cloudreve Aria2 通信的 RPC
服务地址一般可填写为
<code>http://127.0.0.1:6800/</code>,其中端口号
<code>6800</code>
<code>rpc-listen-port</code>
</Typography>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
RPC 服务器地址
</InputLabel>
<Input
required
type={"url"}
value={props.node.Aria2Options.Server}
onChange={props.handleOptionChange(
"Server"
)}
/>
<FormHelperText id="component-helper-text">
包含端口的完整 RPC
服务器地址例如http://127.0.0.1:6800/,留空表示不启用
Aria2 服务
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>4</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
RPC 授权令牌 Aria2 配置文件中
<code>rpc-secure</code>
</Typography>
<div className={classes.form}>
<Input
value={props.node.Aria2Options.Token}
onChange={props.handleOptionChange("Token")}
/>
</div>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>5</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
在下方填写 Aria2 用作临时下载目录的 节点上的
<strong>绝对路径</strong> Cloudreve
进程需要此目录的读执行权限
</Typography>
<div className={classes.form}>
<Input
value={props.node.Aria2Options.TempPath}
onChange={props.handleOptionChange("TempPath")}
/>
</div>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>5</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
在下方按需要填写一些 Aria2 额外参数信息
</Typography>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
状态刷新间隔 ()
</InputLabel>
<Input
type={"number"}
inputProps={{
step: 1,
min: 1,
}}
required
value={props.node.Aria2Options.Interval}
onChange={props.handleOptionChange(
"Interval"
)}
/>
<FormHelperText id="component-helper-text">
Cloudreve Aria2 请求刷新任务状态的间隔
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
RPC 调用超时 ()
</InputLabel>
<Input
type={"number"}
inputProps={{
step: 1,
min: 1,
}}
required
value={props.node.Aria2Options.Timeout}
onChange={props.handleOptionChange(
"Timeout"
)}
/>
<FormHelperText id="component-helper-text">
调用 RPC 服务时最长等待时间
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
全局任务参数
</InputLabel>
<Input
multiline
required
value={props.node.Aria2Options.Options}
onChange={props.handleOptionChange(
"Options"
)}
/>
<FormHelperText id="component-helper-text">
创建下载任务时携带的额外设置参数 JSON
编码后的格式书写您可也可以将这些设置写在
Aria2 配置文件里可用参数请查阅官方文档
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>6</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
完成以上步骤后你可以点击下方的测试按钮测试{mode}
Cloudreve Aria2 通信是否正常
{props.node.Type === 0 &&
"在进行测试前请先确保您已进行并通过上一页面中的“从机通信测试”。"}
</Typography>
<div className={classes.form}>
<Button
disabled={loading}
variant={"outlined"}
onClick={() => testAria2()}
color={"primary"}
>
测试 Aria2 通信
</Button>
</div>
</div>
</div>
</Collapse>
<div className={classes.stepFooter}>
{props.activeStep !== 0 && (
<Button
color={"default"}
className={classes.button}
onClick={props.onBack}
>
上一步
</Button>
)}
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
下一步
</Button>
</div>
</form>
);
}

View File

@ -1,29 +1,15 @@
import { lighten, makeStyles } from "@material-ui/core/styles";
import React, { useCallback, useState } from "react";
import Stepper from "@material-ui/core/Stepper";
import StepLabel from "@material-ui/core/StepLabel";
import Step from "@material-ui/core/Step";
import Typography from "@material-ui/core/Typography";
import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../../../../actions";
import Link from "@material-ui/core/Link";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import Input from "@material-ui/core/Input";
import RadioGroup from "@material-ui/core/RadioGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Radio from "@material-ui/core/Radio";
import Collapse from "@material-ui/core/Collapse";
import Button from "@material-ui/core/Button";
import API from "../../../../middleware/Api";
import MagicVar from "../../Dialogs/MagicVar";
import DomainInput from "../../Common/DomainInput";
import SizeInput from "../../Common/SizeInput";
import { useHistory } from "react-router";
import { getNumber, randomStr } from "../../../../utils";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Alert from "@material-ui/lab/Alert";
import Box from "@material-ui/core/Box";
const useStyles = makeStyles((theme) => ({
stepContent: {
@ -78,40 +64,49 @@ const useStyles = makeStyles((theme) => ({
borderRadius: "2px",
backgroundColor: "rgba(255,229,100,0.1)",
},
pre: {
margin: "24px 0",
padding: "12px 18px",
overflow: "auto",
direction: "ltr",
borderRadius: "4px",
backgroundColor: "#272c34",
color: "#fff",
fontFamily:
' Consolas, "Liberation Mono", Menlo, Courier, monospace',
},
},
}));
export default function Communication(props) {
const classes = useStyles();
const history = useHistory();
const [activeStep, setActiveStep] = useState(0);
const [skipped, setSkipped] = React.useState(new Set());
const [node, setNode] = useState(
props.node
? props.node
: {
Status: 1,
Type: 0,
Aria2Enabled: false,
Server: "https://example.com:5212",
SlaveKey: randomStr(64),
MasterKey: randomStr(64),
Aria2Options: {},
}
);
const isStepSkipped = (step) => {
return skipped.has(step);
};
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const testSlave = () => {
setLoading(true);
// 测试路径是否可用
API.post("/admin/policy/test/slave", {
server: props.node.Server,
secret: props.node.SlaveKey,
})
.then(() => {
ToggleSnackbar("top", "right", "通信正常", "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<form
className={classes.stepContent}
@ -121,13 +116,154 @@ export default function Communication(props) {
}}
>
<Alert severity="info" style={{ marginBottom: 10 }}>
从机存储策略允许你使用同样运行了 Cloudreve 的服务器作为存储端
用户上传下载流量通过 HTTP 直传
您可以添加同样运行了 Cloudreve 的服务器作为从机端
正常运行工作的从机端可以为主机分担某些异步任务如离线下载及转存
请参考下面向导部署并配置连接 Cloudreve 从机节点
<Box fontWeight="fontWeightBold">
如果你已经在目标服务器上部署了从机存储策略您可以跳过本页面的某些步骤
只将从机密钥服务器地址在这里填写并保持与从机存储策略中一致即可
</Box>
在后续版本中从机存储策略的相关配置会合并到这里
</Alert>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>1</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
将和主站相同版本的 Cloudreve
程序拷贝至要作为从机的服务器上
</Typography>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>2</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
下方为系统为您随机生成的从机端密钥一般无需改动如果有自定义需求
可将您的密钥填入下方
</Typography>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
从机密钥
</InputLabel>
<Input
required
inputProps={{
minlength: 64,
}}
value={props.node.SlaveKey}
onChange={props.handleTextChange("SlaveKey")}
/>
</FormControl>
</div>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>3</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
修改从机配置文件
<br />
在从机端 Cloudreve 的同级目录下新建
<code>conf.ini</code>
文件填入从机配置启动/重启从机端 Cloudreve
以下为一个可供参考的配置例子其中密钥部分已帮您填写为上一步所生成的
</Typography>
<pre>
[System]
<br />
Mode = slave
<br />
Listen = :5212
<br />
<br />
[Slave]
<br />
Secret = {props.node.SlaveKey}
<br />
</pre>
<Typography variant={"body2"}>
从机端配置文件格式大致与主站端相同区别在于
<ul>
<li>
<code>System</code>
分区下的
<code>mode</code>
字段必须更改为<code>slave</code>
</li>
<li>
必须指定<code>Slave</code>
<code>Secret</code>
字段其值为第二步里填写或生成的密钥
</li>
</ul>
一个从机 Cloudreve 实例可以对接多个 Cloudreve
主节点只需在所有主节点中添加此从机节点并保持密钥一致即可
</Typography>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>4</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
填写从机地址
<br />
如果主站启用了 HTTPS从机也需要启用并在下方填入 HTTPS
协议的地址
</Typography>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
从机地址
</InputLabel>
<Input
fullWidth
required
type={"url"}
value={props.node.Server}
onChange={props.handleTextChange("Server")}
/>
</FormControl>
</div>
</div>
</div>
<div className={classes.subStepContainer}>
<div className={classes.stepNumberContainer}>
<div className={classes.stepNumber}>5</div>
</div>
<div className={classes.subStepContent}>
<Typography variant={"body2"}>
完成以上步骤后你可以点击下方的测试按钮测试通信是否正常
</Typography>
<div className={classes.form}>
<Button
disabled={loading}
variant={"outlined"}
onClick={() => testSlave()}
color={"primary"}
>
测试从机通信
</Button>
</div>
</div>
</div>
<div className={classes.stepFooter}>
<Button
disabled={props.loading}
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}

View File

@ -1,18 +1,17 @@
import React, { useCallback, useState } from "react";
import React, { useCallback, useMemo, useState } from "react";
import Stepper from "@material-ui/core/Stepper";
import StepLabel from "@material-ui/core/StepLabel";
import Step from "@material-ui/core/Step";
import Typography from "@material-ui/core/Typography";
import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../../../../actions";
import { useHistory } from "react-router";
import { randomStr } from "../../../../utils";
import Button from "@material-ui/core/Button";
import { lighten, makeStyles } from "@material-ui/core/styles";
import Communication from "./Communication";
import Aria2RPC from "./Aria2RPC";
const steps = [
{
slaveOnly: true,
title: "通信配置",
optional: false,
component: function show(p) {
@ -20,17 +19,20 @@ const steps = [
},
},
{
slaveOnly: false,
title: "离线下载",
optional: false,
component: function show(p) {
return <Communication {...p} />;
return <Aria2RPC {...p} />;
},
},
{
slaveOnly: false,
title: "杂项信息",
optional: false,
},
{
slaveOnly: false,
title: "完成",
optional: false,
},
@ -38,7 +40,6 @@ const steps = [
export default function NodeGuide(props) {
const [activeStep, setActiveStep] = useState(0);
const [loading, setLoading] = useState(false);
const [skipped, setSkipped] = React.useState(new Set());
const [node, setNode] = useState(
props.node
@ -46,18 +47,43 @@ export default function NodeGuide(props) {
: {
Status: 1,
Type: 0,
Aria2Enabled: false,
Aria2Enabled: "false",
Server: "https://example.com:5212",
SlaveKey: randomStr(64),
MasterKey: randomStr(64),
Aria2Options: {},
Aria2Options: {
Token: randomStr(32),
Options: "{}",
Interval: 10,
},
}
);
const usedSteps = useMemo(() => {
return steps.filter((step) => !(step.slaveOnly && node.Type === 1));
}, [node.Type]);
const isStepSkipped = (step) => {
return skipped.has(step);
};
const handleTextChange = (name) => (event) => {
setNode({
...node,
[name]: event.target.value,
});
};
const handleOptionChange = (name) => (event) => {
setNode({
...node,
Aria2Options: {
...node.Aria2Options,
[name]: event.target.value,
},
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
@ -71,7 +97,7 @@ export default function NodeGuide(props) {
{props.node ? "修改" : "添加"} 节点
</Typography>
<Stepper activeStep={activeStep}>
{steps.map((label, index) => {
{usedSteps.map((label, index) => {
const stepProps = {};
const labelProps = {};
if (label.optional) {
@ -82,18 +108,25 @@ export default function NodeGuide(props) {
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label.title} {...stepProps}>
<StepLabel {...labelProps}>{label.title}</StepLabel>
</Step>
);
if (!(label.slaveOnly && node.Type === 1)) {
return (
<Step key={label.title} {...stepProps}>
<StepLabel {...labelProps}>
{label.title}
</StepLabel>
</Step>
);
}
})}
</Stepper>
{steps[activeStep].component({
onSubmit: (e) => setActiveStep(1),
loading: loading,
{usedSteps[activeStep].component({
onSubmit: (e) => setActiveStep(activeStep + 1),
node: node,
onBack: (e) => setActiveStep(activeStep - 1),
handleTextChange: handleTextChange,
activeStep: activeStep,
handleOptionChange: handleOptionChange,
})}
</div>
);