Skip to content

Commit

Permalink
Implementing dashboards
Browse files Browse the repository at this point in the history
  • Loading branch information
rmathis committed Apr 23, 2021
1 parent 4ec637a commit e64a180
Show file tree
Hide file tree
Showing 16 changed files with 609 additions and 33 deletions.
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"bootstrap": "^4.6.0",
"date-fns": "^2.21.1",
"http-proxy-middleware": "^1.2.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",
Expand Down
31 changes: 17 additions & 14 deletions ui/src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,27 @@ import Footer from './core/components/Footer';

import Dashboard from './dashboard/container/Dashboard';
import Header from './core/components/Header';
import { UserProvider } from './core/user/UserContext';

function App() {
return (
<div className="shibui">
<I18nProvider>
<Router>
<Header />
<main className="pad-content">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
</Switch>
</main>
<Footer />
</Router>
</I18nProvider>
<UserProvider>
<I18nProvider>
<Router>
<Header />
<main className="pad-content">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
</Switch>
</main>
<Footer />
</Router>
</I18nProvider>
</UserProvider>
</div>
);
}
Expand Down
121 changes: 121 additions & 0 deletions ui/src/app/admin/container/UserManagement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';

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

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

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

const setUserRole = (user, role) => onSetRole(user, role);

const currentUser = useCurrentUser();

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

const toggle = () => setModal(!modal);

const [deleting, setDeleting] = React.useState(null);

const deleteUser = (id) => {
onDelete(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>
<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">
<FontAwesomeIcon className="text-danger mr-4" size="4x" icon={faExclamationTriangle} />
<p className="text-danger font-weight-bold mb-0">
<Translate value="message.delete-user-body">You are requesting to delete a user. If you complete this process the user will be removed. This cannot be undone. Do you wish to continue?</Translate>
</p>
</ModalBody>
<ModalFooter>
<Button color="danger" onClick={() => deleteUser(deleting)}>
<Translate value="action.delete">Delete</Translate>
</Button>{' '}
<Button color="secondary" onClick={() => setDeleting(null)}>
<Translate value="action.cancel">Cancel</Translate>
</Button>
</ModalFooter>
</Modal>
</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"
class="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"
class="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 class="btn btn-link" (click)="deleteUser(user.username)" *ngIf="!(currentUser.username === user.username)">
<span class="sr-only">
<translate-i18n key="label.delete-user">Delete User</translate-i18n>
</span>
<i class="fa fa-trash fa-lg text-danger"></i>
</button>
</td>
</tr>*/
8 changes: 8 additions & 0 deletions ui/src/app/core/components/FormattedDate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import { format } from 'date-fns';

export default function FormattedDate ({ date }) {
const formatted = React.useMemo(() => format(new Date(date), 'MMM Lo, Y'), [date]);

return (<>{ formatted }</>);
}
38 changes: 38 additions & 0 deletions ui/src/app/core/user/UserContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import useFetch from 'use-http';
import API_BASE_PATH from '../../App.constant';

const UserContext = React.createContext();

const { Provider, Consumer } = UserContext;

const path = '/admin/users/current';

/*eslint-disable react-hooks/exhaustive-deps*/
function UserProvider({ children }) {

const { get, response } = useFetch(`${API_BASE_PATH}`, {
cacheLife: 10000,
cachePolicy: 'cache-first'
});

React.useEffect(() => { loadUser() }, []);

async function loadUser() {
const user = await get(`${path}`);
if (response.ok) setUser(user);
}

const [user, setUser] = React.useState({});
return (
<Provider value={user}>{children}</Provider>
);
}

function useCurrentUser() {
const context = React.useContext(UserContext);
return context;
}


export { UserContext, UserProvider, Consumer as UserConsumer, useCurrentUser };
43 changes: 43 additions & 0 deletions ui/src/app/dashboard/container/ActionsTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import useFetch from 'use-http';
import UserManagement from '../../admin/container/UserManagement';
import API_BASE_PATH from '../../App.constant';

import Translate from '../../i18n/components/translate';

export function ActionsTab() {

return (
<>
<section className="section">
<div className="section-body border border-top-0 border-primary">
<div className="section-header bg-primary p-2 text-light">
<div className="row justify-content-between">
<div className="col-12">
<span className="lead"><Translate value="label.enable-metadata-sources">Enable Metadata Sources</Translate></span>
</div>
</div>
</div>
<div className="p-3">
{/*<enable-metadata></enable-metadata>*/}
</div>
</div>
</section>
<section className="section">
<div className="section-body border border-top-0 border-primary">
<div className="section-header bg-primary p-2 text-light">
<div className="row justify-content-between">
<div className="col-12">
<span className="lead"><Translate value="label.user-access-request">User Access Request</Translate></span>
</div>
</div>
</div>
{/*<access-request-component></access-request-component>*/}
</div>
</section>
</>

);
}

export default ActionsTab;
70 changes: 70 additions & 0 deletions ui/src/app/dashboard/container/AdminTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import useFetch from 'use-http';
import UserManagement from '../../admin/container/UserManagement';
import API_BASE_PATH from '../../App.constant';

import Translate from '../../i18n/components/translate';

export function AdminTab () {

const [users, setUsers] = React.useState([]);

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

async function loadUsers() {
const users = await get('/admin/users')
if (response.ok) {
setUsers(users);
}
}
const [roles, setRoles] = React.useState([]);

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

async function setUserRole (user, role) {
const update = await patch(`/admin/users/${user.username}`, {
...user,
role
});
if (response.ok) {
loadUsers();
}
}

async function deleteUser(id) {
const removal = await del(`/admin/users/${id}`);
if (response.ok) {
loadUsers();
}
}

React.useEffect(() => {
loadUsers();
loadRoles();
}, []);


return (
<section className="section">
<div className="section-body border border-top-0 border-primary">
<div className="section-header bg-primary p-2 text-light">
<div className="row justify-content-between">
<div className="col-12">
<span className="lead"><Translate value="label.user-maintenance">User Maintenance</Translate></span>
</div>
</div>
</div>
<div className="p-3">
<UserManagement users={users} roles={roles} onDelete={deleteUser} onSetRole={setUserRole} />
</div>
</div>
</section>
);
}

export default AdminTab;
Loading

0 comments on commit e64a180

Please sign in to comment.