瀏覽代碼

OpenAPI integration for kafka-ui frontend (#120)

* Added react openapi gen

* Integrated Openapi to React

Co-authored-by: German Osin <german.osin@gmail.com>
Co-authored-by: Sofia Shnaidman <sshnaidman@provectus.com>
soffest 4 年之前
父節點
當前提交
494443bb08
共有 54 個文件被更改,包括 749 次插入815 次删除
  1. 1 0
      .gitignore
  2. 50 2
      kafka-ui-contract/pom.xml
  3. 1 1
      kafka-ui-react-app/.env
  4. 4 1
      kafka-ui-react-app/.gitignore
  5. 348 316
      kafka-ui-react-app/package-lock.json
  6. 9 9
      kafka-ui-react-app/src/components/Brokers/Brokers.tsx
  7. 2 2
      kafka-ui-react-app/src/components/Brokers/BrokersContainer.ts
  8. 4 4
      kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx
  9. 1 1
      kafka-ui-react-app/src/components/ConsumerGroups/Details/DetailsContainer.ts
  10. 2 2
      kafka-ui-react-app/src/components/ConsumerGroups/Details/ListItem.tsx
  11. 6 1
      kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx
  12. 1 1
      kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx
  13. 4 4
      kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx
  14. 2 1
      kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClustersWidget.tsx
  15. 2 2
      kafka-ui-react-app/src/components/Nav/ClusterMenu.tsx
  16. 2 2
      kafka-ui-react-app/src/components/Nav/Nav.tsx
  17. 2 1
      kafka-ui-react-app/src/components/Topics/Details/Details.tsx
  18. 6 2
      kafka-ui-react-app/src/components/Topics/Details/DetailsContainer.ts
  19. 23 22
      kafka-ui-react-app/src/components/Topics/Details/Messages/Messages.tsx
  20. 2 1
      kafka-ui-react-app/src/components/Topics/Details/Overview/Overview.tsx
  21. 2 1
      kafka-ui-react-app/src/components/Topics/Details/Settings/Settings.tsx
  22. 6 5
      kafka-ui-react-app/src/components/Topics/Edit/Edit.tsx
  23. 2 2
      kafka-ui-react-app/src/components/Topics/Edit/EditContainer.tsx
  24. 3 3
      kafka-ui-react-app/src/components/Topics/List/ListItem.tsx
  25. 5 4
      kafka-ui-react-app/src/components/Topics/New/New.tsx
  26. 25 12
      kafka-ui-react-app/src/components/Topics/New/NewContainer.ts
  27. 4 4
      kafka-ui-react-app/src/components/Topics/Topics.tsx
  28. 2 2
      kafka-ui-react-app/src/components/Topics/TopicsContainer.ts
  29. 3 2
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamField.tsx
  30. 2 2
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamOptions.tsx
  31. 2 2
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx
  32. 3 2
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamValue.tsx
  33. 6 9
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx
  34. 4 4
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/customParamsOptions.tsx
  35. 4 4
      kafka-ui-react-app/src/lib/constants.ts
  36. 8 0
      kafka-ui-react-app/src/redux/actionType.ts
  37. 20 6
      kafka-ui-react-app/src/redux/actions/actions.ts
  38. 120 35
      kafka-ui-react-app/src/redux/actions/thunks.ts
  39. 0 14
      kafka-ui-react-app/src/redux/api/brokers.ts
  40. 0 11
      kafka-ui-react-app/src/redux/api/clusters.ts
  41. 0 12
      kafka-ui-react-app/src/redux/api/consumerGroups.ts
  42. 0 4
      kafka-ui-react-app/src/redux/api/index.ts
  43. 0 142
      kafka-ui-react-app/src/redux/api/topics.ts
  44. 3 26
      kafka-ui-react-app/src/redux/interfaces/broker.ts
  45. 3 16
      kafka-ui-react-app/src/redux/interfaces/cluster.ts
  46. 3 24
      kafka-ui-react-app/src/redux/interfaces/consumerGroup.ts
  47. 2 2
      kafka-ui-react-app/src/redux/interfaces/index.ts
  48. 27 72
      kafka-ui-react-app/src/redux/interfaces/topic.ts
  49. 6 11
      kafka-ui-react-app/src/redux/reducers/brokers/reducer.ts
  50. 2 1
      kafka-ui-react-app/src/redux/reducers/clusters/reducer.ts
  51. 4 3
      kafka-ui-react-app/src/redux/reducers/clusters/selectors.ts
  52. 2 1
      kafka-ui-react-app/src/redux/reducers/consumerGroups/reducer.ts
  53. 2 1
      kafka-ui-react-app/src/redux/reducers/topics/reducer.ts
  54. 2 1
      kafka-ui-react-app/src/redux/reducers/topics/selectors.ts

+ 1 - 0
.gitignore

@@ -32,3 +32,4 @@ build/
 /kafka-ui-api/app/node
 
 .DS_Store
+*.code-workspace

+ 50 - 2
kafka-ui-contract/pom.xml

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <artifactId>kafka-ui</artifactId>
         <groupId>com.provectus</groupId>
@@ -70,8 +69,57 @@
                                     </configOptions>
                                 </configuration>
                             </execution>
+                            <execution>
+                                <id>generate-frontend-api</id>
+                                <goals>
+                                    <goal>generate</goal>
+                                </goals>
+                                <configuration>
+                                    <inputSpec>${project.basedir}/src/main/resources/swagger/kafka-ui-api.yaml
+                                    </inputSpec>
+                                    <output>${project.build.directory}/generated-sources/frontend/</output>
+                                    <generatorName>typescript-fetch</generatorName>
+                                    <configOptions>
+                                        <modelPackage>com.provectus.kafka.ui.model</modelPackage>
+                                        <apiPackage>com.provectus.kafka.ui.api</apiPackage>
+                                        <apiPackage>com.provectus.kafka.ui.invoker</apiPackage>
+                                        <sourceFolder>kafka-ui-contract</sourceFolder>
+                                        <typescriptThreePlus>true</typescriptThreePlus>
+                                        <supportsES6>true</supportsES6>
+                                        <nullSafeAdditionalProps>true</nullSafeAdditionalProps>
+                                        <withInterfaces>true</withInterfaces>
+                                    </configOptions>
+                                </configuration>
+                            </execution>
                         </executions>
                     </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <version>3.1.0</version>
+                        <executions>
+                            <execution>
+                                <id>copy-resource-one</id>
+                                <phase>generate-resources</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+
+                                <configuration>
+                                    <outputDirectory>${basedir}/..//kafka-ui-react-app/src/generated-sources</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>${project.build.directory}/generated-sources/frontend/</directory>
+                                            <includes>
+                                                <include>**/*.ts</include>
+                                            </includes>
+                                        </resource>
+                                    </resources>
+                                </configuration>
+                            </execution>
+                        </executions>
+
+                    </plugin>
                 </plugins>
             </build>
         </profile>

+ 1 - 1
kafka-ui-react-app/.env

@@ -1,2 +1,2 @@
 # Kafka REST API
-REACT_APP_API_URL=/api
+REACT_APP_API_URL=

+ 4 - 1
kafka-ui-react-app/.gitignore

@@ -23,4 +23,7 @@ npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
 
-.idea
+.idea
+
+# generated sources
+src/generated-sources

文件差異過大導致無法顯示
+ 348 - 316
kafka-ui-react-app/package-lock.json


+ 9 - 9
kafka-ui-react-app/src/components/Brokers/Brokers.tsx

@@ -1,21 +1,21 @@
 import React from 'react';
-import { ClusterName, BrokerMetrics, ZooKeeperStatus } from 'redux/interfaces';
+import { ClusterName, ZooKeeperStatus } from 'redux/interfaces';
+import { ClusterStats } from 'generated-sources';
 import useInterval from 'lib/hooks/useInterval';
 import cx from 'classnames';
 import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
 import Indicator from 'components/common/Dashboard/Indicator';
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 
-interface Props extends BrokerMetrics {
+interface Props extends ClusterStats {
   clusterName: ClusterName;
   isFetched: boolean;
+  fetchClusterStats: (clusterName: ClusterName) => void;
   fetchBrokers: (clusterName: ClusterName) => void;
-  fetchBrokerMetrics: (clusterName: ClusterName) => void;
 }
 
 const Topics: React.FC<Props> = ({
   clusterName,
-  isFetched,
   brokerCount,
   activeControllers,
   zooKeeperStatus,
@@ -24,18 +24,18 @@ const Topics: React.FC<Props> = ({
   inSyncReplicasCount,
   outOfSyncReplicasCount,
   underReplicatedPartitionCount,
+  fetchClusterStats,
   fetchBrokers,
-  fetchBrokerMetrics,
 }) => {
   React.useEffect(
     () => {
+      fetchClusterStats(clusterName);
       fetchBrokers(clusterName);
-      fetchBrokerMetrics(clusterName);
     },
-    [fetchBrokers, fetchBrokerMetrics, clusterName],
+    [fetchClusterStats, fetchBrokers, clusterName],
   );
 
-  useInterval(() => { fetchBrokerMetrics(clusterName); }, 5000);
+  useInterval(() => { fetchClusterStats(clusterName); }, 5000);
 
   const zkOnline = zooKeeperStatus === ZooKeeperStatus.online;
 
@@ -62,7 +62,7 @@ const Topics: React.FC<Props> = ({
           <span className={cx({'has-text-danger': offlinePartitionCount !== 0})}>
             {onlinePartitionCount}
           </span>
-          <span className="subtitle has-text-weight-light"> of {onlinePartitionCount + offlinePartitionCount}</span>
+          <span className="subtitle has-text-weight-light"> of {(onlinePartitionCount || 0) + (offlinePartitionCount || 0)}</span>
         </Indicator>
         <Indicator label="URP" title="Under replicated partitions">
           {underReplicatedPartitionCount}

+ 2 - 2
kafka-ui-react-app/src/components/Brokers/BrokersContainer.ts

@@ -1,7 +1,7 @@
 import { connect } from 'react-redux';
 import {
+  fetchClusterStats,
   fetchBrokers,
-  fetchBrokerMetrics,
 } from 'redux/actions';
 import Brokers from './Brokers';
 import * as brokerSelectors from 'redux/reducers/brokers/selectors';
@@ -28,8 +28,8 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}
 });
 
 const mapDispatchToProps = {
+  fetchClusterStats: (clusterName: ClusterName) => fetchClusterStats(clusterName),
   fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
-  fetchBrokerMetrics: (clusterName: ClusterName) => fetchBrokerMetrics(clusterName),
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(Brokers);

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

@@ -2,12 +2,12 @@ import React from 'react';
 import { ClusterName } from 'redux/interfaces';
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import { clusterConsumerGroupsPath } from 'lib/paths';
+import { ConsumerGroupID } from 'redux/interfaces/consumerGroup';
 import {
-  ConsumerGroupID,
   ConsumerGroup,
   ConsumerGroupDetails,
-  Consumer,
-} from 'redux/interfaces/consumerGroup';
+  ConsumerTopicPartitionDetail,
+} from 'generated-sources';
 
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import ListItem from './ListItem';
@@ -15,7 +15,7 @@ import ListItem from './ListItem';
 interface Props extends ConsumerGroup, ConsumerGroupDetails {
   clusterName: ClusterName;
   consumerGroupID: ConsumerGroupID;
-  consumers: Consumer[];
+  consumers?: ConsumerTopicPartitionDetail[];
   isFetched: boolean;
   fetchConsumerGroupDetails: (
     clusterName: ClusterName,

+ 1 - 1
kafka-ui-react-app/src/components/ConsumerGroups/Details/DetailsContainer.ts

@@ -8,7 +8,7 @@ import { fetchConsumerGroupDetails } from 'redux/actions/thunks';
 
 interface RouteProps {
   clusterName: ClusterName;
-  consumerGroupID: string;
+  consumerGroupID: ConsumerGroupID;
 }
 
 interface OwnProps extends RouteComponentProps<RouteProps> { }

+ 2 - 2
kafka-ui-react-app/src/components/ConsumerGroups/Details/ListItem.tsx

@@ -1,11 +1,11 @@
 import React from 'react';
-import { Consumer } from 'redux/interfaces/consumerGroup';
+import { ConsumerTopicPartitionDetail } from 'generated-sources';
 import { NavLink } from 'react-router-dom';
 import { ClusterName } from 'redux/interfaces/cluster';
 
 interface Props {
   clusterName: ClusterName;
-  consumer: Consumer;
+  consumer: ConsumerTopicPartitionDetail;
 }
 
 const ListItem: React.FC<Props> = ({ clusterName, consumer }) => {

+ 6 - 1
kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx

@@ -1,5 +1,10 @@
 import React from 'react';
-import { ClusterName, ConsumerGroup } from 'redux/interfaces';
+import {
+  ClusterName
+} from 'redux/interfaces';
+import {
+  ConsumerGroup
+} from 'generated-sources';
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import ListItem from './ListItem';
 

+ 1 - 1
kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { useHistory } from 'react-router-dom';
-import { ConsumerGroup } from 'redux/interfaces';
+import { ConsumerGroup } from 'generated-sources';
 
 const ListItem: React.FC<{ consumerGroup: ConsumerGroup }> = ({
   consumerGroup,

+ 4 - 4
kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx

@@ -1,8 +1,8 @@
 import React from 'react';
-import { Cluster, ClusterStatus } from 'redux/interfaces';
 import formatBytes from 'lib/utils/formatBytes';
 import { NavLink } from 'react-router-dom';
 import { clusterBrokersPath } from 'lib/paths';
+import { Cluster, ServerStatus } from 'generated-sources';
 
 const ClusterWidget: React.FC<Cluster> = ({
   name,
@@ -20,7 +20,7 @@ const ClusterWidget: React.FC<Cluster> = ({
         title={name}
       >
         <div
-          className={`tag has-margin-right ${status === ClusterStatus.Online ? 'is-primary' : 'is-danger'}`}
+          className={`tag has-margin-right ${status === ServerStatus.Online ? 'is-primary' : 'is-danger'}`}
         >
           {status}
         </div>
@@ -43,11 +43,11 @@ const ClusterWidget: React.FC<Cluster> = ({
           </tr>
           <tr>
             <th>Production</th>
-            <td>{formatBytes(bytesInPerSec)}</td>
+            <td>{formatBytes(bytesInPerSec || 0)}</td>
           </tr>
           <tr>
             <th>Consumption</th>
-            <td>{formatBytes(bytesOutPerSec)}</td>
+            <td>{formatBytes(bytesOutPerSec || 0)}</td>
           </tr>
         </tbody>
       </table>

+ 2 - 1
kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClustersWidget.tsx

@@ -1,9 +1,10 @@
 import React from 'react';
 import { chunk } from 'lodash';
-import { Cluster } from 'redux/interfaces';
+
 import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
 import Indicator from 'components/common/Dashboard/Indicator';
 import ClusterWidget from './ClusterWidget';
+import { Cluster } from 'generated-sources';
 
 interface Props {
   clusters: Cluster[];

+ 2 - 2
kafka-ui-react-app/src/components/Nav/ClusterMenu.tsx

@@ -1,11 +1,11 @@
 import React, { CSSProperties } from 'react';
-import { Cluster, ClusterStatus } from 'redux/interfaces';
 import { NavLink } from 'react-router-dom';
 import {
   clusterBrokersPath,
   clusterTopicsPath,
   clusterConsumerGroupsPath,
 } from 'lib/paths';
+import { Cluster, ServerStatus } from 'generated-sources';
 
 interface Props {
   cluster: Cluster;
@@ -42,7 +42,7 @@ const StatusIcon: React.FC<Props> = ({ cluster }) => {
   return (
     <span
       className={`tag ${
-        cluster.status === ClusterStatus.Online ? 'is-primary' : 'is-danger'
+        cluster.status === ServerStatus.Online ? 'is-primary' : 'is-danger'
       }`}
       title={cluster.status}
       style={style}

+ 2 - 2
kafka-ui-react-app/src/components/Nav/Nav.tsx

@@ -1,8 +1,8 @@
 import React from 'react';
-import { Cluster } from 'redux/interfaces';
 import { NavLink } from 'react-router-dom';
 import cx from 'classnames';
 import ClusterMenu from './ClusterMenu';
+import { Cluster } from 'generated-sources';
 
 interface Props {
   isClusterListFetched: boolean;
@@ -29,7 +29,7 @@ const Nav: React.FC<Props> = ({
 
     {isClusterListFetched &&
       clusters.map((cluster) => (
-        <ClusterMenu cluster={cluster} key={cluster.id} />
+        <ClusterMenu cluster={cluster} key={cluster.name} />
       ))}
   </aside>
 );

+ 2 - 1
kafka-ui-react-app/src/components/Topics/Details/Details.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
-import { ClusterName, Topic, TopicDetails, TopicName } from 'redux/interfaces';
+import { ClusterName, TopicName } from 'redux/interfaces';
+import { Topic, TopicDetails } from 'generated-sources';
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import { NavLink, Switch, Route } from 'react-router-dom';
 import {

+ 6 - 2
kafka-ui-react-app/src/components/Topics/Details/DetailsContainer.ts

@@ -1,11 +1,15 @@
 import { connect } from 'react-redux';
 import Details from './Details';
-import {ClusterName, RootState} from 'redux/interfaces';
+import {
+  ClusterName,
+  RootState,
+  TopicName
+} from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
 
 interface RouteProps {
   clusterName: ClusterName;
-  topicName: string;
+  topicName: TopicName;
 }
 
 interface OwnProps extends RouteComponentProps<RouteProps> { }

+ 23 - 22
kafka-ui-react-app/src/components/Topics/Details/Messages/Messages.tsx

@@ -1,13 +1,14 @@
 import React, { useCallback, useEffect, useRef } from 'react';
 import {
   ClusterName,
-  SeekType,
-  SeekTypes,
-  TopicMessage,
   TopicMessageQueryParams,
-  TopicName,
-  TopicPartition,
+  TopicName
 } from 'redux/interfaces';
+import {
+  TopicMessage,
+  Partition,
+  SeekType
+} from 'generated-sources';
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import { format } from 'date-fns';
 import DatePicker from 'react-datepicker';
@@ -33,12 +34,12 @@ interface Props {
     queryParams: Partial<TopicMessageQueryParams>
   ) => void;
   messages: TopicMessage[];
-  partitions: TopicPartition[];
+  partitions: Partition[];
 }
 
 interface FilterProps {
-  offset: number;
-  partition: number;
+  offset: TopicMessage['offset'];
+  partition: TopicMessage['partition'];
 }
 
 function usePrevious(value: any) {
@@ -63,7 +64,7 @@ const Messages: React.FC<Props> = ({
   );
   const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
   const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
-    SeekTypes.OFFSET
+    SeekType.OFFSET
   );
   const [searchOffset, setSearchOffset] = React.useState<string>('0');
   const [selectedPartitions, setSelectedPartitions] = React.useState<Option[]>(
@@ -105,7 +106,7 @@ const Messages: React.FC<Props> = ({
     const foundedValues = filterProps.find(
       (prop) => prop.partition === partition.value
     );
-    if (selectedSeekType === SeekTypes.OFFSET) {
+    if (selectedSeekType === SeekType.OFFSET) {
       return foundedValues ? foundedValues.offset : 0;
     }
     return searchTimestamp ? searchTimestamp.getTime() : null;
@@ -134,7 +135,7 @@ const Messages: React.FC<Props> = ({
         setSearchTimestamp(searchTimestamp);
         setQueryParams({
           ...queryParams,
-          seekType: SeekTypes.TIMESTAMP,
+          seekType: SeekType.TIMESTAMP,
           seekTo: selectedPartitions.map((p) => `${p.value}::${timestamp}`),
         });
       } else {
@@ -155,7 +156,7 @@ const Messages: React.FC<Props> = ({
     const offset = event.target.value || '0';
     setSearchOffset(offset);
     debouncedCallback({
-      seekType: SeekTypes.OFFSET,
+      seekType: SeekType.OFFSET,
       seekTo: selectedPartitions.map((p) => `${p.value}::${offset}`),
     });
   };
@@ -176,9 +177,9 @@ const Messages: React.FC<Props> = ({
     fetchTopicMessages(clusterName, topicName, queryParams);
   }, [clusterName, topicName, queryParams]);
 
-  const getTimestampDate = (timestamp: string) => {
-    if (!Date.parse(timestamp)) return;
-    return format(Date.parse(timestamp), 'yyyy-MM-dd HH:mm:ss');
+  const getFormattedDate = (date: Date) => {
+    if (!date) return null;
+    return format(date, 'yyyy-MM-dd HH:mm:ss');
   };
 
   const getMessageContentBody = (content: any) => {
@@ -224,7 +225,7 @@ const Messages: React.FC<Props> = ({
 
     setQueryParams({
       ...queryParams,
-      seekType: SeekTypes.OFFSET,
+      seekType: SeekType.OFFSET,
       seekTo,
     });
     fetchTopicMessages(clusterName, topicName, queryParams);
@@ -255,7 +256,7 @@ const Messages: React.FC<Props> = ({
             {messages.map((message) => (
               <tr key={`${message.timestamp}${Math.random()}`}>
                 <td style={{ width: 200 }}>
-                  {getTimestampDate(message.timestamp)}
+                  {getFormattedDate(message.timestamp)}
                 </td>
                 <td style={{ width: 150 }}>{message.offset}</td>
                 <td style={{ width: 100 }}>{message.partition}</td>
@@ -309,16 +310,16 @@ const Messages: React.FC<Props> = ({
               id="selectSeekType"
               name="selectSeekType"
               onChange={handleSeekTypeChange}
-              defaultValue={SeekTypes.OFFSET}
+              defaultValue={SeekType.OFFSET}
               value={selectedSeekType}
             >
-              <option value={SeekTypes.OFFSET}>Offset</option>
-              <option value={SeekTypes.TIMESTAMP}>Timestamp</option>
+              <option value={SeekType.OFFSET}>Offset</option>
+              <option value={SeekType.TIMESTAMP}>Timestamp</option>
             </select>
           </div>
         </div>
         <div className="column is-one-fifth">
-          {selectedSeekType === SeekTypes.OFFSET ? (
+          {selectedSeekType === SeekType.OFFSET ? (
             <>
               <label className="label">Offset</label>
               <input
@@ -335,7 +336,7 @@ const Messages: React.FC<Props> = ({
               <label className="label">Timestamp</label>
               <DatePicker
                 selected={searchTimestamp}
-                onChange={(date) => setSearchTimestamp(date)}
+                onChange={(date: Date | null) => setSearchTimestamp(date)}
                 onCalendarClose={handleDateTimeChange}
                 showTimeInput
                 timeInputLabel="Time:"

+ 2 - 1
kafka-ui-react-app/src/components/Topics/Details/Overview/Overview.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
-import { ClusterName, Topic, TopicDetails, TopicName } from 'redux/interfaces';
+import { ClusterName, TopicName } from 'redux/interfaces';
+import { Topic, TopicDetails } from 'generated-sources';
 import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
 import Indicator from 'components/common/Dashboard/Indicator';
 

+ 2 - 1
kafka-ui-react-app/src/components/Topics/Details/Settings/Settings.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
-import { ClusterName, TopicName, TopicConfig } from 'redux/interfaces';
+import { ClusterName, TopicName } from 'redux/interfaces';
+import { TopicConfig } from 'generated-sources';
 
 interface Props {
   clusterName: ClusterName;

+ 6 - 5
kafka-ui-react-app/src/components/Topics/Edit/Edit.tsx

@@ -1,12 +1,13 @@
 import React from 'react';
 import {
   ClusterName,
-  TopicFormData,
+  TopicFormDataRaw,
   TopicName,
   TopicConfigByName,
   TopicWithDetailedInfo,
   CleanupPolicy,
 } from 'redux/interfaces';
+import { TopicConfig } from 'generated-sources';
 import { useForm, FormContext } from 'react-hook-form';
 import { camelCase } from 'lodash';
 
@@ -22,7 +23,7 @@ interface Props {
   isTopicUpdated: boolean;
   fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => void;
   fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => void;
-  updateTopic: (clusterName: ClusterName, form: TopicFormData) => void;
+  updateTopic: (clusterName: ClusterName, form: TopicFormDataRaw) => void;
   redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => void;
   resetUploadedState: () => void;
 }
@@ -44,7 +45,7 @@ const topicParams = (topic: TopicWithDetailedInfo | undefined) => {
   const { name, replicationFactor } = topic;
 
   const configs = topic.config?.reduce(
-    (result: { [name: string]: string }, param) => {
+    (result: { [key: string]: TopicConfig['value'] }, param) => {
       result[camelCase(param.name)] = param.value || param.defaultValue;
       return result;
     },
@@ -76,7 +77,7 @@ const Edit: React.FC<Props> = ({
 }) => {
   const defaultValues = topicParams(topic);
 
-  const methods = useForm<TopicFormData>({ defaultValues });
+  const methods = useForm<TopicFormDataRaw>({ defaultValues });
 
   const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
 
@@ -109,7 +110,7 @@ const Edit: React.FC<Props> = ({
     config.byName[param.name] = param;
   });
 
-  const onSubmit = async (data: TopicFormData) => {
+  const onSubmit = async (data: TopicFormDataRaw) => {
     updateTopic(clusterName, data);
     setIsSubmitting(true); // Keep this action after updateTopic to prevent redirect before update.
   };

+ 2 - 2
kafka-ui-react-app/src/components/Topics/Edit/EditContainer.tsx

@@ -2,7 +2,6 @@ import { connect } from 'react-redux';
 import {
   RootState,
   ClusterName,
-  TopicFormData,
   TopicName,
   Action,
 } from 'redux/interfaces';
@@ -21,6 +20,7 @@ import {
 import { clusterTopicPath } from 'lib/paths';
 import { ThunkDispatch } from 'redux-thunk';
 import Edit from './Edit';
+import { TopicFormDataRaw } from 'redux/interfaces';
 
 interface RouteProps {
   clusterName: ClusterName;
@@ -53,7 +53,7 @@ const mapDispatchToProps = (
     dispatch(fetchTopicDetails(clusterName, topicName)),
   fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) =>
     dispatch(fetchTopicConfig(clusterName, topicName)),
-  updateTopic: (clusterName: ClusterName, form: TopicFormData) =>
+  updateTopic: (clusterName: ClusterName, form: TopicFormDataRaw) =>
     dispatch(updateTopic(clusterName, form)),
   redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
     history.push(clusterTopicPath(clusterName, topicName));

+ 3 - 3
kafka-ui-react-app/src/components/Topics/List/ListItem.tsx

@@ -14,8 +14,8 @@ const ListItem: React.FC<TopicWithDetailedInfo> = ({
     }
 
     return partitions.reduce((memo: number, { replicas }) => {
-      const outOfSync = replicas.filter(({ inSync }) => !inSync)
-      return memo + outOfSync.length;
+      const outOfSync = replicas?.filter(({ inSync }) => !inSync)
+      return memo + (outOfSync?.length || 0);
     }, 0);
   }, [partitions])
 
@@ -26,7 +26,7 @@ const ListItem: React.FC<TopicWithDetailedInfo> = ({
           {name}
         </NavLink>
       </td>
-      <td>{partitions.length}</td>
+      <td>{partitions?.length}</td>
       <td>{outOfSyncReplicas}</td>
       <td>
         <div className={cx('tag is-small', internal ? 'is-light' : 'is-success')}>

+ 5 - 4
kafka-ui-react-app/src/components/Topics/New/New.tsx

@@ -1,15 +1,16 @@
 import React from 'react';
-import { ClusterName, TopicFormData, TopicName } from 'redux/interfaces';
+import { ClusterName, TopicName } from 'redux/interfaces';
 import { useForm, FormContext } from 'react-hook-form';
 
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import { clusterTopicsPath } from 'lib/paths';
 import TopicForm from 'components/Topics/shared/Form/TopicForm';
+import { TopicFormDataRaw } from 'redux/interfaces';
 
 interface Props {
   clusterName: ClusterName;
   isTopicCreated: boolean;
-  createTopic: (clusterName: ClusterName, form: TopicFormData) => void;
+  createTopic: (clusterName: ClusterName, form: TopicFormDataRaw) => void;
   redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => void;
   resetUploadedState: () => void;
 }
@@ -21,7 +22,7 @@ const New: React.FC<Props> = ({
   redirectToTopicPath,
   resetUploadedState,
 }) => {
-  const methods = useForm<TopicFormData>();
+  const methods = useForm<TopicFormDataRaw>();
   const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
 
   React.useEffect(() => {
@@ -31,7 +32,7 @@ const New: React.FC<Props> = ({
     }
   }, [isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, methods]);
 
-  const onSubmit = async (data: TopicFormData) => {
+  const onSubmit = async (data: TopicFormDataRaw) => {
     // TODO: need to fix loader. After success loading the first time, we won't wait for creation any more, because state is
     // loaded, and we will try to get entity immediately after pressing the button, and we will receive null
     // going to object page on the second creation. Setting of isSubmitting after createTopic is a workaround, need to tweak loader logic

+ 25 - 12
kafka-ui-react-app/src/components/Topics/New/NewContainer.ts

@@ -1,35 +1,48 @@
 import { connect } from 'react-redux';
-import { RootState, ClusterName, TopicFormData, TopicName, Action } from 'redux/interfaces';
-import New from './New';
+import {
+  RootState,
+  ClusterName,
+  TopicName,
+  Action,
+  TopicFormDataRaw,
+} from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
 import { createTopic } from 'redux/actions';
 import { getTopicCreated } from 'redux/reducers/topics/selectors';
 import { clusterTopicPath } from 'lib/paths';
 import { ThunkDispatch } from 'redux-thunk';
-import * as actions from "../../../redux/actions/actions";
+import * as actions from 'redux/actions';
+import New from './New';
 
 interface RouteProps {
   clusterName: ClusterName;
 }
 
-interface OwnProps extends RouteComponentProps<RouteProps> { }
+type OwnProps = RouteComponentProps<RouteProps>;
 
-const mapStateToProps = (state: RootState, { match: { params: { clusterName } } }: OwnProps) => ({
+const mapStateToProps = (
+  state: RootState,
+  {
+    match: {
+      params: { clusterName },
+    },
+  }: OwnProps
+) => ({
   clusterName,
   isTopicCreated: getTopicCreated(state),
 });
 
-const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, undefined, Action>, { history }: OwnProps) => ({
-  createTopic: (clusterName: ClusterName, form: TopicFormData) => {
+const mapDispatchToProps = (
+  dispatch: ThunkDispatch<RootState, undefined, Action>,
+  { history }: OwnProps
+) => ({
+  createTopic: (clusterName: ClusterName, form: TopicFormDataRaw) => {
     dispatch(createTopic(clusterName, form));
   },
   redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
     history.push(clusterTopicPath(clusterName, topicName));
   },
-  resetUploadedState: (() => dispatch(actions.createTopicAction.failure()))
+  resetUploadedState: () => dispatch(actions.createTopicAction.failure()),
 });
 
-
-export default withRouter(
-  connect(mapStateToProps, mapDispatchToProps)(New)
-);
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(New));

+ 4 - 4
kafka-ui-react-app/src/components/Topics/Topics.tsx

@@ -11,17 +11,17 @@ interface Props {
   clusterName: ClusterName;
   isFetched: boolean;
   fetchBrokers: (clusterName: ClusterName) => void;
-  fetchTopicList: (clusterName: ClusterName) => void;
+  fetchTopicsList: (clusterName: ClusterName) => void;
 }
 
 const Topics: React.FC<Props> = ({
   clusterName,
   isFetched,
-  fetchTopicList,
+  fetchTopicsList,
 }) => {
   React.useEffect(() => {
-    fetchTopicList(clusterName);
-  }, [fetchTopicList, clusterName]);
+    fetchTopicsList(clusterName);
+  }, [fetchTopicsList, clusterName]);
 
   if (isFetched) {
     return (

+ 2 - 2
kafka-ui-react-app/src/components/Topics/TopicsContainer.ts

@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { fetchTopicList } from 'redux/actions';
+import { fetchTopicsList } from 'redux/actions';
 import Topics from './Topics';
 import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
 import { RootState, ClusterName } from 'redux/interfaces';
@@ -17,7 +17,7 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}
 });
 
 const mapDispatchToProps = {
-  fetchTopicList: (clusterName: ClusterName) => fetchTopicList(clusterName),
+  fetchTopicsList: (clusterName: ClusterName) => fetchTopicsList(clusterName),
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(Topics);

+ 3 - 2
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamField.tsx

@@ -2,13 +2,14 @@ import React from 'react';
 import CustomParamSelect from 'components/Topics/shared/Form/CustomParams/CustomParamSelect';
 import CustomParamValue from 'components/Topics/shared/Form/CustomParams/CustomParamValue';
 import CustomParamAction from 'components/Topics/shared/Form/CustomParams/CustomParamAction';
+import { TopicConfig } from 'generated-sources';
 
 interface Props {
   isDisabled: boolean;
   index: string;
-  name: string;
+  name: TopicConfig['name'];
   existingFields: string[];
-  defaultValue: string;
+  defaultValue: TopicConfig['defaultValue'];
   onNameChange: (inputName: string, name: string) => void;
   onRemove: (index: string) => void;
 }

+ 2 - 2
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamOptions.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { TopicCustomParamOption } from 'redux/interfaces';
+import { TopicConfigOption } from 'redux/interfaces';
 import { omitBy } from 'lodash';
 import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
 
@@ -15,7 +15,7 @@ const CustomParamOptions: React.FC<Props> = ({ existingFields }) => {
   return (
     <>
       <option value="">Select</option>
-      {Object.values(fields).map((opt: TopicCustomParamOption) => (
+      {Object.values(fields).map((opt: TopicConfigOption) => (
         <option key={opt.name} value={opt.name}>
           {opt.name}
         </option>

+ 2 - 2
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { useFormContext, ErrorMessage } from 'react-hook-form';
-import { TopicFormCustomParam } from 'redux/interfaces';
+import { TopicConfigValue } from 'redux/interfaces';
 import CustomParamOptions from './CustomParamOptions';
 import { INDEX_PREFIX } from './CustomParams';
 
@@ -24,7 +24,7 @@ const CustomParamSelect: React.FC<Props> = ({
 
   const selectedMustBeUniq = (selected: string) => {
     const values = getValues({ nest: true });
-    const customParamsValues: TopicFormCustomParam = values.customParams;
+    const customParamsValues: TopicConfigValue = values.customParams;
 
     const valid = !Object.entries(customParamsValues).some(
       ([key, customParam]) => {

+ 3 - 2
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamValue.tsx

@@ -1,12 +1,13 @@
 import React from 'react';
 import { useFormContext, ErrorMessage } from 'react-hook-form';
 import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
+import { TopicConfig } from 'generated-sources';
 
 interface Props {
   isDisabled: boolean;
   index: string;
-  name: string;
-  defaultValue: string;
+  name: TopicConfig['name'];
+  defaultValue: TopicConfig['defaultValue'];
 }
 
 const CustomParamValue: React.FC<Props> = ({

+ 6 - 9
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx

@@ -1,7 +1,11 @@
 import React from 'react';
 import { omit, reject, reduce, remove } from 'lodash';
 
-import { TopicFormCustomParams, TopicConfigByName } from 'redux/interfaces';
+import {
+  TopicFormCustomParams,
+  TopicConfigByName,
+  TopicConfigParams
+} from 'redux/interfaces';
 import CustomParamButton, { CustomParamButtonType } from './CustomParamButton';
 import CustomParamField from './CustomParamField';
 
@@ -12,20 +16,13 @@ interface Props {
   config?: TopicConfigByName;
 }
 
-interface Param {
-  [index: string]: {
-    name: string;
-    value: string;
-  };
-}
-
 const existingFields: string[] = [];
 
 const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
   const byIndex = config
     ? reduce(
         config.byName,
-        (result: Param, param, paramName) => {
+        (result: TopicConfigParams, param, paramName) => {
           result[`${INDEX_PREFIX}.${new Date().getTime()}ts`] = {
             name: paramName,
             value: param.value,

+ 4 - 4
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/customParamsOptions.tsx

@@ -1,10 +1,10 @@
-import { TopicCustomParamOption } from 'redux/interfaces';
+import { TopicConfigOption } from 'redux/interfaces';
 
-interface CustomParamOption {
-  [optionName: string]: TopicCustomParamOption;
+interface TopicConfigOptions {
+  [optionName: string]: TopicConfigOption;
 }
 
-const CUSTOM_PARAMS_OPTIONS: CustomParamOption = {
+const CUSTOM_PARAMS_OPTIONS: TopicConfigOptions = {
   'compression.type': {
     name: 'compression.type',
     defaultValue: 'producer',

+ 4 - 4
kafka-ui-react-app/src/lib/constants.ts

@@ -1,13 +1,13 @@
-export const BASE_PARAMS: RequestInit = {
+import { ConfigurationParameters } from 'generated-sources';
+
+export const BASE_PARAMS: ConfigurationParameters = {
+  basePath: process.env.REACT_APP_API_URL,
   credentials: 'include',
-  mode: 'cors',
   headers: {
     'Content-Type': 'application/json',
   },
 };
 
-export const BASE_URL = process.env.REACT_APP_API_URL;
-
 export const TOPIC_NAME_VALIDATION_PATTERN = RegExp(/^[.,A-Za-z0-9_-]+$/);
 
 export const MILLISECONDS_IN_WEEK = 604_800_000;

+ 8 - 0
kafka-ui-react-app/src/redux/actionType.ts

@@ -3,6 +3,14 @@ enum ActionType {
   GET_CLUSTERS__SUCCESS = 'GET_CLUSTERS__SUCCESS',
   GET_CLUSTERS__FAILURE = 'GET_CLUSTERS__FAILURE',
 
+  GET_CLUSTER_STATS__REQUEST = 'GET_CLUSTER_STATUS__REQUEST',
+  GET_CLUSTER_STATS__SUCCESS = 'GET_CLUSTER_STATUS__SUCCESS',
+  GET_CLUSTER_STATS__FAILURE = 'GET_CLUSTER_STATUS__FAILURE',
+
+  GET_CLUSTER_METRICS__REQUEST = 'GET_CLUSTER_METRICS__REQUEST',
+  GET_CLUSTER_METRICS__SUCCESS = 'GET_CLUSTER_METRICS__SUCCESS',
+  GET_CLUSTER_METRICS__FAILURE = 'GET_CLUSTER_METRICS__FAILURE',
+
   GET_BROKERS__REQUEST = 'GET_BROKERS__REQUEST',
   GET_BROKERS__SUCCESS = 'GET_BROKERS__SUCCESS',
   GET_BROKERS__FAILURE = 'GET_BROKERS__FAILURE',

+ 20 - 6
kafka-ui-react-app/src/redux/actions/actions.ts

@@ -1,18 +1,32 @@
 import { createAsyncAction } from 'typesafe-actions';
 import ActionType from 'redux/actionType';
+import { TopicName, ConsumerGroupID } from 'redux/interfaces';
+
 import {
+  Cluster,
+  ClusterStats,
+  ClusterMetrics,
   Broker,
   BrokerMetrics,
-  Cluster,
   Topic,
-  TopicConfig,
   TopicDetails,
+  TopicConfig,
   TopicMessage,
-  TopicName,
   ConsumerGroup,
   ConsumerGroupDetails,
-  ConsumerGroupID,
-} from 'redux/interfaces';
+} from 'generated-sources';
+
+export const fetchClusterStatsAction = createAsyncAction(
+  ActionType.GET_CLUSTER_STATS__REQUEST,
+  ActionType.GET_CLUSTER_STATS__SUCCESS,
+  ActionType.GET_CLUSTER_STATS__FAILURE
+)<undefined, ClusterStats, undefined>();
+
+export const fetchClusterMetricsAction = createAsyncAction(
+  ActionType.GET_CLUSTER_METRICS__REQUEST,
+  ActionType.GET_CLUSTER_METRICS__SUCCESS,
+  ActionType.GET_CLUSTER_METRICS__FAILURE
+)<undefined, ClusterMetrics, undefined>();
 
 export const fetchBrokersAction = createAsyncAction(
   ActionType.GET_BROKERS__REQUEST,
@@ -32,7 +46,7 @@ export const fetchClusterListAction = createAsyncAction(
   ActionType.GET_CLUSTERS__FAILURE
 )<undefined, Cluster[], undefined>();
 
-export const fetchTopicListAction = createAsyncAction(
+export const fetchTopicsListAction = createAsyncAction(
   ActionType.GET_TOPICS__REQUEST,
   ActionType.GET_TOPICS__SUCCESS,
   ActionType.GET_TOPICS__FAILURE

+ 120 - 35
kafka-ui-react-app/src/redux/actions/thunks.ts

@@ -1,23 +1,68 @@
-import * as api from 'redux/api';
+import {
+  ApiClustersApi,
+  Configuration,
+  Cluster,
+  Topic,
+  TopicFormData,
+  TopicConfig,
+} from 'generated-sources';
 import {
   ConsumerGroupID,
   PromiseThunk,
-  Cluster,
   ClusterName,
-  TopicFormData,
+  BrokerId,
   TopicName,
-  Topic,
   TopicMessageQueryParams,
+  TopicFormFormattedParams,
+  TopicFormDataRaw,
 } from 'redux/interfaces';
 
+import { BASE_PARAMS } from 'lib/constants';
 import * as actions from './actions';
 
+const apiClientConf = new Configuration(BASE_PARAMS);
+const apiClient = new ApiClustersApi(apiClientConf);
+
+export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
+  dispatch(actions.fetchClusterListAction.request());
+  try {
+    const clusters: Cluster[] = await apiClient.getClusters();
+    dispatch(actions.fetchClusterListAction.success(clusters));
+  } catch (e) {
+    dispatch(actions.fetchClusterListAction.failure());
+  }
+};
+
+export const fetchClusterStats = (
+  clusterName: ClusterName
+): PromiseThunk<void> => async (dispatch) => {
+  dispatch(actions.fetchClusterStatsAction.request());
+  try {
+    const payload = await apiClient.getClusterStats({ clusterName });
+    dispatch(actions.fetchClusterStatsAction.success(payload));
+  } catch (e) {
+    dispatch(actions.fetchClusterStatsAction.failure());
+  }
+};
+
+export const fetchClusterMetrics = (
+  clusterName: ClusterName
+): PromiseThunk<void> => async (dispatch) => {
+  dispatch(actions.fetchClusterMetricsAction.request());
+  try {
+    const payload = await apiClient.getClusterMetrics({ clusterName });
+    dispatch(actions.fetchClusterMetricsAction.success(payload));
+  } catch (e) {
+    dispatch(actions.fetchClusterMetricsAction.failure());
+  }
+};
+
 export const fetchBrokers = (
   clusterName: ClusterName
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchBrokersAction.request());
   try {
-    const payload = await api.getBrokers(clusterName);
+    const payload = await apiClient.getBrokers({ clusterName });
     dispatch(actions.fetchBrokersAction.success(payload));
   } catch (e) {
     dispatch(actions.fetchBrokersAction.failure());
@@ -25,36 +70,30 @@ export const fetchBrokers = (
 };
 
 export const fetchBrokerMetrics = (
-  clusterName: ClusterName
+  clusterName: ClusterName,
+  brokerId: BrokerId
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchBrokerMetricsAction.request());
   try {
-    const payload = await api.getBrokerMetrics(clusterName);
+    const payload = await apiClient.getBrokersMetrics({
+      clusterName,
+      id: brokerId,
+    });
     dispatch(actions.fetchBrokerMetricsAction.success(payload));
   } catch (e) {
     dispatch(actions.fetchBrokerMetricsAction.failure());
   }
 };
 
-export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
-  dispatch(actions.fetchClusterListAction.request());
-  try {
-    const clusters: Cluster[] = await api.getClusters();
-    dispatch(actions.fetchClusterListAction.success(clusters));
-  } catch (e) {
-    dispatch(actions.fetchClusterListAction.failure());
-  }
-};
-
-export const fetchTopicList = (
+export const fetchTopicsList = (
   clusterName: ClusterName
 ): PromiseThunk<void> => async (dispatch) => {
-  dispatch(actions.fetchTopicListAction.request());
+  dispatch(actions.fetchTopicsListAction.request());
   try {
-    const topics = await api.getTopics(clusterName);
-    dispatch(actions.fetchTopicListAction.success(topics));
+    const topics = await apiClient.getTopics({ clusterName });
+    dispatch(actions.fetchTopicsListAction.success(topics));
   } catch (e) {
-    dispatch(actions.fetchTopicListAction.failure());
+    dispatch(actions.fetchTopicsListAction.failure());
   }
 };
 
@@ -65,11 +104,11 @@ export const fetchTopicMessages = (
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchTopicMessagesAction.request());
   try {
-    const messages = await api.getTopicMessages(
+    const messages = await apiClient.getTopicMessages({
       clusterName,
       topicName,
-      queryParams
-    );
+      ...queryParams,
+    });
     dispatch(actions.fetchTopicMessagesAction.success(messages));
   } catch (e) {
     dispatch(actions.fetchTopicMessagesAction.failure());
@@ -82,7 +121,10 @@ export const fetchTopicDetails = (
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchTopicDetailsAction.request());
   try {
-    const topicDetails = await api.getTopicDetails(clusterName, topicName);
+    const topicDetails = await apiClient.getTopicDetails({
+      clusterName,
+      topicName,
+    });
     dispatch(
       actions.fetchTopicDetailsAction.success({
         topicName,
@@ -100,20 +142,59 @@ export const fetchTopicConfig = (
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchTopicConfigAction.request());
   try {
-    const config = await api.getTopicConfig(clusterName, topicName);
+    const config = await apiClient.getTopicConfigs({ clusterName, topicName });
     dispatch(actions.fetchTopicConfigAction.success({ topicName, config }));
   } catch (e) {
     dispatch(actions.fetchTopicConfigAction.failure());
   }
 };
 
+const formatTopicFormData = (form: TopicFormDataRaw): TopicFormData => {
+  const {
+    name,
+    partitions,
+    replicationFactor,
+    cleanupPolicy,
+    retentionBytes,
+    retentionMs,
+    maxMessageBytes,
+    minInSyncReplicas,
+    customParams,
+  } = form;
+
+  return {
+    name,
+    partitions,
+    replicationFactor,
+    configs: {
+      'cleanup.policy': cleanupPolicy,
+      'retention.ms': retentionMs,
+      'retention.bytes': retentionBytes,
+      'max.message.bytes': maxMessageBytes,
+      'min.insync.replicas': minInSyncReplicas,
+      ...Object.values(customParams || {}).reduce(
+        (result: TopicFormFormattedParams, customParam: TopicConfig) => {
+          return {
+            ...result,
+            [customParam.name]: customParam.value,
+          };
+        },
+        {}
+      ),
+    },
+  };
+};
+
 export const createTopic = (
   clusterName: ClusterName,
-  form: TopicFormData
+  form: TopicFormDataRaw
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.createTopicAction.request());
   try {
-    const topic: Topic = await api.postTopic(clusterName, form);
+    const topic: Topic = await apiClient.createTopic({
+      clusterName,
+      topicFormData: formatTopicFormData(form),
+    });
     dispatch(actions.createTopicAction.success(topic));
   } catch (e) {
     dispatch(actions.createTopicAction.failure());
@@ -122,11 +203,15 @@ export const createTopic = (
 
 export const updateTopic = (
   clusterName: ClusterName,
-  form: TopicFormData
+  form: TopicFormDataRaw
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.updateTopicAction.request());
   try {
-    const topic: Topic = await api.patchTopic(clusterName, form);
+    const topic: Topic = await apiClient.updateTopic({
+      clusterName,
+      topicName: form.name,
+      topicFormData: formatTopicFormData(form),
+    });
     dispatch(actions.updateTopicAction.success(topic));
   } catch (e) {
     dispatch(actions.updateTopicAction.failure());
@@ -138,7 +223,7 @@ export const fetchConsumerGroupsList = (
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchConsumerGroupsAction.request());
   try {
-    const consumerGroups = await api.getConsumerGroups(clusterName);
+    const consumerGroups = await apiClient.getConsumerGroups({ clusterName });
     dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups));
   } catch (e) {
     dispatch(actions.fetchConsumerGroupsAction.failure());
@@ -151,10 +236,10 @@ export const fetchConsumerGroupDetails = (
 ): PromiseThunk<void> => async (dispatch) => {
   dispatch(actions.fetchConsumerGroupDetailsAction.request());
   try {
-    const consumerGroupDetails = await api.getConsumerGroupDetails(
+    const consumerGroupDetails = await apiClient.getConsumerGroup({
       clusterName,
-      consumerGroupID
-    );
+      id: consumerGroupID,
+    });
     dispatch(
       actions.fetchConsumerGroupDetailsAction.success({
         consumerGroupID,

+ 0 - 14
kafka-ui-react-app/src/redux/api/brokers.ts

@@ -1,14 +0,0 @@
-import { Broker, ClusterName, BrokerMetrics } from 'redux/interfaces';
-import { BASE_URL, BASE_PARAMS } from 'lib/constants';
-
-export const getBrokers = (clusterName: ClusterName): Promise<Broker[]> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/brokers`, {
-    ...BASE_PARAMS,
-  }).then((res) => res.json());
-
-export const getBrokerMetrics = (
-  clusterName: ClusterName
-): Promise<BrokerMetrics> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/metrics`, {
-    ...BASE_PARAMS,
-  }).then((res) => res.json());

+ 0 - 11
kafka-ui-react-app/src/redux/api/clusters.ts

@@ -1,11 +0,0 @@
-import {
-  Cluster,
-} from 'redux/interfaces';
-import {
-  BASE_URL,
-  BASE_PARAMS,
-} from 'lib/constants';
-
-export const getClusters = (): Promise<Cluster[]> =>
-  fetch(`${BASE_URL}/clusters`, { ...BASE_PARAMS })
-    .then(res => res.json());

+ 0 - 12
kafka-ui-react-app/src/redux/api/consumerGroups.ts

@@ -1,12 +0,0 @@
-import { ClusterName } from '../interfaces/cluster';
-import { ConsumerGroup, ConsumerGroupID, ConsumerGroupDetails } from '../interfaces/consumerGroup';
-import { BASE_PARAMS, BASE_URL } from '../../lib/constants';
-
-
-export const getConsumerGroups = (clusterName: ClusterName): Promise<ConsumerGroup[]> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/consumerGroups`, { ...BASE_PARAMS })
-    .then(res => res.json());
-
-export const getConsumerGroupDetails = (clusterName: ClusterName, consumerGroupID: ConsumerGroupID): Promise<ConsumerGroupDetails> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/consumer-groups/${consumerGroupID}`, { ...BASE_PARAMS })
-    .then(res => res.json());

+ 0 - 4
kafka-ui-react-app/src/redux/api/index.ts

@@ -1,4 +0,0 @@
-export * from './topics';
-export * from './clusters';
-export * from './brokers';
-export * from './consumerGroups';

+ 0 - 142
kafka-ui-react-app/src/redux/api/topics.ts

@@ -1,142 +0,0 @@
-import {
-  TopicName,
-  TopicMessage,
-  Topic,
-  ClusterName,
-  TopicDetails,
-  TopicConfig,
-  TopicFormData,
-  TopicFormCustomParam,
-  TopicFormFormattedParams,
-  TopicFormCustomParams,
-  TopicMessageQueryParams,
-} from 'redux/interfaces';
-import { BASE_URL, BASE_PARAMS } from 'lib/constants';
-
-const formatCustomParams = (
-  customParams: TopicFormCustomParams
-): TopicFormFormattedParams => {
-  return Object.values(customParams || {}).reduce(
-    (result: TopicFormFormattedParams, customParam: TopicFormCustomParam) => {
-      return {
-        ...result,
-        [customParam.name]: customParam.value,
-      };
-    },
-    {} as TopicFormFormattedParams
-  );
-};
-
-export const getTopicConfig = (
-  clusterName: ClusterName,
-  topicName: TopicName
-): Promise<TopicConfig[]> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/topics/${topicName}/config`, {
-    ...BASE_PARAMS,
-  }).then((res) => res.json());
-
-export const getTopicDetails = (
-  clusterName: ClusterName,
-  topicName: TopicName
-): Promise<TopicDetails> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/topics/${topicName}`, {
-    ...BASE_PARAMS,
-  }).then((res) => res.json());
-
-export const getTopics = (clusterName: ClusterName): Promise<Topic[]> =>
-  fetch(`${BASE_URL}/clusters/${clusterName}/topics`, {
-    ...BASE_PARAMS,
-  }).then((res) => res.json());
-
-export const getTopicMessages = (
-  clusterName: ClusterName,
-  topicName: TopicName,
-  queryParams: Partial<TopicMessageQueryParams>
-): Promise<TopicMessage[]> => {
-  let searchParams = '';
-  Object.entries({ ...queryParams }).forEach((entry) => {
-    const key = entry[0];
-    const value = entry[1];
-    if (value) {
-      if (Array.isArray(value)) {
-        value.forEach((v) => {
-          searchParams += `${key}=${v}&`;
-        });
-      } else {
-        searchParams += `${key}=${value}&`;
-      }
-    }
-  });
-  return fetch(
-    `${BASE_URL}/clusters/${clusterName}/topics/${topicName}/messages?${searchParams}`,
-    {
-      ...BASE_PARAMS,
-    }
-  ).then((res) => res.json());
-};
-
-export const postTopic = (
-  clusterName: ClusterName,
-  form: TopicFormData
-): Promise<Topic> => {
-  const {
-    name,
-    partitions,
-    replicationFactor,
-    cleanupPolicy,
-    retentionBytes,
-    retentionMs,
-    maxMessageBytes,
-    minInSyncReplicas,
-  } = form;
-
-  const body = JSON.stringify({
-    name,
-    partitions,
-    replicationFactor,
-    configs: {
-      'cleanup.policy': cleanupPolicy,
-      'retention.ms': retentionMs,
-      'retention.bytes': retentionBytes,
-      'max.message.bytes': maxMessageBytes,
-      'min.insync.replicas': minInSyncReplicas,
-      ...formatCustomParams(form.customParams),
-    },
-  });
-
-  return fetch(`${BASE_URL}/clusters/${clusterName}/topics`, {
-    ...BASE_PARAMS,
-    method: 'POST',
-    body,
-  }).then((res) => res.json());
-};
-
-export const patchTopic = (
-  clusterName: ClusterName,
-  form: TopicFormData
-): Promise<Topic> => {
-  const {
-    cleanupPolicy,
-    retentionBytes,
-    retentionMs,
-    maxMessageBytes,
-    minInSyncReplicas,
-  } = form;
-
-  const body = JSON.stringify({
-    configs: {
-      'cleanup.policy': cleanupPolicy,
-      'retention.ms': retentionMs,
-      'retention.bytes': retentionBytes,
-      'max.message.bytes': maxMessageBytes,
-      'min.insync.replicas': minInSyncReplicas,
-      ...formatCustomParams(form.customParams),
-    },
-  });
-
-  return fetch(`${BASE_URL}/clusters/${clusterName}/topics/${form.name}`, {
-    ...BASE_PARAMS,
-    method: 'PATCH',
-    body,
-  }).then((res) => res.json());
-};

+ 3 - 26
kafka-ui-react-app/src/redux/interfaces/broker.ts

@@ -1,32 +1,9 @@
-export type BrokerId = string;
+import { ClusterStats, Broker } from 'generated-sources';
 
-export interface Broker {
-  brokerId: BrokerId;
-  bytesInPerSec: number;
-  segmentSize: number;
-  partitionReplicas: number;
-  bytesOutPerSec: number;
-};
+export type BrokerId = Broker['id'];
 
 export enum ZooKeeperStatus { offline, online };
 
-export interface BrokerDiskUsage {
-  brokerId: BrokerId;
-  segmentSize: number;
-}
-
-export interface BrokerMetrics {
-  brokerCount: number;
-  zooKeeperStatus: ZooKeeperStatus;
-  activeControllers: number;
-  onlinePartitionCount: number;
-  offlinePartitionCount: number;
-  inSyncReplicasCount: number,
-  outOfSyncReplicasCount: number,
-  underReplicatedPartitionCount: number;
-  diskUsage: BrokerDiskUsage[];
-}
-
-export interface BrokersState extends BrokerMetrics {
+export interface BrokersState extends ClusterStats {
   items: Broker[];
 }

+ 3 - 16
kafka-ui-react-app/src/redux/interfaces/cluster.ts

@@ -1,18 +1,5 @@
-export enum ClusterStatus {
-  Online = 'online',
-  Offline = 'offline',
-}
+import { Cluster } from 'generated-sources';
 
-export type ClusterName = string;
+export type ClusterName = Cluster['name'];
 
-export interface Cluster {
-  id: string;
-  name: ClusterName;
-  defaultCluster: boolean;
-  status: ClusterStatus;
-  brokerCount: number;
-  onlinePartitionCount: number;
-  topicCount: number;
-  bytesInPerSec: number;
-  bytesOutPerSec: number;
-}
+export type ClusterState = Cluster[];

+ 3 - 24
kafka-ui-react-app/src/redux/interfaces/consumerGroup.ts

@@ -1,27 +1,6 @@
-export type ConsumerGroupID = string;
+import { ConsumerGroup, ConsumerGroupDetails } from 'generated-sources';
 
-export interface ConsumerGroup {
-  consumerGroupId: ConsumerGroupID;
-  numConsumers: number;
-  numTopics: number;
-}
-
-export interface ConsumerGroupDetails {
-  consumerGroupId: ConsumerGroupID;
-  numConsumers: number;
-  numTopics: number;
-  consumers: Consumer[];
-}
-
-export interface Consumer {
-  consumerId: string;
-  topic: string;
-  host: string;
-  partition: number;
-  messagesBehind: number;
-  currentOffset: number;
-  endOffset: number;
-}
+export type ConsumerGroupID = ConsumerGroup['consumerGroupId'];
 
 export interface ConsumerGroupDetailedInfo
   extends ConsumerGroup,
@@ -29,5 +8,5 @@ export interface ConsumerGroupDetailedInfo
 
 export interface ConsumerGroupsState {
   byID: { [consumerGroupID: string]: ConsumerGroupDetailedInfo };
-  allIDs: string[];
+  allIDs: ConsumerGroupID[];
 }

+ 2 - 2
kafka-ui-react-app/src/redux/interfaces/index.ts

@@ -5,7 +5,7 @@ import { ThunkAction } from 'redux-thunk';
 import * as actions from 'redux/actions/actions';
 
 import { TopicsState } from './topic';
-import { Cluster } from './cluster';
+import { ClusterState } from './cluster';
 import { BrokersState } from './broker';
 import { LoaderState } from './loader';
 import { ConsumerGroupsState } from './consumerGroup';
@@ -25,7 +25,7 @@ export enum FetchStatus {
 
 export interface RootState {
   topics: TopicsState;
-  clusters: Cluster[];
+  clusters: ClusterState;
   brokers: BrokersState;
   consumerGroups: ConsumerGroupsState;
   loader: LoaderState;

+ 27 - 72
kafka-ui-react-app/src/redux/interfaces/topic.ts

@@ -1,90 +1,47 @@
-export type TopicName = string;
+import {
+  Topic,
+  TopicDetails,
+  TopicMessage,
+  TopicConfig,
+  TopicFormData,
+  GetTopicMessagesRequest,
+} from 'generated-sources';
+
+export type TopicName = Topic['name'];
 
 export enum CleanupPolicy {
   Delete = 'delete',
   Compact = 'compact',
 }
 
-export interface TopicConfig {
-  name: string;
-  value: string;
-  defaultValue: string;
-}
-
 export interface TopicConfigByName {
-  byName: {
-    [paramName: string]: TopicConfig;
-  };
-}
-
-export interface TopicReplica {
-  broker: number;
-  leader: boolean;
-  inSync: true;
-}
-
-export interface TopicPartition {
-  partition: number;
-  leader: number;
-  offsetMin: number;
-  offsetMax: number;
-  replicas: TopicReplica[];
-}
-
-export interface TopicCustomParamOption {
-  name: string;
-  defaultValue: string;
+  byName: TopicConfigParams;
 }
 
-export interface TopicDetails {
-  partitions: TopicPartition[];
+export interface TopicConfigParams {
+  [paramName: string]: TopicConfig;
 }
 
-export interface Topic {
-  name: TopicName;
-  internal: boolean;
-  partitionCount?: number;
-  replicationFactor?: number;
-  replicas?: number;
-  inSyncReplicas?: number;
-  segmentSize?: number;
-  segmentCount?: number;
-  underReplicatedPartitions?: number;
-  partitions: TopicPartition[];
+export interface TopicConfigOption {
+  name: TopicConfig['name'];
+  defaultValue: TopicConfig['defaultValue'];
 }
 
-export interface TopicMessage {
-  partition: number;
-  offset: number;
-  timestamp: string;
-  timestampType: string;
-  key: string;
-  headers: Record<string, string>;
-  content: any;
+export interface TopicConfigValue {
+  name: TopicConfig['name'];
+  value: TopicConfig['value'];
 }
 
-export enum SeekTypes {
-  OFFSET = 'OFFSET',
-  TIMESTAMP = 'TIMESTAMP',
-}
-
-export type SeekType = keyof typeof SeekTypes;
-
 export interface TopicMessageQueryParams {
-  q: string;
-  limit: number;
-  seekType: SeekType;
-  seekTo: string[];
-}
-
-export interface TopicFormCustomParam {
-  name: string;
-  value: string;
+  q: GetTopicMessagesRequest['q'];
+  limit: GetTopicMessagesRequest['limit'];
+  seekType: GetTopicMessagesRequest['seekType'];
+  seekTo: GetTopicMessagesRequest['seekTo'];
 }
 
 export interface TopicFormCustomParams {
-  byIndex: { [paramIndex: string]: TopicFormCustomParam };
-  allIndexes: string[];
+  byIndex: TopicConfigParams;
+  allIndexes: TopicName[];
 }
 
 export interface TopicWithDetailedInfo extends Topic, TopicDetails {
@@ -97,11 +54,9 @@ export interface TopicsState {
   messages: TopicMessage[];
 }
 
-export interface TopicFormFormattedParams {
-  [name: string]: string;
-}
+export type TopicFormFormattedParams = TopicFormData['configs'];
 
-export interface TopicFormData {
+export interface TopicFormDataRaw {
   name: string;
   partitions: number;
   replicationFactor: number;

+ 6 - 11
kafka-ui-react-app/src/redux/reducers/brokers/reducer.ts

@@ -1,9 +1,5 @@
-import {
-  Action,
-  BrokersState,
-  ZooKeeperStatus,
-  BrokerMetrics,
-} from 'redux/interfaces';
+import { Action, BrokersState, ZooKeeperStatus } from 'redux/interfaces';
+import { ClusterStats } from 'generated-sources';
 import ActionType from 'redux/actionType';
 
 export const initialState: BrokersState = {
@@ -21,15 +17,14 @@ export const initialState: BrokersState = {
 
 const updateBrokerSegmentSize = (
   state: BrokersState,
-  payload: BrokerMetrics
+  payload: ClusterStats
 ) => {
   const brokers = state.items;
   const { diskUsage } = payload;
 
   const items = brokers.map((broker) => {
-    const brokerMetrics = diskUsage.find(
-      ({ brokerId }) => brokerId === broker.brokerId
-    );
+    const brokerMetrics =
+      diskUsage && diskUsage.find(({ brokerId }) => brokerId === broker.id);
     if (brokerMetrics !== undefined) {
       return { ...broker, ...brokerMetrics };
     }
@@ -48,7 +43,7 @@ const reducer = (state = initialState, action: Action): BrokersState => {
         ...state,
         items: action.payload,
       };
-    case ActionType.GET_BROKER_METRICS__SUCCESS:
+    case ActionType.GET_CLUSTER_STATS__SUCCESS:
       return updateBrokerSegmentSize(state, action.payload);
     default:
       return state;

+ 2 - 1
kafka-ui-react-app/src/redux/reducers/clusters/reducer.ts

@@ -1,4 +1,5 @@
-import { Cluster, Action } from 'redux/interfaces';
+import { Action } from 'redux/interfaces';
+import { Cluster } from 'generated-sources';
 import ActionType from 'redux/actionType';
 
 export const initialState: Cluster[] = [];

+ 4 - 3
kafka-ui-react-app/src/redux/reducers/clusters/selectors.ts

@@ -1,6 +1,7 @@
 import { createSelector } from 'reselect';
-import { Cluster, RootState, FetchStatus, ClusterStatus } from 'redux/interfaces';
+import { RootState, FetchStatus } from 'redux/interfaces';
 import { createFetchingSelector } from 'redux/reducers/loader/selectors';
+import { Cluster, ServerStatus } from 'generated-sources';
 
 const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
 
@@ -16,13 +17,13 @@ export const getClusterList = createSelector(clustersState, (clusters) => cluste
 export const getOnlineClusters = createSelector(
   getClusterList,
   (clusters) => clusters.filter(
-    ({ status }) => status === ClusterStatus.Online,
+    ({ status }) => status === ServerStatus.Online,
   ),
 );
 
 export const getOfflineClusters = createSelector(
   getClusterList,
   (clusters) => clusters.filter(
-    ({ status }) => status === ClusterStatus.Offline,
+    ({ status }) => status === ServerStatus.Offline,
   ),
 );

+ 2 - 1
kafka-ui-react-app/src/redux/reducers/consumerGroups/reducer.ts

@@ -1,4 +1,5 @@
-import { Action, ConsumerGroup, ConsumerGroupsState } from 'redux/interfaces';
+import { Action, ConsumerGroupsState } from 'redux/interfaces';
+import { ConsumerGroup } from 'generated-sources';
 import ActionType from 'redux/actionType';
 
 export const initialState: ConsumerGroupsState = {

+ 2 - 1
kafka-ui-react-app/src/redux/reducers/topics/reducer.ts

@@ -1,4 +1,5 @@
-import { Action, TopicsState, Topic } from 'redux/interfaces';
+import { Action, TopicsState } from 'redux/interfaces';
+import { Topic } from 'generated-sources';
 import ActionType from 'redux/actionType';
 
 export const initialState: TopicsState = {

+ 2 - 1
kafka-ui-react-app/src/redux/reducers/topics/selectors.ts

@@ -7,6 +7,7 @@ import {
   TopicConfigByName,
 } from 'redux/interfaces';
 import { createFetchingSelector } from 'redux/reducers/loader/selectors';
+import { Partition } from 'generated-sources';
 
 const topicsState = ({ topics }: RootState): TopicsState => topics;
 
@@ -83,7 +84,7 @@ export const getTopicByName = createSelector(
 export const getPartitionsByTopicName = createSelector(
   getTopicMap,
   getTopicName,
-  (topics, topicName) => topics[topicName].partitions
+  (topics, topicName) => (topics[topicName].partitions) as Partition[]
 );
 
 export const getFullTopic = createSelector(getTopicByName, (topic) =>

部分文件因文件數量過多而無法顯示