فهرست منبع

Merge branch 'master' into Topic_custom_params_are_disabled_upon_editing

David 2 سال پیش
والد
کامیت
aa19b01be7

+ 19 - 11
.github/ISSUE_TEMPLATE/bug_report.md

@@ -15,33 +15,36 @@ https://github.com/provectus/kafka-ui/discussions
 
 
 -->
 -->
 
 
-**Describe the bug**
-<!--(A clear and concise description of what the bug is.)-->
+<!--
+Please follow the naming conventions for bugs:
+<Feature/Area/Scope> :  <Compact, but specific problem summary> 
+Avoid generic titles, like “Topics: incorrect layout of message sorting drop-down list”. Better use something like: “Topics: Message sorting drop-down list overlaps the "Submit" button”.
+
+-->
 
 
+**Describe the bug** (Actual behavior)
+<!--(A clear and concise description of what the bug is.Use a list, if there is more than one problem)-->
+
+**Expected behavior**
+<!--(A clear and concise description of what you expected to happen.)-->
 
 
 **Set up**
 **Set up**
 <!--
 <!--
+WE MIGHT CLOSE THE ISSUE without further explanation IF YOU DON'T PROVIDE THIS INFORMATION.
+
 How do you run the app? Please provide as much info as possible:
 How do you run the app? Please provide as much info as possible:
 1. App version (docker image version or check commit hash in the top left corner in UI)
 1. App version (docker image version or check commit hash in the top left corner in UI)
 2. Helm chart version, if you use one
 2. Helm chart version, if you use one
 3. Any IAAC configs
 3. Any IAAC configs
-
-We might close the issue without further explanation if you don't provide such information.
 -->
 -->
 
 
 
 
 **Steps to Reproduce**
 **Steps to Reproduce**
 <!-- We'd like you to provide an example setup (via docker-compose, helm, etc.) 
 <!-- We'd like you to provide an example setup (via docker-compose, helm, etc.) 
 to reproduce the problem, especially with a complex setups. -->
 to reproduce the problem, especially with a complex setups. -->
-Steps to reproduce the behavior:
 
 
 1. 
 1. 
 
 
-**Expected behavior**
-<!--
-(A clear and concise description of what you expected to happen)
--->
-
 **Screenshots**
 **Screenshots**
 <!--
 <!--
 (If applicable, add screenshots to help explain your problem)
 (If applicable, add screenshots to help explain your problem)
@@ -50,5 +53,10 @@ Steps to reproduce the behavior:
 
 
 **Additional context**
 **Additional context**
 <!--
 <!--
-(Add any other context about the problem here)
+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 successfull or same issue occured? 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?
 -->
 -->

+ 54 - 12
documentation/compose/e2e-tests.yaml

@@ -7,10 +7,18 @@ services:
     image: provectuslabs/kafka-ui:latest
     image: provectuslabs/kafka-ui:latest
     ports:
     ports:
       - 8080:8080
       - 8080:8080
+    healthcheck:
+      test: wget --no-verbose --tries=1 --spider  http://localhost:8080/actuator/health
+      interval: 30s
+      timeout: 10s
+      retries: 10  
     depends_on:
     depends_on:
-      - kafka0
-      - schemaregistry0
-      - kafka-connect0
+        kafka0:
+          condition: service_healthy
+        schemaregistry0:
+          condition: service_healthy
+        kafka-connect0:
+          condition: service_healthy
     environment:
     environment:
       KAFKA_CLUSTERS_0_NAME: local
       KAFKA_CLUSTERS_0_NAME: local
       KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
       KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
@@ -24,6 +32,11 @@ services:
     image: confluentinc/cp-kafka:7.2.1
     image: confluentinc/cp-kafka:7.2.1
     hostname: kafka0
     hostname: kafka0
     container_name: kafka0
     container_name: kafka0
+    healthcheck:
+     test: unset JMX_PORT && KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9999" && kafka-broker-api-versions --bootstrap-server=localhost:9092
+     interval: 30s
+     timeout: 10s
+     retries: 10
     ports:
     ports:
       - "9092:9092"
       - "9092:9092"
       - "9997:9997"
       - "9997:9997"
@@ -54,7 +67,13 @@ services:
     ports:
     ports:
       - 8085:8085
       - 8085:8085
     depends_on:
     depends_on:
