Skip to content

Commit

Permalink
Implemented dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
rmathis committed Apr 27, 2021
1 parent 1d9e320 commit fcc8485
Show file tree
Hide file tree
Showing 20 changed files with 357 additions and 176 deletions.
66 changes: 66 additions & 0 deletions ui/src/app/admin/component/AccessRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import Translate from '../../i18n/components/translate';

export function AccessRequest({ users, roles, onDeleteUser, onChangeUserRole }) {

return (
<>
{(!users || !users.length) ?
<>
<div className="d-flex justify-content-center">
<div className="w-25 alert alert-info m-3">
<p className="text-center">There are no new user requests at this time.</p>
</div>
</div>
</>
:
users.map((user, i) => (
<div key={i}>
<div className="p-3 bg-light border-light rounded m-3">
<div className="row align-items-center">
<div className="col-10">
<div className="row">
<div className="col text-right font-weight-bold">
<Translate value="label.user-id">UserId</Translate>
</div>
<div className="col">{ user.username }</div>
<div className="col text-right font-weight-bold">
<Translate value="label.email">Email</Translate>
</div>
<div className="col">{ user.emailAddress }</div>
</div>
<div className="w-100 my-1"></div>
<div className="row">
<div className="col text-right font-weight-bold" >
<Translate value="label.name">Name</Translate>
</div>
<div className="col">{ user.firstName } { user.lastName }</div>
<label htmlFor={`role-${i}`} className="d-block col text-right font-weight-bold" >
<Translate value="label.role">Role</Translate>
</label>
<div className="col">
<select id={`role-${i}`} name={user.username} value={user.role} className="form-control form-control-sm"
disablevalidation="true" onChange={(event) => onChangeUserRole(user, event.target.value)}>
{roles.map((role, ridx) =>
<option value={role} key={ridx}>{ role }</option>
)}
</select>
</div>
</div>
</div>
<div className="col-2 text-right">
<button className="btn btn-danger btn-sm" onClick={() => onDeleteUser(user.username)}>
<i className="fa fa-trash fa-lg"></i>
&nbsp;
<span >
<Translate value="label.delete-request">Delete Request</Translate>
</span>
</button>
</div>
</div>
</div>
</div>
))}
</>
);
}
63 changes: 63 additions & 0 deletions ui/src/app/admin/component/UserMaintenance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash } from '@fortawesome/free-solid-svg-icons';

import Translate from '../../i18n/components/translate';
import { useCurrentUser } from '../../core/user/UserContext';

