Status + More dependencies
This commit is contained in:
parent
c7b8301db1
commit
077fe68914
53
core/Api/Routes/Fetch.class.php
Normal file
53
core/Api/Routes/Fetch.class.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Routes;
|
||||||
|
|
||||||
|
use \Api\Request;
|
||||||
|
use \Driver\SQL\Condition\Compare;
|
||||||
|
|
||||||
|
class Fetch extends Request {
|
||||||
|
|
||||||
|
private array $notifications;
|
||||||
|
|
||||||
|
public function __construct($user, $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array());
|
||||||
|
$this->loginRequired = true;
|
||||||
|
$this->csrfTokenRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()) {
|
||||||
|
if(!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
|
||||||
|
$res = $sql
|
||||||
|
->select("uid", "request", "action", "target", "extra", "active")
|
||||||
|
->from("Route")
|
||||||
|
->orderBy("uid")
|
||||||
|
->ascending()
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
$this->success = ($res !== FALSE);
|
||||||
|
|
||||||
|
if ($this->success) {
|
||||||
|
$routes = array();
|
||||||
|
foreach($res as $row) {
|
||||||
|
$routes[] = array(
|
||||||
|
"uid" => intval($row["uid"]),
|
||||||
|
"request" => $row["request"],
|
||||||
|
"action" => $row["action"],
|
||||||
|
"target" => $row["target"],
|
||||||
|
"extra" => $row["extra"],
|
||||||
|
"active" => intval($row["active"]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->result["routes"] = $routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace Api;
|
namespace Api;
|
||||||
|
|
||||||
use Driver\SQL\Condition\Compare;
|
use Driver\SQL\Condition\Compare;
|
||||||
|
use Driver\SQL\Condition\CondBool;
|
||||||
|
|
||||||
class Stats extends Request {
|
class Stats extends Request {
|
||||||
|
|
||||||
@ -16,14 +17,21 @@ class Stats extends Request {
|
|||||||
private function getUserCount() {
|
private function getUserCount() {
|
||||||
$sql = $this->user->getSQL();
|
$sql = $this->user->getSQL();
|
||||||
$res = $sql->select($sql->count())->from("User")->execute();
|
$res = $sql->select($sql->count())->from("User")->execute();
|
||||||
$this->success = ($res !== FALSE);
|
$this->success = $this->success && ($res !== FALSE);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
return ($this->success ? $res[0]["count"] : 0);
|
return ($this->success ? $res[0]["count"] : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getPageCount() {
|
private function getPageCount() {
|
||||||
return 0;
|
$sql = $this->user->getSQL();
|
||||||
|
$res = $sql->select($sql->count())->from("Route")
|
||||||
|
->where(new CondBool("active"))
|
||||||
|
->execute();
|
||||||
|
$this->success = $this->success && ($res !== FALSE);
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
|
return ($this->success ? $res[0]["count"] : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getVisitorStatistics() {
|
private function getVisitorStatistics() {
|
||||||
@ -43,7 +51,7 @@ class Stats extends Request {
|
|||||||
->ascending()
|
->ascending()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
$this->success = $this->success && ($res !== FALSE);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
$visitors = array();
|
$visitors = array();
|
||||||
@ -72,6 +80,13 @@ class Stats extends Request {
|
|||||||
$this->result["userCount"] = $userCount;
|
$this->result["userCount"] = $userCount;
|
||||||
$this->result["pageCount"] = $pageCount;
|
$this->result["pageCount"] = $pageCount;
|
||||||
$this->result["visitors"] = $visitorStatistics;
|
$this->result["visitors"] = $visitorStatistics;
|
||||||
|
$this->result["server"] = array(
|
||||||
|
"server" => $_SERVER["SERVER_SOFTWARE"] ?? "Unknown",
|
||||||
|
"memory_usage" => memory_get_usage(),
|
||||||
|
"load_avg" => sys_getloadavg(),
|
||||||
|
"database" => $this->user->getSQL()->getStatus(),
|
||||||
|
"mail" => $this->user->getConfiguration()->getMail() !== NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
|
@ -119,6 +119,18 @@ class CreateDatabase {
|
|||||||
->addString("cookie", 26)
|
->addString("cookie", 26)
|
||||||
->unique("month", "cookie");
|
->unique("month", "cookie");
|
||||||
|
|
||||||
|
$queries[] = $sql->createTable("Route")
|
||||||
|
->addSerial("uid")
|
||||||
|
->addString("request", 128)
|
||||||
|
->addEnum("action", array("redirect_temporary", "redirect_permanently", "static", "dynamic"))
|
||||||
|
->addString("target", 128)
|
||||||
|
->addString("extra", 64, true)
|
||||||
|
->addBool("active", true)
|
||||||
|
->primaryKey("uid");
|
||||||
|
|
||||||
|
$queries[] = $sql->insert("Route", array("request", "action", "target"))
|
||||||
|
->addRow("/admin(/.*)?", "dynamic", "\\Core\\Documents\\AdminDashboard");
|
||||||
|
|
||||||
return $queries;
|
return $queries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,4 +300,7 @@ class MySQL extends SQL {
|
|||||||
return new Keyword("NOW()");
|
return new Keyword("NOW()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStatus() {
|
||||||
|
return mysqli_stat($this->connection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,4 +290,15 @@ class PostgreSQL extends SQL {
|
|||||||
public function currentTimestamp() {
|
public function currentTimestamp() {
|
||||||
return new Keyword("CURRENT_TIMESTAMP");
|
return new Keyword("CURRENT_TIMESTAMP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStatus() {
|
||||||
|
$version = pg_version($this->connection)["client"] ?? "??";
|
||||||
|
$status = pg_connection_status($this->connection);
|
||||||
|
static $statusTexts = array(
|
||||||
|
PGSQL_CONNECTION_OK => "PGSQL_CONNECTION_OK",
|
||||||
|
PGSQL_CONNECTION_BAD => "PGSQL_CONNECTION_BAD",
|
||||||
|
);
|
||||||
|
|
||||||
|
return ($statusTexts[$status] ?? "Unknown") . " (v$version)";
|
||||||
|
}
|
||||||
}
|
}
|
@ -369,4 +369,6 @@ abstract class SQL {
|
|||||||
|
|
||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract function getStatus();
|
||||||
}
|
}
|
249
js/admin.min.js
vendored
249
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
168
src/package-lock.json
generated
168
src/package-lock.json
generated
@ -1129,6 +1129,87 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
||||||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||||
},
|
},
|
||||||
|
"@emotion/cache": {
|
||||||
|
"version": "10.0.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
|
||||||
|
"integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/sheet": "0.9.4",
|
||||||
|
"@emotion/stylis": "0.8.5",
|
||||||
|
"@emotion/utils": "0.11.3",
|
||||||
|
"@emotion/weak-memoize": "0.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/core": {
|
||||||
|
"version": "10.0.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.28.tgz",
|
||||||
|
"integrity": "sha512-pH8UueKYO5jgg0Iq+AmCLxBsvuGtvlmiDCOuv8fGNYn3cowFpLN98L8zO56U0H1PjDIyAlXymgL3Wu7u7v6hbA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"@emotion/cache": "^10.0.27",
|
||||||
|
"@emotion/css": "^10.0.27",
|
||||||
|
"@emotion/serialize": "^0.11.15",
|
||||||
|
"@emotion/sheet": "0.9.4",
|
||||||
|
"@emotion/utils": "0.11.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/css": {
|
||||||
|
"version": "10.0.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz",
|
||||||
|
"integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/serialize": "^0.11.15",
|
||||||
|
"@emotion/utils": "0.11.3",
|
||||||
|
"babel-plugin-emotion": "^10.0.27"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/hash": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
|
||||||
|
},
|
||||||
|
"@emotion/memoize": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
|
||||||
|
},
|
||||||
|
"@emotion/serialize": {
|
||||||
|
"version": "0.11.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz",
|
||||||
|
"integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/hash": "0.8.0",
|
||||||
|
"@emotion/memoize": "0.7.4",
|
||||||
|
"@emotion/unitless": "0.7.5",
|
||||||
|
"@emotion/utils": "0.11.3",
|
||||||
|
"csstype": "^2.5.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/sheet": {
|
||||||
|
"version": "0.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz",
|
||||||
|
"integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA=="
|
||||||
|
},
|
||||||
|
"@emotion/stylis": {
|
||||||
|
"version": "0.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
|
||||||
|
"integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
|
||||||
|
},
|
||||||
|
"@emotion/unitless": {
|
||||||
|
"version": "0.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
|
||||||
|
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||||
|
},
|
||||||
|
"@emotion/utils": {
|
||||||
|
"version": "0.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz",
|
||||||
|
"integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw=="
|
||||||
|
},
|
||||||
|
"@emotion/weak-memoize": {
|
||||||
|
"version": "0.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
|
||||||
|
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
|
||||||
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||||
@ -2550,6 +2631,30 @@
|
|||||||
"object.assign": "^4.1.0"
|
"object.assign": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"babel-plugin-emotion": {
|
||||||
|
"version": "10.0.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz",
|
||||||
|
"integrity": "sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-module-imports": "^7.0.0",
|
||||||
|
"@emotion/hash": "0.8.0",
|
||||||
|
"@emotion/memoize": "0.7.4",
|
||||||
|
"@emotion/serialize": "^0.11.16",
|
||||||
|
"babel-plugin-macros": "^2.0.0",
|
||||||
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
|
"convert-source-map": "^1.5.0",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
|
"source-map": "^0.5.7"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-plugin-istanbul": {
|
"babel-plugin-istanbul": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz",
|
||||||
@ -2668,6 +2773,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz",
|
||||||
"integrity": "sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA=="
|
"integrity": "sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA=="
|
||||||
},
|
},
|
||||||
|
"babel-plugin-syntax-jsx": {
|
||||||
|
"version": "6.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
|
||||||
|
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
|
||||||
|
},
|
||||||
"babel-plugin-syntax-object-rest-spread": {
|
"babel-plugin-syntax-object-rest-spread": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
|
||||||
@ -4579,6 +4689,15 @@
|
|||||||
"utila": "~0.4"
|
"utila": "~0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dom-helpers": {
|
||||||
|
"version": "5.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
|
||||||
|
"integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.8.7",
|
||||||
|
"csstype": "^2.6.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dom-serializer": {
|
"dom-serializer": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||||
@ -5769,6 +5888,11 @@
|
|||||||
"pkg-dir": "^3.0.0"
|
"pkg-dir": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"find-root": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
|
||||||
|
},
|
||||||
"find-up": {
|
"find-up": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||||
@ -8099,6 +8223,11 @@
|
|||||||
"p-is-promise": "^2.0.0"
|
"p-is-promise": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"memoize-one": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
|
||||||
|
},
|
||||||
"memory-fs": {
|
"memory-fs": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
|
||||||
@ -9257,6 +9386,11 @@
|
|||||||
"ts-pnp": "^1.1.6"
|
"ts-pnp": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"porn": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/porn/-/porn-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-76quRf+/Bpku7+IFPtjjKOJhxRI="
|
||||||
|
},
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.26",
|
"version": "1.0.26",
|
||||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
|
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
|
||||||
@ -10739,6 +10873,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
||||||
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
|
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
|
||||||
},
|
},
|
||||||
|
"react-input-autosize": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
@ -11002,6 +11144,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-select": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"@emotion/cache": "^10.0.9",
|
||||||
|
"@emotion/core": "^10.0.9",
|
||||||
|
"@emotion/css": "^10.0.9",
|
||||||
|
"memoize-one": "^5.0.0",
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"react-input-autosize": "^2.2.2",
|
||||||
|
"react-transition-group": "^4.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-tooltip": {
|
"react-tooltip": {
|
||||||
"version": "4.2.7",
|
"version": "4.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.7.tgz",
|
||||||
@ -11018,6 +11175,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-transition-group": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"dom-helpers": "^5.0.1",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"read-pkg": {
|
"read-pkg": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
|
||||||
|
@ -8,12 +8,14 @@
|
|||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
|
"porn": "0.0.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chartjs-2": "^2.9.0",
|
"react-chartjs-2": "^2.9.0",
|
||||||
"react-collapse": "^5.0.1",
|
"react-collapse": "^5.0.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
|
"react-select": "^3.1.0",
|
||||||
"react-tooltip": "^4.2.7"
|
"react-tooltip": "^4.2.7"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -57,4 +57,8 @@ export default class API {
|
|||||||
async getStats() {
|
async getStats() {
|
||||||
return this.apiCall("stats");
|
return this.apiCall("stats");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRoutes() {
|
||||||
|
return this.apiCall("routes/fetch");
|
||||||
|
}
|
||||||
};
|
};
|
@ -4,4 +4,23 @@ function getPeriodString(date) {
|
|||||||
return moment(date).fromNow();
|
return moment(date).fromNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function humanReadableSize(bytes, dp = 1) {
|
||||||
|
const thresh = 1024;
|
||||||
|
|
||||||
|
if (Math.abs(bytes) < thresh) {
|
||||||
|
return bytes + ' B';
|
||||||
|
}
|
||||||
|
|
||||||
|
const units = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
|
let u = -1;
|
||||||
|
const r = 10**dp;
|
||||||
|
|
||||||
|
do {
|
||||||
|
bytes /= thresh;
|
||||||
|
++u;
|
||||||
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||||
|
|
||||||
|
return bytes.toFixed(dp) + ' ' + units[u];
|
||||||
|
}
|
||||||
|
|
||||||
export { getPeriodString };
|
export { getPeriodString };
|
@ -13,6 +13,7 @@ import Dialog from "./elements/dialog";
|
|||||||
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
|
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
|
||||||
import View404 from "./404";
|
import View404 from "./404";
|
||||||
import Logs from "./views/logs";
|
import Logs from "./views/logs";
|
||||||
|
import PageOverview from "./views/pages";
|
||||||
|
|
||||||
class AdminDashboard extends React.Component {
|
class AdminDashboard extends React.Component {
|
||||||
|
|
||||||
@ -82,6 +83,7 @@ class AdminDashboard extends React.Component {
|
|||||||
<Route exact={true} path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>
|
<Route exact={true} path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>
|
||||||
<Route exact={true} path={"/admin/users/adduser"}><CreateUser {...this.controlObj} /></Route>
|
<Route exact={true} path={"/admin/users/adduser"}><CreateUser {...this.controlObj} /></Route>
|
||||||
<Route path={"/admin/logs"}><Logs {...this.controlObj} /></Route>
|
<Route path={"/admin/logs"}><Logs {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
||||||
<Route path={"*"}><View404 /></Route>
|
<Route path={"*"}><View404 /></Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
<Dialog {...this.state.dialog}/>
|
<Dialog {...this.state.dialog}/>
|
||||||
|
@ -5,6 +5,7 @@ import { Bar } from 'react-chartjs-2';
|
|||||||
import {Collapse} from 'react-collapse';
|
import {Collapse} from 'react-collapse';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Alert from "../elements/alert";
|
import Alert from "../elements/alert";
|
||||||
|
import humanReadableSize from "../global";
|
||||||
|
|
||||||
export default class Overview extends React.Component {
|
export default class Overview extends React.Component {
|
||||||
|
|
||||||
@ -18,9 +19,11 @@ export default class Overview extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
chartVisible : true,
|
chartVisible : true,
|
||||||
|
statusVisible : true,
|
||||||
userCount: 0,
|
userCount: 0,
|
||||||
notificationCount: 0,
|
notificationCount: 0,
|
||||||
visitors: { },
|
visitors: { },
|
||||||
|
server: { load_avg: ["Unknown"] },
|
||||||
errors: []
|
errors: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,6 +48,7 @@ export default class Overview extends React.Component {
|
|||||||
userCount: res.userCount,
|
userCount: res.userCount,
|
||||||
pageCount: res.pageCount,
|
pageCount: res.pageCount,
|
||||||
visitors: res.visitors,
|
visitors: res.visitors,
|
||||||
|
server: res.server
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -172,20 +176,40 @@ export default class Overview extends React.Component {
|
|||||||
<Collapse isOpened={this.state.chartVisible}>
|
<Collapse isOpened={this.state.chartVisible}>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="chart">
|
<div className="chart">
|
||||||
<div className="chartjs-size-monitor">
|
|
||||||
<div className="chartjs-size-monitor-expand">
|
|
||||||
<div/>
|
|
||||||
</div>
|
|
||||||
<div className="chartjs-size-monitor-shrink">
|
|
||||||
<div/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Bar data={chartData} options={chartOptions} />
|
<Bar data={chartData} options={chartOptions} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-lg-6 col-12">
|
||||||
|
<div className="card card-warning">
|
||||||
|
<div className="card-header">
|
||||||
|
<h3 className="card-title">Server Status</h3>
|
||||||
|
<div className="card-tools">
|
||||||
|
<button type="button" className={"btn btn-tool"} onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({ ...this.state, statusVisible: !this.state.statusVisible });
|
||||||
|
}}>
|
||||||
|
<Icon icon={"minus"} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Collapse isOpened={this.state.statusVisible}>
|
||||||
|
<div className="card-body">
|
||||||
|
<ul className={"list-unstyled"}>
|
||||||
|
<li><b>Server</b>: {this.state.server.server}</li>
|
||||||
|
<li><b>Memory Usage</b>: {humanReadableSize(this.state.server.memory_usage)}</li>
|
||||||
|
<li><b>Load Average</b>: { this.state.server.load_avg.join(" ") }</li>
|
||||||
|
<li><b>Database</b>: { this.state.server.database }</li>
|
||||||
|
<li><b>Mail</b>: { this.state.server.mail === true
|
||||||
|
? <span>OK<Icon icon={""} className={"ml-2"}/></span>
|
||||||
|
: <Link to={"/admin/settings"}>Not configured</Link>}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
183
src/src/views/pages.js
Normal file
183
src/src/views/pages.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Alert from "../elements/alert";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import Icon from "../elements/icon";
|
||||||
|
import ReactTooltip from "react-tooltip";
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
|
export default class PageOverview extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.parent = {
|
||||||
|
api: props.api
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
routes: [],
|
||||||
|
errors: []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
"redirect_temporary": "Redirect Temporary",
|
||||||
|
"redirect_permanently": "Redirect Permanently",
|
||||||
|
"static": "Serve Static",
|
||||||
|
"dynamic": "Load Dynamic",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
buildOption(key) {
|
||||||
|
if (typeof key === 'object' && key.hasOwnProperty("key") && key.hasOwnProperty("label")) {
|
||||||
|
return key;
|
||||||
|
} else {
|
||||||
|
return { value: key, label: this.options[key] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeError(i) {
|
||||||
|
if (i >= 0 && i < this.state.errors.length) {
|
||||||
|
let errors = this.state.errors.slice();
|
||||||
|
errors.splice(i, 1);
|
||||||
|
this.setState({...this.state, errors: errors});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.parent.api.getRoutes().then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.setState({...this.state, routes: res.routes});
|
||||||
|
} else {
|
||||||
|
let errors = this.state.errors.slice();
|
||||||
|
errors.push({ title: "Error fetching routes", message: res.msg });
|
||||||
|
this.setState({...this.state, errors: errors});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
let errors = [];
|
||||||
|
let rows = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.state.errors.length; i++) {
|
||||||
|
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = [];
|
||||||
|
for (let key in Object.keys(this.options)) {
|
||||||
|
options.push(this.buildOption(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.state.routes.length; i++) {
|
||||||
|
let route = this.state.routes[i];
|
||||||
|
rows.push(
|
||||||
|
<tr key={"route-" + i}>
|
||||||
|
<td className={"align-middle"}>{route.request}</td>
|
||||||
|
<td className={"text-center"}>
|
||||||
|
<Select options={options} value={this.buildOption(route.action)} onChange={(selectedOption) => this.changeAction(i, selectedOption)} />
|
||||||
|
</td>
|
||||||
|
<td className={"align-middle"}>{route.target}</td>
|
||||||
|
<td className={"align-middle"}>{route.extra}</td>
|
||||||
|
<td className={"text-center"}>
|
||||||
|
<input
|
||||||
|
type={"checkbox"}
|
||||||
|
checked={route.active === 1}
|
||||||
|
onChange={(e) => this.changeActive(i, e)} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className={"content-header"}>
|
||||||
|
<div className={"container-fluid"}>
|
||||||
|
<div className={"row mb-2"}>
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<h1 className="m-0 text-dark">Routes & Pages</h1>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<ol className="breadcrumb float-sm-right">
|
||||||
|
<li className="breadcrumb-item"><Link to={"/admin/dashboard"}>Home</Link></li>
|
||||||
|
<li className="breadcrumb-item active">Pages</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={"content"}>
|
||||||
|
{errors}
|
||||||
|
<div className={"content-fluid"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<div className={"col-lg-8 col-12"}>
|
||||||
|
<table className={"table"}>
|
||||||
|
<thead className={"thead-dark"}>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Request
|
||||||
|
<Icon icon={"question-circle"} style={{"color": "#0069d9"}}
|
||||||
|
data-tip={"The request, the user is making. Can also be interpreted as a regular expression."}
|
||||||
|
data-type={"info"} data-place={"bottom"} data-effect={"solid"}/>
|
||||||
|
</td>
|
||||||
|
<td style={{minWidth: "250px"}}>
|
||||||
|
Action
|
||||||
|
<Icon icon={"question-circle"} style={{"color": "#0069d9"}}
|
||||||
|
data-tip={"The action to be taken"}
|
||||||
|
data-type={"info"} data-place={"bottom"} data-effect={"solid"}/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
Target
|
||||||
|
<Icon icon={"question-circle"} style={{"color": "#0069d9"}}
|
||||||
|
data-tip={"Any URL if action is redirect or static. Path to a class inheriting from Document, " +
|
||||||
|
"if dynamic is chosen"}
|
||||||
|
data-type={"info"} data-place={"bottom"} data-effect={"solid"}/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
Extra
|
||||||
|
<Icon icon={"question-circle"} style={{"color": "#0069d9"}}
|
||||||
|
data-tip={"If action is dynamic, a view name can be entered here, otherwise leave empty."}
|
||||||
|
data-type={"info"} data-place={"bottom"} data-effect={"solid"}/>
|
||||||
|
</td>
|
||||||
|
<td className={"text-center"}>
|
||||||
|
Active
|
||||||
|
<Icon icon={"question-circle"} style={{"color": "#0069d9"}}
|
||||||
|
data-tip={"True, if the route is currently active."}
|
||||||
|
data-type={"info"} data-place={"bottom"} data-effect={"solid"}/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ReactTooltip/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
changeAction(index, selectedOption) {
|
||||||
|
if (index < 0 || index >= this.state.routes.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let routes = this.state.routes.slice();
|
||||||
|
routes[index].action = selectedOption;
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
routes: routes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeActive(index, e) {
|
||||||
|
if (index < 0 || index >= this.state.routes.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let routes = this.state.routes.slice();
|
||||||
|
routes[index].active = e.target.checked ? 1 : 0;
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
routes: routes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user