mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-31 01:52:46 +00:00
二步验证 / 密码管理
This commit is contained in:
parent
bd2695cbd8
commit
cf503b671a
|
|
@ -0,0 +1,152 @@
|
|||
import React, { useCallback } from "react";
|
||||
import {Avatar, IconButton, ListItem, ListItemAvatar, ListItemText, makeStyles} from "@material-ui/core";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import {
|
||||
Nas
|
||||
} from "mdi-material-ui";
|
||||
import Popover from "@material-ui/core/Popover";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import API from "../../middleware/Api";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {toggleSnackbar} from "../../actions";
|
||||
import {Check} from "@material-ui/icons";
|
||||
import Backup from "@material-ui/icons/Backup";
|
||||
import {blue, green} from "@material-ui/core/colors";
|
||||
import List from "@material-ui/core/List";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
uploadFromFile: {
|
||||
backgroundColor: blue[100],
|
||||
color: blue[600]
|
||||
},
|
||||
policySelected: {
|
||||
backgroundColor: green[100],
|
||||
color: green[800]
|
||||
},
|
||||
}));
|
||||
|
||||
const PolicySwitcher = (props) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const [policies, setPolicies] = React.useState({
|
||||
current:{id:"",name:""},
|
||||
options:[],
|
||||
});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleClick = event => {
|
||||
if(policies.current.id === ""){
|
||||
API.get("/user/setting/policies", {})
|
||||
.then(response => {
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
}
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const switchTo = id =>{
|
||||
if(id === policies.current.id){
|
||||
handleClose();
|
||||
return;
|
||||
}
|
||||
API
|
||||
.patch("/user/setting/policy", {
|
||||
id: id
|
||||
})
|
||||
.then(response => {
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
this.setState({
|
||||
loading: ""
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? 'simple-popover' : undefined;
|
||||
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
title={"存储策略"}
|
||||
placement="bottom"
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClick}
|
||||
color="inherit">
|
||||
<Nas/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{policies.options.map(
|
||||
(value, index) => (
|
||||
<ListItem
|
||||
button
|
||||
component="label"
|
||||
key={index}
|
||||
onClick={()=>switchTo(value.id)}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
{value.id === policies.current.id &&
|
||||
<Avatar className={classes.policySelected}>
|
||||
<Check />
|
||||
</Avatar>
|
||||
}
|
||||
{value.id !== policies.current.id &&
|
||||
<Avatar className={classes.uploadFromFile}>
|
||||
<Backup />
|
||||
</Avatar>
|
||||
}
|
||||
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={value.name} />
|
||||
</ListItem>
|
||||
)
|
||||
)}
|
||||
</List>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PolicySwitcher;
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { withRouter } from "react-router-dom";
|
||||
import pathHelper from "../../untils/page";
|
||||
import DarkModeSwitcher from "./DarkModeSwitcher"
|
||||
import PolicySwitcher from "./PolicySwitcher";
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
|
|
@ -103,6 +104,7 @@ class UserAvatarCompoment extends Component {
|
|||
<DarkModeSwitcher position="top"/>
|
||||
{loginCheck && (
|
||||
<>
|
||||
<PolicySwitcher/>
|
||||
<Tooltip title={"设置"} placement="bottom">
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
|
|
|
|||
|
|
@ -41,12 +41,14 @@ import {
|
|||
} from "@material-ui/core";
|
||||
import Backup from "@material-ui/icons/Backup";
|
||||
import SettingsInputHdmi from "@material-ui/icons/SettingsInputHdmi";
|
||||
import { blue, yellow } from "@material-ui/core/colors";
|
||||
import { blue, green, yellow } from "@material-ui/core/colors";
|
||||
import API from "../../middleware/Api";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import { withRouter } from "react-router";
|
||||
import TimeAgo from "timeago-react";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import QRCode from "qrcode-react";
|
||||
import { Check } from "@material-ui/icons";
|
||||
import { transformTime } from "../../untils";
|
||||
|
||||
const styles = theme => ({
|
||||
layout: {
|
||||
|
|
@ -76,6 +78,10 @@ const styles = theme => ({
|
|||
backgroundColor: yellow[100],
|
||||
color: yellow[800]
|
||||
},
|
||||
policySelected: {
|
||||
backgroundColor: green[100],
|
||||
color: green[800]
|
||||
},
|
||||
infoText: {
|
||||
marginRight: "17px"
|
||||
},
|
||||
|
|
@ -132,11 +138,18 @@ const styles = theme => ({
|
|||
},
|
||||
paddingText: {
|
||||
paddingRight: theme.spacing(2)
|
||||
}
|
||||
},
|
||||
qrcode:{
|
||||
width: 128,
|
||||
marginTop: 16,
|
||||
marginRight: 16,
|
||||
},
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {};
|
||||
return {
|
||||
title: state.siteConfig.title
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
|
|
@ -176,13 +189,15 @@ class UserSettingCompoment extends Component {
|
|||
group_expires: 0,
|
||||
policy: {
|
||||
current: {
|
||||
name: "-"
|
||||
name: "-",
|
||||
id: ""
|
||||
},
|
||||
options: []
|
||||
},
|
||||
qq: "",
|
||||
homepage: true,
|
||||
two_factor: "",
|
||||
two_fa_secret: "",
|
||||
prefer_theme: "",
|
||||
themes: {}
|
||||
}
|
||||
|
|
@ -276,24 +291,11 @@ class UserSettingCompoment extends Component {
|
|||
};
|
||||
|
||||
changePolicy = id => {
|
||||
axios
|
||||
.post("/Member/ChangePolicy", {
|
||||
id: id
|
||||
})
|
||||
API.patch("/user/setting/policy", {
|
||||
id: id
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.error === "1") {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
response.data.msg,
|
||||
"error"
|
||||
);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
this.setState({
|
||||
loading: ""
|
||||
});
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.toggleSnackbar(
|
||||
|
|
@ -356,8 +358,8 @@ class UserSettingCompoment extends Component {
|
|||
this.setState({
|
||||
settings: {
|
||||
...this.state.settings,
|
||||
qq:false,
|
||||
},
|
||||
qq: false
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.location.href = response.data;
|
||||
|
|
@ -415,26 +417,22 @@ class UserSettingCompoment extends Component {
|
|||
};
|
||||
|
||||
handleToggle = () => {
|
||||
axios
|
||||
.post("/Member/HomePage", {
|
||||
status: this.state.homePage === "1" ? "false" : "true"
|
||||
})
|
||||
API.patch("/user/setting/homepage", {
|
||||
status: !this.state.settings.homepage
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.error === "1") {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
response.data.msg,
|
||||
"error"
|
||||
);
|
||||
} else {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
"设置已保存",
|
||||
"success"
|
||||
);
|
||||
}
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
"设置已保存",
|
||||
"success"
|
||||
);
|
||||
this.setState({
|
||||
settings: {
|
||||
...this.state.settings,
|
||||
homepage: !this.state.settings.homepage
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.toggleSnackbar(
|
||||
|
|
@ -444,9 +442,6 @@ class UserSettingCompoment extends Component {
|
|||
"error"
|
||||
);
|
||||
});
|
||||
this.setState({
|
||||
homePage: this.state.homePage === "1" ? "0" : "1"
|
||||
});
|
||||
};
|
||||
|
||||
changhePwd = () => {
|
||||
|
|
@ -457,29 +452,26 @@ class UserSettingCompoment extends Component {
|
|||
"两次密码输入不一致",
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
loading: "changePassword"
|
||||
});
|
||||
axios
|
||||
.post("/Member/ChangePwd", {
|
||||
origin: this.state.oldPwd,
|
||||
new: this.state.newPwd
|
||||
})
|
||||
API.patch("/user/setting/password", {
|
||||
old: this.state.oldPwd,
|
||||
new: this.state.newPwd
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.error === "1") {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
response.data.msg,
|
||||
"error"
|
||||
);
|
||||
this.setState({
|
||||
loading: ""
|
||||
});
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
"密码已更新",
|
||||
"success"
|
||||
);
|
||||
this.setState({
|
||||
loading: ""
|
||||
});
|
||||
this.handleClose();
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.toggleSnackbar(
|
||||
|
|
@ -575,28 +567,42 @@ class UserSettingCompoment extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
init2FA = () => {
|
||||
API.get("/user/setting/2fa")
|
||||
.then(response => {
|
||||
this.setState({
|
||||
two_fa_secret: response.data
|
||||
});
|
||||
this.setState({ twoFactor: true });
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
twoFactor = () => {
|
||||
this.setState({
|
||||
loading: "twoFactor"
|
||||
});
|
||||
axios
|
||||
.post("/Member/TwoFactorConfirm", {
|
||||
API.patch("/user/setting/2fa", {
|
||||
code: this.state.authCode
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.error === "1") {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
response.data.msg,
|
||||
"error"
|
||||
);
|
||||
this.setState({
|
||||
loading: ""
|
||||
});
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
"设定已保存",
|
||||
"success"
|
||||
);
|
||||
this.setState({
|
||||
loading: ""
|
||||
});
|
||||
this.handleClose();
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.toggleSnackbar(
|
||||
|
|
@ -776,10 +782,7 @@ class UserSettingCompoment extends Component {
|
|||
</div>
|
||||
)}
|
||||
<Divider />
|
||||
<ListItem
|
||||
button
|
||||
onClick={() => this.bindQQ()}
|
||||
>
|
||||
<ListItem button onClick={() => this.bindQQ()}>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<SettingsInputHdmi />
|
||||
</ListItemIcon>
|
||||
|
|
@ -811,7 +814,7 @@ class UserSettingCompoment extends Component {
|
|||
<ListItemIcon className={classes.iconFix}>
|
||||
<Backup />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="上传策略" />
|
||||
<ListItemText primary="存储策略" />
|
||||
|
||||
<ListItemSecondaryAction
|
||||
className={classes.flexContainer}
|
||||
|
|
@ -842,7 +845,9 @@ class UserSettingCompoment extends Component {
|
|||
className={classes.infoText}
|
||||
color="textSecondary"
|
||||
>
|
||||
{user.created_at}
|
||||
{transformTime(
|
||||
parseInt(user.created_at + "000")
|
||||
)}
|
||||
</Typography>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
|
|
@ -888,12 +893,7 @@ class UserSettingCompoment extends Component {
|
|||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
this.setState({ twoFactor: true })
|
||||
}
|
||||
>
|
||||
<ListItem button onClick={() => this.init2FA()}>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<VerifyIcon />
|
||||
</ListItemIcon>
|
||||
|
|
@ -1037,7 +1037,7 @@ class UserSettingCompoment extends Component {
|
|||
open={this.state.changePolicy}
|
||||
onClose={this.handleClose}
|
||||
>
|
||||
<DialogTitle>切换上传策略</DialogTitle>
|
||||
<DialogTitle>切换存储策略</DialogTitle>
|
||||
<List>
|
||||
{this.state.settings.policy.options.map(
|
||||
(value, index) => (
|
||||
|
|
@ -1047,9 +1047,30 @@ class UserSettingCompoment extends Component {
|
|||
key={index}
|
||||
onClick={() => this.changePolicy(value.id)}
|
||||
>
|
||||
<Avatar className={classes.uploadFromFile}>
|
||||
<Backup />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
{value.id ===
|
||||
this.state.settings.policy.current
|
||||
.id && (
|
||||
<Avatar
|
||||
className={
|
||||
classes.policySelected
|
||||
}
|
||||
>
|
||||
<Check />
|
||||
</Avatar>
|
||||
)}
|
||||
{value.id !==
|
||||
this.state.settings.policy.current
|
||||
.id && (
|
||||
<Avatar
|
||||
className={
|
||||
classes.uploadFromFile
|
||||
}
|
||||
>
|
||||
<Backup />
|
||||
</Avatar>
|
||||
)}
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={value.name} />
|
||||
</ListItem>
|
||||
)
|
||||
|
|
@ -1212,7 +1233,16 @@ class UserSettingCompoment extends Component {
|
|||
<DialogTitle>启用二步验证</DialogTitle>
|
||||
<DialogContent>
|
||||
<div className={classes.flexContainerResponse}>
|
||||
<img alt="qrcode" src="/Member/EnableTwoFactor" />
|
||||
<div className={classes.qrcode}>
|
||||
<QRCode
|
||||
value={
|
||||
"otpauth://totp/" +
|
||||
this.props.title +
|
||||
"?secret=" +
|
||||
this.state.two_fa_secret
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.desText}>
|
||||
<Typography>
|
||||
请使用任意二步验证APP或者支持二步验证的密码管理软件扫描左侧二维码添加本站。扫描完成后请填写二步验证APP给出的6位验证码以开启二步验证。
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ export default function WebDAV(props) {
|
|||
{tab === 0 && (
|
||||
<div>
|
||||
<Alert severity="info">
|
||||
WebDAV的登陆用户名统一为:{user.user_name}{" "}
|
||||
WebDAV的地址为:{window.location.origin + "/dav"};登陆用户名统一为:{user.user_name}{" "}
|
||||
;密码为所创建账号的密码。
|
||||
</Alert>
|
||||
<TableContainer className={classes.tableContainer}>
|
||||
|
|
|
|||
|
|
@ -154,4 +154,19 @@ export function basename(path){
|
|||
let pathList = path.split("/");
|
||||
pathList.pop()
|
||||
return pathList.join("/") === "" ? "/" : pathList.join("/")
|
||||
}
|
||||
|
||||
export function transformTime(timestamp = +new Date()) {
|
||||
if (timestamp) {
|
||||
var time = new Date(timestamp);
|
||||
var y = time.getFullYear(); //getFullYear方法以四位数字返回年份
|
||||
var M = time.getMonth() + 1; // getMonth方法从 Date 对象返回月份 (0 ~ 11),返回结果需要手动加一
|
||||
var d = time.getDate(); // getDate方法从 Date 对象返回一个月中的某一天 (1 ~ 31)
|
||||
var h = time.getHours(); // getHours方法返回 Date 对象的小时 (0 ~ 23)
|
||||
var m = time.getMinutes(); // getMinutes方法返回 Date 对象的分钟 (0 ~ 59)
|
||||
var s = time.getSeconds(); // getSeconds方法返回 Date 对象的秒数 (0 ~ 59)
|
||||
return y + '-' + M + '-' + d + ' ' + h + ':' + m + ':' + s;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue