From fcc84851ed824a2b3b0240563d201988aefcf873 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 27 Apr 2021 14:04:14 -0700 Subject: [PATCH] Implemented dashboard --- ui/src/app/admin/component/AccessRequest.js | 66 ++++++++++ ui/src/app/admin/component/UserMaintenance.js | 63 ++++++++++ ui/src/app/admin/container/SourcesActions.js | 21 ++++ ui/src/app/admin/container/UserActions.js | 14 +++ ui/src/app/admin/container/UserManagement.js | 118 ++++++------------ ui/src/app/core/components/AdminRoute.js | 25 ++++ ui/src/app/core/hooks/utils.js | 2 +- ui/src/app/core/user/UserContext.js | 7 +- ui/src/app/core/utility/get_cookie.js | 4 +- ui/src/app/dashboard/component/Ordered.js | 7 +- ui/src/app/dashboard/container/ActionsTab.js | 11 +- ui/src/app/dashboard/container/AdminTab.js | 39 ++---- ui/src/app/dashboard/container/Dashboard.js | 86 +++++++++---- .../app/dashboard/container/ProvidersTab.js | 50 +++++--- ui/src/app/dashboard/container/SourcesTab.js | 5 +- .../component/properties/ArrayProperty.js | 3 +- ui/src/app/metadata/editor/MetadataEditor.js | 2 - ui/src/app/metadata/hoc/MetadataSchema.js | 1 + ui/src/app/metadata/hoc/MetadataSelector.js | 1 + ui/src/app/metadata/hooks/api.js | 8 +- 20 files changed, 357 insertions(+), 176 deletions(-) create mode 100644 ui/src/app/admin/component/AccessRequest.js create mode 100644 ui/src/app/admin/component/UserMaintenance.js create mode 100644 ui/src/app/admin/container/SourcesActions.js create mode 100644 ui/src/app/admin/container/UserActions.js create mode 100644 ui/src/app/core/components/AdminRoute.js diff --git a/ui/src/app/admin/component/AccessRequest.js b/ui/src/app/admin/component/AccessRequest.js new file mode 100644 index 000000000..682c5b1d8 --- /dev/null +++ b/ui/src/app/admin/component/AccessRequest.js @@ -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) ? + <> +
+
+

There are no new user requests at this time.

+
+
+ + : + users.map((user, i) => ( +
+
+
+
+
+
+ UserId +
+
{ user.username }
+
+ Email +
+
{ user.emailAddress }
+
+
+
+
+ Name +
+
{ user.firstName } { user.lastName }
+ +
+ +
+
+
+
+ +
+
+
+
+ ))} + + ); +} \ No newline at end of file diff --git a/ui/src/app/admin/component/UserMaintenance.js b/ui/src/app/admin/component/UserMaintenance.js new file mode 100644 index 000000000..efce47acb --- /dev/null +++ b/ui/src/app/admin/component/UserMaintenance.js @@ -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 ( +
+ + + + + + + + + + + + {users.map((user, idx) => + + + + + + + + )} + +
UserIdNameEmailRoleDelete?
{user.username}{user.firstName} {user.lastName}{user.emailAddress} + + + + {currentUser.username !== user.username && + + } +
+
+ ); +} diff --git a/ui/src/app/admin/container/SourcesActions.js b/ui/src/app/admin/container/SourcesActions.js new file mode 100644 index 000000000..805ffbc66 --- /dev/null +++ b/ui/src/app/admin/container/SourcesActions.js @@ -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 ( + + ); +} \ No newline at end of file diff --git a/ui/src/app/admin/container/UserActions.js b/ui/src/app/admin/container/UserActions.js new file mode 100644 index 000000000..dd05c9594 --- /dev/null +++ b/ui/src/app/admin/container/UserActions.js @@ -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 ( + + {(u, roles, onChangeUserRole, onDeleteUser) => + } + + ); +} + +export default UserActions; \ No newline at end of file diff --git a/ui/src/app/admin/container/UserManagement.js b/ui/src/app/admin/container/UserManagement.js index d06a6e0dd..518a77236 100644 --- a/ui/src/app/admin/container/UserManagement.js +++ b/ui/src/app/admin/container/UserManagement.js @@ -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); @@ -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 ( -
- - - - - - - - - - - - {users.map((user, idx) => - - - - - - - - )} - -
UserIdNameEmailRoleDelete?
{ user.username }{ user.firstName } { user.lastName }{ user.emailAddress } - - - - {currentUser.username !== user.username && - - } -
+
+ {children(users, roles, setUserRoleRequest, (id) => setDeleting(id))} setDeleting(null)}> Delete User? @@ -91,31 +77,3 @@ export default function UserManagement({ users, roles, onDelete, onSetRole }) {
); } - -/* - - {{ user.username }} - {{ user.firstName }} {{ user.lastName }} - {{ user.emailAddress }} - - - - - - - - */ \ No newline at end of file diff --git a/ui/src/app/core/components/AdminRoute.js b/ui/src/app/core/components/AdminRoute.js new file mode 100644 index 000000000..1b8783ec8 --- /dev/null +++ b/ui/src/app/core/components/AdminRoute.js @@ -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 ( + + isAdmin ? ( + children + ) : ( + + ) + } + /> + ); +} \ No newline at end of file diff --git a/ui/src/app/core/hooks/utils.js b/ui/src/app/core/hooks/utils.js index e8ee1f74a..c7ae9a0cf 100644 --- a/ui/src/app/core/hooks/utils.js +++ b/ui/src/app/core/hooks/utils.js @@ -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); }); } diff --git a/ui/src/app/core/user/UserContext.js b/ui/src/app/core/user/UserContext.js index d99e52cc9..b1235877c 100644 --- a/ui/src/app/core/user/UserContext.js +++ b/ui/src/app/core/user/UserContext.js @@ -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 }; \ No newline at end of file +export { UserContext, UserProvider, Consumer as UserConsumer, useCurrentUser, useIsAdmin }; \ No newline at end of file diff --git a/ui/src/app/core/utility/get_cookie.js b/ui/src/app/core/utility/get_cookie.js index 97a5195ac..de9f5bae7 100644 --- a/ui/src/app/core/utility/get_cookie.js +++ b/ui/src/app/core/utility/get_cookie.js @@ -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); } } diff --git a/ui/src/app/dashboard/component/Ordered.js b/ui/src/app/dashboard/component/Ordered.js index 4f352710c..0fe8b1675 100644 --- a/ui/src/app/dashboard/component/Ordered.js +++ b/ui/src/app/dashboard/component/Ordered.js @@ -4,7 +4,6 @@ import first from 'lodash/first'; import last from 'lodash/last'; import API_BASE_PATH from '../../App.constant'; import { array_move } from '../../core/utility/array_move'; -import { pick } from 'lodash'; const orderPaths = { provider: `/MetadataResolversPositionOrder` @@ -42,7 +41,7 @@ export function Ordered ({type = 'provider', entities, children}) { const [lastId, setLastId] = React.useState(null); async function changeOrder(resourceIds) { - const update = await post(`${orderPaths[type]}`, { + await post(`${orderPaths[type]}`, { resourceIds }); if (response.ok) { @@ -64,7 +63,6 @@ export function Ordered ({type = 'provider', entities, children}) { async function loadOrder () { const o = await get(`${orderPaths[type]}`); - console.log(o) if (response.ok) { const ids = o.resourceIds; setOrder(ids); @@ -73,12 +71,11 @@ export function Ordered ({type = 'provider', entities, children}) { } } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => loadOrder(),[]); React.useEffect(() => orderEntities(order, entities), [order, entities]); - React.useEffect(() => console.log(ordered.map(e => pick(e, ['resourceId']))), [ordered]); - return ( <> {children(ordered, firstId, lastId, onOrderUp, onOrderDown)} diff --git a/ui/src/app/dashboard/container/ActionsTab.js b/ui/src/app/dashboard/container/ActionsTab.js index c89e23585..76d1ff592 100644 --- a/ui/src/app/dashboard/container/ActionsTab.js +++ b/ui/src/app/dashboard/container/ActionsTab.js @@ -1,11 +1,10 @@ import React from 'react'; -import useFetch from 'use-http'; -import UserManagement from '../../admin/container/UserManagement'; -import API_BASE_PATH from '../../App.constant'; +import { SourcesActions } from '../../admin/container/SourcesActions'; +import UserActions from '../../admin/container/UserActions'; import Translate from '../../i18n/components/translate'; -export function ActionsTab() { +export function ActionsTab({ sources, users, reloadSources, reloadUsers }) { return ( <> @@ -19,7 +18,7 @@ export function ActionsTab() {
- {/**/} +
@@ -32,7 +31,7 @@ export function ActionsTab() { - {/**/} + diff --git a/ui/src/app/dashboard/container/AdminTab.js b/ui/src/app/dashboard/container/AdminTab.js index 2e04221c0..a458c810a 100644 --- a/ui/src/app/dashboard/container/AdminTab.js +++ b/ui/src/app/dashboard/container/AdminTab.js @@ -1,6 +1,7 @@ import React from 'react'; import useFetch from 'use-http'; import UserManagement from '../../admin/container/UserManagement'; +import UserMaintenance from '../../admin/component/UserMaintenance'; import API_BASE_PATH from '../../App.constant'; import Translate from '../../i18n/components/translate'; @@ -9,43 +10,20 @@ export function AdminTab () { const [users, setUsers] = React.useState([]); - const { get, patch, del, response } = useFetch(`${API_BASE_PATH}`, {}) + const { get, response } = useFetch(`${API_BASE_PATH}/admin/users`, { + cachePolicy: 'no-cache' + }, []); async function loadUsers() { - const users = await get('/admin/users') + const users = await get('') 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(); - } - } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { loadUsers(); - loadRoles(); }, []); @@ -60,7 +38,10 @@ export function AdminTab () {
- + + {(u, roles, onChangeUserRole, onDeleteUser) => + } +
diff --git a/ui/src/app/dashboard/container/Dashboard.js b/ui/src/app/dashboard/container/Dashboard.js index dc1274e98..97b070874 100644 --- a/ui/src/app/dashboard/container/Dashboard.js +++ b/ui/src/app/dashboard/container/Dashboard.js @@ -5,52 +5,96 @@ import { Switch, Route, Redirect, useRouteMatch } from 'react-router-dom'; import { NavLink } from 'react-router-dom'; import Translate from '../../i18n/components/translate'; +import { AdminRoute } from '../../core/components/AdminRoute'; import './Dashboard.scss'; import { SourcesTab } from './SourcesTab'; import { ProvidersTab } from './ProvidersTab'; import { AdminTab } from './AdminTab'; import { ActionsTab } from './ActionsTab'; +import { useIsAdmin } from '../../core/user/UserContext'; +import useFetch from 'use-http'; +import API_BASE_PATH from '../../App.constant'; +import { getMetadataPath } from '../../metadata/hooks/api'; export function Dashboard () { - const actions = 0; + const { path } = useRouteMatch(); - let { path } = useRouteMatch(); + const isAdmin = useIsAdmin(); + + const [actions, setActions] = React.useState(0); + const [users, setUsers] = React.useState([]); + const [sources, setSources] = React.useState([]); + + const { get, response } = useFetch(`${API_BASE_PATH}`, { + cachePolicy: 'no-cache' + }); + + async function loadUsers() { + const users = await get('/admin/users') + if (response.ok) { + setUsers(users.filter(u => u.role === 'ROLE_NONE')); + } + } + + async function loadSources() { + const s = await get(`/${getMetadataPath('source')}/disabledNonAdmin`); + if (response.ok) { + setSources(s); + } + } + + /*eslint-disable react-hooks/exhaustive-deps*/ + React.useEffect(() => { + loadSources(); + loadUsers(); + }, []); + + React.useEffect(() => { + setActions(users.length + sources.length); + }, [users, sources]); return (
+ - - - + + + + +
); diff --git a/ui/src/app/dashboard/container/ProvidersTab.js b/ui/src/app/dashboard/container/ProvidersTab.js index 54daf1786..27d89ece5 100644 --- a/ui/src/app/dashboard/container/ProvidersTab.js +++ b/ui/src/app/dashboard/container/ProvidersTab.js @@ -5,6 +5,8 @@ import Translate from '../../i18n/components/translate'; import ProviderList from '../../metadata/domain/provider/component/ProviderList'; import {Search} from '../component/Search'; import { Ordered } from '../component/Ordered'; +import { useIsAdmin } from '../../core/user/UserContext'; +import { Alert } from 'reactstrap'; const searchProps = ['name', '@type', 'createdBy']; @@ -21,31 +23,39 @@ export function ProvidersTab () { } } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { loadProviders() }, []); + const isAdmin = useIsAdmin(); + return (
-
- - Current Metadata Providers - -
-
- - {(ordered, first, last, onOrderUp, onOrderDown) => - - {(searched) => } - - } - -
+ {isAdmin ? + <> +
+ + Current Metadata Providers + +
+
+ + {(ordered, first, last, onOrderUp, onOrderDown) => + + {(searched) => } + + } + +
+ + : + Access Denied}
); diff --git a/ui/src/app/dashboard/container/SourcesTab.js b/ui/src/app/dashboard/container/SourcesTab.js index 601421da5..073aeaa86 100644 --- a/ui/src/app/dashboard/container/SourcesTab.js +++ b/ui/src/app/dashboard/container/SourcesTab.js @@ -1,7 +1,5 @@ import React from 'react'; -import useFetch from 'use-http'; import Translate from '../../i18n/components/translate'; -import API_BASE_PATH from '../../App.constant'; import SourceList from '../../metadata/domain/source/component/SourceList'; import { useMetadataEntities } from '../../metadata/hooks/api'; @@ -23,12 +21,13 @@ export function SourcesTab () { } async function deleteSource(id) { - const removal = await del(`/${id}`); + await del(`/${id}`); if (response.ok) { loadSources(); } } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { loadSources() }, []); return ( diff --git a/ui/src/app/metadata/component/properties/ArrayProperty.js b/ui/src/app/metadata/component/properties/ArrayProperty.js index 625d20654..979ed95f2 100644 --- a/ui/src/app/metadata/component/properties/ArrayProperty.js +++ b/ui/src/app/metadata/component/properties/ArrayProperty.js @@ -2,7 +2,6 @@ import { faEye } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; import Translate from '../../../i18n/components/translate'; -import { ArrayValue } from './ArrayValue'; import { usePropertyWidth } from './hooks'; import { PropertyValue } from './PropertyValue'; @@ -10,7 +9,7 @@ import { PropertyValue } from './PropertyValue'; const isUri = (value) => { try { - let url = new URL(value); + new URL(value); } catch (err) { return false; } diff --git a/ui/src/app/metadata/editor/MetadataEditor.js b/ui/src/app/metadata/editor/MetadataEditor.js index 8e0e4aece..9dbec266c 100644 --- a/ui/src/app/metadata/editor/MetadataEditor.js +++ b/ui/src/app/metadata/editor/MetadataEditor.js @@ -1,7 +1,5 @@ import React from 'react'; -import Translate from '../../i18n/components/translate'; - import { MetadataObjectContext, MetadataTypeContext } from '../hoc/MetadataSelector'; export function MetadataEditor () { diff --git a/ui/src/app/metadata/hoc/MetadataSchema.js b/ui/src/app/metadata/hoc/MetadataSchema.js index 2db96155f..ad6574bfe 100644 --- a/ui/src/app/metadata/hoc/MetadataSchema.js +++ b/ui/src/app/metadata/hoc/MetadataSchema.js @@ -23,6 +23,7 @@ export function MetadataSchema({ children }) { } } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { loadSchema(definition) }, [definition]); return ( diff --git a/ui/src/app/metadata/hoc/MetadataSelector.js b/ui/src/app/metadata/hoc/MetadataSelector.js index f7cd954a1..be7ab62c6 100644 --- a/ui/src/app/metadata/hoc/MetadataSelector.js +++ b/ui/src/app/metadata/hoc/MetadataSelector.js @@ -21,6 +21,7 @@ export function MetadataSelector ({ children }) { } } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { loadMetadata(id) }, [id]); return ( diff --git a/ui/src/app/metadata/hooks/api.js b/ui/src/app/metadata/hooks/api.js index 0b74f445c..8b76c5233 100644 --- a/ui/src/app/metadata/hooks/api.js +++ b/ui/src/app/metadata/hooks/api.js @@ -28,12 +28,12 @@ export function getSchemaPath(type) { return `/${schema[type]}`; } -export function useMetadataEntities(type = 'source') { - return useFetch(`${API_BASE_PATH}${getMetadataListPath(type)}`); +export function useMetadataEntities(type = 'source', opts = {}) { + return useFetch(`${API_BASE_PATH}${getMetadataListPath(type)}`, opts); } -export function useMetadataEntity(type = 'source') { - return useFetch(`${API_BASE_PATH}${getMetadataPath(type)}`); +export function useMetadataEntity(type = 'source', opts = {}) { + return useFetch(`${API_BASE_PATH}${getMetadataPath(type)}`, opts); } export function useMetadataProviderOrder() {