Skip to content

Commit 34138fa

Browse files
committed
feat(jsbattle-admin): display details of user
1 parent 2f70485 commit 34138fa

File tree

14 files changed

+458
-30
lines changed

14 files changed

+458
-30
lines changed

packages/jsbattle-admin/src/actions/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,7 @@ export const getSystemInfo = () => {
6363
export const getDashboardInfo = () => {
6464
return fetchFromApi(`/api/admin/dashboard` , "DASHBOARD_INFO");
6565
};
66+
67+
export const getUserDetails = (id) => {
68+
return fetchFromApi(`/api/admin/users/${id}/summary` , "USER_VIEW");
69+
};

packages/jsbattle-admin/src/containers/App.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
1414
import {Route, Redirect, HashRouter as Router} from 'react-router-dom';
1515
import UserList from "./UserList.js";
16+
import UserView from "./UserView.js";
1617
import SystemView from "./SystemView.js";
1718
import ScriptList from "./ScriptList.js";
1819
import SessionList from "./SessionList.js";
@@ -44,7 +45,8 @@ class App extends Component {
4445
<Redirect to="/dashboard" />
4546
</Route>
4647
<Route path="/dashboard" component={Dashboard} />
47-
<Route path="/users" component={UserList} />
48+
<Route path="/users/:id" component={UserView} />
49+
<Route path="/users" exact component={UserList} />
4850
<Route path="/sessions" component={SessionList} />
4951
<Route path="/scripts" component={ScriptList} />
5052
<Route path="/battles" component={BattleList} />

packages/jsbattle-admin/src/containers/BattleList.js

+7-13
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import Loading from '../components/Loading.js';
99
import {connect} from 'react-redux';
1010
import {getBattleList} from '../actions';
1111

12-
function vsFormatter(value) {
12+
function vsFormatter(value, row) {
1313
let parts = new RegExp(/(.+)\/(.+) vs (.+)\/(.+)/).exec(value);
14-
15-
return <span style={{color: "#333"}}><strong style={{color: "#000"}}>{parts[1]}</strong>/{parts[2]} <span style={{color: "#888"}}>vs</span> <strong style={{color: "#000"}}>{parts[3]}</strong>/{parts[4]}</span>;
14+
let baseUrl = window.location.href.replace(/(.*)#.*/, '$1').replace(/(.*)(admin\/?)/, '$1');
15+
let url = `${baseUrl}#/league/replay/${row.id}`;
16+
return <a href={url}>
17+
<span><strong>{parts[1]}</strong>/{parts[2]} <span>vs</span> <strong>{parts[3]}</strong>/{parts[4]}</span>
18+
</a>;
1619
}
1720

1821
class BattleList extends Component {
@@ -38,20 +41,11 @@ class BattleList extends Component {
3841
</Col>
3942
<Col lg={9} xl={10} style={{paddingTop: '1em'}}>
4043
<Breadcrumb>
41-
<Breadcrumb.Item href="/#/dashboard">Dashboard</Breadcrumb.Item>
44+
<Breadcrumb.Item href="#/dashboard">Dashboard</Breadcrumb.Item>
4245
<Breadcrumb.Item active>Battles</Breadcrumb.Item>
4346
</Breadcrumb>
4447
<SmartTable
4548
columns={[
46-
{
47-
name: 'ID',
48-
field: 'id',
49-
format: (value) => {
50-
let baseUrl = window.location.href.replace(/(.*)#.*/, '$1').replace(/(.*)(admin\/?)/, '$1');
51-
let url = `${baseUrl}#/league/replay/${value}`;
52-
return <a href={url}>{value}</a>;
53-
}
54-
},
5549
{name: 'Description', field: 'description', format: vsFormatter},
5650
{name: 'Create Date', field: 'createdAt', format: 'datetime'},
5751
{name: 'Expire Date', field: 'expiresAt', format: 'datetime'}

packages/jsbattle-admin/src/containers/LeagueList.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class LeagueList extends Component {
3232
</Col>
3333
<Col lg={9} xl={10} style={{paddingTop: '1em'}}>
3434
<Breadcrumb>
35-
<Breadcrumb.Item href="/#/dashboard">Dashboard</Breadcrumb.Item>
35+
<Breadcrumb.Item href="#/dashboard">Dashboard</Breadcrumb.Item>
3636
<Breadcrumb.Item active>League</Breadcrumb.Item>
3737
</Breadcrumb>
3838
<SmartTable

packages/jsbattle-admin/src/containers/ScriptList.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class ScriptList extends Component {
3333
</Col>
3434
<Col lg={9} xl={10} style={{paddingTop: '1em'}}>
3535
<Breadcrumb>
36-
<Breadcrumb.Item href="/#/dashboard">Dashboard</Breadcrumb.Item>
36+
<Breadcrumb.Item href="#/dashboard">Dashboard</Breadcrumb.Item>
3737
<Breadcrumb.Item active>Scripts</Breadcrumb.Item>
3838
</Breadcrumb>
3939
<SmartTable

packages/jsbattle-admin/src/containers/SessionList.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import Loading from '../components/Loading.js';
99
import {connect} from 'react-redux';
1010
import {getSessionList} from '../actions';
1111

12+
function userFormatter(value, row) {
13+
console.log(row);
14+
let baseUrl = window.location.href.replace(/(.*)#.*/, '$1');
15+
let url = `${baseUrl}#/users/${row.userId}`;
16+
return <a href={url}>{value}</a>;
17+
}
18+
1219
class SessionList extends Component {
1320

1421
constructor(props) {
@@ -32,12 +39,12 @@ class SessionList extends Component {
3239
</Col>
3340
<Col lg={9} xl={10} style={{paddingTop: '1em'}}>
3441
<Breadcrumb>
35-
<Breadcrumb.Item href="/#/dashboard">Dashboard</Breadcrumb.Item>
42+
<Breadcrumb.Item href="#/dashboard">Dashboard</Breadcrumb.Item>
3643
<Breadcrumb.Item active>Sessions</Breadcrumb.Item>
3744
</Breadcrumb>
3845
<SmartTable
3946
columns={[
40-
{name: 'User', field: 'username'},
47+
{name: 'User', field: 'username', format: userFormatter},
4148
{name: 'Role', field: 'role', format: (v) => v == 'admin' ? <span className="badge badge-danger">{v}</span> : <span className="badge badge-info">{v}</span>},
4249
{name: 'Last Activity', field: 'lastAction', format: [
4350
(value) => new Date().getTime() - new Date(value.timestamp).getTime(),

packages/jsbattle-admin/src/containers/UserList.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import SmartTable from '../components/SmartTable.js';
99
import {connect} from 'react-redux';
1010
import {getUserList} from '../actions';
1111

12+
function userFormatter(value, row) {
13+
let baseUrl = window.location.href.replace(/(.*)#.*/, '$1');
14+
let url = `${baseUrl}#/users/${row.id}`;
15+
return <a href={url}>{value}</a>;
16+
}
17+
1218
class UserList extends Component {
1319

1420
constructor(props) {
@@ -32,12 +38,12 @@ class UserList extends Component {
3238
</Col>
3339
<Col lg={9} xl={10} style={{paddingTop: '1em'}}>
3440
<Breadcrumb>
35-
<Breadcrumb.Item href="/#/dashboard">Dashboard</Breadcrumb.Item>
41+
<Breadcrumb.Item href="#/dashboard">Dashboard</Breadcrumb.Item>
3642
<Breadcrumb.Item active>Users</Breadcrumb.Item>
3743
</Breadcrumb>
3844
<SmartTable
3945
columns={[
40-
{name: 'User Name', field: 'username'},
46+
{name: 'User Name', field: 'username', format: userFormatter},
4147
{name: 'Display Name', field: 'displayName'},
4248
{name: 'Auth Provider', field: 'provider'},
4349
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React, {Component} from "react";
2+
import Container from 'react-bootstrap/Container';
3+
import Row from 'react-bootstrap/Row';
4+
import Col from 'react-bootstrap/Col';
5+
import Breadcrumb from 'react-bootstrap/Breadcrumb';
6+
import SideMenu from '../components/SideMenu.js';
7+
import Loading from '../components/Loading.js';
8+
import {connect} from 'react-redux';
9+
import {getUserDetails} from '../actions';
10+
import {
11+
faUser,
12+
faChartBar
13+
} from '@fortawesome/free-solid-svg-icons';
14+
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
15+
16+
class UserView extends Component {
17+
18+
constructor(props) {
19+
super(props);
20+
}
21+
22+
componentDidMount() {
23+
this.props.getUserDetails(this.props.match.params.id);
24+
}
25+
26+
render() {
27+
if(this.props.isLoading) {
28+
return<Loading />;
29+
}
30+
if(!this.props.user) {
31+
return <span>User not found</span>;
32+
}
33+
return (
34+
<div>
35+
<Container fluid>
36+
<Row>
37+
<Col lg={3} xl={2} style={{backgroundColor: '#f7f7f7', borderRight: '1px solid #ececec'}} >
38+
<SideMenu />
39+
</Col>
40+
<Col lg={9} xl={10} style={{paddingTop: '1em'}}>
41+
<Breadcrumb>
42+
<Breadcrumb.Item href="#/dashboard">Dashboard</Breadcrumb.Item>
43+
<Breadcrumb.Item href="#/users">Users</Breadcrumb.Item>
44+
<Breadcrumb.Item active>{this.props.user.account.displayName} ({this.props.user.account.username})</Breadcrumb.Item>
45+
</Breadcrumb>
46+
<div className="card">
47+
<div className="card-header">
48+
<FontAwesomeIcon size="lg" icon={faUser} /> &nbsp; <strong>Account</strong>
49+
</div>
50+
<ul className="list-group list-group-flush">
51+
<li className="list-group-item d-flex justify-content-between align-items-center">
52+
User name <span className="badge badge-primary badge-pill">{this.props.user.account.username}</span>
53+
</li>
54+
<li className="list-group-item d-flex justify-content-between align-items-center">
55+
Display name <span className="badge badge-primary badge-pill">{this.props.user.account.displayName}</span>
56+
</li>
57+
<li className="list-group-item d-flex justify-content-between align-items-center">
58+
Provider <span className="badge badge-primary badge-pill">{this.props.user.account.provider}</span>
59+
</li>
60+
<li className="list-group-item d-flex justify-content-between align-items-center">
61+
Email <span className="badge badge-primary badge-pill">{this.props.user.account.email}</span>
62+
</li>
63+
<li className="list-group-item d-flex justify-content-between align-items-center">
64+
Status <span className="badge badge-primary badge-pill">{this.props.user.account.registered ? 'Registered' : 'Unregistered'}</span>
65+
</li>
66+
<li className="list-group-item d-flex justify-content-between align-items-center">
67+
Role <span className="badge badge-primary badge-pill">{this.props.user.account.role}</span>
68+
</li>
69+
<li className="list-group-item d-flex justify-content-between align-items-center">
70+
Joined at <span className="badge badge-primary badge-pill">{new Date(this.props.user.account.createdAt).toLocaleString()}</span>
71+
</li>
72+
<li className="list-group-item d-flex justify-content-between align-items-center">
73+
Last login <span className="badge badge-primary badge-pill">{new Date(this.props.user.account.lastLoginAt).toLocaleString()}</span>
74+
</li>
75+
</ul>
76+
</div>
77+
<hr />
78+
<div className="card">
79+
<div className="card-header">
80+
<FontAwesomeIcon size="lg" icon={faChartBar} /> &nbsp; <strong>Stats</strong>
81+
</div>
82+
<ul className="list-group list-group-flush">
83+
<li className="list-group-item d-flex justify-content-between align-items-center">
84+
Challenges Completed <span className="badge badge-primary badge-pill">{this.props.user.challenges.length}</span>
85+
</li>
86+
<li className="list-group-item d-flex justify-content-between align-items-center">
87+
Scripts stored <span className="badge badge-primary badge-pill">{this.props.user.scripts.length}</span>
88+
</li>
89+
<li className="list-group-item d-flex justify-content-between align-items-center">
90+
Battles stored <span className="badge badge-primary badge-pill">{this.props.user.battles.length}</span>
91+
</li>
92+
</ul>
93+
</div>
94+
</Col>
95+
</Row>
96+
</Container>
97+
</div>
98+
);
99+
}
100+
}
101+
102+
// eslint-disable-next-line no-unused-vars
103+
const mapStateToProps = (state) => ({
104+
user: state.users.selected,
105+
isLoading: state.loading.USER_VIEW
106+
});
107+
108+
const mapDispatchToProps = (dispatch) => ({
109+
getUserDetails: (id) => dispatch(getUserDetails(id))
110+
});
111+
export default connect(
112+
mapStateToProps,
113+
mapDispatchToProps
114+
)(UserView);

packages/jsbattle-admin/src/reducers/usersReducer.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const initState = {
2-
page: null
2+
page: null,
3+
selected: null
34
};
45

56
const usersReducer = (state, action) => {
@@ -11,6 +12,8 @@ const usersReducer = (state, action) => {
1112
switch (action.type) {
1213
case 'USER_LIST_SUCCESS':
1314
return {...state, page: action.payload};
15+
case 'USER_VIEW_SUCCESS':
16+
return {...state, selected: action.payload};
1417
default:
1518
return state;
1619
}

packages/jsbattle-mockserver/app/MockServer.js

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class MockServer {
132132
"/api/user/league": "/user,league",
133133
"/api/user/league/submission": "/user,league,submission",
134134
"/api/user/league/replay/:id": "/user,league,replay/:id",
135+
"/api/admin/users/:id/:a": "/admin,users,:id,:a",
135136
"/api/:a/:b": "/:a,:b",
136137
"/api/:a/:b/:c": "/:a,:b/:c",
137138
"/api/:a/:b/:c/:d": "/:a,:b/:c/:d",

packages/jsbattle-mockserver/app/init_db.json

+89
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,95 @@
163163
"lastLoginAt": "2020-01-01T00:00:00.000Z"
164164
}
165165
],
166+
"admin,users,8si8Su024sj2cZwo0,summary": {
167+
"account": {
168+
"id": "uqnGXXnSKTs6Xl5h",
169+
"username": "alpha",
170+
"displayName": "Alpha Centauri",
171+
"provider": "mock",
172+
"extUserId": "mock_01",
173+
"email": "[email protected]",
174+
"registered": true,
175+
"role": "admin",
176+
"createdAt": "2020-06-25T11:59:37.453Z",
177+
"lastLoginAt": "2020-06-25T11:59:39.119Z"
178+
},
179+
"scripts": [
180+
{
181+
"id": "1TFcBHuTSNuKVZyH",
182+
"scriptName": "sharma",
183+
"createdAt": "2020-06-25T11:59:59.872Z",
184+
"modifiedAt": "2020-06-25T11:59:59.872Z",
185+
"hash": "90b359ff9cbe540a42c200e096d0a59e"
186+
},
187+
{
188+
"id": "FIoRSZ4A7jzzjVkr",
189+
"scriptName": "venom",
190+
"createdAt": "2020-06-25T11:59:51.087Z",
191+
"modifiedAt": "2020-06-25T11:59:51.087Z",
192+
"hash": "90b359ff9cbe540a42c200e096d0a59e"
193+
}
194+
],
195+
"league": {
196+
"id": "qTYS3Rq5TYCsVIq1",
197+
"scriptId": "FIoRSZ4A7jzzjVkr",
198+
"scriptName": "venom",
199+
"joinedAt": "2020-06-25T11:59:57.311Z",
200+
"fights_total": 2,
201+
"fights_win": 0,
202+
"fights_lose": 2,
203+
"fights_error": 0,
204+
"score": 0
205+
},
206+
"battles": [
207+
{
208+
"id": "H60MduWgZHatFE14",
209+
"meta": [
210+
{
211+
"id": "qTYS3Rq5TYCsVIq1",
212+
"name": "mock/venom",
213+
"battleScore": 20,
214+
"winner": false
215+
},
216+
{
217+
"id": "BtMRNA9fNoTmwlcv",
218+
"name": "jsbattle/jamro",
219+
"battleScore": 215.39000000000001,
220+
"winner": true
221+
}
222+
],
223+
"description": "mock/venom vs jsbattle/jamro",
224+
"createdAt": "2020-06-25T12:00:18.565Z",
225+
"expiresAt": "2020-06-28T12:00:18.565Z"
226+
},
227+
{
228+
"id": "kdmOGcB6yBuGllca",
229+
"meta": [
230+
{
231+
"id": "qTYS3Rq5TYCsVIq1",
232+
"name": "mock/venom",
233+
"battleScore": 0,
234+
"winner": false
235+
},
236+
{
237+
"id": "J9Ws7fDPv0JsjPsK",
238+
"name": "jsbattle/crawler",
239+
"battleScore": 41.150000000000006,
240+
"winner": true
241+
}
242+
],
243+
"description": "mock/venom vs jsbattle/crawler",
244+
"createdAt": "2020-06-25T12:00:03.599Z",
245+
"expiresAt": "2020-06-28T12:00:03.599Z"
246+
}
247+
],
248+
"challenges": [
249+
{
250+
"challengeId": "challenge-8UCUaNvC",
251+
"modifiedAt": "2020-06-25T11:59:41.729Z"
252+
}
253+
]
254+
},
166255
"admin,sessions": [
167256
{
168257
"userId": "Ek4kFhe50m6RWLic",

packages/jsbattle-server/app/services/ApiGateway.service.js

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class ApiGatewayService extends Service {
5252
aliases: {
5353
"PATCH users/:id": "userStore.update",
5454
"GET users": "userStore.list",
55+
"GET users/:id/summary": "stats.getUserSummary",
5556
"GET sessions": "activityMonitor.listActiveSessions",
5657
"PATCH scripts/:id": "scriptStore.update",
5758
"GET scripts": "scriptStore.list",

0 commit comments

Comments
 (0)