User Stuff
This commit is contained in:
parent
a4504d8336
commit
7ac6b4d245
@ -126,7 +126,7 @@ namespace Api\User {
|
|||||||
|
|
||||||
parent::__construct($user, $externalCall, array(
|
parent::__construct($user, $externalCall, array(
|
||||||
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
|
'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;
|
$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 {
|
class Info extends UserAPI {
|
||||||
|
|
||||||
public function __construct($user, $externalCall = false) {
|
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");
|
return this.apiCall("notifications/fetch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUser(id) {
|
||||||
|
return this.apiCall("user/get", { id: id });
|
||||||
|
}
|
||||||
|
|
||||||
async fetchUsers(pageNum = 1, count = 20) {
|
async fetchUsers(pageNum = 1, count = 20) {
|
||||||
return this.apiCall("user/fetch", { page: pageNum, count: count });
|
return this.apiCall("user/fetch", { page: pageNum, count: count });
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import React from "react";
|
|||||||
|
|
||||||
export default function Alert(props) {
|
export default function Alert(props) {
|
||||||
|
|
||||||
const onClose = props.onClose || function() { };
|
const onClose = props.onClose || null;
|
||||||
const title = props.title || "Untitled Alert";
|
const title = props.title || "Untitled Alert";
|
||||||
const message = props.message || "Alert message";
|
const message = props.message || "Alert message";
|
||||||
const type = props.type || "danger";
|
const type = props.type || "danger";
|
||||||
@ -18,7 +18,7 @@ export default function Alert(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"alert alert-" + type + " alert-dismissible"}>
|
<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>
|
<h5><Icon icon={icon} className={"icon"} /> {title}</h5>
|
||||||
{message}
|
{message}
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,7 @@ import Logs from "./views/logs";
|
|||||||
import PageOverview from "./views/pages";
|
import PageOverview from "./views/pages";
|
||||||
import HelpPage from "./views/help";
|
import HelpPage from "./views/help";
|
||||||
import Footer from "./footer";
|
import Footer from "./footer";
|
||||||
|
import EditUser from "./views/edituser";
|
||||||
|
|
||||||
class AdminDashboard extends React.Component {
|
class AdminDashboard extends React.Component {
|
||||||
|
|
||||||
@ -83,7 +84,11 @@ class AdminDashboard extends React.Component {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Route path={"/admin/dashboard"}><Overview {...this.controlObj} notifications={this.state.notifications} /></Route>
|
<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"}><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/logs"}><Logs {...this.controlObj} /></Route>
|
||||||
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
||||||
<Route path={"/admin/help"}><HelpPage {...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 {Link} from "react-router-dom";
|
||||||
import {getPeriodString} from "../global";
|
import {getPeriodString} from "../global";
|
||||||
import Alert from "../elements/alert";
|
import Alert from "../elements/alert";
|
||||||
|
import ReactTooltip from "react-tooltip";
|
||||||
|
|
||||||
const TABLE_SIZE = 10;
|
const TABLE_SIZE = 10;
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ export default class UserOverview extends React.Component {
|
|||||||
totalCount: res.totalCount,
|
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 {
|
} else {
|
||||||
let errors = this.state.errors.slice();
|
let errors = this.state.errors.slice();
|
||||||
errors.push({title: "Error fetching users", message: res.msg});
|
errors.push({title: "Error fetching users", message: res.msg});
|
||||||
@ -145,6 +146,7 @@ export default class UserOverview extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ReactTooltip />
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +178,20 @@ export default class UserOverview extends React.Component {
|
|||||||
<td>{user.name}</td>
|
<td>{user.name}</td>
|
||||||
<td>{user.email}</td>
|
<td>{user.email}</td>
|
||||||
<td>{groups}</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>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -188,6 +203,7 @@ export default class UserOverview extends React.Component {
|
|||||||
<td/>
|
<td/>
|
||||||
<td/>
|
<td/>
|
||||||
<td/>
|
<td/>
|
||||||
|
<td/>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -211,7 +227,7 @@ 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"} >
|
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/user/add"} >
|
||||||
<Icon icon={"plus"}/>
|
<Icon icon={"plus"}/>
|
||||||
</Link>
|
</Link>
|
||||||
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchUsers()}>
|
<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>Email</th>
|
||||||
<th>Groups</th>
|
<th>Groups</th>
|
||||||
<th>Registered</th>
|
<th>Registered</th>
|
||||||
|
<th/>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -273,7 +290,7 @@ export default class UserOverview extends React.Component {
|
|||||||
<td>{group.name}</td>
|
<td>{group.name}</td>
|
||||||
<td className={"text-center"}>{group.memberCount}</td>
|
<td className={"text-center"}>{group.memberCount}</td>
|
||||||
<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}
|
{group.color}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user