Frontend stuff

This commit is contained in:
Roman Hergenreder 2020-04-03 22:10:21 +02:00
parent d9a20ae62e
commit efe3ada470
23 changed files with 515 additions and 650 deletions

@ -5,4 +5,4 @@ RedirectMatch 404 /\.git
RewriteEngine On
RewriteRule ^api/(.*)?$ index.php?api=$1&$2 [L,QSA]
RewriteRule ^((?!((js|css|img|fonts|api)($|\/)))(.*)?)$ index.php?site=$1&$2 [L,QSA]
RewriteRule ^((?!((js|css|img|fonts|api|docs)($|/)))(.*)?)$ index.php?site=$1&$2 [L,QSA]

@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="webbase">
<words>
<w>adminlte</w>
<w>navbar</w>
</words>
</dictionary>
</component>

@ -9,7 +9,7 @@ class Fetch extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
// $this->requiredGroup = USER_GROUP_ADMIN;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {

@ -2,25 +2,25 @@
namespace Documents {
use Documents\Admin\AdminBody;
use Documents\Admin\AdminHead;
use Elements\Document;
use Objects\User;
use Views\AdminDashboard;
use Views\LoginBody;
class Admin extends Document {
public function __construct($user) {
parent::__construct($user, AdminHead::class, AdminBody::class);
public function __construct(User $user) {
$body = $user->isLoggedIn() ? AdminDashboard::class : LoginBody::class;
parent::__construct($user, AdminHead::class, $body);
}
}
}
namespace Documents\Admin {
use Elements\Body;
use Elements\Head;
use Elements\Link;
use Elements\Script;
use Views\Admin;
use Views\Login;
class AdminHead extends Head {
@ -30,7 +30,6 @@ namespace Documents\Admin {
protected function initSources() {
$this->loadJQuery();
$this->loadBootstrap();
$this->loadFontawesome();
$this->addJS(Script::CORE);
$this->addCSS(Link::CORE);
@ -56,24 +55,4 @@ namespace Documents\Admin {
return "WebBase - Administration";
}
}
class AdminBody extends Body {
public function __construct($document) {
parent::__construct($document);
}
public function getCode() {
$html = parent::getCode();
$document = $this->getDocument();
if(!$document->getUser()->isLoggedIn()) {
$html .= new Login($document);
} else {
$html .= new Admin($document);
}
return $html;
}
}
}

@ -794,7 +794,7 @@ namespace Documents\Install {
</div>
<div class=\"col-md-8 order-md-1\">
$progressMainview
<div class=\"alert$errorClass margin-top-m\" id=\"status\"$errorStyle>$this->errorString</div>
<div class=\"alert$errorClass mt-4\" id=\"status\"$errorStyle>$this->errorString</div>
</div>
</div>
</div>

@ -161,7 +161,7 @@ abstract class SQL {
$params = array();
if (!$tables) {
return "SELECT $columns";
return $this->execute("SELECT $columns", $params, true);
}
$tables = $this->tableName($tables);

@ -69,6 +69,11 @@ abstract class Head extends View {
$this->addJS(Script::BOOTSTRAP);
}
public function loadAdminlte() {
$this->addCSS(Link::ADMINLTE);
$this->addJS(Script::ADMINLTE);
}
public function getCode() {
$header = "<head>";

@ -24,6 +24,7 @@ class Link extends View {
// 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;
private string $rel;

@ -20,7 +20,7 @@ class Script extends \View {
// 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 = "/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";
@ -29,8 +29,10 @@ class Script extends \View {
// 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;
private string $content;

@ -2,6 +2,8 @@
namespace Elements;
use View;
class Style extends View {
private string $style;

@ -50,31 +50,6 @@ abstract class View {
}
// UI Functions
// TODO: do we need this in our general web-base?
public function createFileIcon($mimeType) {
$mimeType = htmlspecialchars($mimeType);
return "<img src=\"/img/icons/admin/getIcon.php?mimeType=$mimeType\" class=\"file-icon\" alt=\"[$mimeType icon]\">";
}
public function createParagraph($title, $id, $content) {
$id = replaceCssSelector($id);
$iconId = urlencode("$id-icon");
return "
<div class=\"row\">
<div class=\"col-12\">
<i class=\"fas fa-link\" style=\"display:none;position:absolute\" id=\"$iconId\"></i>
<h2 id=\"$id\" data-target=\"$iconId\" class=\"inlineLink\">$title</h2>
<div class=\"margin-bottom-xl\"><hr>$content</div>
</div>
</div>";
}
public function createSimpleParagraph($content, $class="") {
if($class) $class = " class=\"$class\"";
return "<p$class>$content</p>";
}
private function createList($items, $tag) {
if(count($items) === 0)
return "<$tag></$tag>";
@ -90,19 +65,6 @@ abstract class View {
return $this->createList($items, "ul");
}
public function createJumbotron($content, $lastModified=false) {
$lastModified = ($lastModified ? "<span class=\"float-right text-xxs margin-top-xxxl\">Last modified: $lastModified</span>" : "");
return "
<div class=\"row\">
<div class=\"col-12\">
<div class=\"jumbotron\">
$content
$lastModified
</div>
</div>
</div>";
}
protected function createLink($link, $title=null) {
if(is_null($title)) $title=$link;
return "<a href=\"$link\">$title</a>";
@ -113,91 +75,16 @@ abstract class View {
return "<a href=\"$link\" target=\"_blank\" class=\"external\">$title</a>";
}
protected function createCodeBlock($code, $lang="") {
if($lang) $lang = " class=\"$lang\"";
$html = "<pre><code$lang>";
$html .= intendCode($code);
$html .= "</code></pre>";
return $html;
}
protected function createIcon($icon, $margin = NULL) {
$marginStr = (is_null($margin) ? "" : " margin-$margin");
$iconClass = $this->getIconClass($icon);
return "<i class=\"$iconClass$marginStr\"></i>";
}
protected function getIconClass($icon) {
$mappings = array(
"sign-out" => "sign-out-alt",
"bank" => "university",
"line-chart" => "chart-line",
"circle-right" => "arrow-alt-circle-right",
"refresh" => "sync"
);
if(isset($mappings[$icon]))
$icon = $mappings[$icon];
protected function createIcon($icon, $type = "fas", $classes = "") {
$iconClass = "$type fa-$icon";
if($icon === "spinner")
$icon .= " fa-spin";
$iconClass .= " fa-spin";
return "fas fa-$icon";
}
if($classes)
$iconClass .= " $classes";
protected function createBootstrapTable($data) {
$code = "<div class=\"container\">";
foreach($data as $row) {
$code .= "<div class=\"row margin-top-xs margin-bottom-xs\">";
$columnCount = count($row);
if($columnCount > 0) {
$remainingSize = 12;
$columnSize = 12 / $columnCount;
foreach($row as $col) {
$size = ($columnSize <= $remainingSize ? $columnSize : $remainingSize);
$content = $col;
$class = "";
$code .= "<div";
if(is_array($col)) {
foreach($col as $key => $val) {
if(strcmp($key, "content") === 0) {
$content = $val;
} else if(strcmp($key, "class") === 0) {
$class = " " . $col["class"];
} else if(strcmp($key, "cols") === 0 && is_numeric($val)) {
$size = intval($val);
} else {
$code .= " $key=\"$val\"";
}
}
$content = (isset($col["content"]) ? $col["content"] : "");
if(isset($col["class"])) $class = " " . $col["class"];
}
if($size <= 6) $class .= " col-md-" . intval($size * 2);
$code .= " class=\"col-lg-$size$class\">$content</div>";
$remainingSize -= $size;
}
}
$code .= "</div>";
}
$code .= "</div>";
return $code;
}
protected function createBash($command, $output="", $prefix="") {
$command = htmlspecialchars($command);
$output = htmlspecialchars($output);
$output = str_replace("\n", "<br>", $output);
return "<div class=\"bash\">
<span>$prefix$</span>&nbsp;
<span>$command</span><br>
<span>$output</span>
</div>";
return "<i class=\"$iconClass\"></i>";
}
protected function createErrorText($text, $id="", $hidden=false) {

@ -1,47 +0,0 @@
<?php
namespace Views;
// Source: https://adminlte.io/themes/v3/
class Admin extends \View {
public function __construct($document) {
parent::__construct($document);
}
private function getMainHeader() {
$home = L("Home");
$search = L("Search");
$iconMenu = $this->createIcon("bars");
$iconSearch = $this->createIcon("search");
$iconNotifications = $this->createIcon("bell");
$header = "";
return $header;
}
private function getMainContent() {
return "";
}
private function getSideBar() {
return "";
}
public function getCode() {
$html = parent::getCode();
$html .= "<div class=\"main-wrapper\">";
$html .= $this->getMainHeader();
$html .= "<div id=\"content\">";
$html .= $this->getSideBar();
$html .= $this->getMainContent();
$html .= "</div>
</div>";
return $html;
}
}
?>

@ -0,0 +1,230 @@
<?php
namespace Views;
// Source: https://adminlte.io/themes/v3/
use Elements\Body;
use Elements\Script;
class AdminDashboard extends Body {
private array $errorMessages;
public function __construct($document) {
parent::__construct($document);
$this->errorMessages = array();
}
private function getNotifications() : array {
$req = new \Api\Notifications\Fetch($this->getDocument()->getUser());
if(!$req->execute()) {
$this->errorMessages[] = $req->getLastError();
return array();
} else {
return $req->getResult()['notifications'];
}
}
private function getUsers() : array {
$req = new \Api\User\Fetch($this->getDocument()->getUser());
if(!$req->execute()) {
$this->errorMessages[] = $req->getLastError();
return array();
} else {
return $req->getResult()['users'];
}
}
private function getHeader() {
// Locale
$home = L("Home");
$search = L("Search");
// Icons
$iconMenu = $this->createIcon("bars");
$iconNotification = $this->createIcon("bell", "far");
$iconSearch = $this->createIcon("search");
$iconMail = $this->createIcon("envelope", "fas");
// Notifications
$notifications = $this->getNotifications();
$numNotifications = count($notifications);
if ($numNotifications === 0) {
$notificationText = L("No new notifications");
} else if($numNotifications === 1) {
$notificationText = L("1 new notification");
} else {
$notificationText = sprintf(L("%d new notification"), $numNotifications);
}
$html =
"<nav class=\"main-header navbar navbar-expand navbar-white navbar-light\">
<!-- Left navbar links -->
<ul class=\"navbar-nav\">
<li class=\"nav-item\">
<a class=\"nav-link\" data-widget=\"pushmenu\" href=\"#\" role=\"button\">$iconMenu</a>
</li>
<li class=\"nav-item d-none d-sm-inline-block\">
<a href=\"/\" class=\"nav-link\">$home</a>
</li>
</ul>
<!-- SEARCH FORM -->
<form class=\"form-inline ml-3\">
<div class=\"input-group input-group-sm\">
<input class=\"form-control form-control-navbar\" type=\"search\" placeholder=\"$search\" aria-label=\"$search\">
<div class=\"input-group-append\">
<button class=\"btn btn-navbar\" type=\"submit\">
$iconSearch
</button>
</div>
</div>
</form>
<!-- Right navbar links -->
<ul class=\"navbar-nav ml-auto\">
<!-- Notifications Dropdown Menu -->
<li class=\"nav-item dropdown\">
<a class=\"nav-link\" data-toggle=\"dropdown\" href=\"#\">
$iconNotification
<span class=\"badge badge-warning navbar-badge\">$numNotifications</span>
</a>
<div class=\"dropdown-menu dropdown-menu-lg dropdown-menu-right\">
<span class=\"dropdown-item dropdown-header\">$notificationText</span>
<div class=\"dropdown-divider\"></div>";
// Notifications
$i = 0;
foreach($notifications as $notification) {
$title = $notification["title"];
$notificationId = $notification["uid"];
$createdAt = getPeriodString($notification["created_at"]);
if ($i > 0) {
$html .= "<div class=\"dropdown-divider\"></div>";
}
$html .=
"<a href=\"#\" class=\"dropdown-item\" data-id=\"$notificationId\">
$iconMail<span class=\"ml-2\">$title</span>
<span class=\"float-right text-muted text-sm\">$createdAt</span>
</a>";
$i++;
if ($i >= 5) {
break;
}
}
$html .= "<a href=\"#\" class=\"dropdown-item dropdown-footer\">See All Notifications</a>
</div>
</li>
</ul>
</nav>";
return $html;
}
private function getSidebar() {
$menuEntries = array(
"dashboard" => array(
"name" => "Dashboard",
"icon" => "tachometer-alt"
),
"users" => array(
"name" => "Users",
"icon" => "users"
),
"settings" => array(
"name" => "Settings",
"icon" => "tools"
),
"help" => array(
"name" => "Help",
"icon" => "question-circle"
),
);
$currentView = $_GET["view"] ?? "dashboard";
$html =
"<aside class=\"main-sidebar sidebar-dark-primary elevation-4\">
<!-- Brand Logo -->
<a href=\"index3.html\" 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>
</a>
<!-- Sidebar -->
<div class=\"sidebar\">
<!-- Sidebar Menu -->
<nav class=\"mt-2\">
<ul class=\"nav nav-pills nav-sidebar flex-column\" data-widget=\"treeview\" role=\"menu\" data-accordion=\"false\">";
foreach($menuEntries as $view => $menuEntry) {
$name = L($menuEntry["name"]);
$icon = $this->createIcon($menuEntry["icon"], "fas", "nav-icon");
$active = ($currentView === $view) ? " active" : "";
$html .=
"<li class=\"nav-item\">
<a href=\"?view=$view\" class=\"nav-link$active\">
$icon
<p>$name </p>
</a>
</li>";
}
$html .=
"</ul>
</nav>
</div>
</aside>";
return $html;
}
private function getContent() {
$this->getUsers();
$html = "<div class=\"content-wrapper p-2\">";
foreach($this->errorMessages as $errorMessage) {
$html .= $this->createErrorText($errorMessage);
}
$html .= "</div>";
return $html;
}
public function getCode() {
$head = $this->getDocument()->getHead();
$head->addJS(Script::BOOTSTRAP);
$head->loadAdminlte();
$header = $this->getHeader();
$sidebar = $this->getSidebar();
$content = $this->getContent();
$html =
"<!-- LICENSE: /docs/LICENSE_ADMINLTE -->
<body class=\"hold-transition sidebar-mini layout-fixed\">
<div class=\"wrapper\">
$header
$sidebar
$content
</div>
</body>";
return $html;
}
}

@ -2,6 +2,8 @@
namespace Views;
use Api\GetLanguages;
class LanguageFlags extends \View {
public function __construct($document) {
@ -14,7 +16,7 @@ class LanguageFlags extends \View {
$queryString = $_SERVER['QUERY_STRING'];
$flags = array();
$request = new \Api\GetLanguages($this->getDocument()->getUser());
$request = new GetLanguages($this->getDocument()->getUser());
$params = explode("&", $queryString);
$query = array();
foreach($params as $param) {
@ -40,7 +42,7 @@ class LanguageFlags extends \View {
$flags[] = $this->createLink(
"$url$queryString",
"<img src=\"/img/icons/lang/$langCode.gif\" alt=\"$langName\" title=\"$langName\">"
"<img class=\"p-1\" src=\"/img/icons/lang/$langCode.gif\" alt=\"$langName\" title=\"$langName\">"
);
}
} else {

@ -2,53 +2,65 @@
namespace Views;
use View;
use Elements\Body;
class LoginBody extends Body {
class Login extends View {
public function __construct($document) {
parent::__construct($document);
}
public function getCode() {
$html = parent::getCode();
$this->getDocument()->getHead()->loadBootstrap();
$username = L("Username");
$password = L("Password");
$rememberMe = L("Remember me");
$login = L("Login");
$backToStartPage = L("Back to Start Page");
$stayLoggedIn = L("Stay logged in");
$flags = new LanguageFlags($this->getDocument());
$iconBack = $this->createIcon("arrow-circle-left", "right");
$iconBack = $this->createIcon("arrow-circle-left");
$domain = $_SERVER['HTTP_HOST'];
$protocol = getProtocol();
$html = "<body>";
$accountCreated = "";
if(isset($_GET["accountCreated"])) {
$accountCreated .= '
<div class="alert alert-success margin-top-xs" id="accountCreated">
$accountCreated =
'<div class="alert alert-success mt-3" id="accountCreated">
Your account was successfully created, you may now login with your credentials
</div>';
}
$html .= "
<div class=\"container margin-top-xxl\">
<div class=\"container mt-4\">
<div class=\"title text-center\">
<h2>Admin Control Panel</h2>
</div>
<div class=\"loginContainer margin-center\">
<div class=\"loginContainer m-auto\">
<form class=\"loginForm\">
<label for=\"username\">$username</label>
<input type=\"text\" class=\"form-control\" name=\"username\" id=\"username\" placeholder=\"$username\" required autofocus />
<label for=\"password\">$password</label>
<input type=\"password\" class=\"form-control\" name=\"password\" id=\"password\" placeholder=\"$password\" required />
<div class=\"form-check\">
<input type=\"checkbox\" class=\"form-check-input\" id=\"stayLoggedIn\" name=\"stayLoggedIn\">
<label class=\"form-check-label\" for=\"stayLoggedIn\">$stayLoggedIn</label>
</div>
<button class=\"btn btn-lg btn-primary btn-block\" id=\"btnLogin\" type=\"button\">$login</button>
<div class=\"alert alert-danger hidden\" role=\"alert\" id=\"loginError\"></div>
<span class=\"flags position-absolute\">$flags</span>
</form>
<span class=\"subtitle flags-container\"><span class=\"flags\">$flags</span></span>
<span class=\"subtitle\"><a class=\"link\" href=\"$protocol://$domain\">$iconBack&nbsp;$backToStartPage</a></span>
<div class=\"p-1\">
<a href=\"$protocol://$domain\">$iconBack&nbsp;$backToStartPage</a>
</div>
$accountCreated
</div>
</div>";
</div>
</body>";
return $html;
}

@ -10,6 +10,7 @@
border-radius: 5px;
background-color: #bbb;
color: black;
position: relative;
}
.loginForm input {
@ -27,8 +28,38 @@
vertical-align: bottom;
}
.flags {
background-color: #999;
padding: 6px 3px 3px 3px;
border-radius: 4px;
bottom: -27px;
right: 5px;
z-index: -99;
}
.main-header {
transition: all 0.3s;
margin-left: 75px;
border-bottom: 1px solid #dee2e6;
padding: 0.7rem;
}
.navbar-badge {
font-size: .6rem;
font-weight: 300;
padding: 2px 4px;
position: absolute;
right: 3px;
top: 7px;
}
.navbar-white {
background-color: #fff;
}
.main-wrapper:not(.sidebar-collapsed) .main-header {
transition: all 0.3s;
margin-left: 250px;
}
.main-sidebar {
@ -43,11 +74,40 @@
transition: all 0.3s;
}
.content-wrapper {
background: #f4f6f9;
.main-sidebar.collapsed {
margin-left: 0;
width: 4.6rem;
}
.hide-collapsed {
transition: all 0.2s linear;
opacity: 1;
}
.main-sidebar.collapsed .hide-collapsed {
opacity: 0;
font-size: 0;
margin-left: 0;
}
.main-content {
/* background-color: red; */
height: 100%;
}
.main-wrapper {
display: flex;
width: 100%;
height: 100%;
}
.dropdown-menu-lg {
max-width: 300px;
min-width: 280px;
padding: 0;
}
.brand-link {
display: block;
font-size: 1.5rem;
line-height: 2;
padding: 1rem;
}

12
css/adminlte.min.css vendored Executable file

File diff suppressed because one or more lines are too long

@ -2,138 +2,11 @@ html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Verdana";
font-family: "Verdana", serif;
color: #555;
}
.syntaxhighlighter { margin-left: -25px; }
.no-underline:hover { text-decoration: none; }
.background { background-color: #fefefe; }
.background-gray { background-color: gray; }
.background-lightgray { background-color: lightgray; }
.background-light-blue { background-color: rgb(88, 160, 224); }
.background-jumbotron { background-color: #e9ecef; }
/* TEXT COLOR */
.text-white, .text-white:hover { color: white }
.text-black, .text-black:hover { color: black; }
.text-gray, .text-gray:hover { color: gray; }
.text-default, .text-default:hover { color: #555; }
.text-title, .text-title:hover { color: #333; text-decoration: underline; font-weight: bold; font-size: 1.7em; }
.text-red, .text-red:hover { color: red; }
.text-inherit, .text-inherit:hover { color: inherit; }
.text-green-attr { color: #50a14f; }
.text-cyan { color: cyan; }
.highlight { background-color: yellow; }
.code-box { background-color: #e0e0e0;
color: rgba(0,0,0,.87);
border-radius: 1px;
padding: 2px;
border: 1px solid #ddd;
word-break: break-all;
overflow-wrap: anywhere;
}
/* TEXT TRANSFORM */
.underline { text-decoration: underline; }
.italic { font-style: italic; }
.bold { font-weight: bold; }
.text-super { vertical-align : super; }
/* TEXT SIZE */
.text-xxs { font-size: 13px; }
.text-xs { font-size: 15px; }
.text-m { font-size: 20px; }
.text-xl { font-size: 22px; }
.text-xxl { font-size: 25px; }
.text-xxxl { font-size: 30px; }
/* MARGINS */
.margin-xs { margin: 5px; }
.margin-m { margin: 10px; }
.margin-xl { margin: 15px; }
.margin-top-xxs { margin-top: 10px; }
.margin-top-xs { margin-top: 15px; }
.margin-top-m { margin-top: 20px; }
.margin-top-xl { margin-top: 25px; }
.margin-top-xxl { margin-top: 30px; }
.margin-top-xxxl { margin-top: 40px; }
.margin-bottom-xxs { margin-bottom: 10px; }
.margin-bottom-xs { margin-bottom: 15px; }
.margin-bottom-m { margin-bottom: 20px; }
.margin-bottom-xl { margin-bottom: 25px; }
.margin-bottom-xxl { margin-bottom: 30px; }
.margin-bottom-xxxl { margin-bottom: 40px; }
.margin-left-xxs { margin-left: 10px; }
.margin-left-xs { margin-left: 15px; }
.margin-left-m { margin-left: 20px; }
.margin-left-xl { margin-left: 25px; }
.margin-left-xxl { margin-left: 30px; }
.margin-center { margin-left: auto; margin-right: auto; }
.margin-none { margin: 0 !important; }
/* PADDINGS */
.padding-xs { padding: 5px; }
.padding-m { padding: 10px; }
.padding-xl { padding: 15px; }
.padding-xxl { padding: 20px; }
.padding-xxxl { padding: 25px; }
.padding-top-m { padding-top: 10px; }
.padding-top-xl { padding-top: 15px; }
.padding-top-xxl { padding-top: 20px; }
.padding-bottom-m { padding-bottom: 10px; }
.padding-bottom-xxl { padding-bottom: 20px; }
.padding-left-xs { padding-left: 5px; }
.padding-left-xl { padding-left: 15px; }
.padding-none { padding: 0; }
/* BORDER */
.round { border-radius: 50%; }
.border-white { border-color: white; }
.border-grey { border-color: grey; }
.border-xxs { border: 1px solid; }
.border-xs { border: 2px solid; }
.border-m { border: 3px solid; }
.border-bottom-xss { border-bottom: 1px solid; }
.round-border-xs { border-radius: 2px; }
.round-border { border-radius: 5px; }
.round-border-xl { border-radius: 10px; }
.self-center { align-self: center; }
/* LAYOUT */
.fullheight { height: 100%; }
.fullwidth { width: 100%; }
.max-full-width { max-width: 100%; }
.restwidth { width: auto; }
.width-75 { width: 75%; }
.relative { position: relative; }
.absolute { position: absolute; }
.bottom { bottom: 0; }
.hide-overflow { overflow: hidden; }
.hidden { display: none; }
.clickable { cursor: pointer; }
/* SCROLLBARS */
.vertical-scroll { overflow-y: auto; }
.mainContent {
margin-top: 40px;
margin-bottom: 100px;
}
.sidebar-toggle {
position: absolute;
top: 15px;
left: 15px;
}
.sidebar-toggle:hover {
background-color: rgb(69, 150, 240);
}
.external::after {
font-family: "Font Awesome 5 Free";
@ -142,85 +15,3 @@ html, body {
font-size: 10px;
vertical-align: super;
}
.navigation {
padding-left: 15px;
}
.fullscreen-container {
margin: auto;
position: fixed;
top: 0; left: 0; bottom: 0; right: 0;
z-index: 9999;
overflow-y: auto;
overflow-x: hidden;
background-color: transparent;
background-color: #000;
background-color: rgba(0, 0, 0, 0.6);
}
.thumbnail {
border: 3px solid gray;
}
.fullscreen-container > img {
position: fixed;
z-index: 1000;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 95%;
max-height: 95%;
height: auto;
border: 3px solid gray;
}
.closeButton {
position: fixed;
top: 30px;
right: 30px;
padding: 5px 9px 5px 9px;
background-color: #ddd;
border: 1px solid black;
font-size: 20px;
font-weight: bold;
cursor: pointer;
}
.flags {
padding: 3px;
}
.flags > a {
margin: 5px;
}
span > .hljs {
display: inline;
padding: .2em;
}
.inlineLink:hover {
text-decoration: underline;
cursor: pointer;
}
.bash {
background-color: #2E3436;
padding: 3px 5px 3px 5px;
}
.bash > span {
color: #D3D7CF;
overflow-wrap: anywhere;
word-break: break-all;
}
.bash > span:first-child {
display: inline;
color: #4E9A06;
}
.file-icon {
padding-bottom: 5px;
}

20
docs/LICENSE_ADMINLTE Normal file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014-2018 almasaeed2010
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -2,16 +2,17 @@ $(document).ready(function() {
$("#username").keypress(function(e) { if(e.which == 13) $("#password").focus(); });
$("#password").keypress(function(e) { if(e.which == 13) $("#btnLogin").click(); });
$("#btnLogin").click(function() {
var username = $("#username").val();
var password = $("#password").val();
var errorDiv = $("#loginError");
var createdDiv = $("#accountCreated");
var btn = $(this);
const username = $("#username").val();
const password = $("#password").val();
const errorDiv = $("#loginError");
const createdDiv = $("#accountCreated");
const stayLoggedIn = $("#stayLoggedIn").is(":checked");
const btn = $(this);
errorDiv.hide();
btn.prop("disabled", true);
btn.html("Logging in… <i class=\"fa fa-spin fa-circle-notch\"></i>");
jsCore.apiCall("user/login", {"username": username, "password": password}, function(data) {
jsCore.apiCall("/user/login", {"username": username, "password": password, "stayLoggedIn": stayLoggedIn }, function(data) {
window.location.reload();
}, function(err) {
btn.html("Login");
@ -22,4 +23,9 @@ $(document).ready(function() {
errorDiv.show();
});
});
$("#toggleSidebar").click(function() {
$(".main-wrapper").toggleClass("sidebar-collapsed");
$(".main-sidebar").toggleClass("collapsed");
});
});

7
js/adminlte.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

@ -1,108 +1,79 @@
var Core = function() {
let Core = function () {
this.__construct = function() {
this.__construct = function () {
this.url = document.location.href;
this.parseParameters();
this.langEntries = {};
};
this.apiCall = function(func, aParams, callback, onerror) {
aParams = typeof aParams !== 'undefined' ? aParams : { };
callback = typeof callback !== 'undefined' ? callback : function(data) { };
onerror = typeof onerror !== 'undefined' ? onerror : function(msg) { bootbox.alert("Ein Fehler ist aufgetreten: " + msg); };
this.apiCall = function (func, aParams, callback, onerror) {
aParams = typeof aParams !== 'undefined' ? aParams : {};
callback = typeof callback !== 'undefined' ? callback : function (data) {};
$.post('/api/' + func, aParams, function(data) {
onerror = typeof onerror !== 'undefined' ? onerror : function (msg) {
bootbox.alert("An error occurred: " + msg);
};
const path = '/api' + (func.startsWith('/') ? '' : '/') + func;
$.post(path, aParams, function (data) {
console.log(func + "(): success=" + data.success + " msg=" + data.msg);
if(data.hasOwnProperty('logoutIn') && $("#logoutTimer").length > 0) {
if (data.hasOwnProperty('logoutIn') && $("#logoutTimer").length > 0) {
$("#logoutTimer").attr("data-time", data.logoutIn);
}
if(!data.success) {
if (!data.success) {
onerror.call(this, data.msg);
} else {
callback.call(this, data);
}
}, "json").fail(function(jqXHR, textStatus, errorThrown) {
}, "json").fail(function (jqXHR, textStatus, errorThrown) {
console.log("API-Function Error: " + func + " Status: " + textStatus + " error thrown: " + errorThrown);
onerror.call(this, "An error occurred. API-Function: " + func + " Status: " + textStatus + " - " + errorThrown);
});
};
this.getCookie = function(cname) {
this.getCookie = function (cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(";");
for(var i = 0; i < ca.length; i++) {
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
};
this.requestData = function(api, dest, src, callback) {
if(typeof dest != "undefined" && Object.keys(dest).length > 0)
return true;
callback = typeof callback !== 'undefined' ? callback : function() { };
var core = this;
var dialog = bootbox.dialog({
message: 'Loading ' + src + '... <i class="fa fa-spin fa-spinner"></i>',
closeButton: false
}).bind('shown.bs.modal',function(){
core.apiCall(api, { }, function(data) {
for(var id in data[src])
dest[id] = data[src][id];
dialog.modal('hide');
callback.call(this);
}, function(msg) {
bootbox.alert("Ein Fehler ist aufgetreten: " + msg);
dialog.modal('hide');
});
});
return false;
this.addLangEntry = function (key, val) {
this.langEntries[key] = val;
};
this.requestTags = function(callback) {
this.aTags = typeof this.aTags == "undefined" ? { } : this.aTags;
return this.requestData('tags/getTags', this.aTags, 'tags', callback);
};
this.requestAccounts = function(callback) {
this.aAccounts = typeof this.aAccounts == "undefined" ? { } : this.aAccounts;
return this.requestData('accounts/getAccounts', this.aAccounts, 'accounts', callback);
};
this.requestPartners = function(callback) {
this.aPartners = typeof this.aPartners == "undefined" ? { } : this.aPartners;
return this.requestData('accounts/getPartners', this.aPartners, 'partners', callback);
};
this.addLangEntry = function(key, val) { this.langEntries[key] = val; };
this.getLangEntry = function(key) {
if(typeof this.langEntries[key] !== 'undefined' && this.langEntries.hasOwnProperty(key)) {
this.getLangEntry = function (key) {
if (typeof this.langEntries[key] !== 'undefined' && this.langEntries.hasOwnProperty(key)) {
return this.langEntries[key];
}
return key;
};
this.getUrl = function() { return this.url; };
this.getParameters = function() { return this.aParameters; };
this.getUrl = function () {
return this.url;
};
this.setTitle = function(title) {
this.getParameters = function () {
return this.aParameters;
};
this.setTitle = function (title) {
document.title = title;
};
this.changeURL = function(history) {
if(history) {
this.changeURL = function (history) {
if (history) {
window.history.pushState({
"html": document.getElementsByTagName("body")[0].innerHTML,
"pageTitle": document.title
@ -115,37 +86,37 @@ var Core = function() {
}
};
this.redirect = function() {
this.redirect = function () {
window.location = this.url;
};
this.reload = function() {
this.reload = function () {
window.location.reload();
};
this.removeParameter = function(param) {
this.removeParameter = function (param) {
if (typeof this.aParameters[param] !== 'undefined' && this.aParameters.hasOwnProperty(param)) {
delete this.aParameters[param];
}
this.updateUrl();
};
this.getParameter = function(param) {
this.getParameter = function (param) {
if (typeof this.aParameters[param] !== 'undefined' && this.aParameters.hasOwnProperty(param))
return this.aParameters[param];
else
return null;
};
this.setParameter = function(param, newvalue) {
this.setParameter = function (param, newvalue) {
newvalue = typeof newvalue !== 'undefined' ? newvalue : '';
this.aParameters[param] = newvalue;
this.updateUrl();
};
this.parseParameters = function() {
this.parseParameters = function () {
this.aParameters = [];
if(this.url.indexOf('?') === -1)
if (this.url.indexOf('?') === -1)
return;
var paramString = this.url.substring(this.url.indexOf('?') + 1);
@ -153,7 +124,7 @@ var Core = function() {
for (var i = 0; i < split.length; i++) {
var param = split[i];
var index = param.indexOf('=');
if(index !== -1) {
if (index !== -1) {
var key = param.substr(0, index);
var val = param.substr(index + 1);
this.aParameters[key] = val;
@ -162,9 +133,9 @@ var Core = function() {
}
};
this.updateUrl = function() {
this.updateUrl = function () {
this.clearUrl();
var i = 0;
let i = 0;
for (var parameter in this.aParameters) {
this.url += (i === 0 ? "?" : "&") + parameter;
if (this.aParameters.hasOwnProperty(parameter) && this.aParameters[parameter].toString().length > 0) {
@ -174,107 +145,109 @@ var Core = function() {
}
};
this.clearParameters = function() {
this.clearParameters = function () {
this.aParameters = [];
this.updateUrl();
};
this.clearUrl = function() {
if(this.url.indexOf('?') !== -1)
this.clearUrl = function () {
if (this.url.indexOf('?') !== -1)
this.url = this.url.substring(0, this.url.indexOf('?'));
};
this.logout = function() {
this.logout = function () {
this.apiCall('user/logout');
};
this.getJsonDateTime = function(date) {
this.getJsonDateTime = function (date) {
return date.getFullYear() + "-" +
((date.getMonth() + 1 < 10) ? "0" : "") + (date.getMonth() + 1) + "-" +
(date.getDate() < 10 ? "0" : "") + date.getDate() + " " +
(date.getHours() < 10 ? "0" : "") + date.getHours() + "-" +
(date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + "-" +
(date.getSeconds() < 10 ? "0" : "") + date.getSeconds();
((date.getMonth() + 1 < 10) ? "0" : "") + (date.getMonth() + 1) + "-" +
(date.getDate() < 10 ? "0" : "") + date.getDate() + " " +
(date.getHours() < 10 ? "0" : "") + date.getHours() + "-" +
(date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + "-" +
(date.getSeconds() < 10 ? "0" : "") + date.getSeconds();
};
this.getJsonDate = function(date) {
this.getJsonDate = function (date) {
return this.getJsonDateTime(date).split(' ')[0];
};
this.getJsonTime = function(date) {
this.getJsonTime = function (date) {
return this.getJsonDateTime(date).split(' ')[1];
};
this.showInputDialog = function(title, aInputs, callback, element, onCreated) {
title = typeof title !== "undefined" ? title : "";
aInputs = typeof aInputs !== "undefined" ? aInputs : {};
callback = typeof callback !== "undefined" ? callback : function(aResult, element) {};
onCreated = typeof onCreated !== "undefined" ? onCreated : function() {};
this.showInputDialog = function (title, aInputs, callback, element, onCreated) {
title = typeof title !== "undefined" ? title : "";
aInputs = typeof aInputs !== "undefined" ? aInputs : {};
callback = typeof callback !== "undefined" ? callback : function (aResult, element) {
};
onCreated = typeof onCreated !== "undefined" ? onCreated : function () {
};
var html = '<div class="modal-header"><h4 class="modal-title">' + title + '</h4></div>' +
'<form class="bootbox-form">';
'<form class="bootbox-form">';
for (var i in aInputs) {
var input = aInputs[i];
if(input.type !== "hidden" && input.type !== "checkbox")
html += '<label for="' + input.name + '">' + input.name + ':</label>';
if (input.type !== "hidden" && input.type !== "checkbox")
html += '<label for="' + input.name + '">' + input.name + ':</label>';
if(input.type === "select") {
if (input.type === "select") {
html += '<select id="' + input.id + '" class="bootbox-input bootbox-input-select form-control">';
var aValues = (input.hasOwnProperty("aValues") && typeof input.aValues !== "undefined") ? input.aValues : {};
for (var value in aValues) {
var name = aValues[value];
var selected = (input.value == value) ? " selected" : "";
var selected = (input.value === value) ? " selected" : "";
html += '<option value="' + value + '"' + selected + '>' + name + '</option>';
}
html += '</select>';
input.type = "select";
} else if(input.type === "checkbox") {
} else if (input.type === "checkbox") {
html += '<div class="checkbox">' +
'<label><table><tr>' +
'<td style="vertical-align:top;padding-top:3px;">' +
'<input class="bootbox-input bootbox-input-checkbox" id="' + input.id + '" value="1" type="checkbox"'+ (input.value ? " checked" : "") + '>' +
'</td>' +
'<td style="padding-left: 5px;">' + input.text + '</td>' +
'</tr></table></label>' +
'</div>';
} else if(input.type === "date") {
'<label><table><tr>' +
'<td style="vertical-align:top;padding-top:3px;">' +
'<input class="bootbox-input bootbox-input-checkbox" id="' + input.id + '" value="1" type="checkbox"' + (input.value ? " checked" : "") + '>' +
'</td>' +
'<td style="padding-left: 5px;">' + input.text + '</td>' +
'</tr></table></label>' +
'</div>';
} else if (input.type === "date") {
html += '<input class="bootbox-input form-control customDatePicker" autocomplete="off" ' +
'type="text" ' +
'name="' + input.name + '" ' +
'id="' + input.id + '" ' +
'value="' + (input.value ? input.value : "") + '"' + (input.readonly ? " readonly" : "") +
(input.maxlength ? ' maxlength="' + input.maxlength + '"' : '') + '>';
} else if(input.type === "time") {
'type="text" ' +
'name="' + input.name + '" ' +
'id="' + input.id + '" ' +
'value="' + (input.value ? input.value : "") + '"' + (input.readonly ? " readonly" : "") +
(input.maxlength ? ' maxlength="' + input.maxlength + '"' : '') + '>';
} else if (input.type === "time") {
html += '<div class="input-group">' +
'<input class="bootbox-input" autocomplete="off" value="0" pattern="[0-9][0-9]" type="number" name="' + input.name + '" id="' + input.id + 'Hour" style="width:60px;text-align: center">' +
'<span>:</span>' +
'<input class="bootbox-input" autocomplete="off" type="number" name="' + input.name + '" id="' + input.id + 'Minute" value="00" style="width:60px;text-align: center">' +
'</div>';
'<input class="bootbox-input" autocomplete="off" value="0" pattern="[0-9][0-9]" type="number" name="' + input.name + '" id="' + input.id + 'Hour" style="width:60px;text-align: center">' +
'<span>:</span>' +
'<input class="bootbox-input" autocomplete="off" type="number" name="' + input.name + '" id="' + input.id + 'Minute" value="00" style="width:60px;text-align: center">' +
'</div>';
} else {
html += '<input class="bootbox-input form-control" autocomplete="off" ' +
'type="' + input.type + '" ' +
'name="' + input.name + '" ' +
'id="' + input.id + '" ' +
'value="' + (input.value ? input.value : "") + '"' + (input.readonly ? " readonly" : "") +
(input.maxlength ? ' maxlength="' + input.maxlength + '"' : '') + '>';
'type="' + input.type + '" ' +
'name="' + input.name + '" ' +
'id="' + input.id + '" ' +
'value="' + (input.value ? input.value : "") + '"' + (input.readonly ? " readonly" : "") +
(input.maxlength ? ' maxlength="' + input.maxlength + '"' : '') + '>';
}
}
html += '</form>';
var dialog = bootbox.confirm(html, function(result) {
if(result) {
var dialog = bootbox.confirm(html, function (result) {
if (result) {
var aResult = [];
for (var i in aInputs) {
var input = aInputs[i];
var value = $("#" + input.id).val();
if(input.type === "select")
if (input.type === "select")
value = $("#" + input.id).find(":selected").val();
else if(input.type === "checkbox")
else if (input.type === "checkbox")
value = $("#" + input.id).prop("checked");
aResult[input.id] = value;
@ -283,9 +256,9 @@ var Core = function() {
}
});
dialog.init(function() {
$(".modal-body").on("keypress", "input", function(e) {
if(e.keyCode == 13) {
dialog.init(function () {
$(".modal-body").on("keypress", "input", function (e) {
if (e.keyCode === 13) {
e.preventDefault();
$(".modal-footer .btn-primary").click();
}
@ -297,99 +270,7 @@ var Core = function() {
this.__construct();
};
function findGetParameter(parameterName) {
var result = null, tmp = [];
location.search.substr(1).split("&").forEach(function (item) {
tmp = item.split("=");
if (tmp[0] === parameterName) {
result = decodeURIComponent(tmp[1]);
}
});
return result;
}
var jsCore = new Core();
let jsCore = new Core();
$(document).ready(function() {
$(".nav-toggle-menu").click(function(e) {
e.preventDefault();
var ul = $(this).parents('li').find('ul');
if(ul.hasClass('closed')) {
$(this).removeClass('fa-caret-down');
$(this).addClass('fa-caret-up');
ul.animate({
"max-height": (ul.find('li').length * 38) + "px"
}, {
duration: 350,
easing: "swing",
complete: function() { ul.removeClass('closed'); }
});
} else {
$(this).removeClass('fa-caret-up');
$(this).addClass('fa-caret-down');
ul.animate({
"max-height": "0"
}, {
duration: 350,
easing: "swing",
complete: function() { ul.addClass('closed'); }
});
}
});
$(".copy").click(function() {
var text = $(this).text();
if(navigator.clipboard) {
navigator.clipboard.writeText(text).then(function() { }, function(err) {
console.error('Async: Could not copy text: ', err);
});
}
});
function toggleLinkIcon(icon, show, parent) {
if(show) {
icon.show();
if(parent) {
icon.position({
my: "right-5",
at: "left",
of: parent
});
}
} else {
icon.hide();
}
}
$(".inlineLink").mouseenter(function() {
var target = $(this).data("target");
if(target) {
var icon = $("#" + target);
toggleLinkIcon(icon, true, $(this));
}
});
$(".inlineLink").mouseleave(function() {
var target = $(this).data("target");
if(target) {
var icon = $("#" + target);
toggleLinkIcon(icon, false);
}
});
$(".inlineLink").click(function() {
var id = $(this).attr('id');
if(id) {
var url = window.location.href;
var index = url.indexOf("#");
if(index !== -1) {
url = url.substring(0, index);
}
window.location.href = url + "#" + id;
}
});
});