User Create page
This commit is contained in:
parent
32df152ca7
commit
99672405c6
158
admin/dist/main.js
vendored
158
admin/dist/main.js
vendored
File diff suppressed because one or more lines are too long
16
admin/package-lock.json
generated
16
admin/package-lock.json
generated
@ -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
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>
|
||||||
|
31
core/Api/User/Create.class.php
Normal file
31
core/Api/User/Create.class.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
29
core/Api/User/Invite.class.php
Normal file
29
core/Api/User/Invite.class.php
Normal file
@ -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
158
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user