Logout functionality
This commit is contained in:
parent
11323f2781
commit
95b803a1e4
16
admin/dist/main.js
vendored
16
admin/dist/main.js
vendored
File diff suppressed because one or more lines are too long
2918
admin/package-lock.json
generated
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.user = {};
|
this.loggedIn = false;
|
||||||
this.baseUrl = "http://localhost"
|
this.user = { };
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Dialog(props) {
|
||||||
|
|
||||||
|
const [value, setValue] = useStateFromProp(props);
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
setValue({ });
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
const show = typeof value.message !== "undefined";
|
||||||
this.setState({ hidden: true });
|
const classes = "modal fade" + (show ? " show" : "");
|
||||||
}
|
const style = { paddingRight: "12px", display: (show ? "block" : "none") };
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
|
<div className={classes} id="modal-default" style={style} aria-modal="true" onClick={() => onClose()}>
|
||||||
console.log("Rendering dialog with:", this.props);
|
|
||||||
|
|
||||||
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
4
admin/src/include/adminlte.min.css
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
7
admin/src/include/adminlte.min.js
vendored
7
admin/src/include/adminlte.min.js
vendored
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();
|
<Header />
|
||||||
|
<Sidebar currentView={this.state.currentView} onChangeView={this.onChangeView.bind(this)} showDialog={this.showDialog.bind(this)} api={this.api} />
|
||||||
return <div className={"wrapper"}>
|
<div className={"content-wrapper p-2"}>
|
||||||
<Header />
|
<section className={"content"}>
|
||||||
<Sidebar currentView={this.state.currentView} onChangeView={this.onChangeView.bind(this)} showDialog={this.showDialog.bind(this)} api={this.api} />
|
{this.createContent()}
|
||||||
<div className={"content-wrapper p-2"}>
|
<Dialog {...this.state.dialog}/>
|
||||||
<section className={"content"}>
|
</section>
|
||||||
{content}
|
</div>
|
||||||
{dialog}
|
</>
|
||||||
</section>
|
|
||||||
</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={"mt-2"}>
|
<div className={"os-resize-observer observed"} style={{left: "0px", right: "auto"}}/>
|
||||||
Logged in as: {this.parent.api.user.name}
|
|
||||||
</div>
|
</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%"}}>
|
||||||
|
|
||||||
<hr />
|
{/* LOGGED IN AS */}
|
||||||
|
<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>
|
||||||
|
|
||||||
{/* Sidebar Menu */}
|
{/* SIDEBAR */}
|
||||||
<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);
|
||||||
$values = array_merge($values, $jsonData);
|
if ($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
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
4147
js/admin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user