Settings + Test Mail
This commit is contained in:
parent
6eb9bf333f
commit
09475be545
@ -11,12 +11,9 @@ class SendMail extends Request {
|
|||||||
|
|
||||||
public function __construct($user, $externalCall = false) {
|
public function __construct($user, $externalCall = false) {
|
||||||
parent::__construct($user, $externalCall, array(
|
parent::__construct($user, $externalCall, array(
|
||||||
'from' => new Parameter('from', Parameter::TYPE_EMAIL),
|
|
||||||
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
|
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
|
||||||
'subject' => new StringType('subject', -1),
|
'subject' => new StringType('subject', -1),
|
||||||
'body' => new StringType('body', -1),
|
'body' => new StringType('body', -1),
|
||||||
'fromName' => new StringType('fromName', -1, true, ''),
|
|
||||||
'replyTo' => new Parameter('to', Parameter::TYPE_EMAIL, true, ''),
|
|
||||||
));
|
));
|
||||||
$this->isPublic = false;
|
$this->isPublic = false;
|
||||||
}
|
}
|
||||||
@ -28,6 +25,7 @@ class SendMail extends Request {
|
|||||||
|
|
||||||
if ($this->success) {
|
if ($this->success) {
|
||||||
$settings = $req->getResult()["settings"];
|
$settings = $req->getResult()["settings"];
|
||||||
|
|
||||||
if (!isset($settings["mail_enabled"]) || $settings["mail_enabled"] !== "1") {
|
if (!isset($settings["mail_enabled"]) || $settings["mail_enabled"] !== "1") {
|
||||||
$this->createError("Mail is not configured yet.");
|
$this->createError("Mail is not configured yet.");
|
||||||
return null;
|
return null;
|
||||||
@ -37,7 +35,9 @@ class SendMail extends Request {
|
|||||||
$port = intval($settings["mail_port"] ?? "25");
|
$port = intval($settings["mail_port"] ?? "25");
|
||||||
$login = $settings["mail_username"] ?? "";
|
$login = $settings["mail_username"] ?? "";
|
||||||
$password = $settings["mail_password"] ?? "";
|
$password = $settings["mail_password"] ?? "";
|
||||||
return new ConnectionData($host, $port, $login, $password);
|
$connectionData = new ConnectionData($host, $port, $login, $password);
|
||||||
|
$connectionData->setProperty("from", $settings["mail_from"] ?? "");
|
||||||
|
return $connectionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -56,7 +56,7 @@ class SendMail extends Request {
|
|||||||
try {
|
try {
|
||||||
$mail = new PHPMailer;
|
$mail = new PHPMailer;
|
||||||
$mail->IsSMTP();
|
$mail->IsSMTP();
|
||||||
$mail->setFrom($this->getParam('from'), $this->getParam('fromName'));
|
$mail->setFrom($mailConfig->getProperty("from"));
|
||||||
$mail->addAddress($this->getParam('to'));
|
$mail->addAddress($this->getParam('to'));
|
||||||
$mail->Subject = $this->getParam('subject');
|
$mail->Subject = $this->getParam('subject');
|
||||||
$mail->SMTPDebug = 0;
|
$mail->SMTPDebug = 0;
|
||||||
@ -70,11 +70,6 @@ class SendMail extends Request {
|
|||||||
$mail->CharSet = 'UTF-8';
|
$mail->CharSet = 'UTF-8';
|
||||||
$mail->Body = $this->getParam('body');
|
$mail->Body = $this->getParam('body');
|
||||||
|
|
||||||
$replyTo = $this->getParam('replyTo');
|
|
||||||
if(!is_null($replyTo) && !empty($replyTo)) {
|
|
||||||
$mail->AddReplyTo($replyTo, $this->getParam('fromName'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->success = @$mail->Send();
|
$this->success = @$mail->Send();
|
||||||
if (!$this->success) {
|
if (!$this->success) {
|
||||||
$this->lastError = "Error sending Mail: $mail->ErrorInfo";
|
$this->lastError = "Error sending Mail: $mail->ErrorInfo";
|
||||||
|
33
core/Api/SendTestMail.class.php
Normal file
33
core/Api/SendTestMail.class.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api;
|
||||||
|
|
||||||
|
use Api\Parameter\Parameter;
|
||||||
|
use Objects\User;
|
||||||
|
|
||||||
|
class SendTestMail extends Request {
|
||||||
|
|
||||||
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
"receiver" => new Parameter("receiver", Parameter::TYPE_EMAIL)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()) {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$receiver = $this->getParam("receiver");
|
||||||
|
$req = new SendMail($this->user);
|
||||||
|
$this->success = $req->execute(array(
|
||||||
|
"to" => $receiver,
|
||||||
|
"subject" => "Test E-Mail",
|
||||||
|
"body" => "Hey! If you receive this e-mail, your mail configuration seems to be working."
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->lastError = $req->getLastError();
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,7 @@ namespace Api\Settings {
|
|||||||
use Driver\SQL\Column\Column;
|
use Driver\SQL\Column\Column;
|
||||||
use Driver\SQL\Condition\Compare;
|
use Driver\SQL\Condition\Compare;
|
||||||
use Driver\SQL\Condition\CondLike;
|
use Driver\SQL\Condition\CondLike;
|
||||||
|
use Driver\SQL\Condition\CondNot;
|
||||||
use Driver\SQL\Condition\CondRegex;
|
use Driver\SQL\Condition\CondRegex;
|
||||||
use Driver\SQL\Strategy\UpdateStrategy;
|
use Driver\SQL\Strategy\UpdateStrategy;
|
||||||
use Objects\User;
|
use Objects\User;
|
||||||
@ -42,11 +43,12 @@ namespace Api\Settings {
|
|||||||
$query = $sql->select("name", "value") ->from("Settings");
|
$query = $sql->select("name", "value") ->from("Settings");
|
||||||
|
|
||||||
if (!is_null($key) && !empty($key)) {
|
if (!is_null($key) && !empty($key)) {
|
||||||
$query->where(new CondRegex($key, new Column("name")));
|
$query->where(new CondRegex(new Column("name"), $key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter sensitive values, if called from outside
|
||||||
if ($this->isExternalCall()) {
|
if ($this->isExternalCall()) {
|
||||||
$query->where(new Compare("name", "jwt_secret", "!="));
|
$query->where(new CondNot("private"));
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $query->execute();
|
$res = $query->execute();
|
||||||
|
@ -461,7 +461,8 @@ If the invitation was not intended, you can simply ignore this email.<br><br><a
|
|||||||
return $this->createError("Error creating Session: " . $sql->getLastError());
|
return $this->createError("Error creating Session: " . $sql->getLastError());
|
||||||
} else {
|
} else {
|
||||||
$this->result["loggedIn"] = true;
|
$this->result["loggedIn"] = true;
|
||||||
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
|
$this->result["logoutIn"] = $this->user->getSession()->getExpiresSeconds();
|
||||||
|
$this->result["csrf_token"] = $this->user->getSession()->getCsrfToken();
|
||||||
$this->success = true;
|
$this->success = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -138,24 +138,64 @@ class CreateDatabase {
|
|||||||
->addRow("^/register(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register")
|
->addRow("^/register(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register")
|
||||||
->addRow("^/confirmEmail(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail")
|
->addRow("^/confirmEmail(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail")
|
||||||
->addRow("^/acceptInvite(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite")
|
->addRow("^/acceptInvite(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite")
|
||||||
|
->addRow("^/resetPassword(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResetPassword")
|
||||||
->addRow("^/$", "static", "/static/welcome.html", NULL);
|
->addRow("^/$", "static", "/static/welcome.html", NULL);
|
||||||
|
|
||||||
$queries[] = $sql->createTable("Settings")
|
$queries[] = $sql->createTable("Settings")
|
||||||
->addString("name", 32)
|
->addString("name", 32)
|
||||||
->addString("value", 1024, true)
|
->addString("value", 1024, true)
|
||||||
|
->addBool("private", false)
|
||||||
->primaryKey("name");
|
->primaryKey("name");
|
||||||
|
|
||||||
$settingsQuery = $sql->insert("Settings", array("name", "value"))
|
$settingsQuery = $sql->insert("Settings", array("name", "value", "private"))
|
||||||
// ->addRow("mail_enabled", "0") # this key will be set during installation
|
// ->addRow("mail_enabled", "0") # this key will be set during installation
|
||||||
->addRow("mail_host", "")
|
->addRow("mail_host", "", false)
|
||||||
->addRow("mail_port", "")
|
->addRow("mail_port", "", false)
|
||||||
->addRow("mail_username", "")
|
->addRow("mail_username", "", false)
|
||||||
->addRow("mail_password", "")
|
->addRow("mail_password", "", true)
|
||||||
->addRow("mail_from", "");
|
->addRow("mail_from", "", false)
|
||||||
|
->addRow("message_confirm_email", self::MessageConfirmEmail(), false)
|
||||||
|
->addRow("message_accept_invite", self::MessageAcceptInvite(), false)
|
||||||
|
->addRow("message_reset_password", self::MessageResetPassword(), false);
|
||||||
|
|
||||||
(Settings::loadDefaults())->addRows($settingsQuery);
|
(Settings::loadDefaults())->addRows($settingsQuery);
|
||||||
$queries[] = $settingsQuery;
|
$queries[] = $settingsQuery;
|
||||||
|
|
||||||
return $queries;
|
return $queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function MessageConfirmEmail() : string {
|
||||||
|
return str_replace("\n", "", intendCode(
|
||||||
|
"Hello {{username}},<br>
|
||||||
|
You recently created an account on {{site_name}}. Please click on the following link to
|
||||||
|
confirm your email address and complete your registration. If you haven't registered an
|
||||||
|
account, you can simply ignore this email. The link is valid for the next 48 hours:<br><br>
|
||||||
|
<a href=\"{{link}}\">{{confirm_link}}</a><br><br>
|
||||||
|
Best Regards<br>
|
||||||
|
{{site_name}} Administration", false
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function MessageAcceptInvite() : string {
|
||||||
|
return str_replace("\n", "", intendCode(
|
||||||
|
"Hello {{username}},<br>
|
||||||
|
You were invited to create an account on {{site_name}}. Please click on the following link to
|
||||||
|
confirm your email address and complete your registration by choosing a new password.
|
||||||
|
If you want to decline the invitation, you can simply ignore this email. The link is valid for the next 48 hours:<br><br>
|
||||||
|
<a href=\"{{link}}\">{{link}}</a><br><br>
|
||||||
|
Best Regards<br>
|
||||||
|
{{site_name}} Administration", false
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function MessageResetPassword() : string {
|
||||||
|
return str_replace("\n", "", intendCode(
|
||||||
|
"Hello {{username}},<br>
|
||||||
|
you requested a password reset on {{sitename}}. Please click on the following link to
|
||||||
|
choose a new password. If this request was not intended, you can simply ignore the email. The Link is valid for one hour:<br><br>
|
||||||
|
<a href=\"{{link}}\">{{link}}</a><br><br>
|
||||||
|
Best Regards<br>
|
||||||
|
{{site_name}} Administration", false
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,10 @@ class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function addRows(Insert $query) {
|
public function addRows(Insert $query) {
|
||||||
$query->addRow("site_name", $this->siteName)
|
$query->addRow("site_name", $this->siteName, false)
|
||||||
->addRow("base_url", $this->baseUrl)
|
->addRow("base_url", $this->baseUrl, false)
|
||||||
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0")
|
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false)
|
||||||
->addRow("installation_completed", $this->installationComplete ? "1" : "0")
|
->addRow("installation_completed", $this->installationComplete ? "1" : "0", true)
|
||||||
->addRow("jwt_secret", $this->jwtSecret);
|
->addRow("jwt_secret", $this->jwtSecret, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
16
core/Driver/SQL/Condition/CondNot.class.php
Normal file
16
core/Driver/SQL/Condition/CondNot.class.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Driver\SQL\Condition;
|
||||||
|
|
||||||
|
class CondNot extends Condition {
|
||||||
|
|
||||||
|
private $expression; // string or condition
|
||||||
|
|
||||||
|
public function __construct($expression) {
|
||||||
|
$this->expression = $expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpression() {
|
||||||
|
return $this->expression;
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,9 @@ use Driver\SQL\Column\Column;
|
|||||||
use Driver\SQL\Condition\Compare;
|
use Driver\SQL\Condition\Compare;
|
||||||
use Driver\SQL\Condition\CondBool;
|
use Driver\SQL\Condition\CondBool;
|
||||||
use Driver\SQL\Condition\CondIn;
|
use Driver\SQL\Condition\CondIn;
|
||||||
|
use Driver\SQL\Condition\Condition;
|
||||||
use Driver\SQL\Condition\CondKeyword;
|
use Driver\SQL\Condition\CondKeyword;
|
||||||
|
use Driver\SQL\Condition\CondNot;
|
||||||
use Driver\SQL\Condition\CondOr;
|
use Driver\SQL\Condition\CondOr;
|
||||||
use Driver\SQL\Condition\CondRegex;
|
use Driver\SQL\Condition\CondRegex;
|
||||||
use Driver\SQL\Constraint\Constraint;
|
use Driver\SQL\Constraint\Constraint;
|
||||||
@ -339,6 +341,9 @@ abstract class SQL {
|
|||||||
return implode(" AND ", $conditions);
|
return implode(" AND ", $conditions);
|
||||||
}
|
}
|
||||||
} else if($condition instanceof CondIn) {
|
} else if($condition instanceof CondIn) {
|
||||||
|
|
||||||
|
$value = $condition->getValues();
|
||||||
|
|
||||||
$values = array();
|
$values = array();
|
||||||
foreach ($condition->getValues() as $value) {
|
foreach ($condition->getValues() as $value) {
|
||||||
$values[] = $this->addValue($value, $params);
|
$values[] = $this->addValue($value, $params);
|
||||||
@ -353,6 +358,15 @@ abstract class SQL {
|
|||||||
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
||||||
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
|
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
|
||||||
return "$left $keyword $right ";
|
return "$left $keyword $right ";
|
||||||
|
} else if($condition instanceof CondNot) {
|
||||||
|
$expression = $condition->getExpression();
|
||||||
|
if ($expression instanceof Condition) {
|
||||||
|
$expression = $this->buildCondition($expression, $params);
|
||||||
|
} else {
|
||||||
|
$expression = $this->columnName($expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "NOT $expression";
|
||||||
} else {
|
} else {
|
||||||
$this->lastError = "Unsupported condition type: " . get_class($condition);
|
$this->lastError = "Unsupported condition type: " . get_class($condition);
|
||||||
return false;
|
return false;
|
||||||
|
4
js/admin.min.js
vendored
4
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@ -94,4 +94,13 @@ export default class API {
|
|||||||
async getSettings(key = "") {
|
async getSettings(key = "") {
|
||||||
return this.apiCall("settings/get", { key: key });
|
return this.apiCall("settings/get", { key: key });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveSettings(settings) {
|
||||||
|
return this.apiCall("settings/set", { settings: settings });
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendTestMail(receiver) {
|
||||||
|
return this.apiCall("sendTestMail", { receiver: receiver });
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
@ -11,46 +11,352 @@ export default class Settings extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
errors: [],
|
errors: [],
|
||||||
|
mailErrors: [],
|
||||||
|
generalErrors: [],
|
||||||
|
etcErrors: [],
|
||||||
settings: {},
|
settings: {},
|
||||||
generalOpened: true,
|
generalOpened: true,
|
||||||
mailOpened: true,
|
mailOpened: true,
|
||||||
etcOpened : true
|
etcOpened: true,
|
||||||
|
isResetting: false,
|
||||||
|
isSaving: false,
|
||||||
|
isSending: false,
|
||||||
|
test_email: "",
|
||||||
|
unsavedMailSettings: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this.parent = {
|
this.parent = {
|
||||||
api: props.api
|
api: props.api
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.mailKeys = ["mail_enabled", "mail_host", "mail_port", "mail_username", "mail_password", "mail_from"];
|
||||||
|
this.generalKeys = ["site_name", "base_url", "user_registration_enabled"];
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.parent.api.getSettings().then((res) => {
|
this.parent.api.getSettings().then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.setState({...this.state, settings: res.settings });
|
this.setState({...this.state, settings: res.settings});
|
||||||
} else {
|
} else {
|
||||||
let errors = this.state.errors.slice();
|
let errors = this.state.errors.slice();
|
||||||
errors.push({ title: "Error fetching settings", message: res.msg });
|
errors.push({title: "Error fetching settings", message: res.msg});
|
||||||
this.setState({...this.state, errors: errors});
|
this.setState({...this.state, errors: errors});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeError(i) {
|
removeError(key, i) {
|
||||||
if (i >= 0 && i < this.state.errors.length) {
|
if (i >= 0 && i < this.state[key].length) {
|
||||||
let errors = this.state.errors.slice();
|
let errors = this.state[key].slice();
|
||||||
errors.splice(i, 1);
|
errors.splice(i, 1);
|
||||||
this.setState({...this.state, errors: errors});
|
this.setState({...this.state, [key]: errors});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse(key) {
|
toggleCollapse(key) {
|
||||||
this.setState({ ...this.state, [key]: !this.state[key] });
|
this.setState({...this.state, [key]: !this.state[key]});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGeneralCard() {
|
||||||
|
|
||||||
|
let errors = [];
|
||||||
|
for (let i = 0; i < this.state.generalErrors.length; i++) {
|
||||||
|
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("generalErrors", i)} {...this.state.generalErrors[i]}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className={"card-header"} style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.toggleCollapse("generalOpened")}>
|
||||||
|
<h4 className={"card-title"}>
|
||||||
|
<Icon className={"mr-2"} icon={"cogs"}/>
|
||||||
|
General Settings
|
||||||
|
</h4>
|
||||||
|
<div className={"card-tools"}>
|
||||||
|
<span className={"btn btn-tool btn-sm"}>
|
||||||
|
<Icon icon={this.state.generalOpened ? "angle-up" : "angle-down"}/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Collapse isOpened={this.state.generalOpened}>
|
||||||
|
<div className={"card-body"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<div className={"col-12 col-lg-6"}>
|
||||||
|
{errors}
|
||||||
|
<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"}
|
||||||
|
defaultChecked={(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>
|
||||||
|
<div>
|
||||||
|
<button className={"btn btn-secondary ml-2"} onClick={() => this.onReset("generalErrors", this.generalKeys)}
|
||||||
|
disabled={this.state.isResetting || this.state.isSaving}>
|
||||||
|
{this.state.isResetting ?
|
||||||
|
<span>Resetting <Icon icon={"circle-notch"}/></span> : "Reset"}
|
||||||
|
</button>
|
||||||
|
<button className={"btn btn-success ml-2"} onClick={() => this.onSave("generalErrors", this.generalKeys)}
|
||||||
|
disabled={this.state.isResetting || this.state.isSaving}>
|
||||||
|
{this.state.isSaving ?
|
||||||
|
<span>Saving <Icon icon={"circle-notch"}/></span> : "Save"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmailCard() {
|
||||||
|
|
||||||
|
let errors = [];
|
||||||
|
for (let i = 0; i < this.state.mailErrors.length; i++) {
|
||||||
|
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("mailErrors", i)} {...this.state.mailErrors[i]}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className={"card-header"} style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.toggleCollapse("mailOpened")}>
|
||||||
|
<h4 className={"card-title"}>
|
||||||
|
<Icon className={"mr-2"} icon={"envelope"}/>
|
||||||
|
Mail Settings
|
||||||
|
</h4>
|
||||||
|
<div className={"card-tools"}>
|
||||||
|
<span className={"btn btn-tool btn-sm"}>
|
||||||
|
<Icon icon={this.state.mailOpened ? "angle-up" : "angle-down"}/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Collapse isOpened={this.state.mailOpened}>
|
||||||
|
<div className={"card-body"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<div className={"col-12 col-lg-6"}>
|
||||||
|
{errors}
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<div>
|
||||||
|
<button className={"btn btn-secondary ml-2"}
|
||||||
|
onClick={() => this.onReset("mailErrors", this.mailKeys)}
|
||||||
|
disabled={this.state.isResetting || this.state.isSaving}>
|
||||||
|
{this.state.isResetting ?
|
||||||
|
<span>Resetting <Icon icon={"circle-notch"}/></span> : "Reset"}
|
||||||
|
</button>
|
||||||
|
<button className={"btn btn-success ml-2"}
|
||||||
|
onClick={() => this.onSave("mailErrors", this.mailKeys)}
|
||||||
|
disabled={this.state.isResetting || this.state.isSaving}>
|
||||||
|
{this.state.isSaving ?
|
||||||
|
<span>Saving <Icon icon={"circle-notch"}/></span> : "Save"}
|
||||||
|
</button>
|
||||||
|
</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.test_email}
|
||||||
|
placeholder={"Enter a email address"}
|
||||||
|
onChange={(e) => this.setState({
|
||||||
|
...this.state,
|
||||||
|
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.isSending}>
|
||||||
|
{this.state.isSending ?
|
||||||
|
<span>Sending <Icon icon={"circle-notch"}/></span> : "Send Mail"}
|
||||||
|
</button>
|
||||||
|
<div className={"col-10"}>
|
||||||
|
{ this.state.unsavedMailSettings ? <span className={"text-red"}>You need to save your mail settings first.</span> : null }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
getUncategorisedCard() {
|
||||||
|
|
||||||
|
let keys = [];
|
||||||
|
let tr = [];
|
||||||
|
for (let key in this.state.settings) {
|
||||||
|
if (this.state.settings.hasOwnProperty(key)) {
|
||||||
|
if (!this.generalKeys.includes(key) && !this.mailKeys.includes(key)) {
|
||||||
|
keys.push(key);
|
||||||
|
tr.push(<tr key={"tr-" + key}>
|
||||||
|
<td>{key}</td>
|
||||||
|
<td>{this.state.settings[key]}</td>
|
||||||
|
</tr>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let errors = [];
|
||||||
|
for (let i = 0; i < this.state.etcErrors.length; i++) {
|
||||||
|
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("etcErrors", i)} {...this.state.etcErrors[i]}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className={"card-header"} style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.toggleCollapse("etcOpened")}>
|
||||||
|
<h4 className={"card-title"}>
|
||||||
|
<Icon className={"mr-2"} icon={"cogs"}/>
|
||||||
|
General Settings
|
||||||
|
</h4>
|
||||||
|
<div className={"card-tools"}>
|
||||||
|
<span className={"btn btn-tool btn-sm"}>
|
||||||
|
<Icon icon={this.state.etcOpened ? "angle-up" : "angle-down"}/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Collapse isOpened={this.state.etcOpened}>
|
||||||
|
<div className={"card-body"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<div className={"col-12 col-lg-6"}>
|
||||||
|
{errors}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Key</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{tr}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<button className={"btn btn-secondary ml-2"} onClick={() => this.onReset("etcErrors", keys)}
|
||||||
|
disabled={this.state.isResetting || this.state.isSaving}>
|
||||||
|
{this.state.isResetting ?
|
||||||
|
<span>Resetting <Icon icon={"circle-notch"}/></span> : "Reset"}
|
||||||
|
</button>
|
||||||
|
<button className={"btn btn-success ml-2"} onClick={() => this.onSave("etcErrors", keys)}
|
||||||
|
disabled={this.state.isResetting || this.state.isSaving}>
|
||||||
|
{this.state.isSaving ?
|
||||||
|
<span>Saving <Icon icon={"circle-notch"}/></span> : "Save"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
let errors = [];
|
let errors = [];
|
||||||
for (let i = 0; i < this.state.errors.length; i++) {
|
for (let i = 0; i < this.state.errors.length; i++) {
|
||||||
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
|
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("errors", i)} {...this.state.errors[i]}/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@ -73,80 +379,13 @@ export default class Settings extends React.Component {
|
|||||||
{errors}
|
{errors}
|
||||||
<div>
|
<div>
|
||||||
<div className={"card card-primary"}>
|
<div className={"card card-primary"}>
|
||||||
<div className={"card-header"} style={{cursor: "pointer"}} onClick={() => this.toggleCollapse("generalOpened")}>
|
{this.getGeneralCard()}
|
||||||
<h4 className={"card-title"}>
|
|
||||||
<Icon className={"mr-2"} icon={"cogs"} />
|
|
||||||
General Settings
|
|
||||||
</h4>
|
|
||||||
<div className={"card-tools"}>
|
|
||||||
<span className={"btn btn-tool btn-sm"}>
|
|
||||||
<Icon icon={ this.state.generalOpened ? "angle-up" : "angle-down" }/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Collapse isOpened={this.state.generalOpened}>
|
|
||||||
<div className={"card-body"}>
|
|
||||||
<div className={"row"}>
|
|
||||||
<div className={"col-12 col-lg-6"}>
|
|
||||||
<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"}
|
|
||||||
defaultChecked={(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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={"card card-warning"}>
|
<div className={"card card-warning"}>
|
||||||
<div className={"card-header"} style={{cursor: "pointer"}} onClick={() => this.toggleCollapse("mailOpened")}>
|
{this.getEmailCard()}
|
||||||
<h4 className={"card-title"}>
|
|
||||||
<Icon className={"mr-2"} icon={"envelope"} />
|
|
||||||
Mail Settings
|
|
||||||
</h4>
|
|
||||||
<div className={"card-tools"}>
|
|
||||||
<span className={"btn btn-tool btn-sm"}>
|
|
||||||
<Icon icon={ this.state.generalOpened ? "angle-up" : "angle-down" }/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Collapse isOpened={this.state.mailOpened}>
|
|
||||||
<div className={"card-body"}>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={"card card-secondary"}>
|
<div className={"card card-secondary"}>
|
||||||
<div className={"card-header"} style={{cursor: "pointer"}} onClick={() => this.toggleCollapse("etcOpened")}>
|
{this.getUncategorisedCard()}
|
||||||
<h4 className={"card-title"}>
|
|
||||||
<Icon className={"mr-2"} icon={"stream"} />
|
|
||||||
Uncategorised
|
|
||||||
</h4>
|
|
||||||
<div className={"card-tools"}>
|
|
||||||
<span className={"btn btn-tool btn-sm"}>
|
|
||||||
<Icon icon={ this.state.generalOpened ? "angle-up" : "angle-down" }/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Collapse isOpened={this.state.etcOpened}>
|
|
||||||
<div className={"card-body"}>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -163,6 +402,81 @@ export default class Settings extends React.Component {
|
|||||||
value = event.target.checked ? "1" : "0";
|
value = event.target.checked ? "1" : "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ ...this.state, user: { ...this.state.user, settings: { ...this.state.settings, [name]: value} } });
|
let changedMailSettings = false;
|
||||||
|
if (name.startsWith("mail_")) {
|
||||||
|
changedMailSettings = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({...this.state, settings: {...this.state.settings, [name]: value},
|
||||||
|
unsavedMailSettings: changedMailSettings ? true : this.state.unsavedMailSettings
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onReset(errorKey, keys) {
|
||||||
|
this.setState({...this.state, isResetting: true});
|
||||||
|
|
||||||
|
let values = {};
|
||||||
|
for (let key of keys) {
|
||||||
|
values[key] = this.state.settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mailSettingsSaved = errorKey === "mailErrors";
|
||||||
|
this.parent.api.getSettings().then((res) => {
|
||||||
|
if (!res.success) {
|
||||||
|
let errors = this.state[errorKey].slice();
|
||||||
|
errors.push({title: "Error fetching settings", message: res.msg});
|
||||||
|
this.setState({...this.state, [errorKey]: errors, isResetting: false});
|
||||||
|
} else {
|
||||||
|
let newSettings = {...this.state.settings};
|
||||||
|
for (let key of keys) {
|
||||||
|
newSettings[key] = res.settings[key] ?? "";
|
||||||
|
}
|
||||||
|
this.setState({...this.state, settings: newSettings, isResetting: false,
|
||||||
|
unsavedMailSettings: mailSettingsSaved ? false : this.state.unsavedMailSettings});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSave(errorKey, keys) {
|
||||||
|
this.setState({...this.state, isSaving: true});
|
||||||
|
|
||||||
|
let values = {};
|
||||||
|
for (let key of keys) {
|
||||||
|
if (key === "mail_password" && !this.state.settings[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
values[key] = this.state.settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mailSettingsSaved = errorKey === "mailErrors";
|
||||||
|
this.parent.api.saveSettings(values).then((res) => {
|
||||||
|
if (!res.success) {
|
||||||
|
let errors = this.state[errorKey].slice();
|
||||||
|
errors.push({title: "Error fetching settings", message: res.msg});
|
||||||
|
this.setState({...this.state, [errorKey]: errors, isSaving: false});
|
||||||
|
} else {
|
||||||
|
this.setState({...this.state, isSaving: false, unsavedMailSettings: mailSettingsSaved ? false : this.state.unsavedMailSettings });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSendTestMail() {
|
||||||
|
this.setState({...this.state, isSending: true});
|
||||||
|
|
||||||
|
this.parent.api.sendTestMail(this.state.test_email).then((res) => {
|
||||||
|
let errors = this.state.mailErrors.slice();
|
||||||
|
if (!res.success) {
|
||||||
|
errors.push({title: "Error sending email", message: res.msg});
|
||||||
|
this.setState({...this.state, mailErrors: errors, isSending: false});
|
||||||
|
} else {
|
||||||
|
errors.push({
|
||||||
|
title: "Success!",
|
||||||
|
message: "E-Mail was successfully sent, check your inbox.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
this.setState({...this.state, mailErrors: errors, isSending: false, test_email: ""});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user