export default function UserMaintenance({ users, roles, onDeleteUser, onChangeUserRole }) {

const currentUser = useCurrentUser();

return (
<div className="table-responsive mt-3 provider-list">
<table className="table table-striped w-100 table-hover">
<thead>
<tr>
<th scope="col"><Translate value="label.user-id">UserId</Translate></th>
<th scope="col" ><Translate value="label.name">Name</Translate></th>
<th scope="col"><Translate value="label.email">Email</Translate></th>
<th scope="col" ><Translate value="label.role">Role</Translate></th>
<th scope="col"><Translate value="label.delete">Delete?</Translate></th>
</tr>
</thead>
<tbody>
{users.map((user, idx) =>
<tr key={idx}>
<th>{user.username}</th>
<td>{user.firstName} {user.lastName}</td>
<td>{user.emailAddress}</td>
<td>
<label htmlFor={`role-${user.username}`} className="sr-only"><Translate value="action.user-role">User role</Translate></label>
<select
id={`role-${user.username}`}
name={`role-${user.username}`}
model="user.role"
className="form-control"
onChange={(event) => onChangeUserRole(user, event.target.value)}
value={user.role}
disabled={currentUser.username === user.username}
disablevalidation="true">
{roles.map((role, ridx) => (
<option key={role} value={role}>{role}</option>
))}
</select>
</td>
<td>
{currentUser.username !== user.username &&
<button className="btn btn-link text-danger" onClick={() => onDeleteUser(user.username)}>
<span className="sr-only">
<Translate value="label.delete-user">Delete User</Translate>
</span>
<FontAwesomeIcon icon={faTrash} />
</button>
}
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
21 changes: 21 additions & 0 deletions ui/src/app/admin/container/SourcesActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import SourceList from '../../metadata/domain/source/component/SourceList';
import { useMetadataEntity } from '../../metadata/hooks/api';

export function SourcesActions ({sources, reloadSources}) {

const { del, response } = useMetadataEntity('source', {
cachePolicy: 'no-cache'
});

async function deleteSource(id) {
await del(`/${id}`);
if (response.ok) {
reloadSources();
}
}

return (
<SourceList entities={sources} onDelete={ deleteSource } />
);
}
14 changes: 14 additions & 0 deletions ui/src/app/admin/container/UserActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { AccessRequest } from '../../admin/component/AccessRequest';
import UserManagement from '../../admin/container/UserManagement';

export function UserActions({ users, reloadUsers }) {
return (
<UserManagement users={users} reload={reloadUsers}>
{(u, roles, onChangeUserRole, onDeleteUser) =>
<AccessRequest users={u} roles={roles} onChangeUserRole={onChangeUserRole} onDeleteUser={onDeleteUser} />}
</UserManagement>
);
}

export default UserActions;
118 changes: 38 additions & 80 deletions ui/src/app/admin/container/UserManagement.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import React from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import useFetch from 'use-http';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';

import Translate from '../../i18n/components/translate';
import { useCurrentUser } from '../../core/user/UserContext';
import API_BASE_PATH from '../../App.constant';

export default function UserManagement({ users, roles, onDelete, onSetRole }) {
export default function UserManagement({ users, children, reload }) {

const setUserRole = (user, role) => onSetRole(user, role);
const [roles, setRoles] = React.useState([]);

const currentUser = useCurrentUser();
const { get, patch, del, response } = useFetch(`${API_BASE_PATH}`, {});

async function loadRoles() {
const roles = await get('/supportedRoles')
if (response.ok) {
setRoles(roles);
}
}

async function setUserRoleRequest(user, role) {
await patch(`/admin/users/${user.username}`, {
...user,
role
});
if (response.ok && reload) {
reload();
}
}

async function deleteUserRequest(id) {
await del(`/admin/users/${id}`);
if (response.ok && reload) {
reload();
}
}

/*eslint-disable react-hooks/exhaustive-deps*/
React.useEffect(() => {
loadRoles();
}, []);

const [modal, setModal] = React.useState(false);

Expand All @@ -20,57 +50,13 @@ export default function UserManagement({ users, roles, onDelete, onSetRole }) {
const [deleting, setDeleting] = React.useState(null);

const deleteUser = (id) => {
onDelete(deleting);
deleteUserRequest(deleting);
setDeleting(null);
}

return (
<div className="table-responsive mt-3 provider-list">
<table className="table table-striped w-100 table-hover">
<thead>
<tr>
<th scope="col"><Translate value="label.user-id">UserId</Translate></th>
<th scope="col" ><Translate value="label.name">Name</Translate></th>
<th scope="col"><Translate value="label.email">Email</Translate></th>
<th scope="col" ><Translate value="label.role">Role</Translate></th>
<th scope="col"><Translate value="label.delete">Delete?</Translate></th>
</tr>
</thead>
<tbody>
{users.map((user, idx) =>
<tr key={idx}>
<th>{ user.username }</th>
<td>{ user.firstName } { user.lastName }</td>
<td>{ user.emailAddress }</td>
<td>
<label htmlFor={`role-${user.username}`} className="sr-only"><Translate value="action.user-role">User role</Translate></label>
<select
id={`role-${user.username}`}
name={`role-${user.username}`}
model="user.role"
className="form-control"
onChange={(event) => setUserRole(user, event.target.value) }
disabled={currentUser.username === user.username}>
{ roles.map((role, ridx) => (
<option key={role} value={role}>{ role }</option>
))}

</select>
</td>
<td>
{currentUser.username !== user.username &&
<button className="btn btn-link text-danger" onClick={() => setDeleting(user.username) }>
<span className="sr-only">
<Translate value="label.delete-user">Delete User</Translate>
</span>
<FontAwesomeIcon icon={faTrash} />
</button>
}
</td>
</tr>
)}
</tbody>
</table>
<div className="user-management">
{children(users, roles, setUserRoleRequest, (id) => setDeleting(id))}
<Modal isOpen={!!deleting} toggle={() => setDeleting(null)}>
<ModalHeader toggle={toggle}><Translate value="message.delete-user-title">Delete User?</Translate></ModalHeader>
<ModalBody className="d-flex align-content-center">
Expand All @@ -91,31 +77,3 @@ export default function UserManagement({ users, roles, onDelete, onSetRole }) {
</div>
);
}

/*
<tr *ngFor="let user of users$ | async">
<th>{{ user.username }}</th>
<td>{{ user.firstName }} {{ user.lastName }}</td>
<td>{{ user.emailAddress }}</td>
<td>
<label [for]="'role-' + user.username"
className="sr-only"><translate-i18n key="action.user-role">User role</translate-i18n></label>
<select
[id]="'role-' + user.username"
[name]="'role-' + user.username"
[ngModel]="user.role"
className="form-control"
(change)="setUserRole(user, $event.target.value)"
[disabled]="currentUser.username === user.username">
<option *ngFor="let role of roles$ | async" [value]="role">{{ role }}</option>
</select>
</td>
<td>
<button className="btn btn-link" (click)="deleteUser(user.username)" *ngIf="!(currentUser.username === user.username)">
<span className="sr-only">
<translate-i18n key="label.delete-user">Delete User</translate-i18n>
</span>
<i className="fa fa-trash fa-lg text-danger"></i>
</button>
</td>
</tr>*/
25 changes: 25 additions & 0 deletions ui/src/app/core/components/AdminRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { Redirect, Route } from 'react-router';

import { useIsAdmin } from '../user/UserContext';

export function AdminRoute({ children, ...rest }) {
const isAdmin = useIsAdmin();
return (
<Route
{...rest}
render={({ location }) =>
isAdmin ? (
children
) : (
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
)
}
/>
);
}
2 changes: 1 addition & 1 deletion ui/src/app/core/hooks/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
var r = Math.random() * 16 | 0, v = c === 'x' ? r : ((r & 0x3) | 0x8);
return v.toString(16);
});
}
Expand Down
7 changes: 6 additions & 1 deletion ui/src/app/core/user/UserContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,10 @@ function useCurrentUser() {
return context;
}

function useIsAdmin() {
const user = useCurrentUser();
return user.role === 'ROLE_ADMIN';
}


export { UserContext, UserProvider, Consumer as UserConsumer, useCurrentUser };
export { UserContext, UserProvider, Consumer as UserConsumer, useCurrentUser, useIsAdmin };
4 changes: 2 additions & 2 deletions ui/src/app/core/utility/get_cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ export function get_cookie (cname) {
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
Expand Down
Loading

0 comments on commit fcc8485

Please sign in to comment.