Logout functionality

This commit is contained in:
Roman Hergenreder 2020-06-14 22:35:01 +02:00
parent 11323f2781
commit 95b803a1e4
19 changed files with 4620 additions and 2742 deletions

16
admin/dist/main.js vendored

File diff suppressed because one or more lines are too long

2918
admin/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -6,7 +6,6 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0", "@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"adminlte-reactjs": "^1.0.6",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-scripts": "3.4.1" "react-scripts": "3.4.1"

@ -2,19 +2,35 @@ import 'babel-polyfill';
export default class API { export default class API {
constructor() { constructor() {
this.loggedIn = false;
this.user = { }; this.user = { };
this.baseUrl = "http://localhost" }
csrfToken() {
return this.loggedIn ? this.user.session.csrf_token : null;
}
async apiCall(method, params) {
params = params || { };
params.csrf_token = this.csrfToken();
let response = await fetch("/api/" + method, {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(params)
});
return await response.json();
} }
async fetchUser() { async fetchUser() {
let response = await fetch(this.baseUrl + "/api/user/fetch"); let response = await fetch("/api/user/info");
let data = await response.json() let data = await response.json();
this.user = data["users"][0]; this.user = data["user"];
return data && data.success && data.hasOwnProperty("logoutIn"); this.loggedIn = data["loggedIn"];
return data && data.success && data.loggedIn;
} }
async logout() { async logout() {
let response = await fetch(this.baseUrl + "/api/user/logout"); return this.apiCall("user/logout");
return await response.json();
} }
}; };

@ -1,42 +1,43 @@
import React from "react"; import React, {useEffect, useState} from "react";
export default class Dialog extends React.Component { function useStateFromProp(initialValue) {
const [value, setValue] = useState(initialValue);
constructor(props) { useEffect(() => setValue(initialValue), [initialValue]);
super(props);
this.state = { hidden: !!props.hidden }; return [value, setValue];
} }
onClose() { export default function Dialog(props) {
this.setState({ hidden: true });
const [value, setValue] = useStateFromProp(props);
function onClose() {
setValue({ });
} }
render() { const show = typeof value.message !== "undefined";
const classes = "modal fade" + (show ? " show" : "");
const style = { paddingRight: "12px", display: (show ? "block" : "none") };
console.log("Rendering dialog with:", this.props); return (
<div className={classes} id="modal-default" style={style} aria-modal="true" onClick={() => onClose()}>
let classes = "modal fade";
if (!this.state.hidden) {
classes *= " show";
}
return <div className={classes} id="modal-default" style={{paddingRight: "12px"}} aria-modal="true" onClick={() => this.onClose()}>
<div className="modal-dialog" onClick={(e) => e.stopPropagation()}> <div className="modal-dialog" onClick={(e) => e.stopPropagation()}>
<div className="modal-content"> <div className="modal-content">
<div className="modal-header"> <div className="modal-header">
<h4 className="modal-title">{this.props.title}</h4> <h4 className="modal-title">{props.title}</h4>
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={() => this.onClose()}> <button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={() => onClose()}>
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
</div> </div>
<div className="modal-body"> <div className="modal-body">
<p>{this.props.message}</p> <p>{props.message}</p>
</div> </div>
<div className="modal-footer justify-content-between"> <div className="modal-footer justify-content-between">
<button type="button" className="btn btn-default" data-dismiss="modal" onClick={() => this.onClose()}>Close</button> <button type="button" className="btn btn-default" data-dismiss="modal" onClick={() => onClose()}>Close</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
} );
} }

