From b108413603eb5d4c54689f865a66775d839b3b9b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 31 Aug 2022 11:46:08 -0700 Subject: [PATCH] Implemented download buttons --- .../main/resources/i18n/messages.properties | 16 +++++ .../app/admin/component/ConfigurationForm.js | 36 ++++++---- .../app/admin/component/PropertySelector.js | 2 +- .../app/admin/container/ConfigurationList.js | 71 +++++++++++++++++-- .../app/admin/container/EditConfiguration.js | 2 - .../app/admin/hoc/ConfigurationsProvider.js | 2 +- ui/src/app/admin/hooks.js | 8 ++- .../{download_as_xml.js => download_as.js} | 7 +- .../app/core/utility/download_as_xml.test.js | 2 +- .../app/metadata/hoc/FilterTargetPreview.js | 2 +- ui/src/app/metadata/view/MetadataXml.js | 2 +- ui/src/theme/project/forms.scss | 4 ++ 12 files changed, 123 insertions(+), 31 deletions(-) rename ui/src/app/core/utility/{download_as_xml.js => download_as.js} (54%) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 19ab8999c..816a0bf80 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -757,6 +757,22 @@ tooltip.role-description=A description of the purpose of the role. tooltip.contact-information=Add a contact to organization information. Contacts provide information about how to contact the organization responsible for standing up the entity. +tooltip.download-single-config=Putting all the properties in one file can make it easier for deploying or moving among environments. +tooltip.download-multi-config=Putting the properties into individual files will follow the distribution layout and more closely align with the Shibboleth wiki page sections describing each property. +action.download-single-config=Single file +action.download-multi-config=Separated files +label.download-config=Downloads +message.configurations-none=No configurations defined. +label.configuration-name=Name +label.configuration-name-placeholder=Enter name +label.configuration-property=Property +label.configuration-category=Category +label.configuration-type=Type +label.configuration-value=Value +label.configuration-action=Action +message.delete-property-title=Delete Configuration? +message.delete-property-body=You are requesting to delete a configuration set. If you complete this process the set will be removed. This cannot be undone. Do you wish to continue? + label.external-description=Description tooltip.external-description=A brief description of the purpose of this filter. diff --git a/ui/src/app/admin/component/ConfigurationForm.js b/ui/src/app/admin/component/ConfigurationForm.js index 61e82dba6..87a8739dc 100644 --- a/ui/src/app/admin/component/ConfigurationForm.js +++ b/ui/src/app/admin/component/ConfigurationForm.js @@ -11,6 +11,7 @@ import { useProperties } from '../hoc/PropertiesProvider'; import Form from 'react-bootstrap/Form'; import FloatingLabel from 'react-bootstrap/FloatingLabel'; +import { useTranslator } from '../../i18n/hooks'; export function ConfigurationForm({ configuration = {}, loading, onSave, onCancel }) { @@ -20,7 +21,7 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance } }); - const { fields, prepend, remove } = useFieldArray({ + const { fields, append, remove } = useFieldArray({ control, name: "properties", }); @@ -36,7 +37,7 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance } }, []); - prepend(parsed); + append(parsed); }; const saveConfig = (formValues) => { @@ -44,6 +45,8 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance propertyName: p.propertyName, propertyValue: p.propertyValue, configFile: p.configFile, + category: p.category, + displayType: p.displayType })); onSave({ ...formValues, @@ -51,7 +54,7 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance }); }; - React.useEffect(() => console.log(configuration), [configuration]); + const translator = useTranslator(); return (<>
@@ -79,8 +82,8 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance
- Name - + Name +
@@ -97,11 +100,11 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance - - - - - + + + + + @@ -114,20 +117,23 @@ export function ConfigurationForm({ configuration = {}, loading, onSave, onCance {p.displayType !== 'boolean' ? - + label={translator('label.configuration-value')}> + : } - diff --git a/ui/src/app/admin/component/PropertySelector.js b/ui/src/app/admin/component/PropertySelector.js index 44cdfd085..976e0d220 100644 --- a/ui/src/app/admin/component/PropertySelector.js +++ b/ui/src/app/admin/component/PropertySelector.js @@ -82,7 +82,7 @@ export function PropertySelector ({ properties, options, onAddProperties }) { diff --git a/ui/src/app/admin/container/ConfigurationList.js b/ui/src/app/admin/container/ConfigurationList.js index 527f65c50..688535642 100644 --- a/ui/src/app/admin/container/ConfigurationList.js +++ b/ui/src/app/admin/container/ConfigurationList.js @@ -4,11 +4,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Button from 'react-bootstrap/Button'; import ButtonGroup from 'react-bootstrap/ButtonGroup'; +import Popover from 'react-bootstrap/Popover'; import { Link } from 'react-router-dom'; import { Translate } from '../../i18n/components/translate'; import { DeleteConfirmation } from '../../core/components/DeleteConfirmation'; +import OverlayTrigger from 'react-bootstrap/esm/OverlayTrigger'; +import { useTranslator } from '../../i18n/hooks'; +import useFetch from 'use-http'; +import API_BASE_PATH from '../../App.constant'; +import { downloadAsZip } from '../../core/utility/download_as'; export function ConfigurationList({ configurations, onDelete, loading }) { @@ -16,6 +22,25 @@ export function ConfigurationList({ configurations, onDelete, loading }) { onDelete(id); } + const translate = useTranslator(); + + const downloader = useFetch(`${API_BASE_PATH}/shib/property/set`, { + cachePolicy: 'no-cache', + headers: { + 'Content-Type': 'application/zip', + 'Accept': 'application/zip' + } + }); + + const download = async (id, type) => { + await downloader.get(`/${id}${ type === 'single' ? '/onefile' : '' }`); + const file = await downloader.response.blob(); + if (downloader.response.ok) { + downloadAsZip('configuration', file); + console.log(file); + } + }; + return ( {(block) => @@ -46,7 +71,12 @@ export function ConfigurationList({ configurations, onDelete, loading }) { - + + @@ -57,17 +87,42 @@ export function ConfigurationList({ configurations, onDelete, loading }) { {c.name} + ) : - + }
PropertyCategoryTypeValueActionPropertyCategoryTypeValueAction
+
Configuration Name (label) Actions + Download + + Actions +
+
+ + + + + )} + aria-label={translate('')}> + + +
+ + + + )} + aria-label={translate('')}> + + + {downloader.loading && } +
+
-   Edit - @@ -75,7 +130,9 @@ export function ConfigurationList({ configurations, onDelete, loading }) {
No configurations. + No configurations. +
diff --git a/ui/src/app/admin/container/EditConfiguration.js b/ui/src/app/admin/container/EditConfiguration.js index bad543b69..d164c20e0 100644 --- a/ui/src/app/admin/container/EditConfiguration.js +++ b/ui/src/app/admin/container/EditConfiguration.js @@ -3,12 +3,10 @@ import React from 'react'; import { Prompt, useHistory, useParams } from 'react-router-dom'; import Translate from '../../i18n/components/translate'; import { useConfiguration } from '../hooks'; -import { Schema } from '../../form/Schema'; import { ConfigurationForm } from '../component/ConfigurationForm'; import { createNotificationAction, NotificationTypes, useNotificationDispatcher } from '../../notifications/hoc/Notifications'; import { useTranslator } from '../../i18n/hooks'; -import { BASE_PATH } from '../../App.constant'; import { PropertiesProvider } from '../hoc/PropertiesProvider'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; diff --git a/ui/src/app/admin/hoc/ConfigurationsProvider.js b/ui/src/app/admin/hoc/ConfigurationsProvider.js index aa23ddd45..2cd146260 100644 --- a/ui/src/app/admin/hoc/ConfigurationsProvider.js +++ b/ui/src/app/admin/hoc/ConfigurationsProvider.js @@ -23,7 +23,7 @@ export function ConfigurationsProvider({ children, cache = 'no-cache' }) { async function removeConfiguration(id) { let toast; - const resp = await del(`/${id}`); + const resp = await del(`shib/property/set/${id}`); if (response.ok) { loadConfigurations(); toast = createNotificationAction(`Deleted property successfully.`, NotificationTypes.SUCCESS); diff --git a/ui/src/app/admin/hooks.js b/ui/src/app/admin/hooks.js index 11184e55e..54d9d3117 100644 --- a/ui/src/app/admin/hooks.js +++ b/ui/src/app/admin/hooks.js @@ -61,4 +61,10 @@ export function useConfigurationUiSchema () { 'ui:widget': 'textarea' } }; -} \ No newline at end of file +} + +export function useConfigDownload () { + return useFetch(`${API_BASE_PATH}/shib/property/set`, { + cachePolicy: 'no-cache' + }); +} diff --git a/ui/src/app/core/utility/download_as_xml.js b/ui/src/app/core/utility/download_as.js similarity index 54% rename from ui/src/app/core/utility/download_as_xml.js rename to ui/src/app/core/utility/download_as.js index a9256fc63..4fc0cc4fd 100644 --- a/ui/src/app/core/utility/download_as_xml.js +++ b/ui/src/app/core/utility/download_as.js @@ -1,6 +1,11 @@ import * as FileSaver from 'file-saver'; +export const downloadAsZip = (fileName, data) => { + // const blob = new Blob([data], { type: 'text/zip;charset=utf-8' }); + FileSaver.saveAs(data, `${fileName}.zip`); +} + export const downloadAsXml = (fileName, xml) => { const blob = new Blob([xml], { type: 'text/xml;charset=utf-8' }); FileSaver.saveAs(blob, `${fileName}.xml`); -} \ No newline at end of file +} diff --git a/ui/src/app/core/utility/download_as_xml.test.js b/ui/src/app/core/utility/download_as_xml.test.js index 38a87e6fe..3e8583fe9 100644 --- a/ui/src/app/core/utility/download_as_xml.test.js +++ b/ui/src/app/core/utility/download_as_xml.test.js @@ -1,5 +1,5 @@ import * as FileSaver from 'file-saver'; -import { downloadAsXml } from './download_as_xml'; +import { downloadAsXml } from './download_as'; jest.mock('file-saver'); it('attempts to save the provided content', () => { diff --git a/ui/src/app/metadata/hoc/FilterTargetPreview.js b/ui/src/app/metadata/hoc/FilterTargetPreview.js index 8bd8550d3..2fd81e4c3 100644 --- a/ui/src/app/metadata/hoc/FilterTargetPreview.js +++ b/ui/src/app/metadata/hoc/FilterTargetPreview.js @@ -4,7 +4,7 @@ import { useFetch } from 'use-http'; import Modal from 'react-bootstrap/Modal'; import Button from 'react-bootstrap/Button'; import Translate from '../../i18n/components/translate'; -import { downloadAsXml } from '../../core/utility/download_as_xml'; +import { downloadAsXml } from '../../core/utility/download_as'; export function FilterTargetPreview ({ entityId, children }) { diff --git a/ui/src/app/metadata/view/MetadataXml.js b/ui/src/app/metadata/view/MetadataXml.js index 17e79d26a..fa6252cd9 100644 --- a/ui/src/app/metadata/view/MetadataXml.js +++ b/ui/src/app/metadata/view/MetadataXml.js @@ -9,7 +9,7 @@ import { MetadataObjectContext } from '../hoc/MetadataSelector'; import { MetadataXmlContext } from '../hoc/MetadataXmlLoader'; import { MetadataViewToggle } from '../component/MetadataViewToggle'; -import { downloadAsXml } from '../../core/utility/download_as_xml'; +import { downloadAsXml } from '../../core/utility/download_as'; export function MetadataXml () { const { xml, reload } = React.useContext(MetadataXmlContext); diff --git a/ui/src/theme/project/forms.scss b/ui/src/theme/project/forms.scss index b60471ce0..daa0d8cb9 100644 --- a/ui/src/theme/project/forms.scss +++ b/ui/src/theme/project/forms.scss @@ -124,6 +124,10 @@ mark { } } +.form-floating > label { + color:#9299A0; +} + @media only screen and (max-width: 1200px) { .form-section:not(:first-child) { border-left: 0px;