User Stuff
This commit is contained in:
parent
a4504d8336
commit
7ac6b4d245
@ -126,7 +126,7 @@ namespace Api\User {
|
||||
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
|
||||
'count' => new Parameter('count', Parameter::TYPE_INT, true, 20),
|
||||
'count' => new Parameter('count', Parameter::TYPE_INT, true, 20)
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
@ -214,6 +214,64 @@ namespace Api\User {
|
||||
}
|
||||
}
|
||||
|
||||
class Get extends UserAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'id' => new Parameter('id', Parameter::TYPE_INT)
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->requiredGroup = array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN);
|
||||
}
|
||||
|
||||
public function execute($values = array()) {
|
||||
if (!parent::execute($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$id = $this->getParam("id");
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at",
|
||||
"Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor")
|
||||
->from("User")
|
||||
->leftJoin("UserGroup", "User.uid", "UserGroup.user_id")
|
||||
->leftJoin("Group", "Group.uid", "UserGroup.group_id")
|
||||
->where(new Compare("User.uid", $id))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
if (empty($res)) {
|
||||
return $this->createError("User not found");
|
||||
} else {
|
||||
$row = $res[0];
|
||||
$this->result["user"] = array(
|
||||
"uid" => $row["userId"],
|
||||
"name" => $row["name"],
|
||||
"email" => $row["email"],
|
||||
"registered_at" => $row["registered_at"],
|
||||
"groups" => array()
|
||||
);
|
||||
|
||||
foreach($res as $row) {
|
||||
$this->result["user"]["groups"][] = array(
|
||||
"uid" => $row["groupId"],
|
||||
"name" => $row["groupName"],
|
||||
"color" => $row["groupColor"],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
|
||||
class Info extends UserAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
|
20
js/admin.min.js
vendored
20
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@ -43,6 +43,10 @@ export default class API {
|
||||
return this.apiCall("notifications/fetch");
|
||||
}
|
||||
|
||||
async getUser(id) {
|
||||
return this.apiCall("user/get", { id: id });
|
||||
}
|
||||
|
||||
async fetchUsers(pageNum = 1, count = 20) {
|
||||
return this.apiCall("user/fetch", { page: pageNum, count: count });
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import React from "react";
|
||||
|
||||
export default function Alert(props) {
|
||||
|
||||
const onClose = props.onClose || function() { };
|
||||
const onClose = props.onClose || null;
|
||||
const title = props.title || "Untitled Alert";
|
||||
const message = props.message || "Alert message";
|
||||
const type = props.type || "danger";
|
||||
@ -18,7 +18,7 @@ export default function Alert(props) {
|
||||
|
||||
return (
|
||||
<div className={"alert alert-" + type + " alert-dismissible"}>
|
||||
<button type="button" className={"close"} data-dismiss={"alert"} aria-hidden={"true"} onClick={onClose}>×</button>
|
||||
{onClose ? <button type="button" className={"close"} data-dismiss={"alert"} aria-hidden={"true"} onClick={onClose}>×</button> : null}
|
||||
<h5><Icon icon={icon} className={"icon"} /> {title}</h5>
|
||||
{message}
|
||||
</div>
|
||||
|
@ -16,6 +16,7 @@ import Logs from "./views/logs";
|
||||
import PageOverview from "./views/pages";
|
||||
import HelpPage from "./views/help";
|
||||
import Footer from "./footer";
|
||||
import EditUser from "./views/edituser";
|
||||
|
||||
class AdminDashboard extends React.Component {
|
||||
|
||||
@ -83,7 +84,11 @@ class AdminDashboard extends React.Component {
|
||||
<Switch>
|
||||
<Route path={"/admin/dashboard"}><Overview {...this.controlObj} notifications={this.state.notifications} /></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/user/add"}><CreateUser {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/user/edit/:userId"} render={(props) => {
|
||||
let newProps = {...props, ...this.controlObj};
|
||||
return <EditUser {...newProps} />
|
||||
}}/>
|
||||
<Route path={"/admin/logs"}><Logs {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/help"}><HelpPage {...this.controlObj} /></Route>
|
||||
|
115
src/src/views/edituser.js
Normal file
115
src/src/views/edituser.js
Normal file
@ -0,0 +1,115 @@
|
||||
import * as React from "react";
|
||||
import Icon from "../elements/icon";
|
||||
import Alert from "../elements/alert";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
export default class EditUser extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.parent = {
|
||||
api: props.api
|
||||
};
|
||||
|
||||
this.state = {
|
||||
user: {},
|
||||
errors: [],
|
||||
fetchError: null,
|
||||
loaded: false,
|
||||
isSaving: false
|
||||
}
|
||||
}
|
||||
|
||||
removeError(i) {
|
||||
if (i >= 0 && i < this.state.errors.length) {
|
||||
let errors = this.state.errors.slice();
|
||||
errors.splice(i, 1);
|
||||
this.setState({...this.state, errors: errors});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.parent.api.getUser(this.props.match.params["userId"]).then((res) => {
|
||||
if (res.success) {
|
||||
this.setState({ ...this.state, user: res.user, loaded: true });
|
||||
} else {
|
||||
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (!this.state.loaded) {
|
||||
return <h2 className={"text-center"}>
|
||||
Loading…<br/>
|
||||
<Icon icon={"spinner"} className={"mt-3 text-muted fa-2x"}/>
|
||||
</h2>
|
||||
}
|
||||
|
||||
let errors = [];
|
||||
let form = null;
|
||||
if(this.state.fetchError) {
|
||||
errors.push(
|
||||
<Alert key={"error-fetch"} title={"Error fetching user details"} type={"danger"} message={
|
||||
<div>{this.state.fetchError}<br/>You can meanwhile return to the
|
||||
<Link to={"/admin/users"}>user overview</Link>
|
||||
</div>
|
||||
}/>
|
||||
)
|
||||
} else {
|
||||
for (let i = 0; i < this.state.errors.length; i++) {
|
||||
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
|
||||
}
|
||||
|
||||
form = <form role={"form"} onSubmit={(e) => e.preventDefault()}>
|
||||
<div className={"form-group"}>
|
||||
<label htmlFor={"username"}>Username</label>
|
||||
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
|
||||
name={"username"} id={"username"} maxLength={32} value={this.state.user.name}/>
|
||||
</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} value={this.state.user.email} />
|
||||
</div>
|
||||
|
||||
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
|
||||
<Icon icon={"arrow-left"}/>
|
||||
Back
|
||||
</Link>
|
||||
{ this.state.isSaving
|
||||
? <button type={"submit"} className={"btn btn-primary mt-2"} disabled>Saving… <Icon icon={"circle-notch"} /></button>
|
||||
: <button type={"submit"} className={"btn btn-primary mt-2"}>Save</button>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
|
||||
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">Edit 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"}>
|
||||
<div className={"row"}>
|
||||
<div className={"col-lg-6 pl-5 pr-5"}>
|
||||
{errors}
|
||||
{form}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import Icon from "../elements/icon";
|
||||
import {Link} from "react-router-dom";
|
||||
import {getPeriodString} from "../global";
|
||||
import Alert from "../elements/alert";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
const TABLE_SIZE = 10;
|
||||
|
||||
@ -77,7 +78,7 @@ export default class UserOverview extends React.Component {
|
||||
totalCount: res.totalCount,
|
||||
}
|
||||
});
|
||||
this.rowCount = Math.max(this.rowCount, Object.keys(res.groups).length);
|
||||
this.rowCount = Math.max(this.rowCount, Object.keys(res.users).length);
|
||||
} else {
|
||||
let errors = this.state.errors.slice();
|
||||
errors.push({title: "Error fetching users", message: res.msg});
|
||||
@ -145,6 +146,7 @@ export default class UserOverview extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ReactTooltip />
|
||||
</>;
|
||||
}
|
||||
|
||||
@ -176,7 +178,20 @@ export default class UserOverview extends React.Component {
|
||||
<td>{user.name}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>{groups}</td>
|
||||
<td>{getPeriodString(user.registered_at)}</td>
|
||||
<td>
|
||||
<span data-effect={"solid"}
|
||||
data-tip={user.registered_at}
|
||||
data-place={"bottom"}>
|
||||
{getPeriodString(user.registered_at)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<Link to={"/admin/user/edit/" + uid} className={"text-reset"}>
|
||||
<Icon icon={"pencil-alt"} data-effect={"solid"}
|
||||
data-tip={"Modify user details & group membership"}
|
||||
data-type={"info"} data-place={"right"}/>
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@ -188,6 +203,7 @@ export default class UserOverview extends React.Component {
|
||||
<td/>
|
||||
<td/>
|
||||
<td/>
|
||||
<td/>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@ -211,7 +227,7 @@ export default class UserOverview extends React.Component {
|
||||
<div className={"card-header border-0"}>
|
||||
<h3 className={"card-title"}>Users</h3>
|
||||
<div className={"card-tools"}>
|
||||
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/users/adduser"} >
|
||||
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/user/add"} >
|
||||
<Icon icon={"plus"}/>
|
||||
</Link>
|
||||
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchUsers()}>
|
||||
@ -227,6 +243,7 @@ export default class UserOverview extends React.Component {
|
||||
<th>Email</th>
|
||||
<th>Groups</th>
|
||||
<th>Registered</th>
|
||||
<th/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -273,7 +290,7 @@ export default class UserOverview extends React.Component {
|
||||
<td>{group.name}</td>
|
||||
<td className={"text-center"}>{group.memberCount}</td>
|
||||
<td>
|
||||
<span className={"badge text-white mr-1"} style={{backgroundColor: group.color}}>
|
||||
<span className={"badge text-white mr-1"} style={{backgroundColor: group.color, fontFamily: "monospace"}}>
|
||||
{group.color}
|
||||
</span>
|
||||
</td>
|
||||
|
Loading…
Reference in New Issue
Block a user