User Fetch Fix + Routes + More Fixes
This commit is contained in:
44
admin/dist/main.js
vendored
44
admin/dist/main.js
vendored
File diff suppressed because one or more lines are too long
7
admin/src/404.js
Normal file
7
admin/src/404.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
export default class View404 extends React.Component {
|
||||
render() {
|
||||
return <b>404 Not found</b>
|
||||
}
|
||||
}
|
||||
@@ -37,4 +37,8 @@ export default class API {
|
||||
async getNotifications() {
|
||||
return this.apiCall("notifications/fetch");
|
||||
}
|
||||
|
||||
async fetchUsers(pageNum = 1) {
|
||||
return this.apiCall("user/fetch", { page: pageNum });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
.page-link { color: #222629; }
|
||||
.page-link:hover { color: black; }
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './include/index.css';
|
||||
import './include/adminlte.min.css';
|
||||
import './include/index.css';
|
||||
import API from './api.js';
|
||||
import Header from './header.js';
|
||||
import Sidebar from './sidebar.js';
|
||||
@@ -9,7 +9,9 @@ import UserOverview from './users.js';
|
||||
import Overview from './overview.js'
|
||||
import Icon from "./icon";
|
||||
import Dialog from "./dialog";
|
||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
||||
import {BrowserRouter as Router, Route} from 'react-router-dom'
|
||||
import View404 from "./404";
|
||||
import Switch from "react-router-dom/es/Switch";
|
||||
|
||||
class AdminDashboard extends React.Component {
|
||||
|
||||
@@ -21,12 +23,14 @@ class AdminDashboard extends React.Component {
|
||||
dialog: { onClose: () => this.hideDialog() },
|
||||
notifications: { }
|
||||
};
|
||||
this.controlObj = {
|
||||
showDialog: this.showDialog.bind(this),
|
||||
api: this.api
|
||||
};
|
||||
}
|
||||
|
||||
onUpdate() {
|
||||
if (this.state.loaded) {
|
||||
this.fetchNotifications();
|
||||
}
|
||||
this.fetchNotifications();
|
||||
}
|
||||
|
||||
showDialog(message, title) {
|
||||
@@ -43,7 +47,7 @@ class AdminDashboard extends React.Component {
|
||||
if (!res.success) {
|
||||
this.showDialog("Error fetching notifications: " + res.msg, "Error fetching notifications");
|
||||
} else {
|
||||
this.setState({...this.state, notifications: res.notifications});
|
||||
this.setState({...this.state, notifications: res.notifications });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -54,7 +58,7 @@ class AdminDashboard extends React.Component {
|
||||
document.location = "/admin";
|
||||
} else {
|
||||
this.fetchNotifications();
|
||||
setInterval(this.onUpdate.bind(this), 60000);
|
||||
setInterval(this.onUpdate.bind(this), 60*1000);
|
||||
this.setState({...this.state, loaded: true});
|
||||
}
|
||||
});
|
||||
@@ -66,29 +70,16 @@ class AdminDashboard extends React.Component {
|
||||
return <b>Loading… <Icon icon={"spinner"} /></b>
|
||||
}
|
||||
|
||||
const controlObj = {
|
||||
notifications: this.state.notifications,
|
||||
showDialog: this.showDialog.bind(this),
|
||||
api: this.api
|
||||
};
|
||||
|
||||
const createView = (view) => {
|
||||
controlObj.currentView = view;
|
||||
switch (view) {
|
||||
case "users":
|
||||
return <UserOverview {...controlObj} />;
|
||||
case "dashboard":
|
||||
default:
|
||||
return <Overview {...controlObj} />;
|
||||
}
|
||||
};
|
||||
|
||||
return <Router>
|
||||
<Header {...controlObj} />
|
||||
<Sidebar {...controlObj} />
|
||||
<Header {...this.controlObj} notifications={this.state.notifications} />
|
||||
<Sidebar {...this.controlObj} notifications={this.state.notifications} />
|
||||
<div className={"content-wrapper p-2"}>
|
||||
<section className={"content"}>
|
||||
<Route path={"/admin/:view"} component={(obj) => createView(obj.match.params.view)}/>
|
||||
<Switch>
|
||||
<Route path={"/admin/dashboard"}><Overview {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>
|
||||
<Route path={"*"}><View404 /></Route>
|
||||
</Switch>
|
||||
<Dialog {...this.state.dialog}/>
|
||||
</section>
|
||||
</div>
|
||||
@@ -97,6 +88,6 @@ class AdminDashboard extends React.Component {
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<AdminDashboard />,
|
||||
document.getElementById('root')
|
||||
<AdminDashboard />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from "react";
|
||||
|
||||
export default class Overview extends React.Component {
|
||||
|
||||
|
||||
render() {
|
||||
return <div>Overview</div>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,180 @@
|
||||
import * as React from "react";
|
||||
import Icon from "./icon";
|
||||
import {Link} from "react-router-dom";
|
||||
import {getPeriodString} from "./global";
|
||||
|
||||
export default class UserOverview extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.parent = {
|
||||
showDialog: props.showDialog || function() {},
|
||||
api: props.api,
|
||||
};
|
||||
this.state = {
|
||||
loaded: false,
|
||||
users: {
|
||||
data: {},
|
||||
page: 1,
|
||||
pageCount: 1
|
||||
},
|
||||
groups: {
|
||||
data: {},
|
||||
page: 1,
|
||||
pageCount: 1
|
||||
},
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
|
||||
fetchUsers(page) {
|
||||
page = page || this.state.users.page;
|
||||
this.setState({ ...this.state, users: { ...this.state.users, data: { }, pageCount: 1 } });
|
||||
this.parent.api.fetchUsers(page).then((res) => {
|
||||
if (res.success) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
loaded: true,
|
||||
users: {
|
||||
data: res.users,
|
||||
pageCount: res.pageCount,
|
||||
page: page
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
...this.state,
|
||||
loaded: true,
|
||||
errors: this.state.errors.slice().push(res.msg)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ ...this.state, loaded: false });
|
||||
this.fetchUsers(1);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>UserOverview</div>
|
||||
|
||||
if (!this.state.loaded) {
|
||||
return <div className={"text-center mt-4"}>
|
||||
<h3>Loading… <Icon icon={"spinner"}/></h3>
|
||||
</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">Users & Groups</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 active">Users</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"content"}>
|
||||
<div className={"content-fluid"}>
|
||||
<div className={"row"}>
|
||||
<div className={"col-lg-6"}>
|
||||
{ this.createUserCard() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
createUserCard() {
|
||||
|
||||
let userRows = [];
|
||||
for (let uid in this.state.users.data) {
|
||||
if (!this.state.users.data.hasOwnProperty(uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let user = this.state.users.data[uid];
|
||||
let groups = [];
|
||||
|
||||
for (let groupId in user.groups) {
|
||||
if (user.groups.hasOwnProperty(groupId)) {
|
||||
let groupName = user.groups[groupId];
|
||||
let color = (groupId === "1" ? "danger" : "secondary");
|
||||
groups.push(<span key={"group-" + groupId} className={"mr-1 badge badge-" + color}>{groupName}</span>);
|
||||
}
|
||||
}
|
||||
|
||||
userRows.push(
|
||||
<tr key={"user-" + uid}>
|
||||
<td>{user.name}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>{groups}</td>
|
||||
<td>{getPeriodString(user.registered_at)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
let pages = [];
|
||||
let previousDisabled = (this.state.users.page === 1 ? " disabled" : "");
|
||||
let nextDisabled = (this.state.users.page >= this.state.users.pageCount ? " disabled" : "");
|
||||
|
||||
for (let i = 1; i <= this.state.users.pageCount; i++) {
|
||||
let active = (this.state.users.page === i ? " active" : "");
|
||||
pages.push(
|
||||
<li key={"page-" + i} className={"page-item" + active}>
|
||||
<a className={"page-link"} href={"#"} onClick={() => this.fetchUsers(i)}>
|
||||
{i}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={"card"}>
|
||||
<div className={"card-header border-0"}>
|
||||
<h3 className={"card-title"}>Users</h3>
|
||||
<div className={"card-tools"}>
|
||||
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchUsers()}>
|
||||
<Icon icon={"sync"}/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"card-body table-responsive p-0"}>
|
||||
<table className={"table table-striped table-valign-middle"}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Groups</th>
|
||||
<th>Registered</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ userRows }
|
||||
</tbody>
|
||||
</table>
|
||||
<nav aria-label={""}>
|
||||
<ul className={"pagination p-2 m-0 justify-content-end"}>
|
||||
<li className={"page-item" + previousDisabled}>
|
||||
<a className={"page-link"} href={"#"} onClick={() => this.fetchUsers(this.state.users.page - 1)}>
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
{ pages }
|
||||
<li className={"page-item" + nextDisabled}>
|
||||
<a className={"page-link"} href={"#"} onClick={() => this.fetchUsers(this.state.users.page + 1)}>
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user