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 (
+
+
+
+
+ | UserId |
+ Name |
+ Email |
+ Role |
+ Delete? |
+
+
+
+ {users.map((user, idx) =>
+
+ | {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 (
-
-
-
-
- | UserId |
- Name |
- Email |
- Role |
- Delete? |
-
-
-
- {users.map((user, idx) =>
-
- | { 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() {