issue/21-topic-form-make-time-to-retain-customizable
This commit is contained in:
parent
c26edd1316
commit
3e174e8a44
5 changed files with 223 additions and 179 deletions
13
kafka-ui-react-app/package-lock.json
generated
13
kafka-ui-react-app/package-lock.json
generated
|
@ -10478,6 +10478,11 @@
|
||||||
"json-parse-better-errors": "^1.0.1"
|
"json-parse-better-errors": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"parse-ms": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="
|
||||||
|
},
|
||||||
"parse5": {
|
"parse5": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
|
||||||
|
@ -11686,6 +11691,14 @@
|
||||||
"react-is": "^16.8.4"
|
"react-is": "^16.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pretty-ms": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-ke4njoVmlotekHlHyCZ3wI/c5AMT8peuHs8rKJqekj/oR5G8lND2dVpicFlUz5cbZgE290vvkMuDwfj/OcW1kw==",
|
||||||
|
"requires": {
|
||||||
|
"parse-ms": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"private": {
|
"private": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"json-server": "^0.15.1",
|
"json-server": "^0.15.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"node-sass": "^4.13.1",
|
"node-sass": "^4.13.1",
|
||||||
|
"pretty-ms": "^6.0.1",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-hook-form": "^4.5.5",
|
"react-hook-form": "^4.5.5",
|
||||||
|
|
|
@ -2,12 +2,13 @@ import React from 'react';
|
||||||
import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
|
import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
|
||||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
import { clusterTopicsPath } from 'lib/paths';
|
import { clusterTopicsPath } from 'lib/paths';
|
||||||
import { useForm, ErrorMessage } from 'react-hook-form';
|
import { useForm, FormContext, ErrorMessage } from 'react-hook-form';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TOPIC_NAME_VALIDATION_PATTERN,
|
TOPIC_NAME_VALIDATION_PATTERN,
|
||||||
MILLISECONDS_IN_DAY,
|
|
||||||
BYTES_IN_GB,
|
BYTES_IN_GB,
|
||||||
} from 'lib/constants';
|
} from 'lib/constants';
|
||||||
|
import TimeToRetain from './TimeToRetain';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
|
@ -24,17 +25,17 @@ const New: React.FC<Props> = ({
|
||||||
redirectToTopicPath,
|
redirectToTopicPath,
|
||||||
resetUploadedState
|
resetUploadedState
|
||||||
}) => {
|
}) => {
|
||||||
const {register, handleSubmit, errors, getValues} = useForm<TopicFormData>();
|
const methods = useForm<TopicFormData>();
|
||||||
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (isSubmitting && isTopicCreated) {
|
if (isSubmitting && isTopicCreated) {
|
||||||
const {name} = getValues();
|
const {name} = methods.getValues();
|
||||||
redirectToTopicPath(clusterName, name);
|
redirectToTopicPath(clusterName, name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, getValues],
|
[isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, methods.getValues],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSubmit = async (data: TopicFormData) => {
|
const onSubmit = async (data: TopicFormData) => {
|
||||||
|
@ -59,192 +60,168 @@ const New: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<FormContext {...methods}>
|
||||||
<div className="columns">
|
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
||||||
<div className="column is-three-quarters">
|
<div className="columns">
|
||||||
<label className="label">
|
<div className="column is-three-quarters">
|
||||||
Topic Name *
|
<label className="label">
|
||||||
</label>
|
Topic Name *
|
||||||
<input
|
</label>
|
||||||
className="input"
|
<input
|
||||||
placeholder="Topic Name"
|
className="input"
|
||||||
ref={register({
|
placeholder="Topic Name"
|
||||||
required: 'Topic Name is required.',
|
ref={methods.register({
|
||||||
pattern: {
|
required: 'Topic Name is required.',
|
||||||
value: TOPIC_NAME_VALIDATION_PATTERN,
|
pattern: {
|
||||||
message: 'Only alphanumeric, _, -, and . allowed',
|
value: TOPIC_NAME_VALIDATION_PATTERN,
|
||||||
},
|
message: 'Only alphanumeric, _, -, and . allowed',
|
||||||
})}
|
},
|
||||||
name="name"
|
})}
|
||||||
autoComplete="off"
|
name="name"
|
||||||
disabled={isSubmitting}
|
autoComplete="off"
|
||||||
/>
|
|
||||||
<p className="help is-danger">
|
|
||||||
<ErrorMessage errors={errors} name="name"/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="column">
|
|
||||||
<label className="label">
|
|
||||||
Number of partitions *
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className="input"
|
|
||||||
type="number"
|
|
||||||
placeholder="Number of partitions"
|
|
||||||
defaultValue="1"
|
|
||||||
ref={register({required: 'Number of partitions is required.'})}
|
|
||||||
name="partitions"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
<p className="help is-danger">
|
|
||||||
<ErrorMessage errors={errors} name="partitions"/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="columns">
|
|
||||||
<div className="column">
|
|
||||||
<label className="label">
|
|
||||||
Replication Factor *
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className="input"
|
|
||||||
type="number"
|
|
||||||
placeholder="Replication Factor"
|
|
||||||
defaultValue="1"
|
|
||||||
ref={register({required: 'Replication Factor is required.'})}
|
|
||||||
name="replicationFactor"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
<p className="help is-danger">
|
|
||||||
<ErrorMessage errors={errors} name="replicationFactor"/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="column">
|
|
||||||
<label className="label">
|
|
||||||
Min In Sync Replicas *
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className="input"
|
|
||||||
type="number"
|
|
||||||
placeholder="Replication Factor"
|
|
||||||
defaultValue="1"
|
|
||||||
ref={register({required: 'Min In Sync Replicas is required.'})}
|
|
||||||
name="minInSyncReplicas"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
<p className="help is-danger">
|
|
||||||
<ErrorMessage errors={errors} name="minInSyncReplicas"/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="columns">
|
|
||||||
<div className="column is-one-third">
|
|
||||||
<label className="label">
|
|
||||||
Cleanup policy
|
|
||||||
</label>
|
|
||||||
<div className="select is-block">
|
|
||||||
<select
|
|
||||||
defaultValue={CleanupPolicy.Delete}
|
|
||||||
name="cleanupPolicy"
|
|
||||||
ref={register}
|
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
/>
|
||||||
<option value={CleanupPolicy.Delete}>
|
<p className="help is-danger">
|
||||||
Delete
|
<ErrorMessage errors={methods.errors} name="name"/>
|
||||||
</option>
|
</p>
|
||||||
<option value={CleanupPolicy.Compact}>
|
</div>
|
||||||
Compact
|
|
||||||
</option>
|
<div className="column">
|
||||||
</select>
|
<label className="label">
|
||||||
|
Number of partitions *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="number"
|
||||||
|
placeholder="Number of partitions"
|
||||||
|
defaultValue="1"
|
||||||
|
ref={methods.register({required: 'Number of partitions is required.'})}
|
||||||
|
name="partitions"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
<p className="help is-danger">
|
||||||
|
<ErrorMessage errors={methods.errors} name="partitions"/>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="column is-one-third">
|
<div className="columns">
|
||||||
<label className="label">
|
<div className="column">
|
||||||
Time to retain data
|
<label className="label">
|
||||||
</label>
|
Replication Factor *
|
||||||
<div className="select is-block">
|
</label>
|
||||||
<select
|
<input
|
||||||
defaultValue={MILLISECONDS_IN_DAY * 7}
|
className="input"
|
||||||
name="retentionMs"
|
type="number"
|
||||||
ref={register}
|
placeholder="Replication Factor"
|
||||||
|
defaultValue="1"
|
||||||
|
ref={methods.register({required: 'Replication Factor is required.'})}
|
||||||
|
name="replicationFactor"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
/>
|
||||||
<option value={MILLISECONDS_IN_DAY / 2}>
|
<p className="help is-danger">
|
||||||
12 hours
|
<ErrorMessage errors={methods.errors} name="replicationFactor"/>
|
||||||
</option>
|
</p>
|
||||||
<option value={MILLISECONDS_IN_DAY}>
|
</div>
|
||||||
1 day
|
|
||||||
</option>
|
<div className="column">
|
||||||
<option value={MILLISECONDS_IN_DAY * 2}>
|
<label className="label">
|
||||||
2 days
|
Min In Sync Replicas *
|
||||||
</option>
|
</label>
|
||||||
<option value={MILLISECONDS_IN_DAY * 7}>
|
<input
|
||||||
1 week
|
className="input"
|
||||||
</option>
|
type="number"
|
||||||
<option value={MILLISECONDS_IN_DAY * 7 * 4}>
|
placeholder="Replication Factor"
|
||||||
4 weeks
|
defaultValue="1"
|
||||||
</option>
|
ref={methods.register({required: 'Min In Sync Replicas is required.'})}
|
||||||
</select>
|
name="minInSyncReplicas"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
<p className="help is-danger">
|
||||||
|
<ErrorMessage errors={methods.errors} name="minInSyncReplicas"/>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="column is-one-third">
|
<div className="columns">
|
||||||
<label className="label">
|
<div className="column is-one-third">
|
||||||
Max size on disk in GB
|
<label className="label">
|
||||||
</label>
|
Cleanup policy
|
||||||
<div className="select is-block">
|
</label>
|
||||||
<select
|
<div className="select is-block">
|
||||||
defaultValue={-1}
|
<select
|
||||||
name="retentionBytes"
|
defaultValue={CleanupPolicy.Delete}
|
||||||
ref={register}
|
name="cleanupPolicy"
|
||||||
disabled={isSubmitting}
|
ref={methods.register}
|
||||||
>
|
disabled={isSubmitting}
|
||||||
<option value={-1}>
|
>
|
||||||
Not Set
|
<option value={CleanupPolicy.Delete}>
|
||||||
</option>
|
Delete
|
||||||
<option value={BYTES_IN_GB}>
|
</option>
|
||||||
1 GB
|
<option value={CleanupPolicy.Compact}>
|
||||||
</option>
|
Compact
|
||||||
<option value={BYTES_IN_GB * 10}>
|
</option>
|
||||||
10 GB
|
</select>
|
||||||
</option>
|
</div>
|
||||||
<option value={BYTES_IN_GB * 20}>
|
</div>
|
||||||
20 GB
|
|
||||||
</option>
|
<div className="column is-one-third">
|
||||||
<option value={BYTES_IN_GB * 50}>
|
<TimeToRetain isSubmitting={isSubmitting} />
|
||||||
50 GB
|
</div>
|
||||||
</option>
|
|
||||||
</select>
|
<div className="column is-one-third">
|
||||||
|
<label className="label">
|
||||||
|
Max size on disk in GB
|
||||||
|
</label>
|
||||||
|
<div className="select is-block">
|
||||||
|
<select
|
||||||
|
defaultValue={-1}
|
||||||
|
name="retentionBytes"
|
||||||
|
ref={methods.register}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
<option value={-1}>
|
||||||
|
Not Set
|
||||||
|
</option>
|
||||||
|
<option value={BYTES_IN_GB}>
|
||||||
|
1 GB
|
||||||
|
</option>
|
||||||
|
<option value={BYTES_IN_GB * 10}>
|
||||||
|
10 GB
|
||||||
|
</option>
|
||||||
|
<option value={BYTES_IN_GB * 20}>
|
||||||
|
20 GB
|
||||||
|
</option>
|
||||||
|
<option value={BYTES_IN_GB * 50}>
|
||||||
|
50 GB
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
Maximum message size in bytes *
|
Maximum message size in bytes *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input"
|
||||||
type="number"
|
type="number"
|
||||||
defaultValue="1000012"
|
defaultValue="1000012"
|
||||||
ref={register({required: 'Maximum message size in bytes is required'})}
|
ref={methods.register({required: 'Maximum message size in bytes is required'})}
|
||||||
name="maxMessageBytes"
|
name="maxMessageBytes"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<p className="help is-danger">
|
<p className="help is-danger">
|
||||||
<ErrorMessage errors={errors} name="maxMessageBytes"/>
|
<ErrorMessage errors={methods.errors} name="maxMessageBytes"/>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="submit" className="button is-primary" disabled={isSubmitting}/>
|
<input type="submit" className="button is-primary" disabled={isSubmitting}/>
|
||||||
</form>
|
</form>
|
||||||
|
</FormContext>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React from 'react';
|
||||||
|
import prettyMilliseconds from 'pretty-ms';
|
||||||
|
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
||||||
|
import { MILLISECONDS_IN_WEEK } from 'lib/constants';
|
||||||
|
|
||||||
|
const MILLISECONDS_IN_SECOND = 1000;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isSubmitting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeToRetain: React.FC<Props> = ({
|
||||||
|
isSubmitting,
|
||||||
|
}) => {
|
||||||
|
const { register, errors, watch } = useFormContext();
|
||||||
|
const defaultValue = MILLISECONDS_IN_WEEK;
|
||||||
|
const name: string = 'retentionMs';
|
||||||
|
const watchedValue: any = watch(name, defaultValue.toString());
|
||||||
|
|
||||||
|
const valueHint = React.useMemo(() => {
|
||||||
|
const value = parseInt(watchedValue, 10);
|
||||||
|
return value >= MILLISECONDS_IN_SECOND ? prettyMilliseconds(value) : false;
|
||||||
|
}, [watchedValue])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<label className="label">
|
||||||
|
Time to retain data (in ms)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="number"
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
name={name}
|
||||||
|
ref={register(
|
||||||
|
{ min: { value: -1, message: 'must be greater than or equal to -1' }}
|
||||||
|
)}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
<p className="help is-danger">
|
||||||
|
<ErrorMessage errors={errors} name={name}/>
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
valueHint &&
|
||||||
|
<p className="help is-info">
|
||||||
|
{valueHint}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimeToRetain;
|
|
@ -10,6 +10,6 @@ export const BASE_URL = process.env.REACT_APP_API_URL;
|
||||||
|
|
||||||
export const TOPIC_NAME_VALIDATION_PATTERN = RegExp(/^[.,A-Za-z0-9_-]+$/);
|
export const TOPIC_NAME_VALIDATION_PATTERN = RegExp(/^[.,A-Za-z0-9_-]+$/);
|
||||||
|
|
||||||
export const MILLISECONDS_IN_DAY = 86_400_000;
|
export const MILLISECONDS_IN_WEEK = 604_800_000;
|
||||||
|
|
||||||
export const BYTES_IN_GB = 1_073_741_824;
|
export const BYTES_IN_GB = 1_073_741_824;
|
||||||
|
|
Loading…
Add table
Reference in a new issue