Przeglądaj źródła

Merge branch 'master' of github.com:provectus/kafka-ui into refactor-for-mono

Roman Nedzvetskiy 5 lat temu
rodzic
commit
c5b4a87fff

+ 9 - 0
kafka-ui-react-app/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+end_of_line = lf
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true

+ 59 - 0
kafka-ui-react-app/.eslintrc.json

@@ -0,0 +1,59 @@
+{
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "globals": {
+    "Atomics": "readonly",
+    "SharedArrayBuffer": "readonly"
+  },
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "ecmaFeatures": {
+      "jsx": true
+    },
+    "ecmaVersion": 2018,
+    "sourceType": "module"
+  },
+  "plugins": ["react", "@typescript-eslint", "prettier"],
+  "extends": [
+    "plugin:react/recommended",
+    "airbnb",
+    "plugin:prettier/recommended",
+    "plugin:@typescript-eslint/eslint-recommended",
+    "plugin:@typescript-eslint/recommended"
+  ],
+  "rules": {
+    "import/extensions": [
+      "error",
+      "ignorePackages",
+      {
+        "js": "never",
+        "jsx": "never",
+        "ts": "never",
+        "tsx": "never"
+      }
+    ],
+    "prettier/prettier": "error",
+    "@typescript-eslint/explicit-function-return-type": "off",
+    "react/jsx-filename-extension": [
+      1,
+      { "extensions": [".js", ".jsx", ".ts", ".tsx"] }
+    ]
+  },
+  "overrides": [
+    {
+      "files": ["**/*.tsx"],
+      "rules": {
+        "react/prop-types": "off"
+      }
+    }
+  ],
+  "settings": {
+    "import/resolver": {
+      "node": {
+        "extensions": [".js", ".jsx", ".ts", ".tsx"]
+      }
+    }
+  }
+}

+ 4 - 0
kafka-ui-react-app/.prettierrc

@@ -0,0 +1,4 @@
+{
+  "singleQuote": true,
+  "trailingComma": "es5"
+}

Plik diff jest za duży
+ 572 - 99
kafka-ui-react-app/package-lock.json


+ 28 - 0
kafka-ui-react-app/package.json

@@ -35,13 +35,25 @@
     "typesafe-actions": "^5.1.0",
     "typescript": "~3.7.4"
   },
+  "lint-staged": {
+    "*.{js,ts,jsx,tsx}": [
+      "eslint -c .eslintrc.json --fix"
+    ]
+  },
   "scripts": {
     "start": "react-scripts start",
     "build": "react-scripts build",
+    "lint": "esprint check",
+    "lint:fix": "esprint check --fix",
     "test": "react-scripts test",
     "eject": "react-scripts eject",
     "mock": "node ./mock/index.js"
   },
+  "husky": {
+    "hooks": {
+      "pre-commit": "yarn tsc --noEmit && lint-staged"
+    }
+  },
   "eslintConfig": {
     "extends": "react-app"
   },
@@ -56,5 +68,21 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "@typescript-eslint/eslint-plugin": "^2.27.0",
+    "@typescript-eslint/parser": "^2.27.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-plugin-react-hooks": "^2.5.1",
+    "esprint": "^0.6.0",
+    "husky": "^4.2.5",
+    "lint-staged": ">=10",
+    "prettier": "^2.0.4"
   }
 }

+ 26 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParamAction.tsx

@@ -0,0 +1,26 @@
+import React from 'react';
+import CustomParamButton, { CustomParamButtonType } from './CustomParamButton';
+import { isFirstParam } from './CustomParams';
+
+interface Props {
+  index: string;
+  onAdd: (event: React.MouseEvent<HTMLButtonElement>) => void;
+  onRemove: (index: string) => void;
+}
+
+const CustomParamAction: React.FC<Props> = ({
+  index,
+  onAdd,
+  onRemove,
+}) => (
+  <>
+    <label className='label'>&nbsp;</label>
+    {
+      isFirstParam(index)
+        ? <CustomParamButton className="is-success" type={CustomParamButtonType.plus} onClick={onAdd} />
+        : <CustomParamButton className="is-danger" type={CustomParamButtonType.minus} onClick={() => onRemove(index)} />
+    }
+  </>
+)
+
+export default CustomParamAction;

+ 26 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParamButton.tsx

@@ -0,0 +1,26 @@
+import React from 'react';
+
+export enum CustomParamButtonType {
+  plus = 'fa-plus',
+  minus = 'fa-minus',
+}
+
+interface Props {
+  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void,
+  className: string;
+  type: CustomParamButtonType;
+}
+
+const CustomParamButton: React.FC<Props> = ({
+  onClick,
+  className,
+  type,
+}) => (
+  <button className={`button ${className} is-outlined`} onClick={onClick}>
+    <span className="icon">
+      <i className={`fas fa-lg ${type}`}></i>
+    </span>
+  </button>
+)
+
+export default CustomParamButton;

