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
|
/kafka-ui-api/app/node
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.code-workspace
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
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">
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>kafka-ui</artifactId>
|
<artifactId>kafka-ui</artifactId>
|
||||||
<groupId>com.provectus</groupId>
|
<groupId>com.provectus</groupId>
|
||||||
|
@ -70,8 +69,57 @@
|
||||||
</configOptions>
|
</configOptions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</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>
|
</executions>
|
||||||
</plugin>
|
</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>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
# Kafka REST API
|
# 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*
|
yarn-error.log*
|
||||||
|
|
||||||
.idea
|
.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 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 useInterval from 'lib/hooks/useInterval';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
|
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
|
||||||
import Indicator from 'components/common/Dashboard/Indicator';
|
import Indicator from 'components/common/Dashboard/Indicator';
|
||||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
|
|
||||||
interface Props extends BrokerMetrics {
|
interface Props extends ClusterStats {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
isFetched: boolean;
|
isFetched: boolean;
|
||||||
|
fetchClusterStats: (clusterName: ClusterName) => void;
|
||||||
fetchBrokers: (clusterName: ClusterName) => void;
|
fetchBrokers: (clusterName: ClusterName) => void;
|
||||||
fetchBrokerMetrics: (clusterName: ClusterName) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Topics: React.FC<Props> = ({
|
const Topics: React.FC<Props> = ({
|
||||||
clusterName,
|
clusterName,
|
||||||
isFetched,
|
|
||||||
brokerCount,
|
brokerCount,
|
||||||
activeControllers,
|
activeControllers,
|
||||||
zooKeeperStatus,
|
zooKeeperStatus,
|
||||||
|
@ -24,18 +24,18 @@ const Topics: React.FC<Props> = ({
|
||||||
inSyncReplicasCount,
|
inSyncReplicasCount,
|
||||||
outOfSyncReplicasCount,
|
outOfSyncReplicasCount,
|
||||||
underReplicatedPartitionCount,
|
underReplicatedPartitionCount,
|
||||||
|
fetchClusterStats,
|
||||||
fetchBrokers,
|
fetchBrokers,
|
||||||
fetchBrokerMetrics,
|
|
||||||
}) => {
|
}) => {
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
() => {
|
() => {
|
||||||
|
fetchClusterStats(clusterName);
|
||||||
fetchBrokers(clusterName);
|
fetchBrokers(clusterName);
|
||||||
fetchBrokerMetrics(clusterName);
|
|
||||||
},
|
},
|
||||||
[fetchBrokers, fetchBrokerMetrics, clusterName],
|
[fetchClusterStats, fetchBrokers, clusterName],
|
||||||
);
|
);
|
||||||
|
|
||||||
useInterval(() => { fetchBrokerMetrics(clusterName); }, 5000);
|
useInterval(() => { fetchClusterStats(clusterName); }, 5000);
|
||||||
|
|
||||||
const zkOnline = zooKeeperStatus === ZooKeeperStatus.online;
|
const zkOnline = zooKeeperStatus === ZooKeeperStatus.online;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ const Topics: React.FC<Props> = ({
|
||||||
<span className={cx({'has-text-danger': offlinePartitionCount !== 0})}>
|
<span className={cx({'has-text-danger': offlinePartitionCount !== 0})}>
|
||||||
{onlinePartitionCount}
|
{onlinePartitionCount}
|
||||||
</span>
|
</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>
|
||||||
<Indicator label="URP" title="Under replicated partitions">
|
<Indicator label="URP" title="Under replicated partitions">
|
||||||
{underReplicatedPartitionCount}
|
{underReplicatedPartitionCount}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
|
fetchClusterStats,
|
||||||
fetchBrokers,
|
fetchBrokers,
|
||||||
fetchBrokerMetrics,
|
|
||||||
} from 'redux/actions';
|
} from 'redux/actions';
|
||||||
import Brokers from './Brokers';
|
import Brokers from './Brokers';
|
||||||
import * as brokerSelectors from 'redux/reducers/brokers/selectors';
|
import * as brokerSelectors from 'redux/reducers/brokers/selectors';
|
||||||
|
@ -28,8 +28,8 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
fetchClusterStats: (clusterName: ClusterName) => fetchClusterStats(clusterName),
|
||||||
fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
|
fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
|
||||||
fetchBrokerMetrics: (clusterName: ClusterName) => fetchBrokerMetrics(clusterName),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Brokers);
|
export default connect(mapStateToProps, mapDispatchToProps)(Brokers);
|
||||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
||||||
import { ClusterName } from 'redux/interfaces';
|
import { ClusterName } from 'redux/interfaces';
|
||||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
import { clusterConsumerGroupsPath } from 'lib/paths';
|
import { clusterConsumerGroupsPath } from 'lib/paths';
|
||||||
|
import { ConsumerGroupID } from 'redux/interfaces/consumerGroup';
|
||||||
import {
|
import {
|
||||||
ConsumerGroupID,
|
|
||||||
ConsumerGroup,
|
ConsumerGroup,
|
||||||
ConsumerGroupDetails,
|
ConsumerGroupDetails,
|
||||||
Consumer,
|
ConsumerTopicPartitionDetail,
|
||||||
} from 'redux/interfaces/consumerGroup';
|
} from 'generated-sources';
|
||||||
|
|
||||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import ListItem from './ListItem';
|
import ListItem from './ListItem';
|
||||||
|
@ -15,7 +15,7 @@ import ListItem from './ListItem';
|
||||||
interface Props extends ConsumerGroup, ConsumerGroupDetails {
|
interface Props extends ConsumerGroup, ConsumerGroupDetails {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
consumerGroupID: ConsumerGroupID;
|
consumerGroupID: ConsumerGroupID;
|
||||||
consumers: Consumer[];
|
consumers?: ConsumerTopicPartitionDetail[];
|
||||||
isFetched: boolean;
|
isFetched: boolean;
|
||||||
fetchConsumerGroupDetails: (
|
fetchConsumerGroupDetails: (
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { fetchConsumerGroupDetails } from 'redux/actions/thunks';
|
||||||
|
|
||||||
interface RouteProps {
|
interface RouteProps {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
consumerGroupID: string;
|
consumerGroupID: ConsumerGroupID;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OwnProps extends RouteComponentProps<RouteProps> { }
|
interface OwnProps extends RouteComponentProps<RouteProps> { }
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Consumer } from 'redux/interfaces/consumerGroup';
|
import { ConsumerTopicPartitionDetail } from 'generated-sources';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { ClusterName } from 'redux/interfaces/cluster';
|
import { ClusterName } from 'redux/interfaces/cluster';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
consumer: Consumer;
|
consumer: ConsumerTopicPartitionDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItem: React.FC<Props> = ({ clusterName, consumer }) => {
|
const ListItem: React.FC<Props> = ({ clusterName, consumer }) => {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import React from 'react';
|
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 Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
import ListItem from './ListItem';
|
import ListItem from './ListItem';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { ConsumerGroup } from 'redux/interfaces';
|
import { ConsumerGroup } from 'generated-sources';
|
||||||
|
|
||||||
const ListItem: React.FC<{ consumerGroup: ConsumerGroup }> = ({
|
const ListItem: React.FC<{ consumerGroup: ConsumerGroup }> = ({
|
||||||
consumerGroup,
|
consumerGroup,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Cluster, ClusterStatus } from 'redux/interfaces';
|
|
||||||
import formatBytes from 'lib/utils/formatBytes';
|
import formatBytes from 'lib/utils/formatBytes';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { clusterBrokersPath } from 'lib/paths';
|
import { clusterBrokersPath } from 'lib/paths';
|
||||||
|
import { Cluster, ServerStatus } from 'generated-sources';
|
||||||
|
|
||||||
const ClusterWidget: React.FC<Cluster> = ({
|
const ClusterWidget: React.FC<Cluster> = ({
|
||||||
name,
|
name,
|
||||||
|
@ -20,7 +20,7 @@ const ClusterWidget: React.FC<Cluster> = ({
|
||||||
title={name}
|
title={name}
|
||||||
>
|
>
|
||||||
<div
|
<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}
|
{status}
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,11 +43,11 @@ const ClusterWidget: React.FC<Cluster> = ({
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Production</th>
|
<th>Production</th>
|
||||||
<td>{formatBytes(bytesInPerSec)}</td>
|
<td>{formatBytes(bytesInPerSec || 0)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Consumption</th>
|
<th>Consumption</th>
|
||||||
<td>{formatBytes(bytesOutPerSec)}</td>
|
<td>{formatBytes(bytesOutPerSec || 0)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { chunk } from 'lodash';
|
import { chunk } from 'lodash';
|
||||||
import { Cluster } from 'redux/interfaces';
|
|
||||||
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
|
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
|
||||||
import Indicator from 'components/common/Dashboard/Indicator';
|
import Indicator from 'components/common/Dashboard/Indicator';
|
||||||
import ClusterWidget from './ClusterWidget';
|
import ClusterWidget from './ClusterWidget';
|
||||||
|
import { Cluster } from 'generated-sources';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
clusters: Cluster[];
|
clusters: Cluster[];
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
import { Cluster, ClusterStatus } from 'redux/interfaces';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
clusterBrokersPath,
|
clusterBrokersPath,
|
||||||
clusterTopicsPath,
|
clusterTopicsPath,
|
||||||
clusterConsumerGroupsPath,
|
clusterConsumerGroupsPath,
|
||||||
} from 'lib/paths';
|
} from 'lib/paths';
|
||||||
|
import { Cluster, ServerStatus } from 'generated-sources';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
|
@ -42,7 +42,7 @@ const StatusIcon: React.FC<Props> = ({ cluster }) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`tag ${
|
className={`tag ${
|
||||||
cluster.status === ClusterStatus.Online ? 'is-primary' : 'is-danger'
|
cluster.status === ServerStatus.Online ? 'is-primary' : 'is-danger'
|
||||||
}`}
|
}`}
|
||||||
title={cluster.status}
|
title={cluster.status}
|
||||||
style={style}
|
style={style}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Cluster } from 'redux/interfaces';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import ClusterMenu from './ClusterMenu';
|
import ClusterMenu from './ClusterMenu';
|
||||||
|
import { Cluster } from 'generated-sources';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isClusterListFetched: boolean;
|
isClusterListFetched: boolean;
|
||||||
|
@ -29,7 +29,7 @@ const Nav: React.FC<Props> = ({
|
||||||
|
|
||||||
{isClusterListFetched &&
|
{isClusterListFetched &&
|
||||||
clusters.map((cluster) => (
|
clusters.map((cluster) => (
|
||||||
<ClusterMenu cluster={cluster} key={cluster.id} />
|
<ClusterMenu cluster={cluster} key={cluster.name} />
|
||||||
))}
|
))}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
import { NavLink, Switch, Route } from 'react-router-dom';
|
import { NavLink, Switch, Route } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import {ClusterName, RootState} from 'redux/interfaces';
|
import {
|
||||||
|
ClusterName,
|
||||||
|
RootState,
|
||||||
|
TopicName
|
||||||
|
} from 'redux/interfaces';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
|
|
||||||
interface RouteProps {
|
interface RouteProps {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
topicName: string;
|
topicName: TopicName;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OwnProps extends RouteComponentProps<RouteProps> { }
|
interface OwnProps extends RouteComponentProps<RouteProps> { }
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
ClusterName,
|
ClusterName,
|
||||||
SeekType,
|
|
||||||
SeekTypes,
|
|
||||||
TopicMessage,
|
|
||||||
TopicMessageQueryParams,
|
TopicMessageQueryParams,
|
||||||
TopicName,
|
TopicName
|
||||||
TopicPartition,
|
|
||||||
} from 'redux/interfaces';
|
} from 'redux/interfaces';
|
||||||
|
import {
|
||||||
|
TopicMessage,
|
||||||
|
Partition,
|
||||||
|
SeekType
|
||||||
|
} from 'generated-sources';
|
||||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import DatePicker from 'react-datepicker';
|
import DatePicker from 'react-datepicker';
|
||||||
|
@ -33,12 +34,12 @@ interface Props {
|
||||||
queryParams: Partial<TopicMessageQueryParams>
|
queryParams: Partial<TopicMessageQueryParams>
|
||||||
) => void;
|
) => void;
|
||||||
messages: TopicMessage[];
|
messages: TopicMessage[];
|
||||||
partitions: TopicPartition[];
|
partitions: Partition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterProps {
|
interface FilterProps {
|
||||||
offset: number;
|
offset: TopicMessage['offset'];
|
||||||
partition: number;
|
partition: TopicMessage['partition'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePrevious(value: any) {
|
function usePrevious(value: any) {
|
||||||
|
@ -63,7 +64,7 @@ const Messages: React.FC<Props> = ({
|
||||||
);
|
);
|
||||||
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
||||||
const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
|
const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
|
||||||
SeekTypes.OFFSET
|
SeekType.OFFSET
|
||||||
);
|
);
|
||||||
const [searchOffset, setSearchOffset] = React.useState<string>('0');
|
const [searchOffset, setSearchOffset] = React.useState<string>('0');
|
||||||
const [selectedPartitions, setSelectedPartitions] = React.useState<Option[]>(
|
const [selectedPartitions, setSelectedPartitions] = React.useState<Option[]>(
|
||||||
|
@ -105,7 +106,7 @@ const Messages: React.FC<Props> = ({
|
||||||
const foundedValues = filterProps.find(
|
const foundedValues = filterProps.find(
|
||||||
(prop) => prop.partition === partition.value
|
(prop) => prop.partition === partition.value
|
||||||
);
|
);
|
||||||
if (selectedSeekType === SeekTypes.OFFSET) {
|
if (selectedSeekType === SeekType.OFFSET) {
|
||||||
return foundedValues ? foundedValues.offset : 0;
|
return foundedValues ? foundedValues.offset : 0;
|
||||||
}
|
}
|
||||||
return searchTimestamp ? searchTimestamp.getTime() : null;
|
return searchTimestamp ? searchTimestamp.getTime() : null;
|
||||||
|
@ -134,7 +135,7 @@ const Messages: React.FC<Props> = ({
|
||||||
setSearchTimestamp(searchTimestamp);
|
setSearchTimestamp(searchTimestamp);
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
seekType: SeekTypes.TIMESTAMP,
|
seekType: SeekType.TIMESTAMP,
|
||||||
seekTo: selectedPartitions.map((p) => `${p.value}::${timestamp}`),
|
seekTo: selectedPartitions.map((p) => `${p.value}::${timestamp}`),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,7 +156,7 @@ const Messages: React.FC<Props> = ({
|
||||||
const offset = event.target.value || '0';
|
const offset = event.target.value || '0';
|
||||||
setSearchOffset(offset);
|
setSearchOffset(offset);
|
||||||
debouncedCallback({
|
debouncedCallback({
|
||||||
seekType: SeekTypes.OFFSET,
|
seekType: SeekType.OFFSET,
|
||||||
seekTo: selectedPartitions.map((p) => `${p.value}::${offset}`),
|
seekTo: selectedPartitions.map((p) => `${p.value}::${offset}`),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -176,9 +177,9 @@ const Messages: React.FC<Props> = ({
|
||||||
fetchTopicMessages(clusterName, topicName, queryParams);
|
fetchTopicMessages(clusterName, topicName, queryParams);
|
||||||
}, [clusterName, topicName, queryParams]);
|
}, [clusterName, topicName, queryParams]);
|
||||||
|
|
||||||
const getTimestampDate = (timestamp: string) => {
|
const getFormattedDate = (date: Date) => {
|
||||||
if (!Date.parse(timestamp)) return;
|
if (!date) return null;
|
||||||
return format(Date.parse(timestamp), 'yyyy-MM-dd HH:mm:ss');
|
return format(date, 'yyyy-MM-dd HH:mm:ss');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMessageContentBody = (content: any) => {
|
const getMessageContentBody = (content: any) => {
|
||||||
|
@ -224,7 +225,7 @@ const Messages: React.FC<Props> = ({
|
||||||
|
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
seekType: SeekTypes.OFFSET,
|
seekType: SeekType.OFFSET,
|
||||||
seekTo,
|
seekTo,
|
||||||
});
|
});
|
||||||
fetchTopicMessages(clusterName, topicName, queryParams);
|
fetchTopicMessages(clusterName, topicName, queryParams);
|
||||||
|
@ -255,7 +256,7 @@ const Messages: React.FC<Props> = ({
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<tr key={`${message.timestamp}${Math.random()}`}>
|
<tr key={`${message.timestamp}${Math.random()}`}>
|
||||||
<td style={{ width: 200 }}>
|
<td style={{ width: 200 }}>
|
||||||
{getTimestampDate(message.timestamp)}
|
{getFormattedDate(message.timestamp)}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ width: 150 }}>{message.offset}</td>
|
<td style={{ width: 150 }}>{message.offset}</td>
|
||||||
<td style={{ width: 100 }}>{message.partition}</td>
|
<td style={{ width: 100 }}>{message.partition}</td>
|
||||||
|
@ -309,16 +310,16 @@ const Messages: React.FC<Props> = ({
|
||||||
id="selectSeekType"
|
id="selectSeekType"
|
||||||
name="selectSeekType"
|
name="selectSeekType"
|
||||||
onChange={handleSeekTypeChange}
|
onChange={handleSeekTypeChange}
|
||||||
defaultValue={SeekTypes.OFFSET}
|
defaultValue={SeekType.OFFSET}
|
||||||
value={selectedSeekType}
|
value={selectedSeekType}
|
||||||
>
|
>
|
||||||
<option value={SeekTypes.OFFSET}>Offset</option>
|
<option value={SeekType.OFFSET}>Offset</option>
|
||||||
<option value={SeekTypes.TIMESTAMP}>Timestamp</option>
|
<option value={SeekType.TIMESTAMP}>Timestamp</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-one-fifth">
|
<div className="column is-one-fifth">
|
||||||
{selectedSeekType === SeekTypes.OFFSET ? (
|
{selectedSeekType === SeekType.OFFSET ? (
|
||||||
<>
|
<>
|
||||||
<label className="label">Offset</label>
|
<label className="label">Offset</label>
|
||||||
<input
|
<input
|
||||||
|
@ -335,7 +336,7 @@ const Messages: React.FC<Props> = ({
|
||||||
<label className="label">Timestamp</label>
|
<label className="label">Timestamp</label>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={searchTimestamp}
|
selected={searchTimestamp}
|
||||||
onChange={(date) => setSearchTimestamp(date)}
|
onChange={(date: Date | null) => setSearchTimestamp(date)}
|
||||||
onCalendarClose={handleDateTimeChange}
|
onCalendarClose={handleDateTimeChange}
|
||||||
showTimeInput
|
showTimeInput
|
||||||
timeInputLabel="Time:"
|
timeInputLabel="Time:"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
|
||||||
import Indicator from 'components/common/Dashboard/Indicator';
|
import Indicator from 'components/common/Dashboard/Indicator';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ClusterName, TopicName, TopicConfig } from 'redux/interfaces';
|
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||||
|
import { TopicConfig } from 'generated-sources';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
ClusterName,
|
ClusterName,
|
||||||
TopicFormData,
|
TopicFormDataRaw,
|
||||||
TopicName,
|
TopicName,
|
||||||
TopicConfigByName,
|
TopicConfigByName,
|
||||||
TopicWithDetailedInfo,
|
TopicWithDetailedInfo,
|
||||||
CleanupPolicy,
|
CleanupPolicy,
|
||||||
} from 'redux/interfaces';
|
} from 'redux/interfaces';
|
||||||
|
import { TopicConfig } from 'generated-sources';
|
||||||
import { useForm, FormContext } from 'react-hook-form';
|
import { useForm, FormContext } from 'react-hook-form';
|
||||||
import { camelCase } from 'lodash';
|
import { camelCase } from 'lodash';
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ interface Props {
|
||||||
isTopicUpdated: boolean;
|
isTopicUpdated: boolean;
|
||||||
fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => void;
|
fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => void;
|
||||||
fetchTopicConfig: (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;
|
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => void;
|
||||||
resetUploadedState: () => void;
|
resetUploadedState: () => void;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +45,7 @@ const topicParams = (topic: TopicWithDetailedInfo | undefined) => {
|
||||||
const { name, replicationFactor } = topic;
|
const { name, replicationFactor } = topic;
|
||||||
|
|
||||||
const configs = topic.config?.reduce(
|
const configs = topic.config?.reduce(
|
||||||
(result: { [name: string]: string }, param) => {
|
(result: { [key: string]: TopicConfig['value'] }, param) => {
|
||||||
result[camelCase(param.name)] = param.value || param.defaultValue;
|
result[camelCase(param.name)] = param.value || param.defaultValue;
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
@ -76,7 +77,7 @@ const Edit: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const defaultValues = topicParams(topic);
|
const defaultValues = topicParams(topic);
|
||||||
|
|
||||||
const methods = useForm<TopicFormData>({ defaultValues });
|
const methods = useForm<TopicFormDataRaw>({ defaultValues });
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ const Edit: React.FC<Props> = ({
|
||||||
config.byName[param.name] = param;
|
config.byName[param.name] = param;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (data: TopicFormData) => {
|
const onSubmit = async (data: TopicFormDataRaw) => {
|
||||||
updateTopic(clusterName, data);
|
updateTopic(clusterName, data);
|
||||||
setIsSubmitting(true); // Keep this action after updateTopic to prevent redirect before update.
|
setIsSubmitting(true); // Keep this action after updateTopic to prevent redirect before update.
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
RootState,
|
RootState,
|
||||||
ClusterName,
|
ClusterName,
|
||||||
TopicFormData,
|
|
||||||
TopicName,
|
TopicName,
|
||||||
Action,
|
Action,
|
||||||
} from 'redux/interfaces';
|
} from 'redux/interfaces';
|
||||||
|
@ -21,6 +20,7 @@ import {
|
||||||
import { clusterTopicPath } from 'lib/paths';
|
import { clusterTopicPath } from 'lib/paths';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import Edit from './Edit';
|
import Edit from './Edit';
|
||||||
|
import { TopicFormDataRaw } from 'redux/interfaces';
|
||||||
|
|
||||||
interface RouteProps {
|
interface RouteProps {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
|
@ -53,7 +53,7 @@ const mapDispatchToProps = (
|
||||||
dispatch(fetchTopicDetails(clusterName, topicName)),
|
dispatch(fetchTopicDetails(clusterName, topicName)),
|
||||||
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) =>
|
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) =>
|
||||||
dispatch(fetchTopicConfig(clusterName, topicName)),
|
dispatch(fetchTopicConfig(clusterName, topicName)),
|
||||||
updateTopic: (clusterName: ClusterName, form: TopicFormData) =>
|
updateTopic: (clusterName: ClusterName, form: TopicFormDataRaw) =>
|
||||||
dispatch(updateTopic(clusterName, form)),
|
dispatch(updateTopic(clusterName, form)),
|
||||||
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
|
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
|
||||||
history.push(clusterTopicPath(clusterName, topicName));
|
history.push(clusterTopicPath(clusterName, topicName));
|
||||||
|
|
|
@ -14,8 +14,8 @@ const ListItem: React.FC<TopicWithDetailedInfo> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return partitions.reduce((memo: number, { replicas }) => {
|
return partitions.reduce((memo: number, { replicas }) => {
|
||||||
const outOfSync = replicas.filter(({ inSync }) => !inSync)
|
const outOfSync = replicas?.filter(({ inSync }) => !inSync)
|
||||||
return memo + outOfSync.length;
|
return memo + (outOfSync?.length || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [partitions])
|
}, [partitions])
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ const ListItem: React.FC<TopicWithDetailedInfo> = ({
|
||||||
{name}
|
{name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</td>
|
</td>
|
||||||
<td>{partitions.length}</td>
|
<td>{partitions?.length}</td>
|
||||||
<td>{outOfSyncReplicas}</td>
|
<td>{outOfSyncReplicas}</td>
|
||||||
<td>
|
<td>
|
||||||
<div className={cx('tag is-small', internal ? 'is-light' : 'is-success')}>
|
<div className={cx('tag is-small', internal ? 'is-light' : 'is-success')}>
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import React from 'react';
|
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 { useForm, FormContext } from 'react-hook-form';
|
||||||
|
|
||||||
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 TopicForm from 'components/Topics/shared/Form/TopicForm';
|
import TopicForm from 'components/Topics/shared/Form/TopicForm';
|
||||||
|
import { TopicFormDataRaw } from 'redux/interfaces';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
isTopicCreated: boolean;
|
isTopicCreated: boolean;
|
||||||
createTopic: (clusterName: ClusterName, form: TopicFormData) => void;
|
createTopic: (clusterName: ClusterName, form: TopicFormDataRaw) => void;
|
||||||
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => void;
|
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => void;
|
||||||
resetUploadedState: () => void;
|
resetUploadedState: () => void;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +22,7 @@ const New: React.FC<Props> = ({
|
||||||
redirectToTopicPath,
|
redirectToTopicPath,
|
||||||
resetUploadedState,
|
resetUploadedState,
|
||||||
}) => {
|
}) => {
|
||||||
const methods = useForm<TopicFormData>();
|
const methods = useForm<TopicFormDataRaw>();
|
||||||
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -31,7 +32,7 @@ const New: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
}, [isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, methods]);
|
}, [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
|
// 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
|
// 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
|
// 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 { connect } from 'react-redux';
|
||||||
import { RootState, ClusterName, TopicFormData, TopicName, Action } from 'redux/interfaces';
|
import {
|
||||||
import New from './New';
|
RootState,
|
||||||
|
ClusterName,
|
||||||
|
TopicName,
|
||||||
|
Action,
|
||||||
|
TopicFormDataRaw,
|
||||||
|
} from 'redux/interfaces';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
import { createTopic } from 'redux/actions';
|
import { createTopic } from 'redux/actions';
|
||||||
import { getTopicCreated } from 'redux/reducers/topics/selectors';
|
import { getTopicCreated } from 'redux/reducers/topics/selectors';
|
||||||
import { clusterTopicPath } from 'lib/paths';
|
import { clusterTopicPath } from 'lib/paths';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import * as actions from "../../../redux/actions/actions";
|
import * as actions from 'redux/actions';
|
||||||
|
import New from './New';
|
||||||
|
|
||||||
interface RouteProps {
|
interface RouteProps {
|
||||||
clusterName: ClusterName;
|
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,
|
clusterName,
|
||||||
isTopicCreated: getTopicCreated(state),
|
isTopicCreated: getTopicCreated(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, undefined, Action>, { history }: OwnProps) => ({
|
const mapDispatchToProps = (
|
||||||
createTopic: (clusterName: ClusterName, form: TopicFormData) => {
|
dispatch: ThunkDispatch<RootState, undefined, Action>,
|
||||||
|
{ history }: OwnProps
|
||||||
|
) => ({
|
||||||
|
createTopic: (clusterName: ClusterName, form: TopicFormDataRaw) => {
|
||||||
dispatch(createTopic(clusterName, form));
|
dispatch(createTopic(clusterName, form));
|
||||||
},
|
},
|
||||||
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
|
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
|
||||||
history.push(clusterTopicPath(clusterName, 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;
|
clusterName: ClusterName;
|
||||||
isFetched: boolean;
|
isFetched: boolean;
|
||||||
fetchBrokers: (clusterName: ClusterName) => void;
|
fetchBrokers: (clusterName: ClusterName) => void;
|
||||||
fetchTopicList: (clusterName: ClusterName) => void;
|
fetchTopicsList: (clusterName: ClusterName) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Topics: React.FC<Props> = ({
|
const Topics: React.FC<Props> = ({
|
||||||
clusterName,
|
clusterName,
|
||||||
isFetched,
|
isFetched,
|
||||||
fetchTopicList,
|
fetchTopicsList,
|
||||||
}) => {
|
}) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchTopicList(clusterName);
|
fetchTopicsList(clusterName);
|
||||||
}, [fetchTopicList, clusterName]);
|
}, [fetchTopicsList, clusterName]);
|
||||||
|
|
||||||
if (isFetched) {
|
if (isFetched) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { fetchTopicList } from 'redux/actions';
|
import { fetchTopicsList } from 'redux/actions';
|
||||||
import Topics from './Topics';
|
import Topics from './Topics';
|
||||||
import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
|
import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
|
||||||
import { RootState, ClusterName } from 'redux/interfaces';
|
import { RootState, ClusterName } from 'redux/interfaces';
|
||||||
|
@ -17,7 +17,7 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchTopicList: (clusterName: ClusterName) => fetchTopicList(clusterName),
|
fetchTopicsList: (clusterName: ClusterName) => fetchTopicsList(clusterName),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
|
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
|
||||||
|
|
|
@ -2,13 +2,14 @@ import React from 'react';
|
||||||
import CustomParamSelect from 'components/Topics/shared/Form/CustomParams/CustomParamSelect';
|
import CustomParamSelect from 'components/Topics/shared/Form/CustomParams/CustomParamSelect';
|
||||||
import CustomParamValue from 'components/Topics/shared/Form/CustomParams/CustomParamValue';
|
import CustomParamValue from 'components/Topics/shared/Form/CustomParams/CustomParamValue';
|
||||||
import CustomParamAction from 'components/Topics/shared/Form/CustomParams/CustomParamAction';
|
import CustomParamAction from 'components/Topics/shared/Form/CustomParams/CustomParamAction';
|
||||||
|
import { TopicConfig } from 'generated-sources';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
index: string;
|
index: string;
|
||||||
name: string;
|
name: TopicConfig['name'];
|
||||||
existingFields: string[];
|
existingFields: string[];
|
||||||
defaultValue: string;
|
defaultValue: TopicConfig['defaultValue'];
|
||||||
onNameChange: (inputName: string, name: string) => void;
|
onNameChange: (inputName: string, name: string) => void;
|
||||||
onRemove: (index: string) => void;
|
onRemove: (index: string) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TopicCustomParamOption } from 'redux/interfaces';
|
import { TopicConfigOption } from 'redux/interfaces';
|
||||||
import { omitBy } from 'lodash';
|
import { omitBy } from 'lodash';
|
||||||
import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
|
import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ const CustomParamOptions: React.FC<Props> = ({ existingFields }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<option value="">Select</option>
|
<option value="">Select</option>
|
||||||
{Object.values(fields).map((opt: TopicCustomParamOption) => (
|
{Object.values(fields).map((opt: TopicConfigOption) => (
|
||||||
<option key={opt.name} value={opt.name}>
|
<option key={opt.name} value={opt.name}>
|
||||||
{opt.name}
|
{opt.name}
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
||||||
import { TopicFormCustomParam } from 'redux/interfaces';
|
import { TopicConfigValue } from 'redux/interfaces';
|
||||||
import CustomParamOptions from './CustomParamOptions';
|
import CustomParamOptions from './CustomParamOptions';
|
||||||
import { INDEX_PREFIX } from './CustomParams';
|
import { INDEX_PREFIX } from './CustomParams';
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ const CustomParamSelect: React.FC<Props> = ({
|
||||||
|
|
||||||
const selectedMustBeUniq = (selected: string) => {
|
const selectedMustBeUniq = (selected: string) => {
|
||||||
const values = getValues({ nest: true });
|
const values = getValues({ nest: true });
|
||||||
const customParamsValues: TopicFormCustomParam = values.customParams;
|
const customParamsValues: TopicConfigValue = values.customParams;
|
||||||
|
|
||||||
const valid = !Object.entries(customParamsValues).some(
|
const valid = !Object.entries(customParamsValues).some(
|
||||||
([key, customParam]) => {
|
([key, customParam]) => {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
||||||
import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
|
import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
|
||||||
|
import { TopicConfig } from 'generated-sources';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
index: string;
|
index: string;
|
||||||
name: string;
|
name: TopicConfig['name'];
|
||||||
defaultValue: string;
|
defaultValue: TopicConfig['defaultValue'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomParamValue: React.FC<Props> = ({
|
const CustomParamValue: React.FC<Props> = ({
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { omit, reject, reduce, remove } from 'lodash';
|
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 CustomParamButton, { CustomParamButtonType } from './CustomParamButton';
|
||||||
import CustomParamField from './CustomParamField';
|
import CustomParamField from './CustomParamField';
|
||||||
|
|
||||||
|
@ -12,20 +16,13 @@ interface Props {
|
||||||
config?: TopicConfigByName;
|
config?: TopicConfigByName;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Param {
|
|
||||||
[index: string]: {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingFields: string[] = [];
|
const existingFields: string[] = [];
|
||||||
|
|
||||||
const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
|
const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
|
||||||
const byIndex = config
|
const byIndex = config
|
||||||
? reduce(
|
? reduce(
|
||||||
config.byName,
|
config.byName,
|
||||||
(result: Param, param, paramName) => {
|
(result: TopicConfigParams, param, paramName) => {
|
||||||
result[`${INDEX_PREFIX}.${new Date().getTime()}ts`] = {
|
result[`${INDEX_PREFIX}.${new Date().getTime()}ts`] = {
|
||||||
name: paramName,
|
name: paramName,
|
||||||
value: param.value,
|
value: param.value,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { TopicCustomParamOption } from 'redux/interfaces';
|
import { TopicConfigOption } from 'redux/interfaces';
|
||||||
|
|
||||||
interface CustomParamOption {
|
interface TopicConfigOptions {
|
||||||
[optionName: string]: TopicCustomParamOption;
|
[optionName: string]: TopicConfigOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CUSTOM_PARAMS_OPTIONS: CustomParamOption = {
|
const CUSTOM_PARAMS_OPTIONS: TopicConfigOptions = {
|
||||||
'compression.type': {
|
'compression.type': {
|
||||||
name: 'compression.type',
|
name: 'compression.type',
|
||||||
defaultValue: 'producer',
|
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',
|
credentials: 'include',
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'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 TOPIC_NAME_VALIDATION_PATTERN = RegExp(/^[.,A-Za-z0-9_-]+$/);
|
||||||
|
|
||||||
export const MILLISECONDS_IN_WEEK = 604_800_000;
|
export const MILLISECONDS_IN_WEEK = 604_800_000;
|
||||||
|
|
|
@ -3,6 +3,14 @@ enum ActionType {
|
||||||
GET_CLUSTERS__SUCCESS = 'GET_CLUSTERS__SUCCESS',
|
GET_CLUSTERS__SUCCESS = 'GET_CLUSTERS__SUCCESS',
|
||||||
GET_CLUSTERS__FAILURE = 'GET_CLUSTERS__FAILURE',
|
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__REQUEST = 'GET_BROKERS__REQUEST',
|
||||||
GET_BROKERS__SUCCESS = 'GET_BROKERS__SUCCESS',
|
GET_BROKERS__SUCCESS = 'GET_BROKERS__SUCCESS',
|
||||||
GET_BROKERS__FAILURE = 'GET_BROKERS__FAILURE',
|
GET_BROKERS__FAILURE = 'GET_BROKERS__FAILURE',
|
||||||
|
|
|
@ -1,18 +1,32 @@
|
||||||
import { createAsyncAction } from 'typesafe-actions';
|
import { createAsyncAction } from 'typesafe-actions';
|
||||||
import ActionType from 'redux/actionType';
|
import ActionType from 'redux/actionType';
|
||||||
|
import { TopicName, ConsumerGroupID } from 'redux/interfaces';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Cluster,
|
||||||
|
ClusterStats,
|
||||||
|
ClusterMetrics,
|
||||||
Broker,
|
Broker,
|
||||||
BrokerMetrics,
|
BrokerMetrics,
|
||||||
Cluster,
|
|
||||||
Topic,
|
Topic,
|
||||||
TopicConfig,
|
|
||||||
TopicDetails,
|
TopicDetails,
|
||||||
|
TopicConfig,
|
||||||
TopicMessage,
|
TopicMessage,
|
||||||
TopicName,
|
|
||||||
ConsumerGroup,
|
ConsumerGroup,
|
||||||
ConsumerGroupDetails,
|
ConsumerGroupDetails,
|
||||||
ConsumerGroupID,
|
} from 'generated-sources';
|
||||||
} from 'redux/interfaces';
|
|
||||||
|
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(
|
export const fetchBrokersAction = createAsyncAction(
|
||||||
ActionType.GET_BROKERS__REQUEST,
|
ActionType.GET_BROKERS__REQUEST,
|
||||||
|
@ -32,7 +46,7 @@ export const fetchClusterListAction = createAsyncAction(
|
||||||
ActionType.GET_CLUSTERS__FAILURE
|
ActionType.GET_CLUSTERS__FAILURE
|
||||||
)<undefined, Cluster[], undefined>();
|
)<undefined, Cluster[], undefined>();
|
||||||
|
|
||||||
export const fetchTopicListAction = createAsyncAction(
|
export const fetchTopicsListAction = createAsyncAction(
|
||||||
ActionType.GET_TOPICS__REQUEST,
|
ActionType.GET_TOPICS__REQUEST,
|
||||||
ActionType.GET_TOPICS__SUCCESS,
|
ActionType.GET_TOPICS__SUCCESS,
|
||||||
ActionType.GET_TOPICS__FAILURE
|
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 {
|
import {
|
||||||
ConsumerGroupID,
|
ConsumerGroupID,
|
||||||
PromiseThunk,
|
PromiseThunk,
|
||||||
Cluster,
|
|
||||||
ClusterName,
|
ClusterName,
|
||||||
TopicFormData,
|
BrokerId,
|
||||||
TopicName,
|
TopicName,
|
||||||
Topic,
|
|
||||||
TopicMessageQueryParams,
|
TopicMessageQueryParams,
|
||||||
|
TopicFormFormattedParams,
|
||||||
|
TopicFormDataRaw,
|
||||||
} from 'redux/interfaces';
|
} from 'redux/interfaces';
|
||||||
|
|
||||||
|
import { BASE_PARAMS } from 'lib/constants';
|
||||||
import * as actions from './actions';
|
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 = (
|
export const fetchBrokers = (
|
||||||
clusterName: ClusterName
|
clusterName: ClusterName
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchBrokersAction.request());
|
dispatch(actions.fetchBrokersAction.request());
|
||||||
try {
|
try {
|
||||||
const payload = await api.getBrokers(clusterName);
|
const payload = await apiClient.getBrokers({ clusterName });
|
||||||
dispatch(actions.fetchBrokersAction.success(payload));
|
dispatch(actions.fetchBrokersAction.success(payload));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchBrokersAction.failure());
|
dispatch(actions.fetchBrokersAction.failure());
|
||||||
|
@ -25,36 +70,30 @@ export const fetchBrokers = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchBrokerMetrics = (
|
export const fetchBrokerMetrics = (
|
||||||
clusterName: ClusterName
|
clusterName: ClusterName,
|
||||||
|
brokerId: BrokerId
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchBrokerMetricsAction.request());
|
dispatch(actions.fetchBrokerMetricsAction.request());
|
||||||
try {
|
try {
|
||||||
const payload = await api.getBrokerMetrics(clusterName);
|
const payload = await apiClient.getBrokersMetrics({
|
||||||
|
clusterName,
|
||||||
|
id: brokerId,
|
||||||
|
});
|
||||||
dispatch(actions.fetchBrokerMetricsAction.success(payload));
|
dispatch(actions.fetchBrokerMetricsAction.success(payload));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchBrokerMetricsAction.failure());
|
dispatch(actions.fetchBrokerMetricsAction.failure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
|
export const fetchTopicsList = (
|
||||||
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 = (
|
|
||||||
clusterName: ClusterName
|
clusterName: ClusterName
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchTopicListAction.request());
|
dispatch(actions.fetchTopicsListAction.request());
|
||||||
try {
|
try {
|
||||||
const topics = await api.getTopics(clusterName);
|
const topics = await apiClient.getTopics({ clusterName });
|
||||||
dispatch(actions.fetchTopicListAction.success(topics));
|
dispatch(actions.fetchTopicsListAction.success(topics));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchTopicListAction.failure());
|
dispatch(actions.fetchTopicsListAction.failure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,11 +104,11 @@ export const fetchTopicMessages = (
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchTopicMessagesAction.request());
|
dispatch(actions.fetchTopicMessagesAction.request());
|
||||||
try {
|
try {
|
||||||
const messages = await api.getTopicMessages(
|
const messages = await apiClient.getTopicMessages({
|
||||||
clusterName,
|
clusterName,
|
||||||
topicName,
|
topicName,
|
||||||
queryParams
|
...queryParams,
|
||||||
);
|
});
|
||||||
dispatch(actions.fetchTopicMessagesAction.success(messages));
|
dispatch(actions.fetchTopicMessagesAction.success(messages));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchTopicMessagesAction.failure());
|
dispatch(actions.fetchTopicMessagesAction.failure());
|
||||||
|
@ -82,7 +121,10 @@ export const fetchTopicDetails = (
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchTopicDetailsAction.request());
|
dispatch(actions.fetchTopicDetailsAction.request());
|
||||||
try {
|
try {
|
||||||
const topicDetails = await api.getTopicDetails(clusterName, topicName);
|
const topicDetails = await apiClient.getTopicDetails({
|
||||||
|
clusterName,
|
||||||
|
topicName,
|
||||||
|
});
|
||||||
dispatch(
|
dispatch(
|
||||||
actions.fetchTopicDetailsAction.success({
|
actions.fetchTopicDetailsAction.success({
|
||||||
topicName,
|
topicName,
|
||||||
|
@ -100,20 +142,59 @@ export const fetchTopicConfig = (
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchTopicConfigAction.request());
|
dispatch(actions.fetchTopicConfigAction.request());
|
||||||
try {
|
try {
|
||||||
const config = await api.getTopicConfig(clusterName, topicName);
|
const config = await apiClient.getTopicConfigs({ clusterName, topicName });
|
||||||
dispatch(actions.fetchTopicConfigAction.success({ topicName, config }));
|
dispatch(actions.fetchTopicConfigAction.success({ topicName, config }));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchTopicConfigAction.failure());
|
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 = (
|
export const createTopic = (
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
form: TopicFormData
|
form: TopicFormDataRaw
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.createTopicAction.request());
|
dispatch(actions.createTopicAction.request());
|
||||||
try {
|
try {
|
||||||
const topic: Topic = await api.postTopic(clusterName, form);
|
const topic: Topic = await apiClient.createTopic({
|
||||||
|
clusterName,
|
||||||
|
topicFormData: formatTopicFormData(form),
|
||||||
|
});
|
||||||
dispatch(actions.createTopicAction.success(topic));
|
dispatch(actions.createTopicAction.success(topic));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.createTopicAction.failure());
|
dispatch(actions.createTopicAction.failure());
|
||||||
|
@ -122,11 +203,15 @@ export const createTopic = (
|
||||||
|
|
||||||
export const updateTopic = (
|
export const updateTopic = (
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
form: TopicFormData
|
form: TopicFormDataRaw
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.updateTopicAction.request());
|
dispatch(actions.updateTopicAction.request());
|
||||||
try {
|
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));
|
dispatch(actions.updateTopicAction.success(topic));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.updateTopicAction.failure());
|
dispatch(actions.updateTopicAction.failure());
|
||||||
|
@ -138,7 +223,7 @@ export const fetchConsumerGroupsList = (
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchConsumerGroupsAction.request());
|
dispatch(actions.fetchConsumerGroupsAction.request());
|
||||||
try {
|
try {
|
||||||
const consumerGroups = await api.getConsumerGroups(clusterName);
|
const consumerGroups = await apiClient.getConsumerGroups({ clusterName });
|
||||||
dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups));
|
dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchConsumerGroupsAction.failure());
|
dispatch(actions.fetchConsumerGroupsAction.failure());
|
||||||
|
@ -151,10 +236,10 @@ export const fetchConsumerGroupDetails = (
|
||||||
): PromiseThunk<void> => async (dispatch) => {
|
): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(actions.fetchConsumerGroupDetailsAction.request());
|
dispatch(actions.fetchConsumerGroupDetailsAction.request());
|
||||||
try {
|
try {
|
||||||
const consumerGroupDetails = await api.getConsumerGroupDetails(
|
const consumerGroupDetails = await apiClient.getConsumerGroup({
|
||||||
clusterName,
|
clusterName,
|
||||||
consumerGroupID
|
id: consumerGroupID,
|
||||||
);
|
});
|
||||||
dispatch(
|
dispatch(
|
||||||
actions.fetchConsumerGroupDetailsAction.success({
|
actions.fetchConsumerGroupDetailsAction.success({
|
||||||
consumerGroupID,
|
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 {
|
export type BrokerId = Broker['id'];
|
||||||
brokerId: BrokerId;
|
|
||||||
bytesInPerSec: number;
|
|
||||||
segmentSize: number;
|
|
||||||
partitionReplicas: number;
|
|
||||||
bytesOutPerSec: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum ZooKeeperStatus { offline, online };
|
export enum ZooKeeperStatus { offline, online };
|
||||||
|
|
||||||
export interface BrokerDiskUsage {
|
export interface BrokersState extends ClusterStats {
|
||||||
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 {
|
|
||||||
items: Broker[];
|
items: Broker[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,5 @@
|
||||||
export enum ClusterStatus {
|
import { Cluster } from 'generated-sources';
|
||||||
Online = 'online',
|
|
||||||
Offline = 'offline',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ClusterName = string;
|
export type ClusterName = Cluster['name'];
|
||||||
|
|
||||||
export interface Cluster {
|
export type ClusterState = Cluster[];
|
||||||
id: string;
|
|
||||||
name: ClusterName;
|
|
||||||
defaultCluster: boolean;
|
|
||||||
status: ClusterStatus;
|
|
||||||
brokerCount: number;
|
|
||||||
onlinePartitionCount: number;
|
|
||||||
topicCount: number;
|
|
||||||
bytesInPerSec: number;
|
|
||||||
bytesOutPerSec: number;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,27 +1,6 @@
|
||||||
export type ConsumerGroupID = string;
|
import { ConsumerGroup, ConsumerGroupDetails } from 'generated-sources';
|
||||||
|
|
||||||
export interface ConsumerGroup {
|
export type ConsumerGroupID = ConsumerGroup['consumerGroupId'];
|
||||||
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 interface ConsumerGroupDetailedInfo
|
export interface ConsumerGroupDetailedInfo
|
||||||
extends ConsumerGroup,
|
extends ConsumerGroup,
|
||||||
|
@ -29,5 +8,5 @@ export interface ConsumerGroupDetailedInfo
|
||||||
|
|
||||||
export interface ConsumerGroupsState {
|
export interface ConsumerGroupsState {
|
||||||
byID: { [consumerGroupID: string]: ConsumerGroupDetailedInfo };
|
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 * as actions from 'redux/actions/actions';
|
||||||
|
|
||||||
import { TopicsState } from './topic';
|
import { TopicsState } from './topic';
|
||||||
import { Cluster } from './cluster';
|
import { ClusterState } from './cluster';
|
||||||
import { BrokersState } from './broker';
|
import { BrokersState } from './broker';
|
||||||
import { LoaderState } from './loader';
|
import { LoaderState } from './loader';
|
||||||
import { ConsumerGroupsState } from './consumerGroup';
|
import { ConsumerGroupsState } from './consumerGroup';
|
||||||
|
@ -25,7 +25,7 @@ export enum FetchStatus {
|
||||||
|
|
||||||
export interface RootState {
|
export interface RootState {
|
||||||
topics: TopicsState;
|
topics: TopicsState;
|
||||||
clusters: Cluster[];
|
clusters: ClusterState;
|
||||||
brokers: BrokersState;
|
brokers: BrokersState;
|
||||||
consumerGroups: ConsumerGroupsState;
|
consumerGroups: ConsumerGroupsState;
|
||||||
loader: LoaderState;
|
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 {
|
export enum CleanupPolicy {
|
||||||
Delete = 'delete',
|
Delete = 'delete',
|
||||||
Compact = 'compact',
|
Compact = 'compact',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicConfig {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
defaultValue: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TopicConfigByName {
|
export interface TopicConfigByName {
|
||||||
byName: {
|
byName: TopicConfigParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopicConfigParams {
|
||||||
[paramName: string]: TopicConfig;
|
[paramName: string]: TopicConfig;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicReplica {
|
export interface TopicConfigOption {
|
||||||
broker: number;
|
name: TopicConfig['name'];
|
||||||
leader: boolean;
|
defaultValue: TopicConfig['defaultValue'];
|
||||||
inSync: true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicPartition {
|
export interface TopicConfigValue {
|
||||||
partition: number;
|
name: TopicConfig['name'];
|
||||||
leader: number;
|
value: TopicConfig['value'];
|
||||||
offsetMin: number;
|
|
||||||
offsetMax: number;
|
|
||||||
replicas: TopicReplica[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
export interface TopicMessageQueryParams {
|
||||||
q: string;
|
q: GetTopicMessagesRequest['q'];
|
||||||
limit: number;
|
limit: GetTopicMessagesRequest['limit'];
|
||||||
seekType: SeekType;
|
seekType: GetTopicMessagesRequest['seekType'];
|
||||||
seekTo: string[];
|
seekTo: GetTopicMessagesRequest['seekTo'];
|
||||||
}
|
|
||||||
|
|
||||||
export interface TopicFormCustomParam {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicFormCustomParams {
|
export interface TopicFormCustomParams {
|
||||||
byIndex: { [paramIndex: string]: TopicFormCustomParam };
|
byIndex: TopicConfigParams;
|
||||||
allIndexes: string[];
|
allIndexes: TopicName[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicWithDetailedInfo extends Topic, TopicDetails {
|
export interface TopicWithDetailedInfo extends Topic, TopicDetails {
|
||||||
|
@ -97,11 +54,9 @@ export interface TopicsState {
|
||||||
messages: TopicMessage[];
|
messages: TopicMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicFormFormattedParams {
|
export type TopicFormFormattedParams = TopicFormData['configs'];
|
||||||
[name: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TopicFormData {
|
export interface TopicFormDataRaw {
|
||||||
name: string;
|
name: string;
|
||||||
partitions: number;
|
partitions: number;
|
||||||
replicationFactor: number;
|
replicationFactor: number;
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import {
|
import { Action, BrokersState, ZooKeeperStatus } from 'redux/interfaces';
|
||||||
Action,
|
import { ClusterStats } from 'generated-sources';
|
||||||
BrokersState,
|
|
||||||
ZooKeeperStatus,
|
|
||||||
BrokerMetrics,
|
|
||||||
} from 'redux/interfaces';
|
|
||||||
import ActionType from 'redux/actionType';
|
import ActionType from 'redux/actionType';
|
||||||
|
|
||||||
export const initialState: BrokersState = {
|
export const initialState: BrokersState = {
|
||||||
|
@ -21,15 +17,14 @@ export const initialState: BrokersState = {
|
||||||
|
|
||||||
const updateBrokerSegmentSize = (
|
const updateBrokerSegmentSize = (
|
||||||
state: BrokersState,
|
state: BrokersState,
|
||||||
payload: BrokerMetrics
|
payload: ClusterStats
|
||||||
) => {
|
) => {
|
||||||
const brokers = state.items;
|
const brokers = state.items;
|
||||||
const { diskUsage } = payload;
|
const { diskUsage } = payload;
|
||||||
|
|
||||||
const items = brokers.map((broker) => {
|
const items = brokers.map((broker) => {
|
||||||
const brokerMetrics = diskUsage.find(
|
const brokerMetrics =
|
||||||
({ brokerId }) => brokerId === broker.brokerId
|
diskUsage && diskUsage.find(({ brokerId }) => brokerId === broker.id);
|
||||||
);
|
|
||||||
if (brokerMetrics !== undefined) {
|
if (brokerMetrics !== undefined) {
|
||||||
return { ...broker, ...brokerMetrics };
|
return { ...broker, ...brokerMetrics };
|
||||||
}
|
}
|
||||||
|
@ -48,7 +43,7 @@ const reducer = (state = initialState, action: Action): BrokersState => {
|
||||||
...state,
|
...state,
|
||||||
items: action.payload,
|
items: action.payload,
|
||||||
};
|
};
|
||||||
case ActionType.GET_BROKER_METRICS__SUCCESS:
|
case ActionType.GET_CLUSTER_STATS__SUCCESS:
|
||||||
return updateBrokerSegmentSize(state, action.payload);
|
return updateBrokerSegmentSize(state, action.payload);
|
||||||
default:
|
default:
|
||||||
return state;
|
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';
|
import ActionType from 'redux/actionType';
|
||||||
|
|
||||||
export const initialState: Cluster[] = [];
|
export const initialState: Cluster[] = [];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createSelector } from 'reselect';
|
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 { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
||||||
|
import { Cluster, ServerStatus } from 'generated-sources';
|
||||||
|
|
||||||
const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
|
const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
|
||||||
|
|
||||||
|
@ -16,13 +17,13 @@ export const getClusterList = createSelector(clustersState, (clusters) => cluste
|
||||||
export const getOnlineClusters = createSelector(
|
export const getOnlineClusters = createSelector(
|
||||||
getClusterList,
|
getClusterList,
|
||||||
(clusters) => clusters.filter(
|
(clusters) => clusters.filter(
|
||||||
({ status }) => status === ClusterStatus.Online,
|
({ status }) => status === ServerStatus.Online,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getOfflineClusters = createSelector(
|
export const getOfflineClusters = createSelector(
|
||||||
getClusterList,
|
getClusterList,
|
||||||
(clusters) => clusters.filter(
|
(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';
|
import ActionType from 'redux/actionType';
|
||||||
|
|
||||||
export const initialState: ConsumerGroupsState = {
|
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';
|
import ActionType from 'redux/actionType';
|
||||||
|
|
||||||
export const initialState: TopicsState = {
|
export const initialState: TopicsState = {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
TopicConfigByName,
|
TopicConfigByName,
|
||||||
} from 'redux/interfaces';
|
} from 'redux/interfaces';
|
||||||
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
||||||
|
import { Partition } from 'generated-sources';
|
||||||
|
|
||||||
const topicsState = ({ topics }: RootState): TopicsState => topics;
|
const topicsState = ({ topics }: RootState): TopicsState => topics;
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ export const getTopicByName = createSelector(
|
||||||
export const getPartitionsByTopicName = createSelector(
|
export const getPartitionsByTopicName = createSelector(
|
||||||
getTopicMap,
|
getTopicMap,
|
||||||
getTopicName,
|
getTopicName,
|
||||||
(topics, topicName) => topics[topicName].partitions
|
(topics, topicName) => (topics[topicName].partitions) as Partition[]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getFullTopic = createSelector(getTopicByName, (topic) =>
|
export const getFullTopic = createSelector(getTopicByName, (topic) =>
|
||||||
|
|
Loading…
Add table
Reference in a new issue