Frontend, Bugfixes
This commit is contained in:
parent
efe3ada470
commit
8ce74edc38
@ -2,14 +2,38 @@
|
||||
|
||||
namespace Api\User;
|
||||
|
||||
use Api\Parameter\Parameter;
|
||||
use \Api\Request;
|
||||
|
||||
class Fetch extends Request {
|
||||
|
||||
const SELECT_SIZE = 20;
|
||||
|
||||
private int $userCount;
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1)
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->requiredGroup = USER_GROUP_ADMIN;
|
||||
$this->userCount = 0;
|
||||
}
|
||||
|
||||
private function getUserCount() {
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select($sql->count())->from("User")->execute();
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->userCount = $res[0]["count"];
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public function execute($values = array()) {
|
||||
@ -17,11 +41,25 @@ class Fetch extends Request {
|
||||
return false;
|
||||
}
|
||||
|
||||
$page = $this->getParam("page");
|
||||
if($page < 1) {
|
||||
return $this->createError("Invalid page count");
|
||||
}
|
||||
|
||||
if (!$this->getUserCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("User.uid as userId", "User.name", "User.email", "Group.uid as groupId", "Group.name as groupName")
|
||||
$res = $sql->select("User.uid as userId", "User.name", "User.email", "User.created_at",
|
||||
"Group.uid as groupId", "Group.name as groupName")
|
||||
->from("User")
|
||||
->leftJoin("UserGroup", "User.uid", "UserGroup.user_id")
|
||||
->leftJoin("Group", "Group.uid", "UserGroup.group_id")
|
||||
->orderBy("User.uid")
|
||||
->ascending()
|
||||
->limit(Fetch::SELECT_SIZE)
|
||||
->offset(($page - 1) * Fetch::SELECT_SIZE)
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
@ -30,14 +68,15 @@ class Fetch extends Request {
|
||||
if($this->success) {
|
||||
$this->result["users"] = array();
|
||||
foreach($res as $row) {
|
||||
$userId = $row["userId"];
|
||||
$groupId = $row["groupId"];
|
||||
$userId = intval($row["userId"]);
|
||||
$groupId = intval($row["groupId"]);
|
||||
$groupName = $row["groupName"];
|
||||
if (!isset($this->result["users"][$userId])) {
|
||||
$this->result["users"][$userId] = array(
|
||||
"uid" => $userId,
|
||||
"name" => $row["name"],
|
||||
"email" => $row["email"],
|
||||
"created_at" => $row["created_at"],
|
||||
"groups" => array(),
|
||||
);
|
||||
}
|
||||
@ -46,6 +85,7 @@ class Fetch extends Request {
|
||||
$this->result["users"][$userId]["groups"][$groupId] = $groupName;
|
||||
}
|
||||
}
|
||||
$this->result["pages"] = intval(ceil($this->userCount / Fetch::SELECT_SIZE));
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
|
@ -31,6 +31,7 @@ class CreateDatabase {
|
||||
->addString("salt", 16)
|
||||
->addString("password", 64)
|
||||
->addInt("language_id", true, 1)
|
||||
->addDateTime("registered_at", false, $sql->currentTimestamp())
|
||||
->primaryKey("uid")
|
||||
->unique("email")
|
||||
->unique("name")
|
||||
|
@ -5,12 +5,12 @@ namespace Documents {
|
||||
use Documents\Admin\AdminHead;
|
||||
use Elements\Document;
|
||||
use Objects\User;
|
||||
use Views\AdminDashboard;
|
||||
use Views\Admin\AdminDashboardBody;
|
||||
use Views\LoginBody;
|
||||
|
||||
class Admin extends Document {
|
||||
public function __construct(User $user) {
|
||||
$body = $user->isLoggedIn() ? AdminDashboard::class : LoginBody::class;
|
||||
$body = $user->isLoggedIn() ? AdminDashboardBody::class : LoginBody::class;
|
||||
parent::__construct($user, AdminHead::class, $body);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ namespace Documents\Document404 {
|
||||
|
||||
use Elements\Body;
|
||||
use Elements\Head;
|
||||
use Views\View404;
|
||||
|
||||
class Head404 extends Head {
|
||||
|
||||
@ -25,11 +26,6 @@ namespace Documents\Document404 {
|
||||
}
|
||||
|
||||
protected function initSources() {
|
||||
// $this->loadJQuery();
|
||||
// $this->loadBootstrap();
|
||||
// $this->loadFontawesome();
|
||||
// $this->addJS(\Elements\Script::CORE);
|
||||
// $this->addCSS(\Elements\Link::CORE);
|
||||
}
|
||||
|
||||
protected function initMetas() {
|
||||
@ -59,7 +55,7 @@ namespace Documents\Document404 {
|
||||
|
||||
public function getCode() {
|
||||
$html = parent::getCode();
|
||||
$html .= "<b>404 Not Found</b>";
|
||||
$html .= "<body>" . (new View404($this->getDocument())) . "</body>";
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ namespace Documents {
|
||||
namespace Documents\Install {
|
||||
|
||||
use Api\Notifications\Create;
|
||||
use Api\Parameter\Parameter;
|
||||
use Configuration\CreateDatabase;
|
||||
use Driver\SQL\SQL;
|
||||
use Elements\Body;
|
||||
@ -289,8 +290,8 @@ namespace Documents\Install {
|
||||
$username = $this->getParameter("username");
|
||||
$password = $this->getParameter("password");
|
||||
$confirmPassword = $this->getParameter("confirmPassword");
|
||||
$email = $this->getParameter("email") ?? "";
|
||||
|
||||
$msg = $this->errorString;
|
||||
$success = true;
|
||||
$missingInputs = array();
|
||||
|
||||
@ -321,13 +322,16 @@ namespace Documents\Install {
|
||||
} else if(strlen($password) < 6) {
|
||||
$msg = "The password should be at least 6 characters long";
|
||||
$success = false;
|
||||
} else if($email && Parameter::parseType($email) !== Parameter::TYPE_EMAIL) {
|
||||
$msg = "Invalid email address";
|
||||
$success = false;
|
||||
} else {
|
||||
$salt = generateRandomString(16);
|
||||
$hash = hash('sha256', $password . $salt);
|
||||
$sql = $user->getSQL();
|
||||
|
||||
$success = $sql->insert("User", array("name", "salt", "password"))
|
||||
->addRow($username, $salt, $hash)
|
||||
$success = $sql->insert("User", array("name", "salt", "password", "email"))
|
||||
->addRow($username, $salt, $hash, $email)
|
||||
->returning("uid")
|
||||
->execute()
|
||||
&& $sql->insert("UserGroup", array("group_id", "user_id"))
|
||||
@ -606,6 +610,7 @@ namespace Documents\Install {
|
||||
"title" => "Create a User",
|
||||
"form" => array(
|
||||
array("title" => "Username", "name" => "username", "type" => "text", "required" => true),
|
||||
array("title" => "Email", "name" => "email", "type" => "text"),
|
||||
array("title" => "Password", "name" => "password", "type" => "password", "required" => true),
|
||||
array("title" => "Confirm Password", "name" => "confirmPassword", "type" => "password", "required" => true),
|
||||
),
|
||||
@ -704,7 +709,7 @@ namespace Documents\Install {
|
||||
$id = $button["id"];
|
||||
$float = $button["float"];
|
||||
$disabled = (isset($button["disabled"]) && $button["disabled"]) ? " disabled" : "";
|
||||
$button = "<button type=\"button\" id=\"$id\" class=\"btn btn-$type margin-xs\"$disabled>$title</button>";
|
||||
$button = "<button type=\"button\" id=\"$id\" class=\"btn btn-$type m-1\"$disabled>$title</button>";
|
||||
|
||||
if($float === "left") {
|
||||
$buttonsLeft .= $button;
|
||||
|
@ -7,7 +7,7 @@ class CondOr extends Condition {
|
||||
private array $conditions;
|
||||
|
||||
public function __construct(...$conditions) {
|
||||
$this->conditions = $conditions;
|
||||
$this->conditions = (!empty($conditions) && is_array($conditions[0])) ? $conditions[0] : $conditions;
|
||||
}
|
||||
|
||||
public function getConditions() { return $this->conditions; }
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Driver\SQL\Query;
|
||||
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
|
||||
class Delete extends Query {
|
||||
|
||||
private string $table;
|
||||
@ -14,7 +16,7 @@ class Delete extends Query {
|
||||
}
|
||||
|
||||
public function where(...$conditions) {
|
||||
$this->conditions = array_merge($this->conditions, $conditions);
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,16 @@ use Driver\SQL\SQL;
|
||||
abstract class Query {
|
||||
|
||||
protected SQL $sql;
|
||||
public bool $dump;
|
||||
|
||||
public function __construct($sql) {
|
||||
$this->sql = $sql;
|
||||
$this->dump = false;
|
||||
}
|
||||
|
||||
public function dump() {
|
||||
$this->dump = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public abstract function execute();
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Driver\SQL\Query;
|
||||
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
use Driver\SQL\Join;
|
||||
|
||||
class Select extends Query {
|
||||
@ -33,7 +34,7 @@ class Select extends Query {
|
||||
}
|
||||
|
||||
public function where(...$conditions) {
|
||||
$this->conditions = array_merge($this->conditions, $conditions);
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Driver\SQL\Query;
|
||||
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
|
||||
class Update extends Query {
|
||||
|
||||
private array $values;
|
||||
@ -16,7 +18,7 @@ class Update extends Query {
|
||||
}
|
||||
|
||||
public function where(...$conditions) {
|
||||
$this->conditions = array_merge($this->conditions, $conditions);
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -144,6 +144,7 @@ abstract class SQL {
|
||||
$returning = $this->getReturning($returningCol);
|
||||
|
||||
$query = "INSERT INTO $tableName$columnStr VALUES$values$onDuplicateKey$returning";
|
||||
if($insert->dump) { var_dump($query); var_dump($parameters); }
|
||||
$res = $this->execute($query, $parameters, !empty($returning));
|
||||
$success = ($res !== FALSE);
|
||||
|
||||
@ -189,6 +190,7 @@ abstract class SQL {
|
||||
$limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : "");
|
||||
$offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : "");
|
||||
$query = "SELECT $columns FROM $tables$joinStr$where$orderBy$limit$offset";
|
||||
if($select->dump) { var_dump($query); var_dump($params); }
|
||||
return $this->execute($query, $params, true);
|
||||
}
|
||||
|
||||
@ -198,6 +200,7 @@ abstract class SQL {
|
||||
$where = $this->getWhereClause($delete->getConditions(), $params);
|
||||
|
||||
$query = "DELETE FROM $table$where";
|
||||
if($delete->dump) { var_dump($query); }
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
@ -218,6 +221,7 @@ abstract class SQL {
|
||||
|
||||
$where = $this->getWhereClause($update->getConditions(), $params);
|
||||
$query = "UPDATE $table SET $valueStr$where";
|
||||
if($update->dump) { var_dump($query); var_dump($params); }
|
||||
return $this->execute($query, $params);
|
||||
}
|
||||
|
||||
@ -290,6 +294,7 @@ abstract class SQL {
|
||||
protected abstract function execute($query, $values=NULL, $returnValues=false);
|
||||
|
||||
protected function buildCondition($condition, &$params) {
|
||||
|
||||
if ($condition instanceof CondOr) {
|
||||
$conditions = array();
|
||||
foreach($condition->getConditions() as $cond) {
|
||||
@ -304,7 +309,7 @@ abstract class SQL {
|
||||
} else if ($condition instanceof CondBool) {
|
||||
return $this->columnName($condition->getValue());
|
||||
} else if (is_array($condition)) {
|
||||
if (count($condition) == 1) {
|
||||
if (count($condition) === 1) {
|
||||
return $this->buildCondition($condition[0], $params);
|
||||
} else {
|
||||
$conditions = array();
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use View;
|
||||
|
||||
abstract class Body extends View {
|
||||
public function __construct($document) {
|
||||
parent::__construct($document);
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use View;
|
||||
|
||||
abstract class Head extends View {
|
||||
|
||||
protected array $sources;
|
||||
@ -47,7 +45,7 @@ abstract class Head extends View {
|
||||
public function addKeywords($keywords) { array_merge($this->keywords, $keywords); }
|
||||
public function getTitle() { return $this->title; }
|
||||
|
||||
public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link("stylesheet", $href, $type); }
|
||||
public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link(Link::STYLESHEET, $href, $type); }
|
||||
public function addStyle($style) { $this->sources[] = new Style($style); }
|
||||
public function addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); }
|
||||
public function addJSCode($code) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, "", $code); }
|
||||
|
@ -2,28 +2,15 @@
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use View;
|
||||
|
||||
class Link extends View {
|
||||
class Link extends StaticView {
|
||||
|
||||
const STYLESHEET = "stylesheet";
|
||||
const MIME_TEXT_CSS = "text/css";
|
||||
|
||||
const FONTAWESOME = '/css/fontawesome.min.css';
|
||||
// const JQUERY_UI = '/css/jquery-ui.css';
|
||||
// const JQUERY_TERMINAL = '/css/jquery.terminal.min.css';
|
||||
const BOOTSTRAP = '/css/bootstrap.min.css';
|
||||
// const BOOTSTRAP_THEME = '/css/bootstrap-theme.min.css';
|
||||
// const BOOTSTRAP_DATEPICKER_CSS = '/css/bootstrap-datepicker.standalone.min.css';
|
||||
// const BOOTSTRAP_DATEPICKER3_CSS = '/css/bootstrap-datepicker.standalone.min.css';
|
||||
// const HIGHLIGHT = '/css/highlight.css';
|
||||
// const HIGHLIGHT_THEME = '/css/theme.css';
|
||||
const FONTAWESOME = "/css/fontawesome.min.css";
|
||||
const BOOTSTRAP = "/css/bootstrap.min.css";
|
||||
const CORE = "/css/style.css";
|
||||
const ADMIN = "/css/admin.css";
|
||||
// const HOME = "/css/home.css";
|
||||
// const REVEALJS = "/css/reveal.css";
|
||||
// const REVEALJS_THEME_MOON = "/css/reveal_moon.css";
|
||||
// const REVEALJS_THEME_BLACK = "/css/reveal_black.css";
|
||||
const ADMINLTE = "/css/adminlte.min.css";
|
||||
|
||||
private string $type;
|
||||
|
@ -2,36 +2,15 @@
|
||||
|
||||
namespace Elements;
|
||||
|
||||
class Script extends \View {
|
||||
class Script extends StaticView {
|
||||
|
||||
const MIME_TEXT_JAVASCRIPT = "text/javascript";
|
||||
|
||||
const CORE = "/js/script.js";
|
||||
// const HOME = "/js/home.js";
|
||||
const ADMIN = "/js/admin.js";
|
||||
// const SORTTABLE = "/js/sorttable.js";
|
||||
const JQUERY = "/js/jquery.min.js";
|
||||
// const JQUERY_UI = "/js/jquery-ui.js";
|
||||
// const JQUERY_MASKED_INPUT = "/js/jquery.maskedinput.min.js";
|
||||
// const JQUERY_CONTEXT_MENU = "/js/jquery.contextmenu.min.js";
|
||||
// const JQUERY_TERMINAL = "/js/jquery.terminal.min.js";
|
||||
// const JQUERY_TERMINAL_UNIX = "/js/unix_formatting.js";
|
||||
// const JSCOLOR = "/js/jscolor.min.js";
|
||||
// const SYNTAX_HIGHLIGHTER = "/js/syntaxhighlighter.js";
|
||||
// const HIGHLIGHT = "/js/highlight.pack.js";
|
||||
// const GOOGLE_CHARTS = "/js/loader.js";
|
||||
// const BOOTSTRAP = "/js/bootstrap.min.js";
|
||||
// const BOOTSTRAP_DATEPICKER_JS = "/js/bootstrap-datepicker.min.js";
|
||||
// const POPPER = "/js/popper.min.js";
|
||||
// const JSMPEG = "/js/jsmpeg.min.js";
|
||||
// const MOMENT = "/js/moment.min.js";
|
||||
// const CHART = "/js/chart.js";
|
||||
// const REVEALJS = "/js/reveal.js";
|
||||
// const REVEALJS_PLUGIN_NOTES = "/js/reveal_notes.js";
|
||||
const INSTALL = "/js/install.js";
|
||||
const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
|
||||
|
||||
const HIGHLIGHT_JS_LOADER = "\$(document).ready(function(){\$('code').each(function(i, block) { hljs.highlightBlock(block); }); })";
|
||||
const ADMINLTE = "/js/adminlte.min.js";
|
||||
|
||||
private string $type;
|
||||
|
9
core/Elements/StaticView.class.php
Normal file
9
core/Elements/StaticView.class.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Elements;
|
||||
|
||||
abstract class StaticView {
|
||||
|
||||
public abstract function getCode();
|
||||
|
||||
}
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use View;
|
||||
|
||||
class Style extends View {
|
||||
class Style extends StaticView {
|
||||
|
||||
private string $style;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Elements\Document;
|
||||
namespace Elements;
|
||||
|
||||
abstract class View {
|
||||
abstract class View extends StaticView {
|
||||
|
||||
private Document $document;
|
||||
private bool $loadView;
|
||||
@ -112,4 +112,9 @@ abstract class View {
|
||||
$hidden = ($hidden?" hidden" : "");
|
||||
return "<div class=\"alert alert-$type$hidden\" role=\"alert\"$id>$text</div>";
|
||||
}
|
||||
|
||||
protected function createBadge($type, $text) {
|
||||
$text = htmlspecialchars($text);
|
||||
return "<span class=\"badge badge-$type\">$text</span>";
|
||||
}
|
||||
}
|
@ -9,7 +9,8 @@ use External\JWT;
|
||||
|
||||
class Session extends ApiObject {
|
||||
|
||||
const DURATION = 120;
|
||||
# in minutes
|
||||
const DURATION = 60*24;
|
||||
|
||||
private ?int $sessionId;
|
||||
private User $user;
|
||||
@ -91,13 +92,13 @@ class Session extends ApiObject {
|
||||
$this->updateMetaData();
|
||||
$sql = $this->user->getSQL();
|
||||
|
||||
$hours = Session::DURATION;
|
||||
$minutes = Session::DURATION;
|
||||
$columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in");
|
||||
|
||||
$success = $sql
|
||||
->insert("Session", $columns)
|
||||
->addRow(
|
||||
(new DateTime())->modify("+$hours hour"),
|
||||
(new DateTime())->modify("+$minutes minute"),
|
||||
$this->user->getId(),
|
||||
$this->ipAddress,
|
||||
$this->os,
|
||||
@ -125,11 +126,11 @@ class Session extends ApiObject {
|
||||
|
||||
public function update() {
|
||||
$this->updateMetaData();
|
||||
$hours = Session::DURATION;
|
||||
$minutes = Session::DURATION;
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
return $sql->update("Session")
|
||||
->set("Session.expires", (new DateTime())->modify("+$hours hour"))
|
||||
->set("Session.expires", (new DateTime())->modify("+$minutes minute"))
|
||||
->set("Session.ipAddress", $this->ipAddress)
|
||||
->set("Session.os", $this->os)
|
||||
->set("Session.browser", $this->browser)
|
||||
|
@ -1,19 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Views;
|
||||
namespace Views\Admin;
|
||||
|
||||
// Source: https://adminlte.io/themes/v3/
|
||||
|
||||
use Documents\Document404\Body404;
|
||||
use Elements\Body;
|
||||
use Elements\Script;
|
||||
use Elements\View;
|
||||
use Views\View404;
|
||||
|
||||
class AdminDashboard extends Body {
|
||||
class AdminDashboardBody extends Body {
|
||||
|
||||
private array $errorMessages;
|
||||
private array $notifications;
|
||||
|
||||
public function __construct($document) {
|
||||
parent::__construct($document);
|
||||
$this->errorMessages = array();
|
||||
$this->notifications = array();
|
||||
}
|
||||
|
||||
private function getNotifications() : array {
|
||||
@ -49,8 +54,7 @@ class AdminDashboard extends Body {
|
||||
$iconMail = $this->createIcon("envelope", "fas");
|
||||
|
||||
// Notifications
|
||||
$notifications = $this->getNotifications();
|
||||
$numNotifications = count($notifications);
|
||||
$numNotifications = count($this->notifications);
|
||||
if ($numNotifications === 0) {
|
||||
$notificationText = L("No new notifications");
|
||||
} else if($numNotifications === 1) {
|
||||
@ -98,7 +102,7 @@ class AdminDashboard extends Body {
|
||||
|
||||
// Notifications
|
||||
$i = 0;
|
||||
foreach($notifications as $notification) {
|
||||
foreach($this->notifications as $notification) {
|
||||
|
||||
$title = $notification["title"];
|
||||
$notificationId = $notification["uid"];
|
||||
@ -150,12 +154,17 @@ class AdminDashboard extends Body {
|
||||
),
|
||||
);
|
||||
|
||||
$notificationCount = count($this->notifications);
|
||||
if ($notificationCount > 0) {
|
||||
$menuEntries["dashboard"]["badge"] = array("type" => "warning", "value" => $notificationCount);
|
||||
}
|
||||
|
||||
$currentView = $_GET["view"] ?? "dashboard";
|
||||
|
||||
$html =
|
||||
"<aside class=\"main-sidebar sidebar-dark-primary elevation-4\">
|
||||
<!-- Brand Logo -->
|
||||
<a href=\"index3.html\" class=\"brand-link\">
|
||||
<a href=\"/admin\" class=\"brand-link\">
|
||||
<img src=\"/img/web_base_logo.png\" alt=\"WebBase Logo\" class=\"brand-image img-circle elevation-3\"
|
||||
style=\"opacity: .8\">
|
||||
<span class=\"brand-text font-weight-light\">WebBase</span>
|
||||
@ -172,11 +181,17 @@ class AdminDashboard extends Body {
|
||||
$name = L($menuEntry["name"]);
|
||||
$icon = $this->createIcon($menuEntry["icon"], "fas", "nav-icon");
|
||||
$active = ($currentView === $view) ? " active" : "";
|
||||
$badge = $menuEntry["badge"] ?? "";
|
||||
if($badge) {
|
||||
$badgeType = $badge["type"];
|
||||
$badgeValue = $badge["value"];
|
||||
$badge = "<span class=\"badge badge-$badgeType right\">$badgeValue</span>";
|
||||
}
|
||||
|
||||
$html .=
|
||||
"<li class=\"nav-item\">
|
||||
<a href=\"?view=$view\" class=\"nav-link$active\">
|
||||
$icon
|
||||
<p>$name </p>
|
||||
$icon<p>$name$badge</p>
|
||||
</a>
|
||||
</li>";
|
||||
}
|
||||
@ -190,32 +205,64 @@ class AdminDashboard extends Body {
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getView() {
|
||||
|
||||
$views = array(
|
||||
"dashboard" => Dashboard::class,
|
||||
"users" => UserOverview::class,
|
||||
"404" => View404::class,
|
||||
);
|
||||
|
||||
$currentView = $_GET["view"] ?? "dashboard";
|
||||
if (!isset($views[$currentView])) {
|
||||
$currentView = "404";
|
||||
}
|
||||
|
||||
$view = new $views[$currentView]($this->getDocument());
|
||||
assert($view instanceof View);
|
||||
$code = $view->getCode();
|
||||
|
||||
if ($view instanceof AdminView) {
|
||||
$this->errorMessages = array_merge($this->errorMessages, $view->getErrorMessages());
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function loadView() {
|
||||
parent::loadView();
|
||||
|
||||
$head = $this->getDocument()->getHead();
|
||||
$head->addJS(Script::BOOTSTRAP);
|
||||
$head->loadAdminlte();
|
||||
|
||||
$this->notifications = $this->getNotifications();
|
||||
}
|
||||
|
||||
private function getContent() {
|
||||
|
||||
$this->getUsers();
|
||||
|
||||
$view = $this->getView();
|
||||
$html = "<div class=\"content-wrapper p-2\">";
|
||||
|
||||
foreach($this->errorMessages as $errorMessage) {
|
||||
$html .= $this->createErrorText($errorMessage);
|
||||
}
|
||||
|
||||
$html .= $view;
|
||||
$html .= "</div>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
|
||||
$head = $this->getDocument()->getHead();
|
||||
$head->addJS(Script::BOOTSTRAP);
|
||||
$head->loadAdminlte();
|
||||
$html = parent::getCode();
|
||||
|
||||
$header = $this->getHeader();
|
||||
$sidebar = $this->getSidebar();
|
||||
$content = $this->getContent();
|
||||
|
||||
$html =
|
||||
$html .=
|
||||
"<!-- LICENSE: /docs/LICENSE_ADMINLTE -->
|
||||
<body class=\"hold-transition sidebar-mini layout-fixed\">
|
||||
<div class=\"wrapper\">
|
46
core/Views/Admin/AdminView.class.php
Normal file
46
core/Views/Admin/AdminView.class.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Views\Admin;
|
||||
|
||||
use Elements\Document;
|
||||
use Elements\View;
|
||||
|
||||
class AdminView extends View {
|
||||
|
||||
protected array $errorMessages;
|
||||
|
||||
public function __construct(Document $document) {
|
||||
parent::__construct($document);
|
||||
$this->errorMessages = array();
|
||||
}
|
||||
|
||||
public function getErrorMessages() {
|
||||
return $this->errorMessages;
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
$html = parent::getCode();
|
||||
|
||||
$home = L("Home");
|
||||
|
||||
$html .=
|
||||
"<div class=\"content-header\">
|
||||
<div class=\"container-fluid\">
|
||||
<div class=\"row mb-2\">
|
||||
<div class=\"col-sm-6\">
|
||||
<h1 class=\"m-0 text-dark\">$this->title</h1>
|
||||
</div>
|
||||
<div class=\"col-sm-6\">
|
||||
<ol class=\"breadcrumb float-sm-right\">
|
||||
<li class=\"breadcrumb-item\"><a href=\"/\">$home</a></li>
|
||||
<li class=\"breadcrumb-item active\">$this->title</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
</div>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
25
core/Views/Admin/Dashboard.class.php
Normal file
25
core/Views/Admin/Dashboard.class.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Views\Admin;
|
||||
|
||||
use Elements\Document;
|
||||
|
||||
class Dashboard extends AdminView {
|
||||
|
||||
public function __construct(Document $document) {
|
||||
parent::__construct($document);
|
||||
}
|
||||
|
||||
public function loadView() {
|
||||
parent::loadView();
|
||||
$this->title = L("Dashboard");
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
$html = parent::getCode();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
164
core/Views/Admin/UserOverview.class.php
Normal file
164
core/Views/Admin/UserOverview.class.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Views\Admin;
|
||||
|
||||
use DateTime;
|
||||
use Elements\Document;
|
||||
|
||||
class UserOverview extends AdminView {
|
||||
|
||||
private array $users;
|
||||
private int $page;
|
||||
private int $pageCount;
|
||||
|
||||
public function __construct(Document $document) {
|
||||
parent::__construct($document);
|
||||
$this->users = array();
|
||||
$this->pageCount = 0;
|
||||
$this->page = 1;
|
||||
}
|
||||
|
||||
public function loadView() {
|
||||
parent::loadView();
|
||||
$this->title = L("User Control");
|
||||
$this->requestUsers();
|
||||
}
|
||||
|
||||
private function requestUsers() {
|
||||
|
||||
if(isset($_GET["page"]) && is_numeric($_GET["page"])) {
|
||||
$this->page = intval($_GET["page"]);
|
||||
} else {
|
||||
$this->page = 1;
|
||||
}
|
||||
|
||||
$req = new \Api\User\Fetch($this->getDocument()->getUser());
|
||||
if (!$req->execute(array("page" => $this->page))) {
|
||||
$this->errorMessages[] = $req->getLastError();
|
||||
} else {
|
||||
$result = $req->getResult();
|
||||
$this->users = $result["users"];
|
||||
$this->pageCount = $result["pages"];
|
||||
}
|
||||
}
|
||||
|
||||
private function getGroups($groups) {
|
||||
$badges = [];
|
||||
|
||||
foreach($groups as $groupId => $group) {
|
||||
$badgeClass = "secondary";
|
||||
if ($groupId === USER_GROUP_ADMIN) {
|
||||
$badgeClass = "danger";
|
||||
}
|
||||
|
||||
$badges[] = $this->createBadge($badgeClass, $group);
|
||||
}
|
||||
|
||||
return implode(" ", $badges);
|
||||
}
|
||||
|
||||
private function getPagination() {
|
||||
|
||||
$userPageNavigation = L("User page navigation");
|
||||
$previousDisabled = ($this->page == 1 ? " disabled" : "");
|
||||
$nextDisabled = ($this->page >= $this->pageCount ? " disabled" : "");
|
||||
|
||||
$html =
|
||||
"<nav aria-label=\"$userPageNavigation\" id=\"userPageNavigation\">
|
||||
<ul class=\"pagination p-2 m-0 justify-content-end\">
|
||||
<li class=\"page-item$previousDisabled\"><a class=\"page-link\" href=\"#\">Previous</a></li>";
|
||||
|
||||
for($i = 1; $i <= $this->pageCount; $i++) {
|
||||
$active = $i === $this->page ? " active" : "";
|
||||
$html .=
|
||||
"<li class=\"page-item$active\"><a class=\"page-link\" href=\"#\">$i</a></li>";
|
||||
}
|
||||
|
||||
$html .=
|
||||
"<li class=\"page-item$nextDisabled\"><a class=\"page-link\" href=\"#\">Next</a></li>
|
||||
</ul>
|
||||
</nav>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getUserRows() {
|
||||
|
||||
$dateFormat = L("Y/m/d");
|
||||
$userRows = array();
|
||||
|
||||
foreach($this->users as $uid => $user) {
|
||||
$name = $user["name"];
|
||||
$email = $user["email"] ?? "";
|
||||
$registeredAt = (new DateTime($user["created_at"]))->format($dateFormat);
|
||||
$groups = $this->getGroups($user["groups"]);
|
||||
|
||||
$userRows[] =
|
||||
"<tr data-id=\"$uid\">
|
||||
<td>$name</td>
|
||||
<td>$email</td>
|
||||
<td>$groups</td>
|
||||
<td>$registeredAt</td>
|
||||
</tr>";
|
||||
}
|
||||
|
||||
return implode("", $userRows);
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
$html = parent::getCode();
|
||||
|
||||
// Icons
|
||||
$iconRefresh = $this->createIcon("sync");
|
||||
|
||||
// Locale
|
||||
$users = L("Users");
|
||||
$name = L("Name");
|
||||
$email = L("Email");
|
||||
$groups = L("Groups");
|
||||
$registeredAt = L("Registered At");
|
||||
|
||||
// Content
|
||||
$pagination = $this->getPagination();
|
||||
$userRows = $this->getUserRows();
|
||||
|
||||
$html .=
|
||||
"<div class=\"content\">
|
||||
<div class=\"container-fluid\">
|
||||
<div class=\"row\">
|
||||
<div class=\"col-lg-12\">
|
||||
<div class=\"card\">
|
||||
<div class=\"card-header border-0\">
|
||||
<h3 class=\"card-title\">$users</h3>
|
||||
<div class=\"card-tools\">
|
||||
<a href=\"#\" class=\"btn btn-tool btn-sm\" id=\"userTableRefresh\">
|
||||
$iconRefresh
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\"card-body table-responsive p-0\">
|
||||
<table class=\"table table-striped table-valign-middle\" id=\"userTable\">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>$name</th>
|
||||
<th>$email</th>
|
||||
<th>$groups</th>
|
||||
<th>$registeredAt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
$userRows
|
||||
</tbody>
|
||||
</table>
|
||||
$pagination
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
@ -3,20 +3,26 @@
|
||||
namespace Views;
|
||||
|
||||
use Api\GetLanguages;
|
||||
use Elements\View;
|
||||
|
||||
class LanguageFlags extends \View {
|
||||
class LanguageFlags extends View {
|
||||
|
||||
private array $languageFlags;
|
||||
|
||||
public function __construct($document) {
|
||||
parent::__construct($document);
|
||||
$this->languageFlags = array();
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
public function loadView() {
|
||||
parent::loadView();
|
||||
|
||||
$request = new GetLanguages($this->getDocument()->getUser());
|
||||
if($request->execute()) {
|
||||
|
||||
$requestUri = $_SERVER["REQUEST_URI"];
|
||||
$queryString = $_SERVER['QUERY_STRING'];
|
||||
|
||||
$flags = array();
|
||||
$request = new GetLanguages($this->getDocument()->getUser());
|
||||
$params = explode("&", $queryString);
|
||||
$query = array();
|
||||
foreach($params as $param) {
|
||||
@ -33,22 +39,22 @@ class LanguageFlags extends \View {
|
||||
}
|
||||
|
||||
$url = parse_url($requestUri, PHP_URL_PATH) . "?";
|
||||
if($request->execute()) {
|
||||
|
||||
foreach($request->getResult()['languages'] as $lang) {
|
||||
$langCode = $lang['code'];
|
||||
$langName = $lang['name'];
|
||||
$query['lang'] = $langCode;
|
||||
$queryString = http_build_query($query);
|
||||
|
||||
$flags[] = $this->createLink(
|
||||
$this->languageFlags[] = $this->createLink(
|
||||
"$url$queryString",
|
||||
"<img class=\"p-1\" src=\"/img/icons/lang/$langCode.gif\" alt=\"$langName\" title=\"$langName\">"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$flags[] = $this->createErrorText($request->getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
return implode('', $flags);
|
||||
public function getCode() {
|
||||
return implode('', $this->languageFlags);
|
||||
}
|
||||
}
|
@ -10,9 +10,13 @@ class LoginBody extends Body {
|
||||
parent::__construct($document);
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
|
||||
public function loadView() {
|
||||
parent::loadView();
|
||||
$this->getDocument()->getHead()->loadBootstrap();
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
$html = parent::getCode();
|
||||
|
||||
$username = L("Username");
|
||||
$password = L("Password");
|
||||
@ -25,7 +29,7 @@ class LoginBody extends Body {
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
$protocol = getProtocol();
|
||||
|
||||
$html = "<body>";
|
||||
$html .= "<body>";
|
||||
|
||||
$accountCreated = "";
|
||||
if(isset($_GET["accountCreated"])) {
|
||||
|
13
core/Views/View404.class.php
Normal file
13
core/Views/View404.class.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Views;
|
||||
|
||||
use Elements\View;
|
||||
|
||||
class View404 extends View {
|
||||
|
||||
public function getCode() {
|
||||
return parent::getCode() . "<b>Not found</b>";
|
||||
}
|
||||
|
||||
};
|
26
js/admin.js
26
js/admin.js
@ -1,4 +1,6 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
// Login
|
||||
$("#username").keypress(function(e) { if(e.which == 13) $("#password").focus(); });
|
||||
$("#password").keypress(function(e) { if(e.which == 13) $("#btnLogin").click(); });
|
||||
$("#btnLogin").click(function() {
|
||||
@ -24,8 +26,26 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("#toggleSidebar").click(function() {
|
||||
$(".main-wrapper").toggleClass("sidebar-collapsed");
|
||||
$(".main-sidebar").toggleClass("collapsed");
|
||||
$("#userTableRefresh").click(function() {
|
||||
let tbody = $("#userTable > tbody");
|
||||
let page = parseInt($("#userPageNavigation li.active > a").text().trim());
|
||||
tbody.find("tr").remove();
|
||||
tbody.append("<tr><td colspan=\"4\" class=\"text-center\">Loading… " + createLoadingIcon() + "</td></tr>");
|
||||
|
||||
jsCore.apiCall("/user/fetch", { page: page}, function (data) {
|
||||
let pageCount = data["pages"];
|
||||
let users = data["users"];
|
||||
let userRows = [];
|
||||
|
||||
// TODO: .. maybe use ts instead of plain js?
|
||||
for(let userId in users) {
|
||||
let user = users[userId];
|
||||
userRows.push("<tr><td>" + user.name + "</td><td>" + user.email + "</td><td></td><td></td></tr>");
|
||||
}
|
||||
|
||||
tbody.html(userRows.join(""));
|
||||
}, function (err) {
|
||||
alert(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -274,3 +274,7 @@ let jsCore = new Core();
|
||||
$(document).ready(function() {
|
||||
|
||||
});
|
||||
|
||||
function createLoadingIcon() {
|
||||
return '<i class="fas fa-spin fa-spinner"></i>';
|
||||
}
|
@ -10,7 +10,7 @@ class ApiTestCase(PhpTest):
|
||||
# ApiKeys
|
||||
"Testing get api keys empty…": self.test_get_api_keys_empty,
|
||||
"Testing create api key…": self.test_create_api_key,
|
||||
"Testing referesh api key…": self.test_refresh_api_key,
|
||||
"Testing refresh api key…": self.test_refresh_api_key,
|
||||
"Testing revoke api key…": self.test_revoke_api_key,
|
||||
|
||||
# Notifications
|
||||
|
@ -11,6 +11,7 @@ class InstallTestCase(PhpTest):
|
||||
"Testing invalid usernames…": self.test_invalid_usernames,
|
||||
"Testing invalid password…": self.test_invalid_password,
|
||||
"Testing not matching password…": self.test_not_matching_passwords,
|
||||
"Testing invalid email…": self.test_invalid_email,
|
||||
"Testing user creation…": self.test_create_user,
|
||||
"Testing skip mail configuration…": self.test_skil_mail_config,
|
||||
"Testing complete setup…": self.test_complete_setup,
|
||||
@ -40,8 +41,13 @@ class InstallTestCase(PhpTest):
|
||||
self.assertEquals(False, obj["success"])
|
||||
self.assertEquals("The given passwords do not match", obj["msg"])
|
||||
|
||||
def test_invalid_email(self):
|
||||
obj = self.httpPost(data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD, "confirmPassword": PhpTest.ADMIN_PASSWORD, "email": "123abc" })
|
||||
self.assertEquals(False, obj["success"])
|
||||
self.assertEquals("Invalid email address", obj["msg"])
|
||||
|
||||
def test_create_user(self):
|
||||
obj = self.httpPost(data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD, "confirmPassword": PhpTest.ADMIN_PASSWORD })
|
||||
obj = self.httpPost(data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD, "confirmPassword": PhpTest.ADMIN_PASSWORD, "email": "test@test.com" })
|
||||
self.assertEquals(True, obj["success"], obj["msg"])
|
||||
|
||||
def test_skil_mail_config(self):
|
||||
|
Loading…
Reference in New Issue
Block a user