소스 검색

Merge branch 'master' of github.com:provectus/kafka-ui into audit_be

 Conflicts:
	kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/TopicsController.java
	kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java
	kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/AccessControlServiceMock.java
iliax 2 년 전
부모
커밋
e8b9bf724b
52개의 변경된 파일247개의 추가작업 그리고 1027개의 파일을 삭제
  1. 2 2
      .github/CODEOWNERS
  2. 3 0
      .github/ISSUE_TEMPLATE/config.yml
  3. 0 92
      .github/ISSUE_TEMPLATE/helm.yml
  4. 1 1
      .github/workflows/block_merge.yml
  5. 14 10
      .github/workflows/branch-deploy.yml
  6. 0 6
      .github/workflows/branch-remove.yml
  7. 1 2
      .github/workflows/build-public-image.yml
  8. 0 28
      .github/workflows/create-branch-for-helm.yaml
  9. 1 1
      .github/workflows/cve.yaml
  10. 0 6
      .github/workflows/delete-public-image.yml
  11. 0 38
      .github/workflows/helm.yaml
  12. 1 1
      .github/workflows/pr-checks.yaml
  13. 0 39
      .github/workflows/release-helm.yaml
  14. 3 5
      .github/workflows/release.yaml
  15. 0 25
      charts/kafka-ui/.helmignore
  16. 0 7
      charts/kafka-ui/Chart.yaml
  17. 0 1
      charts/kafka-ui/README.md
  18. 0 3
      charts/kafka-ui/index.yaml
  19. 0 21
      charts/kafka-ui/templates/NOTES.txt
  20. 0 84
      charts/kafka-ui/templates/_helpers.tpl
  21. 0 10
      charts/kafka-ui/templates/configmap.yaml
  22. 0 11
      charts/kafka-ui/templates/configmap_fromValues.yaml
  23. 0 150
      charts/kafka-ui/templates/deployment.yaml
  24. 0 46
      charts/kafka-ui/templates/hpa.yaml
  25. 0 89
      charts/kafka-ui/templates/ingress.yaml
  26. 0 18
      charts/kafka-ui/templates/networkpolicy-egress.yaml
  27. 0 18
      charts/kafka-ui/templates/networkpolicy-ingress.yaml
  28. 0 13
      charts/kafka-ui/templates/secret.yaml
  29. 0 22
      charts/kafka-ui/templates/service.yaml
  30. 0 12
      charts/kafka-ui/templates/serviceaccount.yaml
  31. 0 161
      charts/kafka-ui/values.yaml
  32. 12 5
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaConfigSanitizer.java
  33. 4 15
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaConnectService.java
  34. 1 0
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java
  35. 24 4
      kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/KafkaConfigSanitizerTest.java
  36. 1 1
      kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/AccessControlServiceMock.java
  37. 1 1
      kafka-ui-react-app/package.json
  38. 7 7
      kafka-ui-react-app/pnpm-lock.yaml
  39. 5 3
      kafka-ui-react-app/src/components/App.tsx
  40. 3 0
      kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx
  41. 1 1
      kafka-ui-react-app/src/components/ConsumerGroups/Details/ResetOffsets/ResetOffsets.tsx
  42. 6 42
      kafka-ui-react-app/src/components/NavBar/NavBar.tsx
  43. 27 6
      kafka-ui-react-app/src/components/PageContainer/PageContainer.tsx
  44. 44 13
      kafka-ui-react-app/src/components/PageContainer/__tests__/PageContainer.spec.tsx
  45. 1 0
      kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx
  46. 6 2
      kafka-ui-react-app/src/components/common/SQLEditor/SQLEditor.tsx
  47. 58 0
      kafka-ui-react-app/src/components/contexts/ThemeModeContext.tsx
  48. 4 1
      kafka-ui-react-app/src/index.tsx
  49. 7 1
      kafka-ui-react-app/src/lib/testHelpers.tsx
  50. 7 1
      kafka-ui-react-app/src/widgets/ClusterConfigForm/utils/getJaasConfig.ts
  51. 1 1
      kafka-ui-react-app/src/widgets/ClusterConfigForm/utils/transformFormDataToPayload.ts
  52. 1 1
      kafka-ui-serde-api/pom.xml

+ 2 - 2
.github/CODEOWNERS

@@ -14,5 +14,5 @@
 # TESTS
 /kafka-ui-e2e-checks/       @provectus/kafka-qa
 
-# HELM CHARTS
-/charts/                    @provectus/kafka-devops
+# INFRA
+/.github/workflows/         @provectus/kafka-devops

+ 3 - 0
.github/ISSUE_TEMPLATE/config.yml

@@ -1,5 +1,8 @@
 blank_issues_enabled: false
 contact_links:
+  - name: Report helm issue
+    url: https://github.com/provectus/kafka-ui-charts
+    about: Our helm charts are located in another repo. Please raise issues/PRs regarding charts in that repo.
   - name: Official documentation
     url: https://docs.kafka-ui.provectus.io/
     about: Before reaching out for support, please refer to our documentation. Read "FAQ" and "Common problems", also try using search there.

+ 0 - 92
.github/ISSUE_TEMPLATE/helm.yml