-      - kafka0
+      kafka0:
+          condition: service_healthy
+    healthcheck:
+     test: ["CMD", "timeout", "1", "curl", "--silent", "--fail", "http://schemaregistry0:8085/subjects"]
+     interval: 30s
+     timeout: 10s
+     retries: 10
     environment:
     environment:
       SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
       SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
       SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
       SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
@@ -73,8 +92,15 @@ services:
     ports:
     ports:
       - 8083:8083
       - 8083:8083
     depends_on:
     depends_on:
-      - kafka0
-      - schemaregistry0
+      kafka0:
+          condition: service_healthy
+      schemaregistry0:
+          condition: service_healthy
+    healthcheck:
+      test: ["CMD", "nc", "127.0.0.1", "8083"]
+      interval: 30s
+      timeout: 10s
+      retries: 10
     environment:
     environment:
       CONNECT_BOOTSTRAP_SERVERS: kafka0:29092
       CONNECT_BOOTSTRAP_SERVERS: kafka0:29092
       CONNECT_GROUP_ID: compose-connect-group
       CONNECT_GROUP_ID: compose-connect-group
@@ -100,7 +126,8 @@ services:
     volumes:
     volumes:
       - ./message.json:/data/message.json
       - ./message.json:/data/message.json
     depends_on:
     depends_on:
-      - kafka0
+      kafka0:
+          condition: service_healthy
     command: "bash -c 'echo Waiting for Kafka to be ready... && \
     command: "bash -c 'echo Waiting for Kafka to be ready... && \
                cub kafka-ready -b kafka0:29092 1 30 && \
                cub kafka-ready -b kafka0:29092 1 30 && \
                kafka-topics --create --topic users --partitions 3 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
                kafka-topics --create --topic users --partitions 3 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
@@ -114,6 +141,11 @@ services:
         image: postgres:9.6.22
         image: postgres:9.6.22
     ports:
     ports:
       - 5432:5432
       - 5432:5432
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U dev_user"]
+      interval: 10s
+      timeout: 5s
+      retries: 5  
     environment:
     environment:
       POSTGRES_USER: 'dev_user'
       POSTGRES_USER: 'dev_user'
       POSTGRES_PASSWORD: '12345'
       POSTGRES_PASSWORD: '12345'
@@ -121,18 +153,28 @@ services:
   create-connectors:
   create-connectors:
     image: ellerbrock/alpine-bash-curl-ssl
     image: ellerbrock/alpine-bash-curl-ssl
     depends_on:
     depends_on:
-      - postgres-db
-      - kafka-connect0
+      postgres-db:
+          condition: service_healthy
+      kafka-connect0:
+        condition: service_healthy
     volumes:
     volumes:
       - ./connectors:/connectors
       - ./connectors:/connectors
     command: bash -c '/connectors/start.sh'
     command: bash -c '/connectors/start.sh'
 
 
   ksqldb:
   ksqldb:
     image: confluentinc/ksqldb-server:0.18.0
     image: confluentinc/ksqldb-server:0.18.0
+    healthcheck:
+      test: ["CMD", "timeout", "1", "curl", "--silent", "--fail", "http://localhost:8088/info"]
+      interval: 30s
+      timeout: 10s
+      retries: 10
     depends_on:
     depends_on:
-      - kafka0
-      - kafka-connect0
-      - schemaregistry0
+      kafka0:
+        condition: service_healthy
+      kafka-connect0:
+        condition: service_healthy
+      schemaregistry0:
+         condition: service_healthy
     ports:
     ports:
       - 8088:8088
       - 8088:8088
     environment:
     environment:

+ 3 - 2
kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthProperties.java

@@ -1,6 +1,7 @@
 package com.provectus.kafka.ui.config.auth;
 package com.provectus.kafka.ui.config.auth;
 
 
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 import javax.annotation.PostConstruct;
 import javax.annotation.PostConstruct;
@@ -31,13 +32,13 @@ public class OAuthProperties {
     private String clientName;
     private String clientName;
     private String redirectUri;
     private String redirectUri;
     private String authorizationGrantType;
     private String authorizationGrantType;
-    private Set<String> scope;
+    private Set<String> scope = new HashSet<>();
     private String issuerUri;
     private String issuerUri;
     private String authorizationUri;
     private String authorizationUri;
     private String tokenUri;
     private String tokenUri;
     private String userInfoUri;
     private String userInfoUri;
     private String jwkSetUri;
     private String jwkSetUri;
     private String userNameAttribute;
     private String userNameAttribute;
-    private Map<String, String> customParams;
+    private Map<String, String> customParams = new HashMap<>();
   }
   }
 }
 }

