User Create page

This commit is contained in:
Roman Hergenreder 2020-06-17 14:30:37 +02:00
parent 32df152ca7
commit 99672405c6
14 changed files with 505 additions and 22 deletions

158
admin/dist/main.js vendored

File diff suppressed because one or more lines are too long

@ -10962,6 +10962,22 @@
} }
} }
}, },
"react-tooltip": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.7.tgz",
"integrity": "sha512-z5T3gNplT76rkT7ZImfx/uXfBtS+x7+WK2H8MVZ5skSwETDDx0hs0+P0jwL5gYDaBvsZ7LYiCBzAOd3tN85CMA==",
"requires": {
"prop-types": "^15.7.2",
"uuid": "^7.0.3"
},
"dependencies": {
"uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
}
}
},
"read-pkg": { "read-pkg": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",

@ -10,7 +10,8 @@
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1" "react-scripts": "3.4.1",
"react-tooltip": "^4.2.7"
}, },
"scripts": { "scripts": {
"build": "webpack --mode development && cp dist/main.js ../js/admin.min.js" "build": "webpack --mode development && cp dist/main.js ../js/admin.min.js"

@ -8,7 +8,7 @@ export default class View404 extends React.Component {
<h2 className={"headline text-warning"}>404</h2> <h2 className={"headline text-warning"}>404</h2>
<div className={"error-content"}> <div className={"error-content"}>
<h3> <h3>
<Icon icon={"exclamation-triangle"} classes={"text-warning"}/> Oops! Page not found. <Icon icon={"exclamation-triangle"} className={"text-warning"}/> Oops! Page not found.
</h3> </h3>
<p> <p>
We could not find the page you were looking for. We could not find the page you were looking for.

@ -45,4 +45,12 @@ export default class API {
async fetchGroups(pageNum = 1) { async fetchGroups(pageNum = 1) {
return this.apiCall("groups/fetch", { page: pageNum }); return this.apiCall("groups/fetch", { page: pageNum });
} }
async inviteUser(username, email) {
return this.apiCall("user/invite", { username: username, email: email });
}
async createUser(username, email, password, confirmPassword) {
return this.apiCall("user/create", { username: username, email: email, password: password, confirmPassword: confirmPassword });
}
}; };

@ -11,7 +11,7 @@ export default function Alert(props) {
return ( return (
<div className={"alert alert-danger alert-dismissible"}> <div className={"alert alert-danger alert-dismissible"}>
<button type="button" className={"close"} data-dismiss={"alert"} aria-hidden={"true"} onClick={onClose}>×</button> <button type="button" className={"close"} data-dismiss={"alert"} aria-hidden={"true"} onClick={onClose}>×</button>
<h5><Icon icon={"ban"} classes={"icon"} /> {title}</h5> <h5><Icon icon={"ban"} className={"icon"} /> {title}</h5>
{message} {message}
</div> </div>
) )

@ -2,7 +2,7 @@ import * as React from "react";
export default function Icon(props) { export default function Icon(props) {
let classes = props.classes || []; let classes = props.className || [];
classes = Array.isArray(classes) ? classes : classes.toString().split(" "); classes = Array.isArray(classes) ? classes : classes.toString().split(" ");
let type = props.type || "fas"; let type = props.type || "fas";
let icon = props.icon; let icon = props.icon;
@ -15,7 +15,6 @@ export default function Icon(props) {
} }
let newProps = {...props, className: classes.join(" ") }; let newProps = {...props, className: classes.join(" ") };
delete newProps["classes"];
delete newProps["type"]; delete newProps["type"];
delete newProps["icon"]; delete newProps["icon"];

@ -7,6 +7,7 @@ import Header from './header.js';
import Sidebar from './sidebar.js'; import Sidebar from './sidebar.js';
import UserOverview from './views/users.js'; import UserOverview from './views/users.js';
import Overview from './views/overview.js' import Overview from './views/overview.js'
import CreateUser from "./views/adduser";
import Icon from "./elements/icon"; import Icon from "./elements/icon";
import Dialog from "./elements/dialog"; import Dialog from "./elements/dialog";
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom' import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
@ -77,7 +78,8 @@ class AdminDashboard extends React.Component {
<section className={"content"}> <section className={"content"}>
<Switch> <Switch>
<Route path={"/admin/dashboard"}><Overview {...this.controlObj} /></Route> <Route path={"/admin/dashboard"}><Overview {...this.controlObj} /></Route>
<Route path={"/admin/users"}><UserOverview {...this.controlObj} /></Route> <Route exact={true} path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>
<Route exact={true} path={"/admin/users/adduser"}><CreateUser {...this.controlObj} /></Route>
<Route path={"/admin/logs"}><Logs {...this.controlObj} /></Route> <Route path={"/admin/logs"}><Logs {...this.controlObj} /></Route>
<Route path={"*"}><View404 /></Route> <Route path={"*"}><View404 /></Route>
</Switch> </Switch>

@ -56,7 +56,7 @@ export default function Sidebar(props) {
li.push( li.push(
<li key={id} className={"nav-item"}> <li key={id} className={"nav-item"}>
<NavLink to={"/admin/" + id} className={"nav-link"} activeClassName={"active"}> <NavLink to={"/admin/" + id} className={"nav-link"} activeClassName={"active"}>
<Icon icon={obj.icon} classes={"nav-icon"} /><p>{obj.name}{badge}</p> <Icon icon={obj.icon} className={"nav-icon"} /><p>{obj.name}{badge}</p>
</NavLink> </NavLink>
</li> </li>
); );
@ -64,7 +64,7 @@ export default function Sidebar(props) {
li.push(<li className={"nav-item"} key={"logout"}> li.push(<li className={"nav-item"} key={"logout"}>
<a href={"#"} onClick={() => onLogout()} className={"nav-link"}> <a href={"#"} onClick={() => onLogout()} className={"nav-link"}>
<Icon icon={"arrow-left"} classes={"nav-icon"} /> <Icon icon={"arrow-left"} className={"nav-icon"} />
<p>Logout</p> <p>Logout</p>
</a> </a>
</li>); </li>);

103
admin/src/views/adduser.js Normal file

@ -0,0 +1,103 @@
import * as React from "react";
import {Link} from "react-router-dom";
import Alert from "../elements/alert";
import Icon from "../elements/icon";
import ReactTooltip from 'react-tooltip'
export default class CreateUser extends React.Component {
constructor(props) {
super(props);
this.state = {
errors: [],
sendInvite: true
}
}
render() {
let errors = [];
for (let i = 0; i < this.state.errors.length; i++) {
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
}
let passwordForm = null;
if (!this.state.sendInvite) {
passwordForm = <div className={"mt-2"}>
<div className={"form-group"}>
<label htmlFor={"password"}>Password</label>
<input type={"password"} className={"form-control"} placeholder={"Password"}
id={"password"} name={"password"}/>
</div>
<div className={"form-group"}>
<label htmlFor={"confirmPassword"}>Confirm Password</label>
<input type={"password"} className={"form-control"} placeholder={"Confirm Password"}
id={"confirmPassword"} name={"confirmPassword"}/>
</div>
</div>
}
return <>
<div className="content-header">
<div className="container-fluid">
<div className="row mb-2">
<div className="col-sm-6">
<h1 className="m-0 text-dark">Create a new user</h1>
</div>
<div className="col-sm-6">
<ol className="breadcrumb float-sm-right">
<li className="breadcrumb-item"><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item"><Link to={"/admin/users"}>Users</Link></li>
<li className="breadcrumb-item active">Add User</li>
</ol>
</div>
</div>
</div>
</div>
<div className={"content"}>
{errors}
<div className={"row"}>
<div className={"col-lg-6 p-3"}>
<form role={"form"} onSubmit={(e) => this.submitForm(e)}>
<div className={"form-group"}>
<label htmlFor={"username"}>Username</label>
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
name={"username"} id={"username"} maxLength={32}/>
</div>
<div className={"form-group"}>
<label htmlFor={"email"}>E-Mail</label>
<input type={"email"} className={"form-control"} placeholder={"E-Mail address"}
id={"email"} name={"email"} maxLength={64}/>
</div>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
onChange={() => this.onCheckboxChange()}
id={"sendInvite"} name={"sendInvite"} defaultChecked={this.state.sendInvite}/>
<label className={"form-check-label"} htmlFor={"sendInvite"}>
Send Invitation
<Icon icon={"question-circle"} className={"ml-2"} style={{"color": "#0069d9"}}
data-tip={"The user will receive an invitation token via email and can choose the password on his own."}
data-type={"info"} data-place={"right"} data-effect={"solid"}/>
</label>
</div>
{passwordForm}
<button type={"submit"} className={"btn btn-primary mt-2"}>Submit</button>
</form>
</div>
</div>
</div>
<ReactTooltip/>
</>;
}
submitForm(e) {
e.preventDefault();
}
onCheckboxChange() {
this.setState({
...this.state,
sendInvite: !this.state.sendInvite,
});
}
}

@ -193,6 +193,9 @@ export default class UserOverview extends React.Component {
<div className={"card-header border-0"}> <div className={"card-header border-0"}>
<h3 className={"card-title"}>Users</h3> <h3 className={"card-title"}>Users</h3>
<div className={"card-tools"}> <div className={"card-tools"}>
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/users/adduser"} >
<Icon icon={"plus"}/>
</Link>
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchUsers()}> <a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchUsers()}>
<Icon icon={"sync"}/> <Icon icon={"sync"}/>
</a> </a>
@ -274,6 +277,9 @@ export default class UserOverview extends React.Component {
<div className={"card-header border-0"}> <div className={"card-header border-0"}>
<h3 className={"card-title"}>Groups</h3> <h3 className={"card-title"}>Groups</h3>
<div className={"card-tools"}> <div className={"card-tools"}>
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/users/addgroup"} >
<Icon icon={"plus"}/>
</Link>
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchGroups()}> <a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchGroups()}>
<Icon icon={"sync"}/> <Icon icon={"sync"}/>
</a> </a>

@ -0,0 +1,31 @@
<?php
namespace Api\User;
use Api\Parameter\StringType;
use \Api\Request;
class Create extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new StringType('email', 64, true),
'password' => new StringType('password'),
'confirmPassword' => new StringType('confirmPassword'),
));
$this->csrfTokenRequired = true;
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
// TODO:
return $this->success;
}
}

@ -0,0 +1,29 @@
<?php
namespace Api\User;
use Api\Parameter\StringType;
use \Api\Request;
class Invite extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new StringType('email', 64),
));
$this->csrfTokenRequired = true;
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
// TODO:
return $this->success;
}
}

158
js/admin.min.js vendored

File diff suppressed because one or more lines are too long