From 2813d25d8e889ba6002b3632a835fa5f23ef9802 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 29 Aug 2022 13:25:22 -0700 Subject: [PATCH] Updated configuration builder --- ui/package-lock.json | 14 +- ui/package.json | 2 +- ui/public/assets/data/configuration.json | 29 +++ .../app/admin/component/ConfigurationForm.js | 174 +++++++----------- .../app/admin/component/PropertySelector.js | 92 +++++++++ .../app/admin/container/ConfigurationList.js | 12 +- .../app/admin/container/EditConfiguration.js | 76 ++++---- .../app/admin/container/NewConfiguration.js | 30 ++- .../app/admin/hoc/ConfigurationsProvider.js | 2 +- ui/src/app/admin/hoc/PropertiesProvider.js | 4 +- ui/src/app/admin/hooks.js | 8 +- ui/src/theme/project/configuration.scss | 11 ++ ui/src/theme/project/index.scss | 1 + 13 files changed, 271 insertions(+), 184 deletions(-) create mode 100644 ui/public/assets/data/configuration.json create mode 100644 ui/src/app/admin/component/PropertySelector.js create mode 100644 ui/src/theme/project/configuration.scss diff --git a/ui/package-lock.json b/ui/package-lock.json index 0cc5f3665..2083b22a0 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -25,7 +25,7 @@ "react-bootstrap": "^2.3.0", "react-bootstrap-typeahead": "^5.1.4", "react-dom": "^18.0.0", - "react-hook-form": "^7.30.0", + "react-hook-form": "^7.34.0", "react-infinite-scroll-component": "^6.1.0", "react-router": "^5.1.0", "react-router-dom": "^5.1.0", @@ -13536,9 +13536,9 @@ "dev": true }, "node_modules/react-hook-form": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.30.0.tgz", - "integrity": "sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ==", + "version": "7.34.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.34.2.tgz", + "integrity": "sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==", "engines": { "node": ">=12.22.0" }, @@ -26712,9 +26712,9 @@ "dev": true }, "react-hook-form": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.30.0.tgz", - "integrity": "sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ==", + "version": "7.34.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.34.2.tgz", + "integrity": "sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==", "requires": {} }, "react-infinite-scroll-component": { diff --git a/ui/package.json b/ui/package.json index 25cc8cd8a..b32a48b1d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,7 +21,7 @@ "react-bootstrap": "^2.3.0", "react-bootstrap-typeahead": "^5.1.4", "react-dom": "^18.0.0", - "react-hook-form": "^7.30.0", + "react-hook-form": "^7.34.0", "react-infinite-scroll-component": "^6.1.0", "react-router": "^5.1.0", "react-router-dom": "^5.1.0", diff --git a/ui/public/assets/data/configuration.json b/ui/public/assets/data/configuration.json new file mode 100644 index 000000000..82e86dd4d --- /dev/null +++ b/ui/public/assets/data/configuration.json @@ -0,0 +1,29 @@ +{ + "resourceId": 11, + "name": "setname1", + "properties": [ + { + "resourceId":"577", + "category":"OPSubClaim", + "configFile":"oidc.properties", + "description":"The source attribute used in generating the sub claim", + "idpVersion":"4.1", + "module":"idp.oidc.OP", + "moduleVersion":"3", + "propertyName":"idp.oidc.subject.sourceAttribute", + "displayType":"string", + "propertyValue": "foo" + }, + { + "resourceId": "393", + "category": "ReloadableServices", + "configFile": "services.properties", + "defaultValue": "false", + "description": "Fail at startup if MetadataConfiguration is invalid", + "idpVersion": "all", + "propertyName": "idp.service.metadata.failFast", + "displayType": "boolean", + "propertyValue": "true" + } + ] +} diff --git a/ui/src/app/admin/component/ConfigurationForm.js b/ui/src/app/admin/component/ConfigurationForm.js index ff890a6a2..9db9756da 100644 --- a/ui/src/app/admin/component/ConfigurationForm.js +++ b/ui/src/app/admin/component/ConfigurationForm.js @@ -1,74 +1,34 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import Button from 'react-bootstrap/Button'; +import { useFieldArray, useForm } from 'react-hook-form'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSpinner, faSave, faTrash } from '@fortawesome/free-solid-svg-icons'; -import { Highlighter, Menu, MenuItem, Token, Typeahead } from 'react-bootstrap-typeahead'; + import Translate from '../../i18n/components/translate'; -import { ToggleButton } from '../../form/component/ToggleButton'; +import PropertySelector from './PropertySelector'; import { useProperties, usePropertiesLoading } from '../hoc/PropertiesProvider'; -import { groupBy } from 'lodash'; -import { useCallback } from 'react'; + import Form from 'react-bootstrap/Form'; import FloatingLabel from 'react-bootstrap/FloatingLabel'; -export function ConfigurationForm({ configuration = {}, errors = [], schema, onSave, onCancel }) { - - const properties = useProperties(); - const loading = usePropertiesLoading(); +export function ConfigurationForm({ configuration = {}, schema, onSave, onCancel }) { - const select = (data) => { - console.log(data); - setSelected(data); - }; + const { control, register, getValues, watch, formState: { errors } } = useForm({ + defaultValues: { + ...configuration + } + }); - const [selected, setSelected] = React.useState([]); + const { fields, prepend, remove } = useFieldArray({ + control, + name: "properties", + }); - const [config, setConfig] = React.useState({ name: '', properties: [] }); - - // config.properties.filter(p => p.category === item.category).length === properties.filter(p => p.category === item.category).length - - const menu = useCallback((results, menuProps, state) => { - let index = 0; - const mapped = results.map(p => !p.category || p.category === '?' ? { ...p, category: 'Misc' } : p); - const grouped = groupBy(mapped, 'category'); - const items = Object.keys(grouped).sort().map((item) => ( - - {index !== 0 && } - - - {item} - Add all - - - {grouped[item].map((i) => { - const item = - p.propertyName === i.propertyName) }> - - {`- ${i.propertyName}`} - - ; - index += 1; - return item; - })} - - )); - - return {items}; - }, [config.properties]); - - const token = (option, { onRemove }, index) => ( - - {`${option.propertyName}`} - - ); + const properties = useProperties(); + const loading = usePropertiesLoading(); const addProperties = (props) => { - const parsed = props.reduce((coll, prop, idx) => { if (prop.isCategory) { return [...coll, ...properties.filter(p => p.category === prop.category)]; @@ -77,17 +37,20 @@ export function ConfigurationForm({ configuration = {}, errors = [], schema, onS } }, []); - setConfig({ - ...config, - properties: [ - ...config.properties, - ...parsed, - ] - }); - setSelected([]); + prepend(parsed); }; - React.useEffect(() => console.log(selected), [selected]); + const saveConfig = (formValues) => { + const parsed = formValues.properties.map(p => ({ + propertyName: p.propertyName, + propertyValue: p.propertyValue, + configFile: p.configFile, + })); + onSave({ + ...formValues, + properties: parsed + }); + }; return (<>
@@ -95,7 +58,7 @@ export function ConfigurationForm({ configuration = {}, errors = [], schema, onS

-
-
-
-
- - select(selected)} - options={[...properties]} - selected={selected} - labelKey={option => `${option.propertyName}`} - filterBy={['propertyName', 'category', 'displayType']} - renderMenu={ menu } - multiple={ true } - renderToken={ token } - > - {({ isMenuShown, toggleMenu }) => ( - toggleMenu()}> - Options - - )} - +
+
+
+ + Name + + +
+
+
+
+
+
-
-
-
-
-
- +
+
+
@@ -154,20 +102,27 @@ export function ConfigurationForm({ configuration = {}, errors = [], schema, onS - {config.properties.map((p, idx) => ( - + {fields.map((p, idx) => ( +
{ p.propertyName } { p.category } { p.displayType } - - - + {p.displayType !== 'boolean' ? + + + + : + + } - @@ -176,10 +131,9 @@ export function ConfigurationForm({ configuration = {}, errors = [], schema, onS ))}
- +
-
+
) -} -/**/ \ No newline at end of file +} \ No newline at end of file diff --git a/ui/src/app/admin/component/PropertySelector.js b/ui/src/app/admin/component/PropertySelector.js new file mode 100644 index 000000000..44cdfd085 --- /dev/null +++ b/ui/src/app/admin/component/PropertySelector.js @@ -0,0 +1,92 @@ +import React, { Fragment, useCallback } from 'react'; +import { groupBy } from 'lodash'; +import { Highlighter, Menu, MenuItem, Token, Typeahead } from 'react-bootstrap-typeahead'; +import Button from 'react-bootstrap/Button'; + +import { ToggleButton } from '../../form/component/ToggleButton'; + +export function PropertySelector ({ properties, options, onAddProperties }) { + + // React.useEffect(() => console.log(properties), [properties]); + + const menu = useCallback((results, menuProps, state) => { + let index = 0; + const mapped = results.map(p => !p.category || p.category === '?' ? { ...p, category: 'Misc' } : p); + const grouped = groupBy(mapped, 'category'); + const items = Object.keys(grouped).sort().map((item) => ( + + {index !== 0 && } + + + {item} - Add all + + + {grouped[item].map((i) => { + const item = + p.propertyName === i.propertyName) }> + + {`- ${i.propertyName}`} + + ; + index += 1; + return item; + })} + + )); + + return {items}; + }, [properties]); + + const token = (option, { onRemove }, index) => ( + + {`${option.propertyName}`} + + ); + + const select = (data) => { + setSelected(data); + }; + + const [selected, setSelected] = React.useState([]); + + const add = (s) => { + onAddProperties(s); + setSelected([]); + } + + return ( + +
+ + select(selected)} + options={[...options]} + selected={selected} + labelKey={option => `${option.propertyName}`} + filterBy={['propertyName', 'category', 'displayType']} + renderMenu={ menu } + multiple={ true } + renderToken={ token } + > + {({ isMenuShown, toggleMenu }) => ( + toggleMenu()}> + Options + + )} + +
+ +
+ ) +} + +export default PropertySelector; \ No newline at end of file diff --git a/ui/src/app/admin/container/ConfigurationList.js b/ui/src/app/admin/container/ConfigurationList.js index fcad47048..4acffc1c2 100644 --- a/ui/src/app/admin/container/ConfigurationList.js +++ b/ui/src/app/admin/container/ConfigurationList.js @@ -1,5 +1,5 @@ import React from 'react'; -import { faDownload, faEdit, faPlusCircle, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faDownload, faPlusCircle, faTrash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Button from 'react-bootstrap/Button'; @@ -46,13 +46,17 @@ export function ConfigurationList({ configurations, onDelete }) { {(configurations?.length > 0) ? configurations.map((c, i) => - {c.name} + + + {c.name} + + - +
diff --git a/ui/src/app/admin/container/NewConfiguration.js b/ui/src/app/admin/container/NewConfiguration.js index d2ece36a9..a358c1f84 100644 --- a/ui/src/app/admin/container/NewConfiguration.js +++ b/ui/src/app/admin/container/NewConfiguration.js @@ -21,11 +21,11 @@ export function NewConfiguration() { const [blocking, setBlocking] = React.useState(false); - async function save(property) { + async function save(config) { let toast; - const resp = await post(``, property); + const resp = await post(``, config); if (response.ok) { - gotoDetail({ refresh: true }); + gotoList({ refresh: true }); toast = createNotificationAction(`Added property successfully.`, NotificationTypes.SUCCESS); } else { toast = createNotificationAction(`${resp.errorCode} - ${translator(resp.errorMessage)}`, NotificationTypes.ERROR); @@ -36,14 +36,16 @@ export function NewConfiguration() { }; const cancel = () => { - gotoDetail(); + gotoList(); }; - const gotoDetail = (state = null) => { + const gotoList = (state = null) => { setBlocking(false); - history.push(`/properties`, state); + history.push(`/configurations`, state); }; + const [configuration] = React.useState({}); + return (
{(schema) => - - {(data, errors) => - save(data)} - onCancel={() => cancel()} />} - } + save(data)} + onCancel={() => cancel()} />}
diff --git a/ui/src/app/admin/hoc/ConfigurationsProvider.js b/ui/src/app/admin/hoc/ConfigurationsProvider.js index 661c00d80..aa23ddd45 100644 --- a/ui/src/app/admin/hoc/ConfigurationsProvider.js +++ b/ui/src/app/admin/hoc/ConfigurationsProvider.js @@ -15,7 +15,7 @@ export function ConfigurationsProvider({ children, cache = 'no-cache' }) { }); async function loadConfigurations() { - const list = await get(`assets/data/configurations.json`); + const list = await get(`shib/property/set`); if (response.ok) { setConfigurations(list); } diff --git a/ui/src/app/admin/hoc/PropertiesProvider.js b/ui/src/app/admin/hoc/PropertiesProvider.js index 55dde0696..bf62be7cc 100644 --- a/ui/src/app/admin/hoc/PropertiesProvider.js +++ b/ui/src/app/admin/hoc/PropertiesProvider.js @@ -1,8 +1,6 @@ import React from 'react'; import useFetch from 'use-http'; -import API_BASE_PATH, { BASE_PATH } from '../../App.constant'; -import has from 'lodash/has'; -import { groupBy } from 'lodash'; +import API_BASE_PATH from '../../App.constant'; const PropertiesContext = React.createContext(); diff --git a/ui/src/app/admin/hooks.js b/ui/src/app/admin/hooks.js index 50f0b51c7..11184e55e 100644 --- a/ui/src/app/admin/hooks.js +++ b/ui/src/app/admin/hooks.js @@ -1,7 +1,7 @@ import useFetch from 'use-http'; import isNil from 'lodash/isNil'; import {isValidRegex} from '../core/utility/is_valid_regex'; -import API_BASE_PATH, { BASE_PATH } from '../App.constant'; +import API_BASE_PATH from '../App.constant'; export function useGroups (opts = { cachePolicy: 'no-cache' }) { return useFetch(`${API_BASE_PATH}/admin/groups`, opts); @@ -48,11 +48,11 @@ export function useRoleUiSchema() { } export function useConfigurations (opts = { cachePolicy: 'no-cache' }) { - return useFetch(`${BASE_PATH}/`, opts); + return useFetch(`${API_BASE_PATH}/`, opts); } -export function useConfiguration(id, opts = { cachePolicy: 'no-cache' }) { - return useFetch(`${API_BASE_PATH}/admin/configuration/${id}`, opts); +export function useConfiguration(opts = { cachePolicy: 'no-cache' }) { + return useFetch(`${API_BASE_PATH}/shib/property/set`, opts); } export function useConfigurationUiSchema () { diff --git a/ui/src/theme/project/configuration.scss b/ui/src/theme/project/configuration.scss new file mode 100644 index 000000000..0da05f1ff --- /dev/null +++ b/ui/src/theme/project/configuration.scss @@ -0,0 +1,11 @@ +#property-selector { + .dropdown-header { + padding-right: 0rem; + padding-left: 0rem; + font-size: 1rem; + + .dropdown-item { + font-weight: bold; + } + } +} \ No newline at end of file diff --git a/ui/src/theme/project/index.scss b/ui/src/theme/project/index.scss index 6d0de6f9a..fd2b6a070 100644 --- a/ui/src/theme/project/index.scss +++ b/ui/src/theme/project/index.scss @@ -14,6 +14,7 @@ @import './notifications'; @import './filters'; @import './typeahead'; +@import './configuration'; html, body { height: 100%;