+ 1 - 1
kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthPropertiesConverter.java

@@ -71,7 +71,7 @@ public final class OAuthPropertiesConverter {
   }
   }
 
 
   private static boolean isGoogle(OAuth2Provider provider) {
   private static boolean isGoogle(OAuth2Provider provider) {
-    return provider.getCustomParams().get(TYPE).equalsIgnoreCase(GOOGLE);
+    return GOOGLE.equalsIgnoreCase(provider.getCustomParams().get(TYPE));
   }
   }
 }
 }
 
 

+ 2 - 4
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/StaticController.java

@@ -53,9 +53,7 @@ public class StaticController {
   @SneakyThrows
   @SneakyThrows
   private String buildFile(Resource file, String contextPath) {
   private String buildFile(Resource file, String contextPath) {
     return ResourceUtil.readAsString(file)
     return ResourceUtil.readAsString(file)
-        .replace("\"/assets/", "\"" + contextPath + "/assets/")
-        .replace("\"/favicon/", "\"" + contextPath + "/favicon/")
-        .replace("/manifest.json", contextPath + "/manifest.json")
-        .replace("window.basePath = ''", "window.basePath=\"" + contextPath + "\"");
+        .replace("\"assets/", "\"" + contextPath + "/assets/")
+        .replace("PUBLIC-PATH-VARIABLE",  contextPath);
   }
   }
 }
 }

+ 9 - 6
kafka-ui-react-app/index.html

@@ -3,7 +3,6 @@
   <head>
   <head>
     <meta charset="utf-8" />
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
-
     <!-- Google fonts -->
     <!-- Google fonts -->
     <link rel="preconnect" href="https://fonts.googleapis.com" />
     <link rel="preconnect" href="https://fonts.googleapis.com" />
     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@@ -13,14 +12,18 @@
     />
     />
 
 
     <!-- Favicons -->
     <!-- Favicons -->
-    <link rel="icon" href="/favicon/favicon.ico" sizes="any" />
-    <link rel="icon" href="/favicon/icon.svg" type="image/svg+xml" />
-    <link rel="apple-touch-icon" href="/favicon/apple-touch-icon.png" />
-    <link rel="manifest" href="/manifest.json" />
+    <link rel="icon" href="<%= PUBLIC_PATH %>/favicon/favicon.ico" sizes="any" />
+    <link rel="icon" href="<%= PUBLIC_PATH %>/favicon/icon.svg" type="image/svg+xml" />
+    <link rel="apple-touch-icon" href="<%= PUBLIC_PATH %>/favicon/apple-touch-icon.png" />
+    <link rel="manifest" href="<%= PUBLIC_PATH %>/manifest.json" />
 
 
     <title>UI for Apache Kafka</title>
     <title>UI for Apache Kafka</title>
     <script type="text/javascript">
     <script type="text/javascript">
-      window.basePath = '';
+      window.basePath = '<%= PUBLIC_PATH %>';
+
+      window.__assetsPathBuilder = function (importer) {
+        return window.basePath+ "/" + importer;
+      };
     </script>
     </script>
   </head>
   </head>
 
 

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

@@ -118,7 +118,8 @@
     "rimraf": "^3.0.2",
     "rimraf": "^3.0.2",
     "ts-node": "^10.8.1",
     "ts-node": "^10.8.1",
     "ts-prune": "^0.10.3",
     "ts-prune": "^0.10.3",
