web-base/react/adminPanel.old/src/views/settings.js

620 lines
27 KiB
JavaScript

import React from "react";
import {Link} from "react-router-dom";
import Alert from "../elements/alert";
import {Collapse} from "react-collapse/lib/Collapse";
import Icon from "../elements/icon";
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import ReactTooltip from "react-tooltip";
export default class Settings extends React.Component {
constructor(props) {
super(props);
this.state = {
errors: [],
settings: {},
general: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
keys: ["site_name", "base_url", "user_registration_enabled"]
},
mail: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
isSending: false,
test_email: "",
unsavedMailSettings: false,
keys: ["mail_enabled", "mail_host", "mail_port", "mail_username", "mail_password", "mail_from"]
},
recaptcha: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
keys: ["recaptcha_enabled", "recaptcha_public_key", "recaptcha_private_key"]
},
uncategorised: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
settings: []
},
};
this.parent = {
api: props.api,
showDialog: props.showDialog
};
this.hiddenKeys = [
"recaptcha_private_key",
"mail_password",
"jwt_secret"
];
}
isDefaultKey(key) {
key = key.trim();
return this.state.general.keys.includes(key)
|| this.state.mail.keys.includes(key)
|| this.state.recaptcha.keys.includes(key)
|| this.hiddenKeys.includes(key);
}
getUncategorisedValues(res) {
let uncategorised = [];
for(let key in res.settings) {
if (res.settings.hasOwnProperty(key) && !this.isDefaultKey(key)) {
uncategorised.push({key: key, value: res.settings[key]});
}
}
return uncategorised;
}
onDeleteUncategorisedProp(index) {
if (index < 0 || index >= this.state.uncategorised.settings.length) {
return;
}
let props = this.state.uncategorised.settings.slice();
props.splice(index, 1);
this.setState({ ...this.state, uncategorised: { ...this.state.uncategorised, settings: props }});
}
onChangeUncategorisedValue(event, index, isKey) {
if (index < 0 || index >= this.state.uncategorised.settings.length) {
return;
}
let props = this.state.uncategorised.settings.slice();
if (isKey) {
props[index].key = event.target.value;
} else {
props[index].value = event.target.value;
}
this.setState({ ...this.state, uncategorised: { ...this.state.uncategorised, settings: props }});
}
onAddUncategorisedProperty() {
let props = this.state.uncategorised.settings.slice();
props.push({key: "", value: ""});
this.setState({ ...this.state, uncategorised: { ...this.state.uncategorised, settings: props }});
}
componentDidMount() {
this.parent.api.getSettings().then((res) => {
if (res.success) {
let newState = {
...this.state,
settings: res.settings,
uncategorised: { ...this.state.uncategorised, settings: this.getUncategorisedValues(res) }
};
this.setState(newState);
} else {
let errors = this.state.errors.slice();
errors.push({title: "Error fetching settings", message: res.msg});
this.setState({...this.state, errors: errors });
}
});
}
removeError(i, category = null) {
if (category) {
if (i >= 0 && i < this.state[category].alerts.length) {
let alerts = this.state[category].alerts.slice();
alerts.splice(i, 1);
this.setState({...this.state, [category]: {...this.state[category], alerts: alerts}});
}
} else {
if (i >= 0 && i < this.state.errors.length) {
let errors = this.state.errors.slice();
errors.splice(i, 1);
this.setState({...this.state, errors: errors});
}
}
}
toggleCollapse(category) {
this.setState({...this.state, [category]: {...this.state[category], isOpen: !this.state[category].isOpen}});
}
createCard(category, color, icon, title, content) {
let alerts = [];
for (let i = 0; i < this.state[category].alerts.length; i++) {
alerts.push(<Alert key={"alert-" + i}
onClose={() => this.removeError(i, category)} {...this.state[category].alerts[i]}/>)
}
return <div className={"card card-" + color} key={"card-" + category}>
<div className={"card-header"} style={{cursor: "pointer"}}
onClick={() => this.toggleCollapse(category)}>
<h4 className={"card-title"}>
<Icon className={"mr-2"} icon={icon} type={icon==="google"?"fab":"fas"} />
{title}
</h4>
<div className={"card-tools"}>
<span className={"btn btn-tool btn-sm"}>
<Icon icon={this.state[category].isOpen ? "angle-up" : "angle-down"}/>
</span>
</div>
</div>
<Collapse isOpened={this.state[category].isOpen}>
<div className={"card-body"}>
<div className={"row"}>
<div className={"col-12 col-lg-6"}>
{alerts}
{content}
<div>
<button className={"btn btn-secondary"} onClick={() => this.onReset(category)}
disabled={this.state[category].isResetting || this.state[category].isSaving}>
{this.state[category].isResetting ?
<span>Resetting&nbsp;<Icon icon={"circle-notch"}/></span> : "Reset"}
</button>
<button className={"btn btn-success ml-2"} onClick={() => this.onSave(category)}
disabled={this.state[category].isResetting || this.state[category].isSaving}>
{this.state[category].isSaving ?
<span>Saving&nbsp;<Icon icon={"circle-notch"}/></span> : "Save"}
</button>
</div>
</div>
</div>
</div>
</Collapse>
</div>
}
createGeneralForm() {
return <>
<div className={"form-group"}>
<label htmlFor={"site_name"}>Site Name</label>
<input type={"text"} className={"form-control"}
value={this.state.settings["site_name"] ?? ""}
placeholder={"Enter a title"} name={"site_name"} id={"site_name"}
onChange={this.onChangeValue.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"base_url"}>Base URL</label>
<input type={"text"} className={"form-control"}
value={this.state.settings["base_url"] ?? ""}
placeholder={"Enter a url"} name={"base_url"} id={"base_url"}
onChange={this.onChangeValue.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"user_registration_enabled"}>User Registration</label>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"user_registration_enabled"}
id={"user_registration_enabled"}
checked={(this.state.settings["user_registration_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"}
htmlFor={"user_registration_enabled"}>
Allow anyone to register an account
</label>
</div>
</div>
</>
}
createMailForm() {
return <>
<div className={"form-group mt-2"}>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"mail_enabled"} id={"mail_enabled"}
checked={(this.state.settings["mail_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"} htmlFor={"mail_enabled"}>
Enable E-Mail service
</label>
</div>
</div>
<hr className={"m-3"}/>
<label htmlFor={"mail_username"}>Username</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"hashtag"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["mail_username"] ?? ""}
placeholder={"Enter a username"} name={"mail_username"}
id={"mail_username"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"mail_password"} className={"mt-2"}>Password</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"key"}/>
</span>
</div>
<input type={"password"} className={"form-control"}
value={this.state.settings["mail_password"] ?? ""}
placeholder={"(unchanged)"} name={"mail_password"}
id={"mail_password"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"mail_from"} className={"mt-2"}>Sender Email Address</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>@</span>
</div>
<input type={"email"} className={"form-control"}
value={this.state.settings["mail_from"] ?? ""}
placeholder={"Enter a email address"} name={"mail_from"}
id={"mail_from"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<div className={"row"}>
<div className={"col-6"}>
<label htmlFor={"mail_host"} className={"mt-2"}>SMTP Host</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"project-diagram"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["mail_host"] ?? ""}
placeholder={"e.g. smtp.example.com"} name={"mail_host"}
id={"mail_host"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
</div>
<div className={"col-6"}>
<label htmlFor={"mail_port"} className={"mt-2"}>SMTP Port</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"project-diagram"}/>
</span>
</div>
<input type={"number"} className={"form-control"}
value={parseInt(this.state.settings["mail_port"] ?? "25")}
placeholder={"smtp port"} name={"mail_port"}
id={"mail_port"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
</div>
</div>
<div className={"mt-3"}>
<label htmlFor={"mail_from"} className={"mt-2"}>Send Test E-Mail</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>@</span>
</div>
<input type={"email"} className={"form-control"}
value={this.state.mail.test_email}
placeholder={"Enter a email address"}
onChange={(e) => this.setState({
...this.state,
mail: {...this.state.mail, test_email: e.target.value},
})}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<div className={"form-group form-inline mt-3"}>
<button className={"btn btn-info col-2"}
onClick={() => this.onSendTestMail()}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1" || this.state.mail.isSending}>
{this.state.mail.isSending ?
<span>Sending&nbsp;<Icon icon={"circle-notch"}/></span> : "Send Mail"}
</button>
<div className={"col-10"}>
{this.state.mail.unsavedMailSettings ?
<span className={"text-red"}>You need to save your mail settings first.</span> : null}
</div>
</div>
</div>
</>
}
getRecaptchaForm() {
return <>
<div className={"form-group mt-2"}>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"recaptcha_enabled"} id={"recaptcha_enabled"}
checked={(this.state.settings["recaptcha_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"} htmlFor={"recaptcha_enabled"}>
Enable Google's reCaptcha
<span className={"ml-2"}>
(<a href={"https://www.google.com/recaptcha/intro/v3.html"} target={"_blank"} rel={"noopener noreferrer"}>
More Info
<sup><small><Icon icon={"external-link-alt"} className={"ml-1"}/></small></sup>
</a>)
</span>
</label>
</div>
</div>
<hr className={"m-2"}/>
<label htmlFor={"recaptcha_public_key"} className={"mt-2"}>reCaptcha Site Key</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"unlock"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["recaptcha_public_key"] ?? ""}
placeholder={"Enter site key"} name={"recaptcha_public_key"}
id={"recaptcha_public_key"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["recaptcha_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"recaptcha_private_key"} className={"mt-2"}>reCaptcha Secret Key</label>
<div className={"input-group mb-3"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"lock"}/>
</span>
</div>
<input type={"password"} className={"form-control"}
value={this.state.settings["recaptcha_private_key"] ?? ""}
placeholder={"(unchanged)"} name={"recaptcha_private_key"}
id={"mail_password"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["recaptcha_enabled"] ?? "0") !== "1"}/>
</div>
</>
}
getUncategorizedForm() {
let tr = [];
for(let i = 0; i < this.state.uncategorised.settings.length; i++) {
let key = this.state.uncategorised.settings[i].key;
let value = this.state.uncategorised.settings[i].value;
tr.push(
<tr key={"uncategorised-" + i} className={(i % 2 === 0) ? "even" : "odd"}>
<td>
<input className={"form-control"} type={"text"} value={key} maxLength={32} placeholder={"Key"}
onChange={(e) => this.onChangeUncategorisedValue(e, i, true)} />
</td>
<td>
<input className={"form-control"} type={"text"} value={value} placeholder={"value"}
onChange={(e) => this.onChangeUncategorisedValue(e, i, false)} />
</td>
<td className={"text-center align-middle"}>
<ReactTooltip id={"tooltip-uncategorised-" + i} />
<Icon icon={"trash"} className={"text-danger"} style={{cursor: "pointer"}}
onClick={() => this.onDeleteUncategorisedProp(i)} data-type={"error"}
data-tip={"Delete property"} data-place={"right"} data-effect={"solid"}
data-for={"tooltip-uncategorised-" + i}
/>
</td>
</tr>
);
}
return <>
<table className={"table table-bordered table-hover dataTable dtr-inline"}>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th className={"text-center"}><Icon icon={"tools"}/></th>
</tr>
</thead>
<tbody>
{tr}
</tbody>
</table>
<div className={"mt-2 mb-3"}>
<button className={"btn btn-info"} onClick={() => this.onAddUncategorisedProperty()} >
<Icon icon={"plus"} className={"mr-2"} /> Add property
</button>
</div>
</>
}
render() {
let errors = [];
for (let i = 0; i < this.state.errors.length; i++) {
errors.push(<Alert key={"error-" + i}
onClose={() => this.removeError("errors", i)} {...this.state.errors[i]}/>)
}
const categories = {
"general": {color: "primary", icon: "cogs", title: "General Settings", content: this.createGeneralForm()},
"mail": {color: "warning", icon: "envelope", title: "Mail Settings", content: this.createMailForm()},
"recaptcha": {color: "danger", icon: "google", title: "Google reCaptcha", content: this.getRecaptchaForm()},
"uncategorised": {color: "secondary", icon: "stream", title: "Uncategorised", content: this.getUncategorizedForm()},
};
let cards = [];
for (let name in categories) {
let category = categories[name];
cards.push(this.createCard(name, category.color, category.icon, category.title, category.content));
}
return <>
<div className={"content-header"}>
<div className={"container-fluid"}>
<div className={"row mb-2"}>
<div className="col-sm-6">
<h1 className="m-0 text-dark">Settings</h1>
</div>
<div className="col-sm-6">
<ol className="breadcrumb float-sm-right">
<li className="breadcrumb-item"><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active">Settings</li>
</ol>
</div>
</div>
</div>
</div>
<div className={"content"}>
{errors}
<div>
{cards}
</div>
</div>
<ReactTooltip />
</>
}
onChangeValue(event) {
const target = event.target;
const name = target.name;
const type = target.type;
let value = target.value;
if (type === "checkbox") {
value = event.target.checked ? "1" : "0";
}
let changedMailSettings = false;
if (this.state.mail.keys.includes(name)) {
changedMailSettings = true;
}
let newState = {...this.state, settings: {...this.state.settings, [name]: value}};
if (changedMailSettings) {
newState.mail = {...this.state.mail, unsavedMailSettings: true};
}
this.setState(newState);
}
onReset(category) {
this.setState({...this.state, [category]: {...this.state[category], isResetting: true}});
this.parent.api.getSettings().then((res) => {
if (!res.success) {
let alerts = this.state[category].alerts.slice();
alerts.push({title: "Error fetching settings", message: res.msg});
this.setState({
...this.state,
[category]: {...this.state[category], alerts: alerts, isResetting: false}
});
} else {
let newState = { ...this.state };
let categoryUpdated = {...this.state[category], isResetting: false};
let newSettings = {...this.state.settings};
if (category === "uncategorised") {
categoryUpdated.settings = this.getUncategorisedValues(res);
for (let key in res.settings) {
if (res.settings.hasOwnProperty(key) && !this.isDefaultKey(key)) {
newSettings[key] = res.settings[key] ?? "";
}
}
} else {
for (let key of this.state[category].keys) {
newSettings[key] = res.settings[key] ?? "";
}
if (category === "mail") {
categoryUpdated.unsavedMailSettings = false;
}
}
newState.settings = newSettings;
newState[category] = categoryUpdated;
this.setState(newState);
}
});
}
onSave(category) {
this.setState({...this.state, [category]: {...this.state[category], isSaving: true}});
let values = {};
if (category === "uncategorised") {
for (let prop of this.state.uncategorised.settings) {
if (prop.key) {
values[prop.key] = prop.value;
if (this.isDefaultKey(prop.key)) {
this.parent.showDialog("You cannot use this key as property key: " + prop.key, "System specific key");
this.setState({...this.state, [category]: {...this.state[category], isSaving: false}});
return;
}
}
}
for (let key in this.state.settings) {
if (this.state.settings.hasOwnProperty(key) && !this.isDefaultKey(key) && !values.hasOwnProperty(key)) {
values[key] = null;
}
}
} else {
for (let key of this.state[category].keys) {
if (this.hiddenKeys.includes(key) && !this.state.settings[key]) {
continue;
}
values[key] = this.state.settings[key];
}
}
this.parent.api.saveSettings(values).then((res) => {
let alerts = this.state[category].alerts.slice();
let categoryUpdated = {...this.state[category], isSaving: false};
if (!res.success) {
alerts.push({title: "Error saving settings", message: res.msg});
} else {
alerts.push({title: "Success", message: "Settings were successfully saved.", type: "success"});
if (category === "mail") categoryUpdated.unsavedMailSettings = false;
this.setState({...this.state, [category]: categoryUpdated});
}
categoryUpdated.alerts = alerts;
this.setState({...this.state, [category]: categoryUpdated});
});
}
onSendTestMail() {
this.setState({...this.state, mail: {...this.state.mail, isSending: true}});
this.parent.api.sendTestMail(this.state.mail.test_email).then((res) => {
let alerts = this.state.mail.alerts.slice();
let newState = {...this.state.mail, isSending: false};
if (!res.success) {
alerts.push({title: "Error sending email", message: res.msg});
} else {
alerts.push({
title: "Success!",
message: "E-Mail was successfully sent, check your inbox.",
type: "success"
});
newState.test_email = "";
}
newState.alerts = alerts;
this.setState({...this.state, mail: newState});
});
}
}