+ 20 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParamOptions.tsx

@@ -0,0 +1,20 @@
+import React from 'react';
+import { TopicCustomParamOption } from 'redux/interfaces';
+import { CUSTOM_PARAMS_OPTIONS } from './customParamsOptions';
+
+interface Props {};
+
+const CustomParamOptions: React.FC<Props> = () => (
+  <>
+    <option value=''>Select</option>
+    {
+      Object.values(CUSTOM_PARAMS_OPTIONS).map((opt: TopicCustomParamOption) => (
+        <option key={opt.name} value={opt.name}>
+          {opt.name}
+        </option>
+      ))
+    }
+  </>
+);
+
+export default React.memo(CustomParamOptions);

+ 67 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParamSelect.tsx

@@ -0,0 +1,67 @@
+import React from 'react';
+import { useFormContext, ErrorMessage } from 'react-hook-form';
+import CustomParamOptions from './CustomParamOptions';
+import { isFirstParam, INDEX_PREFIX } from './CustomParams';
+import { TopicFormCustomParam } from 'redux/interfaces';
+
+interface Props {
+  isDisabled: boolean;
+  index: string;
+  name: string;
+}
+
+const CustomParamSelect: React.FC<Props> = ({
+  isDisabled,
+  index,
+  name,
+}) => {
+
+  const { register, unregister, errors, getValues, triggerValidation } = useFormContext();
+  const optInputName = `${index}[name]`;
+
+  React.useEffect(
+    () => { if (isFirstParam(index)) { unregister(optInputName) } },
+  );
+
+  const selectedMustBeUniq = (selected: string) => {
+    const values = getValues({ nest: true });
+    const customParamsValues: TopicFormCustomParam = values.customParams;
+
+    let valid = true;
+
+    for (const [key, customParam] of Object.entries(customParamsValues)) {
+      if (`${INDEX_PREFIX}.${key}` === index) { continue; }
+      if (selected === customParam.name) {
+        valid = false;
+        break;
+      };
+    }
+
+    return valid ? true : 'Custom Parameter must be unique';
+  };
+
+  return (
+    <>
+      <label className="label">Custom Parameter</label>
+      <div className="select is-block">
+        <select
+          name={optInputName}
+          ref={register({
+            required: 'Custom Parameter is required.',
+            validate: { unique: selected => selectedMustBeUniq(selected) },
+          })}
+          onChange={() => triggerValidation(optInputName)}
+          disabled={isDisabled}
+          defaultValue={name}
+        >
+          <CustomParamOptions />
+        </select>
+        <p className="help is-danger">
+          <ErrorMessage errors={errors} name={optInputName}/>
+        </p>
+      </div>
+    </>
+  );
+};
+
+export default React.memo(CustomParamSelect);

+ 61 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParamValue.tsx

@@ -0,0 +1,61 @@
+import React from 'react';
+import { useFormContext, ErrorMessage } from 'react-hook-form';
+import { CUSTOM_PARAMS_OPTIONS } from './customParamsOptions';
+import { isFirstParam } from './CustomParams';
+
+interface Props {
+  isDisabled: boolean;
+  index: string;
+  name: string;
+  defaultValue: string;
+}
+
+const CustomParamValue: React.FC<Props> = ({
+  isDisabled,
+  index,
+  name,
+  defaultValue,
+}) => {
+  const { register, unregister, errors, watch, setValue } = useFormContext();
+  const selectInputName = `${index}[name]`;
+  const valInputName = `${index}[value]`;
+  const selectedParamName = watch(selectInputName, name);
+
+  React.useEffect(() => {
+    if (selectedParamName) {
+      setValue(
+        valInputName,
+        CUSTOM_PARAMS_OPTIONS[selectedParamName].defaultValue,
+        true
+      );
+    }
+  }, [selectedParamName]);
+
+  React.useEffect(() => {
+    if (isFirstParam(index)) {
+      unregister(valInputName);
+    }
+  });
+
+  return (
+    <>
+      <label className="label">Value</label>
+      <input
+        className="input"
+        placeholder="Value"
+        ref={register({
+          required: 'Value is required.',
+        })}
+        name={valInputName}
+        defaultValue={defaultValue}
+        autoComplete="off"
+        disabled={isDisabled}
+      />
+      <p className="help is-danger">
+        <ErrorMessage errors={errors} name={valInputName} />
+      </p>
+    </>
+  );
+};
+
+export default React.memo(CustomParamValue);

