Settings + Test Mail
This commit is contained in:
1 parent
6eb9bf333f
commit
09475be545
11 files changed
+532
-108
No files matched your search
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Vendored
+2
-2
@@ -9110,7 +9110,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return API; });\n/* harmony import */ var babel_polyfill__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babel-polyfill */ \"./node_modules/babel-polyfill/lib/index.js\");\n/* harmony import */ var babel_polyfill__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babel_polyfill__WEBPACK_IMPORTED_MODULE_0__);\nfunction asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }\n\nfunction _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err); } _next(undefined); }); }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n\nvar API = /*#__PURE__*/function () {\n function API() {\n _classCallCheck(this, API);\n\n this.loggedIn = false;\n this.user = {};\n }\n\n _createClass(API, [{\n key: \"csrfToken\",\n value: function csrfToken() {\n return this.loggedIn ? this.user.session.csrf_token : null;\n }\n }, {\n key: \"apiCall\",\n value: function () {\n var _apiCall = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(method, params) {\n var response, res;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n params = params || {};\n params.csrf_token = this.csrfToken();\n _context.next = 4;\n return fetch(\"/api/\" + method, {\n method: 'post',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(params)\n });\n\n case 4:\n response = _context.sent;\n _context.next = 7;\n return response.json();\n\n case 7:\n res = _context.sent;\n\n if (!res.success && res.msg === \"You are not logged in.\") {\n document.location.reload();\n }\n\n return _context.abrupt(\"return\", res);\n\n case 10:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, this);\n }));\n\n function apiCall(_x, _x2) {\n return _apiCall.apply(this, arguments);\n }\n\n return apiCall;\n }()\n }, {\n key: \"fetchUser\",\n value: function () {\n var _fetchUser = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var response, data;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n _context2.next = 2;\n return fetch(\"/api/user/info\");\n\n case 2:\n response = _context2.sent;\n _context2.next = 5;\n return response.json();\n\n case 5:\n data = _context2.sent;\n this.user = data[\"user\"];\n this.loggedIn = data[\"loggedIn\"];\n return _context2.abrupt(\"return\", data && data.success && data.loggedIn);\n\n case 9:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, this);\n }));\n\n function fetchUser() {\n return _fetchUser.apply(this, arguments);\n }\n\n return fetchUser;\n }()\n }, {\n key: \"editUser\",\n value: function () {\n var _editUser = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(id, username, email, password, groups) {\n returnLine truncated
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return API; });\n/* harmony import */ var babel_polyfill__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babel-polyfill */ \"./node_modules/babel-polyfill/lib/index.js\");\n/* harmony import */ var babel_polyfill__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babel_polyfill__WEBPACK_IMPORTED_MODULE_0__);\nfunction asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }\n\nfunction _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err); } _next(undefined); }); }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n\nvar API = /*#__PURE__*/function () {\n function API() {\n _classCallCheck(this, API);\n\n this.loggedIn = false;\n this.user = {};\n }\n\n _createClass(API, [{\n key: \"csrfToken\",\n value: function csrfToken() {\n return this.loggedIn ? this.user.session.csrf_token : null;\n }\n }, {\n key: \"apiCall\",\n value: function () {\n var _apiCall = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(method, params) {\n var response, res;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n params = params || {};\n params.csrf_token = this.csrfToken();\n _context.next = 4;\n return fetch(\"/api/\" + method, {\n method: 'post',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(params)\n });\n\n case 4:\n response = _context.sent;\n _context.next = 7;\n return response.json();\n\n case 7:\n res = _context.sent;\n\n if (!res.success && res.msg === \"You are not logged in.\") {\n document.location.reload();\n }\n\n return _context.abrupt(\"return\", res);\n\n case 10:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, this);\n }));\n\n function apiCall(_x, _x2) {\n return _apiCall.apply(this, arguments);\n }\n\n return apiCall;\n }()\n }, {\n key: \"fetchUser\",\n value: function () {\n var _fetchUser = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var response, data;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n _context2.next = 2;\n return fetch(\"/api/user/info\");\n\n case 2:\n response = _context2.sent;\n _context2.next = 5;\n return response.json();\n\n case 5:\n data = _context2.sent;\n this.user = data[\"user\"];\n this.loggedIn = data[\"loggedIn\"];\n return _context2.abrupt(\"return\", data && data.success && data.loggedIn);\n\n case 9:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, this);\n }));\n\n function fetchUser() {\n return _fetchUser.apply(this, arguments);\n }\n\n return fetchUser;\n }()\n }, {\n key: \"editUser\",\n value: function () {\n var _editUser = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(id, username, email, password, groups) {\n returnLine truncated
|
||||
|
||||
/***/ }),
|
||||
|
||||
@@ -9335,7 +9335,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Settings; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/esm/react-router-dom.js\");\n/* harmony import */ var _elements_alert__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../elements/alert */ \"./src/elements/alert.js\");\n/* harmony import */ var react_collapse_lib_Collapse__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-collapse/lib/Collapse */ \"./node_modules/react-collapse/lib/Collapse.js\");\n/* harmony import */ var react_collapse_lib_Collapse__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react_collapse_lib_Collapse__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _elements_icon__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../elements/icon */ \"./src/elements/icon.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunctionLine truncated
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Settings; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/esm/react-router-dom.js\");\n/* harmony import */ var _elements_alert__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../elements/alert */ \"./src/elements/alert.js\");\n/* harmony import */ var react_collapse_lib_Collapse__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-collapse/lib/Collapse */ \"./node_modules/react-collapse/lib/Collapse.js\");\n/* harmony import */ var react_collapse_lib_Collapse__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react_collapse_lib_Collapse__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _elements_icon__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../elements/icon */ \"./src/elements/icon.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === \"undefined\" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i];
Line truncated
|
||||
|
||||
/***/ }),
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
}
|
||||
};
|
||||
+396
-82
@@ -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>
|
||||
@@ -156,13 +395,88 @@ export default class Settings extends React.Component {
|
||||
onChangeValue(event) {
|
||||
const target = event.target;
|
||||
const name = target.name;
|
||||
const type = target.type;
|
||||
const type = target.type;
|
||||
let value = target.value;
|
||||
|
||||
if (type === "checkbox") {
|
||||
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: ""});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user