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) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'from' => new Parameter('from', Parameter::TYPE_EMAIL),
|
||||
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
|
||||
'subject' => new StringType('subject', -1),
|
||||
'body' => new StringType('body', -1),
|
||||
'fromName' => new StringType('fromName', -1, true, ''),
|
||||
'replyTo' => new Parameter('to', Parameter::TYPE_EMAIL, true, ''),
|
||||
));
|
||||
$this->isPublic = false;
|
||||
}
|
||||
@ -28,6 +25,7 @@ class SendMail extends Request {
|
||||
|
||||
if ($this->success) {
|
||||
$settings = $req->getResult()["settings"];
|
||||
|
||||
if (!isset($settings["mail_enabled"]) || $settings["mail_enabled"] !== "1") {
|
||||
$this->createError("Mail is not configured yet.");
|
||||
return null;
|
||||
@ -37,7 +35,9 @@ class SendMail extends Request {
|
||||
$port = intval($settings["mail_port"] ?? "25");
|
||||
$login = $settings["mail_username"] ?? "";
|
||||
$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;
|
||||
@ -56,7 +56,7 @@ class SendMail extends Request {
|
||||
try {
|
||||
$mail = new PHPMailer;
|
||||
$mail->IsSMTP();
|
||||
$mail->setFrom($this->getParam('from'), $this->getParam('fromName'));
|
||||
$mail->setFrom($mailConfig->getProperty("from"));
|
||||
$mail->addAddress($this->getParam('to'));
|
||||
$mail->Subject = $this->getParam('subject');
|
||||
$mail->SMTPDebug = 0;
|
||||
@ -70,11 +70,6 @@ class SendMail extends Request {
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$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();
|
||||
if (!$this->success) {
|
||||
$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\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondLike;
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\SQL\Condition\CondRegex;
|
||||
use Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Objects\User;
|
||||
@ -42,11 +43,12 @@ namespace Api\Settings {
|
||||
$query = $sql->select("name", "value") ->from("Settings");
|
||||
|
||||
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()) {
|
||||
$query->where(new Compare("name", "jwt_secret", "!="));
|
||||
$query->where(new CondNot("private"));
|
||||
}
|
||||
|
||||
$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());
|
||||
} else {
|
||||
$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;
|
||||
}
|
||||
} else {
|
||||
|
@ -138,24 +138,64 @@ class CreateDatabase {
|
||||
->addRow("^/register(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register")
|
||||
->addRow("^/confirmEmail(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail")
|
||||
->addRow("^/acceptInvite(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite")
|
||||
->addRow("^/resetPassword(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResetPassword")
|
||||
->addRow("^/$", "static", "/static/welcome.html", NULL);
|
||||
|
||||
$queries[] = $sql->createTable("Settings")
|
||||
->addString("name", 32)
|
||||
->addString("value", 1024, true)
|
||||
->addBool("private", false)
|
||||
->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_host", "")
|
||||
->addRow("mail_port", "")
|
||||
->addRow("mail_username", "")
|
||||
->addRow("mail_password", "")
|
||||
->addRow("mail_from", "");
|
||||
->addRow("mail_host", "", false)
|
||||
->addRow("mail_port", "", false)
|
||||
->addRow("mail_username", "", false)
|
||||
->addRow("mail_password", "", true)
|
||||
->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);
|
||||
$queries[] = $settingsQuery;
|
||||
|
||||
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) {
|
||||
$query->addRow("site_name", $this->siteName)
|
||||
->addRow("base_url", $this->baseUrl)
|
||||
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0")
|
||||
->addRow("installation_completed", $this->installationComplete ? "1" : "0")
|
||||
->addRow("jwt_secret", $this->jwtSecret);
|
||||
$query->addRow("site_name", $this->siteName, false)
|
||||
->addRow("base_url", $this->baseUrl, false)
|
||||
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false)
|
||||
->addRow("installation_completed", $this->installationComplete ? "1" : "0", true)
|
||||
->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\CondBool;
|
||||
use Driver\SQL\Condition\CondIn;
|
||||
use Driver\SQL\Condition\Condition;
|
||||
use Driver\SQL\Condition\CondKeyword;
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
use Driver\SQL\Condition\CondRegex;
|
||||
use Driver\SQL\Constraint\Constraint;
|
||||
@ -339,6 +341,9 @@ abstract class SQL {
|
||||
return implode(" AND ", $conditions);
|
||||
}
|
||||
} else if($condition instanceof CondIn) {
|
||||
|
||||
$value = $condition->getValues();
|
||||
|
||||
$values = array();
|
||||
foreach ($condition->getValues() as $value) {
|
||||
$values[] = $this->addValue($value, $params);
|
||||
@ -353,6 +358,15 @@ abstract class SQL {
|
||||
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
||||
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
|
||||
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 {
|
||||
$this->lastError = "Unsupported condition type: " . get_class($condition);
|
||||
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 = "") {
|
||||
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 = {
|
||||
errors: [],
|
||||
mailErrors: [],
|
||||
generalErrors: [],
|
||||
etcErrors: [],
|
||||
settings: {},
|
||||
generalOpened: true,
|
||||
mailOpened: true,
|
||||
etcOpened : true
|
||||
etcOpened: true,
|
||||
isResetting: false,
|
||||
isSaving: false,
|
||||
isSending: false,
|
||||
test_email: "",
|
||||
unsavedMailSettings: false
|
||||
};
|
||||
|
||||
this.parent = {
|
||||
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() {
|
||||
this.parent.api.getSettings().then((res) => {
|
||||
if (res.success) {
|
||||
this.setState({...this.state, settings: res.settings });
|
||||
this.setState({...this.state, settings: res.settings});
|
||||
} else {
|
||||
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});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeError(i) {
|
||||
if (i >= 0 && i < this.state.errors.length) {
|
||||
let errors = this.state.errors.slice();
|
||||
removeError(key, i) {
|
||||
if (i >= 0 && i < this.state[key].length) {
|
||||
let errors = this.state[key].slice();
|
||||
errors.splice(i, 1);
|
||||
this.setState({...this.state, errors: errors});
|
||||
this.setState({...this.state, [key]: errors});
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
let errors = [];
|
||||
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 <>
|
||||
@ -73,80 +379,13 @@ export default class Settings extends React.Component {
|
||||
{errors}
|
||||
<div>
|
||||
<div className={"card card-primary"}>
|
||||
<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"}>
|
||||
<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>
|
||||
{this.getGeneralCard()}
|
||||
</div>
|
||||
<div className={"card card-warning"}>
|
||||
<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.generalOpened ? "angle-up" : "angle-down" }/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Collapse isOpened={this.state.mailOpened}>
|
||||
<div className={"card-body"}>
|
||||
|
||||
</div>
|
||||
</Collapse>
|
||||
{this.getEmailCard()}
|
||||
</div>
|
||||
<div className={"card card-secondary"}>
|
||||
<div className={"card-header"} style={{cursor: "pointer"}} onClick={() => this.toggleCollapse("etcOpened")}>
|
||||
<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>
|
||||
{this.getUncategorisedCard()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,6 +402,81 @@ export default class Settings extends React.Component {
|
||||
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