@ -37,10 +37,11 @@ export default class Header extends React.Component {
{/* Right navbar links */} {/* Right navbar links */}
<ul className={"navbar-nav ml-auto"}> <ul className={"navbar-nav ml-auto"}>
{/* Notifications Dropdown Menu */} {/* Notifications Dropdown Menu */}
<li className={"nav-item dropdown"}> <li className={"nav-item dropdown"}>
<a href={"#"} className={"nav-link"} data-toggle={"dropdown"}> <a href={"#"} className={"nav-link"} data-toggle={"dropdown"}>
<Icon class={"bell"} type={"far"} /> <Icon icon={"bell"} type={"far"} />
<span className={"badge badge-warning navbar-badge"}> <span className={"badge badge-warning navbar-badge"}>
{notificationCount} {notificationCount}
</span> </span>

@ -7,8 +7,8 @@ export default function Icon(props) {
let type = props.type || "fas"; let type = props.type || "fas";
let icon = props.icon; let icon = props.icon;
classes.push("fa"); classes.push(type);
classes.push(type + "-" + icon); classes.push("fa-" + icon);
if (icon === "spinner") { if (icon === "spinner") {
classes.push("fa-spin"); classes.push("fa-spin");

4
admin/src/include/adminlte.min.css vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,77 +0,0 @@
.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 {
background-color: #343a40;
width: 250px;
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 999;
color: #fff;
transition: all 0.3s;
}
.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 {
height: 100%;
}
.main-wrapper {
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;
}
.show {
display: block !important;
}

@ -2,7 +2,6 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './include/index.css'; import './include/index.css';
import './include/adminlte.min.css'; import './include/adminlte.min.css';
// import './include/adminlte.min.js';
import API from './api.js'; import API from './api.js';
import Header from './header.js'; import Header from './header.js';
import Sidebar from './sidebar.js'; import Sidebar from './sidebar.js';
@ -19,18 +18,16 @@ class AdminDashboard extends React.Component {
this.state = { this.state = {
currentView: "dashboard", currentView: "dashboard",
loaded: false, loaded: false,
dialog: { hidden: true } dialog: { }
}; };
} }
onChangeView(view) { onChangeView(view) {
console.log("changing view to: " + view); this.setState({ ...this.state, currentView: view || "dashboard", dialog: { } });
this.setState({ ...this.state, currentView: view || "dashboard" });
} }
showDialog(props) { showDialog(props) {
props = props || { hidden: true }; props = props || { };
if (!props.hasOwnProperty("hidden")) props.hidden = false;
this.setState({ ...this.state, dialog: props }); this.setState({ ...this.state, dialog: props });
} }
@ -47,20 +44,17 @@ class AdminDashboard extends React.Component {
return <b>Loading <Icon icon={"spinner"} /></b> return <b>Loading <Icon icon={"spinner"} /></b>
} }
console.log("Rendering mainview with:", this.state.dialog); console.log("index.render, state=", this.state);
const dialog = <Dialog {...this.state.dialog}/> return <>
const content = this.createContent();
return <div className={"wrapper"}>
<Header /> <Header />
<Sidebar currentView={this.state.currentView} onChangeView={this.onChangeView.bind(this)} showDialog={this.showDialog.bind(this)} api={this.api} /> <Sidebar currentView={this.state.currentView} onChangeView={this.onChangeView.bind(this)} showDialog={this.showDialog.bind(this)} api={this.api} />
<div className={"content-wrapper p-2"}> <div className={"content-wrapper p-2"}>
<section className={"content"}> <section className={"content"}>
{content} {this.createContent()}
{dialog} <Dialog {...this.state.dialog}/>
</section> </section>
</div> </div>
</div> </>
} }
createContent() { createContent() {

@ -9,7 +9,7 @@ export default class Sidebar extends React.Component {
onChangeView: props.onChangeView || function() { }, onChangeView: props.onChangeView || function() { },
showDialog: props.showDialog || function() {}, showDialog: props.showDialog || function() {},
api: props.api api: props.api
} };
this.state = { currentView: props.currentView, } this.state = { currentView: props.currentView, }
} }
@ -50,17 +50,21 @@ export default class Sidebar extends React.Component {
}; };
let li = []; let li = [];
// li.push(<li className={"nav-item"} key={"logged-in-as"}><span className={""}>Logged in as: {this.parent.api.user.name}</span><hr/></li>);
// li.push(<li key={"hr"}><hr/></li>);
// li.push(<li key={"header"} className={"header"}>MAIN NAVIGATION</li>);
for (let id in menuItems) { for (let id in menuItems) {
let obj = menuItems[id]; let obj = menuItems[id];
let active = this.state.currentView === id ? " active" : ""; let active = this.state.currentView === id ? " active" : "";
li.push(<li key={id} className={"nav-item"}> li.push(<li key={id} className={"nav-item"}>
<a href={"#"} onClick={() => this.onChangeView(id)} className={"nav-link" + active}> <a href={"#"} onClick={() => this.onChangeView(id)} className={"nav-link" + active}>
<Icon icon={obj.icon} /><p>{obj.name}</p> <Icon icon={obj.icon} classes={"nav-icon"} /><p>{obj.name}</p>
</a> </a>
</li>); </li>);
} }
li.push(<li key={"logout"} className={"nav-item"}> li.push(<li className={"nav-item"} key={"logout"}>
<a href={"#"} onClick={() => this.onLogout()} className={"nav-link"}> <a href={"#"} onClick={() => this.onLogout()} className={"nav-link"}>
<Icon icon={"arrow-left"} classes={"nav-icon"} /> <Icon icon={"arrow-left"} classes={"nav-icon"} />
<p>Logout</p> <p>Logout</p>
@ -68,26 +72,41 @@ export default class Sidebar extends React.Component {
</li>); </li>);
return <aside className={"main-sidebar sidebar-dark-primary elevation-4"}> return <aside className={"main-sidebar sidebar-dark-primary elevation-4"}>
<a href={"#"} onClick={() => this.onChangeView("dashboard") } className={"brand-link"}> <a href={"#"} className={"brand-link"} onClick={() => this.onChangeView("dashboard")}>
<img src={"/img/web_base_logo.png"} alt={"WebBase Logo"} className={"brand-image img-circle elevation-3"} style={{"opacity": ".8"}} /> <img src={"/img/icons/logo.png"} alt={"Logo"} className={"brand-image img-circle elevation-3"} style={{opacity: ".8"}} />
<span className={"brand-text font-weight-light"}>WebBase</span> <span className={"brand-text font-weight-light ml-2"}>WebBase</span>
</a> </a>
{/* Sidebar */} <div className={"sidebar os-host os-theme-light os-host-overflow os-host-overflow-y os-host-resize-disabled os-host-scrollbar-horizontal-hidden os-host-transition"}>
<div className={"sidebar"}> {/* IDK what this is */}
<div className={"os-resize-observer-host"}>
<div className={"os-resize-observer observed"} style={{left: "0px", right: "auto"}}/>
</div>
<div className={"os-size-auto-observer"} style={{height: "calc(100% + 1px)", float: "left"}}>
<div className={"os-resize-observer observed"}/>
</div>
<div className={"os-content-glue"} style={{margin: "0px -8px"}}/>
<div className={"os-padding"}>
<div className={"os-viewport os-viewport-native-scrollbars-invisible"} style={{right: "0px", bottom: "0px"}}>
<div className={"os-content"} style={{padding: "0px 0px", height: "100%", width: "100%"}}>
<div className={"mt-2"}> {/* LOGGED IN AS */}
Logged in as: {this.parent.api.user.name} <div className="user-panel mt-3 pb-3 mb-3 d-flex">
<div className="info">
<a href="#" className="d-block">Logged in as: {this.parent.api.user.name}</a>
</div>
</div> </div>
<hr /> {/* SIDEBAR */}
{/* Sidebar Menu */}
<nav className={"mt-2"}> <nav className={"mt-2"}>
<ul className={"nav nav-pills nav-sidebar flex-column"} data-widget={"treeview"} role={"menu"} data-accordion={"false"}> <ul className={"nav nav-pills nav-sidebar flex-column"} data-widget={"treeview"} role={"menu"} data-accordion={"false"}>
{li} {li}
</ul> </ul>
</nav> </nav>
</div>
</div>
</div>
</div> </div>
</aside> </aside>
} }

