diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ffce68264..135888e420 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,11 +86,11 @@ jobs: restore-keys: v1-npm-deps- - run: npm --prefix static/js/vue-cdr-access install - - run: npm --prefix static/js/admin/vue-permissions-editor install + - run: npm --prefix static/js/admin/vue-cdr-admin install - run: npm install -g jest-cli@29.0.3 - run: npm --prefix static/js/vue-cdr-access run test - - run: npm --prefix static/js/admin/vue-permissions-editor run test + - run: npm --prefix static/js/admin/vue-cdr-admin run test - name: Report to CodeClimate uses: paambaati/codeclimate-action@v3.0.0 @@ -103,7 +103,7 @@ jobs: ${{github.workspace}}/**/target/site/jacoco/jacoco.xml:jacoco ${{github.workspace}}/**/target/site/jacoco-it/jacoco.xml:jacoco ${{github.workspace}}/static/js/vue-cdr-access/coverage/lcov.info:lcov - ${{github.workspace}}/static/js/admin/vue-permissions-editor/coverage/lcov.info:lcov + ${{github.workspace}}/static/js/admin/vue-cdr-admin/coverage/lcov.info:lcov - name: View fedora service logs if: always() diff --git a/.gitignore b/.gitignore index 56198fc786..3968dba200 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ static/js/cdr-access.js static/css/cdr_access.css static/css/cdr_vue_modal_styles.css static/js/vue-access-index.js +static/js/vue-admin-index.js static/js/vue-permissions-index.js static/js/vue-cdr-access/.env static/css/cdr-ui.css diff --git a/Makefile b/Makefile index d2e0f8caf6..e062968d2f 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ build-admin-concat: static/css/admin/fontawesome/all.min.css \ static/css/structure_browse.css \ static/css/cdr_vue_modal_styles.css \ - static/js/admin/vue-permissions-editor/dist/assets/index.css \ + static/js/admin/vue-cdr-admin/dist/assets/index.css \ > static/css/cdr_admin.css ifneq ($(VERSION), "") @@ -44,11 +44,11 @@ ifneq ($(VERSION), "") endif build-admin-npm: - # Build vue permissions application files - npm --prefix static/js/admin/vue-permissions-editor ci - npm --prefix static/js/admin/vue-permissions-editor run build + # Build vue admin application files + npm --prefix static/js/admin/vue-cdr-admin ci + npm --prefix static/js/admin/vue-cdr-admin run build - cp static/js/admin/vue-permissions-editor/dist/assets/vue-permissions-index.js static/js/vue-permissions-index.js + cp static/js/admin/vue-cdr-admin/dist/assets/vue-admin-index.js static/js/vue-admin-index.js build-access-concat: # Make sure file is empty diff --git a/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/services/DatastreamPermissionUtil.java b/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/services/DatastreamPermissionUtil.java index 5c1aa25431..44aa219d96 100644 --- a/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/services/DatastreamPermissionUtil.java +++ b/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/services/DatastreamPermissionUtil.java @@ -23,6 +23,7 @@ public class DatastreamPermissionUtil { DS_PERMISSION_MAP = new EnumMap<>(DatastreamType.class); DS_PERMISSION_MAP.put(DatastreamType.FULLTEXT_EXTRACTION, Permission.viewHidden); DS_PERMISSION_MAP.put(DatastreamType.JP2_ACCESS_COPY, Permission.viewAccessCopies); + DS_PERMISSION_MAP.put(DatastreamType.AUDIO_ACCESS_COPY, Permission.viewAccessCopies); DS_PERMISSION_MAP.put(DatastreamType.ACCESS_SURROGATE, Permission.viewAccessCopies); DS_PERMISSION_MAP.put(DatastreamType.MD_DESCRIPTIVE, Permission.viewMetadata); DS_PERMISSION_MAP.put(DatastreamType.MD_DESCRIPTIVE_HISTORY, Permission.viewHidden); diff --git a/etc/solr-config/access/conf/schema.xml b/etc/solr-config/access/conf/schema.xml index db4d4ab8dc..9bb6f9b5b4 100644 --- a/etc/solr-config/access/conf/schema.xml +++ b/etc/solr-config/access/conf/schema.xml @@ -225,7 +225,7 @@ - + diff --git a/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/filter/SetDatastreamFilterTest.java b/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/filter/SetDatastreamFilterTest.java index 5fc205886d..ca99dc58ca 100644 --- a/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/filter/SetDatastreamFilterTest.java +++ b/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/filter/SetDatastreamFilterTest.java @@ -45,6 +45,7 @@ import java.util.stream.Collectors; import static edu.unc.lib.boxc.indexing.solr.test.MockRepositoryObjectHelpers.makeFileObject; +import static edu.unc.lib.boxc.model.api.DatastreamType.AUDIO_ACCESS_COPY; import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY; import static edu.unc.lib.boxc.model.api.DatastreamType.ORIGINAL_FILE; import static edu.unc.lib.boxc.model.api.DatastreamType.TECHNICAL_METADATA; @@ -118,6 +119,7 @@ public class SetDatastreamFilterTest { private static final long PREMIS_SIZE = 893l; private static final long JP2_SIZE = 11; + private static final long AUDIO_SIZE = 11; private AutoCloseable closeable; @@ -255,6 +257,8 @@ public void fileObjectAudioOnlyBinaryTest() throws Exception { fileResource(TECHNICAL_METADATA.getId(), FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST)); when(binObj2.getBinaryStream()).thenReturn(getClass().getResourceAsStream("/datastream/techmd_mp3.xml")); when(fileObj.getBinaryObjects()).thenReturn(Arrays.asList(binObj, binObj2)); + List derivs = makeAudioDerivative(); + when(derivativeService.getDerivatives(pid)).thenReturn(derivs); dip.setContentObject(fileObj); filter.filter(dip); @@ -263,6 +267,8 @@ public void fileObjectAudioOnlyBinaryTest() throws Exception { FILE_MP3_SIZE, FILE_MP3_MIMETYPE, FILE_MP3_NAME, FILE_MP3_DIGEST, null, FILE_MP3_EXTENT); assertContainsDatastream(idb.getDatastream(), TECHNICAL_METADATA.getId(), FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST, null, null); + assertContainsDatastream(idb.getDatastream(), AUDIO_ACCESS_COPY.getId(), + AUDIO_SIZE, AUDIO_ACCESS_COPY.getMimetype(), "access.m4a", null, null, null); } @Test @@ -297,6 +303,8 @@ public void fileObjectAudioOnlyBinaryWithDotMillisecondsSeperatorTest() throws E fileResource(TECHNICAL_METADATA.getId(), FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST)); when(binObj2.getBinaryStream()).thenReturn(getClass().getResourceAsStream("/datastream/techmd_dot_separated_milliseconds.xml")); when(fileObj.getBinaryObjects()).thenReturn(Arrays.asList(binObj, binObj2)); + List derivs = makeAudioDerivative(); + when(derivativeService.getDerivatives(pid)).thenReturn(derivs); dip.setContentObject(fileObj); filter.filter(dip); @@ -305,6 +313,8 @@ public void fileObjectAudioOnlyBinaryWithDotMillisecondsSeperatorTest() throws E FILE_MP3_SIZE, FILE_MP3_MIMETYPE, FILE_MP3_NAME, FILE_MP3_DIGEST, null, FILE_MP3_EXTENT); assertContainsDatastream(idb.getDatastream(), TECHNICAL_METADATA.getId(), FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST, null, null); + assertContainsDatastream(idb.getDatastream(), AUDIO_ACCESS_COPY.getId(), + AUDIO_SIZE, AUDIO_ACCESS_COPY.getMimetype(), "access.m4a", null, null, null); } @Test @@ -768,4 +778,11 @@ private List makeJP2Derivative() throws IOException { return List.of(new Derivative(JP2_ACCESS_COPY, jp2File)); } + + private List makeAudioDerivative() throws IOException { + File m4aFile = derivDir.resolve("access.m4a").toFile(); + FileUtils.write(m4aFile, "m4a content", "UTF-8"); + + return List.of(new Derivative(AUDIO_ACCESS_COPY, m4aFile)); + } } diff --git a/model-api/src/main/java/edu/unc/lib/boxc/model/api/DatastreamType.java b/model-api/src/main/java/edu/unc/lib/boxc/model/api/DatastreamType.java index a71eb53275..d7088b81b3 100644 --- a/model-api/src/main/java/edu/unc/lib/boxc/model/api/DatastreamType.java +++ b/model-api/src/main/java/edu/unc/lib/boxc/model/api/DatastreamType.java @@ -13,6 +13,7 @@ */ public enum DatastreamType { ACCESS_SURROGATE("access_surrogate", "application/octet-stream", null, null, EXTERNAL), + AUDIO_ACCESS_COPY("audio", "audio/aac", "m4a", null, EXTERNAL), FULLTEXT_EXTRACTION("fulltext", "text/plain", "txt", null, EXTERNAL), JP2_ACCESS_COPY("jp2", "image/jp2", "jp2", null, EXTERNAL), MD_DESCRIPTIVE("md_descriptive", "text/xml", "xml", METADATA_CONTAINER, INTERNAL), diff --git a/model-fcrepo/src/test/java/edu/unc/lib/boxc/model/fcrepo/services/DerivativeServiceTest.java b/model-fcrepo/src/test/java/edu/unc/lib/boxc/model/fcrepo/services/DerivativeServiceTest.java index 4aec2ace4a..eede5b690a 100644 --- a/model-fcrepo/src/test/java/edu/unc/lib/boxc/model/fcrepo/services/DerivativeServiceTest.java +++ b/model-fcrepo/src/test/java/edu/unc/lib/boxc/model/fcrepo/services/DerivativeServiceTest.java @@ -14,6 +14,7 @@ import java.nio.file.Paths; import java.util.List; +import static edu.unc.lib.boxc.model.api.DatastreamType.AUDIO_ACCESS_COPY; import static edu.unc.lib.boxc.model.api.DatastreamType.FULLTEXT_EXTRACTION; import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY; import static edu.unc.lib.boxc.model.api.DatastreamType.ORIGINAL_FILE; @@ -73,18 +74,22 @@ public void testGetDerivativeNotExist() throws Exception { public void testGetDerivatives() throws Exception { File originalDerivFile1 = createDerivative(pid, FULLTEXT_EXTRACTION); File originalDerivFil21 = createDerivative(pid, JP2_ACCESS_COPY); + File originalDerivFile3 = createDerivative(pid, AUDIO_ACCESS_COPY); List derivs = derivativeService.getDerivatives(pid); - assertEquals(2, derivs.size()); + assertEquals(3, derivs.size()); Derivative textDeriv = findDerivative(derivs, FULLTEXT_EXTRACTION); Derivative jp2Deriv = findDerivative(derivs, JP2_ACCESS_COPY); + Derivative audioDeriv = findDerivative(derivs, AUDIO_ACCESS_COPY); assertNotNull(textDeriv); assertNotNull(jp2Deriv); + assertNotNull(audioDeriv); assertEquals(originalDerivFile1, textDeriv.getFile()); assertEquals(originalDerivFil21, jp2Deriv.getFile()); + assertEquals(originalDerivFile3, audioDeriv.getFile()); } @Test diff --git a/operations-jms/src/main/java/edu/unc/lib/boxc/operations/jms/viewSettings/ViewSettingRequest.java b/operations-jms/src/main/java/edu/unc/lib/boxc/operations/jms/viewSettings/ViewSettingRequest.java index 34ec5cbdf5..77605f10cf 100644 --- a/operations-jms/src/main/java/edu/unc/lib/boxc/operations/jms/viewSettings/ViewSettingRequest.java +++ b/operations-jms/src/main/java/edu/unc/lib/boxc/operations/jms/viewSettings/ViewSettingRequest.java @@ -6,7 +6,7 @@ import edu.unc.lib.boxc.auth.fcrepo.models.AgentPrincipalsImpl; /** - * Request object for updating the view settings of the UV + * Request object for updating the view settings of Clover * @author sharonluong */ public class ViewSettingRequest { diff --git a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/HasValuesFilter.java b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/HasValuesFilter.java new file mode 100644 index 0000000000..9c2623ad38 --- /dev/null +++ b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/HasValuesFilter.java @@ -0,0 +1,34 @@ +package edu.unc.lib.boxc.search.solr.filters; + +import edu.unc.lib.boxc.search.api.SearchFieldKey; +import edu.unc.lib.boxc.search.api.filters.QueryFilter; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Filter which restricts results to entries which contain populated values for the given key with + * the specified field search value + * + * @author lfarrell + */ +public class HasValuesFilter implements QueryFilter { + private final SearchFieldKey fieldKey; + private final List fieldValues; + + protected HasValuesFilter(SearchFieldKey fieldKey, List fieldValues) { + this.fieldKey = fieldKey; + this.fieldValues = fieldValues; + } + + @Override + public String toFilterString() { + return fieldValues.stream().map(v -> getFieldKey().getSolrField() + ":" + v) + .collect(Collectors.joining(" OR ")); + } + + @Override + public SearchFieldKey getFieldKey() { + return fieldKey; + } +} diff --git a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/IIIFv3ViewableFilter.java b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/IIIFv3ViewableFilter.java index ac163a752a..686e16de47 100644 --- a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/IIIFv3ViewableFilter.java +++ b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/IIIFv3ViewableFilter.java @@ -23,7 +23,8 @@ public String toFilterString() { var fileTypeFilter = getFileTypes().stream().map( type -> fileTypeField + ":" + type) .collect(Collectors.joining(" OR ", "(", ")")); var datastreamFilter = SearchFieldKey.DATASTREAM.getSolrField() + ":" + DatastreamType.JP2_ACCESS_COPY.getId() + "|*"; - return "(" + fileTypeFilter + ") OR (" + datastreamFilter + ")"; + var datastreamFilterAudio = SearchFieldKey.DATASTREAM.getSolrField() + ":" + DatastreamType.AUDIO_ACCESS_COPY.getId() + "|*"; + return "(" + fileTypeFilter + ") OR (" + datastreamFilter + ") OR (" + datastreamFilterAudio + ")"; } @Override diff --git a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactory.java b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactory.java index 9eb72f0840..9e43348691 100644 --- a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactory.java +++ b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactory.java @@ -49,4 +49,14 @@ public static QueryFilter createFilter(SearchFieldKey fieldKey) { public static QueryFilter createIIIFv3ViewableFilter(List fileTypes) { return new IIIFv3ViewableFilter(fileTypes); } + + /** + * + * @param fieldKey searchField + * @param fieldValues list of values to search for + * @return new QueryFilter instance with the provided file type + */ + public static QueryFilter createHasValuesFilter(SearchFieldKey fieldKey, List fieldValues) { + return new HasValuesFilter(fieldKey, fieldValues); + } } diff --git a/search-solr/src/test/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactoryTest.java b/search-solr/src/test/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactoryTest.java index 528a9cd260..f71714ad97 100644 --- a/search-solr/src/test/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactoryTest.java +++ b/search-solr/src/test/java/edu/unc/lib/boxc/search/solr/filters/QueryFilterFactoryTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import java.util.HashSet; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -33,4 +34,10 @@ public void HasPopulatedFieldFilterTest() { var filter = QueryFilterFactory.createFilter(SearchFieldKey.STREAMING_TYPE); assertInstanceOf(HasPopulatedFieldFilter.class, filter); } + + @Test + public void HasValuesFilterTest() { + var filter = QueryFilterFactory.createHasValuesFilter(SearchFieldKey.FILE_FORMAT_TYPE, List.of("application/pdf")); + assertInstanceOf(HasValuesFilter.class, filter); + } } diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/audio/AudioDerivativeProcessor.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/audio/AudioDerivativeProcessor.java new file mode 100644 index 0000000000..628edb0d9d --- /dev/null +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/audio/AudioDerivativeProcessor.java @@ -0,0 +1,51 @@ +package edu.unc.lib.boxc.services.camel.audio; + +import edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.regex.Pattern; + +/** + * Processor which validates and prepares audio objects for producing derivatives + * @author krwong + */ +public class AudioDerivativeProcessor implements Processor { + private static final Logger log = LoggerFactory.getLogger(AudioDerivativeProcessor.class); + + private static final Pattern MIMETYPE_PATTERN = + Pattern.compile("^(audio.(basic|mpeg|mp4|x-aiff|x-ms-wma|x-wave|x-wav|wav|wave|3gpp))$"); + + /** + * Returns true if the subject of the exchange is a binary which + * is eligible for having audio derivatives generated from it. + * @param exchange + * @return + */ + public static boolean allowedAudioType(Exchange exchange) { + Message in = exchange.getIn(); + String mimetype = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType); + String binPath = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryPath); + + if (!MIMETYPE_PATTERN.matcher(mimetype).matches()) { + log.debug("File type {} on object {} is not applicable for audio derivatives", mimetype, binPath); + return false; + } + + log.debug("Object {} with type {} is permitted for audio derivatives", binPath, mimetype); + return true; + } + + @Override + public void process(Exchange exchange) throws Exception { + Message in = exchange.getIn(); + String mimetype = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType); + + String binPath = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryPath); + log.debug("Keeping existing audio path as {} for type {}", binPath, mimetype); + in.setHeader(CdrFcrepoHeaders.CdrAudioPath, binPath); + } +} diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/audio/AudioEnhancementsRouter.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/audio/AudioEnhancementsRouter.java new file mode 100644 index 0000000000..c69a5c6377 --- /dev/null +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/audio/AudioEnhancementsRouter.java @@ -0,0 +1,62 @@ +package edu.unc.lib.boxc.services.camel.audio; + +import edu.unc.lib.boxc.model.api.exceptions.RepositoryException; +import edu.unc.lib.boxc.services.camel.images.AddDerivativeProcessor; +import edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders; +import org.apache.camel.BeanInject; +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spi.UuidGenerator; +import org.apache.camel.support.DefaultUuidGenerator; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Router which triggers the creation of audio derivatives + * @author krwong + */ +public class AudioEnhancementsRouter extends RouteBuilder { + private static final Logger log = getLogger(AudioEnhancementsRouter.class); + + @BeanInject(value = "addAudioAccessCopyProcessor") + private AddDerivativeProcessor addAudioAccessCopyProcessor; + + private UuidGenerator uuidGenerator; + + /** + * Configure the audio enhancement route workflow. + */ + @Override + public void configure() throws Exception { + AudioDerivativeProcessor audioDerivProcessor = new AudioDerivativeProcessor(); + + uuidGenerator = new DefaultUuidGenerator(); + + onException(RepositoryException.class) + .redeliveryDelay("{{error.retryDelay}}") + .maximumRedeliveries("{{error.maxRedeliveries}}") + .backOffMultiplier("{{error.backOffMultiplier}}") + .retryAttemptedLogLevel(LoggingLevel.WARN); + + from("direct:process.enhancement.audioAccessCopy") + .routeId("AudioAccessCopy") + .startupOrder(25) + .log(LoggingLevel.DEBUG, log, "Access copy triggered") + .filter().method(addAudioAccessCopyProcessor, "needsRun") + .filter().method(audioDerivProcessor, "allowedAudioType") + .bean(audioDerivProcessor) + .log(LoggingLevel.INFO, log, "Creating/Updating AAC access copy for ${headers[CdrAudioPath]}") + // Generate an random identifier to avoid derivative collisions + .setBody(exchange -> uuidGenerator.generateUuid()) + .setHeader(CdrFcrepoHeaders.CdrTempPath, simple("${properties:services.tempDirectory}/${body}-audio")) + .doTry() + .recipientList(simple("exec:/bin/sh?args=${properties:cdr.enhancement.bin}/convertAudio.sh " + + "${headers[CdrAudioPath]} ${headers[CdrTempPath]}")) + .bean(addAudioAccessCopyProcessor) + .endDoTry() + .doFinally() + .bean(addAudioAccessCopyProcessor, "cleanupTempFile") + .end(); + } +} diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouter.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouter.java index b65a8752c1..094eb48f31 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouter.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouter.java @@ -2,6 +2,8 @@ import edu.unc.lib.boxc.services.camel.fulltext.FulltextProcessor; import edu.unc.lib.boxc.services.camel.images.ImageDerivativeProcessor; +import edu.unc.lib.boxc.services.camel.audio.AudioDerivativeProcessor; + import org.apache.camel.BeanInject; import org.apache.camel.LoggingLevel; import org.apache.camel.PropertyInject; @@ -28,6 +30,9 @@ public class DestroyDerivativesRouter extends RouteBuilder { @BeanInject(value = "destroyFulltextProcessor") private DestroyDerivativesProcessor destroyFulltextProcessor; + @BeanInject(value = "destroyAudioProcessor") + private DestroyDerivativesProcessor destroyAudioProcessor; + private String destroyDerivativesStreamCamel; private long errorRetryDelay; private int errorMaxRedeliveries; @@ -51,6 +56,8 @@ public void configure() throws Exception { .to("direct:image.derivatives.destroy") .when(method(FulltextProcessor.class, "allowedTextType")) .to("direct:fulltext.derivatives.destroy") + .when(method(AudioDerivativeProcessor.class, "allowedAudioType")) + .to("direct:audio.derivatives.destroy") .end(); from("direct:fulltext.derivatives.destroy") @@ -64,6 +71,12 @@ public void configure() throws Exception { .startupOrder(202) .log(LoggingLevel.DEBUG, log, "Destroying access copy derivatives") .bean(destroyAccessCopyProcessor); + + from("direct:audio.derivatives.destroy") + .routeId("CdrDestroyAudio") + .startupOrder(199) + .log(LoggingLevel.DEBUG, log, "Destroying derivative audio files") + .bean(destroyAudioProcessor); } public void setDestroyedMsgProcessor(DestroyedMsgProcessor destroyedMsgProcessor) { @@ -78,6 +91,10 @@ public void setDestroyFulltextProcessor(DestroyDerivativesProcessor destroyFullt this.destroyFulltextProcessor = destroyFulltextProcessor; } + public void setDestroyAudioProcessor(DestroyDerivativesProcessor destroyAudioProcessor) { + this.destroyAudioProcessor = destroyAudioProcessor; + } + @PropertyInject("cdr.destroy.derivatives.stream.camel") public void setDestroyDerivativesStreamCamel(String destroyDerivativesStreamCamel) { this.destroyDerivativesStreamCamel = destroyDerivativesStreamCamel; diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouter.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouter.java index 7f11f96f25..c35449159f 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouter.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouter.java @@ -37,7 +37,7 @@ public class EnhancementRouter extends RouteBuilder { @PropertyInject(value = "cdr.enhancement.processingThreads") private Integer enhancementThreads; - private static final String DEFAULT_ENHANCEMENTS = "imageAccessCopy,extractFulltext"; + private static final String DEFAULT_ENHANCEMENTS = "imageAccessCopy,extractFulltext,audioAccessCopy"; @Override public void configure() throws Exception { diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageEnhancementsRouter.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageEnhancementsRouter.java index f763bb6b74..3ac5c266dc 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageEnhancementsRouter.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageEnhancementsRouter.java @@ -21,6 +21,9 @@ public class ImageEnhancementsRouter extends RouteBuilder { private static final Logger log = getLogger(ImageEnhancementsRouter.class); + private static final int CACHE_INVALIDATE_THREADS = 5; + private static final int CACHE_INVALIDATE_REQUESTS_PER_SEC = 10; + @BeanInject(value = "addAccessCopyProcessor") private AddDerivativeProcessor addAccessCopyProcessor; @@ -59,7 +62,12 @@ public void configure() throws Exception { .recipientList(simple("exec:/bin/sh?args=${properties:cdr.enhancement.bin}/convertJp2.sh " + "${headers[CdrImagePath]} ${headers[CdrMimeType]} ${headers[CdrTempPath]}")) .bean(addAccessCopyProcessor) - .bean(imageCacheInvalidationProcessor) + // Process cache invalidation asynchronously with a limited number of threads + .threads(CACHE_INVALIDATE_THREADS) + // Limit the max number of requests per second + .throttle(CACHE_INVALIDATE_REQUESTS_PER_SEC) + .bean(imageCacheInvalidationProcessor) + .end() .endDoTry() .doFinally() .bean(addAccessCopyProcessor, "cleanupTempFile") diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java index 589da47484..40e0b6b9ed 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java @@ -3,9 +3,15 @@ import edu.unc.lib.boxc.model.fcrepo.ids.PIDs; import edu.unc.lib.boxc.operations.jms.thumbnails.ImportThumbnailRequestSerializationHelper; import org.apache.camel.Exchange; +import org.apache.camel.Message; import org.apache.camel.Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import static edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders.CdrBinaryMimeType; import static edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders.CdrBinaryPath; @@ -17,6 +23,7 @@ * @author snluong */ public class ImportThumbnailRequestProcessor implements Processor { + private static final Logger log = LoggerFactory.getLogger(ImportThumbnailRequestProcessor.class); @Override public void process(Exchange exchange) throws IOException { var in = exchange.getIn(); @@ -30,4 +37,20 @@ public void process(Exchange exchange) throws IOException { in.setHeader(CdrBinaryMimeType, mimetype); in.setHeader(FCREPO_URI, repoPath); } + + /** + * Deletes the temporarily stored uploaded thumbnail file + * @param exchange + * @throws IOException + */ + public void cleanupTempThumbnailFile(Exchange exchange) throws IOException { + final Message in = exchange.getIn(); + String tempValue = (String) in.getHeader(CdrBinaryPath); + Path tempPath = Paths.get(tempValue); + + boolean deleted = Files.deleteIfExists(tempPath); + if (deleted) { + log.debug("Cleaned up leftover temp file {}", tempPath); + } + } } diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouter.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouter.java index 6c68957820..723f6e4555 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouter.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouter.java @@ -30,11 +30,16 @@ public void configure() throws Exception { .bean(thumbnailRequestProcessor); from("{{cdr.import.thumbnails.stream.camel}}") - .routeId("DcrImportThumbnails") - .process(importThumbnailRequestProcessor) - .log(DEBUG, log, - "Received thumbnail request: importing thumbnail for ${headers[CamelFcrepoUri]}") + .routeId("DcrImportThumbnails") + .process(importThumbnailRequestProcessor) + .log(DEBUG, log, + "Received thumbnail request: importing thumbnail for ${headers[CamelFcrepoUri]}") + .doTry() // trigger JP2 generation sequentially followed by indexing - .to("direct:process.enhancement.imageAccessCopy", "direct:solrIndexing"); + .to("direct:process.enhancement.imageAccessCopy", "direct:solrIndexing") + .endDoTry() + .doFinally() + .bean(importThumbnailRequestProcessor, "cleanupTempThumbnailFile") + .end(); } } diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/util/CdrFcrepoHeaders.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/util/CdrFcrepoHeaders.java index 7ec83c6275..2061a4707b 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/util/CdrFcrepoHeaders.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/util/CdrFcrepoHeaders.java @@ -22,6 +22,9 @@ public abstract class CdrFcrepoHeaders { // URI identifying the location public static final String CdrImagePath = "CdrImagePath"; + // URI identifying the location + public static final String CdrAudioPath = "CdrAudioPath"; + // File path for a temp file public static final String CdrTempPath = "CdrTempPath"; diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/viewSettings/ViewSettingRouter.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/viewSettings/ViewSettingRouter.java index 2cadb72aa2..656df013c9 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/viewSettings/ViewSettingRouter.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/viewSettings/ViewSettingRouter.java @@ -9,7 +9,7 @@ import static org.slf4j.LoggerFactory.getLogger; /** - * Router for processing requests to update view setting of UV + * Router for processing requests to update view setting of Clover * * @author snluong */ diff --git a/services-camel-app/src/main/webapp/WEB-INF/service-context.xml b/services-camel-app/src/main/webapp/WEB-INF/service-context.xml index ad70ec2706..5af213c7c4 100644 --- a/services-camel-app/src/main/webapp/WEB-INF/service-context.xml +++ b/services-camel-app/src/main/webapp/WEB-INF/service-context.xml @@ -98,6 +98,8 @@ + + @@ -221,6 +223,14 @@ + + + + + + + + @@ -239,6 +249,11 @@ + + + + + @@ -545,6 +560,7 @@ edu.unc.lib.boxc.services.camel.enhancements edu.unc.lib.boxc.services.camel.images edu.unc.lib.boxc.services.camel.fulltext + edu.unc.lib.boxc.services.camel.audio edu.unc.lib.boxc.services.camel.solr edu.unc.lib.boxc.services.camel.binaryCleanup diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/audio/AudioEnhancementsRouterTest.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/audio/AudioEnhancementsRouterTest.java new file mode 100644 index 0000000000..3a301f4a5a --- /dev/null +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/audio/AudioEnhancementsRouterTest.java @@ -0,0 +1,212 @@ +package edu.unc.lib.boxc.services.camel.audio; + +import edu.unc.lib.boxc.fcrepo.FcrepoJmsConstants; +import edu.unc.lib.boxc.services.camel.images.AddDerivativeProcessor; +import org.apache.camel.BeanInject; +import org.apache.camel.CamelExecutionException; +import org.apache.camel.EndpointInject; +import org.apache.camel.Exchange; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.PropertyInject; +import org.apache.camel.builder.AdviceWith; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.spring.junit5.CamelSpringTestSupport; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static edu.unc.lib.boxc.model.api.ids.RepositoryPathConstants.HASHED_PATH_DEPTH; +import static edu.unc.lib.boxc.model.api.ids.RepositoryPathConstants.HASHED_PATH_SIZE; +import static edu.unc.lib.boxc.model.api.rdf.Fcrepo4Repository.Binary; +import static edu.unc.lib.boxc.model.fcrepo.ids.RepositoryPaths.idToPath; +import static edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders.CdrBinaryMimeType; +import static org.fcrepo.camel.FcrepoHeaders.FCREPO_AGENT; +import static org.fcrepo.camel.FcrepoHeaders.FCREPO_BASE_URL; +import static org.fcrepo.camel.FcrepoHeaders.FCREPO_DATE_TIME; +import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_TYPE; +import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AudioEnhancementsRouterTest extends CamelSpringTestSupport { + private static final long timestamp = 1428360320168L; + private static final String userID = "bypassAdmin"; + private static final String userAgent = "curl/7.37.1"; + private static final String fileID = "343b3da4-8876-42f5-8821-7aabb65e0f19"; + private final String eventTypes = FcrepoJmsConstants.EVENT_NS + "ResourceCreation"; + private final String audioAccessCopy = "AudioAccessCopy"; + + @PropertyInject(value = "fcrepo.baseUrl") + private static String baseUri; + + @EndpointInject("mock:fcrepo") + protected MockEndpoint resultEndpoint; + + @Produce("direct:process.binary.original") + protected ProducerTemplate template; + + @BeanInject(value = "addAudioAccessCopyProcessor") + private AddDerivativeProcessor addAudioAccessCopyProcessor; + + @Override + protected AbstractApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext("/service-context.xml", "/audio-context.xml"); + } + + @AfterEach + public void cleanup() throws IOException { + FileUtils.deleteDirectory(new File("target/34")); + } + + @Test + public void testAudioAccessCopyRouteNoForceNoFileExists() throws Exception { + when(addAudioAccessCopyProcessor.needsRun(any())).thenReturn(true); + createContext(audioAccessCopy); + + var shEndpoint = getMockEndpoint("mock:exec:/bin/sh"); + shEndpoint.expectedMessageCount(1); + + Map headers = createEvent(fileID, eventTypes, "false"); + + template.sendBodyAndHeaders("", headers); + + verify(addAudioAccessCopyProcessor).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor).cleanupTempFile(any(Exchange.class)); + shEndpoint.assertIsSatisfied(); + } + + @Test + public void testAudioAccessCopyRouteScriptFails() throws Exception { + when(addAudioAccessCopyProcessor.needsRun(any())).thenReturn(true); + createContext(audioAccessCopy); + + MockEndpoint shEndpoint = getMockEndpoint("mock:exec:/bin/sh"); + shEndpoint.expectedMessageCount(1); + shEndpoint.whenAnyExchangeReceived(exchange -> { + throw new IllegalStateException("Failing run of exec"); + }); + + Map headers = createEvent(fileID, eventTypes, "false"); + try { + template.sendBodyAndHeaders("", headers); + fail("Exception expected to be thrown"); + } catch (CamelExecutionException e) { + assertTrue(e.getCause() instanceof IllegalStateException); + } + + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor).cleanupTempFile(any(Exchange.class)); + shEndpoint.assertIsSatisfied(); + } + + @Test + public void testAudioAccessCopyRouteForceNoFileExists() throws Exception { + when(addAudioAccessCopyProcessor.needsRun(any())).thenReturn(true); + createContext(audioAccessCopy); + + var shEndpoint = getMockEndpoint("mock:exec:/bin/sh"); + shEndpoint.expectedMessageCount(1); + + Map headers = createEvent(fileID, eventTypes, "true"); + + template.sendBodyAndHeaders("", headers); + + verify(addAudioAccessCopyProcessor).process(any(Exchange.class)); + shEndpoint.assertIsSatisfied(); + } + + @Test + public void testAudioAccessCopyRouteNoForceFileExists() throws Exception { + String derivativePath = idToPath(fileID, HASHED_PATH_DEPTH, HASHED_PATH_SIZE); + File existingFile = new File("target/" + derivativePath + "/" + fileID + ".m4a"); + FileUtils.writeStringToFile(existingFile, "extracted text", "utf-8"); + + createContext(audioAccessCopy); + + var shEndpoint = getMockEndpoint("mock:exec:/bin/sh"); + shEndpoint.expectedMessageCount(0); + + Map headers = createEvent(fileID, eventTypes, "false"); + + template.sendBodyAndHeaders("", headers); + + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).cleanupTempFile(any(Exchange.class)); + shEndpoint.assertIsSatisfied(); + } + + @Test + public void testAudioAccessCopyRouteForceFileExists() throws Exception { + when(addAudioAccessCopyProcessor.needsRun(any())).thenReturn(true); + String derivativePath = idToPath(fileID, HASHED_PATH_DEPTH, HASHED_PATH_SIZE); + File existingFile = new File("target/" + derivativePath + "/" + fileID + ".m4a"); + FileUtils.writeStringToFile(existingFile, "extracted text", "utf-8"); + + createContext(audioAccessCopy); + + var shEndpoint = getMockEndpoint("mock:exec:/bin/sh"); + shEndpoint.expectedMessageCount(1); + + Map headers = createEvent(fileID, eventTypes, "true"); + + template.sendBodyAndHeaders("", headers); + + verify(addAudioAccessCopyProcessor).process(any(Exchange.class)); + shEndpoint.assertIsSatisfied(); + } + + @Test + public void testAudioAccessCopyRejection() throws Exception { + createContext(audioAccessCopy); + + when(addAudioAccessCopyProcessor.needsRun(any())).thenReturn(true); + var audioEndpoint = getMockEndpoint("mock:process.enhancement.audioAccessCopy"); + audioEndpoint.expectedMessageCount(0); + + Map headers = createEvent(fileID, eventTypes, "false"); + headers.put(CdrBinaryMimeType, "audio/aac"); + + template.sendBodyAndHeaders("", headers); + + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); + audioEndpoint.assertIsSatisfied(); + } + + private void createContext(String routeName) throws Exception { + AdviceWith.adviceWith(context, routeName, a -> { + a.replaceFromWith("direct:process.binary.original"); + a.mockEndpointsAndSkip("*"); + }); + context.start(); + } + + private static Map createEvent(final String identifier, final String eventTypes, + final String force) { + final Map headers = new HashMap<>(); + headers.put(FCREPO_URI, identifier); + headers.put(FCREPO_DATE_TIME, timestamp); + headers.put(FCREPO_AGENT, Arrays.asList(userID, userAgent)); + headers.put(FCREPO_EVENT_TYPE, eventTypes); + headers.put(FCREPO_BASE_URL, baseUri); + headers.put(FcrepoJmsConstants.EVENT_TYPE, "ResourceCreation"); + headers.put(FcrepoJmsConstants.IDENTIFIER, "original_file"); + headers.put(FcrepoJmsConstants.RESOURCE_TYPE, Binary.getURI()); + headers.put(CdrBinaryMimeType, "audio/wav"); + headers.put("force", force); + + return headers; + } +} diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterIT.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterIT.java index babaa31b81..76ad705c3b 100644 --- a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterIT.java +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterIT.java @@ -101,6 +101,8 @@ public class DestroyDerivativesRouterIT extends CamelSpringTestSupport { private DestroyDerivativesProcessor destroyFulltextProcessor; + private DestroyDerivativesProcessor destroyAudioProcessor; + private DestroyObjectsJob destroyJob; private AgentPrincipals agent; @@ -146,6 +148,7 @@ public void init() { destroyedMsgProcessor = applicationContext.getBean(DestroyedMsgProcessor.class); destroyAccessCopyProcessor = applicationContext.getBean("destroyAccessCopyProcessor", DestroyDerivativesProcessor.class); destroyFulltextProcessor = applicationContext.getBean("destroyFulltextProcessor", DestroyDerivativesProcessor.class); + destroyAudioProcessor = applicationContext.getBean("destroyAudioProcessor", DestroyDerivativesProcessor.class); TestHelper.setContentBase(baseAddress); @@ -194,6 +197,7 @@ public void destroyImageTest() throws Exception { verify(destroyAccessCopyProcessor).process(any(Exchange.class)); verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } @Test @@ -219,6 +223,7 @@ public void destroyCollectionImageTest() throws Exception { verify(destroyAccessCopyProcessor).process(any(Exchange.class)); verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } @Test @@ -234,6 +239,7 @@ public void destroyCollectionNoImageTest() throws Exception { verify(destroyAccessCopyProcessor, never()).process(any(Exchange.class)); verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } @Test @@ -251,6 +257,7 @@ public void destroyTextTest() throws Exception { verify(destroyAccessCopyProcessor, never()).process(any(Exchange.class)); verify(destroyFulltextProcessor).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } @Test @@ -267,6 +274,24 @@ public void invalidTypeTest() throws Exception { verify(destroyAccessCopyProcessor, never()).process(any(Exchange.class)); verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); + } + + @Test + public void destroyAudioTest() throws Exception { + WorkObject work = repoObjectFactory.createWorkObject(null); + FileObject fileObj = addFileToWork(work, "audio/wav"); + work.addMember(fileObj); + + treeIndexer.indexAll(baseAddress); + + markForDeletion(fileObj.getPid()); + initializeDestroyJob(Collections.singletonList(fileObj.getPid())); + destroyJob.run(); + + verify(destroyAccessCopyProcessor, never()).process(any(Exchange.class)); + verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor).process(any(Exchange.class)); } private FileObject addFileToWork(WorkObject work, String mimetype) throws Exception { diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterTest.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterTest.java index 7751b8912d..14986ef3bb 100644 --- a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterTest.java +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouterTest.java @@ -45,6 +45,9 @@ public class DestroyDerivativesRouterTest extends CamelTestSupport { @Mock private DestroyDerivativesProcessor destroyFulltextProcessor; + @Mock + private DestroyDerivativesProcessor destroyAudioProcessor; + @TempDir public Path tmpFolder; @@ -55,6 +58,7 @@ protected RouteBuilder createRouteBuilder() throws Exception { router.setDestroyedMsgProcessor(destroyedMsgProcessor); router.setDestroyAccessCopyProcessor(destroyAccessCopyProcessor); router.setDestroyFulltextProcessor(destroyFulltextProcessor); + router.setDestroyAudioProcessor(destroyAudioProcessor); router.setDestroyDerivativesStreamCamel("direct:destroy.derivatives.stream"); return router; } @@ -128,6 +132,7 @@ public void destroyTextDerivative() throws Exception { verify(destroyFulltextProcessor).process(any(Exchange.class)); verify(destroyCollectionSrcImgProcessor, never()).process(any(Exchange.class)); verify(destroyAccessCopyProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } @Test @@ -140,6 +145,7 @@ public void destroyImageThumbnailDerivative() throws Exception { verify(destroyAccessCopyProcessor).process(any(Exchange.class)); verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } @Test @@ -154,6 +160,7 @@ public void destroyImageThumbnailDerivativeCollection() throws Exception { verify(destroyAccessCopyProcessor).process(any(Exchange.class)); verify(destroyFulltextProcessor, never()).process(any(Exchange.class)); + verify(destroyAudioProcessor, never()).process(any(Exchange.class)); } // See if any messages are routed for object with no mimetype diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouterIT.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouterIT.java index 91af35f93f..928026fa2c 100644 --- a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouterIT.java +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouterIT.java @@ -89,6 +89,8 @@ public class EnhancementRouterIT extends CamelSpringTestSupport { private FulltextProcessor fulltextProcessor; + private AddDerivativeProcessor addAudioAccessCopyProcessor; + private UpdateDescriptionService updateDescriptionService; @TempDir @@ -116,15 +118,22 @@ public void init() throws Exception { addAccessCopyProcessor = applicationContext.getBean("addAccessCopyProcessor", AddDerivativeProcessor.class); solrIngestProcessor = applicationContext.getBean("solrIngestProcessor", SolrIngestProcessor.class); fulltextProcessor = applicationContext.getBean("fulltextProcessor", FulltextProcessor.class); + addAudioAccessCopyProcessor = applicationContext.getBean("addAudioAccessCopyProcessor", AddDerivativeProcessor.class); updateDescriptionService = applicationContext.getBean(UpdateDescriptionService.class); when(addAccessCopyProcessor.needsRun(any(Exchange.class))).thenReturn(true); + when(addAudioAccessCopyProcessor.needsRun(any(Exchange.class))).thenReturn(true); + TestHelper.setContentBase(baseAddress); tempDir = Files.createDirectory(tmpFolder.resolve("target")).toFile(); File jp2ScriptFile = new File("target/convertJp2.sh"); FileUtils.writeStringToFile(jp2ScriptFile, "exit 0", "utf-8"); jp2ScriptFile.deleteOnExit(); + + File audioScriptFile = new File("target/convertAudio.sh"); + FileUtils.writeStringToFile(audioScriptFile, "exit 0", "utf-8"); + audioScriptFile.deleteOnExit(); } @AfterEach @@ -150,6 +159,7 @@ public void nonBinaryWithSourceImages() throws Exception { template.sendBodyAndHeaders("", headers); verify(solrIngestProcessor, timeout(ALLOW_WAIT)).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); } @Test @@ -160,6 +170,7 @@ public void nonBinaryNoSourceImages() throws Exception { template.sendBodyAndHeaders("", headers); verify(solrIngestProcessor, timeout(ALLOW_WAIT)).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); } @Test @@ -184,6 +195,7 @@ public void testBinaryImageFile() throws Exception { verify(addAccessCopyProcessor, timeout(ALLOW_WAIT)).process(any(Exchange.class)); // Indexing triggered for binary parent verify(solrIngestProcessor, timeout(ALLOW_WAIT)).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); } @Test @@ -210,6 +222,7 @@ public void testBinaryMetadataFile() throws Exception { verify(addAccessCopyProcessor, never()).process(any(Exchange.class)); verify(solrIngestProcessor, never()).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); } @Test @@ -234,6 +247,7 @@ public void testInvalidFile() throws Exception { verify(fulltextProcessor, never()).process(any(Exchange.class)); verify(solrIngestProcessor, never()).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); } @Test @@ -281,6 +295,30 @@ public void testDepositManifestFileMetadata() throws Exception { verify(addAccessCopyProcessor, never()).process(any(Exchange.class)); verify(solrIngestProcessor, never()).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, never()).process(any(Exchange.class)); + } + + @Test + public void testAudioFile() throws Exception { + FileObject fileObj = repoObjectFactory.createFileObject(null); + var storageUri = storageLocationTestHelper.makeTestStorageUri(DatastreamPids.getOriginalFilePid(fileObj.getPid())); + FileUtils.writeStringToFile(new File(storageUri), FILE_CONTENT, "UTF-8"); + BinaryObject binObj = fileObj.addOriginalFile(storageUri, + null, "audio/wav", null, null); + + // Separate exchanges when multicasting + NotifyBuilder notify = new NotifyBuilder(cdrEnhancements) + .whenCompleted(12) + .create(); + + final Map headers = createEvent(binObj.getPid(), Binary.getURI()); + template.sendBodyAndHeaders("", headers); + + boolean result = notify.matches(5L, TimeUnit.SECONDS); + + verify(addAccessCopyProcessor, never()).process(any(Exchange.class)); + verify(addAudioAccessCopyProcessor, timeout(ALLOW_WAIT)).process(any(Exchange.class)); + verify(solrIngestProcessor, timeout(ALLOW_WAIT)).process(any(Exchange.class)); } private Map createEvent(PID pid, String... type) { diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java index c04c27a2eb..4ccb3e3c30 100644 --- a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java @@ -3,24 +3,30 @@ import edu.unc.lib.boxc.auth.api.models.AgentPrincipals; import edu.unc.lib.boxc.auth.fcrepo.models.AccessGroupSetImpl; import edu.unc.lib.boxc.auth.fcrepo.models.AgentPrincipalsImpl; +import edu.unc.lib.boxc.model.api.ids.PID; import edu.unc.lib.boxc.operations.jms.thumbnails.ImportThumbnailRequest; import edu.unc.lib.boxc.operations.jms.thumbnails.ImportThumbnailRequestSerializationHelper; import edu.unc.lib.boxc.services.camel.TestHelper; +import org.apache.camel.Exchange; +import org.apache.camel.Message; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mock; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import static edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders.CdrBinaryMimeType; import static edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders.CdrBinaryPath; import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; public class ImportThumbnailProcessorTest { @@ -28,13 +34,29 @@ public class ImportThumbnailProcessorTest { @TempDir private Path path; + private Path tempThumbnailPath; private final AgentPrincipals agent = new AgentPrincipalsImpl("user", new AccessGroupSetImpl("agroup")); + private Exchange exchange; + private Message message; + private String mimetype = "image/jpeg"; + private PID pid; private AutoCloseable closeable; @BeforeEach public void init() throws IOException { closeable = openMocks(this); processor = new ImportThumbnailRequestProcessor(); + pid = TestHelper.makePid(); + tempThumbnailPath = path.resolve(pid.getId() + ".png"); + + var request = new ImportThumbnailRequest(); + request.setMimetype(mimetype); + request.setAgent(agent); + request.setStoragePath(path); + request.setPidString(pid.getId()); + + exchange = TestHelper.mockExchange(ImportThumbnailRequestSerializationHelper.toJson(request)); + message = exchange.getIn(); } @AfterEach @@ -44,21 +66,20 @@ void closeService() throws Exception { @Test public void testImportThumbnail() throws IOException { - var pid = TestHelper.makePid(); - var mimetype = "image/jpeg"; - var request = new ImportThumbnailRequest(); - request.setMimetype(mimetype); - request.setAgent(agent); - request.setStoragePath(path); - request.setPidString(pid.getId()); - - var exchange = TestHelper.mockExchange(ImportThumbnailRequestSerializationHelper.toJson(request)); - var message = exchange.getIn(); - processor.process(exchange); verify(message).setHeader(FCREPO_URI, pid.getRepositoryPath()); verify(message).setHeader(CdrBinaryMimeType, mimetype); verify(message).setHeader(CdrBinaryPath, path.toString()); } + + @Test + public void cleanupTempThumbnailFileTest() throws Exception { + Files.write(tempThumbnailPath, List.of("fake image")); + assertTrue(Files.exists(tempThumbnailPath)); + when(message.getHeader(eq(CdrBinaryPath))) + .thenReturn(tempThumbnailPath.toString()); + processor.cleanupTempThumbnailFile(exchange); + assertFalse(Files.exists(tempThumbnailPath)); + } } diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouterTest.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouterTest.java index bb849fe838..219116cf5d 100644 --- a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouterTest.java +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ThumbnailRouterTest.java @@ -9,6 +9,7 @@ import edu.unc.lib.boxc.operations.jms.thumbnails.ThumbnailRequestSerializationHelper; import edu.unc.lib.boxc.services.camel.TestHelper; import org.apache.camel.BeanInject; +import org.apache.camel.Exchange; import org.apache.camel.Produce; import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.AdviceWith; @@ -68,6 +69,7 @@ public void importRequestSentTest() throws Exception { template.sendBody(body); verify(importProcessor).process(any()); + verify(importProcessor).cleanupTempThumbnailFile(any(Exchange.class)); solrEndpoint.assertIsSatisfied(); imageAccessCopyEndpoint.assertIsSatisfied(); } diff --git a/services-camel-app/src/test/resources/audio-context.xml b/services-camel-app/src/test/resources/audio-context.xml new file mode 100644 index 0000000000..08f35036b6 --- /dev/null +++ b/services-camel-app/src/test/resources/audio-context.xml @@ -0,0 +1,24 @@ + + + + + + + + + edu.unc.lib.boxc.services.camel.audio + + + \ No newline at end of file diff --git a/services-camel-app/src/test/resources/destroy-derivatives-router-it-context.xml b/services-camel-app/src/test/resources/destroy-derivatives-router-it-context.xml index 017f26efd9..cd2f874f77 100644 --- a/services-camel-app/src/test/resources/destroy-derivatives-router-it-context.xml +++ b/services-camel-app/src/test/resources/destroy-derivatives-router-it-context.xml @@ -32,6 +32,10 @@ + + + + diff --git a/services-camel-app/src/test/resources/enhancement-router-it-context.xml b/services-camel-app/src/test/resources/enhancement-router-it-context.xml index 14e1c9cf9d..f0550dff49 100644 --- a/services-camel-app/src/test/resources/enhancement-router-it-context.xml +++ b/services-camel-app/src/test/resources/enhancement-router-it-context.xml @@ -53,11 +53,16 @@ + + + + edu.unc.lib.boxc.services.camel.enhancements edu.unc.lib.boxc.services.camel.images edu.unc.lib.boxc.services.camel.fulltext edu.unc.lib.boxc.services.camel.solr + edu.unc.lib.boxc.services.camel.audio \ No newline at end of file diff --git a/static/css/admin/cdradmin.css b/static/css/admin/cdradmin.css index 8c62f6057d..c32a737a95 100644 --- a/static/css/admin/cdradmin.css +++ b/static/css/admin/cdradmin.css @@ -658,4 +658,52 @@ div.in-admin-iframe, #streaming_url { min-width: 400px; +} + +#chompb-projects_filter, +#vue-cdr-admin-app .dataTables_filter { + text-align: right; +} + +#chompb-projects_filter input, +#vue-cdr-admin-app .dataTables_filter input { + max-width: 250px; +} + +#chompb-preingest-ui table td a { + padding-right: 10px; + text-decoration: underline; +} + +#chompb-preingest-ui table td:last-child { + padding-right: 0; +} +#chompb-projects_wrapper .is-half, +#vue-cdr-admin-app .dataTables_wrapper .is-half { + margin: 25px auto; +} + +#chompb-projects_length label, +#vue-cdr-admin-app .dataTables_length label { + vertical-align: middle; +} + +#chompb-projects_paginate .pagination-list, +#vue-cdr-admin-app .pagination-list { + justify-content: flex-end; +} + +#vue-cdr-admin-app { + margin: 15px 20px; +} + +@media (max-width: 768px) { + #chompb-projects_filter { + text-align: left; + } + + #chompb-projects_paginate .pagination, + #chompb-projects_paginate .pagination-list { + justify-content: flex-start; + } } \ No newline at end of file diff --git a/static/css/sass/cdr_ui_styles.scss b/static/css/sass/cdr_ui_styles.scss index 2e9e47fd39..dc4f6185e1 100644 --- a/static/css/sass/cdr_ui_styles.scss +++ b/static/css/sass/cdr_ui_styles.scss @@ -827,37 +827,6 @@ table.dataTable { color: white; } -/* UV image viewer */ -.uv { - .footer { - background-color: white; - padding: 0; - } - - .search { - a { - margin-left: 0; - } - } - - .tabs { - margin-bottom: 0; - - .tab.on { - display: inherit !important; - } - } - - .title { - color: white; - font-size: inherit; - } - - li { - text-indent: 0; - } -} - /* MODs display for work and file pages, plus modalMetadata.vue component */ #mods_data_display { margin: auto; @@ -1181,6 +1150,17 @@ iframe { margin: 25px auto; } +.clover-viewer { + margin: auto; + width: 90%; +} + +#information-toggle { + &:focus { + background-color: hsl(209, 100%, 38.2%); + } +} + /*! @creativebulma/bulma-tooltip v1.2.0 | (c) 2020 Gaetan | MIT License | https://github.com/CreativeBulma/bulma-tooltip */ [data-tooltip]:not(.is-disabled),[data-tooltip]:not(.is-loading),[data-tooltip]:not([disabled]){cursor:pointer;overflow:visible;position:relative}[data-tooltip]:not(.is-disabled):before,[data-tooltip]:not(.is-loading):before,[data-tooltip]:not([disabled]):before{background:rgba(74,74,74,.9);border-radius:2px;content:attr(data-tooltip);padding:.5rem 1rem;text-overflow:ellipsis;white-space:pre;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}[data-tooltip]:not(.is-disabled).has-tooltip-arrow:after,[data-tooltip]:not(.is-disabled):before,[data-tooltip]:not(.is-loading).has-tooltip-arrow:after,[data-tooltip]:not(.is-loading):before,[data-tooltip]:not([disabled]).has-tooltip-arrow:after,[data-tooltip]:not([disabled]):before{box-sizing:border-box;color:#fff;display:inline-block;font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:.75rem;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;opacity:0;overflow:hidden;pointer-events:none;position:absolute;visibility:hidden;z-index:1}[data-tooltip]:not(.is-disabled).has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-arrow:after{content:"";border-style:solid;border-width:6px;border-color:rgba(74,74,74,.9) transparent transparent;margin-bottom:-5px}[data-tooltip]:not(.is-disabled).has-tooltip-arrow.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-arrow.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-arrow.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-bottom.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom:before,[data-tooltip]:not([disabled]).has-tooltip-bottom:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}[data-tooltip]:not(.is-disabled).has-tooltip-left.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left:before,[data-tooltip]:not(.is-loading).has-tooltip-left:before,[data-tooltip]:not([disabled]).has-tooltip-left:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-right.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right:before,[data-tooltip]:not(.is-loading).has-tooltip-right:before,[data-tooltip]:not([disabled]).has-tooltip-right:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-multiline:before,[data-tooltip]:not(.is-loading).has-tooltip-multiline:before,[data-tooltip]:not([disabled]).has-tooltip-multiline:before{height:auto;width:15rem;max-width:15rem;text-overflow:clip;white-space:normal;word-break:keep-all}[data-tooltip]:not(.is-disabled).has-tooltip-text-left:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left:before,[data-tooltip]:not([disabled]).has-tooltip-text-left:before{text-align:left}[data-tooltip]:not(.is-disabled).has-tooltip-text-centered:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered:before{text-align:center}[data-tooltip]:not(.is-disabled).has-tooltip-text-right:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right:before,[data-tooltip]:not([disabled]).has-tooltip-text-right:before{text-align:right}[data-tooltip]:not(.is-disabled).has-tooltip-white:after,[data-tooltip]:not(.is-loading).has-tooltip-white:after,[data-tooltip]:not([disabled]).has-tooltip-white:after{border-color:hsla(0,0%,100%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,100%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,100%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-right:after{border-color:transparent hsla(0,0%,100%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-white:before,[data-tooltip]:not(.is-loading).has-tooltip-white:before,[data-tooltip]:not([disabled]).has-tooltip-white:before{background-color:hsla(0,0%,100%,.9);color:#0a0a0a}[data-tooltip]:not(.is-disabled).has-tooltip-black:after,[data-tooltip]:not(.is-loading).has-tooltip-black:after,[data-tooltip]:not([disabled]).has-tooltip-black:after{border-color:hsla(0,0%,4%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,4%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,4%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-right:after{border-color:transparent hsla(0,0%,4%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-black:before,[data-tooltip]:not(.is-loading).has-tooltip-black:before,[data-tooltip]:not([disabled]).has-tooltip-black:before{background-color:hsla(0,0%,4%,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-light:after,[data-tooltip]:not(.is-loading).has-tooltip-light:after,[data-tooltip]:not([disabled]).has-tooltip-light:after{border-color:hsla(0,0%,96%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,96%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,96%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-right:after{border-color:transparent hsla(0,0%,96%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-light:before,[data-tooltip]:not(.is-loading).has-tooltip-light:before,[data-tooltip]:not([disabled]).has-tooltip-light:before{background-color:hsla(0,0%,96%,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-dark:after,[data-tooltip]:not(.is-loading).has-tooltip-dark:after,[data-tooltip]:not([disabled]).has-tooltip-dark:after{border-color:rgba(54,54,54,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-bottom:after{border-color:transparent transparent rgba(54,54,54,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-left:after{border-color:transparent transparent transparent rgba(54,54,54,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-right:after{border-color:transparent rgba(54,54,54,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark:before,[data-tooltip]:not(.is-loading).has-tooltip-dark:before,[data-tooltip]:not([disabled]).has-tooltip-dark:before{background-color:rgba(54,54,54,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-primary:after,[data-tooltip]:not(.is-loading).has-tooltip-primary:after,[data-tooltip]:not([disabled]).has-tooltip-primary:after{border-color:rgba(0,209,178,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-bottom:after{border-color:transparent transparent rgba(0,209,178,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-left:after{border-color:transparent transparent transparent rgba(0,209,178,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-right:after{border-color:transparent rgba(0,209,178,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary:before,[data-tooltip]:not(.is-loading).has-tooltip-primary:before,[data-tooltip]:not([disabled]).has-tooltip-primary:before{background-color:rgba(0,209,178,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-link:after,[data-tooltip]:not(.is-loading).has-tooltip-link:after,[data-tooltip]:not([disabled]).has-tooltip-link:after{border-color:rgba(50,115,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-bottom:after{border-color:transparent transparent rgba(50,115,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-left:after{border-color:transparent transparent transparent rgba(50,115,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-right:after{border-color:transparent rgba(50,115,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-link:before,[data-tooltip]:not(.is-loading).has-tooltip-link:before,[data-tooltip]:not([disabled]).has-tooltip-link:before{background-color:rgba(50,115,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-info:after,[data-tooltip]:not(.is-loading).has-tooltip-info:after,[data-tooltip]:not([disabled]).has-tooltip-info:after{border-color:rgba(50,152,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-bottom:after{border-color:transparent transparent rgba(50,152,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-left:after{border-color:transparent transparent transparent rgba(50,152,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-right:after{border-color:transparent rgba(50,152,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-info:before,[data-tooltip]:not(.is-loading).has-tooltip-info:before,[data-tooltip]:not([disabled]).has-tooltip-info:before{background-color:rgba(50,152,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-success:after,[data-tooltip]:not(.is-loading).has-tooltip-success:after,[data-tooltip]:not([disabled]).has-tooltip-success:after{border-color:rgba(72,199,116,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-bottom:after{border-color:transparent transparent rgba(72,199,116,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-left:after{border-color:transparent transparent transparent rgba(72,199,116,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-right:after{border-color:transparent rgba(72,199,116,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-success:before,[data-tooltip]:not(.is-loading).has-tooltip-success:before,[data-tooltip]:not([disabled]).has-tooltip-success:before{background-color:rgba(72,199,116,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-warning:after,[data-tooltip]:not(.is-loading).has-tooltip-warning:after,[data-tooltip]:not([disabled]).has-tooltip-warning:after{border-color:rgba(255,221,87,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-bottom:after{border-color:transparent transparent rgba(255,221,87,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-left:after{border-color:transparent transparent transparent rgba(255,221,87,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-right:after{border-color:transparent rgba(255,221,87,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning:before,[data-tooltip]:not(.is-loading).has-tooltip-warning:before,[data-tooltip]:not([disabled]).has-tooltip-warning:before{background-color:rgba(255,221,87,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-danger:after,[data-tooltip]:not(.is-loading).has-tooltip-danger:after,[data-tooltip]:not([disabled]).has-tooltip-danger:after{border-color:rgba(241,70,104,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-bottom:after{border-color:transparent transparent rgba(241,70,104,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-left:after{border-color:transparent transparent transparent rgba(241,70,104,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-right:after{border-color:transparent rgba(241,70,104,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger:before,[data-tooltip]:not(.is-loading).has-tooltip-danger:before,[data-tooltip]:not([disabled]).has-tooltip-danger:before{background-color:rgba(241,70,104,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-active:after,[data-tooltip]:not(.is-disabled).has-tooltip-active:before,[data-tooltip]:not(.is-disabled):hover:after,[data-tooltip]:not(.is-disabled):hover:before,[data-tooltip]:not(.is-loading).has-tooltip-active:after,[data-tooltip]:not(.is-loading).has-tooltip-active:before,[data-tooltip]:not(.is-loading):hover:after,[data-tooltip]:not(.is-loading):hover:before,[data-tooltip]:not([disabled]).has-tooltip-active:after,[data-tooltip]:not([disabled]).has-tooltip-active:before,[data-tooltip]:not([disabled]):hover:after,[data-tooltip]:not([disabled]):hover:before{opacity:1;visibility:visible}[data-tooltip]:not(.is-disabled).has-tooltip-fade:after,[data-tooltip]:not(.is-disabled).has-tooltip-fade:before,[data-tooltip]:not(.is-loading).has-tooltip-fade:after,[data-tooltip]:not(.is-loading).has-tooltip-fade:before,[data-tooltip]:not([disabled]).has-tooltip-fade:after,[data-tooltip]:not([disabled]).has-tooltip-fade:before{transition:opacity .3s linear,visibility .3s linear}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-top-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-mobile.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-top-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-top-mobile:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-tablet.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-top-tablet:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-tablet-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-tablet-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-top-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-touch.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-top-touch:before,[data-tooltip]:not([disabled]).has-tooltip-top-touch:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-desktop.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-top-desktop:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-desktop-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-desktop-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-top-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-until-widescreen.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-top-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-top-until-widescreen:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-top-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-until-fullhd.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-top-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-top-until-fullhd:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-top-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-fullhd.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-top-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-top-fullhd:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-right-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-mobile.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-right-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-right-mobile:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-tablet.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-right-tablet:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-tablet-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-tablet-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-right-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-touch.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-right-touch:before,[data-tooltip]:not([disabled]).has-tooltip-right-touch:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-desktop.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-right-desktop:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-desktop-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-desktop-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-right-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-until-widescreen.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-right-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-right-until-widescreen:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-right-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-until-fullhd.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-right-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-right-until-fullhd:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-right-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-fullhd.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-right-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-right-fullhd:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-mobile.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-mobile:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-touch.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-touch:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-touch:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-widescreen:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-fullhd:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-fullhd:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-left-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-mobile.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-left-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-left-mobile:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-tablet.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-left-tablet:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-tablet-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-tablet-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-left-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-touch.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-left-touch:before,[data-tooltip]:not([disabled]).has-tooltip-left-touch:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-desktop.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-left-desktop:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-desktop-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-desktop-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-left-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-until-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-left-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-left-until-widescreen:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-left-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-until-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-left-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-left-until-fullhd:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-left-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-left-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-left-fullhd:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-mobile:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-mobile:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-mobile:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-mobile:before{opacity:0!important;display:none!important}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet:before{opacity:0!important;display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-touch:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-touch:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-touch:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-touch:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-touch:before{opacity:0!important;display:none!important}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop:before{opacity:0!important;display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-widescreen:before{opacity:0!important;display:none!important}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen:before{opacity:0!important;display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-fullhd:before{opacity:0!important;display:none!important}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-fullhd:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-fullhd:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-fullhd:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-fullhd:before{opacity:0!important;display:none!important}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-mobile:before{text-align:left}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-tablet:before{text-align:left}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-tablet-only:before{text-align:left}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-touch:before{text-align:left}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-desktop:before{text-align:left}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-desktop-only:before{text-align:left}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-until-widescreen:before{text-align:left}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-widescreen:before{text-align:left}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-widescreen-only:before{text-align:left}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-until-fullhd:before{text-align:left}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-fullhd:before{text-align:left}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-mobile:before{text-align:center}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-tablet:before{text-align:center}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-tablet-only:before{text-align:center}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-touch:before{text-align:center}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-desktop:before{text-align:center}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-desktop-only:before{text-align:center}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-until-widescreen:before{text-align:center}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-widescreen:before{text-align:center}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-widescreen-only:before{text-align:center}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-until-fullhd:before{text-align:center}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-fullhd:before{text-align:center}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-mobile:before{text-align:right}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-tablet:before{text-align:right}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-tablet-only:before{text-align:right}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-touch:before{text-align:right}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-desktop:before{text-align:right}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-desktop-only:before{text-align:right}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-until-widescreen:before{text-align:right}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-widescreen:before{text-align:right}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-widescreen-only:before{text-align:right}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-until-fullhd:before{text-align:right}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-fullhd:before{text-align:right}}span[data-tooltip]{border-bottom:1px dashed #dbdbdb}span[data-tooltip].has-tooltip-white{border-bottom-color:#fff}span[data-tooltip].has-tooltip-black{border-bottom-color:#171717}span[data-tooltip].has-tooltip-light{border-bottom-color:#fff}span[data-tooltip].has-tooltip-dark{border-bottom-color:#424242}span[data-tooltip].has-tooltip-primary{border-bottom-color:#00ebc7}span[data-tooltip].has-tooltip-link{border-bottom-color:#4882e0}span[data-tooltip].has-tooltip-info{border-bottom-color:#48a3e0}span[data-tooltip].has-tooltip-success{border-bottom-color:#5bcd83}span[data-tooltip].has-tooltip-warning{border-bottom-color:#ffe270}span[data-tooltip].has-tooltip-danger{border-bottom-color:#f35e7c}.control span[data-tooltip]{border-bottom:none} diff --git a/static/js/admin/vue-permissions-editor/.browserslistrc b/static/js/admin/vue-cdr-admin/.browserslistrc similarity index 100% rename from static/js/admin/vue-permissions-editor/.browserslistrc rename to static/js/admin/vue-cdr-admin/.browserslistrc diff --git a/static/js/admin/vue-permissions-editor/.gitignore b/static/js/admin/vue-cdr-admin/.gitignore similarity index 100% rename from static/js/admin/vue-permissions-editor/.gitignore rename to static/js/admin/vue-cdr-admin/.gitignore diff --git a/static/js/admin/vue-permissions-editor/babel.config.js b/static/js/admin/vue-cdr-admin/babel.config.js similarity index 100% rename from static/js/admin/vue-permissions-editor/babel.config.js rename to static/js/admin/vue-cdr-admin/babel.config.js diff --git a/static/js/admin/vue-permissions-editor/index.html b/static/js/admin/vue-cdr-admin/index.html similarity index 100% rename from static/js/admin/vue-permissions-editor/index.html rename to static/js/admin/vue-cdr-admin/index.html diff --git a/static/js/admin/vue-permissions-editor/jest.config.js b/static/js/admin/vue-cdr-admin/jest.config.js similarity index 100% rename from static/js/admin/vue-permissions-editor/jest.config.js rename to static/js/admin/vue-cdr-admin/jest.config.js diff --git a/static/js/admin/vue-permissions-editor/package-lock.json b/static/js/admin/vue-cdr-admin/package-lock.json similarity index 87% rename from static/js/admin/vue-permissions-editor/package-lock.json rename to static/js/admin/vue-cdr-admin/package-lock.json index 8369c9a78e..0c0694a4c7 100644 --- a/static/js/admin/vue-permissions-editor/package-lock.json +++ b/static/js/admin/vue-cdr-admin/package-lock.json @@ -8,56 +8,61 @@ "name": "vue-permissions-editor", "version": "0.1.0", "dependencies": { - "axios": "^1.6.4", - "date-fns": "^3.0.6", + "axios": "^1.7.7", + "datatables.net": "^2.1.6", + "datatables.net-bm": "^2.1.6", + "datatables.net-searchpanes-bm": "^2.3.2", + "datatables.net-select-bm": "^2.1.0", + "datatables.net-vue3": "^3.0.2", + "date-fns": "^3.6.0", "lodash.clonedeep": "^4.5.0", "lodash.findindex": "^4.6.0", "lodash.isempty": "^4.4.0", - "pinia": "^2.1.7", - "vue": "^3.4.5", - "vue-router": "^4.2.5" + "pinia": "^2.2.2", + "vue": "^3.5.6", + "vue-router": "^4.4.5" }, "devDependencies": { - "@babel/plugin-transform-runtime": "^7.23.7", - "@babel/preset-env": "^7.23.7", - "@pinia/testing": "0.1.3", - "@testing-library/jest-dom": "^6.2.0", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.25.3", + "@pinia/testing": "0.1.5", + "@testing-library/jest-dom": "^6.4.8", "@vitejs/plugin-vue": "^4.6.2", - "@vue/compiler-sfc": "^3.4.5", - "@vue/test-utils": "2.4.3", + "@vue/compiler-sfc": "^3.5.6", + "@vue/test-utils": "2.4.6", "@vue/vue3-jest": "^29.2.6", "babel-jest": "^29.7.0", - "caniuse-lite": "^1.0.30001574", + "caniuse-lite": "^1.0.30001651", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", "moxios": "^0.4.0", - "sass": "^1.69.7", + "sass": "^1.77.8", "vite": "4.5.5" } }, "node_modules/@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==", "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -94,14 +99,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -109,38 +114,39 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -164,19 +170,17 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz", - "integrity": "sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", "semver": "^6.3.1" }, "engines": { @@ -187,12 +191,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -204,9 +208,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -219,75 +223,42 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -297,35 +268,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -335,14 +306,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -352,77 +323,65 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true, + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -443,23 +402,27 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -467,13 +430,44 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -483,14 +477,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -500,13 +494,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -603,12 +597,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -618,12 +612,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -805,12 +799,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -820,15 +814,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", - "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -838,14 +832,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -855,12 +849,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -870,12 +864,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -885,13 +879,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -901,13 +895,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -918,19 +912,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", + "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.0", "globals": "^11.1.0" }, "engines": { @@ -941,13 +932,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -957,12 +948,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -972,13 +963,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -988,12 +979,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1002,13 +993,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1019,13 +1026,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1035,12 +1042,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1051,13 +1058,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1067,14 +1074,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -1084,12 +1091,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1100,12 +1107,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1115,12 +1122,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1131,12 +1138,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1146,13 +1153,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1162,14 +1169,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1179,15 +1186,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1197,13 +1204,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1213,13 +1220,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1229,12 +1236,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1244,12 +1251,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1260,12 +1267,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -1276,16 +1283,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1295,13 +1301,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1311,12 +1317,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -1327,13 +1333,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -1344,12 +1350,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1359,13 +1365,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1375,14 +1381,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1393,12 +1399,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1408,12 +1414,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1424,12 +1430,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1439,16 +1445,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", - "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -1459,12 +1465,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1474,13 +1480,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1490,12 +1496,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1505,12 +1511,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1520,12 +1526,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1535,12 +1541,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1550,13 +1556,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1566,13 +1572,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1582,13 +1588,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1598,26 +1604,28 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", - "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -1629,59 +1637,60 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.7", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -1724,34 +1733,31 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1759,13 +1765,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2593,14 +2598,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -2616,23 +2621,23 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2646,24 +2651,24 @@ "dev": true }, "node_modules/@pinia/testing": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.3.tgz", - "integrity": "sha512-D2Ds2s69kKFaRf2KCcP1NhNZEg5+we59aRyQalwRm7ygWfLM25nDH66267U3hNvRUOTx8ofL24GzodZkOmB5xw==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.5.tgz", + "integrity": "sha512-AcGzuotkzhRoF00htuxLfIPBBHVE6HjjB3YC5Y3os8vRgKu6ipknK5GBQq9+pduwYQhZ+BcCZDC9TyLAUlUpoQ==", "dev": true, "dependencies": { - "vue-demi": ">=0.14.5" + "vue-demi": "^0.14.10" }, "funding": { "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "pinia": ">=2.1.5" + "pinia": ">=2.2.1" } }, "node_modules/@pinia/testing/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "dev": true, "hasInstallScript": true, "bin": { @@ -2721,44 +2726,24 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", - "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz", + "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==", "dev": true, "dependencies": { - "@adobe/css-tools": "^4.3.2", + "@adobe/css-tools": "^4.4.0", "@babel/runtime": "^7.9.2", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.15", + "lodash": "^4.17.21", "redent": "^3.0.0" }, "engines": { "node": ">=14", "npm": ">=6", "yarn": ">=1" - }, - "peerDependencies": { - "@jest/globals": ">= 28", - "@types/jest": ">= 28", - "jest": ">= 28", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "@jest/globals": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "jest": { - "optional": true - }, - "vitest": { - "optional": true - } } }, "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { @@ -2981,117 +2966,109 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.5.tgz", - "integrity": "sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.6.tgz", + "integrity": "sha512-r+gNu6K4lrvaQLQGmf+1gc41p3FO2OUJyWmNqaIITaJU6YFiV5PtQSFZt8jfztYyARwqhoCayjprC7KMvT3nRA==", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.5", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.6", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.5.tgz", - "integrity": "sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.6.tgz", + "integrity": "sha512-xRXqxDrIqK8v8sSScpistyYH0qYqxakpsIvqMD2e5sV/PXQ1mTwtXp4k42yHK06KXxKSmitop9e45Ui/3BrTEw==", "dependencies": { - "@vue/compiler-core": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-core": "3.5.6", + "@vue/shared": "3.5.6" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.5.tgz", - "integrity": "sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ==", - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.5", - "@vue/compiler-dom": "3.4.5", - "@vue/compiler-ssr": "3.4.5", - "@vue/shared": "3.4.5", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.6.tgz", + "integrity": "sha512-pjWJ8Kj9TDHlbF5LywjVso+BIxCY5wVOLhkEXRhuCHDxPFIeX1zaFefKs8RYoHvkSMqRWt93a0f2gNJVJixHwg==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.6", + "@vue/compiler-dom": "3.5.6", + "@vue/compiler-ssr": "3.5.6", + "@vue/shared": "3.5.6", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.32", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.11", + "postcss": "^8.4.47", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.5.tgz", - "integrity": "sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.6.tgz", + "integrity": "sha512-VpWbaZrEOCqnmqjE83xdwegtr5qO/2OPUC6veWgvNqTJ3bYysz6vY3VqMuOijubuUYPRpG3OOKIh9TD0Stxb9A==", "dependencies": { - "@vue/compiler-dom": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-dom": "3.5.6", + "@vue/shared": "3.5.6" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", - "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" }, "node_modules/@vue/reactivity": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz", - "integrity": "sha512-BcWkKvjdvqJwb7BhhFkXPLDCecX4d4a6GATvCduJQDLv21PkPowAE5GKuIE5p6RC07/Lp9FMkkq4AYCTVF5KlQ==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.6.tgz", + "integrity": "sha512-shZ+KtBoHna5GyUxWfoFVBCVd7k56m6lGhk5e+J9AKjheHF6yob5eukssHRI+rzvHBiU1sWs/1ZhNbLExc5oYQ==", "dependencies": { - "@vue/shared": "3.4.5" + "@vue/shared": "3.5.6" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.5.tgz", - "integrity": "sha512-wh9ELIOQKeWT9SaUPdLrsxRkZv14jp+SJm9aiQGWio+/MWNM3Lib0wE6CoKEqQ9+SCYyGjDBhTOTtO47kCgbkg==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.6.tgz", + "integrity": "sha512-FpFULR6+c2lI+m1fIGONLDqPQO34jxV8g6A4wBOgne8eSRHP6PQL27+kWFIx5wNhhjkO7B4rgtsHAmWv7qKvbg==", "dependencies": { - "@vue/reactivity": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/reactivity": "3.5.6", + "@vue/shared": "3.5.6" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.5.tgz", - "integrity": "sha512-n5ewvOjyG3IEpqGBahdPXODFSpVlSz3H4LF76Sx0XAqpIOqyJ5bIb2PrdYuH2ogBMAQPh+o5tnoH4nJpBr8U0Q==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.6.tgz", + "integrity": "sha512-SDPseWre45G38ENH2zXRAHL1dw/rr5qp91lS4lt/nHvMr0MhsbCbihGAWLXNB/6VfFOJe2O+RBRkXU+CJF7/sw==", "dependencies": { - "@vue/runtime-core": "3.4.5", - "@vue/shared": "3.4.5", + "@vue/reactivity": "3.5.6", + "@vue/runtime-core": "3.5.6", + "@vue/shared": "3.5.6", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.5.tgz", - "integrity": "sha512-jOFc/VE87yvifQpNju12VcqimH8pBLxdcT+t3xMeiED1K6DfH9SORyhFEoZlW5TG2Vwfn3Ul5KE+1aC99xnSBg==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.6.tgz", + "integrity": "sha512-zivnxQnOnwEXVaT9CstJ64rZFXMS5ZkKxCjDQKiMSvUhXRzFLWZVbaBiNF4HGDqGNNsTgmjcCSmU6TB/0OOxLA==", "dependencies": { - "@vue/compiler-ssr": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-ssr": "3.5.6", + "@vue/shared": "3.5.6" }, "peerDependencies": { - "vue": "3.4.5" + "vue": "3.5.6" } }, "node_modules/@vue/shared": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.5.tgz", - "integrity": "sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg==" + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.6.tgz", + "integrity": "sha512-eidH0HInnL39z6wAt6SFIwBrvGOpDWsDxlw3rCgo1B+CQ1781WzQUSU3YjxgdkcJo9Q8S6LmXTkvI+cLHGkQfA==" }, "node_modules/@vue/test-utils": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.3.tgz", - "integrity": "sha512-F4K7mF+ad++VlTrxMJVRnenKSJmO6fkQt2wpRDiKDesQMkfpniGWsqEi/JevxGBo2qEkwwjvTUAoiGJLNx++CA==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, "dependencies": { "js-beautify": "^1.14.9", - "vue-component-type-helpers": "^1.8.21" - }, - "peerDependencies": { - "@vue/server-renderer": "^3.0.1", - "vue": "^3.0.1" - }, - "peerDependenciesMeta": { - "@vue/server-renderer": { - "optional": true - } + "vue-component-type-helpers": "^2.0.0" } }, "node_modules/@vue/vue3-jest": { @@ -3275,11 +3252,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz", - "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -3407,13 +3384,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", + "@babel/helper-define-polyfill-provider": "^0.6.2", "semver": "^6.3.1" }, "peerDependencies": { @@ -3421,25 +3398,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3528,9 +3505,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -3547,10 +3524,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -3593,9 +3570,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001574", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001574.tgz", - "integrity": "sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -3777,12 +3754,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", - "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz", + "integrity": "sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==", "dev": true, "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -3957,10 +3934,77 @@ "node": ">=12" } }, + "node_modules/datatables.net": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.1.6.tgz", + "integrity": "sha512-ziX0Wz91oDJ4o7gQNuGxQiVK91OUu/bRcXyxa6EZtDwLObmaGKpkCNS59QpzrGtIytQIVFtLfF1EDdYid5RVog==", + "dependencies": { + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-bm": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/datatables.net-bm/-/datatables.net-bm-2.1.6.tgz", + "integrity": "sha512-JOrDvPQ1/j6z1cO8QFMISbQSIoO4nJOQn56oCn/a187ZR3Hv5DT59tMcEcF88obYcokmVKPIOmsQ7hqo6HcQ+g==", + "dependencies": { + "datatables.net": "2.1.6", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-searchpanes": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/datatables.net-searchpanes/-/datatables.net-searchpanes-2.3.2.tgz", + "integrity": "sha512-I1sgKL3JiXtQhGJT3sbIpkN+czCjAox+gmyV6UBgHFQcAyspHhl1KnPdm/nWmABhPAIIM2RLotyh4U+A/0Z4hQ==", + "dependencies": { + "datatables.net": "^2", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-searchpanes-bm": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/datatables.net-searchpanes-bm/-/datatables.net-searchpanes-bm-2.3.2.tgz", + "integrity": "sha512-O4iPgdICaOKzSast8YLcHy9cMmgKtpqvDsuFHwXArvfRmKA6Jdl6viPHCyjer8FAWBKWQCvoyKETTBnLV2UoXA==", + "dependencies": { + "datatables.net-bm": "^2", + "datatables.net-searchpanes": "2.3.2", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/datatables.net-select/-/datatables.net-select-2.1.0.tgz", + "integrity": "sha512-6B1sI5pfvBen+8kySl1T0hkGoUmrw+5YGqaW4NCg+1srMw+48YV5SdWPiyCQDoev2ajBdKzHuG/wzD2INMbmJw==", + "dependencies": { + "datatables.net": "^2", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-select-bm": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/datatables.net-select-bm/-/datatables.net-select-bm-2.1.0.tgz", + "integrity": "sha512-75Maykp2FfuCC4g9B2blQ2t7llNpS7BsdyKbuctvjdAVJdZ6kJX5PQo44nGzSW+ZAgC5/FKEfsekOiImMiC+ew==", + "dependencies": { + "datatables.net-bm": "^2", + "datatables.net-select": "2.1.0", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-vue3": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/datatables.net-vue3/-/datatables.net-vue3-3.0.2.tgz", + "integrity": "sha512-UJs3IrKS+gsaPSy3CeL5ny/hGWm8xs77DZLYSW8QXPpDhMS7ZZfC4AGKNvXa2C9J1OllWhiR20dScpi0lKdCvQ==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "datatables.net": "^2", + "vue": "^3.0.5" + } + }, "node_modules/date-fns": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.6.tgz", - "integrity": "sha512-W+G99rycpKMMF2/YD064b2lE7jJGUe+EjOES7Q8BIGY8sbNdbgcs9XFTZwvzc9Jx1f3k7LB7gZaZa7f8Agzljg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -4138,9 +4182,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.621", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.621.tgz", - "integrity": "sha512-MGJM6S0MuF/wTzM9NoItWXN56J1kolrHS/vzl/KlhXAbVkogTy0wzKYliQDJgNypxSCFjxdRhHYS3bffyYUGEw==", + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.9.tgz", + "integrity": "sha512-HfkT8ndXR0SEkU8gBQQM3rz035bpE/hxkZ1YIt4KJPEFES68HfIU6LzKukH0H794Lm83WJtkSAMfEToxCs15VA==", "dev": true }, "node_modules/emittery": { @@ -4219,9 +4263,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -6548,6 +6592,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, "node_modules/js-beautify": { "version": "1.14.11", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", @@ -6786,14 +6835,11 @@ "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -6860,12 +6906,12 @@ "dev": true }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -6975,9 +7021,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/nopt": { @@ -7203,9 +7249,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -7220,12 +7266,12 @@ } }, "node_modules/pinia": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz", - "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.2.tgz", + "integrity": "sha512-ja2XqFWZC36mupU4z1ZzxeTApV7DOw44cV4dhQ9sGwun+N89v/XP7+j7q6TanS1u1tdbK4r+1BUx7heMaIdagA==", "dependencies": { - "@vue/devtools-api": "^6.5.0", - "vue-demi": ">=0.14.5" + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -7245,9 +7291,9 @@ } }, "node_modules/pinia/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -7291,9 +7337,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "funding": [ { "type": "opencollective", @@ -7310,8 +7356,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -7606,9 +7652,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.69.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", - "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -7695,9 +7741,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -7908,7 +7954,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, "engines": { "node": ">=4" } @@ -8047,9 +8092,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -8066,8 +8111,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -8162,15 +8207,15 @@ } }, "node_modules/vue": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.5.tgz", - "integrity": "sha512-VH6nHFhLPjgu2oh5vEBXoNZxsGHuZNr3qf4PHClwJWw6IDqw6B3x+4J+ABdoZ0aJuT8Zi0zf3GpGlLQCrGWHrw==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.6.tgz", + "integrity": "sha512-zv+20E2VIYbcJOzJPUWp03NOGFhMmpCKOfSxVTmCYyYFFko48H9tmuQFzYj7tu4qX1AeXlp9DmhIP89/sSxxhw==", "dependencies": { - "@vue/compiler-dom": "3.4.5", - "@vue/compiler-sfc": "3.4.5", - "@vue/runtime-dom": "3.4.5", - "@vue/server-renderer": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-dom": "3.5.6", + "@vue/compiler-sfc": "3.5.6", + "@vue/runtime-dom": "3.5.6", + "@vue/server-renderer": "3.5.6", + "@vue/shared": "3.5.6" }, "peerDependencies": { "typescript": "*" @@ -8182,17 +8227,17 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "1.8.27", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.27.tgz", - "integrity": "sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==", + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.29.tgz", + "integrity": "sha512-58i+ZhUAUpwQ+9h5Hck0D+jr1qbYl4voRt5KffBx8qzELViQ4XdT/Tuo+mzq8u63teAG8K0lLaOiL5ofqW38rg==", "dev": true }, "node_modules/vue-router": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", - "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", + "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", "dependencies": { - "@vue/devtools-api": "^6.5.0" + "@vue/devtools-api": "^6.6.4" }, "funding": { "url": "https://github.com/sponsors/posva" diff --git a/static/js/admin/vue-cdr-admin/package.json b/static/js/admin/vue-cdr-admin/package.json new file mode 100644 index 0000000000..6964a60d8d --- /dev/null +++ b/static/js/admin/vue-cdr-admin/package.json @@ -0,0 +1,43 @@ +{ + "name": "vue-permissions-editor", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite", + "serve": "vite preview", + "build": "vite build", + "test": "jest" + }, + "dependencies": { + "axios": "^1.7.7", + "datatables.net": "^2.1.6", + "datatables.net-bm": "^2.1.6", + "datatables.net-searchpanes-bm": "^2.3.2", + "datatables.net-select-bm": "^2.1.0", + "datatables.net-vue3": "^3.0.2", + "date-fns": "^3.6.0", + "lodash.clonedeep": "^4.5.0", + "lodash.findindex": "^4.6.0", + "lodash.isempty": "^4.4.0", + "pinia": "^2.2.2", + "vue": "^3.5.6", + "vue-router": "^4.4.5" + }, + "devDependencies": { + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.25.3", + "@pinia/testing": "0.1.5", + "@testing-library/jest-dom": "^6.4.8", + "@vitejs/plugin-vue": "^4.6.2", + "@vue/compiler-sfc": "^3.5.6", + "@vue/test-utils": "2.4.6", + "@vue/vue3-jest": "^29.2.6", + "babel-jest": "^29.7.0", + "caniuse-lite": "^1.0.30001651", + "jest": "29.7.0", + "jest-environment-jsdom": "^29.7.0", + "moxios": "^0.4.0", + "sass": "^1.77.8", + "vite": "4.5.5" + } +} diff --git a/static/js/admin/vue-permissions-editor/src/App.vue b/static/js/admin/vue-cdr-admin/src/App.vue similarity index 62% rename from static/js/admin/vue-permissions-editor/src/App.vue rename to static/js/admin/vue-cdr-admin/src/App.vue index 9cee4d7ded..224acbcc88 100644 --- a/static/js/admin/vue-permissions-editor/src/App.vue +++ b/static/js/admin/vue-cdr-admin/src/App.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/static/js/admin/vue-permissions-editor/src/assets/common-styles.css b/static/js/admin/vue-cdr-admin/src/assets/common-styles.css similarity index 98% rename from static/js/admin/vue-permissions-editor/src/assets/common-styles.css rename to static/js/admin/vue-cdr-admin/src/assets/common-styles.css index fb4f220ae3..42a50a2c73 100644 --- a/static/js/admin/vue-permissions-editor/src/assets/common-styles.css +++ b/static/js/admin/vue-cdr-admin/src/assets/common-styles.css @@ -215,4 +215,10 @@ .slide-enter, .slide-leave-to { max-height: 0 +} + +.pull-right, +.buttons.pull-right { + display: flex; + justify-content: flex-end; } \ No newline at end of file diff --git a/static/js/admin/vue-cdr-admin/src/components/chompb/preIngest.vue b/static/js/admin/vue-cdr-admin/src/components/chompb/preIngest.vue new file mode 100644 index 0000000000..c408ea4065 --- /dev/null +++ b/static/js/admin/vue-cdr-admin/src/components/chompb/preIngest.vue @@ -0,0 +1,157 @@ + + + + + + \ No newline at end of file diff --git a/static/js/admin/vue-cdr-admin/src/components/chompb/velocicroptorReport.vue b/static/js/admin/vue-cdr-admin/src/components/chompb/velocicroptorReport.vue new file mode 100644 index 0000000000..962663f874 --- /dev/null +++ b/static/js/admin/vue-cdr-admin/src/components/chompb/velocicroptorReport.vue @@ -0,0 +1,212 @@ + + + + + + \ No newline at end of file diff --git a/static/js/admin/vue-permissions-editor/src/components/embargo.vue b/static/js/admin/vue-cdr-admin/src/components/permissions-editor/embargo.vue similarity index 99% rename from static/js/admin/vue-permissions-editor/src/components/embargo.vue rename to static/js/admin/vue-cdr-admin/src/components/permissions-editor/embargo.vue index 4713363750..12efabaf7d 100644 --- a/static/js/admin/vue-permissions-editor/src/components/embargo.vue +++ b/static/js/admin/vue-cdr-admin/src/components/permissions-editor/embargo.vue @@ -46,7 +46,7 @@ - - \ No newline at end of file diff --git a/static/js/vue-cdr-access/src/components/full_record/bulkDownload.vue b/static/js/vue-cdr-access/src/components/full_record/bulkDownload.vue new file mode 100644 index 0000000000..125c9e4c7a --- /dev/null +++ b/static/js/vue-cdr-access/src/components/full_record/bulkDownload.vue @@ -0,0 +1,51 @@ + + + \ No newline at end of file diff --git a/static/js/vue-cdr-access/src/components/full_record/fileList.vue b/static/js/vue-cdr-access/src/components/full_record/fileList.vue index 4d7024135d..1edeeeee46 100644 --- a/static/js/vue-cdr-access/src/components/full_record/fileList.vue +++ b/static/js/vue-cdr-access/src/components/full_record/fileList.vue @@ -9,14 +9,10 @@ force it to reload

