diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java index 9b1002c527..96978cdeae 100644 --- a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java +++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java @@ -41,7 +41,8 @@ import java.util.NoSuchElementException; /** - * This class is responsible for managing all the adapter instances which are executed on worker nodes + * This class is responsible for managing all the adapter instances which are + * executed on worker nodes */ public class AdapterMasterManagement { @@ -57,8 +58,7 @@ public AdapterMasterManagement( IAdapterStorage adapterInstanceStorage, AdapterResourceManager adapterResourceManager, DataStreamResourceManager dataStreamResourceManager, - AdapterMetrics adapterMetrics - ) { + AdapterMetrics adapterMetrics) { this.adapterInstanceStorage = adapterInstanceStorage; this.adapterMetrics = adapterMetrics; this.adapterResourceManager = adapterResourceManager; @@ -68,8 +68,7 @@ public AdapterMasterManagement( public void addAdapter( AdapterDescription adapterDescription, String adapterId, - String principalSid - ) + String principalSid) throws AdapterException { // Create elementId for datastream @@ -92,8 +91,7 @@ private void createDataStreamForAdapter( AdapterDescription adapterDescription, String adapterId, String streamId, - String principalSid - ) throws AdapterException { + String principalSid) throws AdapterException { var storedDescription = new SourcesManagement() .createAdapterDataStream(adapterDescription, streamId); storedDescription.setCorrespondingAdapterId(adapterId); @@ -116,7 +114,8 @@ public AdapterDescription getAdapter(String elementId) throws AdapterException { } /** - * First the adapter is stopped removed, then the corresponding data source is deleted + * First the adapter is stopped removed, then the corresponding data source is + * deleted * * @param elementId The elementId of the adapter instance * @throws AdapterException when adapter can not be stopped @@ -145,8 +144,20 @@ public List getAllAdapterInstances() { return adapterInstanceStorage.findAll(); } + public List getPaginatedAdapterInstances(String startKey, String endKey, int limit, String view, + boolean descending) { + return adapterInstanceStorage.getAdapterPaginator(startKey,endKey, limit, view, descending); + } + + + public List getItemsByCategoryPaginated(String category, String startKey, int limit, + boolean descending) { + return adapterInstanceStorage.getItemsByCategoryPaginated(category,startKey, limit, descending); + } + + public void stopStreamAdapter(String elementId, - boolean forceStop) throws AdapterException { + boolean forceStop) throws AdapterException { AdapterDescription ad = adapterInstanceStorage.getElementById(elementId); try { @@ -181,8 +192,7 @@ public void startStreamAdapter(String elementId) throws AdapterException { ad.getAppId(), SpServiceUrlProvider.ADAPTER, ad.getDeploymentConfiguration() - .getDesiredServiceTags() - ); + .getDesiredServiceTags()); // Update selected endpoint URL of adapter ad.setSelectedEndpointUrl(baseUrl); @@ -191,7 +201,8 @@ public void startStreamAdapter(String elementId) throws AdapterException { // Invoke adapter instance WorkerRestClient.invokeStreamAdapter(baseUrl, elementId); - // register the adapter at the metrics manager so that the AdapterHealthCheck can send metrics + // register the adapter at the metrics manager so that the AdapterHealthCheck + // can send metrics adapterMetrics.register(ad.getElementId(), ad.getName()); LOG.info("Started adapter " + elementId + " on: " + baseUrl); @@ -202,8 +213,7 @@ public void startStreamAdapter(String elementId) throws AdapterException { private void installDataSource( SpDataStream stream, - String principalSid - ) throws AdapterException { + String principalSid) throws AdapterException { try { new DataStreamVerifier(stream).verifyAndAdd(principalSid, false); } catch (SepaParseException e) { diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java index 1e627973c6..2df4e3b42d 100644 --- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java +++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java @@ -52,8 +52,7 @@ public void install() { new CreateAssetLinkTypeTask().execute(); new CreateDefaultAssetTask().execute(); new AddDefaultPipelineTemplatesTask( - StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineTemplateStorage() - ).execute(); + StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineTemplateStorage()).execute(); } @Override @@ -79,6 +78,7 @@ private void createViews() { addNotificationView(); addPipelineView(); addDataLakeMeasureView(); + addPaginatorView(); } private void addNotificationView() { @@ -113,8 +113,7 @@ private void addNotificationView() { + "}"); notificationCountTypeViews.put("unread", countFunction); notificationCountDocument.setViews(notificationCountTypeViews); - Response countResp = - Utils.getCouchDbNotificationClient().design().synchronizeWithDb(notificationCountDocument); + Response countResp = Utils.getCouchDbNotificationClient().design().synchronizeWithDb(notificationCountDocument); if (resp.getError() != null && countResp != null) { logFailure(PREPARING_NOTIFICATIONS_TEXT); @@ -126,6 +125,70 @@ private void addNotificationView() { } } + private void addPaginatorView() { + DesignDocument paginatorDocument = prepareDocument("_design/paginator"); + + Map paginatorViews = new HashMap<>(); + + // View to paginate documents by creation time + MapReduce paginationFunctionByCreate = new MapReduce(); + paginationFunctionByCreate.setMap( + "function (doc) {\n" + + " if (doc.properties && doc.properties.createdAt) {\n" + + " emit(doc.properties.createdAt, doc);\n" + + " }\n" + + "}"); + + // View to paginate documents by name + MapReduce paginationFunctionByName = new MapReduce(); + paginationFunctionByName.setMap( + "function (doc) {\n" + + " if (doc.properties && doc.properties.name && typeof doc.properties.name === 'string') {\n" + + " emit(doc.properties.name, doc);\n" + + " }\n" + + "}"); + + // View to paginate documents by running + MapReduce paginationFunctionByRunning = new MapReduce(); + paginationFunctionByRunning.setMap( + "function (doc) {\n" + + " emit([doc.properties.running, doc._id], doc);\n" + + "}"); + + MapReduce paginationFunctionByCategory = new MapReduce(); + paginationFunctionByCategory.setMap( + "function (doc) {\n" + + " if (doc.properties && Array.isArray(doc.properties.category)) {\n" + + " doc.properties.category.forEach(function (cat) {\n" + + " emit([cat, doc._id], doc);\n" + + " });\n" + + " }\n" + + "}"); + + // View to list all non-design documents + MapReduce nonDesignDocsView = new MapReduce(); + nonDesignDocsView.setMap( + "function (doc) {\n" + + " if (!doc._id.startsWith(\"_design/\")) {\n" + + " emit(doc._id, doc);\n" + + " }\n" + + "}"); + + // Add views to the document + paginatorViews.put("by_createdAt", paginationFunctionByCreate); + paginatorViews.put("by_name", paginationFunctionByName); + paginatorViews.put("by_running", paginationFunctionByRunning); + paginatorViews.put("by_category", paginationFunctionByCategory); + paginatorViews.put("non_design_docs", nonDesignDocsView); + + paginatorDocument.setViews(paginatorViews); + + // Push the design document to CouchDB + Utils.getCouchDbAdapterInstanceClient() + .design() + .synchronizeWithDb(paginatorDocument); + } + private void addPipelineView() { DesignDocument pipelineDocument = prepareDocument("_design/adapters"); DesignDocument allPipelinesDocument = prepareDocument("_design/pipelines"); @@ -146,7 +209,6 @@ private void addPipelineView() { pipelineDocument.setViews(adapterViews); Utils.getCouchDbPipelineClient().design().synchronizeWithDb(pipelineDocument); - MapReduce allPipelinesFunction = new MapReduce(); allPipelinesFunction.setMap("function (doc) {\n" + " emit(doc._id, doc);\n" diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java index e5804a563c..d1c5592bc1 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java @@ -44,7 +44,8 @@ import java.util.Map; public class ResetManagement { - // This class should be moved into another package. I moved it here because I got a cyclic maven + // This class should be moved into another package. I moved it here because I + // got a cyclic maven // dependency between this package and streampipes-pipeline-management // See in issue [STREAMPIPES-405] @@ -104,11 +105,10 @@ private static void stopAndDeleteAllPipelines() { private static void stopAndDeleteAllAdapters() { AdapterMasterManagement adapterMasterManagement = new AdapterMasterManagement( StorageDispatcher.INSTANCE.getNoSqlStore() - .getAdapterInstanceStorage(), + .getAdapterInstanceStorage(), new SpResourceManager().manageAdapters(), new SpResourceManager().manageDataStreams(), - AdapterMetricsManager.INSTANCE.getAdapterMetrics() - ); + AdapterMetricsManager.INSTANCE.getAdapterMetrics()); List allAdapters = adapterMasterManagement.getAllAdapterInstances(); allAdapters.forEach(adapterDescription -> { @@ -144,24 +144,22 @@ private static void removeAllDataInDataLake() { } private static void removeAllDataViewWidgets() { - var widgetStorage = - StorageDispatcher.INSTANCE.getNoSqlStore() - .getDataExplorerWidgetStorage(); + var widgetStorage = StorageDispatcher.INSTANCE.getNoSqlStore() + .getDataExplorerWidgetStorage(); widgetStorage.findAll() - .forEach(widget -> widgetStorage.deleteElementById(widget.getElementId())); + .forEach(widget -> widgetStorage.deleteElementById(widget.getElementId())); } private static void removeAllDataViews() { - var dataLakeDashboardStorage = - StorageDispatcher.INSTANCE.getNoSqlStore() - .getDataExplorerDashboardStorage(); + var dataLakeDashboardStorage = StorageDispatcher.INSTANCE.getNoSqlStore() + .getDataExplorerDashboardStorage(); dataLakeDashboardStorage.findAll() - .forEach(dashboard -> dataLakeDashboardStorage.deleteElementById(dashboard.getElementId())); + .forEach(dashboard -> dataLakeDashboardStorage.deleteElementById(dashboard.getElementId())); } private static void removeAllAssets(String username) { IGenericStorage genericStorage = StorageDispatcher.INSTANCE.getNoSqlStore() - .getGenericStorage(); + .getGenericStorage(); try { for (Map asset : genericStorage.findAll("asset-management")) { genericStorage.delete((String) asset.get("_id"), (String) asset.get("_rev")); @@ -172,8 +170,7 @@ private static void removeAllAssets(String username) { } private static void removeAllPipelineTemplates() { - var pipelineElementTemplateStorage = StorageDispatcher - .INSTANCE + var pipelineElementTemplateStorage = StorageDispatcher.INSTANCE .getNoSqlStore() .getPipelineElementTemplateStorage(); @@ -186,10 +183,9 @@ private static void removeAllPipelineTemplates() { private static void clearGenericStorage() { var appDocTypesToDelete = List.of( "asset-management", - "asset-sites" - ); + "asset-sites"); var genericStorage = StorageDispatcher.INSTANCE.getNoSqlStore() - .getGenericStorage(); + .getGenericStorage(); appDocTypesToDelete.forEach(docType -> { try { @@ -197,10 +193,9 @@ private static void clearGenericStorage() { for (var doc : allDocs) { genericStorage.delete( doc.get("_id") - .toString(), + .toString(), doc.get("_rev") - .toString() - ); + .toString()); } } catch (IOException e) { throw new RuntimeException(e); diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java index cf046735b1..3d60bbbd35 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java @@ -72,11 +72,10 @@ public class AdapterResource extends AbstractAdapterResource new AdapterMasterManagement( StorageDispatcher.INSTANCE.getNoSqlStore() - .getAdapterInstanceStorage(), + .getAdapterInstanceStorage(), new SpResourceManager().manageAdapters(), new SpResourceManager().manageDataStreams(), - AdapterMetricsManager.INSTANCE.getAdapterMetrics() - )); + AdapterMetricsManager.INSTANCE.getAdapterMetrics())); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @@ -99,7 +98,7 @@ public ResponseEntity addAdapter(@RequestBody AdapterDescript return ok(Notifications.success(adapterId)); } - @PostMapping(path = "compact", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = { + @PostMapping(path = "compact", consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE, SpMediaType.YAML, SpMediaType.YML @@ -124,36 +123,32 @@ public ResponseEntity updateAdapter(@RequestBody AdapterDescr return ok(Notifications.success(adapterDescription.getElementId())); } - @PutMapping(path = "pipeline-migration-preflight", consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + @PutMapping(path = "pipeline-migration-preflight", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE) public ResponseEntity> performPipelineMigrationPreflight( - @RequestBody AdapterDescription adapterDescription - ) { + @RequestBody AdapterDescription adapterDescription) { var updateManager = new AdapterUpdateManagement(managementService); var migrations = updateManager.checkPipelineMigrations(adapterDescription); return ok(migrations); } - @GetMapping(path = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, SpMediaType.YAML, SpMediaType.YML}) + @GetMapping(path = "/{id}", produces = { MediaType.APPLICATION_JSON_VALUE, SpMediaType.YAML, SpMediaType.YML }) @PreAuthorize("this.hasReadAuthority()") public ResponseEntity getAdapter( @PathVariable("id") String elementId, - @RequestParam(value = "output", - defaultValue = "full", - required = false) String outputMode - ) { + @RequestParam(value = "output", defaultValue = "full", required = false) String outputMode) { try { var adapterDescription = getAdapterDescription(elementId); - // This check is done here because the adapter permission is checked based on the corresponding data stream + // This check is done here because the adapter permission is checked based on + // the corresponding data stream // and not based on the element id if (!checkAdapterReadPermission(adapterDescription)) { LOG.error("User is not allowed to read adapter {}", elementId); return ResponseEntity.status(HttpStatus.SC_UNAUTHORIZED) - .build(); + .build(); } if (outputMode.equalsIgnoreCase("compact")) { @@ -176,18 +171,17 @@ public ResponseEntity getAdapter( private boolean checkAdapterReadPermission(AdapterDescription adapterDescription) { var spPermissionEvaluator = new SpPermissionEvaluator(); var authentication = SecurityContextHolder.getContext() - .getAuthentication(); + .getAuthentication(); return spPermissionEvaluator.hasPermission( authentication, adapterDescription.getCorrespondingDataStreamElementId(), - "READ" - ); + "READ"); } @PostMapping(path = "/{id}/stop", produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("this.hasWriteAuthority() and hasPermission('#elementId', 'WRITE')") public ResponseEntity stopAdapter(@PathVariable("id") String elementId, - @RequestParam(value = "forceStop", defaultValue = "false") boolean forceStop) { + @RequestParam(value = "forceStop", defaultValue = "false") boolean forceStop) { try { managementService.stopStreamAdapter(elementId, forceStop); return ok(Notifications.success("Adapter stopped")); @@ -213,12 +207,10 @@ public ResponseEntity startAdapter(@PathVariable("id") String elementId) { @PreAuthorize("this.hasWriteAuthority() and hasPermission('#elementId', 'WRITE')") public ResponseEntity deleteAdapter( @PathVariable("id") String elementId, - @RequestParam(value = "deleteAssociatedPipelines", defaultValue = "false") - boolean deleteAssociatedPipelines - ) { + @RequestParam(value = "deleteAssociatedPipelines", defaultValue = "false") boolean deleteAssociatedPipelines) { List pipelinesUsingAdapter = getPipelinesUsingAdapter(elementId); IPipelineStorage pipelineStorageAPI = StorageDispatcher.INSTANCE.getNoSqlStore() - .getPipelineStorageAPI(); + .getPipelineStorageAPI(); if (pipelinesUsingAdapter.isEmpty()) { try { @@ -232,40 +224,40 @@ public ResponseEntity deleteAdapter( List namesOfPipelinesUsingAdapter = pipelinesUsingAdapter .stream() .map(pipelineId -> pipelineStorageAPI.getElementById( - pipelineId) - .getName()) + pipelineId) + .getName()) .collect(Collectors.toList()); return ResponseEntity.status(HttpStatus.SC_CONFLICT) - .body(String.join(", ", namesOfPipelinesUsingAdapter)); + .body(String.join(", ", namesOfPipelinesUsingAdapter)); } else { PermissionResourceManager permissionResourceManager = new PermissionResourceManager(); - // find out the names of pipelines that have an owner and the owner is not the current user + // find out the names of pipelines that have an owner and the owner is not the + // current user List namesOfPipelinesNotOwnedByUser = pipelinesUsingAdapter .stream() - .filter(pipelineId -> - !permissionResourceManager.findForObjectId( - pipelineId) - .stream() - .findFirst() - .map( - Permission::getOwnerSid) - // if a pipeline has no owner, pretend the owner - // is the user so the user can delete it - .orElse( - this.getAuthenticatedUserSid()) - .equals( - this.getAuthenticatedUserSid())) + .filter(pipelineId -> !permissionResourceManager.findForObjectId( + pipelineId) + .stream() + .findFirst() + .map( + Permission::getOwnerSid) + // if a pipeline has no owner, pretend the owner + // is the user so the user can delete it + .orElse( + this.getAuthenticatedUserSid()) + .equals( + this.getAuthenticatedUserSid())) .map(pipelineId -> pipelineStorageAPI.getElementById( - pipelineId) - .getName()) + pipelineId) + .getName()) .collect(Collectors.toList()); boolean isAdmin = SecurityContextHolder.getContext() - .getAuthentication() - .getAuthorities() - .stream() - .anyMatch(r -> r.getAuthority() - .equals( - DefaultRole.ROLE_ADMIN.name())); + .getAuthentication() + .getAuthorities() + .stream() + .anyMatch(r -> r.getAuthority() + .equals( + DefaultRole.ROLE_ADMIN.name())); // if the user is admin or owns all pipelines using this adapter, // the user can delete all associated pipelines and this adapter if (isAdmin || namesOfPipelinesNotOwnedByUser.isEmpty()) { @@ -276,18 +268,19 @@ public ResponseEntity deleteAdapter( } managementService.deleteAdapter(elementId); return ok(Notifications.success("Adapter with id: " + elementId - + " and all pipelines using the adapter are deleted.")); + + " and all pipelines using the adapter are deleted.")); } catch (Exception e) { LOG.error( "Error while deleting adapter with id " - + elementId + " and all pipelines using the adapter", e - ); + + elementId + " and all pipelines using the adapter", + e); return ok(Notifications.error(e.getMessage())); } } else { - // otherwise, hint the user the names of pipelines using the adapter but not owned by the user + // otherwise, hint the user the names of pipelines using the adapter but not + // owned by the user return ResponseEntity.status(HttpStatus.SC_CONFLICT) - .body(String.join(", ", namesOfPipelinesNotOwnedByUser)); + .body(String.join(", ", namesOfPipelinesNotOwnedByUser)); } } } @@ -299,6 +292,29 @@ public List getAllAdapters() { return managementService.getAllAdapterInstances(); } + @GetMapping(path = "/paginator", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("this.hasReadAuthority()") + @PostFilter("hasPermission(filterObject.correspondingDataStreamElementId, 'READ')") + public List getAllAdaptersPaginated( + @RequestParam(required = false) String startKey, + @RequestParam(required = false) String endKey, + @RequestParam(defaultValue = "10") int limit, + @RequestParam(defaultValue = "createdAt") String view, + @RequestParam(defaultValue = "false") boolean descending) { + return managementService.getPaginatedAdapterInstances(startKey,endKey, limit, view, descending); + } + + @GetMapping(path = "/paginator/category", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("this.hasReadAuthority()") + @PostFilter("hasPermission(filterObject.correspondingDataStreamElementId, 'READ')") + public List getCategoryAdaptersPaginated( + @RequestParam(required = false) String startKey, + @RequestParam(required = false) String category, + @RequestParam(defaultValue = "10") int limit, + @RequestParam(defaultValue = "false") boolean descending) { + return managementService.getItemsByCategoryPaginated(category, startKey, limit,descending); + } + private AdapterDescription getAdapterDescription(String elementId) throws AdapterException { return managementService.getAdapter(elementId); } @@ -309,8 +325,8 @@ private CompactAdapter toCompactAdapterDescription(AdapterDescription adapterDes private List getPipelinesUsingAdapter(String adapterId) { return StorageDispatcher.INSTANCE.getNoSqlStore() - .getPipelineStorageAPI() - .getPipelinesUsingAdapter(adapterId); + .getPipelineStorageAPI() + .getPipelinesUsingAdapter(adapterId); } } diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java index b547059f59..19991038f9 100644 --- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java +++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java @@ -29,6 +29,7 @@ import org.apache.streampipes.service.core.migrations.v0980.AddDataLakeMeasureViewMigration; import org.apache.streampipes.service.core.migrations.v0980.ModifyAssetLinkTypesMigration; import org.apache.streampipes.service.core.migrations.v0980.ModifyAssetLinksMigration; +import org.apache.streampipes.service.core.migrations.v099.AddAdapterPaginatorViewsToDB; import org.apache.streampipes.service.core.migrations.v970.AddDataLakePipelineTemplateMigration; import org.apache.streampipes.service.core.migrations.v970.AddLinkSettingsMigration; import org.apache.streampipes.service.core.migrations.v970.AddRolesToUserDbMigration; @@ -36,6 +37,7 @@ import org.apache.streampipes.service.core.migrations.v970.ModifyAssetLinkTypeMigration; import org.apache.streampipes.service.core.migrations.v970.RemoveNodesFromOpcUaAdaptersMigration; + import java.util.Arrays; import java.util.List; @@ -43,6 +45,7 @@ public class AvailableMigrations { public List getAvailableMigrations() { return Arrays.asList( + new AddAdapterPaginatorViewsToDB(), new CreateAssetLinkTypeMigration(), new CreateDefaultAssetMigration(), new CreateFileAssetTypeMigration(), diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/AdapterMigration.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/AdapterMigration.java index 0e488db656..01247b68cb 100644 --- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/AdapterMigration.java +++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/AdapterMigration.java @@ -67,9 +67,8 @@ public boolean shouldExecute() { findDocsToMigrate(adapterInstanceClient, adapterInstanceUri, adaptersToMigrate); findDocsToMigrate(adapterDescriptionClient, adapterDescriptionUri, adapterDescriptionsToRemove); - return !adaptersToMigrate.isEmpty() || !adapterDescriptionsToRemove.isEmpty(); + return !adaptersToMigrate.isEmpty() || !adapterDescriptionsToRemove.isEmpty(); } - private void findDocsToMigrate(CouchDbClient adapterClient, String uri, List collector) { @@ -78,6 +77,11 @@ private void findDocsToMigrate(CouchDbClient adapterClient, var rows = existingAdapters.get(ROWS); rows.getAsJsonArray().forEach(row -> { var doc = row.getAsJsonObject().get("doc").getAsJsonObject(); + var id = doc.get("_id").getAsString(); + // Skip design documents + if (id.startsWith("_design/")) { + return; + } var docType = doc.get("type").getAsString(); if (AdapterModels.shouldMigrate(docType)) { collector.add(doc); @@ -135,7 +139,7 @@ public String getDescription() { return "Migrate all adapters to new data model"; } - private String getAllDocsUri(CouchDbClient client) { +private String getAllDocsUri(CouchDbClient client) { return client.getDBUri().toString() + "_all_docs" + "?include_docs=true"; } diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v099/AddAdapterPaginatorViewsToDB.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v099/AddAdapterPaginatorViewsToDB.java new file mode 100644 index 0000000000..b3568512e0 --- /dev/null +++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v099/AddAdapterPaginatorViewsToDB.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.service.core.migrations.v099; + +import org.apache.streampipes.service.core.migrations.Migration; +import org.apache.streampipes.storage.couchdb.utils.Utils; + +import org.lightcouch.CouchDbClient; +import org.lightcouch.CouchDbException; +import org.lightcouch.DesignDocument; +import org.lightcouch.DesignDocument.MapReduce; +import org.lightcouch.Document; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.streampipes.manager.setup.design.DesignDocumentUtils.prepareDocument; + +public class AddAdapterPaginatorViewsToDB implements Migration { + + @Override + public boolean shouldExecute() { + + CouchDbClient client = Utils.getCouchDbAdapterInstanceClient(); + String designDocId = "_design/paginator"; + + // Check if the design document exists + if (doesDesignDocumentExist(client, designDocId)) { + return false; + } else { + return true; + } + } + + public static boolean doesDesignDocumentExist(CouchDbClient client, String designDocId) { + try { + Document doc = client.find(Document.class, designDocId); + return doc != null; + } catch (CouchDbException e) { + return false; + } + } + + @Override + public void executeMigration() throws IOException { + // TODO CALL ORIGINAL CODE + + DesignDocument paginatorDocument = prepareDocument("_design/paginator"); + + Map paginatorViews = new HashMap<>(); + + // View to paginate documents by creation time + MapReduce paginationFunctionByCreate = new MapReduce(); + paginationFunctionByCreate.setMap( + "function (doc) {\n" + + " if (doc.properties && doc.properties.createdAt) {\n" + + " emit(doc.properties.createdAt, doc);\n" + + " }\n" + + "}"); + + // View to paginate documents by name + MapReduce paginationFunctionByName = new MapReduce(); + paginationFunctionByName.setMap( + "function (doc) {\n" + + " if (doc.properties && doc.properties.name && typeof doc.properties.name === 'string') {\n" + + " emit(doc.properties.name, doc);\n" + + " }\n" + + "}"); + + // View to paginate documents by running + MapReduce paginationFunctionByRunning = new MapReduce(); + paginationFunctionByRunning.setMap( + "function (doc) {\n" + + " emit([doc.properties.running, doc._id], doc);\n" + + "}"); + + MapReduce paginationFunctionByCategory = new MapReduce(); + paginationFunctionByCategory.setMap( + "function (doc) {\n" + + " if (doc.properties && Array.isArray(doc.properties.category)) {\n" + + " doc.properties.category.forEach(function (cat) {\n" + + " emit([cat, doc._id], doc);\n" + + " });\n" + + " }\n" + + "}"); + + // View to list all non-design documents + MapReduce nonDesignDocsView = new MapReduce(); + nonDesignDocsView.setMap( + "function (doc) {\n" + + " if (!doc._id.startsWith(\"_design/\")) {\n" + + " emit(doc._id, doc);\n" + + " }\n" + + "}"); + + // Add views to the document + paginatorViews.put("by_createdAt", paginationFunctionByCreate); + paginatorViews.put("by_name", paginationFunctionByName); + paginatorViews.put("by_running", paginationFunctionByRunning); + paginatorViews.put("by_category", paginationFunctionByCategory); + paginatorViews.put("non_design_docs", nonDesignDocsView); + + paginatorDocument.setViews(paginatorViews); + + // Push the design document to CouchDB + Utils.getCouchDbAdapterInstanceClient() + .design() + .synchronizeWithDb(paginatorDocument); + } + + @Override + public String getDescription() { + return "Check for Paginator view in AdapterInstances, if it does not exist, add the Paginatorview."; + + } + +} diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAdapterStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAdapterStorage.java index 36ecb20ac8..877be16082 100644 --- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAdapterStorage.java +++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAdapterStorage.java @@ -27,4 +27,10 @@ public interface IAdapterStorage extends CRUDStorage { AdapterDescription getFirstAdapterByAppId(String appId); List getAdaptersByAppId(String appId); + + List getAdapterPaginator(String startItem, String endItem, int limit, String view, + boolean descending); + + List getItemsByCategoryPaginated(String category, String startDocId, + int limit, boolean descending); } diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterDescriptionStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterDescriptionStorageImpl.java index cd78b5af69..5e38cebe7e 100644 --- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterDescriptionStorageImpl.java +++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterDescriptionStorageImpl.java @@ -59,4 +59,18 @@ public AdapterDescription updateElement(AdapterDescription element) { private String getCurrentRev(String elementId) { return find(elementId).get().getRev(); } + + @Override + public List getAdapterPaginator(String startItem, String endItem, int limit, String view, + boolean descending) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getAdapterPaginator'"); + } + + @Override + public List getItemsByCategoryPaginated(String category, String startDocId, int limit, + boolean descending) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getItemsByCategoryPaginated'"); + } } diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java index 9b9f68eb3f..33def3aeb8 100644 --- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java +++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java @@ -18,33 +18,202 @@ package org.apache.streampipes.storage.couchdb.impl; +import org.apache.streampipes.commons.environment.Environments; import org.apache.streampipes.model.connect.adapter.AdapterDescription; import org.apache.streampipes.storage.api.IAdapterStorage; import org.apache.streampipes.storage.couchdb.utils.Utils; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.lightcouch.CouchDbClient; +import org.lightcouch.View; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.NoSuchElementException; public class AdapterInstanceStorageImpl extends DefaultCrudStorage implements IAdapterStorage { - public AdapterInstanceStorageImpl() { - super(Utils::getCouchDbAdapterInstanceClient, AdapterDescription.class); - } - - @Override - public AdapterDescription getFirstAdapterByAppId(String appId) { - return this.findAll() - .stream() - .filter(p -> p.getAppId().equals(appId)) - .findFirst() - .orElseThrow(NoSuchElementException::new); - } - - @Override - public List getAdaptersByAppId(String appId) { - return this.findAll() - .stream() - .filter(p -> p.getAppId().equals(appId)) - .toList(); - } + public AdapterInstanceStorageImpl() { + super(Utils::getCouchDbAdapterInstanceClient, AdapterDescription.class); + } + + + @Override + public AdapterDescription getFirstAdapterByAppId(String appId) { + return this.findAll() + .stream() + .filter(p -> p.getAppId().equals(appId)) + .findFirst() + .orElseThrow(NoSuchElementException::new); + } + + @Override + public List getAdaptersByAppId(String appId) { + return this.findAll() + .stream() + .filter(p -> p.getAppId().equals(appId)) + .toList(); + } + + @Override + public List findAll() { + List adapters = findAll("paginator/non_design_docs"); + return adapters.stream() + .toList(); + } + + @Override + public List getAdapterPaginator(String startItem, String endItem, int limit, String view, + boolean descending) { + String uri = "paginator/by_" + view; + var dbClient = couchDbClientSupplier.get(); + var viewBuilder = dbClient.view(uri) + .includeDocs(true) + .limit(limit) + .descending(descending); + + if (startItem != null && !startItem.isEmpty()) { + viewBuilder = applyStartKey(viewBuilder, view, startItem); + } + + if (endItem != null && !endItem.isEmpty()) { + viewBuilder = viewBuilder.endKey(endItem); + } + + return viewBuilder.query(AdapterDescription.class); + } + + private View applyStartKey(View viewBuilder, String view, String startItem) { + try { + if ("createdAt".equals(view)) { + long startItemLong = Long.parseLong(startItem); + return viewBuilder.startKey(startItemLong); + } + + if (startItem.startsWith("[") && startItem.endsWith("]")) { + ObjectMapper mapper = new ObjectMapper(); + Object[] startKeyArray = mapper.readValue(startItem, Object[].class); + return viewBuilder.startKey(startKeyArray); + } + + return viewBuilder.startKey(startItem); + + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid startItem format for 'createdAt'", e); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid startItem format for compound key", e); + } + } + + @Override + public List getItemsByCategoryPaginated(String category, String startDocId, + int limit, boolean descending) { + List resultList = new ArrayList<>(); + + try { + String url = buildCategoryPaginatedUrl(category, startDocId, limit); + HttpURLConnection conn = createAuthenticatedConnection(url); + + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new RuntimeException("Failed with HTTP code: " + conn.getResponseCode()); + } + + try (Reader reader = new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)) { + resultList = parseAdapterDescriptions(reader, couchDbClientSupplier.get().getGson()); + } + + } catch (IOException e) { + System.err.println("I/O error during CouchDB request: " + e.getMessage()); + e.printStackTrace(); + } catch (RuntimeException e) { + System.err.println("Runtime exception: " + e.getMessage()); + e.printStackTrace(); + } + + return resultList; + } + + private String buildCategoryPaginatedUrl(String category, String startDocId, int limit) + throws UnsupportedEncodingException { + String dbName = "adapterinstance"; + String designDoc = "paginator"; + String viewName = "by_category"; + + String startKey = startDocId != null && !startDocId.isEmpty() + ? "[\"" + category + "\", \"" + startDocId + "\"]" + : "[\"" + category + "\"]"; + + String endKey = "[\"" + category + "\", \"\ufff0\"]"; + + CouchDbClient dbClient = couchDbClientSupplier.get(); + URI baseUri = dbClient.getBaseUri(); + + return String.format( + "http://%s:%d/%s/_design/%s/_view/%s?startkey=%s&endkey=%s&limit=%d&include_docs=true", + baseUri.getHost(), + baseUri.getPort(), + dbName, + designDoc, + viewName, + URLEncoder.encode(startKey, StandardCharsets.UTF_8), + URLEncoder.encode(endKey, StandardCharsets.UTF_8), + limit); + } + + private HttpURLConnection createAuthenticatedConnection(String urlStr) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + String username = Environments.getEnvironment().getCouchDbUsername().getValueOrDefault(); + String password = Environments.getEnvironment().getCouchDbPassword().getValueOrDefault(); + String authHeader = Base64.getEncoder() + .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + + conn.setRequestMethod("GET"); + conn.setRequestProperty("Authorization", "Basic " + authHeader); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + return conn; + } + + private List parseAdapterDescriptions(Reader reader, Gson gson) { + List result = new ArrayList<>(); + JsonObject root = JsonParser.parseReader(reader).getAsJsonObject(); + + if (root.has("rows")) { + JsonArray rows = root.getAsJsonArray("rows"); + + for (JsonElement rowElem : rows) { + JsonObject rowObj = rowElem.getAsJsonObject(); + JsonElement docElem = rowObj.get("doc"); + + if (docElem != null && !docElem.isJsonNull()) { + docElem.getAsJsonObject().addProperty("@class", + "org.apache.streampipes.model.connect.adapter.AdapterDescription"); + + AdapterDescription adapter = gson.fromJson(docElem, AdapterDescription.class); + result.add(adapter); + } + } + } + + return result; + } + } diff --git a/ui/cypress/support/utils/connect/CompactAdapterUtils.ts b/ui/cypress/support/utils/connect/CompactAdapterUtils.ts index 43757f580c..98e9de0910 100644 --- a/ui/cypress/support/utils/connect/CompactAdapterUtils.ts +++ b/ui/cypress/support/utils/connect/CompactAdapterUtils.ts @@ -70,12 +70,27 @@ export class CompactAdapterUtils { /** * Creates a CompactAdapterBuilder instance configured for a machine data simulator. */ - public static getMachineDataSimulator(): CompactAdapterBuilder { + public static getMachineDataSimulator( + name: string = 'Test', + ): CompactAdapterBuilder { return CompactAdapterBuilder.create( 'org.apache.streampipes.connect.iiot.adapters.simulator.machine', ) - .setName('Test') + .setName(name) .addConfiguration('wait-time-ms', '1000') .addConfiguration('selected-simulator-option', 'flowrate'); } + + public static getAndSaveNMachineDataSimulator( + name: string = 'Test', + n: number = 10, + ): void { + for (let i = 0; i < n; i++) { + const compactAdapter = CompactAdapterUtils.getMachineDataSimulator( + 'test_' + i, + ).build(); + + CompactAdapterUtils.storeCompactAdapter(compactAdapter); + } + } } diff --git a/ui/cypress/support/utils/connect/ConnectBtns.ts b/ui/cypress/support/utils/connect/ConnectBtns.ts index 2a2223284c..1acba037ff 100644 --- a/ui/cypress/support/utils/connect/ConnectBtns.ts +++ b/ui/cypress/support/utils/connect/ConnectBtns.ts @@ -150,5 +150,15 @@ export class ConnectBtns { return 'undefined-org.apache.streampipes.extensions.management.connect.adapter.parser.xml-2-tag-0'; } + // ======================================================================== + // ===================== Filter and Pagination Buttons ========================== + public static sortingHeader(name: string) { + return cy + .get('th[mat-sort-header=""] .mat-sort-header-content', { + timeout: 10000, + }) + .contains(name); + } + // ======================================================================== } diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts b/ui/cypress/support/utils/connect/ConnectUtils.ts index 4841a01e19..edbb73ad33 100644 --- a/ui/cypress/support/utils/connect/ConnectUtils.ts +++ b/ui/cypress/support/utils/connect/ConnectUtils.ts @@ -105,27 +105,25 @@ export class ConnectUtils { .setName(name) .addInput('input', 'wait-time-ms', '1000'); + console.log('Step 1 Done'); + if (persist) { builder.setTimestampProperty('timestamp').setStoreInDataLake(); } const configuration = builder.build(); - ConnectUtils.goToConnect(); - ConnectUtils.goToNewAdapterPage(); - ConnectUtils.selectAdapter(configuration.adapterType); - ConnectUtils.configureAdapter(configuration); - ConnectEventSchemaUtils.finishEventSchemaConfiguration(); - ConnectUtils.startAdapter(configuration); } public static goToConnect() { cy.visit('#/connect'); + cy.dataCy('all-adapters-table', { timeout: 15000 }) // Increase timeout if needed + .should('exist'); // Wait for the element to exist in the DOM } public static goToNewAdapterPage() { @@ -444,6 +442,60 @@ export class ConnectUtils { ConnectBtns.stopAdapter().should('have.length', 1); } + public static validateAdapterPagination() { + cy.get('[data-cy="adapter-name"]') + .first() + .invoke('text') + .then(firstPageFirstItem => { + cy.get('[data-cy="table-paginator"]') + .find('button[aria-label="Next"]') + .should('not.be.disabled') + .click(); + + cy.wait(1000); + + cy.get('[data-cy="adapter-name"]') + .first() + .should('exist') + .invoke('text') + .should(secondPageFirstItem => { + expect(secondPageFirstItem.trim()).to.not.equal( + firstPageFirstItem.trim(), + ); + }); + }); + } + + public static filterAdapterPagination(name: string) { + cy.dataCy('adapter-name', { timeout: 10000 }) + .first() + .invoke('text') + .then(firstItemNameBefore => { + cy.log(firstItemNameBefore); + + cy.get('th[mat-sort-header=""] .mat-sort-header-content', { + timeout: 10000, + }) + .contains(name) + .click(); + + cy.dataCy('adapter-name', { timeout: 10000 }) + .first() + .should('not.have.text', firstItemNameBefore.trim()) + .invoke('text') + .then(firstItemNameAfter => { + cy.log(firstItemNameAfter); + expect(firstItemNameBefore.trim()).to.not.equal( + firstItemNameAfter.trim(), + ); + }); + }); + } + public static filterAdapterForCategory(category: string) { + cy.dataCy('category-select', { timeout: 10000 }).click(); + + cy.get('mat-option').contains(category).click(); + } public static validateAdapterIsStopped() { ConnectUtils.goToConnect(); ConnectBtns.startAdapter().should('have.length', 1); @@ -461,4 +513,10 @@ export class ConnectUtils { ConnectBtns.deleteAdapter().should('have.length', amount); } } + + public static waitingForExistingAdapters() { + return cy + .dataCy('no-table-entries', { timeout: 10000 }) + .should('not.exist'); + } } diff --git a/ui/cypress/tests/connect/adapterPaging.spec.ts b/ui/cypress/tests/connect/adapterPaging.spec.ts new file mode 100644 index 0000000000..b8e6c71f80 --- /dev/null +++ b/ui/cypress/tests/connect/adapterPaging.spec.ts @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { ConnectUtils } from '../../support/utils/connect/ConnectUtils'; +import { ConnectBtns } from '../../support/utils/connect/ConnectBtns'; +import { CompactAdapterUtils } from '../../support/utils/connect/CompactAdapterUtils'; + +describe('Adapter Paging Test', () => { + beforeEach('Setup Test', () => { + cy.initStreamPipesTest(); + }); + + it('Basic Paging check', () => { + CompactAdapterUtils.getAndSaveNMachineDataSimulator(); + ConnectUtils.goToConnect(); + + cy.dataCy('table-paginator', { timeout: 10000 }).within(() => { + cy.get('mat-select').click(); + }); + cy.get('mat-option').contains('5').click(); + + cy.dataCy('adapter-name', { timeout: 10000 }).should('have.length', 5); + ConnectUtils.validateAdapterPagination(); + }); + + it('Basic Filtering Name', () => { + CompactAdapterUtils.getAndSaveNMachineDataSimulator(); + ConnectUtils.goToConnect(); + + ConnectUtils.waitingForExistingAdapters(); + + ConnectBtns.sortingHeader('Name').click(); + + ConnectUtils.filterAdapterPagination('Name'); + }); + + it('Basic Filtering CreatedAT', () => { + CompactAdapterUtils.getAndSaveNMachineDataSimulator(); + ConnectUtils.goToConnect(); + ConnectUtils.waitingForExistingAdapters(); + ConnectUtils.filterAdapterPagination('Created'); + }); + + it('Basic Filtering Running', () => { + CompactAdapterUtils.getAndSaveNMachineDataSimulator(); + ConnectUtils.goToConnect(); + + ConnectUtils.waitingForExistingAdapters(); + + // Add one Adapter running + const compactAdapter = CompactAdapterUtils.getMachineDataSimulator() + .setStart() + .build(); + 0; + CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => { + cy.wait(1000); + + ConnectBtns.sortingHeader('Status').should('be.visible').click(); + + ConnectUtils.filterAdapterPagination('Status'); + }); + }); + it('Basic Filtering Category', () => { + CompactAdapterUtils.getAndSaveNMachineDataSimulator(); + ConnectUtils.goToConnect(); + ConnectUtils.waitingForExistingAdapters(); + + ConnectUtils.filterAdapterForCategory('Finance'); + + cy.dataCy('no-table-entries').should('be.visible'); + cy.dataCy('no-table-entries') + .should('be.visible') + .contains('No entries available.'); + ConnectUtils.filterAdapterForCategory('Debugging'); + cy.dataCy('no-table-entries').should('not.exist'); + }); +}); diff --git a/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts b/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts index 57d2466c15..ddb7ab9c36 100644 --- a/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts +++ b/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts @@ -27,12 +27,13 @@ describe('Add Compact Adapters', () => { }); it('Add an adapter via the compact API. Do not start', () => { + console.log('Start ADD Adapter'); const compactAdapter = CompactAdapterUtils.getMachineDataSimulator().build(); + console.log('Adapter built'); CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => { ConnectUtils.validateAdapterIsStopped(); - PipelineUtils.checkAmountOfPipelinesPipeline(0); }); }); diff --git a/ui/package-lock.json b/ui/package-lock.json index ce201a098d..3455219d31 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -23,6 +23,7 @@ "@bluehalo/ngx-leaflet": "^19.0.0", "@ctrl/ngx-codemirror": "7.0.0", "@fortawesome/fontawesome-free": "6.5.1", + "@gradle-tech/develocity-agent": "^2.0.2", "@jsplumb/browser-ui": "^6.2.10", "@ngbracket/ngx-layout": "^19.0.0", "@ngx-loading-bar/core": "6.0.2", @@ -181,6 +182,40 @@ } } }, + "node_modules/@angular-devkit/architect/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-devkit/architect/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular": { "version": "19.2.13", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.13.tgz", @@ -376,6 +411,24 @@ "semver": "bin/semver.js" } }, + "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -412,6 +465,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-webpack": { "version": "0.1902.13", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.13.tgz", @@ -479,6 +548,40 @@ } } }, + "node_modules/@angular-devkit/schematics/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-eslint/builder": { "version": "19.4.0", "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.4.0.tgz", @@ -522,6 +625,40 @@ } } }, + "node_modules/@angular-eslint/builder/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-eslint/builder/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-eslint/bundled-angular-compiler": { "version": "19.4.0", "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.4.0.tgz", @@ -608,6 +745,40 @@ } } }, + "node_modules/@angular-eslint/schematics/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-eslint/template-parser": { "version": "19.4.0", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.4.0.tgz", @@ -937,6 +1108,40 @@ } } }, + "node_modules/@angular/cli/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/cli/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/common": { "version": "19.2.13", "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.13.tgz", @@ -3683,6 +3888,31 @@ "node": ">=6" } }, + "node_modules/@gradle-tech/develocity-agent": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@gradle-tech/develocity-agent/-/develocity-agent-2.0.2.tgz", + "integrity": "sha512-79k2YkaLDzuqLf5YPYR2RldYtHwi1vWXNd4+X1gpMwqZkKTFO16h4GQfaz7mlfYx0Yssrj9c2WTPwsK5pyocig==", + "license": "SEE LICENSE AT https://gradle.com/help/legal-terms-of-use", + "engines": { + "node": ">=18.20.5" + }, + "peerDependencies": { + "@jest/jest-message-util": ">=28.1", + "@jest/reporters": ">=28.1", + "mocha": ">=7.0.1" + }, + "peerDependenciesMeta": { + "@jest/jest-message-util": { + "optional": true + }, + "@jest/reporters": { + "optional": true + }, + "mocha": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -4108,7 +4338,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -4126,7 +4356,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -4139,7 +4369,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -4152,14 +4382,14 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -4177,7 +4407,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -4193,7 +4423,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -5826,6 +6056,21 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", @@ -5982,25 +6227,59 @@ } } }, - "node_modules/@sigstore/bundle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", - "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "node_modules/@schematics/angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" + "readdirp": "^4.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@sigstore/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", - "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@schematics/angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sigstore/bundle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -7544,7 +7823,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -7554,7 +7833,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -7618,7 +7897,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, + "devOptional": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -8092,7 +8371,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -8115,7 +8394,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, + "devOptional": true, "license": "ISC", "peer": true }, @@ -8392,7 +8671,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -8434,7 +8713,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -8771,7 +9050,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -8786,7 +9065,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -8848,7 +9127,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -8861,7 +9140,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/colorette": { @@ -9165,7 +9444,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -10168,7 +10447,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -10406,7 +10685,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "peer": true, "engines": { @@ -10527,7 +10806,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/ecc-jsbn": { @@ -10597,7 +10876,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/emoji-toolkit": { @@ -10933,7 +11212,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -10950,7 +11229,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -11768,7 +12047,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -11856,7 +12135,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -11988,7 +12267,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -12099,7 +12378,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -12257,7 +12536,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12322,7 +12601,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "bin": { @@ -13117,7 +13396,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -13170,7 +13449,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/isobject": { @@ -13381,7 +13660,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -13467,7 +13746,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -14775,7 +15054,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -14837,7 +15116,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -15412,7 +15691,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -15438,7 +15717,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -15623,7 +15902,7 @@ "version": "11.7.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.2.tgz", "integrity": "sha512-lkqVJPmqqG/w5jmmFtiRvtA2jkDyNVUcefFJKb2uyX4dekk8Okgqop3cgbFiaIvj8uCRJVTP5x9dfxGyXm2jvQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -15660,7 +15939,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -15677,7 +15956,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -15692,7 +15971,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -16695,7 +16974,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -16711,7 +16990,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -16781,7 +17060,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { @@ -16950,7 +17229,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -16969,7 +17248,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -16986,7 +17265,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -17003,7 +17282,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -17068,7 +17347,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/picomatch": { @@ -17646,7 +17925,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -17824,7 +18103,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -18392,7 +18671,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -18640,7 +18919,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -18653,7 +18932,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -18769,7 +19048,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=14" @@ -19259,7 +19538,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -19275,7 +19554,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -19290,7 +19569,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -19300,7 +19579,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -19310,7 +19589,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -19324,7 +19603,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -19357,7 +19636,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -19377,7 +19656,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -20236,6 +20515,291 @@ } } }, + "node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, "node_modules/vite/node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -20713,7 +21277,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -20767,7 +21331,7 @@ "version": "9.3.4", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "peer": true }, @@ -20791,7 +21355,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -20837,7 +21401,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=10" @@ -20867,7 +21431,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -20886,7 +21450,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=12" @@ -20896,7 +21460,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -20913,7 +21477,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "peer": true, "bin": { @@ -20924,7 +21488,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -20946,7 +21510,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/ui/package.json b/ui/package.json index 597d61859f..be404c858e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -44,6 +44,7 @@ "@bluehalo/ngx-leaflet": "^19.0.0", "@ctrl/ngx-codemirror": "7.0.0", "@fortawesome/fontawesome-free": "6.5.1", + "@gradle-tech/develocity-agent": "^2.0.2", "@jsplumb/browser-ui": "^6.2.10", "@ngbracket/ngx-layout": "^19.0.0", "@ngx-loading-bar/core": "6.0.2", diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts index fcd293a0ff..e08aca9e6f 100644 --- a/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts +++ b/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts @@ -17,7 +17,7 @@ */ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; @@ -28,6 +28,8 @@ import { Message, PipelineUpdateInfo, } from '../model/gen/streampipes-model'; +import { property } from 'cypress/types/lodash'; +import { descending } from 'd3-array'; @Injectable({ providedIn: 'root' }) export class AdapterService { @@ -48,6 +50,37 @@ export class AdapterService { return this.requestAdapterDescriptions('/master/adapters'); } + getAdaptersPaginated( + startId: string | number | null, + endId: string | number | null, + limit: number, + property: string = 'createdAt', + descending: boolean = false, + ): Observable { + return this.requestAdapterDescriptionsPaginated( + '/master/adapters/paginator', + startId, + endId, + limit, + property, + descending, + ); + } + getAdaptersCategorywisePaginated( + category: string, + startId: string | number | null, + limit: number, + descending: boolean = false, + ): Observable { + return this.requestAdapterCategoryWiseDescriptionsPaginated( + '/master/adapters/paginator/category', + category, + startId, + limit, + descending, + ); + } + getAdapter(id: string): Observable { return this.http .get(this.connectPath + `/master/adapters/${id}`) @@ -65,6 +98,61 @@ export class AdapterService { ); } + requestAdapterCategoryWiseDescriptionsPaginated( + path: string, + category: string, + startId: string | number | null, + limit: number, + descending: boolean, + ): Observable { + let params = new HttpParams().set('limit', limit.toString()); + + params = params.set('category', category); + + if (startId) { + params = params.set('startKey', startId); + } + + params = params.set('descending', descending); + const url = `${this.connectPath}${path}`; + + return this.http.get(url, { params }).pipe( + map(response => { + return (response as any[]).map(p => + AdapterDescription.fromData(p), + ); + }), + ); + } + + requestAdapterDescriptionsPaginated( + path: string, + startId: string | number | null, + endId: string | number | null, + limit: number, + property: string, + descending: boolean, + ): Observable { + let params = new HttpParams().set('limit', limit.toString()); + + if (startId) { + params = params.set('startKey', startId); + } + if (endId) { + params = params.set('endKey', endId); + } + params = params.set('view', property); + params = params.set('descending', descending); + const url = `${this.connectPath}${path}`; + return this.http.get(url, { params }).pipe( + map(response => { + return (response as any[]).map(p => + AdapterDescription.fromData(p), + ); + }), + ); + } + requestAdapterDescriptions(path: string): Observable { return this.http.get(this.connectPath + path).pipe( map(response => { diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/custom-paginator-intl.ts b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/custom-paginator-intl.ts new file mode 100644 index 0000000000..16d5e48725 --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/custom-paginator-intl.ts @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import { MatPaginatorIntl } from '@angular/material/paginator'; + +export function getCustomPaginatorIntl(): MatPaginatorIntl { + const paginatorIntl = new MatPaginatorIntl(); + + paginatorIntl.itemsPerPageLabel = 'Items per page:'; + paginatorIntl.nextPageLabel = 'Next'; + paginatorIntl.previousPageLabel = 'Previous'; + + paginatorIntl.getRangeLabel = ( + page: number, + pageSize: number, + length: number, + ) => { + const start = page * pageSize + 1; + const end = Math.min((page + 1) * pageSize); + return `Showing documents ${start} - ${end}`; + }; + + return paginatorIntl; +} diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.html new file mode 100644 index 0000000000..a7ef8ec932 --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.html @@ -0,0 +1,47 @@ + + +
+ + + + + + + + + +
+ {{ 'No entries available.' | translate }} +
+
+ + +
+
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.scss b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.scss new file mode 100644 index 0000000000..ef1fb3c79c --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.scss @@ -0,0 +1,34 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +.paginator-container { + border-top: 1px solid rgba(0, 0, 0, 0.12); +} + +.mat-mdc-row:hover { + background-color: var(--color-bg-1); +} + +.mat-mdc-no-data-row { + height: var(--mat-table-row-item-container-height, 52px); + text-align: center; +} + +::ng-deep .mat-paginator-range-label { + display: none !important; +} diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.ts b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.ts new file mode 100644 index 0000000000..46e722eef6 --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.component.ts @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import { + Component, + Input, + ViewChild, + ContentChildren, + ContentChild, + QueryList, + SimpleChanges, + OnChanges, + AfterContentInit, +} from '@angular/core'; +import { + MatColumnDef, + MatHeaderRowDef, + MatNoDataRow, + MatRowDef, + MatTable, + MatTableDataSource, +} from '@angular/material/table'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { AdapterService } from '@streampipes/platform-services'; +import { Observable } from 'rxjs'; +import { MatSort, Sort } from '@angular/material/sort'; +import { BehaviorSubject } from 'rxjs'; +@Component({ + selector: 'sp-table-pagination', + templateUrl: './sp-table-pagination.component.html', + styleUrls: ['./sp-table-pagination.component.scss'], + standalone: false, +}) +export class SpTablePaginationComponent + implements OnChanges, AfterContentInit +{ + @ContentChildren(MatHeaderRowDef) headerRowDefs: QueryList; + @ContentChildren(MatRowDef) rowDefs: QueryList>; + @ContentChildren(MatColumnDef) columnDefs: QueryList; + @ContentChild(MatNoDataRow) noDataRow: MatNoDataRow; + + @ViewChild(MatTable, { static: true }) table: MatTable; + + @Input() columns: string[] = []; + + @ViewChild('paginator') paginator: MatPaginator; + + @Input() sort: MatSort; + //Necessary if other refreshs than based on sort are crucial + @Input() refresh: BehaviorSubject; + @Input() filter: BehaviorSubject; + + @Input() fetchDataFn: ( + startKey?: any, + pageSize?: number, + ) => Observable; + // This is necessary in case the element names in the HTML and the keys used for sorting follow different naming conventions or are composite keys. (E.g., created in HTML and the database key is createdAt) + // Provide the information as sortmap + @Input() getViewFn: (sort: string) => string; + + dataSource = new MatTableDataSource([]); + pageSize = 20; + totalItems = 1000000; + last_key = undefined; + currentPage = 0; + propertyName = 'createdAt'; + isNextDisabled: boolean = false; + filtering = ''; + + startKeyMap: Map = new Map(); + + private sortInitialized = false; + private filterInitialized = false; + private refreshInitialized = false; + + ngOnChanges(changes: SimpleChanges): void { + if (changes['sort'] && this.sort && !this.sortInitialized) { + this.initSortSubscription(); + } + + if (changes['refresh'] && !this.refreshInitialized) { + this.initRefreshSubscription(); + } + + if (changes['filter'] && !this.filterInitialized) { + this.initFilterSubscription(); + } + } + + private initSortSubscription(): void { + this.sort.sortChange.subscribe(sortChange => + this.updateSort(sortChange), + ); + this.sortInitialized = true; + } + + private initRefreshSubscription(): void { + this.refresh?.subscribe(() => { + this.loadData(this.currentPage); + }); + this.refreshInitialized = true; + } + + private initFilterSubscription(): void { + this.filter?.subscribe(() => { + this.filtering = this.filter.value; + this.propertyName = this.getViewFn(this.filter.value['view']); + this.resetPagination(); + this.loadData(0); + }); + this.filterInitialized = true; + } + + private updateSort(sortChange: Sort): void { + this.propertyName = this.getViewFn(sortChange.active); + this.clearFilters(); + this.resetPagination(); + this.loadData(0); + } + + private clearFilters(): void { + this.filter.value['category'] = ''; + this.filter.value['text'] = ''; + } + + resetPagination() { + this.startKeyMap.clear(); + this.currentPage = 0; + this.last_key = null; + this.paginator?.firstPage(); + this.loadData(this.currentPage); + this.isNextDisabled = false; + this.totalItems = 1000000; + } + onPageChange(event: PageEvent) { + this.pageSize = event.pageSize; + this.currentPage = event.pageIndex; + this.loadData(this.currentPage); + } + + ngAfterContentInit() { + this.columnDefs.forEach(columnDef => + this.table.addColumnDef(columnDef), + ); + this.rowDefs.forEach(rowDef => this.table.addRowDef(rowDef)); + this.headerRowDefs.forEach(headerRowDef => + this.table.addHeaderRowDef(headerRowDef), + ); + this.table.setNoDataRow(this.noDataRow); + } + loadData(pageIndex: number) { + const start = this.startKeyMap.get(pageIndex) || null; + let startkey = start; + + this.fetchDataFn(startkey, this.pageSize + 1).subscribe({ + next: (data: T[]) => { + if (data.length < this.pageSize) { + this.dataSource.data = data; + this.totalItems = data.length + pageIndex * this.pageSize; + } else { + const trimmedData = data.slice(0, this.pageSize); + this.dataSource.data = trimmedData; + this.totalItems = data.length + pageIndex * this.pageSize; + } + + if (data.length > this.pageSize) { + let nextStartKey; + + if (Array.isArray(this.propertyName)) { + nextStartKey = this.propertyName.map( + prop => data[this.pageSize][prop], + ); + } else { + nextStartKey = data[this.pageSize][this.propertyName]; + } + const nextStartKeyString = Array.isArray(nextStartKey) + ? JSON.stringify(nextStartKey) + : nextStartKey; + this.startKeyMap.set(pageIndex + 1, nextStartKeyString); + this.dataSource.data = data.slice(0, this.pageSize); + } + }, + error: err => { + console.error('Failed to fetch paginated data', err); + }, + }); + } +} diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.module.ts b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.module.ts new file mode 100644 index 0000000000..7a20e2aa6d --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table-pagination/sp-table-pagination.module.ts @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SpTablePaginationComponent } from './sp-table-pagination.component'; +import { + MatPaginatorIntl, + MatPaginatorModule, +} from '@angular/material/paginator'; +import { getCustomPaginatorIntl } from './custom-paginator-intl'; + +// Angular Material modules +import { MatTableModule } from '@angular/material/table'; +import { MatSortModule } from '@angular/material/sort'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatIconModule } from '@angular/material/icon'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSelectModule } from '@angular/material/select'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTreeModule } from '@angular/material/tree'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + +import { FlexLayoutModule } from '@ngbracket/ngx-layout'; +import { FormsModule } from '@angular/forms'; +import { PortalModule } from '@angular/cdk/portal'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { DragDropModule } from '@angular/cdk/drag-drop'; + +import { TranslateModule } from '@ngx-translate/core'; +import { MarkdownModule } from 'ngx-markdown'; + +@NgModule({ + declarations: [SpTablePaginationComponent], + imports: [ + CommonModule, + FormsModule, + FlexLayoutModule, + + // Angular Material modules + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatDividerModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatCheckboxModule, + MatButtonModule, + MatTabsModule, + MatMenuModule, + MatSelectModule, + MatDatepickerModule, + MatTooltipModule, + MatTreeModule, + MatExpansionModule, + MatStepperModule, + MatRadioModule, + MatProgressSpinnerModule, + + // CDK and other modules + PortalModule, + OverlayModule, + DragDropModule, + + // i18n and markdown + TranslateModule.forChild({}), + MarkdownModule.forRoot(), + ], + providers: [ + { provide: MatPaginatorIntl, useFactory: getCustomPaginatorIntl }, + ], + exports: [SpTablePaginationComponent], +}) +export class SpTablePaginationModule {} diff --git a/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts b/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts index 0ead4013e3..5817908879 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts +++ b/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts @@ -98,6 +98,7 @@ import { InputSchemaPropertyComponent } from './components/input-schema-panel/in import { MatExpansionModule } from '@angular/material/expansion'; import { SortByRuntimeNamePipe } from './pipes/sort-by-runtime-name.pipe'; import { DragDropModule } from '@angular/cdk/drag-drop'; +import { SpTablePaginationModule } from './components/sp-table-pagination/sp-table-pagination.module'; @NgModule({ declarations: [ @@ -216,6 +217,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; PipelineElementComponent, InputSchemaPanelComponent, SidebarResizeComponent, + SpTablePaginationModule, ], }) export class SharedUiModule {} diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts b/ui/projects/streampipes/shared-ui/src/public-api.ts index ae6cdd29c1..afcc2780cb 100644 --- a/ui/projects/streampipes/shared-ui/src/public-api.ts +++ b/ui/projects/streampipes/shared-ui/src/public-api.ts @@ -53,6 +53,8 @@ export * from './lib/components/pipeline-element-documentation/pipeline-element- export * from './lib/components/pipeline-element/pipeline-element.component'; export * from './lib/components/input-schema-panel/input-schema-panel.component'; export * from './lib/components/sidebar-resize/sidebar-resize.component'; +export * from './lib/components/sp-table-pagination/sp-table-pagination.module'; +export * from './lib/components/sp-table-pagination/sp-table-pagination.component'; export * from './lib/models/sp-navigation.model'; diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html index 73f8efa9cd..1066b6ab1d 100644 --- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html +++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html @@ -97,11 +97,15 @@ title="Adapters" >
- @@ -191,7 +195,9 @@ - Created + + Created +
{{ @@ -289,7 +295,7 @@
- + diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts index 34cc2adb65..3f08d79f6e 100644 --- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts +++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts @@ -16,7 +16,13 @@ * */ -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { + Component, + OnDestroy, + OnInit, + ViewChild, + ChangeDetectorRef, +} from '@angular/core'; import { AdapterDescription, AdapterMonitoringService, @@ -25,7 +31,8 @@ import { SpLogMessage, SpMetricsEntry, } from '@streampipes/platform-services'; -import { MatTableDataSource } from '@angular/material/table'; +import { MatSortHeader } from '@angular/material/sort'; +import { Observable } from 'rxjs'; import { CurrentUserService, DialogRef, @@ -45,6 +52,8 @@ import { AdapterFilterPipe } from '../../filter/adapter-filter.pipe'; import { SpConnectRoutes } from '../../connect.routes'; import { Subscription, zip } from 'rxjs'; import { ShepherdService } from '../../../services/tour/shepherd.service'; +import { BehaviorSubject } from 'rxjs'; +import { tap } from 'rxjs/operators'; @Component({ selector: 'sp-existing-adapters', @@ -73,9 +82,17 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { 'action', ]; - dataSource: MatTableDataSource = - new MatTableDataSource(); isAdmin = false; + refreshSwitch = new BehaviorSubject(false); + filter = new BehaviorSubject<{ + text: string; + category: string; + view: string; + }>({ + text: '', + category: '', + view: '', + }); adapterMetrics: Record = {}; tutorialActive = false; @@ -87,7 +104,9 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { startAdapterErrorText = 'Could not start adapter'; stopAdapterErrorText = 'Could not stop adapter'; + allAdapters: AdapterDescription[] = []; constructor( + private cdRef: ChangeDetectorRef, private adapterService: AdapterService, private dialogService: DialogService, private currentUserService: CurrentUserService, @@ -113,6 +132,23 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { this.shepherdService.tutorialActive$.subscribe(tutorialActive => { this.tutorialActive = tutorialActive; }); + + this.setDefaultSort(); + } + + ngAfterViewInit(): void { + this.setDefaultSort(); + } + + private setDefaultSort(): void { + if (this.sort) { + this.sort.sort({ + id: 'lastModified', + start: 'asc', + disableClear: false, + }); + } + this.cdRef.detectChanges(); } startAdapter(adapter: AdapterDescription) { @@ -151,7 +187,38 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { return active; } - startAllAdapters(action: boolean) { + private getAllAdapters(): Observable { + return this.adapterService.getAdapters().pipe( + tap(adapters => { + this.allAdapters = adapters; + this.operationInProgressAdapterId = undefined; + this.getMonitoringInfos(adapters); + }), + ); + } + + startAllAdapters(action: boolean): void { + this.getAllAdapters().subscribe(allAdapters => { + const dialogRef: DialogRef = + this.dialogService.open(AllAdapterActionsComponent, { + panelType: PanelType.STANDARD_PANEL, + title: (action ? 'Start' : 'Stop') + ' all adapters', + width: '70vw', + data: { + adapters: allAdapters, + action: action, + }, + }); + + dialogRef.afterClosed().subscribe(data => { + if (data) { + this.getAdaptersRunning(); + } + }); + }); + } + + startAdapters(action: boolean) { const dialogRef: DialogRef = this.dialogService.open(AllAdapterActionsComponent, { panelType: PanelType.STANDARD_PANEL, @@ -267,19 +334,12 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { } getAdaptersRunning(): void { - this.adapterService.getAdapters().subscribe(adapters => { - this.existingAdapters = adapters; - this.existingAdapters.sort((a, b) => a.name.localeCompare(b.name)); - this.applyAdapterFilters(this.currentFilterIds); - this.operationInProgressAdapterId = undefined; - this.getMonitoringInfos(adapters); - setTimeout(() => { - this.dataSource.sort = this.sort; - }); - }); + this.operationInProgressAdapterId = undefined; + this.refreshSwitch.next(!this.refreshSwitch.value); } applyAdapterFilters(elementIds: Set): void { + // left in here for usage in Asset Browser this.currentFilterIds = elementIds; this.filteredAdapters = this.adapterFilter .transform(this.existingAdapters, this.currentFilter) @@ -290,7 +350,6 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { return elementIds.has(a.elementId); } }); - this.dataSource.data = this.filteredAdapters; } startAdapterTutorial() { @@ -303,11 +362,94 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { }); } - applyFilter(filter: AdapterFilterSettingsModel) { - this.currentFilter = filter; - if (this.dataSource) { - this.applyAdapterFilters(this.currentFilterIds); + applyFilter(filtering: AdapterFilterSettingsModel): void { + if (filtering.textFilter) { + this.filter.next({ + text: filtering.textFilter, + category: '', + view: 'name', + }); + return; + } + if ( + filtering.selectedCategory && + filtering.selectedCategory !== 'All' + ) { + this.filter.next({ + text: '', + category: filtering.selectedCategory, + view: 'category', + }); + return; + } + + if (filtering.selectedCategory && filtering.selectedCategory == 'All') { + if (this.sort.active === 'category') { + this.sort.active = 'createdAt'; + } + } + // TODO MAYBE NECESSARY TO DO THIS AS ELSE + this.filter.next({ text: '', category: '', view: 'createdAt' }); + } + + private buildRangeForTextFilter( + text: string, + startKey: string | null, + ): { + startKey: string | null; + endKey: string | null; + } { + if (!startKey || startKey == null) { + startKey = text; + } + const endKey = text + '\ufff0'; + this.sort.active = 'name'; + // Search Field only works if asc + this.sort.direction = 'asc'; + return { startKey, endKey }; + } + + private buildRangeForCategoryFilter( + category: string, + startKey: string | null, + ): { + startKey: string | null; + endKey: string | null; + } { + this.sort.active = 'category'; + + if (!startKey) { + startKey = `["${category}"]`; } + + const endKey = startKey.startsWith('[') + ? startKey.slice(0, -1) + ', "\ufff0"]' + : startKey + '\ufff0'; + + return { startKey, endKey }; + } + + getStartAndEndKeyFromFilter(startKeyOrg: string | null): { + startKey: string | null; + endKey: string | null; + } { + const filterValue = this.filter.value; + const startKey = startKeyOrg; + if (filterValue.text != '') { + return this.buildRangeForTextFilter(filterValue.text, startKey); + } else if ( + filterValue.category != '' && + filterValue.category !== 'All' + ) { + return this.buildRangeForCategoryFilter( + filterValue.category, + startKey, + ); + } else if (filterValue.category == 'All') { + this.sort.active = 'createdAt'; + } + + return { startKey, endKey: null }; } navigateToDetailsOverviewPage(adapter: AdapterDescription): void { @@ -318,4 +460,64 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { this.userSubscription?.unsubscribe(); this.tutorialActiveSubscription?.unsubscribe(); } + + getViewKeysForSort(sortActive: string | undefined): string | string[] { + // This is necessary in case the element names in the HTML and the keys used for sorting follow different naming conventions or are composite keys. (E.g., created in HTML and the database key is createdAt) + const sortMap: { [key: string]: string | string[] } = { + lastModified: 'createdAt', + status: ['running', 'elementId'], + category: ['category', 'elementId'], + }; + + return sortMap[sortActive ?? ''] || sortActive || ''; + } + + fetchAdapters = ( + startKey?: string, + pageSize?: number, + ): Observable => { + const { startKey: derivedStartKey, endKey } = + this.getStartAndEndKeyFromFilter(startKey); + const sortBy = this.getSortView(); + + if (sortBy == 'category') { + // Unfortunatly needs a different endpoint + const arr = JSON.parse(derivedStartKey); + + return this.adapterService.getAdaptersCategorywisePaginated( + arr[0], + arr[1], + pageSize, + false, + ); + } else { + return this.adapterService + .getAdaptersPaginated( + derivedStartKey, + endKey, + pageSize, + sortBy, + this.sort?.direction !== 'asc', + ) + .pipe( + tap(adapters => { + this.existingAdapters = adapters; + this.operationInProgressAdapterId = undefined; + this.getMonitoringInfos(adapters); + }), + ); + } + }; + + private getSortView(): string { + // Parse naming of the view + if (this.sort?.active === 'category') { + return 'category'; + } else if (this.sort?.active === 'lastModified') { + return 'createdAt'; + } else if (this.sort?.active === 'status') { + return 'running'; + } + return this.sort?.active || 'createdAt'; + } } diff --git a/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.html b/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.html index 7f790adbdd..ac7f3b88eb 100644 --- a/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.html +++ b/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.html @@ -44,6 +44,7 @@
diff --git a/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.ts b/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.ts index e90973b000..0cffe5a037 100644 --- a/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.ts +++ b/ui/src/app/connect/components/filter-toolbar/filter-toolbar.component.ts @@ -59,11 +59,15 @@ export class SpConnectFilterToolbarComponent implements OnInit { } filterAdapter(event: MatSelectChange) { + // Reset the text filter when a category is selected + this.currentFilter.textFilter = ''; this.filterChangedEmitter.emit(this.currentFilter); } - updateFilterTerm(event: string) { - this.currentFilter.textFilter = event; + updateFilterTerm(term: string) { + // Reset the category filter when text input is used + this.currentFilter.textFilter = term; + this.currentFilter.selectedCategory = 'All'; this.filterChangedEmitter.emit(this.currentFilter); } }