@ -91,7 +91,13 @@ class Request {
$values = $_REQUEST; $values = $_REQUEST;
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) { if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) {
$jsonData = json_decode(file_get_contents('php://input'), true); $jsonData = json_decode(file_get_contents('php://input'), true);
if ($jsonData) {
$values = array_merge($values, $jsonData); $values = array_merge($values, $jsonData);
} else {
$this->lastError = 'Invalid request body.';
header('HTTP 1.1 400 Bad Request');
return false;
}
} }
} }

@ -18,10 +18,6 @@ class Logout extends Request {
return false; return false;
} }
$this->lastError = "CUSTOM ERROR MESSAGE";
$this->success = false;
return false;
$this->success = $this->user->logout(); $this->success = $this->user->logout();
$this->lastError = $this->user->getSQL()->getLastError(); $this->lastError = $this->user->getSQL()->getLastError();
return $this->success; return $this->success;

@ -30,7 +30,7 @@ namespace Documents\Admin {
protected function initSources() { protected function initSources() {
// $this->loadJQuery(); // $this->loadJQuery();
// $this->loadFontawesome(); $this->loadFontawesome();
// $this->addJS(Script::CORE); // $this->addJS(Script::CORE);
// $this->addCSS(Link::CORE); // $this->addCSS(Link::CORE);
// $this->addJS(Script::ADMIN); // $this->addJS(Script::ADMIN);

@ -273,7 +273,7 @@ class AdminDashboardBody extends Body {
*/ */
$script = new Script(Script::MIME_TEXT_JAVASCRIPT, "/js/admin.min.js"); $script = new Script(Script::MIME_TEXT_JAVASCRIPT, "/js/admin.min.js");
$html .= "<body id=\"root\">$script</body>"; $html .= "<body><div class=\"wrapper\" id=\"root\">$script</div></body>";
return $html; return $html;
} }
} }

@ -17,7 +17,6 @@ class LoginBody extends Body {
$head = $this->getDocument()->getHead(); $head = $this->getDocument()->getHead();
$head->loadBootstrap(); $head->loadBootstrap();
$head->loadJQuery(); $head->loadJQuery();
$head->loadFontawesome();
$head->addJS(Script::CORE); $head->addJS(Script::CORE);
$head->addCSS(Link::CORE); $head->addCSS(Link::CORE);
$head->addJS(Script::ADMIN); $head->addJS(Script::ADMIN);

BIN
img/icons/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

4147
js/admin.min.js vendored Normal file

File diff suppressed because one or more lines are too long