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>
This commit is contained in:
parent
9fd3697062
commit
494443bb08
54 changed files with 4090 additions and 4416 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -32,3 +32,4 @@ build/
|
|||
/kafka-ui-api/app/node
|
||||
|
||||
.DS_Store
|
||||
*.code-workspace
|
||||
|
|
|
@ -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,2 +1,2 @@
|
|||
# Kafka REST API
|
||||
REACT_APP_API_URL=/api
|
||||
REACT_APP_API_URL=
|
||||
|
|
3
kafka-ui-react-app/.gitignore
vendored
3
kafka-ui-react-app/.gitignore
vendored
|
@ -24,3 +24,6 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
.idea
|
||||
|
||||
# generated sources
|
||||
src/generated-sources
|
7574
kafka-ui-react-app/package-lock.json
generated
7574
kafka-ui-react-app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -8,7 +8,7 @@ import { fetchConsumerGroupDetails } from 'redux/actions/thunks';
|
|||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
consumerGroupID: string;
|
||||
consumerGroupID: ConsumerGroupID;
|
||||
}
|
||||
|
||||
interface OwnProps extends RouteComponentProps<RouteProps> { }
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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,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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> { }
|
||||
|
|
|
@ -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:"
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,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));
|
||||
|
|
|
@ -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')}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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]) => {
|
||||
|
|
|
@ -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> = ({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
|
@ -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());
|
|
@ -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());
|
|
@ -1,4 +0,0 @@
|
|||
export * from './topics';
|
||||
export * from './clusters';
|
||||
export * from './brokers';
|
||||
export * from './consumerGroups';
|
|
@ -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());
|
||||
};
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {
|
||||
byName: TopicConfigParams;
|
||||
}
|
||||
|
||||
export interface TopicConfigParams {
|
||||
[paramName: string]: TopicConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TopicReplica {
|
||||
broker: number;
|
||||
leader: boolean;
|
||||
inSync: true;
|
||||
export interface TopicConfigOption {
|
||||
name: TopicConfig['name'];
|
||||
defaultValue: TopicConfig['defaultValue'];
|
||||
}
|
||||
|
||||
export interface TopicPartition {
|
||||
partition: number;
|
||||
leader: number;
|
||||
offsetMin: number;
|
||||
offsetMax: number;
|
||||
replicas: TopicReplica[];
|
||||
export interface TopicConfigValue {
|
||||
name: TopicConfig['name'];
|
||||
value: TopicConfig['value'];
|
||||
}
|
||||
|
||||
export interface TopicCustomParamOption {
|
||||
name: string;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
export interface TopicDetails {
|
||||
partitions: TopicPartition[];
|
||||
}
|
||||
|
||||
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 TopicMessage {
|
||||
partition: number;
|
||||
offset: number;
|
||||
timestamp: string;
|
||||
timestampType: string;
|
||||
key: string;
|
||||
headers: Record<string, string>;
|
||||
content: any;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[] = [];
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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) =>
|
||||
|
|
Loading…
Add table
Reference in a new issue