-    "typescript": "^4.7.4"
+    "typescript": "^4.7.4",
+    "vite-plugin-ejs": "^1.6.4"
   },
   },
   "engines": {
   "engines": {
     "node": "v16.15.0",
     "node": "v16.15.0",

+ 50 - 0
kafka-ui-react-app/pnpm-lock.yaml

@@ -85,6 +85,7 @@ specifiers:
   typescript: ^4.7.4
   typescript: ^4.7.4
   use-debounce: ^8.0.1
   use-debounce: ^8.0.1
   vite: ^4.0.0
   vite: ^4.0.0
+  vite-plugin-ejs: ^1.6.4
   vite-tsconfig-paths: ^4.0.2
   vite-tsconfig-paths: ^4.0.2
   whatwg-fetch: ^3.6.2
   whatwg-fetch: ^3.6.2
   yup: ^0.32.11
   yup: ^0.32.11
@@ -181,6 +182,7 @@ devDependencies:
   ts-node: 10.8.1_seagpw47opwyivxvtfydnuwcuy
   ts-node: 10.8.1_seagpw47opwyivxvtfydnuwcuy
   ts-prune: 0.10.3
   ts-prune: 0.10.3
   typescript: 4.7.4
   typescript: 4.7.4
+  vite-plugin-ejs: 1.6.4
 
 
 packages:
 packages:
 
 
@@ -4266,6 +4268,10 @@ packages:
     engines: {node: '>=8'}
     engines: {node: '>=8'}
     dev: true
     dev: true
 
 
+  /async/3.2.4:
+    resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
+    dev: true
+
   /asynckit/0.4.0:
   /asynckit/0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
     dev: true
     dev: true
@@ -4514,6 +4520,12 @@ packages:
       balanced-match: 1.0.2
       balanced-match: 1.0.2
       concat-map: 0.0.1
       concat-map: 0.0.1
 
 
+  /brace-expansion/2.0.1:
+    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+    dependencies:
+      balanced-match: 1.0.2
+    dev: true
+
   /braces/3.0.2:
   /braces/3.0.2:
     resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
     resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
     engines: {node: '>=8'}
     engines: {node: '>=8'}
@@ -5040,6 +5052,14 @@ packages:
       wcwidth: 1.0.1
       wcwidth: 1.0.1
     dev: true
     dev: true
 
 
+  /ejs/3.1.8:
+    resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==}
+    engines: {node: '>=0.10.0'}
+    hasBin: true
+    dependencies:
+      jake: 10.8.5
+    dev: true
+
   /electron-to-chromium/1.4.151:
   /electron-to-chromium/1.4.151:
     resolution: {integrity: sha512-XaG2LpZi9fdiWYOqJh0dJy4SlVywCvpgYXhzOlZTp4JqSKqxn5URqOjbm9OMYB3aInA2GuHQiem1QUOc1yT0Pw==}
     resolution: {integrity: sha512-XaG2LpZi9fdiWYOqJh0dJy4SlVywCvpgYXhzOlZTp4JqSKqxn5URqOjbm9OMYB3aInA2GuHQiem1QUOc1yT0Pw==}
 
 
@@ -5740,6 +5760,12 @@ packages:
       flat-cache: 3.0.4
       flat-cache: 3.0.4
     dev: true
     dev: true
 
 
+  /filelist/1.0.4:
+    resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
+    dependencies:
+      minimatch: 5.1.2
+    dev: true
+
   /fill-range/7.0.1:
   /fill-range/7.0.1:
     resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
     resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
     engines: {node: '>=8'}
     engines: {node: '>=8'}
@@ -6340,6 +6366,17 @@ packages:
     engines: {node: '>=6'}
     engines: {node: '>=6'}
     dev: true
     dev: true
 
 
+  /jake/10.8.5:
+    resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      async: 3.2.4
+      chalk: 4.1.2
+      filelist: 1.0.4
+      minimatch: 3.1.2
+    dev: true
+
   /jest-changed-files/29.0.0:
   /jest-changed-files/29.0.0:
     resolution: {integrity: sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==}
     resolution: {integrity: sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -7176,6 +7213,13 @@ packages:
     dependencies:
     dependencies:
       brace-expansion: 1.1.11
       brace-expansion: 1.1.11
 
 
+  /minimatch/5.1.2:
+    resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==}
+    engines: {node: '>=10'}
+    dependencies:
+      brace-expansion: 2.0.1
+    dev: true
+
   /minimist/1.2.6:
   /minimist/1.2.6:
     resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
     resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
     dev: true
     dev: true
@@ -8675,6 +8719,12 @@ packages:
       '@types/istanbul-lib-coverage': 2.0.3
       '@types/istanbul-lib-coverage': 2.0.3
       convert-source-map: 1.7.0
       convert-source-map: 1.7.0
 
 
+  /vite-plugin-ejs/1.6.4:
+    resolution: {integrity: sha512-23p1RS4PiA0veXY5/gHZ60pl3pPvd8NEqdBsDgxNK8nM1rjFFDcVb0paNmuipzCgNP/Y0f/Id22M7Il4kvZ2jA==}
+    dependencies:
+      ejs: 3.1.8
+    dev: true
+
   /vite-tsconfig-paths/4.0.2_eqmiqdrctagsk5ranq2vs4ssty:
   /vite-tsconfig-paths/4.0.2_eqmiqdrctagsk5ranq2vs4ssty:
     resolution: {integrity: sha512-UzU8zwbCQrdUkj/Z0tnh293n4ScRcjJLoS8nPme2iB2FHoU5q8rhilb7AbhLlUC1uv4t6jSzVWnENjPnyGseeQ==}
     resolution: {integrity: sha512-UzU8zwbCQrdUkj/Z0tnh293n4ScRcjJLoS8nPme2iB2FHoU5q8rhilb7AbhLlUC1uv4t6jSzVWnENjPnyGseeQ==}
     peerDependencies:
     peerDependencies:

+ 45 - 51
kafka-ui-react-app/src/components/Cluster/Cluster.tsx

@@ -16,23 +16,15 @@ import {
 import ClusterContext from 'components/contexts/ClusterContext';
 import ClusterContext from 'components/contexts/ClusterContext';
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import { useClusters } from 'lib/hooks/api/clusters';
 import { useClusters } from 'lib/hooks/api/clusters';
-import Brokers from 'components/Brokers/Brokers';
-import Topics from 'components/Topics/Topics';
-import Schemas from 'components/Schemas/Schemas';
-import Connect from 'components/Connect/Connect';
-import KsqlDb from 'components/KsqlDb/KsqlDb';
-import ConsumerGroups from 'components/ConsumerGroups/ConsumerGroups';
 
 
-// We can't use Lazy loading till we have a better way to update publicPath in runtime
-// Now java app replaces paths in builded index.html file.
-// const Brokers = React.lazy(() => import('components/Brokers/Brokers'));
-// const Topics = React.lazy(() => import('components/Topics/Topics'));
-// const Schemas = React.lazy(() => import('components/Schemas/Schemas'));
-// const Connect = React.lazy(() => import('components/Connect/Connect'));
-// const KsqlDb = React.lazy(() => import('components/KsqlDb/KsqlDb'));
-// const ConsumerGroups = React.lazy(
-//   () => import('components/ConsumerGroups/ConsumerGroups')
-// );
+const Brokers = React.lazy(() => import('components/Brokers/Brokers'));
+const Topics = React.lazy(() => import('components/Topics/Topics'));
+const Schemas = React.lazy(() => import('components/Schemas/Schemas'));
+const Connect = React.lazy(() => import('components/Connect/Connect'));
+const KsqlDb = React.lazy(() => import('components/KsqlDb/KsqlDb'));
+const ConsumerGroups = React.lazy(
+  () => import('components/ConsumerGroups/ConsumerGroups')
+);
 
 
 const Cluster: React.FC = () => {
 const Cluster: React.FC = () => {
   const { clusterName } = useAppParams<ClusterNameRoute>();
   const { clusterName } = useAppParams<ClusterNameRoute>();
@@ -59,49 +51,51 @@ const Cluster: React.FC = () => {
   return (
   return (
     <Suspense fallback={<PageLoader />}>
     <Suspense fallback={<PageLoader />}>
       <ClusterContext.Provider value={contextValue}>
       <ClusterContext.Provider value={contextValue}>
-        <Routes>
-          <Route
-            path={getNonExactPath(clusterBrokerRelativePath)}
-            element={<Brokers />}
-          />
-          <Route
-            path={getNonExactPath(clusterTopicsRelativePath)}
-            element={<Topics />}
-          />
-          <Route
-            path={getNonExactPath(clusterConsumerGroupsRelativePath)}
-            element={<ConsumerGroups />}
-          />
-          {contextValue.hasSchemaRegistryConfigured && (
+        <Suspense fallback={<PageLoader />}>
+          <Routes>
             <Route
             <Route
-              path={getNonExactPath(clusterSchemasRelativePath)}
-              element={<Schemas />}
+              path={getNonExactPath(clusterBrokerRelativePath)}
+              element={<Brokers />}
             />
             />
-          )}
-          {contextValue.hasKafkaConnectConfigured && (
             <Route
             <Route
-              path={getNonExactPath(clusterConnectsRelativePath)}
-              element={<Connect />}
+              path={getNonExactPath(clusterTopicsRelativePath)}
+              element={<Topics />}
             />
             />
-          )}
-          {contextValue.hasKafkaConnectConfigured && (
             <Route
             <Route
-              path={getNonExactPath(clusterConnectorsRelativePath)}
-              element={<Connect />}
+              path={getNonExactPath(clusterConsumerGroupsRelativePath)}
+              element={<ConsumerGroups />}
             />
             />
-          )}
-          {contextValue.hasKsqlDbConfigured && (
+            {contextValue.hasSchemaRegistryConfigured && (
+              <Route
+                path={getNonExactPath(clusterSchemasRelativePath)}
+                element={<Schemas />}
+              />
+            )}
+            {contextValue.hasKafkaConnectConfigured && (
+              <Route
+                path={getNonExactPath(clusterConnectsRelativePath)}
+                element={<Connect />}
+              />
+            )}
+            {contextValue.hasKafkaConnectConfigured && (
+              <Route
+                path={getNonExactPath(clusterConnectorsRelativePath)}
+                element={<Connect />}
+              />
+            )}
+            {contextValue.hasKsqlDbConfigured && (
+              <Route
+                path={getNonExactPath(clusterKsqlDbRelativePath)}
+                element={<KsqlDb />}
+              />
+            )}
             <Route
             <Route
-              path={getNonExactPath(clusterKsqlDbRelativePath)}
-              element={<KsqlDb />}
+              path="/"
+              element={<Navigate to={clusterBrokerRelativePath} replace />}
             />
             />
-          )}
-          <Route
-            path="/"
-            element={<Navigate to={clusterBrokerRelativePath} replace />}
-          />
-        </Routes>
-        <Outlet />
+          </Routes>
+          <Outlet />
+        </Suspense>
       </ClusterContext.Provider>
       </ClusterContext.Provider>
     </Suspense>
     </Suspense>
   );
   );

+ 4 - 1
kafka-ui-react-app/src/components/Cluster/__tests__/Cluster.spec.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import React from 'react';
 import { Cluster, ClusterFeaturesEnum } from 'generated-sources';
 import { Cluster, ClusterFeaturesEnum } from 'generated-sources';
 import ClusterComponent from 'components/Cluster/Cluster';
 import ClusterComponent from 'components/Cluster/Cluster';
-import { screen } from '@testing-library/react';
+import { screen, waitFor } from '@testing-library/react';
 import { render, WithRoute } from 'lib/testHelpers';
 import { render, WithRoute } from 'lib/testHelpers';
 import {
 import {
   clusterBrokersPath,
   clusterBrokersPath,
@@ -59,6 +59,9 @@ describe('Cluster', () => {
       </WithRoute>,
       </WithRoute>,
       { initialEntries: [pathname] }
       { initialEntries: [pathname] }
     );
     );
+    await waitFor(() => {
+      expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
+    });
   };
   };
 
 
   it('renders Brokers', async () => {
   it('renders Brokers', async () => {

+ 29 - 1
kafka-ui-react-app/vite.config.ts

@@ -6,18 +6,46 @@ import {
 } from 'vite';
 } from 'vite';
 import react from '@vitejs/plugin-react-swc';
 import react from '@vitejs/plugin-react-swc';
 import tsconfigPaths from 'vite-tsconfig-paths';
 import tsconfigPaths from 'vite-tsconfig-paths';
+import { ViteEjsPlugin } from 'vite-plugin-ejs';
 
 
 export default defineConfig(({ mode }) => {
 export default defineConfig(({ mode }) => {
   process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
   process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
 
 
   const defaultConfig: UserConfigExport = {
   const defaultConfig: UserConfigExport = {
-    plugins: [react(), tsconfigPaths(), splitVendorChunkPlugin()],
+    plugins: [
+      react(),
+      tsconfigPaths(),
+      splitVendorChunkPlugin(),
+      ViteEjsPlugin({
+        PUBLIC_PATH: mode !== 'development' ? 'PUBLIC-PATH-VARIABLE' : '',
+      }),
+    ],
     server: {
     server: {
       port: 3000,
       port: 3000,
     },
     },
     build: {
     build: {
       outDir: 'build',
       outDir: 'build',
     },
     },
+    experimental: {
+      renderBuiltUrl(
+        filename: string,
+        {
+          hostType,
+        }: {
+          hostId: string;
+          hostType: 'js' | 'css' | 'html';
+          type: 'asset' | 'public';
+        }
+      ) {
+        if (hostType === 'js') {
+          return {
+            runtime: `window.__assetsPathBuilder(${JSON.stringify(filename)})`,
+          };
+        }
+
+        return filename;
+      },
+    },
     define: {
     define: {
       'process.env.NODE_ENV': `"${mode}"`,
       'process.env.NODE_ENV': `"${mode}"`,
       'process.env.VITE_TAG': `"${process.env.VITE_TAG}"`,
       'process.env.VITE_TAG': `"${process.env.VITE_TAG}"`,