@@ -1,92 +0,0 @@
-name: "⎈ K8s/Helm problem report"
-description: "Report a problem with k8s/helm charts/etc"
-labels: ["status/triage", "scope/k8s"]
-assignees: []
-
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Hi, thanks for raising the issue(-s), all contributions really matter!
-        Please, note that we'll close the issue without further explanation if you don't follow
-        this template and don't provide the information requested within this template.
-
-  - type: checkboxes
-    id: terms
-    attributes:
-      label: Issue submitter TODO list
-      description: By you checking these checkboxes we can be sure you've done the essential things.
-      options:
-        - label: I've looked up my issue in [FAQ](https://docs.kafka-ui.provectus.io/faq/common-problems)
-          required: true
-        - label: I've searched for an already existing issues [here](https://github.com/provectus/kafka-ui/issues)
-          required: true
-        - label: I've tried running `master`-labeled docker image and the issue still persists there
-          required: true
-        - label: I'm running a supported version of the application which is listed [here](https://github.com/provectus/kafka-ui/blob/master/SECURITY.md)
-          required: true
-
-  - type: textarea
-    attributes:
-      label: Describe the bug (actual behavior)
-      description: A clear and concise description of what the bug is. Use a list, if there is more than one problem
-    validations:
-      required: true
-
-  - type: textarea
-    attributes:
-      label: Expected behavior
-      description: A clear and concise description of what you expected to happen
-    validations:
-      required: false
-
-  - type: textarea
-    attributes:
-      label: Your installation details
-      description: |
-        How do you run the app? Please provide as much info as possible:
-        1. App version (commit hash in the top left corner of the UI)
-        2. Helm chart version
-        3. Your application config. Please remove the sensitive info like passwords or API keys.
-        4. Any IAAC configs
-    validations:
-      required: true
-
-  - type: textarea
-    attributes:
-      label: Steps to reproduce
-      description: |
-        Please write down the order of the actions required to reproduce the issue.
-        For the advanced setups/complicated issue, we might need you to provide
-        a minimal [reproducible example](https://stackoverflow.com/help/minimal-reproducible-example).
-    validations:
-      required: true
-
-  - type: textarea
-    attributes:
-      label: Screenshots
-      description: |
-        If applicable, add screenshots to help explain your problem
-    validations:
-      required: false
-
-  - type: textarea
-    attributes:
-      label: Logs
-      description: |
-        If applicable, *upload* screenshots to help explain your problem
-    validations:
-      required: false
-
-  - type: textarea
-    attributes:
-      label: Additional context
-      description: |
-        Add any other context about the problem here. E.G.:
-        1. Are there any alternative scenarios (different data/methods/configuration/setup) you have tried?
-          Were they successful or the same issue occurred? Please provide steps as well.
-        2. Related issues (if there are any).
-        3. Logs (if available)
-        4. Is there any serious impact or behaviour on the end-user because of this issue, that can be overlooked?
-    validations:
-      required: false

+ 1 - 1
.github/workflows/block_merge.yml

@@ -6,7 +6,7 @@ jobs:
   block_merge:
     runs-on: ubuntu-latest
     steps:
-      - uses: mheap/github-action-required-labels@v4
+      - uses: mheap/github-action-required-labels@v5
         with:
           mode: exactly
           count: 0

+ 14 - 10
.github/workflows/branch-deploy.yml

@@ -84,18 +84,22 @@ jobs:
           git add ../kafka-ui-from-branch/
           git commit -m "added env:${{ needs.build.outputs.deploy }}" && git push || true
 
-      - name: make comment with private deployment link
+      - name: update status check for private deployment
         if: ${{ github.event.label.name == 'status/feature_testing' }}
-        uses: peter-evans/create-or-update-comment@v3
+        uses: Sibz/github-status-action@v1.1.6
         with:
-          issue-number: ${{ github.event.pull_request.number }}
-          body: |
-            Custom deployment will be available at http://${{ needs.build.outputs.tag }}.internal.kafka-ui.provectus.io
+          authToken: ${{secrets.GITHUB_TOKEN}}
+          context: "Click Details button to open custom deployment page"
+          state: "success"
+          sha: ${{ github.event.pull_request.head.sha  || github.sha }}
+          target_url: "http://${{ needs.build.outputs.tag }}.internal.kafka-ui.provectus.io"
 
-      - name: make comment with public deployment link
+      - name: update status check for public deployment
         if: ${{ github.event.label.name == 'status/feature_testing_public' }}
-        uses: peter-evans/create-or-update-comment@v3
+        uses: Sibz/github-status-action@v1.1.6
         with:
-          issue-number: ${{ github.event.pull_request.number }}
-          body: |
-            Custom deployment will be available at http://${{ needs.build.outputs.tag }}.kafka-ui.provectus.io in 5 minutes
+          authToken: ${{secrets.GITHUB_TOKEN}}
+          context: "Click Details button to open custom deployment page"
+          state: "success"
+          sha: ${{ github.event.pull_request.head.sha  || github.sha }}
+          target_url: "http://${{ needs.build.outputs.tag }}.internal.kafka-ui.provectus.io"

+ 0 - 6
.github/workflows/branch-remove.yml

@@ -20,9 +20,3 @@ jobs:
           git config --global user.name "infra-tech"
           git add ../kafka-ui-from-branch/
           git commit -m "removed env:${{ needs.build.outputs.deploy }}" && git push || true
-      - name: make comment with deployment link
-        uses: peter-evans/create-or-update-comment@v3
-        with:
-          issue-number: ${{ github.event.pull_request.number }}
-          body: |
-            Custom deployment removed

+ 1 - 2
.github/workflows/build-public-image.yml

@@ -65,11 +65,10 @@ jobs:
           cache-from: type=local,src=/tmp/.buildx-cache
           cache-to: type=local,dest=/tmp/.buildx-cache
       - name: make comment with private deployment link
-        uses: peter-evans/create-or-update-comment@v3
+        uses: peter-evans/create-or-update-comment@v2
         with:
           issue-number: ${{ github.event.pull_request.number }}
           body: |
             Image published at public.ecr.aws/provectus/kafka-ui-custom-build:${{ steps.extract_branch.outputs.tag }}
-
     outputs:
       tag: ${{ steps.extract_branch.outputs.tag }}

+ 0 - 28
.github/workflows/create-branch-for-helm.yaml

@@ -1,28 +0,0 @@
-name: Prepare helm release
-on:
-  repository_dispatch:
-    types: [prepare-helm-release]
-jobs:
-  change-app-version:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v3
-      - run: |
-          git config user.name github-actions
-          git config user.email github-actions@github.com
-      - name: Change versions
-        run: |
-          git checkout -b release-${{ github.event.client_payload.appversion}}
-          version=$(cat charts/kafka-ui/Chart.yaml  | grep version | awk '{print $2}')
-          version=${version%.*}.$((${version##*.}+1))
-          sed -i "s/version:.*/version: ${version}/" charts/kafka-ui/Chart.yaml
-          sed -i "s/appVersion:.*/appVersion: ${{ github.event.client_payload.appversion}}/" charts/kafka-ui/Chart.yaml
-          git add  charts/kafka-ui/Chart.yaml
-          git commit -m "release ${version}"
-          git push --set-upstream origin release-${{ github.event.client_payload.appversion}}
-      - name: Slack Notification
-        uses: rtCamp/action-slack-notify@v2
-        env:
-          SLACK_TITLE: "release-${{ github.event.client_payload.appversion}}"
-          SLACK_MESSAGE: "A new release of the helm chart has been prepared. Branch name: release-${{ github.event.client_payload.appversion}}"
-          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

+ 1 - 1
.github/workflows/cve.yaml

@@ -55,7 +55,7 @@ jobs:
           cache-to: type=local,dest=/tmp/.buildx-cache
 
       - name: Run CVE checks
-        uses: aquasecurity/trivy-action@0.10.0
+        uses: aquasecurity/trivy-action@0.11.2
         with:
           image-ref: "provectuslabs/kafka-ui:${{ steps.build.outputs.version }}"
           format: "table"

+ 0 - 6
.github/workflows/delete-public-image.yml

@@ -32,9 +32,3 @@ jobs:
                 --repository-name kafka-ui-custom-build \
                 --image-ids imageTag=${{ steps.extract_branch.outputs.tag }} \
                 --region us-east-1
-      - name: make comment with private deployment link
-        uses: peter-evans/create-or-update-comment@v3
-        with:
-          issue-number: ${{ github.event.pull_request.number }}
-          body: |
-            Image tag public.ecr.aws/provectus/kafka-ui-custom-build:${{ steps.extract_branch.outputs.tag }} has been removed

+ 0 - 38
.github/workflows/helm.yaml

@@ -1,38 +0,0 @@
-name: Helm linter
-on:
- pull_request:
-  types: ["opened", "edited", "reopened", "synchronize"]
-  branches:
-   - 'master'
-  paths:
-   - "charts/**"
-jobs:
-  build-and-test:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v3
-      - name: Helm tool installer
-        uses: Azure/setup-helm@v3
-      - name: Setup Kubeval
-        uses: lra/setup-kubeval@v1.0.1
-      #check, was helm version increased in Chart.yaml?
-      - name: Check version
-        shell: bash
-        run: |
-          helm_version_new=$(cat charts/kafka-ui/Chart.yaml  | grep version | awk  '{print $2}')
-          helm_version_old=$(curl -s https://raw.githubusercontent.com/provectus/kafka-ui/master/charts/kafka-ui/Chart.yaml |   grep version | awk  '{print $2}' )
-          echo $helm_version_old
-          echo $helm_version_new
-          if [[ "$helm_version_new" > "$helm_version_old" ]]; then exit 0 ; else exit 1 ; fi
-      - name: Run kubeval
-        shell: bash
-        run: |
-          sed -i "s@enabled: false@enabled: true@g" charts/kafka-ui/values.yaml
-          K8S_VERSIONS=$(git ls-remote --refs --tags https://github.com/kubernetes/kubernetes.git | cut -d/ -f3 | grep -e '^v1\.[0-9]\{2\}\.[0]\{1,2\}$' | grep -v -e  '^v1\.1[0-7]\{1\}' | cut -c2-)
-          echo "NEXT K8S VERSIONS ARE GOING TO BE TESTED: $K8S_VERSIONS"
-          echo ""
-          for version in $K8S_VERSIONS
-            do
-              echo $version;
-              helm template --kube-version $version --set ingress.enabled=true charts/kafka-ui -f charts/kafka-ui/values.yaml | kubeval --additional-schema-locations https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master --strict -v $version;
-            done

+ 1 - 1
.github/workflows/pr-checks.yaml

@@ -7,7 +7,7 @@ jobs:
   task-check:
     runs-on: ubuntu-latest
     steps:
-      - uses: kentaro-m/task-completed-checker-action@v0.1.1
+      - uses: kentaro-m/task-completed-checker-action@v0.1.2
         with:
           repo-token: "${{ secrets.GITHUB_TOKEN }}"
       - uses: dekinderfiets/pr-description-enforcer@0.0.1

+ 0 - 39
.github/workflows/release-helm.yaml

@@ -1,39 +0,0 @@
-name: Release helm
-on:
- push:
-    branches:
-     - master
-    paths:
-      - "charts/**"
-
-jobs:
- release-helm:
-  runs-on:
-   ubuntu-latest
-  steps:
-      - uses: actions/checkout@v3
-        with:
-          fetch-depth: 1
-
-      - run: |
-          git config user.name github-actions
-          git config user.email github-actions@github.com
-
-      - uses: azure/setup-helm@v3
-
-      - name: add chart #realse helm with new version
-        run: |
-          VERSION=$(cat charts/kafka-ui/Chart.yaml  | grep version | awk '{print $2}')
-          echo "HELM_VERSION=$(echo ${VERSION})" >> $GITHUB_ENV
-          MSG=$(helm package charts/kafka-ui)
-          git fetch origin
-          git stash
-          git checkout -b gh-pages origin/gh-pages
-          git pull
-          helm repo index .
-          git add -f ${MSG##*/} index.yaml
-          git commit -m "release ${VERSION}"
-          git push
-      - uses: rickstaa/action-create-tag@v1 #create new tag
-        with:
-          tag: "charts/kafka-ui-${{ env.HELM_VERSION }}"

+ 3 - 5
.github/workflows/release.yaml

@@ -34,7 +34,7 @@ jobs:
           echo "version=${VERSION}" >> $GITHUB_OUTPUT
 
       - name: Upload files to a GitHub release
-        uses: svenstaro/upload-release-action@2.5.0
+        uses: svenstaro/upload-release-action@2.6.1
         with:
           repo_token: ${{ secrets.GITHUB_TOKEN }}
           file: kafka-ui-api/target/kafka-ui-api-${{ steps.build.outputs.version }}.jar
@@ -89,14 +89,12 @@ jobs:
 
   charts:
     runs-on: ubuntu-latest
-    permissions:
-      contents: write
     needs: release
     steps:
       - name: Repository Dispatch
         uses: peter-evans/repository-dispatch@v2
         with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          repository: provectus/kafka-ui
+          token: ${{ secrets.CHARTS_ACTIONS_TOKEN }}
+          repository: provectus/kafka-ui-charts
           event-type: prepare-helm-release
           client-payload: '{"appversion": "${{ needs.release.outputs.version }}"}'

+ 0 - 25
charts/kafka-ui/.helmignore

@@ -1,25 +0,0 @@
-# Patterns to ignore when building packages.
-# This supports shell glob matching, relative path matching, and
-# negation (prefixed with !). Only one pattern per line.
-.DS_Store
-# Common VCS dirs
-.git/
-.gitignore
-.bzr/
-.bzrignore
-.hg/
-.hgignore
-.svn/
-# Common backup files
-*.swp
-*.bak
-*.tmp
-*.orig
-*~
-# Various IDEs
-.project
-.idea/
-*.tmproj
-.vscode/
-example/
-README.md

+ 0 - 7
charts/kafka-ui/Chart.yaml

@@ -1,7 +0,0 @@
-apiVersion: v2
-name: kafka-ui
-description: A Helm chart for kafka-UI
-type: application
-version: 0.7.0
-appVersion: v0.7.0
-icon: https://github.com/provectus/kafka-ui/raw/master/documentation/images/kafka-ui-logo.png

+ 0 - 1
charts/kafka-ui/README.md

@@ -1 +0,0 @@
-Please refer to our [documentation](https://docs.kafka-ui.provectus.io/configuration/helm-charts) to get some info on our helm charts.

+ 0 - 3
charts/kafka-ui/index.yaml

@@ -1,3 +0,0 @@
-apiVersion: v1
-entries: {}
-generated: "2021-11-11T12:26:08.479581+03:00"

+ 0 - 21
charts/kafka-ui/templates/NOTES.txt

@@ -1,21 +0,0 @@
-1. Get the application URL by running these commands:
-{{- if .Values.ingress.enabled }}
-{{- range $host := .Values.ingress.hosts }}
-  {{- range .paths }}
-  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
-  {{- end }}
-{{- end }}
-{{- else if contains "NodePort" .Values.service.type }}
-  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "kafka-ui.fullname" . }})
-  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
-  echo http://$NODE_IP:$NODE_PORT
-{{- else if contains "LoadBalancer" .Values.service.type }}
-     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
-           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "kafka-ui.fullname" . }}'
-  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "kafka-ui.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
-  echo http://$SERVICE_IP:{{ .Values.service.port }}
-{{- else if contains "ClusterIP" .Values.service.type }}
-  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "kafka-ui.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
-  echo "Visit http://127.0.0.1:8080 to use your application"
-  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:8080
-{{- end }}

+ 0 - 84
charts/kafka-ui/templates/_helpers.tpl

@@ -1,84 +0,0 @@
-{{/* vim: set filetype=mustache: */}}
-{{/*
-Expand the name of the chart.
-*/}}
-{{- define "kafka-ui.name" -}}
-{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
-{{- end }}
-
-{{/*
-Create a default fully qualified app name.
-We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
-If release name contains chart name it will be used as a full name.
-*/}}
-{{- define "kafka-ui.fullname" -}}
-{{- if .Values.fullnameOverride }}
-{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
-{{- else }}
-{{- $name := default .Chart.Name .Values.nameOverride }}
-{{- if contains $name .Release.Name }}
-{{- .Release.Name | trunc 63 | trimSuffix "-" }}
-{{- else }}
-{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
-{{- end }}
-{{- end }}
-{{- end }}
-
-{{/*
-Create chart name and version as used by the chart label.
-*/}}
-{{- define "kafka-ui.chart" -}}
-{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
-{{- end }}
-
-{{/*
-Common labels
-*/}}
-{{- define "kafka-ui.labels" -}}
-helm.sh/chart: {{ include "kafka-ui.chart" . }}
-{{ include "kafka-ui.selectorLabels" . }}
-{{- if .Chart.AppVersion }}
-app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
-{{- end }}
-app.kubernetes.io/managed-by: {{ .Release.Service }}
-{{- end }}
-
-{{/*
-Selector labels
-*/}}
-{{- define "kafka-ui.selectorLabels" -}}
-app.kubernetes.io/name: {{ include "kafka-ui.name" . }}
-app.kubernetes.io/instance: {{ .Release.Name }}
-{{- end }}
-
-{{/*
-Create the name of the service account to use
-*/}}
-{{- define "kafka-ui.serviceAccountName" -}}
-{{- if .Values.serviceAccount.create }}
-{{- default (include "kafka-ui.fullname" .) .Values.serviceAccount.name }}
-{{- else }}
-{{- default "default" .Values.serviceAccount.name }}
-{{- end }}
-{{- end }}
-
-
-{{/*
-This allows us to check if the registry of the image is specified or not.
-*/}}
-{{- define "kafka-ui.imageName" -}}
-{{- $registryName := .Values.image.registry -}}
-{{- if .Values.global }}
-    {{- if .Values.global.imageRegistry }}
-     {{- $registryName = .Values.global.imageRegistry -}}
-    {{- end -}}
-{{- end -}}
-{{- $repository := .Values.image.repository -}}
-{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
-{{- if $registryName }}
-{{- printf "%s/%s:%s" $registryName $repository $tag -}}
-{{- else }}
-{{- printf "%s:%s" $repository $tag -}}
-{{- end }}
-{{- end -}}
-

+ 0 - 10
charts/kafka-ui/templates/configmap.yaml

@@ -1,10 +0,0 @@
-{{- if .Values.envs.config -}}
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: {{ include "kafka-ui.fullname" . }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-data:
-  {{- toYaml .Values.envs.config | nindent 2 }}
-{{- end -}}

+ 0 - 11
charts/kafka-ui/templates/configmap_fromValues.yaml

@@ -1,11 +0,0 @@
-{{- if .Values.yamlApplicationConfig -}}
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: {{ include "kafka-ui.fullname" . }}-fromvalues
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-data:
-  config.yml: |-
-    {{- toYaml .Values.yamlApplicationConfig | nindent 4}}
-{{ end }}

+ 0 - 150
charts/kafka-ui/templates/deployment.yaml

@@ -1,150 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: {{ include "kafka-ui.fullname" . }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-  {{- with .Values.annotations }}
-  annotations:
-    {{- toYaml . | nindent 4 }}
-  {{- end }}
-spec:
-{{- if not .Values.autoscaling.enabled }}
-  replicas: {{ .Values.replicaCount }}
-{{- end }}
-  selector:
-    matchLabels:
-      {{- include "kafka-ui.selectorLabels" . | nindent 6 }}
-  template:
-    metadata:
-      annotations:
-      {{- with .Values.podAnnotations }}
-          {{- toYaml . | nindent 8 }}
-      {{- end }}
-        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
-        checksum/configFromValues: {{ include (print $.Template.BasePath "/configmap_fromValues.yaml") . | sha256sum }}
-        checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
-      labels:
-        {{- include "kafka-ui.selectorLabels" . | nindent 8 }}
-        {{- if .Values.podLabels }}
-        {{- toYaml .Values.podLabels | nindent 8 }}
-        {{- end }}
-    spec:
-      {{- with .Values.imagePullSecrets }}
-      imagePullSecrets:
-        {{- toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.initContainers }}
-      initContainers:
-        {{- toYaml . | nindent 8 }}
-      {{- end }}
-      serviceAccountName: {{ include "kafka-ui.serviceAccountName" . }}
-      securityContext:
-        {{- toYaml .Values.podSecurityContext | nindent 8 }}
-      containers:
-        - name: {{ .Chart.Name }}
-          securityContext:
-            {{- toYaml .Values.securityContext | nindent 12 }}
-          image: {{ include "kafka-ui.imageName" . }}
-          imagePullPolicy: {{ .Values.image.pullPolicy }}
-          {{- if or .Values.env  .Values.yamlApplicationConfig .Values.yamlApplicationConfigConfigMap}}
-          env:
-            {{- with .Values.env }}
-              {{- toYaml . | nindent 12 }}
-            {{- end }}
-            {{- if or .Values.yamlApplicationConfig .Values.yamlApplicationConfigConfigMap}}
-            - name: SPRING_CONFIG_ADDITIONAL-LOCATION
-              {{- if .Values.yamlApplicationConfig }}
-              value: /kafka-ui/config.yml
-              {{- else if .Values.yamlApplicationConfigConfigMap }}
-              value: /kafka-ui/{{ .Values.yamlApplicationConfigConfigMap.keyName | default "config.yml" }}
-              {{- end }}
-            {{- end }}
-          {{- end }}
-          envFrom:
-            {{- if .Values.existingConfigMap }}
-            - configMapRef:
-                name: {{ .Values.existingConfigMap }}
-            {{- end }}
-            {{- if .Values.envs.config }}
-            - configMapRef:
-                name: {{ include "kafka-ui.fullname" . }}
-            {{- end }}
-            {{- if .Values.existingSecret }}
-            - secretRef:
-                name: {{ .Values.existingSecret }}
-            {{- end }}
-            {{- if .Values.envs.secret}}
-            - secretRef:
-                name: {{ include "kafka-ui.fullname" . }}
-            {{- end}}    
-          ports:
-            - name: http
-              containerPort: 8080
-              protocol: TCP
-          livenessProbe:
-            httpGet:
-              {{- $contextPath := .Values.envs.config.SERVER_SERVLET_CONTEXT_PATH | default "" | printf "%s/actuator/health" | urlParse }}
-              path: {{ get $contextPath "path" }}
-              port: http
-              {{- if .Values.probes.useHttpsScheme }}
-              scheme: HTTPS
-              {{- end }}
-            initialDelaySeconds: 60
-            periodSeconds: 30
-            timeoutSeconds: 10
-          readinessProbe:
-            httpGet:
-              {{- $contextPath := .Values.envs.config.SERVER_SERVLET_CONTEXT_PATH | default "" | printf "%s/actuator/health" | urlParse }}
-              path: {{ get $contextPath "path" }}
-              port: http
-              {{- if .Values.probes.useHttpsScheme }}
-              scheme: HTTPS
-              {{- end }}
-            initialDelaySeconds: 60
-            periodSeconds: 30
-            timeoutSeconds: 10
-          resources:
-            {{- toYaml .Values.resources | nindent 12 }}
-          {{- if or .Values.yamlApplicationConfig .Values.volumeMounts .Values.yamlApplicationConfigConfigMap}}
-          volumeMounts:
-            {{- with .Values.volumeMounts }} 
-              {{- toYaml . | nindent 12 }}
-            {{- end }}
-            {{- if .Values.yamlApplicationConfig }}
-            - name: kafka-ui-yaml-conf
-              mountPath: /kafka-ui/
-            {{- end }}
-            {{- if .Values.yamlApplicationConfigConfigMap}}
-            - name: kafka-ui-yaml-conf-configmap
-              mountPath: /kafka-ui/
-            {{- end }}
-          {{- end }}
-      {{- if or .Values.yamlApplicationConfig .Values.volumes .Values.yamlApplicationConfigConfigMap}}
-      volumes:
-        {{- with .Values.volumes }}
-          {{- toYaml . | nindent 8 }}
-        {{- end }}
-        {{- if .Values.yamlApplicationConfig }}
-        - name: kafka-ui-yaml-conf
-          configMap: 
-            name: {{ include "kafka-ui.fullname" . }}-fromvalues
-        {{- end }}
-        {{- if .Values.yamlApplicationConfigConfigMap}}
-        - name: kafka-ui-yaml-conf-configmap
-          configMap: 
-            name: {{ .Values.yamlApplicationConfigConfigMap.name }}
-        {{- end }}
-      {{- end }}
-      {{- with .Values.nodeSelector }}
-      nodeSelector:
-        {{- toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.affinity }}
-      affinity:
-        {{- toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.tolerations }}
-      tolerations:
-        {{- toYaml . | nindent 8 }}
-      {{- end }}

+ 0 - 46
charts/kafka-ui/templates/hpa.yaml

@@ -1,46 +0,0 @@
-{{- if .Values.autoscaling.enabled }}
-{{- $kubeCapabilityVersion := semver .Capabilities.KubeVersion.Version -}}
-{{- $isHigher1p25 := ge (semver "1.25" | $kubeCapabilityVersion.Compare) 0 -}}
-{{- if and ($.Capabilities.APIVersions.Has "autoscaling/v2") $isHigher1p25 -}}
-apiVersion: autoscaling/v2
-{{- else  }}
-apiVersion: autoscaling/v2beta1
-{{- end }}
-kind: HorizontalPodAutoscaler
-metadata:
-  name: {{ include "kafka-ui.fullname" . }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-spec:
-  scaleTargetRef:
-    apiVersion: apps/v1
-    kind: Deployment
-    name: {{ include "kafka-ui.fullname" . }}
-  minReplicas: {{ .Values.autoscaling.minReplicas }}
-  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
-  metrics:
-  {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
-    - type: Resource
-      resource:
-        name: cpu
-  {{- if  $isHigher1p25 }}
-        target:
-         type: Utilization
-         averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
-  {{- else  }}        
-        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
-  {{- end }}      
-  {{- end }}
-  {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
-    - type: Resource
-      resource:
-        name: memory
-  {{- if  $isHigher1p25 }}     
-        target:
-          type: Utilization
-          averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
-  {{- else  }}   
-        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
-  {{- end }}
-  {{- end }}
-{{- end }}

+ 0 - 89
charts/kafka-ui/templates/ingress.yaml

@@ -1,89 +0,0 @@
-{{- if .Values.ingress.enabled -}}
-{{- $fullName := include "kafka-ui.fullname" . -}}
-{{- $svcPort := .Values.service.port -}}
-{{- $kubeCapabilityVersion := semver .Capabilities.KubeVersion.Version -}}
-{{- $isHigher1p19 := ge (semver "1.19" | $kubeCapabilityVersion.Compare) 0 -}}
-{{- if and ($.Capabilities.APIVersions.Has "networking.k8s.io/v1") $isHigher1p19 -}}
-apiVersion: networking.k8s.io/v1
-{{- else if $.Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }}
-apiVersion: networking.k8s.io/v1beta1
-{{- else }}
-apiVersion: extensions/v1beta1
-{{- end }}
-kind: Ingress
-metadata:
-  name: {{ $fullName }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-  {{- with .Values.ingress.annotations }}
-  annotations:
-    {{- toYaml . | nindent 4 }}
-  {{- end }}
-spec:
-  {{- if .Values.ingress.tls.enabled }}
-  tls:
-    - hosts:
-        - {{ tpl .Values.ingress.host . }}
-      secretName: {{ .Values.ingress.tls.secretName }}
-  {{- end }}
-  {{- if .Values.ingress.ingressClassName }}
-  ingressClassName: {{ .Values.ingress.ingressClassName }}
-  {{- end }}
-  rules:
-    - http:
-        paths:
-{{- if and ($.Capabilities.APIVersions.Has "networking.k8s.io/v1") $isHigher1p19 -}}
-          {{- range .Values.ingress.precedingPaths }}
-          - path: {{ .path }}
-            pathType: {{ .Values.ingress.pathType }}
-            backend:
-              service:
-                name: {{ .serviceName }}
-                port:
-                  number: {{ .servicePort }}
-          {{- end }}
-          - backend:
-              service:
-                name: {{ $fullName }}
-                port:
-                  number: {{ $svcPort }}
-            pathType: {{ .Values.ingress.pathType }}
-{{- if .Values.ingress.path }}
-            path: {{ .Values.ingress.path }}
-{{- end }}
-          {{- range .Values.ingress.succeedingPaths }}
-          - path: {{ .path }}
-            pathType: {{ .Values.ingress.pathType }}
-            backend:
-              service:
-                name: {{ .serviceName }}
-                port:
-                  number: {{ .servicePort }}
-          {{- end }}
-{{- if tpl .Values.ingress.host . }}
-      host: {{tpl .Values.ingress.host . }}
-{{- end }}
-{{- else -}}
-          {{- range .Values.ingress.precedingPaths }}
-          - path: {{ .path }}
-            backend:
-              serviceName: {{ .serviceName }}
-              servicePort: {{ .servicePort }}
-          {{- end }}
-          - backend:
-              serviceName: {{ $fullName }}
-              servicePort: {{ $svcPort }}
-{{- if .Values.ingress.path }}
-            path: {{ .Values.ingress.path }}
-{{- end }}
-          {{- range .Values.ingress.succeedingPaths }}
-          - path: {{ .path }}
-            backend:
-              serviceName: {{ .serviceName }}
-              servicePort: {{ .servicePort }}
-          {{- end }}
-{{- if tpl .Values.ingress.host . }}
-      host: {{ tpl .Values.ingress.host . }}
-{{- end }}
-{{- end }}
-{{- end }}

+ 0 - 18
charts/kafka-ui/templates/networkpolicy-egress.yaml

@@ -1,18 +0,0 @@
-{{- if and .Values.networkPolicy.enabled .Values.networkPolicy.egressRules.customRules }}
-apiVersion: networking.k8s.io/v1
-kind: NetworkPolicy
-metadata:
-  name: {{ printf "%s-egress" (include "kafka-ui.fullname" .) }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-spec:
-  podSelector:
-    matchLabels:
-      {{- include "kafka-ui.selectorLabels" . | nindent 6 }}
-  policyTypes:
-    - Egress
-  egress:
-    {{- if .Values.networkPolicy.egressRules.customRules }}
-    {{- toYaml .Values.networkPolicy.egressRules.customRules | nindent 4 }}
-    {{- end }}
-{{- end }}

+ 0 - 18
charts/kafka-ui/templates/networkpolicy-ingress.yaml

@@ -1,18 +0,0 @@
-{{- if and .Values.networkPolicy.enabled .Values.networkPolicy.ingressRules.customRules }}
-apiVersion: networking.k8s.io/v1
-kind: NetworkPolicy
-metadata:
-  name: {{ printf "%s-ingress" (include "kafka-ui.fullname" .) }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-spec:
-  podSelector:
-    matchLabels:
-      {{- include "kafka-ui.selectorLabels" . | nindent 6 }}
-  policyTypes:
-    - Ingress
-  ingress:
-    {{- if .Values.networkPolicy.ingressRules.customRules }}
-    {{- toYaml .Values.networkPolicy.ingressRules.customRules | nindent 4 }}
-    {{- end }}
-{{- end }}

+ 0 - 13
charts/kafka-ui/templates/secret.yaml

@@ -1,13 +0,0 @@
-{{- if .Values.envs.secret -}}
-apiVersion: v1
-kind: Secret
-metadata:
-  name: {{ include "kafka-ui.fullname" . }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-type: Opaque
-data:
-  {{- range $key, $val := .Values.envs.secret }}
-  {{ $key }}: {{ $val | b64enc | quote }}
-  {{- end -}}
-{{- end}}

+ 0 - 22
charts/kafka-ui/templates/service.yaml

@@ -1,22 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
-  name: {{ include "kafka-ui.fullname" . }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-{{- if .Values.service.annotations }}
-  annotations:
-{{ toYaml .Values.service.annotations | nindent 4 }}
-{{- end }}
-spec:
-  type: {{ .Values.service.type }}
-  ports:
-    - port: {{ .Values.service.port }}
-      targetPort: http
-      protocol: TCP
-      name: http
-      {{- if (and (eq .Values.service.type "NodePort") .Values.service.nodePort) }}
-      nodePort: {{ .Values.service.nodePort }}
-      {{- end }}
-  selector:
-    {{- include "kafka-ui.selectorLabels" . | nindent 4 }}

+ 0 - 12
charts/kafka-ui/templates/serviceaccount.yaml

@@ -1,12 +0,0 @@
-{{- if .Values.serviceAccount.create -}}
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: {{ include "kafka-ui.serviceAccountName" . }}
-  labels:
-    {{- include "kafka-ui.labels" . | nindent 4 }}
-  {{- with .Values.serviceAccount.annotations }}
-  annotations:
-    {{- toYaml . | nindent 4 }}
-  {{- end }}
-{{- end }}

+ 0 - 161
charts/kafka-ui/values.yaml

@@ -1,161 +0,0 @@
-replicaCount: 1
-
-image:
-  registry: docker.io
-  repository: provectuslabs/kafka-ui
-  pullPolicy: IfNotPresent
-  # Overrides the image tag whose default is the chart appVersion.
-  tag: ""
-
-imagePullSecrets: []
-nameOverride: ""
-fullnameOverride: ""
-
-serviceAccount:
-  # Specifies whether a service account should be created
-  create: true
-  # Annotations to add to the service account
-  annotations: {}
-  # The name of the service account to use.
-  # If not set and create is true, a name is generated using the fullname template
-  name: ""
-
-existingConfigMap: ""
-yamlApplicationConfig:
-  {}
-  # kafka:
-  #   clusters:
-  #     - name: yaml
-  #       bootstrapServers: kafka-service:9092
-  # spring:
-  #   security:
-  #     oauth2:
-  # auth:
-  #   type: disabled
-  # management:
-  #   health:
-  #     ldap:
-  #       enabled: false
-yamlApplicationConfigConfigMap:
-  {}
-  # keyName: config.yml
-  # name: configMapName
-existingSecret: ""
-envs:
-  secret: {}
-  config: {}
-
-networkPolicy:
-  enabled: false
-  egressRules:
-    ## Additional custom egress rules
-    ## e.g:
-    ## customRules:
-    ##   - to:
-    ##       - namespaceSelector:
-    ##           matchLabels:
-    ##             label: example
-    customRules: []
-  ingressRules:
-    ## Additional custom ingress rules
-    ## e.g:
-    ## customRules:
-    ##   - from:
-    ##       - namespaceSelector:
-    ##           matchLabels:
-    ##             label: example
-    customRules: []
-
-podAnnotations: {}
-podLabels: {}
-
-## Annotations to be added to kafka-ui Deployment
-##
-annotations: {}
-
-## Set field schema as HTTPS for readines and liveness probe
-##
-probes:
-  useHttpsScheme: false
-
-podSecurityContext:
-  {}
-  # fsGroup: 2000
-
-securityContext:
-  {}
-  # capabilities:
-  #   drop:
-  #   - ALL
-  # readOnlyRootFilesystem: true
-  # runAsNonRoot: true
-  # runAsUser: 1000
-
-service:
-  type: ClusterIP
-  port: 80
-  # if you want to force a specific nodePort. Must be use with service.type=NodePort
-  # nodePort:
-
-# Ingress configuration
-ingress:
-  # Enable ingress resource
-  enabled: false
-
-  # Annotations for the Ingress
-  annotations: {}
-
-  # ingressClassName for the Ingress
-  ingressClassName: ""
-
-  # The path for the Ingress
-  path: "/"
-
-  # The path type for the Ingress
-  pathType: "Prefix"  
-
-  # The hostname for the Ingress
-  host: ""
-
-  # configs for Ingress TLS
-  tls:
-    # Enable TLS termination for the Ingress
-    enabled: false
-    # the name of a pre-created Secret containing a TLS private key and certificate
-    secretName: ""
-
-  # HTTP paths to add to the Ingress before the default path
-  precedingPaths: []
-
-  # Http paths to add to the Ingress after the default path
-  succeedingPaths: []
-
-resources:
-  {}
-  # limits:
-  #   cpu: 200m
-  #   memory: 512Mi
-  # requests:
-  #   cpu: 200m
-  #   memory: 256Mi
-
-autoscaling:
-  enabled: false
-  minReplicas: 1
-  maxReplicas: 100
-  targetCPUUtilizationPercentage: 80
-  # targetMemoryUtilizationPercentage: 80
-
-nodeSelector: {}
-
-tolerations: []
-
-affinity: {}
-
-env: {}
-
-initContainers: {}
-
-volumeMounts: {}
-
-volumes: {}

+ 12 - 5
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaConfigSanitizer.java

@@ -5,11 +5,13 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import org.apache.kafka.common.config.ConfigDef;
 import org.apache.kafka.common.config.SaslConfigs;
 import org.apache.kafka.common.config.SslConfigs;
@@ -17,7 +19,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 @Component
-class KafkaConfigSanitizer  {
+class KafkaConfigSanitizer {
 
   private static final String SANITIZED_VALUE = "******";
 
@@ -65,10 +67,8 @@ class KafkaConfigSanitizer  {
         .collect(Collectors.toSet());
   }
 
-  public Object sanitize(String key, Object value) {
-    if (value == null) {
-      return null;
-    }
+  @Nullable
+  public Object sanitize(String key, @Nullable Object value) {
     for (Pattern pattern : sanitizeKeysPatterns) {
       if (pattern.matcher(key).matches()) {
         return SANITIZED_VALUE;
@@ -77,5 +77,12 @@ class KafkaConfigSanitizer  {
     return value;
   }
 
+  public Map<String, Object> sanitizeConnectorConfig(@Nullable Map<String, Object> original) {
+    var result = new HashMap<String, Object>(); //null-values supporting map!
+    if (original != null) {
+      original.forEach((k, v) -> result.put(k, sanitize(k, v)));
+    }
+    return result;
+  }
 
 }

+ 4 - 15
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaConnectService.java

@@ -24,7 +24,6 @@ import com.provectus.kafka.ui.model.NewConnectorDTO;
 import com.provectus.kafka.ui.model.TaskDTO;
 import com.provectus.kafka.ui.model.connect.InternalConnectInfo;
 import com.provectus.kafka.ui.util.ReactiveFailover;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -176,19 +175,14 @@ public class KafkaConnectService {
                         e -> emptyStatus(connectorName))
                     .map(connectorStatus -> {
                       var status = connectorStatus.getConnector();
-                      final Map<String, Object> obfuscatedConfig = connector.getConfig().entrySet()
-                          .stream()
-                          .collect(Collectors.toMap(
-                              Map.Entry::getKey,
-                              e -> kafkaConfigSanitizer.sanitize(e.getKey(), e.getValue())
-                          ));
-                      ConnectorDTO result = (ConnectorDTO) new ConnectorDTO()
+                      var sanitizedConfig = kafkaConfigSanitizer.sanitizeConnectorConfig(connector.getConfig());
+                      ConnectorDTO result = new ConnectorDTO()
                           .connect(connectName)
                           .status(kafkaConnectMapper.fromClient(status))
                           .type(connector.getType())
                           .tasks(connector.getTasks())
                           .name(connector.getName())
-                          .config(obfuscatedConfig);
+                          .config(sanitizedConfig);
 
                       if (connectorStatus.getTasks() != null) {
                         boolean isAnyTaskFailed = connectorStatus.getTasks().stream()
@@ -217,12 +211,7 @@ public class KafkaConnectService {
                                                       String connectorName) {
     return api(cluster, connectName)
         .mono(c -> c.getConnectorConfig(connectorName))
-        .map(connectorConfig -> {
-          final Map<String, Object> obfuscatedMap = new HashMap<>();
-          connectorConfig.forEach((key, value) ->
-              obfuscatedMap.put(key, kafkaConfigSanitizer.sanitize(key, value)));
-          return obfuscatedMap;
-        });
+        .map(kafkaConfigSanitizer::sanitizeConnectorConfig);
   }
 
   public Mono<ConnectorDTO> setConnectorConfig(KafkaCluster cluster, String connectName,

+ 1 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java

@@ -207,6 +207,7 @@ public class AccessControlService {
     if (!rbacEnabled) {
       return Mono.just(topics);
     }
+
     return getUser()
         .map(user -> topics.stream()
             .filter(topic -> {

+ 24 - 4
kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/KafkaConfigSanitizerTest.java

@@ -3,14 +3,16 @@ package com.provectus.kafka.ui.service;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.Arrays;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import org.junit.jupiter.api.Test;
 
 class KafkaConfigSanitizerTest {
 
   @Test
   void doNothingIfEnabledPropertySetToFalse() {
-    final var sanitizer = new KafkaConfigSanitizer(false, Collections.emptyList());
+    final var sanitizer = new KafkaConfigSanitizer(false, List.of());
     assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("secret");
     assertThat(sanitizer.sanitize("sasl.jaas.config", "secret")).isEqualTo("secret");
     assertThat(sanitizer.sanitize("database.password", "secret")).isEqualTo("secret");
@@ -18,7 +20,7 @@ class KafkaConfigSanitizerTest {
 
   @Test
   void obfuscateCredentials() {
-    final var sanitizer = new KafkaConfigSanitizer(true, Collections.emptyList());
+    final var sanitizer = new KafkaConfigSanitizer(true, List.of());
     assertThat(sanitizer.sanitize("sasl.jaas.config", "secret")).isEqualTo("******");
     assertThat(sanitizer.sanitize("consumer.sasl.jaas.config", "secret")).isEqualTo("******");
     assertThat(sanitizer.sanitize("producer.sasl.jaas.config", "secret")).isEqualTo("******");
@@ -36,7 +38,7 @@ class KafkaConfigSanitizerTest {
 
   @Test
   void notObfuscateNormalConfigs() {
-    final var sanitizer = new KafkaConfigSanitizer(true, Collections.emptyList());
+    final var sanitizer = new KafkaConfigSanitizer(true, List.of());
     assertThat(sanitizer.sanitize("security.protocol", "SASL_SSL")).isEqualTo("SASL_SSL");
     final String[] bootstrapServer = new String[] {"test1:9092", "test2:9092"};
     assertThat(sanitizer.sanitize("bootstrap.servers", bootstrapServer)).isEqualTo(bootstrapServer);
@@ -52,4 +54,22 @@ class KafkaConfigSanitizerTest {
     assertThat(sanitizer.sanitize("database.password", "no longer credential"))
             .isEqualTo("no longer credential");
   }
+
+  @Test
+  void sanitizeConnectorConfigDoNotFailOnNullableValues() {
+    Map<String, Object> originalConfig = new HashMap<>();
+    originalConfig.put("password", "secret");
+    originalConfig.put("asIs", "normal");
+    originalConfig.put("nullVal", null);
+
+    var sanitizedConfig = new KafkaConfigSanitizer(true, List.of())
+        .sanitizeConnectorConfig(originalConfig);
+
+    assertThat(sanitizedConfig)
+        .hasSize(3)
+        .containsEntry("password", "******")
+        .containsEntry("asIs", "normal")
+        .containsEntry("nullVal", null);
+  }
+
 }

+ 1 - 1
kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/AccessControlServiceMock.java

@@ -16,7 +16,7 @@ public class AccessControlServiceMock {
     when(mock.validateAccess(any())).thenReturn(Mono.empty());
     when(mock.isSchemaAccessible(anyString(), anyString())).thenReturn(Mono.just(true));
 
-    when(mock.filterViewableTopics(any(), any())).then(i -> Mono.just(i.getArgument(0)));
+    when(mock.filterViewableTopics(any(), any())).then(invocation -> Mono.just(invocation.getArgument(0)));
 
     return mock;
   }

+ 1 - 1
kafka-ui-react-app/package.json

@@ -9,7 +9,7 @@
     "@hookform/resolvers": "^2.7.1",
     "@microsoft/fetch-event-source": "^2.0.1",
     "@reduxjs/toolkit": "^1.8.3",
-    "@szhsin/react-menu": "^3.1.1",
+    "@szhsin/react-menu": "^3.5.3",
     "@tanstack/react-query": "^4.0.5",
     "@tanstack/react-table": "^8.5.10",
     "@testing-library/react": "^14.0.0",

+ 7 - 7
kafka-ui-react-app/pnpm-lock.yaml

@@ -10,7 +10,7 @@ specifiers:
   '@reduxjs/toolkit': ^1.8.3
   '@swc/core': ^1.3.36
   '@swc/jest': ^0.2.24
-  '@szhsin/react-menu': ^3.1.1
+  '@szhsin/react-menu': ^3.5.3
   '@tanstack/react-query': ^4.0.5
   '@tanstack/react-table': ^8.5.10
   '@testing-library/dom': ^9.0.0
@@ -91,7 +91,7 @@ dependencies:
   '@hookform/resolvers': 2.8.9_react-hook-form@7.43.1
   '@microsoft/fetch-event-source': 2.0.1
   '@reduxjs/toolkit': 1.8.3_ctm756ikdwcjcvyfxxwskzbr6q
-  '@szhsin/react-menu': 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm
+  '@szhsin/react-menu': 3.5.3_ef5jwxihqo6n7gxfmzogljlgcm
   '@tanstack/react-query': 4.0.5_ef5jwxihqo6n7gxfmzogljlgcm
   '@tanstack/react-table': 8.5.10_ef5jwxihqo6n7gxfmzogljlgcm
   '@testing-library/react': 14.0.0_ef5jwxihqo6n7gxfmzogljlgcm
@@ -1536,8 +1536,8 @@ packages:
       jsonc-parser: 3.2.0
     dev: true
 
-  /@szhsin/react-menu/3.1.1_ef5jwxihqo6n7gxfmzogljlgcm:
-    resolution: {integrity: sha512-IdHLyH61M+KqjTrvqglKo7JnbC0GIkg4OCtlXBxQPEjx/ecR5g0Iycqm+SG3rObEoniLZEz32iJkefve/LAHMA==}
+  /@szhsin/react-menu/3.5.3_ef5jwxihqo6n7gxfmzogljlgcm:
+    resolution: {integrity: sha512-jxo8oaRwxmVjUzkyOi/ZJiXaZiuFPMIxFzyJdUKfnhBLYiEOVTU9M2CiPuEkirILoareR2GJj2K3y8a81CBPlw==}
     peerDependencies:
       react: '>=16.14.0'
       react-dom: '>=16.14.0'
@@ -1545,7 +1545,7 @@ packages:
       prop-types: 15.8.1
       react: 18.1.0
       react-dom: 18.1.0_react@18.1.0
-      react-transition-state: 1.1.4_ef5jwxihqo6n7gxfmzogljlgcm
+      react-transition-state: 1.1.5_ef5jwxihqo6n7gxfmzogljlgcm
     dev: false
 
   /@tanstack/query-core/4.0.5:
@@ -5576,8 +5576,8 @@ packages:
       react: 18.1.0
     dev: false
 
-  /react-transition-state/1.1.4_ef5jwxihqo6n7gxfmzogljlgcm:
-    resolution: {integrity: sha512-6nQLWWx95gYazCm6OdtD1zGbRiirvVXPrDtHAGsYb4xs9spMM7bA8Vx77KCpjL8PJ8qz1lXFGz2PTboCSvt7iw==}
+  /react-transition-state/1.1.5_ef5jwxihqo6n7gxfmzogljlgcm:
+    resolution: {integrity: sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==}
     peerDependencies:
       react: '>=16.8.0'
       react-dom: '>=16.8.0'

+ 5 - 3
kafka-ui-react-app/src/components/App.tsx

@@ -1,4 +1,4 @@
-import React, { Suspense } from 'react';
+import React, { Suspense, useContext } from 'react';
 import { Routes, Route, Navigate } from 'react-router-dom';
 import {
   accessErrorPage,
@@ -18,6 +18,7 @@ import { Toaster } from 'react-hot-toast';
 import GlobalCSS from 'components/globalCss';
 import * as S from 'components/App.styled';
 import ClusterConfigForm from 'widgets/ClusterConfigForm';
+import { ThemeModeContext } from 'components/contexts/ThemeModeContext';
 
 import ConfirmationModal from './common/ConfirmationModal/ConfirmationModal';
 import { ConfirmContextProvider } from './contexts/ConfirmContext';
@@ -30,6 +31,7 @@ const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
       suspense: true,
+      networkMode: 'offlineFirst',
       onError(error) {
         showServerError(error as Response);
       },
@@ -42,7 +44,7 @@ const queryClient = new QueryClient({
   },
 });
 const App: React.FC = () => {
-  const [isDarkMode, setDarkMode] = React.useState<boolean>(false);
+  const { isDarkMode } = useContext(ThemeModeContext);
 
   return (
     <QueryClientProvider client={queryClient}>
@@ -53,7 +55,7 @@ const App: React.FC = () => {
               <ConfirmContextProvider>
                 <GlobalCSS />
                 <S.Layout>
-                  <PageContainer setDarkMode={setDarkMode}>
+                  <PageContainer>
                     <Routes>
                       {['/', '/ui', '/ui/clusters'].map((path) => (
                         <Route

+ 3 - 0
kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx

@@ -54,6 +54,8 @@ const Details: React.FC = () => {
     ? filteredPartitionsByTopic
     : Object.keys(partitionsByTopic);
 
+  const hasAssignedTopics = consumerGroup?.data?.topics !== 0;
+
   return (
     <div>
       <div>
@@ -71,6 +73,7 @@ const Details: React.FC = () => {
                   action: Action.RESET_OFFSETS,
                   value: consumerGroupID,
                 }}
+                disabled={!hasAssignedTopics}
               >
                 Reset offset
               </ActionDropdownItem>

+ 1 - 1
kafka-ui-react-app/src/components/ConsumerGroups/Details/ResetOffsets/ResetOffsets.tsx

@@ -21,7 +21,7 @@ const ResetOffsets: React.FC = () => {
     return <PageLoader />;
 
   const partitions = consumerGroup.data.partitions || [];
-  const { topic } = partitions[0];
+  const { topic } = partitions[0] || '';
 
   const uniqTopics = Array.from(
     new Set(partitions.map((partition) => partition.topic))

+ 6 - 42
kafka-ui-react-app/src/components/NavBar/NavBar.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useContext } from 'react';
 import Select from 'components/common/Select/Select';
 import Logo from 'components/common/Logo/Logo';
 import Version from 'components/Version/Version';
@@ -7,16 +7,16 @@ import DiscordIcon from 'components/common/Icons/DiscordIcon';
 import AutoIcon from 'components/common/Icons/AutoIcon';
 import SunIcon from 'components/common/Icons/SunIcon';
 import MoonIcon from 'components/common/Icons/MoonIcon';
+import { ThemeModeContext } from 'components/contexts/ThemeModeContext';
 
 import UserInfo from './UserInfo/UserInfo';
 import * as S from './NavBar.styled';
 
 interface Props {
   onBurgerClick: () => void;
-  setDarkMode: (value: boolean) => void;
 }
 
-type ThemeDropDownValue = 'auto_theme' | 'light_theme' | 'dark_theme';
+export type ThemeDropDownValue = 'auto_theme' | 'light_theme' | 'dark_theme';
 
 const options = [
   {
@@ -48,44 +48,8 @@ const options = [
   },
 ];
 
-const NavBar: React.FC<Props> = ({ onBurgerClick, setDarkMode }) => {
-  const matchDark = window.matchMedia('(prefers-color-scheme: dark)');
-  const [themeMode, setThemeMode] = React.useState<ThemeDropDownValue>();
-
-  React.useLayoutEffect(() => {
-    const mode = localStorage.getItem('mode');
-    if (mode) {
-      setThemeMode(mode as ThemeDropDownValue);
-      if (mode === 'auto_theme') {
-        setDarkMode(matchDark.matches);
-      } else if (mode === 'light_theme') {
-        setDarkMode(false);
-      } else if (mode === 'dark_theme') {
-        setDarkMode(true);
-      }
-    } else {
-      setThemeMode('auto_theme');
-    }
-  }, []);
-
-  React.useEffect(() => {
-    if (themeMode === 'auto_theme') {
-      setDarkMode(matchDark.matches);
-      matchDark.addListener((e) => {
-        setDarkMode(e.matches);
-      });
-    }
-  }, [matchDark, themeMode]);
-
-  const onChangeThemeMode = (value: string | number) => {
-    setThemeMode(value as ThemeDropDownValue);
-    localStorage.setItem('mode', value as string);
-    if (value === 'light_theme') {
-      setDarkMode(false);
-    } else if (value === 'dark_theme') {
-      setDarkMode(true);
-    }
-  };
+const NavBar: React.FC<Props> = ({ onBurgerClick }) => {
+  const { themeMode, setThemeMode } = useContext(ThemeModeContext);
 
   return (
     <S.Navbar role="navigation" aria-label="Page Header">
@@ -117,7 +81,7 @@ const NavBar: React.FC<Props> = ({ onBurgerClick, setDarkMode }) => {
         <Select
           options={options}
           value={themeMode}
-          onChange={onChangeThemeMode}
+          onChange={setThemeMode}
           isThemeMode
         />
         <S.SocialLink

+ 27 - 6
kafka-ui-react-app/src/components/PageContainer/PageContainer.tsx

@@ -1,27 +1,48 @@
-import React, { PropsWithChildren } from 'react';
-import { useLocation } from 'react-router-dom';
+import React, { PropsWithChildren, useEffect, useMemo } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
 import NavBar from 'components/NavBar/NavBar';
 import * as S from 'components/PageContainer/PageContainer.styled';
 import Nav from 'components/Nav/Nav';
 import useBoolean from 'lib/hooks/useBoolean';
+import { clusterNewConfigPath } from 'lib/paths';
+import { GlobalSettingsContext } from 'components/contexts/GlobalSettingsContext';
+import { useClusters } from 'lib/hooks/api/clusters';
+import { ResourceType } from 'generated-sources';
+import { useGetUserInfo } from 'lib/hooks/api/roles';
 
-const PageContainer: React.FC<
-  PropsWithChildren<{ setDarkMode: (value: boolean) => void }>
-> = ({ children, setDarkMode }) => {
+const PageContainer: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
   const {
     value: isSidebarVisible,
     toggle,
     setFalse: closeSidebar,
   } = useBoolean(false);
+  const clusters = useClusters();
+  const appInfo = React.useContext(GlobalSettingsContext);
   const location = useLocation();
+  const navigate = useNavigate();
+  const { data: authInfo } = useGetUserInfo();
 
   React.useEffect(() => {
     closeSidebar();
   }, [location, closeSidebar]);
 
+  const hasApplicationPermissions = useMemo(() => {
+    if (!authInfo?.rbacEnabled) return true;
+    return !!authInfo?.userInfo?.permissions.some(
+      (permission) => permission.resource === ResourceType.APPLICATIONCONFIG
+    );
+  }, [authInfo]);
+
+  useEffect(() => {
+    if (!appInfo.hasDynamicConfig) return;
+    if (clusters?.data?.length !== 0) return;
+    if (!hasApplicationPermissions) return;
+    navigate(clusterNewConfigPath);
+  }, [clusters?.data, appInfo.hasDynamicConfig]);
+
   return (
     <>
-      <NavBar onBurgerClick={toggle} setDarkMode={setDarkMode} />
+      <NavBar onBurgerClick={toggle} />
       <S.Container>
         <S.Sidebar aria-label="Sidebar" $visible={isSidebarVisible}>
           <Nav />

+ 44 - 13
kafka-ui-react-app/src/components/PageContainer/__tests__/PageContainer.spec.tsx

@@ -4,21 +4,24 @@ import userEvent from '@testing-library/user-event';
 import { render } from 'lib/testHelpers';
 import PageContainer from 'components/PageContainer/PageContainer';
 import { useClusters } from 'lib/hooks/api/clusters';
+import { Cluster, ServerStatus } from 'generated-sources';
 
 const burgerButtonOptions = { name: 'burger' };
 
-jest.mock('lib/hooks/api/clusters', () => ({
-  ...jest.requireActual('lib/hooks/api/roles'),
-  useClusters: jest.fn(),
-}));
-
 jest.mock('components/Version/Version', () => () => <div>Version</div>);
-
+interface DataType {
+  data: Cluster[] | undefined;
+}
+jest.mock('lib/hooks/api/clusters');
+const mockedNavigate = jest.fn();
+jest.mock('react-router-dom', () => ({
+  ...jest.requireActual('react-router-dom'),
+  useNavigate: () => mockedNavigate,
+}));
 describe('Page Container', () => {
-  beforeEach(() => {
-    (useClusters as jest.Mock).mockImplementation(() => ({
-      isSuccess: false,
-    }));
+  const renderComponent = (hasDynamicConfig: boolean, data: DataType) => {
+    const useClustersMock = useClusters as jest.Mock;
+    useClustersMock.mockReturnValue(data);
     Object.defineProperty(window, 'matchMedia', {
       writable: true,
       value: jest.fn().mockImplementation(() => ({
@@ -26,15 +29,18 @@ describe('Page Container', () => {
         addListener: jest.fn(),
       })),
     });
-
     render(
       <PageContainer setDarkMode={jest.fn()}>
         <div>child</div>
-      </PageContainer>
+      </PageContainer>,
+      {
+        globalSettings: { hasDynamicConfig },
+      }
     );
-  });
+  };
 
   it('handle burger click correctly', async () => {
+    renderComponent(false, { data: undefined });
     const burger = within(screen.getByLabelText('Page Header')).getByRole(
       'button',
       burgerButtonOptions
@@ -49,6 +55,31 @@ describe('Page Container', () => {
   });
 
   it('render the inner container', async () => {
+    renderComponent(false, { data: undefined });
     expect(screen.getByText('child')).toBeInTheDocument();
   });
+
+  describe('Redirect to the Wizard page', () => {
+    it('redirects to new cluster configuration page if there are no clusters and dynamic config is enabled', async () => {
+      await renderComponent(true, { data: [] });
+
+      expect(mockedNavigate).toHaveBeenCalled();
+    });
+
+    it('should not navigate to new cluster config page when there are clusters', async () => {
+      await renderComponent(true, {
+        data: [{ name: 'Cluster 1', status: ServerStatus.ONLINE }],
+      });
+
+      expect(mockedNavigate).not.toHaveBeenCalled();
+    });
+
+    it('should not navigate to new cluster config page when there are no clusters and hasDynamicConfig is false', async () => {
+      await renderComponent(false, {
+        data: [],
+      });
+
+      expect(mockedNavigate).not.toHaveBeenCalled();
+    });
+  });
 });

+ 1 - 0
kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx

@@ -188,6 +188,7 @@ const Filters: React.FC<FiltersProps> = ({
   const handleClearAllFilters = () => {
     setCurrentSeekType(SeekType.OFFSET);
     setOffset('');
+    setTimestamp(null);
     setQuery('');
     changeSeekDirection(SeekDirection.FORWARD);
     getSelectedPartitionsFromSeekToParam(searchParams, partitions);

+ 6 - 2
kafka-ui-react-app/src/components/common/SQLEditor/SQLEditor.tsx

@@ -3,7 +3,9 @@ import AceEditor, { IAceEditorProps } from 'react-ace';
 import 'ace-builds/src-noconflict/ace';
 import 'ace-builds/src-noconflict/mode-sql';
 import 'ace-builds/src-noconflict/theme-textmate';
-import React from 'react';
+import 'ace-builds/src-noconflict/theme-dracula';
+import React, { useContext } from 'react';
+import { ThemeModeContext } from 'components/contexts/ThemeModeContext';
 
 interface SQLEditorProps extends IAceEditorProps {
   isFixedHeight?: boolean;
@@ -12,11 +14,13 @@ interface SQLEditorProps extends IAceEditorProps {
 const SQLEditor = React.forwardRef<AceEditor | null, SQLEditorProps>(
   (props, ref) => {
     const { isFixedHeight, ...rest } = props;
+    const { isDarkMode } = useContext(ThemeModeContext);
+
     return (
       <AceEditor
         ref={ref}
         mode="sql"
-        theme="textmate"
+        theme={isDarkMode ? 'dracula' : 'textmate'}
         tabSize={2}
         width="100%"
         height={

+ 58 - 0
kafka-ui-react-app/src/components/contexts/ThemeModeContext.tsx

@@ -0,0 +1,58 @@
+import React, { useMemo } from 'react';
+import type { FC, PropsWithChildren } from 'react';
+import type { ThemeDropDownValue } from 'components/NavBar/NavBar';
+
+interface ThemeModeContextProps {
+  isDarkMode: boolean;
+  themeMode: ThemeDropDownValue;
+  setThemeMode: (value: string | number) => void;
+}
+
+export const ThemeModeContext = React.createContext<ThemeModeContextProps>({
+  isDarkMode: false,
+  themeMode: 'auto_theme',
+  setThemeMode: () => {},
+});
+
+export const ThemeModeProvider: FC<PropsWithChildren<unknown>> = ({
+  children,
+}) => {
+  const matchDark = window.matchMedia('(prefers-color-scheme: dark)');
+  const [themeMode, setThemeModeState] =
+    React.useState<ThemeDropDownValue>('auto_theme');
+
+  React.useLayoutEffect(() => {
+    const mode = localStorage.getItem('mode');
+    setThemeModeState((mode as ThemeDropDownValue) ?? 'auto_theme');
+  }, [setThemeModeState]);
+
+  const isDarkMode = React.useMemo(() => {
+    if (themeMode === 'auto_theme') {
+      return matchDark.matches;
+    }
+    return themeMode === 'dark_theme';
+  }, [themeMode]);
+
+  const setThemeMode = React.useCallback(
+    (value: string | number) => {
+      setThemeModeState(value as ThemeDropDownValue);
+      localStorage.setItem('mode', value as string);
+    },
+    [setThemeModeState]
+  );
+
+  const contextValue = useMemo(
+    () => ({
+      isDarkMode,
+      themeMode,
+      setThemeMode,
+    }),
+    [isDarkMode, themeMode, setThemeMode]
+  );
+
+  return (
+    <ThemeModeContext.Provider value={contextValue}>
+      {children}
+    </ThemeModeContext.Provider>
+  );
+};

+ 4 - 1
kafka-ui-react-app/src/index.tsx

@@ -2,6 +2,7 @@ import React from 'react';
 import { createRoot } from 'react-dom/client';
 import { BrowserRouter } from 'react-router-dom';
 import { Provider } from 'react-redux';
+import { ThemeModeProvider } from 'components/contexts/ThemeModeContext';
 import App from 'components/App';
 import { store } from 'redux/store';
 import 'lib/constants';
@@ -14,7 +15,9 @@ const root = createRoot(container);
 root.render(
   <Provider store={store}>
     <BrowserRouter basename={window.basePath || '/'}>
-      <App />
+      <ThemeModeProvider>
+        <App />
+      </ThemeModeProvider>
     </BrowserRouter>
   </Provider>
 );

+ 7 - 1
kafka-ui-react-app/src/lib/testHelpers.tsx

@@ -39,6 +39,9 @@ interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
     roles?: RolesType;
     rbacFlag: boolean;
   };
+  globalSettings?: {
+    hasDynamicConfig: boolean;
+  };
 }
 
 interface WithRouteProps {
@@ -111,6 +114,7 @@ const customRender = (
     }),
     initialEntries,
     userInfo,
+    globalSettings,
     ...renderOptions
   }: CustomRenderOptions = {}
 ) => {
@@ -119,7 +123,9 @@ const customRender = (
     children,
   }) => (
     <TestQueryClientProvider>
-      <GlobalSettingsContext.Provider value={{ hasDynamicConfig: false }}>
+      <GlobalSettingsContext.Provider
+        value={globalSettings || { hasDynamicConfig: false }}
+      >
         <ThemeProvider theme={theme}>
           <TestUserInfoProvider data={userInfo}>
             <ConfirmContextProvider>

+ 7 - 1
kafka-ui-react-app/src/widgets/ClusterConfigForm/utils/getJaasConfig.ts

@@ -20,7 +20,13 @@ export const getJaasConfig = (
   options: Record<string, string>
 ) => {
   const optionsString = Object.entries(options)
-    .map(([key, value]) => (isUndefined(value) ? null : ` ${key}="${value}"`))
+    .map(([key, value]) => {
+      if (isUndefined(value)) return null;
+      if (value === 'true' || value === 'false') {
+        return ` ${key}=${value}`;
+      }
+      return ` ${key}="${value}"`;
+    })
     .join('');
 
   return `${JAAS_CONFIGS[method]} required${optionsString};`;

+ 1 - 1
kafka-ui-react-app/src/widgets/ClusterConfigForm/utils/transformFormDataToPayload.ts

@@ -122,7 +122,7 @@ export const transformFormDataToPayload = (data: ClusterConfigFormValues) => {
           'sasl.mechanism': 'GSSAPI',
           'sasl.kerberos.service.name': props.saslKerberosServiceName,
           'sasl.jaas.config': getJaasConfig('SASL/GSSAPI', {
-            useKeytab: props.keyTabFile ? 'true' : 'false',
+            useKeyTab: props.keyTabFile ? 'true' : 'false',
             keyTab: props.keyTabFile,
             storeKey: String(!!props.storeKey),
             principal: props.principal,

+ 1 - 1
kafka-ui-serde-api/pom.xml

@@ -55,7 +55,7 @@
 				<plugin>
 					<groupId>org.apache.maven.plugins</groupId>
 					<artifactId>maven-jar-plugin</artifactId>
-					<version>3.0.2</version>
+					<version>3.3.0</version>
 				</plugin>
 				<plugin>
 					<groupId>org.sonatype.plugins</groupId>