+ 89 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParams.tsx

@@ -0,0 +1,89 @@
+import React from 'react';
+import { omit, reject } from 'lodash';
+
+import { TopicFormCustomParams } from 'redux/interfaces';
+import CustomParamSelect from './CustomParamSelect';
+import CustomParamValue from './CustomParamValue';
+import CustomParamAction from './CustomParamAction';
+
+const DEFAULT_INDEX = 'default';
+export const INDEX_PREFIX = 'customParams';
+export const isFirstParam = (index: string) => (index === DEFAULT_INDEX);
+
+interface Props {
+  isSubmitting: boolean;
+}
+
+const CustomParams: React.FC<Props> = ({
+  isSubmitting,
+}) => {
+
+  const [formCustomParams, setFormCustomParams] = React.useState<TopicFormCustomParams>({
+    byIndex: { [DEFAULT_INDEX]: { name: '', value: '' } },
+    allIndexes: [DEFAULT_INDEX],
+  });
+
+  const onAdd = (event: React.MouseEvent<HTMLButtonElement>) => {
+    event.preventDefault();
+
+    const newIndex = `${INDEX_PREFIX}.${new Date().getTime()}`;
+
+    setFormCustomParams({
+      ...formCustomParams,
+      byIndex: {
+        ...formCustomParams.byIndex,
+        [newIndex]: { name: '', value: '' },
+      },
+      allIndexes: [
+        formCustomParams.allIndexes[0],
+        newIndex,
+        ...formCustomParams.allIndexes.slice(1),
+      ],
+    });
+  }
+
+  const onRemove = (index: string) => {
+    setFormCustomParams({
+      ...formCustomParams,
+      byIndex: omit(formCustomParams.byIndex, index),
+      allIndexes: reject(formCustomParams.allIndexes, (i) => (i === index)),
+    });
+  }
+
+  return (
+    <>
+      {
+        formCustomParams.allIndexes.map((index) => (
+          <div className="columns is-centered" key={index}>
+            <div className="column">
+              <CustomParamSelect
+                index={index}
+                isDisabled={isFirstParam(index) || isSubmitting}
+                name={formCustomParams.byIndex[index].name}
+              />
+            </div>
+
+            <div className="column">
+              <CustomParamValue
+                index={index}
+                isDisabled={isFirstParam(index) || isSubmitting}
+                name={formCustomParams.byIndex[index].name}
+                defaultValue={formCustomParams.byIndex[index].value}
+              />
+            </div>
+
+            <div className="column is-narrow">
+              <CustomParamAction
+                index={index}
+                onAdd={onAdd}
+                onRemove={onRemove}
+              />
+            </div>
+          </div>
+        ))
+      }
+    </>
+  );
+};
+
+export default CustomParams;

+ 18 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/CustomParamsContainer.tsx

@@ -0,0 +1,18 @@
+import { connect } from 'react-redux';
+import { RootState } from 'redux/interfaces';
+import { withRouter, RouteComponentProps } from 'react-router-dom';
+import CustomParams from './CustomParams';
+
+interface RouteProps {};
+
+interface OwnProps extends RouteComponentProps<RouteProps> {
+  isSubmitting: boolean;
+}
+
+const mapStateToProps = (state: RootState, { isSubmitting }: OwnProps) => ({
+  isSubmitting,
+})
+
+export default withRouter(
+  connect(mapStateToProps)(CustomParams)
+);

+ 96 - 0
kafka-ui-react-app/src/components/Topics/New/CustomParams/customParamsOptions.tsx

