diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js new file mode 100644 index 000000000..76d1ff592 --- /dev/null +++ b/ui/src/app/dashboard/view/ActionsTab.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { SourcesActions } from '../../admin/container/SourcesActions'; +import UserActions from '../../admin/container/UserActions'; + +import Translate from '../../i18n/components/translate'; + +export function ActionsTab({ sources, users, reloadSources, reloadUsers }) { + + return ( + <> +
+
+
+
+
+ Enable Metadata Sources +
+
+
+
+ +
+
+
+
+
+
+
+
+ User Access Request +
+
+
+ +
+
+ + + ); +} + +export default ActionsTab; \ No newline at end of file diff --git a/ui/src/app/dashboard/view/AdminTab.js b/ui/src/app/dashboard/view/AdminTab.js new file mode 100644 index 000000000..a458c810a --- /dev/null +++ b/ui/src/app/dashboard/view/AdminTab.js @@ -0,0 +1,51 @@ +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'; + +export function AdminTab () { + + const [users, setUsers] = React.useState([]); + + const { get, response } = useFetch(`${API_BASE_PATH}/admin/users`, { + cachePolicy: 'no-cache' + }, []); + + async function loadUsers() { + const users = await get('') + if (response.ok) { + setUsers(users); + } + } + + /*eslint-disable react-hooks/exhaustive-deps*/ + React.useEffect(() => { + loadUsers(); + }, []); + + + return ( +
+
+
+
+
+ User Maintenance +
+
+
+
+ + {(u, roles, onChangeUserRole, onDeleteUser) => + } + +
+
+
+ ); +} + +export default AdminTab; \ No newline at end of file diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js new file mode 100644 index 000000000..97b070874 --- /dev/null +++ b/ui/src/app/dashboard/view/Dashboard.js @@ -0,0 +1,108 @@ +import React from 'react'; + +import { Nav, NavItem } from 'reactstrap'; +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 { 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 ( +
+ + + + + + + + + + + + + +
+ ); +} +/* + + & nbsp; + {{ actionsRequired$ | async}} + + */ +export default Dashboard; \ No newline at end of file diff --git a/ui/src/app/dashboard/view/Dashboard.scss b/ui/src/app/dashboard/view/Dashboard.scss new file mode 100644 index 000000000..74201a46e --- /dev/null +++ b/ui/src/app/dashboard/view/Dashboard.scss @@ -0,0 +1,2 @@ +@import '../../../theme/variables.scss'; + diff --git a/ui/src/app/dashboard/view/ProvidersTab.js b/ui/src/app/dashboard/view/ProvidersTab.js new file mode 100644 index 000000000..f5a2d719b --- /dev/null +++ b/ui/src/app/dashboard/view/ProvidersTab.js @@ -0,0 +1,62 @@ +import React from 'react'; + +import { useMetadataEntities } from '../../metadata/hooks/api'; +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']; + +export function ProvidersTab () { + + const [providers, setProviders] = React.useState([]); + + const { get, response } = useMetadataEntities('provider'); + + async function loadProviders() { + const providers = await get('') + if (response.ok) { + setProviders(providers); + } + } + + /*eslint-disable react-hooks/exhaustive-deps*/ + React.useEffect(() => { loadProviders() }, []); + + const isAdmin = useIsAdmin(); + + return ( +
+
+ {isAdmin ? + <> +
+ + Current Metadata Providers + +
+
+ + {(ordered, first, last, onOrderUp, onOrderDown) => + + {(searched) => } + + } + +
+ + : + Access Denied} +
+
+ ); +} \ No newline at end of file diff --git a/ui/src/app/dashboard/view/SourcesTab.js b/ui/src/app/dashboard/view/SourcesTab.js new file mode 100644 index 000000000..94305795b --- /dev/null +++ b/ui/src/app/dashboard/view/SourcesTab.js @@ -0,0 +1,46 @@ +import React from 'react'; +import Translate from '../../i18n/components/translate'; + +import SourceList from '../../metadata/domain/source/component/SourceList'; +import { useMetadataEntities } from '../../metadata/hooks/api'; +import { Search } from '../component/Search'; + +const searchProps = ['serviceProviderName', 'entityId', 'createdBy']; + +export function SourcesTab () { + + const [sources, setSources] = React.useState([]); + + const { get, response } = useMetadataEntities('source', { + cachePolicy: 'no-cache' + }); + + async function loadSources() { + const sources = await get('/'); + if (response.ok) { + setSources(sources); + } + } + + const updateSources = () => loadSources(); + + /*eslint-disable react-hooks/exhaustive-deps*/ + React.useEffect(() => { loadSources() }, []); + + return ( +
+
+
+ + Current Metadata Sources + +
+
+ + {(searched) => } + +
+
+
+ ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/hoc/Configuration.js b/ui/src/app/metadata/hoc/Configuration.js new file mode 100644 index 000000000..1e12f541c --- /dev/null +++ b/ui/src/app/metadata/hoc/Configuration.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { useMetadataConfiguration } from '../hooks/configuration'; + +export function Configuration ({entities, schema, definition, limited, children}) { + + const config = useMetadataConfiguration(entities, schema, definition, limited); + + console.log(config) + + return ( + <>{children(config)} + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/hoc/MetadataVersionLoader.js b/ui/src/app/metadata/hoc/MetadataVersionLoader.js new file mode 100644 index 000000000..a2c62c10e --- /dev/null +++ b/ui/src/app/metadata/hoc/MetadataVersionLoader.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { useParams } from 'react-router'; +import { getMetadataPath } from '../hooks/api'; +import API_BASE_PATH from '../../App.constant'; +import useFetch from 'use-http'; +import { last } from 'lodash'; + +export function MetadataVersionLoader ({versions, children}) { + + const ref = React.useRef({}); + const [list, setList] = React.useState({}); + + const { type, id } = useParams(); + + const { get, response } = useFetch(`/${API_BASE_PATH}${getMetadataPath(type)}/${id}/Versions`, { + cachePolicy: 'no-cache', + }, []); + + async function loadVersion(v) { + const l = await get(`/${v}`); + if (response.ok) { + addToList(v, l); + if (last(versions) !== v) { + loadNext(versions[versions.indexOf(v) + 1]); + } + } + } + + function addToList(version, item) { + ref.current = { + ...ref.current, + [version]: item + }; + setList(ref.current); + } + + function loadNext (v) { + loadVersion(v); + } + + React.useEffect(() => { + loadNext(versions[0]); + }, []); + + return ( + {children(versions.map(v => list[v]).filter(v => !!v))} + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/view/MetadataComparison.js b/ui/src/app/metadata/view/MetadataComparison.js new file mode 100644 index 000000000..5b04c013a --- /dev/null +++ b/ui/src/app/metadata/view/MetadataComparison.js @@ -0,0 +1,121 @@ +import React from 'react'; +import { + useQueryParam, + ArrayParam, + withDefault +} from 'use-query-params'; +import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema'; +import { MetadataVersionLoader } from '../hoc/MetadataVersionLoader'; +import { Configuration } from '../hoc/Configuration'; +import { MetadataConfiguration } from '../component/MetadataConfiguration'; +import { Link, useParams } from 'react-router-dom'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faHistory } from '@fortawesome/free-solid-svg-icons'; +import Translate from '../../i18n/components/translate'; +import { CustomInput } from 'reactstrap'; +import { useTranslation } from '../../i18n/hooks'; + +export function MetadataComparison () { + + const { type, id } = useParams(); + + const [versions] = useQueryParam('versions', withDefault(ArrayParam, [])); + const schema = React.useContext(MetadataSchemaContext); + const definition = React.useContext(MetadataDefinitionContext); + + const [limited, setLimited] = React.useState(false); + + const toggleLimited = useTranslation('action.view-only-changes'); + + return ( + <> + {versions && + + {(v) => + + {(config) => +
2 ? 'container-fluid' : 'container'}> + + +
+ } +
+ } +
+ } + + ); +} + +/* + +

+ Compare + Source + Provider + Configuration +

+
+
+ +   + Version History + + +
+ + +
+

+ Metadata Filter +

+
+ + + + + +
+ +
+ +
+
+
+
+
+ + Loading... +
+ +*/ \ No newline at end of file diff --git a/ui/src/app/metadata/view/MetadataHistory.js b/ui/src/app/metadata/view/MetadataHistory.js new file mode 100644 index 000000000..605b506ed --- /dev/null +++ b/ui/src/app/metadata/view/MetadataHistory.js @@ -0,0 +1,111 @@ +import React from 'react'; +import { useHistory, useParams } from 'react-router'; +import { Link } from 'react-router-dom'; +import queryString from 'query-string'; + +import FormattedDate from '../../core/components/FormattedDate'; + +import Translate from '../../i18n/components/translate'; +import { useMetadataHistory } from '../hooks/api'; + +const sortVersionsByDate = (versions) => { + return versions.sort((a, b) => { + const aDate = new Date(a.date).getTime(); + const bDate = new Date(b.date).getTime(); + return aDate === bDate ? 0 : aDate < bDate ? -1 : 1; + }).reverse(); +} + +export function MetadataHistory () { + const { type, id } = useParams(); + + const history = useHistory(); + + const { data, loading } = useMetadataHistory(type, id, {}, []); + + const toggleVersionSelected = (version) => { + let s = [...selected]; + if (s.indexOf(version) > -1) { + s = s.filter(i => i !== version); + } else { + s = [...s, version]; + } + setSelected(s); + }; + const restore = () => {}; + const compare = (versions) => { + const sorted = sortVersionsByDate(versions); + const path = `/metadata/${type}/${id}/configuration/compare?${queryString.stringify({versions: sorted.map(s => s.id)}, { + skipNull: true, + })}`; + history.push(path); + }; + + const [selected, setSelected] = React.useState([]); + + return ( + <> +

+ version history +

+ {data && !loading &&
+ <> + + + + + + + + + + + + {data.map((version, i) => + + + + + + + )} + +
Metadata Version History
+ Select Version + Save DateChanged ByActions
+
toggleVersionSelected(version)}> + -1} onChange={() => {}} /> + +
+
+ {i === 0 ? +  (Current) + + : + + + + } + { version.creator } + {i > 0 && + + } +
+ + +
} + {loading &&
+ + Loading... +
} + + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/view/MetadataOptions.js b/ui/src/app/metadata/view/MetadataOptions.js new file mode 100644 index 000000000..61ba46e3a --- /dev/null +++ b/ui/src/app/metadata/view/MetadataOptions.js @@ -0,0 +1,109 @@ +import React from 'react'; +import { faArrowDown, faArrowUp, faHistory, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Link, useHistory, useParams } from 'react-router-dom'; + +import { scroller } from 'react-scroll'; + +import Translate from '../../i18n/components/translate'; + +import { MetadataObjectContext } from '../hoc/MetadataSelector'; +import { MetadataHeader } from '../component/MetadataHeader'; +import { MetadataConfiguration } from '../component/MetadataConfiguration'; +import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema'; + +import { useMetadataConfiguration } from '../hooks/configuration'; +import { MetadataViewToggle } from '../component/MetadataViewToggle'; +import { DeleteSourceConfirmation } from '../domain/source/component/DeleteSourceConfirmation'; +import { MetadataFilters } from '../domain/filter/component/MetadataFilters'; +import { MetadataFilterConfigurationList } from '../domain/filter/component/MetadataFilterConfigurationList'; +import { MetadataFilterTypes } from '../domain/filter'; + +export function MetadataOptions () { + + const metadata = React.useContext(MetadataObjectContext); + const definition = React.useContext(MetadataDefinitionContext); + const schema = React.useContext(MetadataSchemaContext); + const history = useHistory(); + + const { type, id } = useParams(); + + const configuration = useMetadataConfiguration([metadata], schema, definition); + + const onScrollTo = (element, offset = 0) => { + scroller.scrollTo(element, { + duration: 500, + smooth: true, + offset + }); + }; + + const redirectOnDelete = () => history.push('/dashboard'); + + return ( + + {(onDeleteSource) => + <> + +
+ + {type === 'source' && + + } + + + +
+ {type === 'provider' && + <> +
+

+ Filters +

+
+ +   + Add Filter + +
+
+ + + + + } +
+ +
+ } +
+ ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/view/MetadataVersion.js b/ui/src/app/metadata/view/MetadataVersion.js new file mode 100644 index 000000000..02a88d558 --- /dev/null +++ b/ui/src/app/metadata/view/MetadataVersion.js @@ -0,0 +1,110 @@ +import React from 'react'; + +import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema'; +import { Configuration } from '../hoc/Configuration'; +import { MetadataConfiguration } from '../component/MetadataConfiguration'; +import { Link, useParams } from 'react-router-dom'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowUp, faHistory, faPlus } from '@fortawesome/free-solid-svg-icons'; + +import { scroller } from 'react-scroll'; + +import Translate from '../../i18n/components/translate'; +import { useMetadataEntity } from '../hooks/api'; +import { MetadataHeader } from '../component/MetadataHeader'; +import { MetadataFilters } from '../domain/filter/component/MetadataFilters'; +import { MetadataFilterConfigurationList } from '../domain/filter/component/MetadataFilterConfigurationList'; +import { MetadataFilterTypes } from '../domain/filter'; + + +export function MetadataVersion() { + + const { type, id, versionId } = useParams(); + + const [metadata, setMetadata] = React.useState(); + + + const schema = React.useContext(MetadataSchemaContext); + const definition = React.useContext(MetadataDefinitionContext); + + const { get, response } = useMetadataEntity(type, { + cachePolicy: 'no-cache', + }, []); + + async function loadVersion(v) { + const l = await get(`/${id}/Versions/${v}`); + if (response.ok) { + setMetadata(l); + } + } + + React.useEffect(() => { + loadVersion(versionId); + }, [versionId]); + + const onScrollTo = (element, offset = 0) => { + scroller.scrollTo(element, { + duration: 500, + smooth: true, + offset + }); + }; + + return ( + <> + {metadata && + + {(config) => + <> + +
+ + + + + +
+ {type === 'provider' && + <> +
+

+ Filters +

+
+ +   + Add Filter + +
+
+ + + + + } +
+ +
+ + } +
+ } + + ); +} diff --git a/ui/src/app/metadata/view/MetadataXml.js b/ui/src/app/metadata/view/MetadataXml.js new file mode 100644 index 000000000..4a39b22f9 --- /dev/null +++ b/ui/src/app/metadata/view/MetadataXml.js @@ -0,0 +1,45 @@ +import { faSave } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React from 'react'; + +import { useParams } from 'react-router'; +import Translate from '../../i18n/components/translate'; +import { MetadataObjectContext } from '../hoc/MetadataSelector'; + +import { MetadataXmlContext } from '../hoc/MetadataXmlLoader'; +import { MetadataViewToggle } from '../component/MetadataViewToggle'; +import { downloadAsXml } from '../../core/utility/download_as_xml'; + +export function MetadataXml () { + const xml = React.useContext(MetadataXmlContext); + const entity = React.useContext(MetadataObjectContext); + const { type } = useParams(); + + const download = () => downloadAsXml(entity, xml); + + return ( + <> +

+ Source Configuration +

+
+
+ +
+
+
+
{xml}
+
+ { xml } +
+
+ + +
+
+ + ); +} \ No newline at end of file diff --git a/ui/src/setupProxy.js b/ui/src/setupProxy.js index abb8ec30d..78c38f7fb 100644 --- a/ui/src/setupProxy.js +++ b/ui/src/setupProxy.js @@ -2,10 +2,13 @@ const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function (app) { + + const port = 10101; + app.use( '/api', createProxyMiddleware({ - target: 'http://localhost:8080', + target: `http://localhost:${port}`, changeOrigin: true, onProxyRes: function (proxyRes, req, res) { proxyRes.headers['Access-Control-Allow-Origin'] = '*'; @@ -16,7 +19,7 @@ module.exports = function (app) { app.use( '/actuator', createProxyMiddleware({ - target: 'http://localhost:8080', + target: `http://localhost:${port}`, changeOrigin: true }) ); @@ -24,7 +27,7 @@ module.exports = function (app) { app.use( '/login', createProxyMiddleware({ - target: 'http://localhost:8080', + target: `http://localhost:${port}`, changeOrigin: true }) ); @@ -32,7 +35,7 @@ module.exports = function (app) { app.use( '/logout', createProxyMiddleware({ - target: 'http://localhost:8080', + target: `http://localhost:${port}`, changeOrigin: true }) );