{{ $t('full_record.item_list') }} ({{ childCount }})

- + +
- - -
-
- - - \ No newline at end of file diff --git a/web-access-app/src/main/webapp/WEB-INF/solr-search-context.xml b/web-access-app/src/main/webapp/WEB-INF/solr-search-context.xml index 9be25541d1..5d627ce948 100644 --- a/web-access-app/src/main/webapp/WEB-INF/solr-search-context.xml +++ b/web-access-app/src/main/webapp/WEB-INF/solr-search-context.xml @@ -102,6 +102,11 @@ + + + + + diff --git a/web-admin-app/src/main/java/edu/unc/lib/boxc/web/admin/controllers/ChompbController.java b/web-admin-app/src/main/java/edu/unc/lib/boxc/web/admin/controllers/ChompbController.java new file mode 100644 index 0000000000..de6bf55660 --- /dev/null +++ b/web-admin-app/src/main/java/edu/unc/lib/boxc/web/admin/controllers/ChompbController.java @@ -0,0 +1,84 @@ +package edu.unc.lib.boxc.web.admin.controllers; + +import edu.unc.lib.boxc.auth.fcrepo.models.AgentPrincipalsImpl; +import edu.unc.lib.boxc.web.admin.controllers.processing.ChompbPreIngestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.io.IOException; +import java.nio.file.Paths; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * @author lfarrell + */ +@Controller +public class ChompbController { + @Autowired + ChompbPreIngestService chompbPreIngestService; + + @RequestMapping(value = "chompb", method = RequestMethod.GET) + public String chompb() { + return "report/chompb"; + } + + @RequestMapping(value = "chompb/listProjects", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE) + public @ResponseBody String chompbProjectsJSON() { + var agentPrincipals = AgentPrincipalsImpl.createFromThread(); + return chompbPreIngestService.getProjectLists(agentPrincipals); + } + + /** + * Until the admin is a full SPA we'll need to set routes here and in the vue-admin-apps router + * for each chompb action + * @return + */ + @RequestMapping(value = "chompb/project/**", method = RequestMethod.GET) + public String chompbCroppingReport() { + return "report/chompb"; + } + + /** + * Get processing result files for a specific job + * @param projectName + * @param jobName + * @param filePath + * @return + * @throws IOException + */ + @RequestMapping(value = "chompb/project/{projectName}/processing_results/{jobName}/files", + method = RequestMethod.GET) + public ResponseEntity getProcessingResults(@PathVariable("projectName") String projectName, + @PathVariable("jobName") String jobName, + @RequestParam(value = "path", defaultValue = "false") String filename) + throws IOException { + var agentPrincipals = AgentPrincipalsImpl.createFromThread(); + var nameSegment = Paths.get(filename).getFileName().toString(); + var stream = chompbPreIngestService.getProcessingResults(agentPrincipals, projectName, jobName, filename); + InputStreamResource resource = new InputStreamResource(stream); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + nameSegment) + .contentType(getMediaType(filename)) + .body(resource); + } + + private MediaType getMediaType(String filename) { + if (filename.endsWith(".json")) { + return MediaType.APPLICATION_JSON; + } else if (filename.endsWith(".csv")) { + return MediaType.TEXT_PLAIN; + } else { + return MediaType.IMAGE_JPEG; + } + } +} diff --git a/web-admin-app/src/main/java/edu/unc/lib/boxc/web/admin/controllers/processing/ChompbPreIngestService.java b/web-admin-app/src/main/java/edu/unc/lib/boxc/web/admin/controllers/processing/ChompbPreIngestService.java new file mode 100644 index 0000000000..265e11daed --- /dev/null +++ b/web-admin-app/src/main/java/edu/unc/lib/boxc/web/admin/controllers/processing/ChompbPreIngestService.java @@ -0,0 +1,117 @@ +package edu.unc.lib.boxc.web.admin.controllers.processing; + +import edu.unc.lib.boxc.auth.api.Permission; +import edu.unc.lib.boxc.auth.api.exceptions.AccessRestrictionException; +import edu.unc.lib.boxc.auth.api.models.AgentPrincipals; +import edu.unc.lib.boxc.auth.api.services.GlobalPermissionEvaluator; +import edu.unc.lib.boxc.model.api.exceptions.RepositoryException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +/** + * Service for interacting with the chompb command-line tool for pre-ingest processing + * @author lfarrell + */ +public class ChompbPreIngestService { + private GlobalPermissionEvaluator globalPermissionEvaluator; + private Path baseProjectsPath; + private static final Set VALID_FILENAMES = Set.of("data.json", "data.csv"); + + /** + * List all of the chompb projects in the base projects path + * + * @param agent + * @return output of the list projects command, which is a json string + */ + public String getProjectLists(AgentPrincipals agent) { + assertHasPermission(agent); + + return executeChompbCommand("chompb", "-w", baseProjectsPath.toAbsolutePath().toString(), "list_projects"); + } + + protected String executeChompbCommand(String... command) { + StringBuilder output = new StringBuilder(); + String outputString; + + try { + ProcessBuilder builder = new ProcessBuilder(command); + builder.redirectErrorStream(true); + Process process = builder.start(); + InputStream is = process.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String line; + + while ((line = br.readLine()) != null) { + output.append(line).append("\n"); + } + outputString = output.toString().trim(); + if (process.waitFor() != 0) { + throw new RepositoryException("Command exited with status code " + process.waitFor() + ": " + outputString); + } + } catch (IOException e) { + throw new RepositoryException("Failed to execute chompb command", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RepositoryException("Interrupted while waiting for chompb command to complete", e); + } + + return outputString; + } + + /** + * Get the processing results for a specific project and job + * + * @param agent + * @param projectName + * @param jobName + * @param filename + * @return Contents of the file as a InputStream + */ + public InputStream getProcessingResults(AgentPrincipals agent, String projectName, String jobName, String filename) throws IOException { + assertHasPermission(agent); + assertValidProcessingResultFilename(filename); + + var projectPath = buildProjectPath(projectName); + // Build path to results file + Path resultsPath = projectPath.resolve("processing/results") + .resolve(jobName) + .resolve("report") + .resolve(filename) + .normalize(); + if (!resultsPath.startsWith(projectPath)) { + throw new AccessRestrictionException("Cannot access specified file"); + } + // Read the file and return it as a stream + return Files.newInputStream(resultsPath); + } + + private void assertValidProcessingResultFilename(String filename) { + if (!VALID_FILENAMES.contains(filename) && !(filename.startsWith("images/") && filename.endsWith(".jpg"))) { + throw new IllegalArgumentException("Invalid filename: " + filename); + } + } + + private void assertHasPermission(AgentPrincipals agent) { + if (!globalPermissionEvaluator.hasGlobalPermission(agent.getPrincipals(), Permission.ingest)) { + throw new AccessRestrictionException("User " + agent.getUsername() + " does not have permission to use chompb"); + } + } + + private Path buildProjectPath(String projectName) { + return baseProjectsPath.resolve(projectName); + } + + public void setGlobalPermissionEvaluator(GlobalPermissionEvaluator globalPermissionEvaluator) { + this.globalPermissionEvaluator = globalPermissionEvaluator; + } + + public void setBaseProjectsPath(Path baseProjectsPath) { + this.baseProjectsPath = baseProjectsPath; + } +} diff --git a/web-admin-app/src/main/webapp/WEB-INF/jsp/common/header.jsp b/web-admin-app/src/main/webapp/WEB-INF/jsp/common/header.jsp index cd2d497507..91adcc6be9 100644 --- a/web-admin-app/src/main/webapp/WEB-INF/jsp/common/header.jsp +++ b/web-admin-app/src/main/webapp/WEB-INF/jsp/common/header.jsp @@ -54,6 +54,10 @@ active Status Monitor +
  • + active + Pre-ingest +
  • diff --git a/web-admin-app/src/main/webapp/WEB-INF/jsp/report/chompb.jsp b/web-admin-app/src/main/webapp/WEB-INF/jsp/report/chompb.jsp new file mode 100644 index 0000000000..2167a5a04d --- /dev/null +++ b/web-admin-app/src/main/webapp/WEB-INF/jsp/report/chompb.jsp @@ -0,0 +1,2 @@ + +
    \ No newline at end of file diff --git a/web-admin-app/src/main/webapp/WEB-INF/jsp/template/default.jsp b/web-admin-app/src/main/webapp/WEB-INF/jsp/template/default.jsp index 6db231c4a6..1e2d98bdc0 100644 --- a/web-admin-app/src/main/webapp/WEB-INF/jsp/template/default.jsp +++ b/web-admin-app/src/main/webapp/WEB-INF/jsp/template/default.jsp @@ -13,8 +13,6 @@ -
    -
    @@ -23,6 +21,8 @@ +
    +
    diff --git a/web-admin-app/src/main/webapp/WEB-INF/service-context.xml b/web-admin-app/src/main/webapp/WEB-INF/service-context.xml index 5783fbb83e..6dfacf0247 100644 --- a/web-admin-app/src/main/webapp/WEB-INF/service-context.xml +++ b/web-admin-app/src/main/webapp/WEB-INF/service-context.xml @@ -72,6 +72,11 @@ destroy-method="shutdown"> + + + + + diff --git a/web-admin-app/src/test/java/edu/unc/lib/boxc/web/admin/controllers/ChompbControllerIT.java b/web-admin-app/src/test/java/edu/unc/lib/boxc/web/admin/controllers/ChompbControllerIT.java new file mode 100644 index 0000000000..cb3df6102f --- /dev/null +++ b/web-admin-app/src/test/java/edu/unc/lib/boxc/web/admin/controllers/ChompbControllerIT.java @@ -0,0 +1,113 @@ +package edu.unc.lib.boxc.web.admin.controllers; + +import edu.unc.lib.boxc.web.admin.controllers.processing.ChompbPreIngestService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author lfarrell + */ +public class ChompbControllerIT { + @Mock + private ChompbPreIngestService chompbPreIngestService; + @InjectMocks + private ChompbController controller; + private MockMvc mvc; + private AutoCloseable closeable; + + @BeforeEach + public void init() { + closeable = openMocks(this); + mvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @AfterEach + void closeService() throws Exception { + closeable.close(); + } + + @Test + public void testPageLoad() throws Exception { + MvcResult result = mvc.perform(get("/chompb")) + .andExpect(status().isOk()) + .andReturn(); + assertEquals("report/chompb", result.getResponse().getForwardedUrl()); + } + + @Test + public void testProcessingReportRequest() throws Exception { + MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor")) + .andExpect(status().isOk()) + .andReturn(); + assertEquals("report/chompb", result.getResponse().getForwardedUrl()); + } + + @Test + public void testListProjects() throws Exception { + when(chompbPreIngestService.getProjectLists(any())).thenReturn("test"); + MvcResult result = mvc.perform(get("/chompb/listProjects")) + .andExpect(status().isOk()) + .andReturn(); + var resp = result.getResponse().getContentAsString(); + assertEquals("test", resp); + } + + @Test + public void testGetProcessingResultsJson() throws Exception { + var resultContent = "jsontest"; + var resultStream = new ByteArrayInputStream(resultContent.getBytes()); + when(chompbPreIngestService.getProcessingResults(any(), any(), any(), any())).thenReturn(resultStream); + MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/files?path=data.json")) + .andExpect(status().isOk()) + .andReturn(); + assertEquals("application/json", result.getResponse().getContentType()); + assertEquals("attachment; filename=data.json", result.getResponse().getHeader("Content-Disposition")); + var resp = result.getResponse().getContentAsString(); + assertEquals(resultContent, resp); + } + + @Test + public void testGetProcessingResultsCsv() throws Exception { + var resultContent = "csvtest"; + var resultStream = new ByteArrayInputStream(resultContent.getBytes()); + when(chompbPreIngestService.getProcessingResults(any(), any(), any(), any())).thenReturn(resultStream); + MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/files?path=data.csv")) + .andExpect(status().isOk()) + .andReturn(); + assertEquals("text/plain", result.getResponse().getContentType()); + assertEquals("attachment; filename=data.csv", result.getResponse().getHeader("Content-Disposition")); + var resp = result.getResponse().getContentAsString(); + assertEquals(resultContent, resp); + } + + @Test + public void testGetProcessingResultsImage() throws Exception { + var resultContent = "testimage"; + var resultStream = new ByteArrayInputStream(resultContent.getBytes()); + var imagePath = "images/path/to/chomp.jpg"; + when(chompbPreIngestService.getProcessingResults(any(), any(), any(), eq(imagePath))).thenReturn(resultStream); + MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/files?path=" + imagePath)) + .andExpect(status().isOk()) + .andReturn(); + assertEquals("image/jpeg", result.getResponse().getContentType()); + assertEquals("attachment; filename=chomp.jpg", result.getResponse().getHeader("Content-Disposition")); + var resp = result.getResponse().getContentAsString(); + assertEquals(resultContent, resp); + } +} diff --git a/web-admin-app/src/test/java/edu/unc/lib/boxc/web/admin/controllers/processing/ChompbPreIngestServiceTest.java b/web-admin-app/src/test/java/edu/unc/lib/boxc/web/admin/controllers/processing/ChompbPreIngestServiceTest.java new file mode 100644 index 0000000000..e83de86e88 --- /dev/null +++ b/web-admin-app/src/test/java/edu/unc/lib/boxc/web/admin/controllers/processing/ChompbPreIngestServiceTest.java @@ -0,0 +1,177 @@ +package edu.unc.lib.boxc.web.admin.controllers.processing; + +import edu.unc.lib.boxc.auth.api.Permission; +import edu.unc.lib.boxc.auth.api.exceptions.AccessRestrictionException; +import edu.unc.lib.boxc.auth.api.models.AccessGroupSet; +import edu.unc.lib.boxc.auth.api.models.AgentPrincipals; +import edu.unc.lib.boxc.auth.api.services.GlobalPermissionEvaluator; +import edu.unc.lib.boxc.auth.fcrepo.models.AccessGroupSetImpl; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +/** + * @author bbpennel + */ +public class ChompbPreIngestServiceTest { + @TempDir + public Path tmpFolder; + private AutoCloseable closeable; + private static final String PROJ_NAME = "chompb_proj"; + private static final String VELO_JOB_NAME = "velocicroptor"; + private static final String DATA_JSON = "{ \"data\": [ ] }"; + private static final String JSON_FILENAME = "data.json"; + + private ChompbPreIngestService service; + @Mock + private GlobalPermissionEvaluator globalPermissionEvaluator; + @Mock + private AgentPrincipals agentPrincipals; + @Mock + private Process mockProcess; + + @BeforeEach + public void setup() { + closeable = openMocks(this); + service = new ChompbPreIngestService(); + service.setBaseProjectsPath(tmpFolder); + service.setGlobalPermissionEvaluator(globalPermissionEvaluator); + when(agentPrincipals.getPrincipals()).thenReturn(new AccessGroupSetImpl("group")); + when(globalPermissionEvaluator.hasGlobalPermission(any(AccessGroupSet.class), eq(Permission.ingest))).thenReturn(true); + } + + @AfterEach + public void closeService() throws Exception { + closeable.close(); + } + + @Test + public void getProjectListsTest() throws Exception { + String expectedOutput = "[ { \"projectPath\" : \"/path/to/project\" } ]"; + InputStream mockInputStream = new ByteArrayInputStream(expectedOutput.getBytes()); + when(mockProcess.getInputStream()).thenReturn(mockInputStream); + when(mockProcess.waitFor()).thenReturn(0); + + try (MockedConstruction ignored = Mockito.mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenReturn(mockProcess); + }) + ){ + var output = service.getProjectLists(agentPrincipals); + assertEquals(expectedOutput, output); + } + } + + @Test + public void executeChompbCommandExitErrorTest() throws Exception { + String expectedOutput = "error"; + InputStream mockInputStream = new ByteArrayInputStream(expectedOutput.getBytes()); + when(mockProcess.getInputStream()).thenReturn(mockInputStream); + when(mockProcess.waitFor()).thenReturn(1); + + try (MockedConstruction ignored = Mockito.mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenReturn(mockProcess); + }) + ){ + var e = assertThrows(RuntimeException.class, () -> service.executeChompbCommand("chompb")); + assertEquals("Command exited with status code 1: error", e.getMessage()); + } + } + + @Test + public void executeChompbCommandStreamErrorTest() throws Exception { + try (MockedConstruction ignored = Mockito.mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenThrow(new IOException("boom")); + }) + ){ + var e = assertThrows(RuntimeException.class, () -> service.executeChompbCommand("chompb")); + assertEquals("Failed to execute chompb command", e.getMessage()); + } + } + + @Test + public void getProjectListsNoPermissionsTest() { + when(globalPermissionEvaluator.hasGlobalPermission(any(AccessGroupSet.class), eq(Permission.ingest))).thenReturn(false); + + assertThrows(AccessRestrictionException.class, + () -> service.getProjectLists(agentPrincipals)); + } + + @Test + public void getProcessingResultsTest() throws Exception { + createDataJson(); + + var results = service.getProcessingResults(agentPrincipals, PROJ_NAME, VELO_JOB_NAME, JSON_FILENAME); + assertEquals(DATA_JSON, IOUtils.toString(results, StandardCharsets.UTF_8)); + } + + @Test + public void getProcessingResultsNoPermissionTest() throws Exception { + when(globalPermissionEvaluator.hasGlobalPermission(any(AccessGroupSet.class), eq(Permission.ingest))).thenReturn(false); + createDataJson(); + + assertThrows(AccessRestrictionException.class, + () -> service.getProcessingResults(agentPrincipals, PROJ_NAME, VELO_JOB_NAME, JSON_FILENAME)); + } + + @Test + public void getProcessingResultsImageTest() throws Exception { + var imagePath = "images/path/to/image.jpg"; + var testContent = "test"; + createResultFile(imagePath, testContent); + + var results = service.getProcessingResults(agentPrincipals, PROJ_NAME, VELO_JOB_NAME, imagePath); + assertEquals(testContent, IOUtils.toString(results, StandardCharsets.UTF_8)); + } + + @Test + public void getProcessingResultsImageWithTraversalTest() throws Exception { + var imagePath = "images/../../../../../../attack.jpg"; + + assertThrows(AccessRestrictionException.class, + () -> service.getProcessingResults(agentPrincipals, PROJ_NAME, VELO_JOB_NAME, imagePath)); + } + + @Test + public void getProcessingResultsInvalidFilenameTest() throws Exception { + assertThrows(IllegalArgumentException.class, + () -> service.getProcessingResults(agentPrincipals, PROJ_NAME, VELO_JOB_NAME, "rando.json")); + } + + private void createDataJson() throws IOException { + createResultFile(JSON_FILENAME, DATA_JSON); + } + + private void createResultFile(String filename, String testContent) throws IOException { + var path = tmpFolder.resolve(PROJ_NAME) + .resolve("processing/results") + .resolve(VELO_JOB_NAME) + .resolve("report") + .resolve(filename); + Files.createDirectories(path.getParent()); + Files.writeString(path, testContent); + } +} diff --git a/web-admin-app/src/test/resources/server.properties b/web-admin-app/src/test/resources/server.properties index 2f90d8b664..67aa9da038 100644 --- a/web-admin-app/src/test/resources/server.properties +++ b/web-admin-app/src/test/resources/server.properties @@ -47,4 +47,5 @@ data.dir=/tmp/data file.max.uploadSize=100000000 repo.dir=/tmp/deposit spoofing.enabled=false -spoofing.emailSuffix=@example.com \ No newline at end of file +spoofing.emailSuffix=@example.com +chompb.projects.basePath=/tmp/projects \ No newline at end of file diff --git a/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/AccessCopiesService.java b/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/AccessCopiesService.java index aa8d549793..32fee4ab0f 100644 --- a/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/AccessCopiesService.java +++ b/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/AccessCopiesService.java @@ -24,7 +24,9 @@ import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Service to check for or list resources with access copies @@ -38,7 +40,8 @@ public class AccessCopiesService { private GlobalPermissionEvaluator globalPermissionEvaluator; private PermissionsHelper permissionsHelper; private SolrSearchService solrSearchService; - public static final String AUDIO_MIMETYPE_REGEX = "audio/(x-)?mpeg(-?3)?"; + public static final String AUDIO_MIMETYPE_REGEX = "audio/((x-)?mpeg(-?3)?|aac)"; + public static final String VIDEO_MIMETYPE_REGEX = "(video|application)/(x-)?(mpeg|mp|quicktime)(-?4)?"; public static final String PDF_MIMETYPE_REGEX = "application/(x-)?pdf"; /** @@ -51,7 +54,8 @@ public List listViewableFiles(PID pid, AccessGroupSet princ ContentObjectRecord briefObj = solrSearchService.getObjectById(new SimpleIdRequest(pid, principals)); String resourceType = briefObj.getResourceType(); if (ResourceType.File.nameEquals(resourceType)) { - if (briefObj.getDatastreamObject(DatastreamType.JP2_ACCESS_COPY.getId()) != null) { + if (briefObj.getDatastreamObject(DatastreamType.JP2_ACCESS_COPY.getId()) != null + || briefObj.getDatastreamObject(DatastreamType.AUDIO_ACCESS_COPY.getId()) != null) { return Collections.singletonList(briefObj); } else { return Collections.emptyList(); @@ -76,9 +80,10 @@ public List listViewableFiles(PID pid, AccessGroupSet princ */ public boolean hasViewableFiles(ContentObjectRecord briefObj, AccessGroupSet principals) { String resourceType = briefObj.getResourceType(); - if (ResourceType.File.nameEquals(resourceType)) { - Datastream datastream = briefObj.getDatastreamObject(DatastreamType.JP2_ACCESS_COPY.getId()); - return datastream != null; + if (ResourceType.File.nameEquals(resourceType) && + (briefObj.getDatastreamObject(DatastreamType.JP2_ACCESS_COPY.getId()) != null || + briefObj.getDatastreamObject(DatastreamType.AUDIO_ACCESS_COPY.getId()) != null)) { + return true; } if (!ResourceType.Work.nameEquals(resourceType)) { return false; @@ -107,6 +112,26 @@ public ContentObjectRecord getFirstStreamingChild(ContentObjectRecord briefObj, return null; } + public ContentObjectRecord getFirstMatchingChild(ContentObjectRecord briefObj, List fileType, AccessGroupSet principals) { + String resourceType = briefObj.getResourceType(); + if (!ResourceType.Work.nameEquals(resourceType)) { + return null; + } + + var request = buildFirstChildQuery(briefObj, principals); + // Limit query to just children which have streaming content + var searchState = request.getSearchState(); + searchState.addFilter(QueryFilterFactory.createHasValuesFilter(SearchFieldKey.FILE_FORMAT_TYPE, fileType)); + var resp = solrSearchService.getSearchResults(request); + + if (resp.getResultCount() > 0) { + var id = resp.getResultList().get(0).getId(); + log.debug("Found {} content {} for work {}", fileType, id, briefObj.getId()); + return resp.getResultList().get(0); + } + return null; + } + /** * Retrieves the ID of the owner of the original file for the provided object, if the mimetype of the * file matches the provided regular expression pattern. If there is no matching original file, null is returned. @@ -213,7 +238,9 @@ private SearchRequest buildFirstChildQuery(ContentObjectRecord briefObj, AccessG } private SearchResultResponse performQuery(ContentObjectRecord briefObj, AccessGroupSet principals, int rows) { - // Search for child objects with jp2 datastreams with user can access + // Search for child objects with jp2 or audio datastreams with user can access + Set datastreams = new HashSet<>(Arrays.asList(DatastreamType.JP2_ACCESS_COPY, + DatastreamType.AUDIO_ACCESS_COPY)); SearchState searchState = new SearchState(); if (!globalPermissionEvaluator.hasGlobalPrincipal(principals)) { searchState.setPermissionLimits(Arrays.asList(Permission.viewAccessCopies)); @@ -223,8 +250,7 @@ private SearchResultResponse performQuery(ContentObjectRecord briefObj, AccessGr CutoffFacet selectedPath = briefObj.getPath(); searchState.addFacet(selectedPath); searchState.setSortType("default"); - searchState.addFilter( - QueryFilterFactory.createFilter(SearchFieldKey.DATASTREAM, DatastreamType.JP2_ACCESS_COPY)); + searchState.addFilter(QueryFilterFactory.createFilter(SearchFieldKey.DATASTREAM, datastreams)); var searchRequest = new SearchRequest(searchState, principals); return solrSearchService.getSearchResults(searchRequest); diff --git a/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/WorkFilesizeService.java b/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/WorkFilesizeService.java new file mode 100644 index 0000000000..7d96cbeadc --- /dev/null +++ b/web-common/src/main/java/edu/unc/lib/boxc/web/common/services/WorkFilesizeService.java @@ -0,0 +1,67 @@ +package edu.unc.lib.boxc.web.common.services; + +import edu.unc.lib.boxc.auth.api.Permission; +import edu.unc.lib.boxc.auth.api.models.AccessGroupSet; +import edu.unc.lib.boxc.auth.api.services.GlobalPermissionEvaluator; +import edu.unc.lib.boxc.search.api.SearchFieldKey; +import edu.unc.lib.boxc.search.api.facets.CutoffFacet; +import edu.unc.lib.boxc.search.api.models.ContentObjectRecord; +import edu.unc.lib.boxc.search.api.requests.SearchRequest; +import edu.unc.lib.boxc.search.api.requests.SearchState; +import edu.unc.lib.boxc.search.solr.services.SolrSearchService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Service to retrieve the total filesize of all files in a work + * + * @author lfarrell + */ +public class WorkFilesizeService { + private static final Logger log = LoggerFactory.getLogger(AccessCopiesService.class); + + private GlobalPermissionEvaluator globalPermissionEvaluator; + private SolrSearchService solrSearchService; + + public Long getTotalFilesize(ContentObjectRecord contentObjectRecord, AccessGroupSet principals) { + var request = buildChildrenQuery(contentObjectRecord, principals); + var resp = solrSearchService.getSearchResults(request); + + if (resp.getResultCount() > 0) { + return resp.getResultList().stream() + .mapToLong(ContentObjectRecord::getFilesizeSort) + .reduce(0, Long::sum); + } else { + log.debug("No child objects for work {}", contentObjectRecord.getId()); + return null; + } + } + + private SearchRequest buildChildrenQuery(ContentObjectRecord briefObj, AccessGroupSet principals) { + SearchState searchState = new SearchState(); + if (!globalPermissionEvaluator.hasGlobalPrincipal(principals)) { + searchState.setPermissionLimits(List.of(Permission.viewOriginal)); + } + searchState.setFacetsToRetrieve(null); + searchState.setIgnoreMaxRows(true); + searchState.setRowsPerPage(3000); + searchState.setSortType("default"); + searchState.setResultFields(List.of(SearchFieldKey.FILESIZE.name())); + CutoffFacet selectedPath = briefObj.getPath(); + searchState.addFacet(selectedPath); + SearchRequest searchRequest = new SearchRequest(searchState, principals); + searchRequest.setApplyCutoffs(true); + + return searchRequest; + } + + public void setGlobalPermissionEvaluator(GlobalPermissionEvaluator globalPermissionEvaluator) { + this.globalPermissionEvaluator = globalPermissionEvaluator; + } + + public void setSolrSearchService(SolrSearchService solrSearchService) { + this.solrSearchService = solrSearchService; + } +} diff --git a/web-common/src/main/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtil.java b/web-common/src/main/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtil.java index 5ba6695bf8..02252e894f 100644 --- a/web-common/src/main/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtil.java +++ b/web-common/src/main/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtil.java @@ -3,7 +3,6 @@ import edu.unc.lib.boxc.model.api.DatastreamType; import edu.unc.lib.boxc.search.api.models.ContentObjectRecord; import edu.unc.lib.boxc.search.api.models.Datastream; -import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -87,11 +86,13 @@ public static boolean originalFileMimetypeMatches(ContentObjectRecord metadata, if (formatTypes == null || formatTypes.isEmpty()) { return false; } - String mimetype = formatTypes.get(0); - if (StringUtils.isBlank(mimetype)) { - return false; + + for (String format : formatTypes) { + if (format.matches(pattern)) { + return true; + } } - return mimetype.matches(pattern); + return false; } /** diff --git a/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/AccessCopiesServiceTest.java b/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/AccessCopiesServiceTest.java index 8735914cef..ea7e71200d 100644 --- a/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/AccessCopiesServiceTest.java +++ b/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/AccessCopiesServiceTest.java @@ -11,6 +11,7 @@ import edu.unc.lib.boxc.search.api.models.ContentObjectRecord; import edu.unc.lib.boxc.search.api.requests.SearchRequest; import edu.unc.lib.boxc.search.solr.filters.HasPopulatedFieldFilter; +import edu.unc.lib.boxc.search.solr.filters.MultipleDirectlyOwnedDatastreamsFilter; import edu.unc.lib.boxc.search.solr.filters.NamedDatastreamFilter; import edu.unc.lib.boxc.search.solr.models.ContentObjectSolrRecord; import edu.unc.lib.boxc.search.solr.responses.SearchResultResponse; @@ -26,7 +27,9 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import static edu.unc.lib.boxc.auth.api.Permission.viewOriginal; @@ -104,7 +107,6 @@ public void init() throws IOException, SolrServerException { accessCopiesService.setGlobalPermissionEvaluator(globalPermissionEvaluator); when(solrSearchService.getSearchResults(searchRequestCaptor.capture())).thenReturn(searchResultResponse); - when(searchResultResponse.getResultCount()).thenReturn(1L); } @AfterEach @@ -152,6 +154,18 @@ private ContentObjectSolrRecord createPdfObject(ResourceType resourceType) { return mdObject; } + private ContentObjectSolrRecord createXPdfObject(ResourceType resourceType) { + var mdObject = new ContentObjectSolrRecord(); + mdObject.setResourceType(resourceType.name()); + mdObject.setId(UUID.randomUUID().toString()); + List datastreams = Collections.singletonList( + ORIGINAL_FILE.getId() + "|application/x-pdf|file.pdf|pdf|766|urn:sha1:checksum|"); + mdObject.setFileFormatCategory(Collections.singletonList(ContentCategory.text.getDisplayName())); + mdObject.setFileFormatType(Collections.singletonList("application/x-pdf")); + mdObject.setDatastream(datastreams); + return mdObject; + } + private ContentObjectSolrRecord createImgObject(ResourceType resourceType) { var mdObjectImg = new ContentObjectSolrRecord(); mdObjectImg.setResourceType(resourceType.name()); @@ -322,6 +336,13 @@ private void assertRequestedDatastreamFilter(DatastreamType expectedType) { "Expected request to be filtered by datastream " + expectedType.name()); } + private void assertRequestedDatastreamFilters(Set expectedTypeSet) { + var searchState = searchRequestCaptor.getValue().getSearchState(); + var queryFilter = (MultipleDirectlyOwnedDatastreamsFilter) searchState.getFilters().get(0); + assertEquals(expectedTypeSet, queryFilter.getDatastreamTypes(), + "Expected request to be filtered by datastreams " + expectedTypeSet); + } + private void assertHasPopulatedFieldFilter(SearchFieldKey expectedKey) { var searchState = searchRequestCaptor.getValue().getSearchState(); var queryFilter = (HasPopulatedFieldFilter) searchState.getFilters().get(0); @@ -380,16 +401,18 @@ public void hasViewableFilesImageFileTest() { public void hasViewableFilesAudioFileTest() { var mdObjectAudio = createAudioObject(ResourceType.File); hasPermissions(mdObjectAudio, true); - + when(searchResultResponse.getResultCount()).thenReturn(1L); assertFalse(accessCopiesService.hasViewableFiles(mdObjectAudio, principals)); } @Test public void hasViewableFilesImageWorkTest() { hasPermissions(mdObjectImg, true); - + when(searchResultResponse.getResultCount()).thenReturn(1L); assertTrue(accessCopiesService.hasViewableFiles(mdObjectImg, principals)); - assertRequestedDatastreamFilter(DatastreamType.JP2_ACCESS_COPY); + Set datastreams = new HashSet<>(Arrays.asList(DatastreamType.JP2_ACCESS_COPY, + DatastreamType.AUDIO_ACCESS_COPY)); + assertRequestedDatastreamFilters(datastreams); } @Test @@ -398,6 +421,7 @@ public void hasStreamingSoundTest() { hasPermissions(mdObjectAudio, true); when(searchResultResponse.getResultList()).thenReturn(List.of(mdObjectAudio)); + when(searchResultResponse.getResultCount()).thenReturn(1L); var audioObj = accessCopiesService.getFirstStreamingChild(mdObjectAudio, principals); assertEquals("sound", audioObj.getStreamingType()); assertHasPopulatedFieldFilter(SearchFieldKey.STREAMING_TYPE); @@ -408,6 +432,7 @@ public void hasStreamingVideoTest() { var mdObjectVideo = createVideoObject(ResourceType.Work); hasPermissions(mdObjectVideo, true); when(searchResultResponse.getResultList()).thenReturn(List.of(mdObjectVideo)); + when(searchResultResponse.getResultCount()).thenReturn(1L); var videoObj = accessCopiesService.getFirstStreamingChild(mdObjectVideo, principals); assertEquals("video", videoObj.getStreamingType()); assertHasPopulatedFieldFilter(SearchFieldKey.STREAMING_TYPE); @@ -429,6 +454,51 @@ public void doesNotHaveStreamingNonWorkTest() { assertNull(accessCopiesService.getFirstStreamingChild(mdObjectVideoFile, principals)); } + @Test + public void hasMatchingChildTest() { + var mdObjectPdf = createPdfObject(ResourceType.Work); + hasPermissions(mdObjectPdf, true); + when(searchResultResponse.getResultList()).thenReturn(List.of(mdObjectPdf)); + when(searchResultResponse.getResultCount()).thenReturn(1L); + var pdfObj = accessCopiesService.getFirstMatchingChild(mdObjectPdf, + List.of("application/pdf"), principals); + assertNotNull(pdfObj); + assertTrue(pdfObj.getFileFormatType().contains("application/pdf")); + } + + @Test + public void hasMatchingChildXPDFTest() { + var mdObjectPdf = createXPdfObject(ResourceType.Work); + hasPermissions(mdObjectPdf, true); + when(searchResultResponse.getResultList()).thenReturn(List.of(mdObjectPdf)); + when(searchResultResponse.getResultCount()).thenReturn(1L); + var pdfObj = accessCopiesService.getFirstMatchingChild(mdObjectPdf, + Arrays.asList("application/pdf", "application/x-pdf"), principals); + assertNotNull(pdfObj); + assertTrue(pdfObj.getFileFormatType().contains("application/x-pdf")); + } + + @Test + public void hasNoMatchingChildForSpecifiedFileTypeTest() { + hasPermissions(mdObjectXml, true); + when(searchResultResponse.getResultList()).thenReturn(List.of(mdObjectXml)); + when(searchResultResponse.getResultCount()).thenReturn(0L); + var xmlObj = accessCopiesService.getFirstMatchingChild(mdObjectXml, + List.of("application/pdf"), principals); + + assertNull(xmlObj); + } + + @Test + public void hasNoMatchingChildForFilesTest() { + var mdObject = createPdfObject(ResourceType.File); + hasPermissions(mdObject, true); + when(searchResultResponse.getResultList()).thenReturn(List.of(mdObject)); + var obj = accessCopiesService.getFirstMatchingChild(mdObject, + List.of("application/pdf"), principals); + assertNull(obj); + } + private void hasPermissions(ContentObjectSolrRecord contentObject, boolean hasAccess) { when(accessControlService.hasAccess(contentObject.getPid(), principals, viewOriginal)).thenReturn(hasAccess); } diff --git a/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/WorkFilesizeServiceTest.java b/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/WorkFilesizeServiceTest.java new file mode 100644 index 0000000000..ca468e2ee6 --- /dev/null +++ b/web-common/src/test/java/edu/unc/lib/boxc/web/common/services/WorkFilesizeServiceTest.java @@ -0,0 +1,92 @@ +package edu.unc.lib.boxc.web.common.services; + +import edu.unc.lib.boxc.auth.api.models.AccessGroupSet; +import edu.unc.lib.boxc.auth.api.services.GlobalPermissionEvaluator; +import edu.unc.lib.boxc.auth.fcrepo.models.AccessGroupSetImpl; +import edu.unc.lib.boxc.model.api.ResourceType; +import edu.unc.lib.boxc.search.api.ContentCategory; +import edu.unc.lib.boxc.search.api.requests.SearchRequest; +import edu.unc.lib.boxc.search.solr.models.ContentObjectSolrRecord; +import edu.unc.lib.boxc.search.solr.responses.SearchResultResponse; +import edu.unc.lib.boxc.search.solr.services.SolrSearchService; +import org.apache.solr.client.solrj.SolrServerException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY; +import static edu.unc.lib.boxc.model.api.DatastreamType.ORIGINAL_FILE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +/** + * @author lfarrell + */ +public class WorkFilesizeServiceTest { + private AccessGroupSet principals; + + private WorkFilesizeService workFilesizeService; + + private AutoCloseable closeable; + + @Mock + private GlobalPermissionEvaluator globalPermissionEvaluator; + @Mock + private SolrSearchService solrSearchService; + @Mock + private SearchResultResponse searchResultResponse; + @Captor + private ArgumentCaptor searchRequestCaptor; + + @BeforeEach + public void init() throws IOException, SolrServerException { + closeable = openMocks(this); + + principals = new AccessGroupSetImpl("group"); + + workFilesizeService = new WorkFilesizeService(); + workFilesizeService.setSolrSearchService(solrSearchService); + workFilesizeService.setGlobalPermissionEvaluator(globalPermissionEvaluator); + + when(solrSearchService.getSearchResults(searchRequestCaptor.capture())).thenReturn(searchResultResponse); + when(searchResultResponse.getResultCount()).thenReturn(1L); + } + + @AfterEach + void closeService() throws Exception { + closeable.close(); + } + + @Test + public void workObjectWithBulkDownload() { + var fileSize = 8194L; + var mdObject = createObject(ResourceType.Work, fileSize); + when(solrSearchService.getSearchResults(searchRequestCaptor.capture()).getResultList()) + .thenReturn(List.of(mdObject)); + assertEquals(fileSize, workFilesizeService.getTotalFilesize(mdObject, principals)); + } + + private ContentObjectSolrRecord createObject(ResourceType resourceType, Long filesize) { + var mdObjectImg = new ContentObjectSolrRecord(); + mdObjectImg.setResourceType(resourceType.name()); + var id = UUID.randomUUID().toString(); + mdObjectImg.setId(id); + mdObjectImg.setFilesizeSort(filesize); + List imgDatastreams = List.of( + ORIGINAL_FILE.getId() + "|image/png|file.png|png|" + filesize + "|urn:sha1:checksum|", + JP2_ACCESS_COPY.getId() + "|image/jp2|bunny.jp2|jp2|||" + id + "|1200x1200"); + mdObjectImg.setFileFormatCategory(Collections.singletonList(ContentCategory.image.getDisplayName())); + mdObjectImg.setFileFormatType(Collections.singletonList("image/png")); + mdObjectImg.setDatastream(imgDatastreams); + return mdObjectImg; + } +} diff --git a/web-common/src/test/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtilTest.java b/web-common/src/test/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtilTest.java index 7d3f0bdc0e..94bb77e42f 100644 --- a/web-common/src/test/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtilTest.java +++ b/web-common/src/test/java/edu/unc/lib/boxc/web/common/utils/DatastreamUtilTest.java @@ -13,7 +13,9 @@ import static edu.unc.lib.boxc.model.fcrepo.test.TestHelper.makePid; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @@ -109,4 +111,16 @@ public void testGetThumbnailUrlNoThumbs() { assertNull(id); assertNull(DatastreamUtil.constructThumbnailUrl(id)); } + + @Test + public void testOriginalFileMimetypesMatching() { + PID pid = makePid(); + ContentObjectSolrRecord mdObj = new ContentObjectSolrRecord(); + mdObj.setId(pid.getId()); + mdObj.setFileFormatType(asList("text/rtf", "application/pdf")); + + assertTrue(DatastreamUtil.originalFileMimetypeMatches(mdObj, "application/pdf")); + assertTrue(DatastreamUtil.originalFileMimetypeMatches(mdObj, "text/rtf")); + assertFalse(DatastreamUtil.originalFileMimetypeMatches(mdObj, "image/png")); + } } diff --git a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java index 6a8a21733e..9bfd9e2684 100644 --- a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java +++ b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java @@ -45,10 +45,13 @@ import java.util.Objects; import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY; +import static info.freelibrary.iiif.presentation.v3.MediaType.AUDIO_AAC; import static info.freelibrary.iiif.presentation.v3.MediaType.AUDIO_MP4; import static info.freelibrary.iiif.presentation.v3.MediaType.AUDIO_MPEG; import static info.freelibrary.iiif.presentation.v3.MediaType.IMAGE_JPEG; import static info.freelibrary.iiif.presentation.v3.MediaType.VIDEO_MP4; +import static info.freelibrary.iiif.presentation.v3.MediaType.VIDEO_MPEG; +import static info.freelibrary.iiif.presentation.v3.MediaType.VIDEO_QUICKTIME; import static info.freelibrary.iiif.presentation.v3.properties.behaviors.ManifestBehavior.from; /** @@ -60,7 +63,8 @@ public class IiifV3ManifestService { public static final String DURATION = "duration"; public static final String WIDTH = "width"; public static final String HEIGHT = "height"; - private static final List FILE_TYPES = Arrays.asList(VIDEO_MP4.toString(), AUDIO_MP4.toString(), AUDIO_MPEG.toString()); + private static final List FILE_TYPES = Arrays.asList(VIDEO_MP4.toString(), VIDEO_MPEG.toString(), + VIDEO_QUICKTIME.toString(), AUDIO_MP4.toString(), AUDIO_MPEG.toString()); private AccessControlService accessControlService; private SolrSearchService solrSearchService; private GlobalPermissionEvaluator globalPermissionEvaluator; @@ -176,7 +180,7 @@ private Canvas constructCanvasSection(ContentObjectRecord contentObj) { // Child of the Annotation is a Content Object var mimetype = getMimetype(contentObj); - if (isAudio(mimetype)) { + if (isAudio(mimetype, contentObj)) { setSoundContent(contentObj, paintingAnno, canvas); } else if (isVideo(mimetype)) { setVideoContent(contentObj, paintingAnno, canvas); @@ -192,7 +196,12 @@ private Canvas constructCanvasSection(ContentObjectRecord contentObj) { } private void setSoundContent(ContentObjectRecord contentObj, PaintingAnnotation paintingAnno, Canvas canvas) { - var soundContent = new SoundContent(getDownloadPath(contentObj)); + SoundContent soundContent; + if (contentObj.getDatastreamObject(DatastreamType.AUDIO_ACCESS_COPY.getId()) != null) { + soundContent = new SoundContent(getAccessPath(contentObj)); + } else { + soundContent = new SoundContent(getDownloadPath(contentObj)); + } soundContent.setFormat(AUDIO_MP4); var dimensions = getDimensions(contentObj); if (dimensions != null && (dimensions.get(DURATION) >= 0)) { @@ -290,11 +299,17 @@ private String getMimetype(ContentObjectRecord contentObj) { } private boolean isVideo(String mimetype) { - return Objects.equals(mimetype, VIDEO_MP4.toString()); + return Objects.equals(mimetype, VIDEO_MP4.toString()) || Objects.equals(mimetype, VIDEO_MPEG.toString()) + || Objects.equals(mimetype, VIDEO_QUICKTIME.toString()); } - private boolean isAudio(String mimetype) { - return Objects.equals(mimetype, AUDIO_MP4.toString()) || Objects.equals(mimetype, AUDIO_MPEG.toString()); + private boolean isAudio(String mimetype, ContentObjectRecord contentObj) { + if (contentObj.getDatastreamObject(DatastreamType.AUDIO_ACCESS_COPY.getId()) != null) { + return true; + } + + return Objects.equals(mimetype, AUDIO_MP4.toString()) || Objects.equals(mimetype, AUDIO_MPEG.toString()) + || Objects.equals(mimetype, AUDIO_AAC.toString()); } private boolean hasViewableContent(ContentObjectRecord contentObj) { @@ -309,7 +324,7 @@ private boolean hasViewableContent(ContentObjectRecord contentObj) { // check if original datastream mimetype is image or video if (!isValidDatastream && originalDatastream != null) { var mimetype = originalDatastream.getMimetype(); - isValidDatastream = isAudio(mimetype) || isVideo(mimetype); + isValidDatastream = isAudio(mimetype, contentObj) || isVideo(mimetype); } return isValidDatastream; @@ -408,6 +423,11 @@ private String getDownloadPath(ContentObjectRecord contentObj) { return URIUtil.join(baseServicesApiPath, "file", contentObj.getId()); } + private String getAccessPath(ContentObjectRecord contentObj) { + return URIUtil.join(baseServicesApiPath, "file", contentObj.getId(), + DatastreamType.AUDIO_ACCESS_COPY.getId()); + } + public void setAccessControlService(AccessControlService accessControlService) { this.accessControlService = accessControlService; } diff --git a/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java b/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java index 35fc06a483..a9612bbea8 100644 --- a/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java +++ b/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java @@ -51,6 +51,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static edu.unc.lib.boxc.auth.api.Permission.viewAccessCopies; import static edu.unc.lib.boxc.auth.api.Permission.viewHidden; +import static edu.unc.lib.boxc.model.api.DatastreamType.AUDIO_ACCESS_COPY; import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY; import static edu.unc.lib.boxc.model.api.DatastreamType.MD_EVENTS; import static edu.unc.lib.boxc.model.api.DatastreamType.TECHNICAL_METADATA; @@ -327,6 +328,25 @@ public void testGetFileDerivative() throws Exception { response.getHeader(CONTENT_DISPOSITION)); } + @Test + public void testGetAudioFileDerivative() throws Exception { + PID filePid = makePid(); + String id = filePid.getId(); + createDerivative(id, AUDIO_ACCESS_COPY, BINARY_CONTENT.getBytes()); + + MvcResult result = mvc.perform(get("/file/" + filePid.getId() + "/" + AUDIO_ACCESS_COPY.getId())) + .andExpect(status().is2xxSuccessful()) + .andReturn(); + + // Verify content was retrieved + MockHttpServletResponse response = result.getResponse(); + assertEquals(BINARY_CONTENT, response.getContentAsString()); + assertEquals(BINARY_CONTENT.length(), response.getContentLength()); + assertEquals("audio/aac", response.getContentType()); + assertEquals("inline; filename=\"" + id + "." + AUDIO_ACCESS_COPY.getExtension() + "\"", + response.getHeader(CONTENT_DISPOSITION)); + } + @Test public void testGetThumbnailNotPresent() throws Exception { PID filePid = makePid(); diff --git a/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/IiifV3ManifestControllerTest.java b/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/IiifV3ManifestControllerTest.java index f2c78744d2..e0d807a21a 100644 --- a/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/IiifV3ManifestControllerTest.java +++ b/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/IiifV3ManifestControllerTest.java @@ -236,6 +236,34 @@ public void testGetCanvasWithAudioFile() throws Exception { var respJson = MvcTestHelpers.getResponseAsJson(result); var body = respJson.get("items").get(0).get("items").get(0).get("body"); assertEquals("http://example.com/services/file/f277bb38-272c-471c-a28a-9887a1328a1f", body.get("id").textValue()); + assertEquals("audio/mp4", body.get("format").textValue()); + assertEquals("Sound", body.get("type").textValue()); + assertEquals(500, body.get(DURATION).intValue()); + } + + @Test + public void testGetCanvasWithAudioAccessFile() throws Exception { + var fileObj = new ContentObjectSolrRecord(); + fileObj.setId(OBJECT_ID); + fileObj.setResourceType(ResourceType.File.name()); + fileObj.setTitle("File Object"); + var originalDs = new DatastreamImpl("original_file|audio/mp4|sound.mp3|mp3|0|||xx500"); + var audioAccessDs = new DatastreamImpl("audio|audio/aac|audio.m4a|aac|0|||xx500"); + fileObj.setDatastream(Arrays.asList(originalDs.toString(), audioAccessDs.toString())); + when(solrSearchService.getSearchResults(any())) + .thenReturn(MvcTestHelpers.createSearchResponse(List.of(fileObj))); + when(solrSearchService.getObjectById(any())).thenReturn(fileObj); + when(globalPermissionEvaluator.hasGlobalPrincipal(any())).thenReturn(true); + + var result = mockMvc.perform(get("/iiif/v3/" + OBJECT_ID + "/canvas") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + var respJson = MvcTestHelpers.getResponseAsJson(result); + var body = respJson.get("items").get(0).get("items").get(0).get("body"); + assertEquals("http://example.com/services/file/f277bb38-272c-471c-a28a-9887a1328a1f/audio", body.get("id").textValue()); + assertEquals("audio/mp4", body.get("format").textValue()); assertEquals("Sound", body.get("type").textValue()); assertEquals(500, body.get(DURATION).intValue()); }