@@ -0,0 +1,96 @@
+import { TopicCustomParamOption } from 'redux/interfaces';
+
+interface CustomParamOption {
+  [optionName: string]: TopicCustomParamOption;
+}
+
+export const CUSTOM_PARAMS_OPTIONS: CustomParamOption = {
+  "compression.type": {
+    "name": "compression.type",
+    "defaultValue": "producer"
+  },
+  "leader.replication.throttled.replicas": {
+    "name": "leader.replication.throttled.replicas",
+    "defaultValue": ""
+  },
+  "message.downconversion.enable": {
+    "name": "message.downconversion.enable",
+    "defaultValue": "true"
+  },
+  "segment.jitter.ms": {
+    "name": "segment.jitter.ms",
+    "defaultValue": "0"
+  },
+  "flush.ms": {
+    "name": "flush.ms",
+    "defaultValue": "9223372036854775807"
+  },
+  "follower.replication.throttled.replicas": {
+    "name": "follower.replication.throttled.replicas",
+    "defaultValue": ""
+  },
+  "segment.bytes": {
+    "name": "segment.bytes",
+    "defaultValue": "1073741824"
+  },
+  "flush.messages": {
+    "name": "flush.messages",
+    "defaultValue": "9223372036854775807"
+  },
+  "message.format.version": {
+    "name": "message.format.version",
+    "defaultValue": "2.3-IV1"
+  },
+  "file.delete.delay.ms": {
+    "name": "file.delete.delay.ms",
+    "defaultValue": "60000"
+  },
+  "max.compaction.lag.ms": {
+    "name": "max.compaction.lag.ms",
+    "defaultValue": "9223372036854775807"
+  },
+  "min.compaction.lag.ms": {
+    "name": "min.compaction.lag.ms",
+    "defaultValue": "0"
+  },
+  "message.timestamp.type": {
+    "name": "message.timestamp.type",
+    "defaultValue": "CreateTime"
+  },
+  "preallocate": {
+    "name": "preallocate",
+    "defaultValue": "false"
+  },
+  "min.cleanable.dirty.ratio": {
+    "name": "min.cleanable.dirty.ratio",
+    "defaultValue": "0.5"
+  },
+  "index.interval.bytes": {
+    "name": "index.interval.bytes",
+    "defaultValue": "4096"
+  },
+  "unclean.leader.election.enable": {
+    "name": "unclean.leader.election.enable",
+    "defaultValue": "true"
+  },
+  "retention.bytes": {
+    "name": "retention.bytes",
+    "defaultValue": "-1"
+  },
+  "delete.retention.ms": {
+    "name": "delete.retention.ms",
+    "defaultValue": "86400000"
+  },
+  "segment.ms": {
+    "name": "segment.ms",
+    "defaultValue": "604800000"
+  },
+  "message.timestamp.difference.max.ms": {
+    "name": "message.timestamp.difference.max.ms",
+    "defaultValue": "9223372036854775807"
+  },
+  "segment.index.bytes": {
+    "name": "segment.index.bytes",
+    "defaultValue": "10485760"
+  }
+}

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

@@ -1,14 +1,16 @@
 import React from 'react';
 import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
-import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
-import { clusterTopicsPath } from 'lib/paths';
 import { useForm, FormContext, ErrorMessage } from 'react-hook-form';
 
+import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
+import CustomParamsContainer from "./CustomParams/CustomParamsContainer";
+import TimeToRetain from './TimeToRetain';
+import { clusterTopicsPath } from 'lib/paths';
 import {
   TOPIC_NAME_VALIDATION_PATTERN,
   BYTES_IN_GB,
 } from 'lib/constants';
-import TimeToRetain from './TimeToRetain';
+
 
 interface Props {
   clusterName: ClusterName;
@@ -219,6 +221,8 @@ const New: React.FC<Props> = ({
               </div>
             </div>
 
+            <CustomParamsContainer isSubmitting={isSubmitting} />
+
             <input type="submit" className="button is-primary" disabled={isSubmitting}/>
           </form>
         </FormContext>

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

@@ -1,4 +1,4 @@
-import { createAsyncAction} from 'typesafe-actions';
+import { createAsyncAction } from 'typesafe-actions';
 import { ActionType } from 'redux/actionType';
 import { ConsumerGroup } from '../interfaces/consumerGroup';
 import {

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

@@ -23,6 +23,11 @@ export interface TopicPartition {
   replicas: TopicReplica[];
 }
 
+export interface TopicCustomParamOption {
+  name: string;
+  defaultValue: string;
+}
+
 export interface TopicDetails {
   partitionCount?: number;
   replicationFactor?: number;
@@ -39,13 +44,23 @@ export interface Topic {
   partitions: TopicPartition[];
 }
 
+export interface TopicFormCustomParam {
+  name: string;
+  value: string;
+}
+
+export interface TopicFormCustomParams {
+  byIndex: { [paramIndex: string]: TopicFormCustomParam };
+  allIndexes: string[];
+}
+
 export interface TopicWithDetailedInfo extends Topic, TopicDetails {
   config?: TopicConfig[];
 }
 
 export interface TopicsState {
-  byName: { [topicName: string]: TopicWithDetailedInfo },
-  allNames: TopicName[],
+  byName: { [topicName: string]: TopicWithDetailedInfo };
+  allNames: TopicName[];
 }
 
 export interface TopicFormData {
@@ -57,4 +72,7 @@ export interface TopicFormData {
   retentionMs: number;
   retentionBytes: number;
   maxMessageBytes: number;
+  customParams: {
+    [index: string]: TopicFormCustomParam;
+  };
 };

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików