瀏覽代碼

Merge pull request #117 from whotake/enhancement/fix-eslint-errors

[FE] Fix eslint and prettier errors
Azat Mutigullin 4 年之前
父節點
當前提交
3c4ccf9eb4
共有 50 個文件被更改,包括 669 次插入814 次删除
  1. 2 1
      kafka-ui-react-app/.eslintrc.json
  2. 234 445
      kafka-ui-react-app/package-lock.json
  3. 23 19
      kafka-ui-react-app/package.json
  4. 3 5
      kafka-ui-react-app/src/components/AppContainer.tsx
  5. 18 19
      kafka-ui-react-app/src/components/Brokers/Brokers.tsx
  6. 16 9
      kafka-ui-react-app/src/components/Brokers/BrokersContainer.ts
  7. 11 4
      kafka-ui-react-app/src/components/ConsumerGroups/ConsumersGroupsContainer.ts
  8. 20 7
      kafka-ui-react-app/src/components/ConsumerGroups/Details/DetailsContainer.ts
  9. 2 6
      kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx
  10. 13 8
      kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.ts
  11. 18 13
      kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx
  12. 27 27
      kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClustersWidget.tsx
  13. 1 1
      kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClustersWidgetContainer.ts
  14. 1 1
      kafka-ui-react-app/src/components/Nav/Nav.tsx
  15. 5 2
      kafka-ui-react-app/src/components/Nav/NavConatiner.ts
  16. 12 11
      kafka-ui-react-app/src/components/Topics/Details/DetailsContainer.ts
  17. 3 7
      kafka-ui-react-app/src/components/Topics/Details/Messages/Messages.tsx
  18. 17 8
      kafka-ui-react-app/src/components/Topics/Details/Overview/OverviewContainer.ts
  19. 17 22
      kafka-ui-react-app/src/components/Topics/Details/Settings/Settings.tsx
  20. 13 8
      kafka-ui-react-app/src/components/Topics/Details/Settings/SettingsContainer.ts
  21. 2 1
      kafka-ui-react-app/src/components/Topics/Edit/EditContainer.tsx
  22. 7 16
      kafka-ui-react-app/src/components/Topics/List/List.tsx
  23. 16 8
      kafka-ui-react-app/src/components/Topics/List/ListContainer.ts
  24. 18 9
      kafka-ui-react-app/src/components/Topics/List/ListItem.tsx
  25. 1 2
      kafka-ui-react-app/src/components/Topics/New/New.tsx
  26. 10 3
      kafka-ui-react-app/src/components/Topics/TopicsContainer.ts
  27. 1 1
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamValue.tsx
  28. 4 2
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx
  29. 8 10
      kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx
  30. 3 7
      kafka-ui-react-app/src/components/common/Dashboard/Indicator.tsx
  31. 3 9
      kafka-ui-react-app/src/components/common/Dashboard/MetricsWrapper.tsx
  32. 1 2
      kafka-ui-react-app/src/index.tsx
  33. 14 19
      kafka-ui-react-app/src/lib/hooks/useInterval.ts
  34. 7 7
      kafka-ui-react-app/src/lib/utils/formatBytes.ts
  35. 1 1
      kafka-ui-react-app/src/react-app-env.d.ts
  36. 4 1
      kafka-ui-react-app/src/redux/interfaces/broker.ts
  37. 6 1
      kafka-ui-react-app/src/redux/interfaces/index.ts
  38. 6 1
      kafka-ui-react-app/src/redux/interfaces/topic.ts
  39. 9 12
      kafka-ui-react-app/src/redux/reducers/clusters/selectors.ts
  40. 27 16
      kafka-ui-react-app/src/redux/reducers/consumerGroups/selectors.ts
  41. 1 1
      kafka-ui-react-app/src/redux/reducers/index.ts
  42. 1 5
      kafka-ui-react-app/src/redux/reducers/loader/reducer.ts
  43. 2 2
      kafka-ui-react-app/src/redux/reducers/loader/selectors.ts
  44. 8 3
      kafka-ui-react-app/src/redux/reducers/topics/reducer.ts
  45. 1 1
      kafka-ui-react-app/src/redux/reducers/topics/selectors.ts
  46. 4 5
      kafka-ui-react-app/src/redux/store/configureStore/dev.ts
  47. 4 1
      kafka-ui-react-app/src/redux/store/configureStore/index.ts
  48. 2 2
      kafka-ui-react-app/src/redux/store/configureStore/prod.ts
  49. 41 43
      kafka-ui-react-app/src/serviceWorker.ts
  50. 1 0
      kafka-ui-react-app/src/setupTests.ts

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

@@ -42,7 +42,8 @@
       { "extensions": [".js", ".jsx", ".ts", ".tsx"] }
     ],
     "jsx-a11y/label-has-associated-control": "off",
-    "no-param-reassign": [2, { "props": false }]
+    "no-param-reassign": [2, { "props": false }],
+    "import/prefer-default-export": "off"
   },
   "overrides": [
     {

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


+ 23 - 19
kafka-ui-react-app/package.json

@@ -4,11 +4,12 @@
   "private": true,
   "dependencies": {
     "@types/react-datepicker": "^3.0.2",
+    "@types/uuid": "^8.3.0",
     "bulma": "^0.8.0",
     "bulma-switch": "^2.0.0",
     "classnames": "^2.2.6",
     "date-fns": "^2.14.0",
-    "eslint-import-resolver-typescript": "^2.0.0",
+    "eslint-import-resolver-typescript": "^2.3.0",
     "immer": "^6.0.5",
     "lodash": "^4.17.15",
     "pretty-ms": "^6.0.1",
@@ -24,25 +25,28 @@
     "redux-thunk": "^2.3.0",
     "reselect": "^4.0.0",
     "typesafe-actions": "^5.1.0",
-    "use-debounce": "^3.4.3"
+    "use-debounce": "^3.4.3",
+    "uuid": "^8.3.1"
   },
   "lint-staged": {
     "*.{js,ts,jsx,tsx}": [
-      "eslint -c .eslintrc.json --fix"
+      "eslint -c .eslintrc.json --fix",
+      "git add"
     ]
   },
   "scripts": {
     "start": "react-scripts start",
     "build": "react-scripts build",
-    "lint": "esprint check",
-    "lint:fix": "esprint check --fix",
+    "lint": "eslint --ext .tsx,.ts src/",
+    "lint:fix": "eslint --ext .tsx,.ts src/ --fix",
     "test": "react-scripts test",
     "eject": "react-scripts eject",
-    "mock": "node ./mock/index.js"
+    "mock": "node ./mock/index.js",
+    "tsc": "tsc"
   },
   "husky": {
     "hooks": {
-      "pre-commit": "yarn tsc --noEmit && lint-staged"
+      "pre-commit": "npm run tsc --noEmit && lint-staged"
     }
   },
   "eslintConfig": {
@@ -74,24 +78,24 @@
     "@types/react-router-dom": "^5.1.3",
     "@types/redux": "^3.6.0",
     "@types/redux-thunk": "^2.1.0",
-    "@typescript-eslint/eslint-plugin": "^2.27.0",
-    "@typescript-eslint/parser": "^2.27.0",
+    "@typescript-eslint/eslint-plugin": "^2.34.0",
+    "@typescript-eslint/parser": "^2.34.0",
     "dotenv": "^8.2.0",
     "eslint": "^6.8.0",
-    "eslint-config-airbnb": "^18.1.0",
-    "eslint-config-prettier": "^6.10.1",
-    "eslint-plugin-import": "^2.20.2",
-    "eslint-plugin-jsx-a11y": "^6.2.3",
-    "eslint-plugin-prettier": "^3.1.2",
-    "eslint-plugin-react": "^7.19.0",
+    "eslint-config-airbnb": "^18.2.1",
+    "eslint-config-prettier": "^6.15.0",
+    "eslint-plugin-import": "^2.22.1",
+    "eslint-plugin-jsx-a11y": "^6.4.1",
+    "eslint-plugin-prettier": "^3.1.4",
+    "eslint-plugin-react": "^7.21.5",
     "eslint-plugin-react-hooks": "^2.5.1",
     "esprint": "^0.6.0",
-    "husky": "^4.2.5",
+    "husky": "^4.3.0",
     "json-server": "^0.15.1",
-    "lint-staged": ">=10",
+    "lint-staged": "^10.5.1",
     "node-sass": "^4.13.1",
-    "prettier": "^2.0.4",
-    "react-scripts": "3.4.0",
+    "prettier": "^2.1.2",
+    "react-scripts": "3.4.4",
     "typescript": "~3.7.4"
   },
   "proxy": "http://localhost:8080"

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

@@ -1,10 +1,8 @@
 import { connect } from 'react-redux';
-import {
-  fetchClustersList,
-} from 'redux/actions';
-import App from './App';
+import { fetchClustersList } from 'redux/actions';
 import { getIsClusterListFetched } from 'redux/reducers/clusters/selectors';
 import { RootState } from 'redux/interfaces';
+import App from './App';
 
 const mapStateToProps = (state: RootState) => ({
   isClusterListFetched: getIsClusterListFetched(state),
@@ -12,6 +10,6 @@ const mapStateToProps = (state: RootState) => ({
 
 const mapDispatchToProps = {
   fetchClustersList,
-}
+};
 
 export default connect(mapStateToProps, mapDispatchToProps)(App);

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

@@ -27,15 +27,14 @@ const Topics: React.FC<Props> = ({
   fetchClusterStats,
   fetchBrokers,
 }) => {
-  React.useEffect(
-    () => {
-      fetchClusterStats(clusterName);
-      fetchBrokers(clusterName);
-    },
-    [fetchClusterStats, fetchBrokers, clusterName],
-  );
+  React.useEffect(() => {
+    fetchClusterStats(clusterName);
+    fetchBrokers(clusterName);
+  }, [fetchClusterStats, fetchBrokers, clusterName]);
 
-  useInterval(() => { fetchClusterStats(clusterName); }, 5000);
+  useInterval(() => {
+    fetchClusterStats(clusterName);
+  }, 5000);
 
   const zkOnline = zooKeeperStatus === ZooKeeperStatus.online;
 
@@ -44,12 +43,8 @@ const Topics: React.FC<Props> = ({
       <Breadcrumb>Brokers overview</Breadcrumb>
 
       <MetricsWrapper title="Uptime">
-        <Indicator label="Total Brokers">
-          {brokerCount}
-        </Indicator>
-        <Indicator label="Active Controllers">
-          {activeControllers}
-        </Indicator>
+        <Indicator label="Total Brokers">{brokerCount}</Indicator>
+        <Indicator label="Active Controllers">{activeControllers}</Indicator>
         <Indicator label="Zookeeper Status">
           <span className={cx('tag', zkOnline ? 'is-primary' : 'is-danger')}>
             {zkOnline ? 'Online' : 'Offline'}
@@ -59,17 +54,21 @@ const Topics: React.FC<Props> = ({
 
       <MetricsWrapper title="Partitions">
         <Indicator label="Online">
-          <span className={cx({'has-text-danger': offlinePartitionCount !== 0})}>
+          <span
+            className={cx({ 'has-text-danger': offlinePartitionCount !== 0 })}
+          >
             {onlinePartitionCount}
           </span>
-          <span className="subtitle has-text-weight-light"> of {(onlinePartitionCount || 0) + (offlinePartitionCount || 0)}</span>
+          <span className="subtitle has-text-weight-light">
+            {' '}
+            of
+            {(onlinePartitionCount || 0) + (offlinePartitionCount || 0)}
+          </span>
         </Indicator>
         <Indicator label="URP" title="Under replicated partitions">
           {underReplicatedPartitionCount}
         </Indicator>
-        <Indicator label="In Sync Replicas">
-          {inSyncReplicasCount}
-        </Indicator>
+        <Indicator label="In Sync Replicas">{inSyncReplicasCount}</Indicator>
         <Indicator label="Out of Sync Replicas">
           {outOfSyncReplicasCount}
         </Indicator>

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

@@ -1,20 +1,24 @@
 import { connect } from 'react-redux';
-import {
-  fetchClusterStats,
-  fetchBrokers,
-} from 'redux/actions';
-import Brokers from './Brokers';
+import { fetchClusterStats, fetchBrokers } from 'redux/actions';
 import * as brokerSelectors from 'redux/reducers/brokers/selectors';
 import { RootState, ClusterName } from 'redux/interfaces';
 import { RouteComponentProps } from 'react-router-dom';
+import Brokers from './Brokers';
 
 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
+) => ({
   isFetched: brokerSelectors.getIsBrokerListFetched(state),
   clusterName,
   brokerCount: brokerSelectors.getBrokerCount(state),
@@ -24,11 +28,14 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}
   offlinePartitionCount: brokerSelectors.getOfflinePartitionCount(state),
   inSyncReplicasCount: brokerSelectors.getInSyncReplicasCount(state),
   outOfSyncReplicasCount: brokerSelectors.getOutOfSyncReplicasCount(state),
-  underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(state)
+  underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(
+    state
+  ),
 });
 
 const mapDispatchToProps = {
-  fetchClusterStats: (clusterName: ClusterName) => fetchClusterStats(clusterName),
+  fetchClusterStats: (clusterName: ClusterName) =>
+    fetchClusterStats(clusterName),
   fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
 };
 

+ 11 - 4
kafka-ui-react-app/src/components/ConsumerGroups/ConsumersGroupsContainer.ts

@@ -5,20 +5,27 @@ import { RouteComponentProps } from 'react-router-dom';
 import ConsumerGroups from './ConsumerGroups';
 import { getIsConsumerGroupsListFetched } from '../../redux/reducers/consumerGroups/selectors';
 
-
 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
+) => ({
   isFetched: getIsConsumerGroupsListFetched(state),
   clusterName,
 });
 
 const mapDispatchToProps = {
-  fetchConsumerGroupsList: (clusterName: ClusterName) => fetchConsumerGroupsList(clusterName),
+  fetchConsumerGroupsList: (clusterName: ClusterName) =>
+    fetchConsumerGroupsList(clusterName),
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(ConsumerGroups);

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

@@ -1,27 +1,40 @@
 import { connect } from 'react-redux';
-import Details from './Details';
-import {ClusterName, RootState} from 'redux/interfaces';
+import { ClusterName, RootState } from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
-import { getIsConsumerGroupDetailsFetched, getConsumerGroupByID } from 'redux/reducers/consumerGroups/selectors';
+import {
+  getIsConsumerGroupDetailsFetched,
+  getConsumerGroupByID,
+} from 'redux/reducers/consumerGroups/selectors';
 import { ConsumerGroupID } from 'redux/interfaces/consumerGroup';
 import { fetchConsumerGroupDetails } from 'redux/actions/thunks';
+import Details from './Details';
 
 interface RouteProps {
   clusterName: ClusterName;
   consumerGroupID: ConsumerGroupID;
 }
 
-interface OwnProps extends RouteComponentProps<RouteProps> { }
+type OwnProps = RouteComponentProps<RouteProps>;
 
-const mapStateToProps = (state: RootState, { match: { params: { consumerGroupID, clusterName } } }: OwnProps) => ({
+const mapStateToProps = (
+  state: RootState,
+  {
+    match: {
+      params: { consumerGroupID, clusterName },
+    },
+  }: OwnProps
+) => ({
   clusterName,
   consumerGroupID,
   isFetched: getIsConsumerGroupDetailsFetched(state),
-  ...getConsumerGroupByID(state, consumerGroupID)
+  ...getConsumerGroupByID(state, consumerGroupID),
 });
 
 const mapDispatchToProps = {
-  fetchConsumerGroupDetails: (clusterName: ClusterName, consumerGroupID: ConsumerGroupID) => fetchConsumerGroupDetails(clusterName, consumerGroupID),
+  fetchConsumerGroupDetails: (
+    clusterName: ClusterName,
+    consumerGroupID: ConsumerGroupID
+  ) => fetchConsumerGroupDetails(clusterName, consumerGroupID),
 };
 
 export default withRouter(

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

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

+ 13 - 8
kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.ts

@@ -1,20 +1,25 @@
 import { connect } from 'react-redux';
-import {ClusterName, RootState} from 'redux/interfaces';
+import { ClusterName, RootState } from 'redux/interfaces';
 import { getConsumerGroupsList } from 'redux/reducers/consumerGroups/selectors';
-import List from './List';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
+import List from './List';
 
 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,
-  consumerGroups: getConsumerGroupsList(state)
+  consumerGroups: getConsumerGroupsList(state),
 });
 
-export default withRouter(
-  connect(mapStateToProps)(List)
-);
+export default withRouter(connect(mapStateToProps)(List));

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

@@ -4,23 +4,28 @@ import { NavLink } from 'react-router-dom';
 import { clusterBrokersPath } from 'lib/paths';
 import { Cluster, ServerStatus } from 'generated-sources';
 
-const ClusterWidget: React.FC<Cluster> = ({
-  name,
-  status,
-  topicCount,
-  brokerCount,
-  bytesInPerSec,
-  bytesOutPerSec,
-  onlinePartitionCount,
+interface ClusterWidgetProps {
+  cluster: Cluster;
+}
+
+const ClusterWidget: React.FC<ClusterWidgetProps> = ({
+  cluster: {
+    name,
+    status,
+    topicCount,
+    brokerCount,
+    bytesInPerSec,
+    bytesOutPerSec,
+    onlinePartitionCount,
+  },
 }) => (
   <NavLink to={clusterBrokersPath(name)} className="column is-full-modile is-6">
     <div className="box is-hoverable">
-      <div
-        className="title is-6 has-text-overflow-ellipsis"
-        title={name}
-      >
+      <div className="title is-6 has-text-overflow-ellipsis" title={name}>
         <div
-          className={`tag has-margin-right ${status === ServerStatus.Online ? 'is-primary' : 'is-danger'}`}
+          className={`tag has-margin-right ${
+            status === ServerStatus.Online ? 'is-primary' : 'is-danger'
+          }`}
         >
           {status}
         </div>

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

@@ -1,10 +1,11 @@
 import React from 'react';
 import { chunk } from 'lodash';
+import { v4 } from 'uuid';
 
 import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
 import Indicator from 'components/common/Dashboard/Indicator';
-import ClusterWidget from './ClusterWidget';
 import { Cluster } from 'generated-sources';
+import ClusterWidget from './ClusterWidget';
 
 interface Props {
   clusters: Cluster[];
@@ -12,6 +13,11 @@ interface Props {
   offlineClusters: Cluster[];
 }
 
+interface ChunkItem {
+  id: string;
+  data: Cluster[];
+}
+
 const ClustersWidget: React.FC<Props> = ({
   clusters,
   onlineClusters,
@@ -19,36 +25,31 @@ const ClustersWidget: React.FC<Props> = ({
 }) => {
   const [showOfflineOnly, setShowOfflineOnly] = React.useState<boolean>(false);
 
-  const clusterList: Array<Cluster[]> = React.useMemo(() => {
-      let list = clusters;
+  const clusterList: ChunkItem[] = React.useMemo(() => {
+    let list = clusters;
 
-      if (showOfflineOnly) {
-        list = offlineClusters;
-      }
+    if (showOfflineOnly) {
+      list = offlineClusters;
+    }
 
-      return chunk(list, 2);
-    },
-    [clusters, offlineClusters, showOfflineOnly],
-  );
+    return chunk(list, 2).map((data) => ({
+      id: v4(),
+      data,
+    }));
+  }, [clusters, offlineClusters, showOfflineOnly]);
 
   const handleSwitch = () => setShowOfflineOnly(!showOfflineOnly);
 
   return (
     <div>
-      <h5 className="title is-5">
-        Clusters
-      </h5>
+      <h5 className="title is-5">Clusters</h5>
 
       <MetricsWrapper>
-        <Indicator label="Online Clusters" >
-          <span className="tag is-primary">
-            {onlineClusters.length}
-          </span>
+        <Indicator label="Online Clusters">
+          <span className="tag is-primary">{onlineClusters.length}</span>
         </Indicator>
         <Indicator label="Offline Clusters">
-          <span className="tag is-danger">
-            {offlineClusters.length}
-          </span>
+          <span className="tag is-danger">{offlineClusters.length}</span>
         </Indicator>
         <Indicator label="Hide online clusters">
           <input
@@ -59,20 +60,19 @@ const ClustersWidget: React.FC<Props> = ({
             checked={showOfflineOnly}
             onChange={handleSwitch}
           />
-          <label htmlFor="switchRoundedDefault">
-          </label>
+          <label htmlFor="switchRoundedDefault" />
         </Indicator>
       </MetricsWrapper>
 
-      {clusterList.map((chunk, idx) => (
-        <div className="columns" key={`dashboard-cluster-list-row-key-${idx}`}>
-          {chunk.map((cluster, idx) => (
-            <ClusterWidget {...cluster} key={`dashboard-cluster-list-item-key-${idx}`}/>
+      {clusterList.map((chunkItem) => (
+        <div className="columns" key={chunkItem.id}>
+          {chunkItem.data.map((cluster) => (
+            <ClusterWidget cluster={cluster} key={cluster.name} />
           ))}
         </div>
       ))}
     </div>
-  )
+  );
 };
 
 export default ClustersWidget;

+ 1 - 1
kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClustersWidgetContainer.ts

@@ -1,11 +1,11 @@
 import { connect } from 'react-redux';
-import ClustersWidget from './ClustersWidget';
 import {
   getClusterList,
   getOnlineClusters,
   getOfflineClusters,
 } from 'redux/reducers/clusters/selectors';
 import { RootState } from 'redux/interfaces';
+import ClustersWidget from './ClustersWidget';
 
 const mapStateToProps = (state: RootState) => ({
   clusters: getClusterList(state),

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

@@ -1,8 +1,8 @@
 import React from 'react';
 import { NavLink } from 'react-router-dom';
 import cx from 'classnames';
-import ClusterMenu from './ClusterMenu';
 import { Cluster } from 'generated-sources';
+import ClusterMenu from './ClusterMenu';
 
 interface Props {
   isClusterListFetched: boolean;

+ 5 - 2
kafka-ui-react-app/src/components/Nav/NavConatiner.ts

@@ -1,7 +1,10 @@
 import { connect } from 'react-redux';
-import Nav from './Nav';
-import { getIsClusterListFetched, getClusterList } from 'redux/reducers/clusters/selectors';
+import {
+  getIsClusterListFetched,
+  getClusterList,
+} from 'redux/reducers/clusters/selectors';
 import { RootState } from 'redux/interfaces';
+import Nav from './Nav';
 
 const mapStateToProps = (state: RootState) => ({
   isClusterListFetched: getIsClusterListFetched(state),

+ 12 - 11
kafka-ui-react-app/src/components/Topics/Details/DetailsContainer.ts

@@ -1,24 +1,25 @@
 import { connect } from 'react-redux';
-import Details from './Details';
-import {
-  ClusterName,
-  RootState,
-  TopicName
-} from 'redux/interfaces';
+import { ClusterName, RootState, TopicName } from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
+import Details from './Details';
 
 interface RouteProps {
   clusterName: ClusterName;
   topicName: TopicName;
 }
 
-interface OwnProps extends RouteComponentProps<RouteProps> { }
+type OwnProps = RouteComponentProps<RouteProps>;
 
-const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
+const mapStateToProps = (
+  state: RootState,
+  {
+    match: {
+      params: { topicName, clusterName },
+    },
+  }: OwnProps
+) => ({
   clusterName,
   topicName,
 });
 
-export default withRouter(
-  connect(mapStateToProps)(Details)
-);
+export default withRouter(connect(mapStateToProps)(Details));

+ 3 - 7
kafka-ui-react-app/src/components/Topics/Details/Messages/Messages.tsx

@@ -2,13 +2,9 @@ import React, { useCallback, useEffect, useRef } from 'react';
 import {
   ClusterName,
   TopicMessageQueryParams,
-  TopicName
+  TopicName,
 } from 'redux/interfaces';
-import {
-  TopicMessage,
-  Partition,
-  SeekType
-} from 'generated-sources';
+import { TopicMessage, Partition, SeekType } from 'generated-sources';
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import { format } from 'date-fns';
 import DatePicker from 'react-datepicker';
@@ -360,7 +356,7 @@ const Messages: React.FC<Props> = ({
         </div>
       </div>
       <div className="columns">
-        <div className="column is-full" style={{textAlign: "right"}}>
+        <div className="column is-full" style={{ textAlign: 'right' }}>
           <input
             type="submit"
             className="button is-primary"

+ 17 - 8
kafka-ui-react-app/src/components/Topics/Details/Overview/OverviewContainer.ts

@@ -1,20 +1,28 @@
 import { connect } from 'react-redux';
-import {
-  fetchTopicDetails,
-} from 'redux/actions';
-import Overview from './Overview';
+import { fetchTopicDetails } from 'redux/actions';
 import { RootState, TopicName, ClusterName } from 'redux/interfaces';
-import { getTopicByName, getIsTopicDetailsFetched } from 'redux/reducers/topics/selectors';
+import {
+  getTopicByName,
+  getIsTopicDetailsFetched,
+} from 'redux/reducers/topics/selectors';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
+import Overview from './Overview';
 
 interface RouteProps {
   clusterName: ClusterName;
   topicName: TopicName;
 }
 
-interface OwnProps extends RouteComponentProps<RouteProps> { }
+type OwnProps = RouteComponentProps<RouteProps>;
 
-const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
+const mapStateToProps = (
+  state: RootState,
+  {
+    match: {
+      params: { topicName, clusterName },
+    },
+  }: OwnProps
+) => ({
   clusterName,
   topicName,
   isFetched: getIsTopicDetailsFetched(state),
@@ -22,7 +30,8 @@ const mapStateToProps = (state: RootState, { match: { params: { topicName, clust
 });
 
 const mapDispatchToProps = {
-  fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => fetchTopicDetails(clusterName, topicName),
+  fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) =>
+    fetchTopicDetails(clusterName, topicName),
 };
 
 export default withRouter(

+ 17 - 22
kafka-ui-react-app/src/components/Topics/Details/Settings/Settings.tsx

@@ -1,6 +1,5 @@
 import React from 'react';
-import { ClusterName, TopicName } from 'redux/interfaces';
-import { TopicConfig } from 'generated-sources';
+import { ClusterName, TopicName, TopicConfig } from 'redux/interfaces';
 
 interface Props {
   clusterName: ClusterName;
@@ -10,29 +9,24 @@ interface Props {
   fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => void;
 }
 
-const ConfigListItem: React.FC<TopicConfig> = ({
-  name,
-  value,
-  defaultValue,
+interface ListItemProps {
+  config: TopicConfig;
+}
+
+const ConfigListItem: React.FC<ListItemProps> = ({
+  config: { name, value, defaultValue },
 }) => {
   const hasCustomValue = value !== defaultValue;
 
   return (
     <tr>
-      <td className={hasCustomValue ? 'has-text-weight-bold' : ''}>
-        {name}
-      </td>
-      <td className={hasCustomValue ? 'has-text-weight-bold' : ''}>
-        {value}
-      </td>
-      <td
-        className="has-text-grey"
-        title="Default Value"
-      >
+      <td className={hasCustomValue ? 'has-text-weight-bold' : ''}>{name}</td>
+      <td className={hasCustomValue ? 'has-text-weight-bold' : ''}>{value}</td>
+      <td className="has-text-grey" title="Default Value">
         {hasCustomValue && defaultValue}
       </td>
     </tr>
-  )
+  );
 };
 
 const Sertings: React.FC<Props> = ({
@@ -42,10 +36,9 @@ const Sertings: React.FC<Props> = ({
   fetchTopicConfig,
   config,
 }) => {
-  React.useEffect(
-    () => { fetchTopicConfig(clusterName, topicName); },
-    [fetchTopicConfig, clusterName, topicName],
-  );
+  React.useEffect(() => {
+    fetchTopicConfig(clusterName, topicName);
+  }, [fetchTopicConfig, clusterName, topicName]);
 
   if (!isFetched || !config) {
     return null;
@@ -62,7 +55,9 @@ const Sertings: React.FC<Props> = ({
           </tr>
         </thead>
         <tbody>
-          {config.map((item, index) => <ConfigListItem key={`config-list-item-key-${index}`} {...item} />)}
+          {config.map((item) => (
+            <ConfigListItem key={item.id} config={item} />
+          ))}
         </tbody>
       </table>
     </div>

+ 13 - 8
kafka-ui-react-app/src/components/Topics/Details/Settings/SettingsContainer.ts

@@ -1,24 +1,28 @@
 import { connect } from 'react-redux';
 import { RootState, ClusterName, TopicName } from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
-import {
-  fetchTopicConfig,
-} from 'redux/actions';
-import Settings from './Settings';
+import { fetchTopicConfig } from 'redux/actions';
 import {
   getTopicConfig,
   getTopicConfigFetched,
 } from 'redux/reducers/topics/selectors';
-
+import Settings from './Settings';
 
 interface RouteProps {
   clusterName: ClusterName;
   topicName: TopicName;
 }
 
-interface OwnProps extends RouteComponentProps<RouteProps> { }
+type OwnProps = RouteComponentProps<RouteProps>;
 
-const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
+const mapStateToProps = (
+  state: RootState,
+  {
+    match: {
+      params: { topicName, clusterName },
+    },
+  }: OwnProps
+) => ({
   clusterName,
   topicName,
   config: getTopicConfig(state, topicName),
@@ -26,7 +30,8 @@ const mapStateToProps = (state: RootState, { match: { params: { topicName, clust
 });
 
 const mapDispatchToProps = {
-  fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => fetchTopicConfig(clusterName, topicName),
+  fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) =>
+    fetchTopicConfig(clusterName, topicName),
 };
 
 export default withRouter(

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

@@ -4,6 +4,7 @@ import {
   ClusterName,
   TopicName,
   Action,
+  TopicFormDataRaw,
 } from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
 import {
@@ -19,8 +20,8 @@ import {
 } from 'redux/reducers/topics/selectors';
 import { clusterTopicPath } from 'lib/paths';
 import { ThunkDispatch } from 'redux-thunk';
+
 import Edit from './Edit';
-import { TopicFormDataRaw } from 'redux/interfaces';
 
 interface RouteProps {
   clusterName: ClusterName;

+ 7 - 16
kafka-ui-react-app/src/components/Topics/List/List.tsx

@@ -1,21 +1,17 @@
 import React from 'react';
 import { TopicWithDetailedInfo, ClusterName } from 'redux/interfaces';
-import ListItem from './ListItem';
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import { NavLink } from 'react-router-dom';
 import { clusterTopicNewPath } from 'lib/paths';
+import ListItem from './ListItem';
 
 interface Props {
   clusterName: ClusterName;
-  topics: (TopicWithDetailedInfo)[];
-  externalTopics: (TopicWithDetailedInfo)[];
+  topics: TopicWithDetailedInfo[];
+  externalTopics: TopicWithDetailedInfo[];
 }
 
-const List: React.FC<Props> = ({
-  clusterName,
-  topics,
-  externalTopics,
-}) => {
+const List: React.FC<Props> = ({ clusterName, topics, externalTopics }) => {
   const [showInternal, setShowInternal] = React.useState<boolean>(true);
 
   const handleSwitch = () => setShowInternal(!showInternal);
@@ -38,9 +34,7 @@ const List: React.FC<Props> = ({
                 checked={showInternal}
                 onChange={handleSwitch}
               />
-              <label htmlFor="switchRoundedDefault">
-                Show Internal Topics
-              </label>
+              <label htmlFor="switchRoundedDefault">Show Internal Topics</label>
             </div>
           </div>
           <div className="level-item level-right">
@@ -64,11 +58,8 @@ const List: React.FC<Props> = ({
             </tr>
           </thead>
           <tbody>
-            {items.map((topic, index) => (
-              <ListItem
-                key={`topic-list-item-key-${index}`}
-                {...topic}
-              />
+            {items.map((topic) => (
+              <ListItem key={topic.id} topic={topic} />
             ))}
           </tbody>
         </table>

+ 16 - 8
kafka-ui-react-app/src/components/Topics/List/ListContainer.ts

@@ -1,21 +1,29 @@
 import { connect } from 'react-redux';
-import {ClusterName, RootState} from 'redux/interfaces';
-import { getTopicList, getExternalTopicList } from 'redux/reducers/topics/selectors';
-import List from './List';
+import { ClusterName, RootState } from 'redux/interfaces';
+import {
+  getTopicList,
+  getExternalTopicList,
+} from 'redux/reducers/topics/selectors';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
+import List from './List';
 
 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,
   topics: getTopicList(state),
   externalTopics: getExternalTopicList(state),
 });
 
-export default withRouter(
-  connect(mapStateToProps)(List)
-);
+export default withRouter(connect(mapStateToProps)(List));

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

@@ -3,10 +3,12 @@ import cx from 'classnames';
 import { NavLink } from 'react-router-dom';
 import { TopicWithDetailedInfo } from 'redux/interfaces';
 
-const ListItem: React.FC<TopicWithDetailedInfo> = ({
-  name,
-  internal,
-  partitions,
+interface ListItemProps {
+  topic: TopicWithDetailedInfo;
+}
+
+const ListItem: React.FC<ListItemProps> = ({
+  topic: { name, internal, partitions },
 }) => {
   const outOfSyncReplicas = React.useMemo(() => {
     if (partitions === undefined || partitions.length === 0) {
@@ -14,27 +16,34 @@ const ListItem: React.FC<TopicWithDetailedInfo> = ({
     }
 
     return partitions.reduce((memo: number, { replicas }) => {
-      const outOfSync = replicas?.filter(({ inSync }) => !inSync)
+      const outOfSync = replicas?.filter(({ inSync }) => !inSync);
       return memo + (outOfSync?.length || 0);
     }, 0);
-  }, [partitions])
+  }, [partitions]);
 
   return (
     <tr>
       <td>
-        <NavLink exact to={`topics/${name}`} activeClassName="is-active" className="title is-6">
+        <NavLink
+          exact
+          to={`topics/${name}`}
+          activeClassName="is-active"
+          className="title is-6"
+        >
           {name}
         </NavLink>
       </td>
       <td>{partitions?.length}</td>
       <td>{outOfSyncReplicas}</td>
       <td>
-        <div className={cx('tag is-small', internal ? 'is-light' : 'is-success')}>
+        <div
+          className={cx('tag is-small', internal ? 'is-light' : 'is-success')}
+        >
           {internal ? 'Internal' : 'External'}
         </div>
       </td>
     </tr>
   );
-}
+};
 
 export default ListItem;

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

@@ -1,11 +1,10 @@
 import React from 'react';
-import { ClusterName, TopicName } from 'redux/interfaces';
+import { ClusterName, TopicName, TopicFormDataRaw } 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;

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

@@ -1,17 +1,24 @@
 import { connect } from 'react-redux';
 import { fetchTopicsList } from 'redux/actions';
-import Topics from './Topics';
 import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
 import { RootState, ClusterName } from 'redux/interfaces';
 import { RouteComponentProps } from 'react-router-dom';
+import Topics from './Topics';
 
 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
+) => ({
   isFetched: getIsTopicListFetched(state),
   clusterName,
 });

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

@@ -1,7 +1,7 @@
 import React from 'react';
 import { useFormContext, ErrorMessage } from 'react-hook-form';
-import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
 import { TopicConfig } from 'generated-sources';
+import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
 
 interface Props {
   isDisabled: boolean;

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

@@ -1,10 +1,11 @@
 import React from 'react';
 import { omit, reject, reduce, remove } from 'lodash';
+import { v4 } from 'uuid';
 
 import {
   TopicFormCustomParams,
   TopicConfigByName,
-  TopicConfigParams
+  TopicConfigParams,
 } from 'redux/interfaces';
 import CustomParamButton, { CustomParamButtonType } from './CustomParamButton';
 import CustomParamField from './CustomParamField';
@@ -26,6 +27,7 @@ const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
           result[`${INDEX_PREFIX}.${new Date().getTime()}ts`] = {
             name: paramName,
             value: param.value,
+            id: v4(),
           };
           return result;
         },
@@ -49,7 +51,7 @@ const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
       ...formCustomParams,
       byIndex: {
         ...formCustomParams.byIndex,
-        [newIndex]: { name: '', value: '' },
+        [newIndex]: { name: '', value: '', id: v4() },
       },
       allIndexes: [newIndex, ...formCustomParams.allIndexes],
     });

+ 8 - 10
kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx

@@ -10,18 +10,16 @@ interface Props {
   links?: Link[];
 }
 
-const Breadcrumb: React.FC<Props> = ({
-  links,
-  children,
-}) => {
+const Breadcrumb: React.FC<Props> = ({ links, children }) => {
   return (
     <nav className="breadcrumb" aria-label="breadcrumbs">
       <ul>
-        {links && links.map(({ label, href }, index) => (
-          <li key={`breadcrumb-item-key-${index}`}>
-            <NavLink to={href}>{label}</NavLink>
-          </li>
-        ))}
+        {links &&
+          links.map(({ label, href }) => (
+            <li key={href}>
+              <NavLink to={href}>{label}</NavLink>
+            </li>
+          ))}
 
         <li className="is-active">
           <span className="">{children}</span>
@@ -29,6 +27,6 @@ const Breadcrumb: React.FC<Props> = ({
       </ul>
     </nav>
   );
-}
+};
 
 export default Breadcrumb;

+ 3 - 7
kafka-ui-react-app/src/components/common/Dashboard/Indicator.tsx

@@ -5,19 +5,15 @@ interface Props {
   title?: string;
 }
 
-const Indicator: React.FC<Props> = ({
-  label,
-  title,
-  children,
-}) => {
+const Indicator: React.FC<Props> = ({ label, title, children }) => {
   return (
     <div className="level-item level-left">
-      <div title={title ? title : label}>
+      <div title={title || label}>
         <p className="heading">{label}</p>
         <p className="title">{children}</p>
       </div>
     </div>
   );
-}
+};
 
 export default Indicator;

+ 3 - 9
kafka-ui-react-app/src/components/common/Dashboard/MetricsWrapper.tsx

@@ -13,16 +13,10 @@ const MetricsWrapper: React.FC<Props> = ({
 }) => {
   return (
     <div className={cx('box', wrapperClassName)}>
-      {title && (
-        <h5 className="subtitle is-6">
-          {title}
-        </h5>
-      )}
-      <div className="level">
-        {children}
-      </div>
+      {title && <h5 className="subtitle is-6">{title}</h5>}
+      <div className="level">{children}</div>
     </div>
   );
-}
+};
 
 export default MetricsWrapper;

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

@@ -1,4 +1,3 @@
-
 import React from 'react';
 import ReactDOM from 'react-dom';
 import { BrowserRouter } from 'react-router-dom';
@@ -16,7 +15,7 @@ ReactDOM.render(
       <AppContainer />
     </BrowserRouter>
   </Provider>,
-  document.getElementById('root'),
+  document.getElementById('root')
 );
 
 // If you want your app to work offline and load faster, you can change

+ 14 - 19
kafka-ui-react-app/src/lib/hooks/useInterval.ts

@@ -5,26 +5,21 @@ type Callback = () => any;
 const useInterval = (callback: Callback, delay: number) => {
   const savedCallback = React.useRef<Callback>();
 
-  React.useEffect(
-    () => {
-      savedCallback.current = callback;
-    },
-    [callback],
-  );
+  React.useEffect(() => {
+    savedCallback.current = callback;
+  }, [callback]);
 
-  React.useEffect(
-    () => {
-      const tick = () => {
-        savedCallback.current && savedCallback.current()
-      };
+  // eslint-disable-next-line consistent-return
+  React.useEffect(() => {
+    const tick = () => {
+      if (savedCallback.current) savedCallback.current();
+    };
 
-      if (delay !== null) {
-        const id = setInterval(tick, delay);
-        return () => clearInterval(id);
-      }
-    },
-    [delay],
-  );
-}
+    if (delay !== null) {
+      const id = setInterval(tick, delay);
+      return () => clearInterval(id);
+    }
+  }, [delay]);
+};
 
 export default useInterval;

+ 7 - 7
kafka-ui-react-app/src/lib/utils/formatBytes.ts

@@ -1,13 +1,13 @@
-function formatBytes(bytes: number, decimals: number = 0) {
-    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-    if (bytes === 0) return [0, sizes[0]];
+function formatBytes(bytes: number, decimals = 0) {
+  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+  if (bytes === 0) return [0, sizes[0]];
 
-    const k = 1024;
-    const dm = decimals < 0 ? 0 : decimals;
+  const k = 1024;
+  const dm = decimals < 0 ? 0 : decimals;
 
-    const i = Math.floor(Math.log(bytes) / Math.log(k));
+  const i = Math.floor(Math.log(bytes) / Math.log(k));
 
-    return [parseFloat((bytes / Math.pow(k, i)).toFixed(dm)), sizes[i]];
+  return [parseFloat((bytes / k ** i).toFixed(dm)), sizes[i]];
 }
 
 export default formatBytes;

+ 1 - 1
kafka-ui-react-app/src/react-app-env.d.ts

@@ -1 +1 @@
-/// <reference types="react-scripts" />
+// / <reference types="react-scripts" />

+ 4 - 1
kafka-ui-react-app/src/redux/interfaces/broker.ts

@@ -2,7 +2,10 @@ import { ClusterStats, Broker } from 'generated-sources';
 
 export type BrokerId = Broker['id'];
 
-export enum ZooKeeperStatus { offline, online };
+export enum ZooKeeperStatus {
+  offline,
+  online,
+}
 
 export interface BrokersState extends ClusterStats {
   items: Broker[];

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

@@ -33,4 +33,9 @@ export interface RootState {
 
 export type Action = ActionType<typeof actions>;
 
-export type PromiseThunk<T> = ThunkAction<Promise<T>, RootState, undefined, AnyAction>;
+export type PromiseThunk<T> = ThunkAction<
+  Promise<T>,
+  RootState,
+  undefined,
+  AnyAction
+>;

+ 6 - 1
kafka-ui-react-app/src/redux/interfaces/topic.ts

@@ -2,7 +2,7 @@ import {
   Topic,
   TopicDetails,
   TopicMessage,
-  TopicConfig,
+  TopicConfig as InputTopicConfig,
   TopicFormData,
   GetTopicMessagesRequest,
 } from 'generated-sources';
@@ -14,6 +14,10 @@ export enum CleanupPolicy {
   Compact = 'compact',
 }
 
+export interface TopicConfig extends InputTopicConfig {
+  id: string;
+}
+
 export interface TopicConfigByName {
   byName: TopicConfigParams;
 }
@@ -46,6 +50,7 @@ export interface TopicFormCustomParams {
 
 export interface TopicWithDetailedInfo extends Topic, TopicDetails {
   config?: TopicConfig[];
+  id: string;
 }
 
 export interface TopicsState {

+ 9 - 12
kafka-ui-react-app/src/redux/reducers/clusters/selectors.ts

@@ -9,21 +9,18 @@ const getClusterListFetchingStatus = createFetchingSelector('GET_CLUSTERS');
 
 export const getIsClusterListFetched = createSelector(
   getClusterListFetchingStatus,
-  (status) => status === FetchStatus.fetched,
+  (status) => status === FetchStatus.fetched
 );
 
-export const getClusterList = createSelector(clustersState, (clusters) => clusters);
+export const getClusterList = createSelector(
+  clustersState,
+  (clusters) => clusters
+);
 
-export const getOnlineClusters = createSelector(
-  getClusterList,
-  (clusters) => clusters.filter(
-    ({ status }) => status === ServerStatus.Online,
-  ),
+export const getOnlineClusters = createSelector(getClusterList, (clusters) =>
+  clusters.filter(({ status }) => status === ServerStatus.Online)
 );
 
-export const getOfflineClusters = createSelector(
-  getClusterList,
-  (clusters) => clusters.filter(
-    ({ status }) => status === ServerStatus.Offline,
-  ),
+export const getOfflineClusters = createSelector(getClusterList, (clusters) =>
+  clusters.filter(({ status }) => status === ServerStatus.Offline)
 );

+ 27 - 16
kafka-ui-react-app/src/redux/reducers/consumerGroups/selectors.ts

@@ -1,25 +1,35 @@
 import { createSelector } from 'reselect';
 import { RootState, FetchStatus } from 'redux/interfaces';
 import { createFetchingSelector } from 'redux/reducers/loader/selectors';
-import { ConsumerGroupID, ConsumerGroupsState } from '../../interfaces/consumerGroup';
-
-
-const consumerGroupsState = ({ consumerGroups }: RootState): ConsumerGroupsState => consumerGroups;
-
-const getConsumerGroupsMap = (state: RootState) => consumerGroupsState(state).byID;
-const getConsumerGroupsIDsList = (state: RootState) => consumerGroupsState(state).allIDs;
-
-const getConsumerGroupsListFetchingStatus = createFetchingSelector('GET_CONSUMER_GROUPS');
-const getConsumerGroupDetailsFetchingStatus = createFetchingSelector('GET_CONSUMER_GROUP_DETAILS');
+import {
+  ConsumerGroupID,
+  ConsumerGroupsState,
+} from '../../interfaces/consumerGroup';
+
+const consumerGroupsState = ({
+  consumerGroups,
+}: RootState): ConsumerGroupsState => consumerGroups;
+
+const getConsumerGroupsMap = (state: RootState) =>
+  consumerGroupsState(state).byID;
+const getConsumerGroupsIDsList = (state: RootState) =>
+  consumerGroupsState(state).allIDs;
+
+const getConsumerGroupsListFetchingStatus = createFetchingSelector(
+  'GET_CONSUMER_GROUPS'
+);
+const getConsumerGroupDetailsFetchingStatus = createFetchingSelector(
+  'GET_CONSUMER_GROUP_DETAILS'
+);
 
 export const getIsConsumerGroupsListFetched = createSelector(
   getConsumerGroupsListFetchingStatus,
-  (status) => status === FetchStatus.fetched,
+  (status) => status === FetchStatus.fetched
 );
 
 export const getIsConsumerGroupDetailsFetched = createSelector(
   getConsumerGroupDetailsFetchingStatus,
-  (status) => status === FetchStatus.fetched,
+  (status) => status === FetchStatus.fetched
 );
 
 export const getConsumerGroupsList = createSelector(
@@ -31,14 +41,15 @@ export const getConsumerGroupsList = createSelector(
       return [];
     }
 
-    return ids.map(key => byID[key]);
-  },
+    return ids.map((key) => byID[key]);
+  }
 );
 
-const getConsumerGroupID = (_: RootState, consumerGroupID: ConsumerGroupID) => consumerGroupID;
+const getConsumerGroupID = (_: RootState, consumerGroupID: ConsumerGroupID) =>
+  consumerGroupID;
 
 export const getConsumerGroupByID = createSelector(
   getConsumerGroupsMap,
   getConsumerGroupID,
-  (consumerGroups, consumerGroupID) => consumerGroups[consumerGroupID],
+  (consumerGroups, consumerGroupID) => consumerGroups[consumerGroupID]
 );

+ 1 - 1
kafka-ui-react-app/src/redux/reducers/index.ts

@@ -1,10 +1,10 @@
 import { combineReducers } from 'redux';
+import { RootState } from 'redux/interfaces';
 import topics from './topics/reducer';
 import clusters from './clusters/reducer';
 import brokers from './brokers/reducer';
 import consumerGroups from './consumerGroups/reducer';
 import loader from './loader/reducer';
-import { RootState } from 'redux/interfaces';
 
 export default combineReducers<RootState>({
   topics,

+ 1 - 5
kafka-ui-react-app/src/redux/reducers/loader/reducer.ts

@@ -1,8 +1,4 @@
-import {
-  FetchStatus,
-  Action,
-  LoaderState,
-} from 'redux/interfaces';
+import { FetchStatus, Action, LoaderState } from 'redux/interfaces';
 
 export const initialState: LoaderState = {};
 

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

@@ -1,4 +1,4 @@
 import { RootState, FetchStatus } from 'redux/interfaces';
 
-export const createFetchingSelector = (action: string) =>
-  (state: RootState) => (state.loader[action] || FetchStatus.notFetched);
+export const createFetchingSelector = (action: string) => (state: RootState) =>
+  state.loader[action] || FetchStatus.notFetched;

+ 8 - 3
kafka-ui-react-app/src/redux/reducers/topics/reducer.ts

@@ -1,5 +1,6 @@
-import { Action, TopicsState } from 'redux/interfaces';
+import { v4 } from 'uuid';
 import { Topic } from 'generated-sources';
+import { Action, TopicsState } from 'redux/interfaces';
 import ActionType from 'redux/actionType';
 
 export const initialState: TopicsState = {
@@ -19,6 +20,7 @@ const updateTopicList = (state: TopicsState, payload: Topic[]): TopicsState => {
     memo.byName[name] = {
       ...memo.byName[name],
       ...topic,
+      id: v4(),
     };
     memo.allNames.push(name);
 
@@ -31,7 +33,7 @@ const addToTopicList = (state: TopicsState, payload: Topic): TopicsState => {
     ...state,
   };
   newState.allNames.push(payload.name);
-  newState.byName[payload.name] = payload;
+  newState.byName[payload.name] = { ...payload, id: v4() };
   return newState;
 };
 
@@ -62,7 +64,10 @@ const reducer = (state = initialState, action: Action): TopicsState => {
           ...state.byName,
           [action.payload.topicName]: {
             ...state.byName[action.payload.topicName],
-            config: action.payload.config,
+            config: action.payload.config.map((inputConfig) => ({
+              ...inputConfig,
+              id: v4(),
+            })),
           },
         },
       };

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

@@ -84,7 +84,7 @@ export const getTopicByName = createSelector(
 export const getPartitionsByTopicName = createSelector(
   getTopicMap,
   getTopicName,
-  (topics, topicName) => (topics[topicName].partitions) as Partition[]
+  (topics, topicName) => topics[topicName].partitions as Partition[]
 );
 
 export const getFullTopic = createSelector(getTopicByName, (topic) =>

+ 4 - 5
kafka-ui-react-app/src/redux/store/configureStore/dev.ts

@@ -5,13 +5,12 @@ import rootReducer from '../../reducers';
 export default () => {
   const middlewares = [thunk];
 
-  const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+  const composeEnhancers =
+    (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
 
-  const enhancer = composeEnhancers(
-    applyMiddleware(...middlewares),
-  );
+  const enhancer = composeEnhancers(applyMiddleware(...middlewares));
 
   const store = createStore(rootReducer, undefined, enhancer);
 
-  return store
+  return store;
 };

+ 4 - 1
kafka-ui-react-app/src/redux/store/configureStore/index.ts

@@ -1,6 +1,9 @@
 import devConfigureStore from './dev';
 import prodConfigureStore from './prod';
 
-const configureStore = (process.env.NODE_ENV === 'production') ? prodConfigureStore : devConfigureStore
+const configureStore =
+  process.env.NODE_ENV === 'production'
+    ? prodConfigureStore
+    : devConfigureStore;
 
 export default configureStore;

+ 2 - 2
kafka-ui-react-app/src/redux/store/configureStore/prod.ts

@@ -3,11 +3,11 @@ import thunk from 'redux-thunk';
 import rootReducer from '../../reducers';
 
 export default () => {
-  const middlewares = [thunk]
+  const middlewares = [thunk];
 
   const enhancer = applyMiddleware(...middlewares);
 
   const store = createStore(rootReducer, undefined, enhancer);
 
-  return store
+  return store;
 };

+ 41 - 43
kafka-ui-react-app/src/serviceWorker.ts

@@ -25,47 +25,10 @@ type Config = {
   onUpdate?: (registration: ServiceWorkerRegistration) => void;
 };
 
-export function register(config?: Config) {
-  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
-    // The URL constructor is available in all browsers that support SW.
-    const publicUrl = new URL(
-      process.env.PUBLIC_URL,
-      window.location.href
-    );
-    if (publicUrl.origin !== window.location.origin) {
-      // Our service worker won't work if PUBLIC_URL is on a different origin
-      // from what our page is served on. This might happen if a CDN is used to
-      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
-      return;
-    }
-
-    window.addEventListener('load', () => {
-      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
-
-      if (isLocalhost) {
-        // This is running on localhost. Let's check if a service worker still exists or not.
-        checkValidServiceWorker(swUrl, config);
-
-        // Add some additional logging to localhost, pointing developers to the
-        // service worker/PWA documentation.
-        navigator.serviceWorker.ready.then(() => {
-          console.log(
-            'This web app is being served cache-first by a service ' +
-              'worker. To learn more, visit https://bit.ly/CRA-PWA'
-          );
-        });
-      } else {
-        // Is not localhost. Just register service worker
-        registerValidSW(swUrl, config);
-      }
-    });
-  }
-}
-
 function registerValidSW(swUrl: string, config?: Config) {
   navigator.serviceWorker
     .register(swUrl)
-    .then(registration => {
+    .then((registration) => {
       registration.onupdatefound = () => {
         const installingWorker = registration.installing;
         if (installingWorker == null) {
@@ -101,7 +64,7 @@ function registerValidSW(swUrl: string, config?: Config) {
         };
       };
     })
-    .catch(error => {
+    .catch((error) => {
       console.error('Error during service worker registration:', error);
     });
 }
@@ -109,9 +72,9 @@ function registerValidSW(swUrl: string, config?: Config) {
 function checkValidServiceWorker(swUrl: string, config?: Config) {
   // Check if the service worker can be found. If it can't reload the page.
   fetch(swUrl, {
-    headers: { 'Service-Worker': 'script' }
+    headers: { 'Service-Worker': 'script' },
   })
-    .then(response => {
+    .then((response) => {
       // Ensure service worker exists, and that we really are getting a JS file.
       const contentType = response.headers.get('content-type');
       if (
@@ -119,7 +82,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
         (contentType != null && contentType.indexOf('javascript') === -1)
       ) {
         // No service worker found. Probably a different app. Reload the page.
-        navigator.serviceWorker.ready.then(registration => {
+        navigator.serviceWorker.ready.then((registration) => {
           registration.unregister().then(() => {
             window.location.reload();
           });
@@ -136,9 +99,44 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
     });
 }
 
+export function register(config?: Config) {
+  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+    const url = process.env.PUBLIC_URL || 'localhost';
+    // The URL constructor is available in all browsers that support SW.
+    const publicUrl = new URL(url, window.location.href);
+    if (publicUrl.origin !== window.location.origin) {
+      // Our service worker won't work if PUBLIC_URL is on a different origin
+      // from what our page is served on. This might happen if a CDN is used to
+      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+      return;
+    }
+
+    window.addEventListener('load', () => {
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+      if (isLocalhost) {
+        // This is running on localhost. Let's check if a service worker still exists or not.
+        checkValidServiceWorker(swUrl, config);
+
+        // Add some additional logging to localhost, pointing developers to the
+        // service worker/PWA documentation.
+        navigator.serviceWorker.ready.then(() => {
+          console.log(
+            'This web app is being served cache-first by a service ' +
+              'worker. To learn more, visit https://bit.ly/CRA-PWA'
+          );
+        });
+      } else {
+        // Is not localhost. Just register service worker
+        registerValidSW(swUrl, config);
+      }
+    });
+  }
+}
+
 export function unregister() {
   if ('serviceWorker' in navigator) {
-    navigator.serviceWorker.ready.then(registration => {
+    navigator.serviceWorker.ready.then((registration) => {
       registration.unregister();
     });
   }

+ 1 - 0
kafka-ui-react-app/src/setupTests.ts

@@ -2,4 +2,5 @@
 // allows you to do things like:
 // expect(element).toHaveTextContent(/react/i)
 // learn more: https://github.com/testing-library/jest-dom
+// eslint-disable-next-line import/no-extraneous-dependencies
 import '@testing-library/jest-dom/extend-expect';

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