This commit is contained in:
2024-02-25 08:27:01 +08:00
commit 20c1fa08dc
279 changed files with 78489 additions and 0 deletions

View File

@@ -0,0 +1,502 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import Input from "@material-ui/core/Input";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Switch from "@material-ui/core/Switch";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import AlertDialog from "../Dialogs/Alert";
import Alert from "@material-ui/lab/Alert";
import FileSelector from "../Common/FileSelector";
import { toggleSnackbar } from "../../../redux/explorer";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
}));
export default function Access() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const { t: tVas } = useTranslation("dashboard", { keyPrefix: "vas" });
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [initCompleted, setInitComplete] = useState(false);
const [options, setOptions] = useState({
register_enabled: "1",
default_group: "1",
email_active: "0",
login_captcha: "0",
reg_captcha: "0",
forget_captcha: "0",
qq_login: "0",
qq_direct_login: "0",
qq_login_id: "",
qq_login_key: "",
authn_enabled: "0",
mail_domain_filter: "0",
mail_domain_filter_list: "",
initial_files: "[]",
});
const [siteURL, setSiteURL] = useState("");
const [groups, setGroups] = useState([]);
const [httpAlert, setHttpAlert] = useState(false);
const handleChange = (name) => (event) => {
let value = event.target.value;
if (event.target.checked !== undefined) {
value = event.target.checked ? "1" : "0";
}
setOptions({
...options,
[name]: value,
});
};
const handleInputChange = (name) => (event) => {
const value = event.target.value;
setOptions({
...options,
[name]: value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
useEffect(() => {
API.post("/admin/setting", {
keys: [...Object.keys(options), "siteURL"],
})
.then((response) => {
setSiteURL(response.data.siteURL);
delete response.data.siteURL;
setOptions(response.data);
setInitComplete(true);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
API.get("/admin/groups")
.then((response) => {
setGroups(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<div>
<AlertDialog
title={t("hint")}
msg={t("webauthnNoHttps")}
onClose={() => setHttpAlert(false)}
open={httpAlert}
/>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("accountManagement")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.register_enabled === "1"
}
onChange={handleChange(
"register_enabled"
)}
/>
}
label={t("allowNewRegistrations")}
/>
<FormHelperText id="component-helper-text">
{t("allowNewRegistrationsDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.email_active === "1"
}
onChange={handleChange(
"email_active"
)}
/>
}
label={t("emailActivation")}
/>
<FormHelperText id="component-helper-text">
{t("emailActivationDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.reg_captcha === "1"
}
onChange={handleChange(
"reg_captcha"
)}
/>
}
label={t("captchaForSignup")}
/>
<FormHelperText id="component-helper-text">
{t("captchaForSignupDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.login_captcha === "1"
}
onChange={handleChange(
"login_captcha"
)}
/>
}
label={t("captchaForLogin")}
/>
<FormHelperText id="component-helper-text">
{t("captchaForLoginDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.forget_captcha === "1"
}
onChange={handleChange(
"forget_captcha"
)}
/>
}
label={t("captchaForReset")}
/>
<FormHelperText id="component-helper-text">
{t("captchaForResetDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.authn_enabled === "1"
}
onChange={(e) => {
if (
!siteURL.startsWith(
"https://"
)
) {
setHttpAlert(true);
return;
}
handleChange("authn_enabled")(
e
);
}}
/>
}
label={t("webauthn")}
/>
<FormHelperText id="component-helper-text">
{t("webauthnDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("defaultGroup")}
</InputLabel>
<Select
value={options.default_group}
onChange={handleInputChange(
"default_group"
)}
required
>
{groups.map((v) => {
if (v.ID === 3) {
return null;
}
return (
<MenuItem
key={v.ID}
value={v.ID.toString()}
>
{v.Name}
</MenuItem>
);
})}
</Select>
<FormHelperText id="component-helper-text">
{t("defaultGroupDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
{initCompleted && (
<FileSelector
label={tVas("initialFiles")}
value={JSON.parse(
options.initial_files
)}
onChange={(v) =>
handleInputChange("initial_files")({
target: { value: v },
})
}
/>
)}
<FormHelperText id="component-helper-text">
{tVas("initialFilesDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{tVas("filterEmailProvider")}
</InputLabel>
<Select
value={options.mail_domain_filter}
onChange={handleInputChange(
"mail_domain_filter"
)}
required
>
{[
tVas("filterEmailProviderDisabled"),
tVas("filterEmailProviderWhitelist"),
tVas("filterEmailProviderBlacklist"),
].map((v, i) => (
<MenuItem key={i} value={i.toString()}>
{v}
</MenuItem>
))}
</Select>
<FormHelperText id="component-helper-text">
{tVas("filterEmailProviderDes")}
</FormHelperText>
</FormControl>
</div>
{options.mail_domain_filter !== "0" && (
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("filterEmailProviderRule")}
</InputLabel>
<Input
value={options.mail_domain_filter_list}
onChange={handleChange(
"mail_domain_filter_list"
)}
multiline
rowsMax="10"
/>
<FormHelperText id="component-helper-text">
{tVas("filterEmailProviderRuleDes")}
</FormHelperText>
</FormControl>
</div>
)}
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{tVas("qqConnect")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<Alert severity="info">
{tVas("qqConnectHint", {
url: siteURL.endsWith("/")
? siteURL + "login/qq"
: siteURL + "/login/qq",
})}
</Alert>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={options.qq_login === "1"}
onChange={handleChange("qq_login")}
/>
}
label={tVas("enableQQConnect")}
/>
<FormHelperText id="component-helper-text">
{tVas("enableQQConnectDes")}
</FormHelperText>
</FormControl>
</div>
{options.qq_login === "1" && (
<>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.qq_direct_login ===
"1"
}
onChange={handleChange(
"qq_direct_login"
)}
/>
}
label={tVas("loginWithoutBinding")}
/>
<FormHelperText id="component-helper-text">
{tVas("loginWithoutBindingDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("appid")}
</InputLabel>
<Input
required
value={options.qq_login_id}
onChange={handleInputChange(
"qq_login_id"
)}
/>
<FormHelperText id="component-helper-text">
{tVas("appidDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("appKey")}
</InputLabel>
<Input
required
value={options.qq_login_key}
onChange={handleInputChange(
"qq_login_key"
)}
/>
<FormHelperText id="component-helper-text">
{tVas("appKeyDes")}
</FormHelperText>
</FormControl>
</div>
</>
)}
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,537 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Input from "@material-ui/core/Input";
import Link from "@material-ui/core/Link";
import { toggleSnackbar } from "../../../redux/explorer";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";
import { Trans, useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
}));
export default function Captcha() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState({
captcha_type: "normal",
captcha_height: "1",
captcha_width: "1",
captcha_mode: "3",
captcha_CaptchaLen: "6",
captcha_ComplexOfNoiseText: "0",
captcha_ComplexOfNoiseDot: "0",
captcha_IsShowHollowLine: "0",
captcha_IsShowNoiseDot: "0",
captcha_IsShowNoiseText: "0",
captcha_IsShowSlimeLine: "0",
captcha_IsShowSineLine: "0",
captcha_ReCaptchaKey: "",
captcha_ReCaptchaSecret: "",
captcha_TCaptcha_CaptchaAppId: "",
captcha_TCaptcha_AppSecretKey: "",
captcha_TCaptcha_SecretId: "",
captcha_TCaptcha_SecretKey: "",
});
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
useEffect(() => {
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
const handleCheckChange = (name) => (event) => {
const value = event.target.checked ? "1" : "0";
setOptions({
...options,
[name]: value,
});
};
return (
<div>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("captcha")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("captchaType")}
</InputLabel>
<Select
value={options.captcha_type}
onChange={handleChange("captcha_type")}
required
>
<MenuItem value={"normal"}>
{t("plainCaptcha")}
</MenuItem>
<MenuItem value={"recaptcha"}>
{t("reCaptchaV2")}
</MenuItem>
<MenuItem value={"tcaptcha"}>
{t("tencentCloudCaptcha")}
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
{t("captchaProvider")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
{options.captcha_type === "normal" && (
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("plainCaptchaTitle")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("captchaWidth")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_width}
onChange={handleChange("captcha_width")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("captchaHeight")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_height}
onChange={handleChange(
"captcha_height"
)}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("captchaLength")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_CaptchaLen}
onChange={handleChange(
"captcha_CaptchaLen"
)}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("captchaMode")}
</InputLabel>
<Select
value={options.captcha_mode}
onChange={handleChange("captcha_mode")}
required
>
<MenuItem value={"0"}>
{t("captchaModeNumber")}
</MenuItem>
<MenuItem value={"1"}>
{t("captchaModeLetter")}
</MenuItem>
<MenuItem value={"2"}>
{t("captchaModeMath")}
</MenuItem>
<MenuItem value={"3"}>
{t("captchaModeNumberLetter")}
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
{t("captchaElement")}
</FormHelperText>
</FormControl>
</div>
{[
{
name: "complexOfNoiseText",
field: "captcha_ComplexOfNoiseText",
},
{
name: "complexOfNoiseDot",
field: "captcha_ComplexOfNoiseDot",
},
{
name: "showHollowLine",
field: "captcha_IsShowHollowLine",
},
{
name: "showNoiseDot",
field: "captcha_IsShowNoiseDot",
},
{
name: "showNoiseText",
field: "captcha_IsShowNoiseText",
},
{
name: "showSlimeLine",
field: "captcha_IsShowSlimeLine",
},
{
name: "showSineLine",
field: "captcha_IsShowSineLine",
},
].map((input) => (
<div key={input.name} className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options[input.field] ===
"1"
}
onChange={handleCheckChange(
input.field
)}
/>
}
label={t(input.name)}
/>
</FormControl>
</div>
))}
</div>
</div>
)}
{options.captcha_type === "recaptcha" && (
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("reCaptchaV2")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("siteKey")}
</InputLabel>
<Input
required
value={options.captcha_ReCaptchaKey}
onChange={handleChange(
"captcha_ReCaptchaKey"
)}
/>
<FormHelperText id="component-helper-text">
<Trans
ns={"dashboard"}
i18nKey={"settings.siteKeyDes"}
components={[
<Link
key={0}
href={
"https://www.google.com/recaptcha/admin/create"
}
target={"_blank"}
/>,
]}
/>
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("siteSecret")}
</InputLabel>
<Input
required
value={
options.captcha_ReCaptchaSecret
}
onChange={handleChange(
"captcha_ReCaptchaSecret"
)}
/>
<FormHelperText id="component-helper-text">
<Trans
ns={"dashboard"}
i18nKey={
"settings.siteSecretDes"
}
components={[
<Link
key={0}
href={
"https://www.google.com/recaptcha/admin/create"
}
target={"_blank"}
/>,
]}
/>
</FormHelperText>
</FormControl>
</div>
</div>
</div>
</div>
)}
{options.captcha_type === "tcaptcha" && (
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("tencentCloudCaptcha")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("secretID")}
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_SecretId
}
onChange={handleChange(
"captcha_TCaptcha_SecretId"
)}
/>
<FormHelperText id="component-helper-text">
<Trans
ns={"dashboard"}
i18nKey={
"settings.siteSecretDes"
}
components={[
<Link
key={0}
href={
"https://console.cloud.tencent.com/capi"
}
target={"_blank"}
/>,
]}
/>
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("secretKey")}
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_SecretKey
}
onChange={handleChange(
"captcha_TCaptcha_SecretKey"
)}
/>
<FormHelperText id="component-helper-text">
<Trans
ns={"dashboard"}
i18nKey={
"settings.secretKeyDes"
}
components={[
<Link
key={0}
href={
"https://console.cloud.tencent.com/capi"
}
target={"_blank"}
/>,
]}
/>
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("tCaptchaAppID")}
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_CaptchaAppId
}
onChange={handleChange(
"captcha_TCaptcha_CaptchaAppId"
)}
/>
<FormHelperText id="component-helper-text">
<Trans
ns={"dashboard"}
i18nKey={
"settings.tCaptchaAppIDDes"
}
components={[
<Link
key={0}
href={
"https://console.cloud.tencent.com/captcha/graphical"
}
target={"_blank"}
/>,
]}
/>
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("tCaptchaSecretKey")}
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_AppSecretKey
}
onChange={handleChange(
"captcha_TCaptcha_AppSecretKey"
)}
/>
<FormHelperText id="component-helper-text">
<Trans
ns={"dashboard"}
i18nKey={
"settings.tCaptchaSecretKeyDes"
}
components={[
<Link
key={0}
href={
"https://console.cloud.tencent.com/captcha/graphical"
}
target={"_blank"}
/>,
]}
/>
</FormHelperText>
</FormControl>
</div>
</div>
</div>
</div>
)}
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,664 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import Input from "@material-ui/core/Input";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import SizeInput from "../Common/SizeInput";
import { toggleSnackbar } from "../../../redux/explorer";
import Alert from "@material-ui/lab/Alert";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";
import { Trans, useTranslation } from "react-i18next";
import Link from "@material-ui/core/Link";
import ThumbGenerators from "./ThumbGenerators";
import PolicySelector from "../Common/PolicySelector";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
}));
export default function ImageSetting() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState({
gravatar_server: "",
avatar_path: "",
avatar_size: "",
avatar_size_l: "",
avatar_size_m: "",
avatar_size_s: "",
thumb_width: "",
thumb_height: "",
office_preview_service: "",
thumb_file_suffix: "",
thumb_max_task_count: "",
thumb_encode_method: "",
thumb_gc_after_gen: "0",
thumb_encode_quality: "",
maxEditSize: "",
wopi_enabled: "0",
wopi_endpoint: "",
wopi_session_timeout: "0",
thumb_builtin_enabled: "0",
thumb_vips_enabled: "0",
thumb_vips_exts: "",
thumb_ffmpeg_enabled: "0",
thumb_vips_path: "",
thumb_ffmpeg_path: "",
thumb_ffmpeg_exts: "",
thumb_ffmpeg_seek: "",
thumb_libreoffice_path: "",
thumb_libreoffice_enabled: "0",
thumb_libreoffice_exts: "",
thumb_proxy_enabled: "0",
thumb_proxy_policy: [],
thumb_max_src_size: "",
});
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
useEffect(() => {
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
response.data.thumb_proxy_policy = JSON.parse(
response.data.thumb_proxy_policy
).map((v) => {
return v.toString();
});
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const reload = () => {
API.get("/admin/reload/wopi")
// eslint-disable-next-line @typescript-eslint/no-empty-function
.then(() => {})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
.then(() => {});
};
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
let value = options[k];
if (k === "thumb_proxy_policy") {
value = JSON.stringify(value.map((v) => parseInt(v)));
}
option.push({
key: k,
value,
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
reload();
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
const handleCheckChange = (name) => (event) => {
const value = event.target.checked ? "1" : "0";
setOptions({
...options,
[name]: value,
});
};
return (
<div>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("avatar")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("gravatarServer")}
</InputLabel>
<Input
type={"url"}
value={options.gravatar_server}
onChange={handleChange("gravatar_server")}
required
/>
<FormHelperText id="component-helper-text">
{t("gravatarServerDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("avatarFilePath")}
</InputLabel>
<Input
value={options.avatar_path}
onChange={handleChange("avatar_path")}
required
/>
<FormHelperText id="component-helper-text">
{t("avatarFilePathDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
{options.avatar_size !== "" && (
<SizeInput
value={options.avatar_size}
onChange={handleChange("avatar_size")}
required
min={0}
max={2147483647}
label={t("avatarSize")}
/>
)}
<FormHelperText id="component-helper-text">
{t("avatarSizeDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("smallAvatarSize")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.avatar_size_s}
onChange={handleChange("avatar_size_s")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("mediumAvatarSize")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.avatar_size_m}
onChange={handleChange("avatar_size_m")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("largeAvatarSize")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.avatar_size_l}
onChange={handleChange("avatar_size_l")}
required
/>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("filePreview")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("officePreviewService")}
</InputLabel>
<Input
value={options.office_preview_service}
onChange={handleChange(
"office_preview_service"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("officePreviewServiceDes")}
<br />
<code>{"{$src}"}</code> -{" "}
{t("officePreviewServiceSrcDes")}
<br />
<code>{"{$srcB64}"}</code> -{" "}
{t("officePreviewServiceSrcB64Des")}
<br />
<code>{"{$name}"}</code> -{" "}
{t("officePreviewServiceName")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
{options.maxEditSize !== "" && (
<SizeInput
value={options.maxEditSize}
onChange={handleChange("maxEditSize")}
required
min={0}
max={2147483647}
label={t("textEditMaxSize")}
/>
)}
<FormHelperText id="component-helper-text">
{t("textEditMaxSizeDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("wopiClient")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<Alert severity="info">
<Trans
ns={"dashboard"}
i18nKey={"settings.wopiClientDes"}
components={[
<Link
key={0}
target={"_blank"}
href={t("wopiDocLink")}
/>,
]}
/>
</Alert>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.wopi_enabled === "1"
}
onChange={handleCheckChange(
"wopi_enabled"
)}
/>
}
label={t("enableWopi")}
/>
</FormControl>
</div>
{options.wopi_enabled === "1" && (
<>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("wopiEndpoint")}
</InputLabel>
<Input
value={options.wopi_endpoint}
onChange={handleChange(
"wopi_endpoint"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("wopiEndpointDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("wopiSessionTtl")}
</InputLabel>
<Input
inputProps={{ min: 1, step: 1 }}
type={"number"}
value={options.wopi_session_timeout}
onChange={handleChange(
"wopi_session_timeout"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("wopiSessionTtlDes")}
</FormHelperText>
</FormControl>
</div>
</>
)}
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("thumbnails")}
</Typography>
<div className={classes.form}>
<Alert severity="info">
<Trans
ns={"dashboard"}
i18nKey={"settings.thumbnailDoc"}
components={[
<Link
key={0}
target={"_blank"}
href={t("thumbnailDocLink")}
/>,
]}
/>
</Alert>
</div>
<Typography variant="subtitle1" gutterBottom>
{t("thumbnailBasic")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("thumbWidth")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.thumb_width}
onChange={handleChange("thumb_width")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("thumbHeight")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.thumb_height}
onChange={handleChange("thumb_height")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("thumbSuffix")}
</InputLabel>
<Input
type={"text"}
value={options.thumb_file_suffix}
onChange={handleChange("thumb_file_suffix")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("thumbConcurrent")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: -1,
step: 1,
}}
value={options.thumb_max_task_count}
onChange={handleChange(
"thumb_max_task_count"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("thumbConcurrentDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("thumbFormat")}
</InputLabel>
<Input
type={"test"}
value={options.thumb_encode_method}
onChange={handleChange(
"thumb_encode_method"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("thumbFormatDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("thumbQuality")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
max: 100,
}}
value={options.thumb_encode_quality}
onChange={handleChange(
"thumb_encode_quality"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("thumbQualityDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
{options.thumb_max_src_size !== "" && (
<SizeInput
value={options.thumb_max_src_size}
onChange={handleChange(
"thumb_max_src_size"
)}
required
min={0}
max={2147483647}
label={t("thumbMaxSize")}
/>
)}
<FormHelperText id="component-helper-text">
{t("thumbMaxSizeDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.thumb_gc_after_gen ===
"1"
}
onChange={handleCheckChange(
"thumb_gc_after_gen"
)}
/>
}
label={t("thumbGC")}
/>
</FormControl>
</div>
</div>
<Typography variant="subtitle1" gutterBottom>
{t("generators")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<ThumbGenerators
options={options}
setOptions={setOptions}
/>
</div>
</div>
<Typography variant="subtitle1" gutterBottom>
{t("generatorProxy")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<Alert severity="info">
{t("generatorProxyWarning")}
</Alert>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.thumb_proxy_enabled ===
"1"
}
onChange={handleCheckChange(
"thumb_proxy_enabled"
)}
/>
}
label={t("enableThumbProxy")}
/>
</FormControl>
</div>
{options.thumb_proxy_enabled === "1" && (
<>
<div className={classes.form}>
<PolicySelector
value={options.thumb_proxy_policy}
onChange={handleChange(
"thumb_proxy_policy"
)}
filter={(t) => t.Type !== "local"}
label={t("proxyPolicyList")}
helperText={t("proxyPolicyListDes")}
/>
</div>
</>
)}
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,450 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import Input from "@material-ui/core/Input";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";
import { useDispatch } from "react-redux";
import Dialog from "@material-ui/core/Dialog";
import DialogTitle from "@material-ui/core/DialogTitle";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import TextField from "@material-ui/core/TextField";
import DialogActions from "@material-ui/core/DialogActions";
import { toggleSnackbar } from "../../../redux/explorer";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
buttonMargin: {
marginLeft: 8,
},
}));
export default function Mail() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const { t: tVas } = useTranslation("dashboard", { keyPrefix: "vas" });
const { t: tGlobal } = useTranslation("common");
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [test, setTest] = useState(false);
const [tesInput, setTestInput] = useState("");
const [options, setOptions] = useState({
fromName: "",
fromAdress: "",
smtpHost: "",
smtpPort: "",
replyTo: "",
smtpUser: "",
smtpPass: "",
smtpEncryption: "",
mail_keepalive: "30",
over_used_template: "",
mail_activation_template: "",
mail_reset_pwd_template: "",
});
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const handleCheckChange = (name) => (event) => {
let value = event.target.value;
if (event.target.checked !== undefined) {
value = event.target.checked ? "1" : "0";
}
setOptions({
...options,
[name]: value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
useEffect(() => {
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const sendTestMail = () => {
setLoading(true);
API.post("/admin/test/mail", {
to: tesInput,
})
.then(() => {
ToggleSnackbar("top", "right", t("testMailSent"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
const reload = () => {
API.get("/admin/reload/email")
// eslint-disable-next-line @typescript-eslint/no-empty-function
.then(() => {})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
.then(() => {});
};
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
reload();
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<div>
<Dialog
open={test}
onClose={() => setTest(false)}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">
{t("testSMTPSettings")}
</DialogTitle>
<DialogContent>
<DialogContentText>
<Typography>{t("testSMTPTooltip")}</Typography>
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label={t("recipient")}
value={tesInput}
onChange={(e) => setTestInput(e.target.value)}
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setTest(false)} color="default">
{tGlobal("cancel")}
</Button>
<Button
onClick={() => sendTestMail()}
disabled={loading}
color="primary"
>
{t("send")}
</Button>
</DialogActions>
</Dialog>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("smtp")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("senderName")}
</InputLabel>
<Input
value={options.fromName}
onChange={handleChange("fromName")}
required
/>
<FormHelperText id="component-helper-text">
{t("senderNameDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("senderAddress")}
</InputLabel>
<Input
type={"email"}
required
value={options.fromAdress}
onChange={handleChange("fromAdress")}
/>
<FormHelperText id="component-helper-text">
{t("senderAddressDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("smtpServer")}
</InputLabel>
<Input
value={options.smtpHost}
onChange={handleChange("smtpHost")}
required
/>
<FormHelperText id="component-helper-text">
{t("smtpServerDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("smtpPort")}
</InputLabel>
<Input
inputProps={{ min: 1, step: 1 }}
type={"number"}
value={options.smtpPort}
onChange={handleChange("smtpPort")}
required
/>
<FormHelperText id="component-helper-text">
{t("smtpPortDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("smtpUsername")}
</InputLabel>
<Input
value={options.smtpUser}
onChange={handleChange("smtpUser")}
required
/>
<FormHelperText id="component-helper-text">
{t("smtpUsernameDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("smtpPassword")}
</InputLabel>
<Input
type={"password"}
value={options.smtpPass}
onChange={handleChange("smtpPass")}
required
/>
<FormHelperText id="component-helper-text">
{t("smtpPasswordDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("replyToAddress")}
</InputLabel>
<Input
value={options.replyTo}
onChange={handleChange("replyTo")}
required
/>
<FormHelperText id="component-helper-text">
{t("replyToAddressDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.smtpEncryption === "1"
}
onChange={handleCheckChange(
"smtpEncryption"
)}
/>
}
label={t("enforceSSL")}
/>
<FormHelperText id="component-helper-text">
{t("enforceSSLDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("smtpTTL")}
</InputLabel>
<Input
inputProps={{ min: 1, step: 1 }}
type={"number"}
value={options.mail_keepalive}
onChange={handleChange("mail_keepalive")}
required
/>
<FormHelperText id="component-helper-text">
{t("smtpTTLDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("emailTemplates")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("activateNewUser")}
</InputLabel>
<Input
value={options.mail_activation_template}
onChange={handleChange(
"mail_activation_template"
)}
multiline
rowsMax="10"
required
/>
<FormHelperText id="component-helper-text">
{t("activateNewUserDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("overuseReminder")}
</InputLabel>
<Input
value={options.over_used_template}
onChange={handleChange(
"over_used_template"
)}
multiline
rowsMax="10"
required
/>
<FormHelperText id="component-helper-text">
{tVas("overuseReminderDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("resetPassword")}
</InputLabel>
<Input
value={options.mail_reset_pwd_template}
onChange={handleChange(
"mail_reset_pwd_template"
)}
multiline
rowsMax="10"
required
/>
<FormHelperText id="component-helper-text">
{t("resetPasswordDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
{" "}
<Button
className={classes.buttonMargin}
variant={"outlined"}
color={"primary"}
onClick={() => setTest(true)}
>
{t("sendTestEmail")}
</Button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,517 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import Input from "@material-ui/core/Input";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import { toggleSnackbar } from "../../../redux/explorer";
import { Trans, useTranslation } from "react-i18next";
import InputAdornment from "@material-ui/core/InputAdornment";
import { green } from "@material-ui/core/colors";
import { Cancel, CheckCircle, Sync } from "@material-ui/icons";
import IconButton from "@material-ui/core/IconButton";
import { Tooltip } from "@material-ui/core";
import Alert from "@material-ui/lab/Alert";
import Link from "@material-ui/core/Link";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
}));
export default function SiteInformation() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const { t: tVas } = useTranslation("dashboard", { keyPrefix: "vas" });
const { t: tGlobal } = useTranslation("dashboard");
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState({
siteURL: "",
siteName: "",
siteTitle: "",
siteKeywords: "",
siteDes: "",
siteScript: "",
siteNotice: "",
pwa_small_icon: "",
pwa_medium_icon: "",
pwa_large_icon: "",
pwa_display: "",
pwa_theme_color: "",
pwa_background_color: "",
vol_content: "",
show_app_promotion: "0",
app_feedback_link: "",
app_forum_link: "",
});
const vol = useMemo(() => {
if (options.vol_content) {
const volJson = atob(options.vol_content);
return JSON.parse(volJson);
}
}, [options]);
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const handleOptionChange = (name) => (event) => {
let value = event.target.value;
if (event.target.checked !== undefined) {
value = event.target.checked ? "1" : "0";
}
setOptions({
...options,
[name]: value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const refresh = () =>
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
useEffect(() => {
refresh();
// eslint-disable-next-line
}, []);
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
const syncVol = () => {
setLoading(true);
API.get("/admin/vol/sync")
.then(() => {
refresh();
ToggleSnackbar("top", "right", tVas("volSynced"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<div>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("basicInformation")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("mainTitle")}
</InputLabel>
<Input
value={options.siteName}
onChange={handleChange("siteName")}
required
/>
<FormHelperText id="component-helper-text">
{t("mainTitleDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("subTitle")}
</InputLabel>
<Input
value={options.siteTitle}
onChange={handleChange("siteTitle")}
/>
<FormHelperText id="component-helper-text">
{t("subTitleDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("siteKeywords")}
</InputLabel>
<Input
value={options.siteKeywords}
onChange={handleChange("siteKeywords")}
/>
<FormHelperText id="component-helper-text">
{t("siteKeywordsDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("siteDescription")}
</InputLabel>
<Input
value={options.siteDes}
onChange={handleChange("siteDes")}
/>
<FormHelperText id="component-helper-text">
{t("siteDescriptionDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("siteURL")}
</InputLabel>
<Input
type={"url"}
value={options.siteURL}
onChange={handleChange("siteURL")}
required
/>
<FormHelperText id="component-helper-text">
{t("siteURLDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("customFooterHTML")}
</InputLabel>
<Input
multiline
value={options.siteScript}
onChange={handleChange("siteScript")}
/>
<FormHelperText id="component-helper-text">
{t("customFooterHTMLDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("announcement")}
</InputLabel>
<Input
placeholder={t("supportHTML")}
multiline
value={options.siteNotice}
onChange={handleChange("siteNotice")}
/>
<FormHelperText id="component-helper-text">
{t("announcementDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{tVas("mobileApp")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<Alert severity="info">
<Typography variant="body2">
<Trans
ns={"dashboard"}
i18nKey={"vas.volPurchase"}
components={[
<Link
key={0}
href={
"https://cloudreve.org/login"
}
target={"_blank"}
/>,
<Link
key={1}
href={
"https://cloudreve.org/ios"
}
target={"_blank"}
/>,
]}
/>
</Typography>
</Alert>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("iosVol")}
</InputLabel>
<Input
startAdornment={
<InputAdornment position="start">
{vol ? (
<CheckCircle
style={{
color: green[500],
}}
/>
) : (
<Cancel color={"error"} />
)}
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
<Tooltip
title={tVas("syncLicense")}
>
<IconButton
disabled={loading}
onClick={() => syncVol()}
aria-label="toggle password visibility"
>
<Sync />
</IconButton>
</Tooltip>
</InputAdornment>
}
readOnly
value={
vol ? vol.domain : tGlobal("share.none")
}
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.show_app_promotion ===
"1"
}
onChange={handleOptionChange(
"show_app_promotion"
)}
/>
}
label={tVas("showAppPromotion")}
/>
<FormHelperText id="component-helper-text">
{tVas("showAppPromotionDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("appFeedback")}
</InputLabel>
<Input
value={options.app_feedback_link}
onChange={handleChange("app_feedback_link")}
/>
<FormHelperText id="component-helper-text">
{tVas("appLinkDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{tVas("appForum")}
</InputLabel>
<Input
value={options.app_forum_link}
onChange={handleChange("app_forum_link")}
/>
<FormHelperText id="component-helper-text">
{tVas("appLinkDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("pwa")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("smallIcon")}
</InputLabel>
<Input
value={options.pwa_small_icon}
onChange={handleChange("pwa_small_icon")}
/>
<FormHelperText id="component-helper-text">
{t("smallIconDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("mediumIcon")}
</InputLabel>
<Input
value={options.pwa_medium_icon}
onChange={handleChange("pwa_medium_icon")}
/>
<FormHelperText id="component-helper-text">
{t("mediumIconDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("largeIcon")}
</InputLabel>
<Input
value={options.pwa_large_icon}
onChange={handleChange("pwa_large_icon")}
/>
<FormHelperText id="component-helper-text">
{t("largeIconDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("displayMode")}
</InputLabel>
<Select
value={options.pwa_display}
onChange={handleChange("pwa_display")}
>
<MenuItem value={"fullscreen"}>
fullscreen
</MenuItem>
<MenuItem value={"standalone"}>
standalone
</MenuItem>
<MenuItem value={"minimal-ui"}>
minimal-ui
</MenuItem>
<MenuItem value={"browser"}>
browser
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
{t("displayModeDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("themeColor")}
</InputLabel>
<Input
value={options.pwa_theme_color}
onChange={handleChange("pwa_theme_color")}
/>
<FormHelperText id="component-helper-text">
{t("themeColorDes")}
</FormHelperText>
</FormControl>
</div>
</div>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("backgroundColor")}
</InputLabel>
<Input
value={options.pwa_background_color}
onChange={handleChange(
"pwa_background_color"
)}
/>
<FormHelperText id="component-helper-text">
{t("backgroundColorDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,465 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import TableHead from "@material-ui/core/TableHead";
import Table from "@material-ui/core/Table";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import TableBody from "@material-ui/core/TableBody";
import { Delete } from "@material-ui/icons";
import IconButton from "@material-ui/core/IconButton";
import TextField from "@material-ui/core/TextField";
import CreateTheme from "../Dialogs/CreateTheme";
import Alert from "@material-ui/lab/Alert";
import Link from "@material-ui/core/Link";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import FormHelperText from "@material-ui/core/FormHelperText";
import { toggleSnackbar } from "../../../redux/explorer";
import { Trans, useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 500,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
colorContainer: {
display: "flex",
},
colorDot: {
width: 20,
height: 20,
borderRadius: "50%",
marginLeft: 6,
},
}));
export default function Theme() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const { t: tApp } = useTranslation();
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [theme, setTheme] = useState({});
const [options, setOptions] = useState({
themes: "{}",
defaultTheme: "",
home_view_method: "icon",
share_view_method: "list",
});
const [themeConfig, setThemeConfig] = useState({});
const [themeConfigError, setThemeConfigError] = useState({});
const [create, setCreate] = useState(false);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const deleteTheme = (color) => {
if (color === options.defaultTheme) {
ToggleSnackbar(
"top",
"right",
t("cannotDeleteDefaultTheme"),
"warning"
);
return;
}
if (Object.keys(theme).length <= 1) {
ToggleSnackbar("top", "right", t("keepAtLeastOneTheme"), "warning");
return;
}
const themeCopy = { ...theme };
delete themeCopy[color];
const resStr = JSON.stringify(themeCopy);
setOptions({
...options,
themes: resStr,
});
};
const addTheme = (newTheme) => {
setCreate(false);
if (theme[newTheme.palette.primary.main] !== undefined) {
ToggleSnackbar(
"top",
"right",
t("duplicatedThemePrimaryColor"),
"warning"
);
return;
}
const res = {
...theme,
[newTheme.palette.primary.main]: newTheme,
};
const resStr = JSON.stringify(res);
setOptions({
...options,
themes: resStr,
});
};
useEffect(() => {
const res = JSON.parse(options.themes);
const themeString = {};
Object.keys(res).map((k) => {
themeString[k] = JSON.stringify(res[k]);
});
setTheme(res);
setThemeConfig(themeString);
}, [options.themes]);
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
useEffect(() => {
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<div>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("themes")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>{t("colors")}</TableCell>
<TableCell>
{t("themeConfig")}
</TableCell>
<TableCell>{t("actions")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(theme).map((k) => (
<TableRow key={k}>
<TableCell
component="th"
scope="row"
>
<div
className={
classes.colorContainer
}
>
<div
style={{
backgroundColor:
theme[k].palette
.primary
.main,
}}
className={
classes.colorDot
}
/>
<div
style={{
backgroundColor:
theme[k].palette
.secondary
.main,
}}
className={
classes.colorDot
}
/>
</div>
</TableCell>
<TableCell>
<TextField
error={themeConfigError[k]}
helperText={
themeConfigError[k] &&
t("wrongFormat")
}
fullWidth
multiline
onChange={(e) => {
setThemeConfig({
...themeConfig,
[k]: e.target.value,
});
}}
onBlur={(e) => {
try {
const res = JSON.parse(
e.target.value
);
if (
!(
"palette" in
res
) ||
!(
"primary" in
res.palette
) ||
!(
"main" in
res.palette
.primary
) ||
!(
"secondary" in
res.palette
) ||
!(
"main" in
res.palette
.secondary
)
) {
throw "error";
}
setTheme({
...theme,
[k]: res,
});
} catch (e) {
setThemeConfigError(
{
...themeConfigError,
[k]: true,
}
);
return;
}
setThemeConfigError({
...themeConfigError,
[k]: false,
});
}}
value={themeConfig[k]}
/>
</TableCell>
<TableCell>
<IconButton
onClick={() =>
deleteTheme(k)
}
>
<Delete />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div>
<Button
variant="outlined"
color="primary"
style={{ marginTop: 8 }}
onClick={() => setCreate(true)}
>
{t("createNewTheme")}
</Button>
</div>
<Alert severity="info" style={{ marginTop: 8 }}>
<Typography variant="body2">
<Trans
i18nKey={"settings.themeConfigDes"}
ns={"dashboard"}
components={[
<Link
key={0}
href={t("themeConfigDoc")}
target={"_blank"}
/>,
]}
/>
</Typography>
</Alert>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("defaultTheme")}
</InputLabel>
<Select
value={options.defaultTheme}
onChange={handleChange("defaultTheme")}
>
{Object.keys(theme).map((k) => (
<MenuItem key={k} value={k}>
<div
className={
classes.colorContainer
}
>
<div
style={{
backgroundColor:
theme[k].palette
.primary.main,
}}
className={classes.colorDot}
/>
<div
style={{
backgroundColor:
theme[k].palette
.secondary.main,
}}
className={classes.colorDot}
/>
</div>
</MenuItem>
))}
</Select>
<FormHelperText id="component-helper-text">
{t("defaultThemeDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("appearance")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("personalFileListView")}
</InputLabel>
<Select
value={options.home_view_method}
onChange={handleChange("home_view_method")}
required
>
<MenuItem value={"icon"}>
{tApp("fileManager.gridViewLarge")}
</MenuItem>
<MenuItem value={"smallIcon"}>
{tApp("fileManager.gridViewSmall")}
</MenuItem>
<MenuItem value={"list"}>
{tApp("fileManager.listView")}
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
{t("personalFileListViewDes")}
</FormHelperText>
</FormControl>
</div>
</div>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("sharedFileListView")}
</InputLabel>
<Select
value={options.share_view_method}
onChange={handleChange("share_view_method")}
required
>
<MenuItem value={"icon"}>
{tApp("fileManager.gridViewLarge")}
</MenuItem>
<MenuItem value={"smallIcon"}>
{tApp("fileManager.gridViewSmall")}
</MenuItem>
<MenuItem value={"list"}>
{tApp("fileManager.listView")}
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
{t("sharedFileListViewDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
</div>
</form>
<CreateTheme
onSubmit={addTheme}
open={create}
onClose={() => setCreate(false)}
/>
</div>
);
}

View File

@@ -0,0 +1,248 @@
import React, { useCallback, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Typography from "@material-ui/core/Typography";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../../../redux/explorer";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import { Button, TextField } from "@material-ui/core";
import InputAdornment from "@material-ui/core/InputAdornment";
import API from "../../../middleware/Api";
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
},
secondaryHeading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
},
column: {
flexBasis: "33.33%",
},
details: {
display: "block",
},
}));
const generators = [
{
name: "policyBuiltin",
des: "policyBuiltinDes",
readOnly: true,
},
{
name: "libreOffice",
des: "libreOfficeDes",
enableFlag: "thumb_libreoffice_enabled",
executableSetting: "thumb_libreoffice_path",
inputs: [
{
name: "thumb_libreoffice_exts",
label: "generatorExts",
des: "generatorExtsDes",
},
],
},
{
name: "vips",
des: "vipsDes",
enableFlag: "thumb_vips_enabled",
executableSetting: "thumb_vips_path",
inputs: [
{
name: "thumb_vips_exts",
label: "generatorExts",
des: "generatorExtsDes",
},
],
},
{
name: "ffmpeg",
des: "ffmpegDes",
enableFlag: "thumb_ffmpeg_enabled",
executableSetting: "thumb_ffmpeg_path",
inputs: [
{
name: "thumb_ffmpeg_exts",
label: "generatorExts",
des: "generatorExtsDes",
},
{
name: "thumb_ffmpeg_seek",
label: "ffmpegSeek",
des: "ffmpegSeekDes",
required: true,
},
],
},
{
name: "cloudreveBuiltin",
des: "cloudreveBuiltinDes",
enableFlag: "thumb_builtin_enabled",
},
];
export default function ThumbGenerators({ options, setOptions }) {
const classes = useStyles();
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const testExecutable = (name, executable) => {
setLoading(true);
API.post("/admin/test/thumb", {
name,
executable,
})
.then((response) => {
ToggleSnackbar(
"top",
"right",
t("executableTestSuccess", { version: response.data }),
"success"
);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
const handleEnableChange = (name) => (event) => {
const newOpts = {
...options,
[name]: event.target.checked ? "1" : "0",
};
setOptions(newOpts);
if (
newOpts["thumb_libreoffice_enabled"] === "1" &&
newOpts["thumb_builtin_enabled"] === "0" &&
newOpts["thumb_vips_enabled"] === "0"
) {
ToggleSnackbar(
"top",
"center",
t("thumbDependencyWarning"),
"warning"
);
}
};
return (
<div className={classes.root}>
{generators.map((generator) => (
<Accordion key={generator.name}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-label="Expand"
aria-controls="additional-actions1-content"
id="additional-actions1-header"
>
<FormControlLabel
aria-label="Acknowledge"
onClick={(event) => event.stopPropagation()}
onFocus={(event) => event.stopPropagation()}
control={
<Checkbox
checked={
generator.readOnly ||
options[generator.enableFlag] === "1"
}
onChange={handleEnableChange(
generator.enableFlag
)}
/>
}
label={t(generator.name)}
disabled={generator.readOnly}
/>
</AccordionSummary>
<AccordionDetails className={classes.details}>
<Typography color="textSecondary">
{t(generator.des)}
</Typography>
{generator.executableSetting && (
<FormControl margin="normal" fullWidth>
<TextField
label={t("executable")}
variant="outlined"
value={options[generator.executableSetting]}
onChange={handleChange(
generator.executableSetting
)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Button
disabled={loading}
onClick={() =>
testExecutable(
generator.name,
options[
generator
.executableSetting
]
)
}
color="primary"
>
{t("executableTest")}
</Button>
</InputAdornment>
),
}}
required
/>
<FormHelperText id="component-helper-text">
{t("executableDes")}
</FormHelperText>
</FormControl>
)}
{generator.inputs &&
generator.inputs.map((input) => (
<FormControl
key={input.name}
margin="normal"
fullWidth
>
<TextField
label={t(input.label)}
variant="outlined"
value={options[input.name]}
onChange={handleChange(input.name)}
required={!!input.required}
/>
<FormHelperText id="component-helper-text">
{t(input.des)}
</FormHelperText>
</FormControl>
))}
</AccordionDetails>
</Accordion>
))}
</div>
);
}

View File

@@ -0,0 +1,407 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import Input from "@material-ui/core/Input";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import SizeInput from "../Common/SizeInput";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";
import { toggleSnackbar } from "../../../redux/explorer";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
}));
export default function UploadDownload() {
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState({
max_worker_num: "1",
max_parallel_transfer: "1",
temp_path: "",
chunk_retries: "0",
archive_timeout: "0",
download_timeout: "0",
preview_timeout: "0",
doc_preview_timeout: "0",
upload_credential_timeout: "0",
upload_session_timeout: "0",
slave_api_timeout: "0",
onedrive_monitor_timeout: "0",
share_download_session_timeout: "0",
onedrive_callback_check: "0",
reset_after_upload_failed: "0",
onedrive_source_timeout: "0",
slave_node_retry: "0",
slave_ping_interval: "0",
slave_recover_interval: "0",
slave_transfer_timeout: "0",
use_temp_chunk_buffer: "1",
public_resource_maxage: "0",
});
const handleCheckChange = (name) => (event) => {
const value = event.target.checked ? "1" : "0";
setOptions({
...options,
[name]: value,
});
};
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
useEffect(() => {
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", t("saved"), "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<div>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("transportation")}
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("workerNum")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.max_worker_num}
onChange={handleChange("max_worker_num")}
required
/>
<FormHelperText id="component-helper-text">
{t("workerNumDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("transitParallelNum")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.max_parallel_transfer}
onChange={handleChange(
"max_parallel_transfer"
)}
required
/>
<FormHelperText id="component-helper-text">
{t("transitParallelNumDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
{t("tempFolder")}
</InputLabel>
<Input
value={options.temp_path}
onChange={handleChange("temp_path")}
required
/>
<FormHelperText id="component-helper-text">
{t("tempFolderDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t("failedChunkRetry")}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 0,
step: 1,
}}
value={options.chunk_retries}
onChange={handleChange("chunk_retries")}
required
/>
<FormHelperText id="component-helper-text">
{t("failedChunkRetryDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.use_temp_chunk_buffer ===
"1"
}
onChange={handleCheckChange(
"use_temp_chunk_buffer"
)}
/>
}
label={t("cacheChunks")}
/>
<FormHelperText id="component-helper-text">
{t("cacheChunksDes")}
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.reset_after_upload_failed ===
"1"
}
onChange={handleCheckChange(
"reset_after_upload_failed"
)}
/>
}
label={t("resetConnection")}
/>
<FormHelperText id="component-helper-text">
{t("resetConnectionDes")}
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("expirationDuration")}
</Typography>
<div className={classes.formContainer}>
{[
{
name: "batchDownload",
field: "archive_timeout",
},
{
name: "downloadSession",
field: "download_timeout",
},
{
name: "previewURL",
field: "preview_timeout",
},
{
name: "docPreviewURL",
field: "doc_preview_timeout",
},
{
name: "staticResourceCache",
field: "public_resource_maxage",
des: "staticResourceCacheDes",
},
{
name: "uploadSession",
field: "upload_session_timeout",
des: "uploadSessionDes",
},
{
name: "downloadSessionForShared",
field: "share_download_session_timeout",
des: "downloadSessionForSharedDes",
},
{
name: "onedriveMonitorInterval",
field: "onedrive_monitor_timeout",
des: "onedriveMonitorIntervalDes",
},
{
name: "onedriveCallbackTolerance",
field: "onedrive_callback_check",
des: "onedriveCallbackToleranceDes",
},
{
name: "onedriveDownloadURLCache",
field: "onedrive_source_timeout",
des: "onedriveDownloadURLCacheDes",
},
].map((input) => (
<div key={input.name} className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t(input.name)}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options[input.field]}
onChange={handleChange(input.field)}
required
/>
{input.des && (
<FormHelperText id="component-helper-text">
{t(input.des)}
</FormHelperText>
)}
</FormControl>
</div>
))}
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
{t("nodesCommunication")}
</Typography>
<div className={classes.formContainer}>
{[
{
name: "slaveAPIExpiration",
field: "slave_api_timeout",
des: "slaveAPIExpirationDes",
},
{
name: "heartbeatInterval",
field: "slave_ping_interval",
des: "heartbeatIntervalDes",
},
{
name: "heartbeatFailThreshold",
field: "slave_node_retry",
des: "heartbeatFailThresholdDes",
},
{
name: "heartbeatRecoverModeInterval",
field: "slave_recover_interval",
des: "heartbeatRecoverModeIntervalDes",
},
{
name: "slaveTransitExpiration",
field: "slave_transfer_timeout",
des: "slaveTransitExpirationDes",
},
].map((input) => (
<div key={input.name} className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
{t(input.name)}
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options[input.field]}
onChange={handleChange(input.field)}
required
/>
<FormHelperText id="component-helper-text">
{t(input.des)}
</FormHelperText>
</FormControl>
</div>
))}
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
{t("save")}
</Button>
</div>
</form>
</div>
);
}

File diff suppressed because it is too large Load Diff