From a717994d04edbb52f21d0f517e082b4dc6ece178 Mon Sep 17 00:00:00 2001 From: Aditya Baldwa Date: Sat, 19 Oct 2024 14:04:12 +0530 Subject: [PATCH] Correct checkstyle --- .github/workflows/build-public-image.yml | 11 +- .github/workflows/docker_build.yml | 87 +++++ .github/workflows/docker_publish.yml | 100 ++++++ .github/workflows/main.yml | 67 ++-- .github/workflows/release.yml | 58 ++-- api/pom.xml | 15 +- .../io/kafbat/ui/config/CorsProperties.java | 4 +- .../kafbat/ui/config/ReadOnlyModeFilter.java | 2 +- .../controller/ConsumerGroupsController.java | 18 ++ .../kafbat/ui/model/InternalBrokerConfig.java | 4 +- .../io/kafbat/ui/service/BrokerService.java | 2 +- .../ui/service/ConsumerGroupService.java | 20 +- .../ui/service/ReactiveAdminClient.java | 22 ++ .../io/kafbat/ui/service/acl/AclsService.java | 4 +- .../io/kafbat/ui/KafkaConsumerGroupTests.java | 67 ++++ .../ui/serdes/PropertyResolverImplTest.java | 8 +- .../ui/service/acl/AclsServiceTest.java | 21 +- .../ui/service/ksql/KsqlApiClientTest.java | 11 +- .../main/resources/swagger/kafbat-ui-api.yaml | 26 ++ frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 301 ++++++++++-------- .../InputCell/InputCellViewMode.tsx | 6 +- .../TableComponents/InputCell/styled.ts | 7 +- .../ConsumerGroups/Details/ListItem.tsx | 36 ++- .../PageContainer/PageContainer.tsx | 4 +- .../src/components/common/Editor/Editor.tsx | 1 + .../common/NewTable/Table.styled.ts | 1 + frontend/src/lib/hooks/api/consumers.ts | 27 ++ frontend/src/lib/hooks/filterUtils.ts | 2 +- pom.xml | 18 +- 30 files changed, 673 insertions(+), 279 deletions(-) create mode 100644 .github/workflows/docker_build.yml create mode 100644 .github/workflows/docker_publish.yml diff --git a/.github/workflows/build-public-image.yml b/.github/workflows/build-public-image.yml index ac48d29a0..3e364c992 100644 --- a/.github/workflows/build-public-image.yml +++ b/.github/workflows/build-public-image.yml @@ -6,7 +6,9 @@ on: types: ['labeled'] permissions: + id-token: write contents: read + pull-requests: write jobs: build: @@ -47,12 +49,11 @@ jobs: key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - - name: Configure AWS credentials for Kafka-UI account + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_ROLE }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 @@ -65,7 +66,7 @@ jobs: builder: ${{ steps.buildx.outputs.name }} context: api push: true - tags: public.ecr.aws/kafbat/kafka-ui-custom-build:${{ steps.extract_branch.outputs.tag }} + tags: ${{ vars.ECR_REGISTRY }}/${{ github.repository }}:${{ steps.extract_branch.outputs.tag }} build-args: | JAR_FILE=api-${{ steps.build.outputs.version }}.jar cache-from: type=local,src=/tmp/.buildx-cache @@ -75,6 +76,6 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} body: | - Image published at public.ecr.aws/kafbat/kafka-ui-custom-build:${{ steps.extract_branch.outputs.tag }} + Image published at ${{ vars.ECR_REGISTRY }}/${{ github.repository }}:${{ steps.extract_branch.outputs.tag }} outputs: tag: ${{ steps.extract_branch.outputs.tag }} diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml new file mode 100644 index 000000000..2c4f513d5 --- /dev/null +++ b/.github/workflows/docker_build.yml @@ -0,0 +1,87 @@ +name: "Docker build" + +on: + workflow_call: + inputs: + sha: + required: true + type: string + version: + required: true + type: string + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + token: ${{ github.token }} + + - name: Download maven artifacts + uses: actions/download-artifact@v4 + with: + name: kafbat-ui-${{ inputs.version }} + path: api/target + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ inputs.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + # Build multi platform images and loading them at the same time is not possible with default container runtime : https://github.com/docker/buildx/issues/59 + # So let's use containerd instead as it supports this option + # Also containerd is one of the option to allow preserving provenance attestations :https://docs.docker.com/build/attestations/#creating-attestations + - name: Setup docker with containerd + uses: crazy-max/ghaction-setup-docker@v3 + with: + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } + + - name: Build docker image + id: docker_build + uses: docker/build-push-action@v5 + with: + builder: ${{ steps.buildx.outputs.name }} + context: api + platforms: linux/amd64,linux/arm64 + provenance: mode=min + sbom: true + push: false + load: true + tags: | + kafka-ui:temp + build-args: | + JAR_FILE=api-${{ inputs.version }}.jar + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Dump docker image + run: | + docker image save kafka-ui:temp > /tmp/image.tar + + - name: Upload docker image + uses: actions/upload-artifact@v4 + with: + name: image + path: /tmp/image.tar + retention-days: 1 diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml new file mode 100644 index 000000000..e359ea740 --- /dev/null +++ b/.github/workflows/docker_publish.yml @@ -0,0 +1,100 @@ +name: "Docker publish" + +on: + workflow_call: + inputs: + version: + required: true + type: string + generic_tag: + required: true + type: string + +permissions: + packages: write + id-token: write # Required to authenticate with OIDC for AWS + +jobs: + deploy: + continue-on-error: true + strategy: + fail-fast: false + matrix: + registry: [ 'docker.io', 'ghcr.io', 'ecr' ] + + runs-on: ubuntu-latest + steps: + + - name: Download docker image + uses: actions/download-artifact@v4 + with: + name: image + path: /tmp + + # setup containerd to preserve provenance attestations :https://docs.docker.com/build/attestations/#creating-attestations + - name: Setup docker with containerd + uses: crazy-max/ghaction-setup-docker@v3 + with: + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } + + - name: Load docker image into daemon + run: | + docker load --input /tmp/image.tar + + - name: Login to docker.io + if: matrix.registry == 'docker.io' + uses: docker/login-action@v3 + with: + registry: ${{ matrix.registry }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to ghcr.io + if: matrix.registry == 'ghcr.io' + uses: docker/login-action@v3 + with: + registry: ${{ matrix.registry }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS credentials + if: matrix.registry == 'ecr' + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-east-1 # This region only for public ECR + role-to-assume: ${{ secrets.AWS_ROLE }} + + - name: Login to public ECR + if: matrix.registry == 'ecr' + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + + - name: define env vars + run: | + if [ ${{matrix.registry }} == 'docker.io' ]; then + echo "REGISTRY=${{ matrix.registry }}" >> $GITHUB_ENV + echo "REPOSITORY=${{ github.repository }}" >> $GITHUB_ENV + elif [ ${{ matrix.registry }} == 'ghcr.io' ]; then + echo "REGISTRY=${{ matrix.registry }}" >> $GITHUB_ENV + echo "REPOSITORY=${{ github.repository }}" >> $GITHUB_ENV + elif [ ${{ matrix.registry }} == 'ecr' ]; then + echo "REGISTRY=${{ vars.ECR_REGISTRY }}" >> $GITHUB_ENV + echo "REPOSITORY=${{ github.repository }}" >> $GITHUB_ENV + else + echo "REGISTRY=" >> $GITHUB_ENV + echo "REPOSITORY=notworking" >> $GITHUB_ENV + fi + + - name: Push images to ${{ matrix.registry }} + run: | + docker tag kafka-ui:temp ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ inputs.generic_tag }} + docker tag kafka-ui:temp ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ inputs.version }} + docker push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ inputs.generic_tag }} + docker push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ inputs.version }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d4e180086..7701b91e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,11 +9,14 @@ permissions: contents: read jobs: - build: + jar-build: runs-on: ubuntu-latest + permissions: contents: read - packages: write + + outputs: + version: ${{steps.build.outputs.version}} steps: - name: Checkout @@ -37,42 +40,30 @@ jobs: export VERSION=$(./mvnw -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) echo "version=${VERSION}" >> $GITHUB_OUTPUT - # docker images - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Cache Docker layers - uses: actions/cache@v4 + - name: Upload jar + uses: actions/upload-artifact@v4 with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + name: kafbat-ui-${{ steps.build.outputs.version }} + path: api/target/api-${{ steps.build.outputs.version }}.jar + retention-days: 1 - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + docker-build: + needs: jar-build + permissions: + contents: read + uses: ./.github/workflows/docker_build.yml + secrets: inherit + with: + sha: ${{ github.sha }} + version: ${{ needs.jar-build.outputs.version }} - - name: Build & push docker image - id: docker_build_and_push - uses: docker/build-push-action@v5 - with: - builder: ${{ steps.buildx.outputs.name }} - context: api - platforms: linux/amd64,linux/arm64 - provenance: false - push: true - tags: | - ghcr.io/kafbat/kafka-ui:${{ steps.build.outputs.version }} - ghcr.io/kafbat/kafka-ui:main - build-args: | - JAR_FILE=api-${{ steps.build.outputs.version }}.jar - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache + docker-deploy: + needs: [ jar-build, docker-build ] + permissions: + packages: write + id-token: write # Required to authenticate with OIDC for AWS + uses: ./.github/workflows/docker_publish.yml + secrets: inherit + with: + version: ${{ needs.jar-build.outputs.version }} + generic_tag: main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c7fcce52..3a3c9de23 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,47 +52,27 @@ jobs: with: name: kafbat-ui-${{ steps.build.outputs.version }} path: api/target/api-${{ steps.build.outputs.version }}.jar - ################# - # # - # Docker images # - # # - ################# - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Cache Docker layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + docker-build: + needs: release + permissions: + contents: read + uses: ./.github/workflows/docker_build.yml + secrets: inherit + with: + sha: ${{ github.sha }} + version: ${{ needs.release.outputs.version }} - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - id: docker_build_and_push - uses: docker/build-push-action@v5 - with: - builder: ${{ steps.buildx.outputs.name }} - context: api - platforms: linux/amd64,linux/arm64 - provenance: false - push: true - tags: | - ghcr.io/kafbat/kafka-ui:${{ steps.build.outputs.version }} - ghcr.io/kafbat/kafka-ui:latest - build-args: | - JAR_FILE=api-${{ steps.build.outputs.version }}.jar - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache + docker-deploy: + needs: [release, docker-build] + permissions: + packages: write + id-token: write # Required to authenticate with OIDC for AWS + uses: ./.github/workflows/docker_publish.yml + secrets: inherit + with: + version: ${{ needs.release.outputs.version }} + generic_tag: latest charts: runs-on: ubuntu-latest diff --git a/api/pom.xml b/api/pom.xml index a892eba7f..8452167ce 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -91,7 +91,7 @@ software.amazon.msk aws-msk-iam-auth - 2.1.0 + 2.2.0 @@ -266,18 +266,6 @@ cel - - ch.qos.logback - logback-classic - 1.4.12 - - - - ch.qos.logback - logback-core - 1.4.12 - - com.squareup.okhttp3 logging-interceptor @@ -289,7 +277,6 @@ commons-compress 1.26.0 - diff --git a/api/src/main/java/io/kafbat/ui/config/CorsProperties.java b/api/src/main/java/io/kafbat/ui/config/CorsProperties.java index a3c1a47ea..e32a00cf1 100644 --- a/api/src/main/java/io/kafbat/ui/config/CorsProperties.java +++ b/api/src/main/java/io/kafbat/ui/config/CorsProperties.java @@ -1,12 +1,12 @@ package io.kafbat.ui.config; +import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import lombok.Data; +@Data @Component @ConfigurationProperties(prefix = "cors") -@Data public class CorsProperties { diff --git a/api/src/main/java/io/kafbat/ui/config/ReadOnlyModeFilter.java b/api/src/main/java/io/kafbat/ui/config/ReadOnlyModeFilter.java index 81165e988..ac7c6747f 100644 --- a/api/src/main/java/io/kafbat/ui/config/ReadOnlyModeFilter.java +++ b/api/src/main/java/io/kafbat/ui/config/ReadOnlyModeFilter.java @@ -25,7 +25,7 @@ public class ReadOnlyModeFilter implements WebFilter { Pattern.compile("/api/clusters/(?[^/]++)"); private static final Set SAFE_ENDPOINTS = Set.of( - Pattern.compile("/api/clusters/[^/]+/topics/[^/]+/(smartfilters)$") + Pattern.compile("/api/clusters/[^/]+/topics/[^/]+/(smartfilters|analysis)$") ); private final ClustersStorage clustersStorage; diff --git a/api/src/main/java/io/kafbat/ui/controller/ConsumerGroupsController.java b/api/src/main/java/io/kafbat/ui/controller/ConsumerGroupsController.java index 248c6f7d7..32337bbcf 100644 --- a/api/src/main/java/io/kafbat/ui/controller/ConsumerGroupsController.java +++ b/api/src/main/java/io/kafbat/ui/controller/ConsumerGroupsController.java @@ -59,6 +59,24 @@ public Mono> deleteConsumerGroup(String clusterName, .thenReturn(ResponseEntity.ok().build()); } + @Override + public Mono> deleteConsumerGroupOffsets(String clusterName, + String groupId, + String topicName, + ServerWebExchange exchange) { + var context = AccessContext.builder() + .cluster(clusterName) + .consumerGroupActions(groupId, RESET_OFFSETS) + .topicActions(topicName, TopicAction.VIEW) + .operationName("deleteConsumerGroupOffsets") + .build(); + + return validateAccess(context) + .then(consumerGroupService.deleteConsumerGroupOffset(getCluster(clusterName), groupId, topicName)) + .doOnEach(sig -> audit(context, sig)) + .thenReturn(ResponseEntity.ok().build()); + } + @Override public Mono> getConsumerGroup(String clusterName, String consumerGroupId, diff --git a/api/src/main/java/io/kafbat/ui/model/InternalBrokerConfig.java b/api/src/main/java/io/kafbat/ui/model/InternalBrokerConfig.java index 496c8bfe5..5f87b0487 100644 --- a/api/src/main/java/io/kafbat/ui/model/InternalBrokerConfig.java +++ b/api/src/main/java/io/kafbat/ui/model/InternalBrokerConfig.java @@ -16,12 +16,12 @@ public class InternalBrokerConfig { private final boolean isReadOnly; private final List synonyms; - public static InternalBrokerConfig from(ConfigEntry configEntry) { + public static InternalBrokerConfig from(ConfigEntry configEntry, boolean readOnlyCluster) { InternalBrokerConfig.InternalBrokerConfigBuilder builder = InternalBrokerConfig.builder() .name(configEntry.name()) .value(configEntry.value()) .source(configEntry.source()) - .isReadOnly(configEntry.isReadOnly()) + .isReadOnly(readOnlyCluster || configEntry.isReadOnly()) .isSensitive(configEntry.isSensitive()) .synonyms(configEntry.synonyms()); return builder.build(); diff --git a/api/src/main/java/io/kafbat/ui/service/BrokerService.java b/api/src/main/java/io/kafbat/ui/service/BrokerService.java index fd9c4c8ab..198685b93 100644 --- a/api/src/main/java/io/kafbat/ui/service/BrokerService.java +++ b/api/src/main/java/io/kafbat/ui/service/BrokerService.java @@ -59,7 +59,7 @@ private Flux getBrokersConfig(KafkaCluster cluster, Intege } return loadBrokersConfig(cluster, brokerId) .map(list -> list.stream() - .map(InternalBrokerConfig::from) + .map(configEntry -> InternalBrokerConfig.from(configEntry, cluster.isReadOnly())) .collect(Collectors.toList())) .flatMapMany(Flux::fromIterable); } diff --git a/api/src/main/java/io/kafbat/ui/service/ConsumerGroupService.java b/api/src/main/java/io/kafbat/ui/service/ConsumerGroupService.java index 452de4a59..27593cd6f 100644 --- a/api/src/main/java/io/kafbat/ui/service/ConsumerGroupService.java +++ b/api/src/main/java/io/kafbat/ui/service/ConsumerGroupService.java @@ -209,12 +209,13 @@ private Mono> describeConsumerGroups(ReactiveAdmi } - private Mono> loadDescriptionsByInternalConsumerGroups(ReactiveAdminClient ac, - List groups, - Comparator comparator, - int pageNum, - int perPage, - SortOrderDTO sortOrderDto) { + private Mono> loadDescriptionsByInternalConsumerGroups( + ReactiveAdminClient ac, + List groups, + Comparator comparator, + int pageNum, + int perPage, + SortOrderDTO sortOrderDto) { var groupNames = groups.stream().map(ConsumerGroupListing::groupId).toList(); return ac.describeConsumerGroups(groupNames) @@ -247,6 +248,13 @@ public Mono deleteConsumerGroupById(KafkaCluster cluster, .flatMap(adminClient -> adminClient.deleteConsumerGroups(List.of(groupId))); } + public Mono deleteConsumerGroupOffset(KafkaCluster cluster, + String groupId, + String topicName) { + return adminClientService.get(cluster) + .flatMap(adminClient -> adminClient.deleteConsumerGroupOffsets(groupId, topicName)); + } + public EnhancedConsumer createConsumer(KafkaCluster cluster) { return createConsumer(cluster, Map.of()); } diff --git a/api/src/main/java/io/kafbat/ui/service/ReactiveAdminClient.java b/api/src/main/java/io/kafbat/ui/service/ReactiveAdminClient.java index bb04f5527..651f6d531 100644 --- a/api/src/main/java/io/kafbat/ui/service/ReactiveAdminClient.java +++ b/api/src/main/java/io/kafbat/ui/service/ReactiveAdminClient.java @@ -74,6 +74,7 @@ import org.apache.kafka.common.errors.ClusterAuthorizationException; import org.apache.kafka.common.errors.GroupIdNotFoundException; import org.apache.kafka.common.errors.GroupNotEmptyException; +import org.apache.kafka.common.errors.GroupSubscribedToTopicException; import org.apache.kafka.common.errors.InvalidRequestException; import org.apache.kafka.common.errors.SecurityDisabledException; import org.apache.kafka.common.errors.TopicAuthorizationException; @@ -436,6 +437,27 @@ public Mono deleteConsumerGroups(Collection groupIds) { th -> Mono.error(new IllegalEntityStateException("The group is not empty"))); } + public Mono deleteConsumerGroupOffsets(String groupId, String topicName) { + return listConsumerGroupOffsets(List.of(groupId), null) + .flatMap(table -> { + // filter TopicPartitions by topicName + Set partitions = table.row(groupId).keySet().stream() + .filter(tp -> tp.topic().equals(topicName)) + .collect(Collectors.toSet()); + // check if partitions have no committed offsets + return partitions.isEmpty() + ? Mono.error(new NotFoundException("The topic or partition is unknown")) + // call deleteConsumerGroupOffsets + : toMono(client.deleteConsumerGroupOffsets(groupId, partitions).all()); + }) + .onErrorResume(GroupIdNotFoundException.class, + th -> Mono.error(new NotFoundException("The group id does not exist"))) + .onErrorResume(UnknownTopicOrPartitionException.class, + th -> Mono.error(new NotFoundException("The topic or partition is unknown"))) + .onErrorResume(GroupSubscribedToTopicException.class, + th -> Mono.error(new IllegalEntityStateException("The group is not empty"))); + } + public Mono createTopic(String name, int numPartitions, @Nullable Integer replicationFactor, diff --git a/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java b/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java index 4ea82fe12..b3877a336 100644 --- a/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java +++ b/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java @@ -158,7 +158,7 @@ public Mono createConsumerAcl(KafkaCluster cluster, CreateConsumerAclDTO r .then(); } - //Read, Describe on topics, Read on consumerGroups + //Read, Describe on topics and consumerGroups private List createConsumerBindings(CreateConsumerAclDTO request) { List bindings = new ArrayList<>(); bindings.addAll( @@ -172,7 +172,7 @@ private List createConsumerBindings(CreateConsumerAclDTO request) { bindings.addAll( createAllowBindings( GROUP, - List.of(READ), + List.of(READ, DESCRIBE), request.getPrincipal(), request.getHost(), request.getConsumerGroupsPrefix(), diff --git a/api/src/test/java/io/kafbat/ui/KafkaConsumerGroupTests.java b/api/src/test/java/io/kafbat/ui/KafkaConsumerGroupTests.java index 5f97317f2..b1bf4baa7 100644 --- a/api/src/test/java/io/kafbat/ui/KafkaConsumerGroupTests.java +++ b/api/src/test/java/io/kafbat/ui/KafkaConsumerGroupTests.java @@ -4,6 +4,7 @@ import io.kafbat.ui.model.ConsumerGroupDTO; import io.kafbat.ui.model.ConsumerGroupsPageResponseDTO; +import io.kafbat.ui.producer.KafkaTestProducer; import java.io.Closeable; import java.time.Duration; import java.util.Comparator; @@ -22,6 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; @Slf4j public class KafkaConsumerGroupTests extends AbstractIntegrationTest { @@ -31,12 +34,76 @@ public class KafkaConsumerGroupTests extends AbstractIntegrationTest { @Test void shouldNotFoundWhenNoSuchConsumerGroupId() { String groupId = "groupA"; + String topicName = "topicX"; + webTestClient .delete() .uri("/api/clusters/{clusterName}/consumer-groups/{groupId}", LOCAL, groupId) .exchange() .expectStatus() .isNotFound(); + + webTestClient + .delete() + .uri("/api/clusters/{clusterName}/consumer-groups/{groupId}/topics/{topicName}", LOCAL, groupId, topicName) + .exchange() + .expectStatus() + .isNotFound(); + } + + @Test + void shouldNotFoundWhenNoSuchTopic() { + String topicName = createTopicWithRandomName(); + String topicNameUnSubscribed = "topicX"; + + //Create a consumer and subscribe to the topic + String groupId = UUID.randomUUID().toString(); + try (val consumer = createTestConsumerWithGroupId(groupId)) { + consumer.subscribe(List.of(topicName)); + consumer.poll(Duration.ofMillis(100)); + + webTestClient + .delete() + .uri("/api/clusters/{clusterName}/consumer-groups/{groupId}/topics/{topicName}", LOCAL, groupId, + topicNameUnSubscribed) + .exchange() + .expectStatus() + .isNotFound(); + } + } + + @Test + void shouldOkWhenConsumerGroupIsNotActiveAndPartitionOffsetExists() { + String topicName = createTopicWithRandomName(); + + //Create a consumer and subscribe to the topic + String groupId = UUID.randomUUID().toString(); + + try (KafkaTestProducer producer = KafkaTestProducer.forKafka(kafka)) { + Flux.fromStream( + Stream.of("one", "two", "three", "four") + .map(value -> Mono.fromFuture(producer.send(topicName, value))) + ).blockLast(); + } catch (Throwable e) { + log.error("Error on sending", e); + throw new RuntimeException(e); + } + + try (val consumer = createTestConsumerWithGroupId(groupId)) { + consumer.subscribe(List.of(topicName)); + consumer.poll(Duration.ofMillis(100)); + + //Stop consumers to delete consumer offset from the topic + consumer.pause(consumer.assignment()); + } + + //Delete the consumer offset when it's INACTIVE and check + webTestClient + .delete() + .uri("/api/clusters/{clusterName}/consumer-groups/{groupId}/topics/{topicName}", LOCAL, groupId, topicName) + .exchange() + .expectStatus() + .isOk(); } @Test diff --git a/api/src/test/java/io/kafbat/ui/serdes/PropertyResolverImplTest.java b/api/src/test/java/io/kafbat/ui/serdes/PropertyResolverImplTest.java index 59d650ef7..97d592dc6 100644 --- a/api/src/test/java/io/kafbat/ui/serdes/PropertyResolverImplTest.java +++ b/api/src/test/java/io/kafbat/ui/serdes/PropertyResolverImplTest.java @@ -5,8 +5,6 @@ import java.util.List; import java.util.Map; -import lombok.AllArgsConstructor; -import lombok.Data; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.boot.context.properties.bind.BindException; @@ -21,11 +19,7 @@ class PropertyResolverImplTest { private final MockEnvironment env = new MockEnvironment(); - @Data - @AllArgsConstructor - public static class CustomPropertiesClass { - private String f1; - private Integer f2; + public record CustomPropertiesClass(String f1, Integer f2) { } @Test diff --git a/api/src/test/java/io/kafbat/ui/service/acl/AclsServiceTest.java b/api/src/test/java/io/kafbat/ui/service/acl/AclsServiceTest.java index 189e7c060..cfa46d1eb 100644 --- a/api/src/test/java/io/kafbat/ui/service/acl/AclsServiceTest.java +++ b/api/src/test/java/io/kafbat/ui/service/acl/AclsServiceTest.java @@ -103,10 +103,10 @@ void createsConsumerDependantAcls() { .topics(List.of("t1", "t2")) ).block(); - //Read, Describe on topics, Read on consumerGroups + //Read, Describe on topics and consumerGroups Collection createdBindings = createdCaptor.getValue(); assertThat(createdBindings) - .hasSize(6) + .hasSize(8) .contains(new AclBinding( new ResourcePattern(ResourceType.TOPIC, "t1", PatternType.LITERAL), new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))) @@ -122,9 +122,15 @@ void createsConsumerDependantAcls() { .contains(new AclBinding( new ResourcePattern(ResourceType.GROUP, "cg1", PatternType.LITERAL), new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))) + .contains(new AclBinding( + new ResourcePattern(ResourceType.GROUP, "cg1", PatternType.LITERAL), + new AccessControlEntry(principal, host, AclOperation.DESCRIBE, AclPermissionType.ALLOW))) + .contains(new AclBinding( + new ResourcePattern(ResourceType.GROUP, "cg2", PatternType.LITERAL), + new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))) .contains(new AclBinding( new ResourcePattern(ResourceType.GROUP, "cg2", PatternType.LITERAL), - new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))); + new AccessControlEntry(principal, host, AclOperation.DESCRIBE, AclPermissionType.ALLOW))); } @Test @@ -145,10 +151,10 @@ void createsConsumerDependantAclsWhenTopicsAndGroupsSpecifiedByPrefix() { .topicsPrefix("topicPref") ).block(); - //Read, Describe on topics, Read on consumerGroups + //Read, Describe on topics and consumerGroups Collection createdBindings = createdCaptor.getValue(); assertThat(createdBindings) - .hasSize(3) + .hasSize(4) .contains(new AclBinding( new ResourcePattern(ResourceType.TOPIC, "topicPref", PatternType.PREFIXED), new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))) @@ -157,7 +163,10 @@ void createsConsumerDependantAclsWhenTopicsAndGroupsSpecifiedByPrefix() { new AccessControlEntry(principal, host, AclOperation.DESCRIBE, AclPermissionType.ALLOW))) .contains(new AclBinding( new ResourcePattern(ResourceType.GROUP, "cgPref", PatternType.PREFIXED), - new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))); + new AccessControlEntry(principal, host, AclOperation.READ, AclPermissionType.ALLOW))) + .contains(new AclBinding( + new ResourcePattern(ResourceType.GROUP, "cgPref", PatternType.PREFIXED), + new AccessControlEntry(principal, host, AclOperation.DESCRIBE, AclPermissionType.ALLOW))); } @Test diff --git a/api/src/test/java/io/kafbat/ui/service/ksql/KsqlApiClientTest.java b/api/src/test/java/io/kafbat/ui/service/ksql/KsqlApiClientTest.java index 7bfb7c22a..90e549662 100644 --- a/api/src/test/java/io/kafbat/ui/service/ksql/KsqlApiClientTest.java +++ b/api/src/test/java/io/kafbat/ui/service/ksql/KsqlApiClientTest.java @@ -3,12 +3,11 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.DoubleNode; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.TextNode; import io.kafbat.ui.AbstractIntegrationTest; -import java.math.BigDecimal; import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.AfterAll; @@ -80,11 +79,11 @@ private void assertLastKsqTutorialQueryResult(KsqlApiClient client) { assertThat(header.getValues()).isNull(); }) .assertNext(row -> { - var distance = (DecimalNode) row.getValues().get(0).get(0); + var distance = (DoubleNode) row.getValues().get(0).get(0); var riders = (ArrayNode) row.getValues().get(0).get(1); var count = (IntNode) row.getValues().get(0).get(2); - assertThat(distance).isEqualTo(new DecimalNode(new BigDecimal(0))); + assertThat(distance).isEqualTo(new DoubleNode(0D)); assertThat(riders).isEqualTo(new ArrayNode(JsonNodeFactory.instance) .add(new TextNode("4ab5cbad")) .add(new TextNode("8b6eae59")) @@ -92,11 +91,11 @@ private void assertLastKsqTutorialQueryResult(KsqlApiClient client) { assertThat(count).isEqualTo(new IntNode(3)); }) .assertNext(row -> { - var distance = (DecimalNode) row.getValues().get(0).get(0); + var distance = (DoubleNode) row.getValues().get(0).get(0); var riders = (ArrayNode) row.getValues().get(0).get(1); var count = (IntNode) row.getValues().get(0).get(2); - assertThat(distance).isEqualTo(new DecimalNode(new BigDecimal(10))); + assertThat(distance).isEqualTo(new DoubleNode(10D)); assertThat(riders).isEqualTo(new ArrayNode(JsonNodeFactory.instance) .add(new TextNode("18f4ea86"))); assertThat(count).isEqualTo(new IntNode(1)); diff --git a/contract/src/main/resources/swagger/kafbat-ui-api.yaml b/contract/src/main/resources/swagger/kafbat-ui-api.yaml index 7ca62831f..5eede6cef 100644 --- a/contract/src/main/resources/swagger/kafbat-ui-api.yaml +++ b/contract/src/main/resources/swagger/kafbat-ui-api.yaml @@ -1048,6 +1048,32 @@ paths: 200: description: OK + /api/clusters/{clusterName}/consumer-groups/{id}/topics/{topicName}: + delete: + tags: + - Consumer Groups + summary: delete consumer group offsets + operationId: deleteConsumerGroupOffsets + parameters: + - name: clusterName + in: path + required: true + schema: + type: string + - name: id + in: path + required: true + schema: + type: string + - name: topicName + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + /api/clusters/{clusterName}/schemas: post: tags: diff --git a/frontend/package.json b/frontend/package.json index aef4f7fea..c4fef99db 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,7 @@ "pretty-ms": "7.0.1", "react": "18.2.0", "react-ace": "11.0.1", - "react-datepicker": "6.9.0", + "react-datepicker": "7.4.0", "react-dom": "18.2.0", "react-error-boundary": "4.0.13", "react-hook-form": "7.51.3", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 7ad87cafd..a7ba33ed0 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -21,28 +21,28 @@ importers: dependencies: '@floating-ui/react': specifier: 0.26.13 - version: 0.26.13(react-dom@18.2.0)(react@18.2.0) + version: 0.26.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@hookform/error-message': specifier: 2.0.1 - version: 2.0.1(react-dom@18.2.0)(react-hook-form@7.51.3)(react@18.2.0) + version: 2.0.1(react-dom@18.2.0(react@18.2.0))(react-hook-form@7.51.3(react@18.2.0))(react@18.2.0) '@hookform/resolvers': specifier: 2.7.1 - version: 2.7.1(react-hook-form@7.51.3) + version: 2.7.1(react-hook-form@7.51.3(react@18.2.0)) '@microsoft/fetch-event-source': specifier: 2.0.1 version: 2.0.1 '@szhsin/react-menu': specifier: 3.5.3 - version: 3.5.3(react-dom@18.2.0)(react@18.2.0) + version: 3.5.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tanstack/react-query': specifier: 4.36.1 - version: 4.36.1(react-dom@18.2.0)(react@18.2.0) + version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tanstack/react-query-devtools': specifier: 4.36.1 - version: 4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0) + version: 4.36.1(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tanstack/react-table': specifier: 8.16.0 - version: 8.16.0(react-dom@18.2.0)(react@18.2.0) + version: 8.16.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) ace-builds: specifier: 1.33.0 version: 1.33.0 @@ -69,10 +69,10 @@ importers: version: 18.2.0 react-ace: specifier: 11.0.1 - version: 11.0.1(react-dom@18.2.0)(react@18.2.0) + version: 11.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-datepicker: - specifier: 6.9.0 - version: 6.9.0(react-dom@18.2.0)(react@18.2.0) + specifier: 7.4.0 + version: 7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) @@ -84,22 +84,22 @@ importers: version: 7.51.3(react@18.2.0) react-hot-toast: specifier: 2.4.1 - version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0) + version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-is: specifier: 18.2.0 version: 18.2.0 react-multi-select-component: specifier: 4.3.4 - version: 4.3.4(react-dom@18.2.0)(react@18.2.0) + version: 4.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-router-dom: specifier: 6.23.0 - version: 6.23.0(react-dom@18.2.0)(react@18.2.0) + version: 6.23.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) sass: specifier: 1.66.1 version: 1.66.1 styled-components: specifier: 6.1.8 - version: 6.1.8(react-dom@18.2.0)(react@18.2.0) + version: 6.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) use-debounce: specifier: 10.0.0 version: 10.0.0(react@18.2.0) @@ -127,10 +127,10 @@ importers: version: 10.0.0 '@testing-library/jest-dom': specifier: 6.4.2 - version: 6.4.2(jest@29.7.0) + version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3))) '@testing-library/react': specifier: 14.3.1 - version: 14.3.1(react-dom@18.2.0)(react@18.2.0) + version: 14.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@testing-library/user-event': specifier: 14.5.2 version: 14.5.2(@testing-library/dom@10.0.0) @@ -148,7 +148,7 @@ importers: version: 18.2.79 '@types/react-datepicker': specifier: 6.2.0 - version: 6.2.0(react-dom@18.2.0)(react@18.2.0) + version: 6.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@types/react-dom': specifier: 18.2.25 version: 18.2.25 @@ -163,13 +163,13 @@ importers: version: 5.14.9 '@typescript-eslint/eslint-plugin': specifier: 6.21.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.3) + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0)(typescript@5.3.3) '@typescript-eslint/parser': specifier: 6.21.0 version: 6.21.0(eslint@8.57.0)(typescript@5.3.3) '@vitejs/plugin-react-swc': specifier: 3.6.0 - version: 3.6.0(vite@5.2.10) + version: 3.6.0(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)) dotenv: specifier: 16.4.5 version: 16.4.5 @@ -178,10 +178,10 @@ importers: version: 8.57.0 eslint-config-airbnb: specifier: 19.0.4 - version: 19.0.4(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@8.57.0) + version: 19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.0(eslint@8.57.0))(eslint-plugin-react@7.34.1(eslint@8.57.0))(eslint@8.57.0) eslint-config-airbnb-typescript: specifier: 18.0.0 - version: 18.0.0(@typescript-eslint/eslint-plugin@6.21.0)(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + version: 18.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0)(typescript@5.3.3))(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) eslint-config-prettier: specifier: 9.1.0 version: 9.1.0(eslint@8.57.0) @@ -190,10 +190,10 @@ importers: version: 0.3.9 eslint-import-resolver-typescript: specifier: 3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + version: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: specifier: 2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jest-dom: specifier: 5.4.0 version: 5.4.0(@testing-library/dom@10.0.0)(eslint@8.57.0) @@ -202,7 +202,7 @@ importers: version: 6.8.0(eslint@8.57.0) eslint-plugin-prettier: specifier: 5.1.3 - version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) + version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) eslint-plugin-react: specifier: 7.34.1 version: 7.34.1(eslint@8.57.0) @@ -211,10 +211,10 @@ importers: version: 4.6.0(eslint@8.57.0) fetch-mock: specifier: 9.11.0 - version: 9.11.0 + version: 9.11.0(node-fetch@2.6.7) jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + version: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) jest-environment-jsdom: specifier: 29.7.0 version: 29.7.0 @@ -223,10 +223,10 @@ importers: version: 2.0.0 jest-styled-components: specifier: 7.1.1 - version: 7.1.1(styled-components@6.1.8) + version: 7.1.1(styled-components@6.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)) jest-watch-typeahead: specifier: 2.2.2 - version: 2.2.2(jest@29.7.0) + version: 2.2.2(jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3))) prettier: specifier: 3.2.5 version: 3.2.5 @@ -247,13 +247,13 @@ importers: version: 5.2.10(@types/node@20.11.17)(sass@1.66.1) vite-plugin-checker: specifier: 0.6.4 - version: 0.6.4(eslint@8.57.0)(typescript@5.3.3)(vite@5.2.10) + version: 0.6.4(eslint@8.57.0)(optionator@0.9.3)(typescript@5.3.3)(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)) vite-plugin-ejs: specifier: 1.7.0 - version: 1.7.0(vite@5.2.10) + version: 1.7.0(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)) vite-tsconfig-paths: specifier: 4.3.2 - version: 4.3.2(typescript@5.3.3)(vite@5.2.10) + version: 4.3.2(typescript@5.3.3)(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)) whatwg-fetch: specifier: 3.6.20 version: 3.6.20 @@ -627,15 +627,30 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/react@0.26.13': resolution: {integrity: sha512-kBa9wntpugzrZ8t/4yWelvSmEKZdeTXTJzrxqyrLmcU/n1SM4nvse8yQh2e1b37rJGvtu0EplV9+IkBrCJ1vkw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react@0.26.24': + resolution: {integrity: sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/utils@0.2.1': resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@hookform/error-message@2.0.1': resolution: {integrity: sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==} peerDependencies: @@ -1621,8 +1636,8 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - clsx@2.1.0: - resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} co@4.6.0: @@ -3323,8 +3338,8 @@ packages: react: ^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-datepicker@6.9.0: - resolution: {integrity: sha512-QTxuzeem7BUfVFWv+g5WuvzT0c5BPo+XTCNbMTZKSZQLU+cMMwSUHwspaxuIcDlwNcOH0tiJ+bh1fJ2yxOGYWA==} + react-datepicker@7.4.0: + resolution: {integrity: sha512-vSSok4DTZ9/Os8O4HjZLxh4SZVFU6dQvoCX6mfbNdBqMsBBdzftrvMz0Nb4UUVVbgj9o8PfX84K3/31oPrTqmg==} peerDependencies: react: ^16.9.0 || ^17 || ^18 react-dom: ^16.9.0 || ^17 || ^18 @@ -3367,12 +3382,6 @@ packages: react: ^16 || ^17 || ^18 react-dom: ^16 || ^17 || ^18 - react-onclickoutside@6.13.0: - resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==} - peerDependencies: - react: ^15.5.x || ^16.x || ^17.x || ^18.x - react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x - react-router-dom@6.23.0: resolution: {integrity: sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==} engines: {node: '>=14.0.0'} @@ -4548,31 +4557,47 @@ snapshots: '@floating-ui/dom@1.6.3': dependencies: '@floating-ui/core': 1.2.1 - '@floating-ui/utils': 0.2.1 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@floating-ui/dom': 1.6.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) - '@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0)': + '@floating-ui/react-dom@2.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@floating-ui/dom': 1.6.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@floating-ui/react@0.26.13(react-dom@18.2.0)(react@18.2.0)': + '@floating-ui/react@0.26.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@floating-ui/utils': 0.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) tabbable: 6.1.1 + '@floating-ui/react@0.26.24(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@floating-ui/utils': 0.2.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tabbable: 6.1.1 + '@floating-ui/utils@0.2.1': {} - '@hookform/error-message@2.0.1(react-dom@18.2.0)(react-hook-form@7.51.3)(react@18.2.0)': + '@floating-ui/utils@0.2.8': {} + + '@hookform/error-message@2.0.1(react-dom@18.2.0(react@18.2.0))(react-hook-form@7.51.3(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-hook-form: 7.51.3(react@18.2.0) - '@hookform/resolvers@2.7.1(react-hook-form@7.51.3)': + '@hookform/resolvers@2.7.1(react-hook-form@7.51.3(react@18.2.0))': dependencies: react-hook-form: 7.51.3(react@18.2.0) @@ -4625,7 +4650,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2)': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -4639,7 +4664,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4820,7 +4845,7 @@ snapshots: '@microsoft/fetch-event-source@2.0.1': {} - '@nestjs/axios@3.0.2(@nestjs/common@10.3.0)(axios@1.6.8)(rxjs@7.8.1)': + '@nestjs/axios@3.0.2(@nestjs/common@10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1) axios: 1.6.8 @@ -4834,7 +4859,7 @@ snapshots: tslib: 2.6.2 uid: 2.0.2 - '@nestjs/core@10.3.0(@nestjs/common@10.3.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)': + '@nestjs/core@10.3.0(@nestjs/common@10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1))(reflect-metadata@0.1.13)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 @@ -4870,9 +4895,9 @@ snapshots: '@openapitools/openapi-generator-cli@2.13.4': dependencies: - '@nestjs/axios': 3.0.2(@nestjs/common@10.3.0)(axios@1.6.8)(rxjs@7.8.1) + '@nestjs/axios': 3.0.2(@nestjs/common@10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1) '@nestjs/common': 10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1))(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 axios: 1.6.8 chalk: 4.1.2 @@ -5022,12 +5047,12 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@szhsin/react-menu@3.5.3(react-dom@18.2.0)(react@18.2.0)': + '@szhsin/react-menu@3.5.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-transition-state: 1.1.5(react-dom@18.2.0)(react@18.2.0) + react-transition-state: 1.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tanstack/match-sorter-utils@8.15.1': dependencies: @@ -5035,23 +5060,24 @@ snapshots: '@tanstack/query-core@4.36.1': {} - '@tanstack/react-query-devtools@4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0)': + '@tanstack/react-query-devtools@4.36.1(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/match-sorter-utils': 8.15.1 - '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) superjson: 1.13.3 use-sync-external-store: 1.2.0(react@18.2.0) - '@tanstack/react-query@4.36.1(react-dom@18.2.0)(react@18.2.0)': + '@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/query-core': 4.36.1 react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0) + optionalDependencies: + react-dom: 18.2.0(react@18.2.0) - '@tanstack/react-table@8.16.0(react-dom@18.2.0)(react@18.2.0)': + '@tanstack/react-table@8.16.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/table-core': 8.16.0 react: 18.2.0 @@ -5081,7 +5107,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.2(jest@29.7.0)': + '@testing-library/jest-dom@6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)))': dependencies: '@adobe/css-tools': 4.3.3 '@babel/runtime': 7.22.11 @@ -5089,11 +5115,14 @@ snapshots: chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) lodash: 4.17.21 redent: 3.0.0 + optionalDependencies: + '@jest/globals': 29.7.0 + '@types/jest': 29.5.12 + jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) - '@testing-library/react@14.3.1(react-dom@18.2.0)(react@18.2.0)': + '@testing-library/react@14.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.22.11 '@testing-library/dom': 9.3.1 @@ -5195,9 +5224,9 @@ snapshots: '@types/prop-types@15.7.5': {} - '@types/react-datepicker@6.2.0(react-dom@18.2.0)(react@18.2.0)': + '@types/react-datepicker@6.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@floating-ui/react': 0.26.13(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/react': 0.26.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@types/react': 18.2.79 date-fns: 3.6.0 transitivePeerDependencies: @@ -5248,7 +5277,7 @@ snapshots: dependencies: '@types/yargs-parser': 20.2.0 - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.8.0 '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.3.3) @@ -5263,6 +5292,7 @@ snapshots: natural-compare: 1.4.0 semver: 7.5.4 ts-api-utils: 1.3.0(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -5275,6 +5305,7 @@ snapshots: '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.4 eslint: 8.57.0 + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -5291,6 +5322,7 @@ snapshots: debug: 4.3.4 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -5307,6 +5339,7 @@ snapshots: minimatch: 9.0.3 semver: 7.5.4 ts-api-utils: 1.3.0(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -5332,7 +5365,7 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.6.0(vite@5.2.10)': + '@vitejs/plugin-react-swc@3.6.0(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1))': dependencies: '@swc/core': 1.3.107 vite: 5.2.10(@types/node@20.11.17)(sass@1.66.1) @@ -5373,7 +5406,7 @@ snapshots: - supports-color ajv-formats@2.1.1(ajv@8.8.2): - dependencies: + optionalDependencies: ajv: 8.8.2 ajv@6.12.6: @@ -5726,7 +5759,7 @@ snapshots: clone@1.0.4: {} - clsx@2.1.0: {} + clsx@2.1.1: {} co@4.6.0: {} @@ -5797,13 +5830,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -6190,29 +6223,29 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) object.assign: 4.1.4 object.entries: 1.1.7 semver: 6.3.1 - eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@6.21.0)(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0)(typescript@5.3.3))(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0)(typescript@5.3.3) '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.3.3) eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - eslint-plugin-import - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@8.57.0): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.0(eslint@8.57.0))(eslint-plugin-react@7.34.1(eslint@8.57.0))(eslint@8.57.0): dependencies: eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -6231,13 +6264,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.4 enhanced-resolve: 5.15.0 eslint: 8.57.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -6248,19 +6281,19 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.3.3) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.3.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.3.3) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 @@ -6269,7 +6302,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -6279,6 +6312,8 @@ snapshots: object.values: 1.1.7 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -6287,9 +6322,10 @@ snapshots: eslint-plugin-jest-dom@5.4.0(@testing-library/dom@10.0.0)(eslint@8.57.0): dependencies: '@babel/runtime': 7.22.11 - '@testing-library/dom': 10.0.0 eslint: 8.57.0 requireindex: 1.2.0 + optionalDependencies: + '@testing-library/dom': 10.0.0 eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0): dependencies: @@ -6311,13 +6347,14 @@ snapshots: object.entries: 1.1.7 object.fromentries: 2.0.7 - eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5): + eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5): dependencies: eslint: 8.57.0 - eslint-config-prettier: 9.1.0(eslint@8.57.0) prettier: 3.2.5 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-plugin-react-hooks@4.6.0(eslint@8.57.0): dependencies: @@ -6469,7 +6506,7 @@ snapshots: dependencies: bser: 2.1.1 - fetch-mock@9.11.0: + fetch-mock@9.11.0(node-fetch@2.6.7): dependencies: '@babel/core': 7.18.9 '@babel/runtime': 7.22.11 @@ -6481,6 +6518,8 @@ snapshots: path-to-regexp: 2.4.0 querystring: 0.2.1 whatwg-url: 6.5.0 + optionalDependencies: + node-fetch: 2.6.7 transitivePeerDependencies: - supports-color @@ -7040,16 +7079,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + jest-cli@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.5.1 @@ -7059,12 +7098,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)): dependencies: '@babel/core': 7.18.9 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 babel-jest: 29.7.0(@babel/core@7.18.9) chalk: 4.1.2 ci-info: 3.3.1 @@ -7084,6 +7122,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.11.17 ts-node: 10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros @@ -7193,7 +7233,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.2(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -7299,10 +7339,10 @@ snapshots: dependencies: xml: 1.0.1 - jest-styled-components@7.1.1(styled-components@6.1.8): + jest-styled-components@7.1.1(styled-components@6.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)): dependencies: '@adobe/css-tools': 4.3.3 - styled-components: 6.1.8(react-dom@18.2.0)(react@18.2.0) + styled-components: 6.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) jest-util@29.6.3: dependencies: @@ -7331,11 +7371,11 @@ snapshots: leven: 3.1.0 pretty-format: 29.7.0 - jest-watch-typeahead@2.2.2(jest@29.7.0): + jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3))): dependencies: ansi-escapes: 6.0.0 chalk: 5.2.0 - jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) jest-regex-util: 29.6.3 jest-watcher: 29.6.4 slash: 5.0.0 @@ -7371,12 +7411,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7849,7 +7889,7 @@ snapshots: queue-microtask@1.2.3: {} - react-ace@11.0.1(react-dom@18.2.0)(react@18.2.0): + react-ace@11.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: ace-builds: 1.33.0 diff-match-patch: 1.0.5 @@ -7859,15 +7899,14 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-datepicker@6.9.0(react-dom@18.2.0)(react@18.2.0): + react-datepicker@7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: - '@floating-ui/react': 0.26.13(react-dom@18.2.0)(react@18.2.0) - clsx: 2.1.0 + '@floating-ui/react': 0.26.24(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + clsx: 2.1.1 date-fns: 3.6.0 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-onclickoutside: 6.13.0(react-dom@18.2.0)(react@18.2.0) react-dom@18.2.0(react@18.2.0): dependencies: @@ -7884,7 +7923,7 @@ snapshots: dependencies: react: 18.2.0 - react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0): + react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: goober: 2.1.10(csstype@3.1.2) react: 18.2.0 @@ -7898,17 +7937,12 @@ snapshots: react-is@18.2.0: {} - react-multi-select-component@4.3.4(react-dom@18.2.0)(react@18.2.0): - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - - react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0): + react-multi-select-component@4.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router-dom@6.23.0(react-dom@18.2.0)(react@18.2.0): + react-router-dom@6.23.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@remix-run/router': 1.16.0 react: 18.2.0 @@ -7920,7 +7954,7 @@ snapshots: '@remix-run/router': 1.16.0 react: 18.2.0 - react-transition-state@1.1.5(react-dom@18.2.0)(react@18.2.0): + react-transition-state@1.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -8276,7 +8310,7 @@ snapshots: strip-json-comments@3.1.1: {} - styled-components@6.1.8(react-dom@18.2.0)(react@18.2.0): + styled-components@6.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@emotion/is-prop-valid': 1.2.1 '@emotion/unitless': 0.8.0 @@ -8382,7 +8416,6 @@ snapshots: ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.11.17)(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 - '@swc/core': 1.3.107 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 @@ -8397,6 +8430,8 @@ snapshots: typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.3.107 ts-prune@0.10.3: dependencies: @@ -8408,7 +8443,7 @@ snapshots: ts-morph: 13.0.3 tsconfck@3.0.3(typescript@5.3.3): - dependencies: + optionalDependencies: typescript: 5.3.3 tsconfig-paths@3.15.0: @@ -8547,37 +8582,40 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.3 convert-source-map: 1.7.0 - vite-plugin-checker@0.6.4(eslint@8.57.0)(typescript@5.3.3)(vite@5.2.10): + vite-plugin-checker@0.6.4(eslint@8.57.0)(optionator@0.9.3)(typescript@5.3.3)(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)): dependencies: '@babel/code-frame': 7.23.5 ansi-escapes: 4.3.2 chalk: 4.1.2 chokidar: 3.5.2 commander: 8.3.0 - eslint: 8.57.0 fast-glob: 3.3.2 fs-extra: 11.2.0 npm-run-path: 4.0.1 semver: 7.5.4 strip-ansi: 6.0.1 tiny-invariant: 1.3.3 - typescript: 5.3.3 vite: 5.2.10(@types/node@20.11.17)(sass@1.66.1) vscode-languageclient: 7.0.0 vscode-languageserver: 7.0.0 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 + optionalDependencies: + eslint: 8.57.0 + optionator: 0.9.3 + typescript: 5.3.3 - vite-plugin-ejs@1.7.0(vite@5.2.10): + vite-plugin-ejs@1.7.0(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)): dependencies: ejs: 3.1.10 vite: 5.2.10(@types/node@20.11.17)(sass@1.66.1) - vite-tsconfig-paths@4.3.2(typescript@5.3.3)(vite@5.2.10): + vite-tsconfig-paths@4.3.2(typescript@5.3.3)(vite@5.2.10(@types/node@20.11.17)(sass@1.66.1)): dependencies: debug: 4.3.4 globrex: 0.1.2 tsconfck: 3.0.3(typescript@5.3.3) + optionalDependencies: vite: 5.2.10(@types/node@20.11.17)(sass@1.66.1) transitivePeerDependencies: - supports-color @@ -8585,13 +8623,13 @@ snapshots: vite@5.2.10(@types/node@20.11.17)(sass@1.66.1): dependencies: - '@types/node': 20.11.17 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.16.1 - sass: 1.66.1 optionalDependencies: + '@types/node': 20.11.17 fsevents: 2.3.3 + sass: 1.66.1 vscode-jsonrpc@6.0.0: {} @@ -8790,6 +8828,7 @@ snapshots: zustand@4.5.2(@types/react@18.2.79)(react@18.2.0): dependencies: + use-sync-external-store: 1.2.0(react@18.2.0) + optionalDependencies: '@types/react': 18.2.79 react: 18.2.0 - use-sync-external-store: 1.2.0(react@18.2.0) diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx index 1ddfa2b03..0fd2bad12 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx @@ -31,8 +31,10 @@ const InputCellViewMode: FC = ({ ); return ( - - {displayValue} + + + {displayValue} + ` +export const ValueWrapper = styled.div` display: flex; justify-content: space-between; - font-weight: ${({ $isDynamic }) => ($isDynamic ? 600 : 400)}; + font-weight: 400; button { margin: 0 10px; } `; -export const Value = styled.span` +export const Value = styled.span<{ $isDynamic?: boolean }>` line-height: 24px; margin-right: 10px; text-overflow: ellipsis; max-width: 400px; overflow: hidden; white-space: nowrap; + font-weight: ${({ $isDynamic }) => ($isDynamic ? 600 : 400)}; `; export const ButtonsWrapper = styled.div` diff --git a/frontend/src/components/ConsumerGroups/Details/ListItem.tsx b/frontend/src/components/ConsumerGroups/Details/ListItem.tsx index 57b7cb133..21560e9ca 100644 --- a/frontend/src/components/ConsumerGroups/Details/ListItem.tsx +++ b/frontend/src/components/ConsumerGroups/Details/ListItem.tsx @@ -1,8 +1,16 @@ import React from 'react'; -import { ConsumerGroupTopicPartition } from 'generated-sources'; +import { + Action, + ConsumerGroupTopicPartition, + ResourceType, +} from 'generated-sources'; import { Link } from 'react-router-dom'; import { ClusterName } from 'lib/interfaces/cluster'; -import { clusterTopicPath } from 'lib/paths'; +import { ClusterGroupParam, clusterTopicPath } from 'lib/paths'; +import { useDeleteConsumerGroupOffsetsMutation } from 'lib/hooks/api/consumers'; +import useAppParams from 'lib/hooks/useAppParams'; +import { Dropdown } from 'components/common/Dropdown'; +import { ActionDropdownItem } from 'components/common/ActionComponent'; import MessageToggleIcon from 'components/common/Icons/MessageToggleIcon'; import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper'; import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled'; @@ -18,6 +26,9 @@ interface Props { const ListItem: React.FC = ({ clusterName, name, consumers }) => { const [isOpen, setIsOpen] = React.useState(false); + const consumerProps = useAppParams(); + const deleteOffsetMutation = + useDeleteConsumerGroupOffsetsMutation(consumerProps); const getTotalconsumerLag = () => { let count = 0; @@ -27,6 +38,11 @@ const ListItem: React.FC = ({ clusterName, name, consumers }) => { return count; }; + const deleteOffsetHandler = (topicName?: string) => { + if (topicName === undefined) return; + deleteOffsetMutation.mutateAsync(topicName); + }; + return ( <> @@ -41,6 +57,22 @@ const ListItem: React.FC = ({ clusterName, name, consumers }) => { {getTotalconsumerLag()} + + + deleteOffsetHandler(name)} + danger + confirm="Are you sure you want to delete offsets from the topic?" + permission={{ + resource: ResourceType.CONSUMER, + action: Action.RESET_OFFSETS, + value: consumerProps.consumerGroupID, + }} + > + Delete offsets + + + {isOpen && } diff --git a/frontend/src/components/PageContainer/PageContainer.tsx b/frontend/src/components/PageContainer/PageContainer.tsx index 9d209e032..4459de6a5 100644 --- a/frontend/src/components/PageContainer/PageContainer.tsx +++ b/frontend/src/components/PageContainer/PageContainer.tsx @@ -1,6 +1,7 @@ import React, { type FC, type PropsWithChildren, + Suspense, useEffect, useMemo, } from 'react'; @@ -15,6 +16,7 @@ import { useClusters } from 'lib/hooks/api/clusters'; import { ResourceType } from 'generated-sources'; import { useGetUserInfo } from 'lib/hooks/api/roles'; import { useScreenSize } from 'lib/hooks/useScreenSize'; +import PageLoader from 'components/common/PageLoader/PageLoader'; const PageContainer: FC = ({ children }) => { const { isLarge } = useScreenSize(); @@ -62,7 +64,7 @@ const PageContainer: FC = ({ children }) => { aria-hidden="true" aria-label="Overlay" /> - {children} + }>{children} ); diff --git a/frontend/src/components/common/Editor/Editor.tsx b/frontend/src/components/common/Editor/Editor.tsx index 05c91e355..42ee7c93b 100644 --- a/frontend/src/components/common/Editor/Editor.tsx +++ b/frontend/src/components/common/Editor/Editor.tsx @@ -1,4 +1,5 @@ import AceEditor, { IAceEditorProps } from 'react-ace'; +import 'ace-builds/src-noconflict/ext-searchbox'; import 'ace-builds/src-noconflict/mode-json5'; import 'ace-builds/src-noconflict/mode-protobuf'; import 'ace-builds/src-noconflict/theme-tomorrow'; diff --git a/frontend/src/components/common/NewTable/Table.styled.ts b/frontend/src/components/common/NewTable/Table.styled.ts index 5db0ba806..3cb247654 100644 --- a/frontend/src/components/common/NewTable/Table.styled.ts +++ b/frontend/src/components/common/NewTable/Table.styled.ts @@ -152,6 +152,7 @@ export const Table = styled.table( color: ${table.td.color.normal}; vertical-align: middle; word-wrap: break-word; + white-space: pre; & a { color: ${table.td.color.normal}; diff --git a/frontend/src/lib/hooks/api/consumers.ts b/frontend/src/lib/hooks/api/consumers.ts index 85ae048a9..f2373e989 100644 --- a/frontend/src/lib/hooks/api/consumers.ts +++ b/frontend/src/lib/hooks/api/consumers.ts @@ -90,3 +90,30 @@ export const useResetConsumerGroupOffsetsMutation = ({ } ); }; + +export const useDeleteConsumerGroupOffsetsMutation = ({ + clusterName, + consumerGroupID, +}: UseConsumerGroupDetailsProps) => { + const queryClient = useQueryClient(); + return useMutation( + (topicName: string) => + api.deleteConsumerGroupOffsets({ + clusterName, + id: consumerGroupID, + topicName, + }), + { + onSuccess: (_, topicName) => { + showSuccessAlert({ + message: `Consumer ${consumerGroupID} group offsets in topic ${topicName} deleted`, + }); + queryClient.invalidateQueries([ + 'clusters', + clusterName, + 'consumerGroups', + ]); + }, + } + ); +}; diff --git a/frontend/src/lib/hooks/filterUtils.ts b/frontend/src/lib/hooks/filterUtils.ts index 9e9d23fc6..5b3bcd2f0 100644 --- a/frontend/src/lib/hooks/filterUtils.ts +++ b/frontend/src/lib/hooks/filterUtils.ts @@ -1,8 +1,8 @@ import { PollingMode } from 'generated-sources'; export const ModeOptions = [ - { value: PollingMode.EARLIEST, label: 'Oldest' }, { value: PollingMode.LATEST, label: 'Newest' }, + { value: PollingMode.EARLIEST, label: 'Oldest' }, { value: PollingMode.TAILING, label: 'Live' }, { value: PollingMode.FROM_OFFSET, label: 'From offset' }, { value: PollingMode.TO_OFFSET, label: 'To offset' }, diff --git a/pom.xml b/pom.xml index 75cae5bfc..e38658fbb 100644 --- a/pom.xml +++ b/pom.xml @@ -33,30 +33,30 @@ 4.12.0 2.12.0 3.25.3 - 1.11.3 + 1.11.4 1.12.19 7.4.4 3.1.0 3.0.13 2.14.0 3.5.2 - 1.5.5.Final - 1.18.32 + 1.6.2 + 1.18.34 3.25.5 2.13.9 - 2.2 - 3.1.9 + 2.3 + 3.3.4 1.0.0 0.1.17 0.1.39 - 20231013 + 20240303 0.3.0 - 33.0.0-jre + 33.3.1-jre - 5.9.1 + 5.11.2 5.11.0 4.12.0 - 1.19.5 + 1.20.2 v18.17.1