use cluster name instead of cluster id

This commit is contained in:
Zhenya Taran 2020-03-01 16:07:33 +02:00
parent 01c2c8b20b
commit 8b64575bd5
34 changed files with 224 additions and 234 deletions

View file

@ -16,11 +16,8 @@ public class ClustersProperties {
@Data
public static class Cluster {
String id;
String name;
String bootstrapServers;
String jmxHost;
String jmxPort;
String zookeeper;
}
}

View file

@ -7,14 +7,13 @@ import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
@Component
@RequiredArgsConstructor
public class ClustersStorage {
private final List<KafkaCluster> kafkaClusters = new ArrayList<>();
private final Map<String, KafkaCluster> kafkaClusters = new HashMap<>();
private final ClustersProperties clusterProperties;
@ -23,18 +22,18 @@ public class ClustersStorage {
@PostConstruct
public void init() {
for (ClustersProperties.Cluster clusterProperties : clusterProperties.getClusters()) {
kafkaClusters.add(clusterMapper.toKafkaCluster(clusterProperties));
if (kafkaClusters.get(clusterProperties.getName()) != null) {
throw new IllegalStateException("Application config isn't correct. Two clusters can't have the same name");
}
kafkaClusters.put(clusterProperties.getName(), clusterMapper.toKafkaCluster(clusterProperties));
}
}
public List<KafkaCluster> getKafkaClusters() {
return kafkaClusters;
public Collection<KafkaCluster> getKafkaClusters() {
return kafkaClusters.values();
}
public KafkaCluster getClusterById(String clusterId) {
return kafkaClusters.stream()
.filter(cluster -> cluster.getId() != null && cluster.getId().equals(clusterId))
.findFirst()
.orElse(null);
public KafkaCluster getClusterByName(String clusterName) {
return kafkaClusters.get(clusterName);
}
}

View file

@ -29,32 +29,32 @@ public class ClusterService {
return Mono.just(ResponseEntity.ok(Flux.fromIterable(clusters)));
}
public Mono<ResponseEntity<BrokersMetrics>> getBrokersMetrics(String clusterId) {
KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
public Mono<ResponseEntity<BrokersMetrics>> getBrokersMetrics(String name) {
KafkaCluster cluster = clustersStorage.getClusterByName(name);
if (cluster == null) return null;
return Mono.just(ResponseEntity.ok(cluster.getBrokersMetrics()));
}
public Mono<ResponseEntity<Flux<Topic>>> getTopics(String clusterId) {
KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
public Mono<ResponseEntity<Flux<Topic>>> getTopics(String name) {
KafkaCluster cluster = clustersStorage.getClusterByName(name);
if (cluster == null) return null;
return Mono.just(ResponseEntity.ok(Flux.fromIterable(cluster.getTopics())));
}
public Mono<ResponseEntity<TopicDetails>> getTopicDetails(String clusterId, String topicName) {
KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
public Mono<ResponseEntity<TopicDetails>> getTopicDetails(String name, String topicName) {
KafkaCluster cluster = clustersStorage.getClusterByName(name);
if (cluster == null) return null;
return Mono.just(ResponseEntity.ok(cluster.getOrCreateTopicDetails(topicName)));
}
public Mono<ResponseEntity<Flux<TopicConfig>>> getTopicConfigs(String clusterId, String topicName) {
KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
public Mono<ResponseEntity<Flux<TopicConfig>>> getTopicConfigs(String name, String topicName) {
KafkaCluster cluster = clustersStorage.getClusterByName(name);
if (cluster == null) return null;
return Mono.just(ResponseEntity.ok(Flux.fromIterable(cluster.getTopicConfigsMap().get(topicName))));
}
public Mono<ResponseEntity<Topic>> createTopic(String clusterId, Mono<TopicFormData> topicFormData) {
KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
public Mono<ResponseEntity<Topic>> createTopic(String name, Mono<TopicFormData> topicFormData) {
KafkaCluster cluster = clustersStorage.getClusterByName(name);
if (cluster == null) return null;
return kafkaService.createTopic(cluster, topicFormData);
}

View file

@ -139,7 +139,7 @@ public class KafkaService {
}
private Topic collectTopicData(KafkaCluster kafkaCluster, TopicDescription topicDescription) {
var topic = new Topic().clusterId(kafkaCluster.getId());
var topic = new Topic();
topic.setInternal(topicDescription.isInternal());
topic.setName(topicDescription.name());

View file

@ -30,14 +30,14 @@ paths:
items:
$ref: '#/components/schemas/Cluster'
/api/clusters/{clusterId}/brokers:
/api/clusters/{clusterName}/brokers:
get:
tags:
- /api/clusters
summary: getBrokers
operationId: getBrokers
parameters:
- name: clusterId
- name: clusterName
in: path
required: true
schema:
@ -52,14 +52,14 @@ paths:
items:
$ref: '#/components/schemas/Broker'
/api/clusters/{clusterId}/metrics/broker:
/api/clusters/{clusterName}/metrics/broker:
get:
tags:
- /api/clusters
summary: getBrokersMetrics
operationId: getBrokersMetrics
parameters:
- name: clusterId
- name: clusterName
in: path
required: true
schema:
@ -72,14 +72,14 @@ paths:
schema:
$ref: '#/components/schemas/BrokersMetrics'
/api/clusters/{clusterId}/topics:
/api/clusters/{clusterName}/topics:
get:
tags:
- /api/clusters
summary: getTopics
operationId: getTopics
parameters:
- name: clusterId
- name: clusterName
in: path
required: true
schema:
@ -99,7 +99,7 @@ paths:
summary: createTopic
operationId: createTopic
parameters:
- name: clusterId
- name: clusterName
in: path
required: true
schema:
@ -117,14 +117,14 @@ paths:
schema:
$ref: '#/components/schemas/Topic'
/api/clusters/{clusterId}/topics/{topicName}:
/api/clusters/{clusterName}/topics/{topicName}:
get:
tags:
- /api/clusters
summary: getTopicDetails
operationId: getTopicDetails
parameters:
- name: clusterId
- name: clusterName
in: path
required: true
schema:
@ -142,14 +142,14 @@ paths:
schema:
$ref: '#/components/schemas/TopicDetails'
/api/clusters/{clusterId}/topics/{topicName}/config:
/api/clusters/{clusterName}/topics/{topicName}/config:
get:
tags:
- /api/clusters
summary: getTopicConfigs
operationId: getTopicConfigs
parameters:
- name: clusterId
- name: clusterName
in: path
required: true
schema:
@ -228,8 +228,6 @@ components:
Topic:
type: object
properties:
clusterId:
type: string
name:
type: string
internal:

View file

@ -1,5 +1,4 @@
const jsonServer = require('json-server');
const _ = require('lodash');
const clusters = require('./payload/clusters.json');
const brokers = require('./payload/brokers.json');
const brokerMetrics = require('./payload/brokerMetrics.json');
@ -10,11 +9,11 @@ const topicConfigs = require('./payload/topicConfigs.json');
const db = {
clusters,
brokers,
brokerMetrics: brokerMetrics.map(({ clusterId, ...rest }) => ({ ...rest, id: clusterId })),
brokerMetrics: brokerMetrics.map(({clusterName, ...rest}) => ({...rest, id: clusterName})),
topics: topics.map((topic) => ({...topic, id: topic.name})),
topicDetails,
topicConfigs,
}
};
const server = jsonServer.create();
const router = jsonServer.router(db);
const middlewares = jsonServer.defaults();
@ -29,11 +28,10 @@ server.use((_req, _res, next) => {
server.use(
jsonServer.rewriter({
'/*': '/$1',
'/clusters/:clusterId/metrics/broker': '/brokerMetrics/:clusterId',
'/clusters/:clusterId/topics/:id': '/topicDetails',
'/clusters/:clusterId/topics/:id/config': '/topicDetails',
'/clusters/:clusterId/topics/:id/config': '/topicConfigs',
'/api/*': '/$1',
'/clusters/:clusterName/metrics/broker': '/brokerMetrics/:clusterName',
'/clusters/:clusterName/topics/:id': '/topicDetails',
'/clusters/:clusterName/topics/:id/config': '/topicConfigs',
})
);

View file

@ -1,6 +1,6 @@
[
{
"clusterId": "wrYGf-csNgiGdK7B_ADF7Z",
"clusterName": "fake.cluster",
"bytesInPerSec": 8027,
"brokerCount": 1,
"zooKeeperStatus": 1,
@ -20,7 +20,7 @@
"diskUsageDistribution": "even"
},
{
"clusterId": "dMMQx-WRh77BKYas_g2ZTz",
"clusterName": "kafka-ui.cluster",
"bytesInPerSec": 8194,
"brokerCount": 1,
"zooKeeperStatus": 1,

View file

@ -1,7 +1,7 @@
[
{
"brokerId": 1,
"clusterId": "wrYGf-csNgiGdK7B_ADF7Z",
"name": "fake.cluster",
"bytesInPerSec": 1234,
"bytesOutPerSec": 3567,
"segmentSize": 912360707,
@ -9,7 +9,7 @@
},
{
"brokerId": 2,
"clusterId": "dMMQx-WRh77BKYas_g2ZTz",
"name": "kafka-ui.cluster",
"bytesInPerSec": 9194,
"bytesOutPerSec": 7924,
"segmentSize": 840060707,

View file

@ -1,6 +1,6 @@
[
{
"id": "wrYGf-csNgiGdK7B_ADF7Z",
"id": "fake.cluster",
"name": "fake.cluster",
"defaultCluster": true,
"status": "online",
@ -11,7 +11,7 @@
"bytesOutPerSec": 4320
},
{
"id": "dMMQx-WRh77BKYas_g2ZTz",
"id": "kafka-ui.cluster",
"name": "kafka-ui.cluster",
"defaultCluster": false,
"status": "offline",

View file

@ -1,6 +1,6 @@
[
{
"clusterId": "wrYGf-csNgiGdK7B_ADF7Z",
"clusterId": "fake.cluster",
"name": "docker-connect-status",
"internal": true,
"partitions": [
@ -62,7 +62,7 @@
]
},
{
"clusterId": "wrYGf-csNgiGdK7B_ADF7Z",
"clusterId": "fake.cluster",
"name": "dsadsda",
"internal": false,
"partitions": [
@ -80,7 +80,7 @@
]
},
{
"clusterId": "wrYGf-csNgiGdK7B_ADF7Z",
"clusterId": "fake.cluster",
"name": "my-topic",
"internal": false,
"partitions": [
@ -98,7 +98,7 @@
]
},
{
"clusterId": "wrYGf-csNgiGdK7B_ADF7Z",
"clusterId": "fake.cluster",
"name": "docker-connect-offsets",
"internal": false,
"partitions": [
@ -171,7 +171,7 @@
]
},
{
"clusterId": "dMMQx-WRh77BKYas_g2ZTz",
"clusterId": "kafka-ui.cluster",
"name": "_schemas",
"internal": false,
"partitions": [
@ -189,7 +189,7 @@
]
},
{
"clusterId": "dMMQx-WRh77BKYas_g2ZTz",
"clusterId": "kafka-ui.cluster",
"name": "docker-connect-configs",
"internal": false,
"partitions": [
@ -207,7 +207,7 @@
]
},
{
"clusterId": "dMMQx-WRh77BKYas_g2ZTz",
"clusterId": "kafka-ui.cluster",
"name": "_kafka-ui-test-topic-monitoring-message-rekey-store",
"internal": false,
"partitions": [

View file

@ -37,9 +37,9 @@ const App: React.FC<AppProps> = ({
<Switch>
<Route exact path="/" component={Dashboard} />
<Route exact path="/clusters" component={Dashboard} />
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
<Route path="/clusters/:clusterId/brokers" component={BrokersContainer} />
<Redirect from="/clusters/:clusterId" to="/clusters/:clusterId/brokers" />
<Route path="/clusters/:clusterName/topics" component={TopicsContainer} />
<Route path="/clusters/:clusterName/brokers" component={BrokersContainer} />
<Redirect from="/clusters/:clusterName" to="/clusters/:clusterName/brokers" />
</Switch>
) : (
<PageLoader />
@ -48,6 +48,6 @@ const App: React.FC<AppProps> = ({
</main>
</div>
);
}
};
export default App;

View file

@ -1,5 +1,5 @@
import React from 'react';
import { ClusterId, BrokerMetrics, ZooKeeperStatus } from 'redux/interfaces';
import { ClusterName, BrokerMetrics, ZooKeeperStatus } from 'redux/interfaces';
import useInterval from 'lib/hooks/useInterval';
import formatBytes from 'lib/utils/formatBytes';
import cx from 'classnames';
@ -8,16 +8,16 @@ import Indicator from 'components/common/Dashboard/Indicator';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
interface Props extends BrokerMetrics {
clusterId: string;
clusterName: ClusterName;
isFetched: boolean;
minDiskUsage: number;
maxDiskUsage: number;
fetchBrokers: (clusterId: ClusterId) => void;
fetchBrokerMetrics: (clusterId: ClusterId) => void;
fetchBrokers: (clusterName: ClusterName) => void;
fetchBrokerMetrics: (clusterName: ClusterName) => void;
}
const Topics: React.FC<Props> = ({
clusterId,
clusterName,
isFetched,
brokerCount,
activeControllers,
@ -35,13 +35,13 @@ const Topics: React.FC<Props> = ({
}) => {
React.useEffect(
() => {
fetchBrokers(clusterId);
fetchBrokerMetrics(clusterId);
fetchBrokers(clusterName);
fetchBrokerMetrics(clusterName);
},
[fetchBrokers, fetchBrokerMetrics, clusterId],
[fetchBrokers, fetchBrokerMetrics, clusterName],
);
useInterval(() => { fetchBrokerMetrics(clusterId); }, 5000);
useInterval(() => { fetchBrokerMetrics(clusterName); }, 5000);
const [minDiskUsageValue, minDiskUsageSize] = formatBytes(minDiskUsage);
const [maxDiskUsageValue, maxDiskUsageSize] = formatBytes(maxDiskUsage);
@ -116,6 +116,6 @@ const Topics: React.FC<Props> = ({
</MetricsWrapper>
</div>
);
}
};
export default Topics;

View file

@ -5,18 +5,18 @@ import {
} from 'redux/actions';
import Brokers from './Brokers';
import * as brokerSelectors from 'redux/reducers/brokers/selectors';
import { RootState, ClusterId } from 'redux/interfaces';
import { RootState, ClusterName } from 'redux/interfaces';
import { RouteComponentProps } from 'react-router-dom';
interface RouteProps {
clusterId: string;
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { clusterId } }}: OwnProps) => ({
const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}: OwnProps) => ({
isFetched: brokerSelectors.getIsBrokerListFetched(state),
clusterId,
clusterName,
brokerCount: brokerSelectors.getBrokerCount(state),
zooKeeperStatus: brokerSelectors.getZooKeeperStatus(state),
activeControllers: brokerSelectors.getActiveControllers(state),
@ -31,8 +31,8 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterId } }}:
});
const mapDispatchToProps = {
fetchBrokers: (clusterId: ClusterId) => fetchBrokers(clusterId),
fetchBrokerMetrics: (clusterId: ClusterId) => fetchBrokerMetrics(clusterId),
}
fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
fetchBrokerMetrics: (clusterName: ClusterName) => fetchBrokerMetrics(clusterName),
};
export default connect(mapStateToProps, mapDispatchToProps)(Brokers);

View file

@ -5,7 +5,6 @@ import { NavLink } from 'react-router-dom';
import { clusterBrokersPath } from 'lib/paths';
const ClusterWidget: React.FC<Cluster> = ({
id,
name,
status,
topicCount,
@ -14,7 +13,7 @@ const ClusterWidget: React.FC<Cluster> = ({
bytesOutPerSec,
onlinePartitionCount,
}) => (
<NavLink to={clusterBrokersPath(id)} className="column is-full-modile is-6">
<NavLink to={clusterBrokersPath(name)} className="column is-full-modile is-6">
<div className="box is-hoverable">
<div
className="title is-6 has-text-overflow-ellipsis"

View file

@ -21,21 +21,20 @@ const DefaultIcon: React.FC = () => {
};
const ClusterMenu: React.FC<Props> = ({
id,
name,
defaultCluster,
}) => (
<ul className="menu-list">
<li>
<NavLink exact to={clusterBrokersPath(id)} title={name} className="has-text-overflow-ellipsis">
<NavLink exact to={clusterBrokersPath(name)} title={name} className="has-text-overflow-ellipsis">
{defaultCluster && <DefaultIcon />}
{name}
</NavLink>
<ul>
<NavLink to={clusterBrokersPath(id)} activeClassName="is-active" title="Brokers">
<NavLink to={clusterBrokersPath(name)} activeClassName="is-active" title="Brokers">
Brokers
</NavLink>
<NavLink to={clusterTopicsPath(id)} activeClassName="is-active" title="Topics">
<NavLink to={clusterTopicsPath(name)} activeClassName="is-active" title="Topics">
Topics
</NavLink>
</ul>

View file

@ -1,5 +1,5 @@
import React from 'react';
import { ClusterId, Topic, TopicDetails, TopicName } from 'redux/interfaces';
import { ClusterName, Topic, TopicDetails, TopicName } from 'redux/interfaces';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import { NavLink, Switch, Route } from 'react-router-dom';
import { clusterTopicsPath, clusterTopicSettingsPath, clusterTopicPath, clusterTopicMessagesPath } from 'lib/paths';
@ -8,12 +8,12 @@ import MessagesContainer from './Messages/MessagesContainer';
import SettingsContainer from './Settings/SettingsContainer';
interface Props extends Topic, TopicDetails {
clusterId: ClusterId;
clusterName: ClusterName;
topicName: TopicName;
}
const Details: React.FC<Props> = ({
clusterId,
clusterName,
topicName,
}) => {
return (
@ -21,7 +21,7 @@ const Details: React.FC<Props> = ({
<div className="level">
<div className="level-item level-left">
<Breadcrumb links={[
{ href: clusterTopicsPath(clusterId), label: 'All Topics' },
{ href: clusterTopicsPath(clusterName), label: 'All Topics' },
]}>
{topicName}
</Breadcrumb>
@ -32,7 +32,7 @@ const Details: React.FC<Props> = ({
<nav className="navbar" role="navigation">
<NavLink
exact
to={clusterTopicPath(clusterId, topicName)}
to={clusterTopicPath(clusterName, topicName)}
className="navbar-item is-tab"
activeClassName="is-active is-primary"
>
@ -40,7 +40,7 @@ const Details: React.FC<Props> = ({
</NavLink>
<NavLink
exact
to={clusterTopicMessagesPath(clusterId, topicName)}
to={clusterTopicMessagesPath(clusterName, topicName)}
className="navbar-item is-tab"
activeClassName="is-active"
>
@ -48,7 +48,7 @@ const Details: React.FC<Props> = ({
</NavLink>
<NavLink
exact
to={clusterTopicSettingsPath(clusterId, topicName)}
to={clusterTopicSettingsPath(clusterName, topicName)}
className="navbar-item is-tab"
activeClassName="is-active"
>
@ -57,13 +57,13 @@ const Details: React.FC<Props> = ({
</nav>
<br />
<Switch>
<Route exact path="/clusters/:clusterId/topics/:topicName/messages" component={MessagesContainer} />
<Route exact path="/clusters/:clusterId/topics/:topicName/settings" component={SettingsContainer} />
<Route exact path="/clusters/:clusterId/topics/:topicName" component={OverviewContainer} />
<Route exact path="/clusters/:clusterName/topics/:topicName/messages" component={MessagesContainer} />
<Route exact path="/clusters/:clusterName/topics/:topicName/settings" component={SettingsContainer} />
<Route exact path="/clusters/:clusterName/topics/:topicName" component={OverviewContainer} />
</Switch>
</div>
</div>
);
}
};
export default Details;

View file

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

View file

@ -1,20 +1,20 @@
import React from 'react';
import { ClusterId, TopicName } from 'redux/interfaces';
import { ClusterName, TopicName } from 'redux/interfaces';
interface Props {
clusterId: ClusterId;
clusterName: ClusterName;
topicName: TopicName;
}
const Messages: React.FC<Props> = ({
clusterId,
clusterName,
topicName,
}) => {
return (
<h1>
Messages from {clusterId}{topicName}
Messages from {clusterName}{topicName}
</h1>
);
}
};
export default Messages;

View file

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

View file

@ -1,18 +1,18 @@
import React from 'react';
import { ClusterId, Topic, TopicDetails, TopicName } from 'redux/interfaces';
import { ClusterName, Topic, TopicDetails, TopicName } from 'redux/interfaces';
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
import Indicator from 'components/common/Dashboard/Indicator';
interface Props extends Topic, TopicDetails {
isFetched: boolean;
clusterId: ClusterId;
clusterName: ClusterName;
topicName: TopicName;
fetchTopicDetails: (clusterId: ClusterId, topicName: TopicName) => void;
fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => void;
}
const Overview: React.FC<Props> = ({
isFetched,
clusterId,
clusterName,
topicName,
partitions,
underReplicatedPartitions,
@ -24,8 +24,8 @@ const Overview: React.FC<Props> = ({
fetchTopicDetails,
}) => {
React.useEffect(
() => { fetchTopicDetails(clusterId, topicName); },
[fetchTopicDetails, clusterId, topicName],
() => { fetchTopicDetails(clusterName, topicName); },
[fetchTopicDetails, clusterName, topicName],
);
if (!isFetched) {
@ -74,6 +74,6 @@ const Overview: React.FC<Props> = ({
</div>
</>
);
}
};
export default Overview;

View file

@ -3,27 +3,27 @@ import {
fetchTopicDetails,
} from 'redux/actions';
import Overview from './Overview';
import { RootState, TopicName, ClusterId } from 'redux/interfaces';
import { RootState, TopicName, ClusterName } from 'redux/interfaces';
import { getTopicByName, getIsTopicDetailsFetched } from 'redux/reducers/topics/selectors';
import { withRouter, RouteComponentProps } from 'react-router-dom';
interface RouteProps {
clusterId: string;
topicName: string;
clusterName: ClusterName;
topicName: TopicName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterId } } }: OwnProps) => ({
clusterId,
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
clusterName,
topicName,
isFetched: getIsTopicDetailsFetched(state),
...getTopicByName(state, topicName),
});
const mapDispatchToProps = {
fetchTopicDetails: (clusterId: ClusterId, topicName: TopicName) => fetchTopicDetails(clusterId, topicName),
}
fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => fetchTopicDetails(clusterName, topicName),
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Overview)

View file

@ -1,12 +1,12 @@
import React from 'react';
import { ClusterId, TopicName, TopicConfig } from 'redux/interfaces';
import { ClusterName, TopicName, TopicConfig } from 'redux/interfaces';
interface Props {
clusterId: ClusterId;
clusterName: ClusterName;
topicName: TopicName;
config?: TopicConfig[];
isFetched: boolean;
fetchTopicConfig: (clusterId: ClusterId, topicName: TopicName) => void;
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => void;
}
const ConfigListItem: React.FC<TopicConfig> = ({
@ -32,22 +32,22 @@ const ConfigListItem: React.FC<TopicConfig> = ({
</td>
</tr>
)
}
};
const Sertings: React.FC<Props> = ({
clusterId,
clusterName,
topicName,
isFetched,
fetchTopicConfig,
config,
}) => {
React.useEffect(
() => { fetchTopicConfig(clusterId, topicName); },
[fetchTopicConfig, clusterId, topicName],
() => { fetchTopicConfig(clusterName, topicName); },
[fetchTopicConfig, clusterName, topicName],
);
if (!isFetched || !config) {
return (null);
return null;
}
return (
@ -66,6 +66,6 @@ const Sertings: React.FC<Props> = ({
</table>
</div>
);
}
};
export default Sertings;

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { RootState, ClusterId, TopicName } from 'redux/interfaces';
import { RootState, ClusterName, TopicName } from 'redux/interfaces';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import {
fetchTopicConfig,
@ -12,22 +12,22 @@ import {
interface RouteProps {
clusterId: string;
topicName: string;
clusterName: ClusterName;
topicName: TopicName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterId } } }: OwnProps) => ({
clusterId,
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
clusterName,
topicName,
config: getTopicConfig(state, topicName),
isFetched: getTopicConfigFetched(state),
});
const mapDispatchToProps = {
fetchTopicConfig: (clusterId: ClusterId, topicName: TopicName) => fetchTopicConfig(clusterId, topicName),
}
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => fetchTopicConfig(clusterName, topicName),
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Settings)

View file

@ -1,18 +1,18 @@
import React from 'react';
import { TopicWithDetailedInfo, ClusterId } from 'redux/interfaces';
import { TopicWithDetailedInfo, ClusterName } from 'redux/interfaces';
import ListItem from './ListItem';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import { NavLink } from 'react-router-dom';
import { clusterTopicNewPath } from 'lib/paths';
interface Props {
clusterId: ClusterId;
clusterName: ClusterName;
topics: (TopicWithDetailedInfo)[];
externalTopics: (TopicWithDetailedInfo)[];
}
const List: React.FC<Props> = ({
clusterId,
clusterName,
topics,
externalTopics,
}) => {
@ -46,7 +46,7 @@ const List: React.FC<Props> = ({
<div className="level-item level-right">
<NavLink
className="button is-primary"
to={clusterTopicNewPath(clusterId)}
to={clusterTopicNewPath(clusterName)}
>
Add a Topic
</NavLink>
@ -75,6 +75,6 @@ const List: React.FC<Props> = ({
</div>
</div>
);
}
};
export default List;

View file

@ -1,17 +1,17 @@
import { connect } from 'react-redux';
import { RootState } from 'redux/interfaces';
import {ClusterName, RootState} from 'redux/interfaces';
import { getTopicList, getExternalTopicList } from 'redux/reducers/topics/selectors';
import List from './List';
import { withRouter, RouteComponentProps } from 'react-router-dom';
interface RouteProps {
clusterId: string;
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { clusterId } } }: OwnProps) => ({
clusterId,
const mapStateToProps = (state: RootState, { match: { params: { clusterName } } }: OwnProps) => ({
clusterName,
topics: getTopicList(state),
externalTopics: getExternalTopicList(state),
});

View file

@ -1,5 +1,5 @@
import React from 'react';
import { ClusterId, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import { clusterTopicsPath } from 'lib/paths';
import { useForm, ErrorMessage } from 'react-hook-form';
@ -10,15 +10,15 @@ import {
} from 'lib/constants';
interface Props {
clusterId: ClusterId;
clusterName: ClusterName;
isTopicCreated: boolean;
createTopic: (clusterId: ClusterId, form: TopicFormData) => void;
redirectToTopicPath: (clusterId: ClusterId, topicName: TopicName) => void;
createTopic: (clusterName: ClusterName, form: TopicFormData) => void;
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => void;
resetUploadedState: () => void;
}
const New: React.FC<Props> = ({
clusterId,
clusterName,
isTopicCreated,
createTopic,
redirectToTopicPath,
@ -31,10 +31,10 @@ const New: React.FC<Props> = ({
() => {
if (isSubmitting && isTopicCreated) {
const {name} = getValues();
redirectToTopicPath(clusterId, name);
redirectToTopicPath(clusterName, name);
}
},
[isSubmitting, isTopicCreated, redirectToTopicPath, clusterId, getValues],
[isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, getValues],
);
const onSubmit = async (data: TopicFormData) => {
@ -43,7 +43,7 @@ const New: React.FC<Props> = ({
//going to object page on the second creation. Resetting loaded state is workaround, need to tweak loader logic
resetUploadedState();
setIsSubmitting(true);
createTopic(clusterId, data);
createTopic(clusterName, data);
};
return (
@ -51,7 +51,7 @@ const New: React.FC<Props> = ({
<div className="level">
<div className="level-item level-left">
<Breadcrumb links={[
{href: clusterTopicsPath(clusterId), label: 'All Topics'},
{href: clusterTopicsPath(clusterName), label: 'All Topics'},
]}>
New Topic
</Breadcrumb>

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { RootState, ClusterId, TopicFormData, TopicName, Action } from 'redux/interfaces';
import { RootState, ClusterName, TopicFormData, TopicName, Action } from 'redux/interfaces';
import New from './New';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { createTopic } from 'redux/actions';
@ -9,22 +9,22 @@ import { ThunkDispatch } from 'redux-thunk';
import * as actions from "../../../redux/actions/actions";
interface RouteProps {
clusterId: string;
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { clusterId } } }: OwnProps) => ({
clusterId,
const mapStateToProps = (state: RootState, { match: { params: { clusterName } } }: OwnProps) => ({
clusterName,
isTopicCreated: getTopicCreated(state),
});
const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, undefined, Action>, { history }: OwnProps) => ({
createTopic: (clusterId: ClusterId, form: TopicFormData) => {
dispatch(createTopic(clusterId, form));
createTopic: (clusterName: ClusterName, form: TopicFormData) => {
dispatch(createTopic(clusterName, form));
},
redirectToTopicPath: (clusterId: ClusterId, topicName: TopicName) => {
history.push(clusterTopicPath(clusterId, topicName));
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
history.push(clusterTopicPath(clusterName, topicName));
},
resetUploadedState: (() => dispatch(actions.createTopicAction.failure()))
});

View file

@ -1,5 +1,5 @@
import React from 'react';
import { ClusterId } from 'redux/interfaces';
import { ClusterName } from 'redux/interfaces';
import {
Switch,
Route,
@ -10,30 +10,30 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
import NewContainer from './New/NewContainer';
interface Props {
clusterId: string;
clusterName: ClusterName;
isFetched: boolean;
fetchBrokers: (clusterId: ClusterId) => void;
fetchTopicList: (clusterId: ClusterId) => void;
fetchBrokers: (clusterName: ClusterName) => void;
fetchTopicList: (clusterName: ClusterName) => void;
}
const Topics: React.FC<Props> = ({
clusterId,
clusterName,
isFetched,
fetchTopicList,
}) => {
React.useEffect(() => { fetchTopicList(clusterId); }, [fetchTopicList, clusterId]);
React.useEffect(() => { fetchTopicList(clusterName); }, [fetchTopicList, clusterName]);
if (isFetched) {
return (
<Switch>
<Route exact path="/clusters/:clusterId/topics" component={ListContainer} />
<Route exact path="/clusters/:clusterId/topics/new" component={NewContainer} />
<Route path="/clusters/:clusterId/topics/:topicName" component={DetailsContainer} />
<Route exact path="/clusters/:clusterName/topics" component={ListContainer} />
<Route exact path="/clusters/:clusterName/topics/new" component={NewContainer} />
<Route path="/clusters/:clusterName/topics/:topicName" component={DetailsContainer} />
</Switch>
);
}
return (<PageLoader />);
}
};
export default Topics;

View file

@ -2,22 +2,22 @@ import { connect } from 'react-redux';
import { fetchTopicList } from 'redux/actions';
import Topics from './Topics';
import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
import { RootState, ClusterId } from 'redux/interfaces';
import { RootState, ClusterName } from 'redux/interfaces';
import { RouteComponentProps } from 'react-router-dom';
interface RouteProps {
clusterId: string;
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { clusterId } }}: OwnProps) => ({
const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}: OwnProps) => ({
isFetched: getIsTopicListFetched(state),
clusterId,
clusterName,
});
const mapDispatchToProps = {
fetchTopicList: (clusterId: ClusterId) => fetchTopicList(clusterId),
}
fetchTopicList: (clusterName: ClusterName) => fetchTopicList(clusterName),
};
export default connect(mapStateToProps, mapDispatchToProps)(Topics);

View file

@ -1,15 +1,15 @@
import {
ClusterId,
ClusterName,
TopicName,
} from 'redux/interfaces';
const clusterPath = (clusterId: ClusterId) => `/clusters/${clusterId}`;
const clusterPath = (clusterName: ClusterName) => `/clusters/${clusterName}`;
export const clusterBrokersPath = (clusterId: ClusterId) => `${clusterPath(clusterId)}/brokers`;
export const clusterBrokersPath = (clusterName: ClusterName) => `${clusterPath(clusterName)}/brokers`;
export const clusterTopicsPath = (clusterId: ClusterId) => `${clusterPath(clusterId)}/topics`;
export const clusterTopicNewPath = (clusterId: ClusterId) => `${clusterPath(clusterId)}/topics/new`;
export const clusterTopicsPath = (clusterName: ClusterName) => `${clusterPath(clusterName)}/topics`;
export const clusterTopicNewPath = (clusterName: ClusterName) => `${clusterPath(clusterName)}/topics/new`;
export const clusterTopicPath = (clusterId: ClusterId, topicName: TopicName) => `${clusterTopicsPath(clusterId)}/${topicName}`;
export const clusterTopicSettingsPath = (clusterId: ClusterId, topicName: TopicName) => `${clusterTopicsPath(clusterId)}/${topicName}/settings`;
export const clusterTopicMessagesPath = (clusterId: ClusterId, topicName: TopicName) => `${clusterTopicsPath(clusterId)}/${topicName}/messages`;
export const clusterTopicPath = (clusterName: ClusterName, topicName: TopicName) => `${clusterTopicsPath(clusterName)}/${topicName}`;
export const clusterTopicSettingsPath = (clusterName: ClusterName, topicName: TopicName) => `${clusterTopicsPath(clusterName)}/${topicName}/settings`;
export const clusterTopicMessagesPath = (clusterName: ClusterName, topicName: TopicName) => `${clusterTopicsPath(clusterName)}/${topicName}/messages`;

View file

@ -3,30 +3,30 @@ import * as actions from './actions';
import {
PromiseThunk,
Cluster,
ClusterId,
ClusterName,
TopicFormData,
TopicName, Topic,
} from 'redux/interfaces';
export const fetchBrokers = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
export const fetchBrokers = (clusterName: ClusterName): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchBrokersAction.request());
try {
const payload = await api.getBrokers(clusterId);
const payload = await api.getBrokers(clusterName);
dispatch(actions.fetchBrokersAction.success(payload));
} catch (e) {
dispatch(actions.fetchBrokersAction.failure());
}
}
};
export const fetchBrokerMetrics = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
export const fetchBrokerMetrics = (clusterName: ClusterName): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchBrokerMetricsAction.request());
try {
const payload = await api.getBrokerMetrics(clusterId);
const payload = await api.getBrokerMetrics(clusterName);
dispatch(actions.fetchBrokerMetricsAction.success(payload));
} catch (e) {
dispatch(actions.fetchBrokerMetricsAction.failure());
}
}
};
export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchClusterListAction.request());
@ -36,44 +36,44 @@ export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
} catch (e) {
dispatch(actions.fetchClusterListAction.failure());
}
}
};
export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
export const fetchTopicList = (clusterName: ClusterName): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchTopicListAction.request());
try {
const topics = await api.getTopics(clusterId);
const topics = await api.getTopics(clusterName);
dispatch(actions.fetchTopicListAction.success(topics));
} catch (e) {
dispatch(actions.fetchTopicListAction.failure());
}
}
};
export const fetchTopicDetails = (clusterId: ClusterId, topicName: TopicName): PromiseThunk<void> => async (dispatch) => {
export const fetchTopicDetails = (clusterName: ClusterName, topicName: TopicName): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchTopicDetailsAction.request());
try {
const topicDetails = await api.getTopicDetails(clusterId, topicName);
const topicDetails = await api.getTopicDetails(clusterName, topicName);
dispatch(actions.fetchTopicDetailsAction.success({ topicName, details: topicDetails }));
} catch (e) {
dispatch(actions.fetchTopicDetailsAction.failure());
}
}
};
export const fetchTopicConfig = (clusterId: ClusterId, topicName: TopicName): PromiseThunk<void> => async (dispatch) => {
export const fetchTopicConfig = (clusterName: ClusterName, topicName: TopicName): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchTopicConfigAction.request());
try {
const config = await api.getTopicConfig(clusterId, topicName);
const config = await api.getTopicConfig(clusterName, topicName);
dispatch(actions.fetchTopicConfigAction.success({ topicName, config }));
} catch (e) {
dispatch(actions.fetchTopicConfigAction.failure());
}
}
};
export const createTopic = (clusterId: ClusterId, form: TopicFormData): PromiseThunk<void> => async (dispatch) => {
export const createTopic = (clusterName: ClusterName, form: TopicFormData): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.createTopicAction.request());
try {
const topic: Topic = await api.postTopic(clusterId, form);
const topic: Topic = await api.postTopic(clusterName, form);
dispatch(actions.createTopicAction.success(topic));
} catch (e) {
dispatch(actions.createTopicAction.failure());
}
}
};

View file

@ -1,6 +1,6 @@
import {
Broker,
ClusterId,
ClusterName,
BrokerMetrics,
} from 'redux/interfaces';
import {
@ -8,10 +8,10 @@ import {
BASE_PARAMS,
} from 'lib/constants';
export const getBrokers = (clusterId: ClusterId): Promise<Broker[]> =>
fetch(`${BASE_URL}/clusters/${clusterId}/brokers`, { ...BASE_PARAMS })
export const getBrokers = (clusterName: ClusterName): Promise<Broker[]> =>
fetch(`${BASE_URL}/clusters/${clusterName}/brokers`, { ...BASE_PARAMS })
.then(res => res.json());
export const getBrokerMetrics = (clusterId: ClusterId): Promise<BrokerMetrics> =>
fetch(`${BASE_URL}/clusters/${clusterId}/metrics/broker`, { ...BASE_PARAMS })
export const getBrokerMetrics = (clusterName: ClusterName): Promise<BrokerMetrics> =>
fetch(`${BASE_URL}/clusters/${clusterName}/metrics/broker`, { ...BASE_PARAMS })
.then(res => res.json());

View file

@ -1,7 +1,7 @@
import {
TopicName,
Topic,
ClusterId,
ClusterName,
TopicDetails,
TopicConfig,
TopicFormData,
@ -11,19 +11,19 @@ import {
BASE_PARAMS,
} from 'lib/constants';
export const getTopicConfig = (clusterId: ClusterId, topicName: TopicName): Promise<TopicConfig[]> =>
fetch(`${BASE_URL}/clusters/${clusterId}/topics/${topicName}/config`, { ...BASE_PARAMS })
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 = (clusterId: ClusterId, topicName: TopicName): Promise<TopicDetails> =>
fetch(`${BASE_URL}/clusters/${clusterId}/topics/${topicName}`, { ...BASE_PARAMS })
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 = (clusterId: ClusterId): Promise<Topic[]> =>
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
export const getTopics = (clusterName: ClusterName): Promise<Topic[]> =>
fetch(`${BASE_URL}/clusters/${clusterName}/topics`, { ...BASE_PARAMS })
.then(res => res.json());
export const postTopic = (clusterId: ClusterId, form: TopicFormData): Promise<Topic> => {
export const postTopic = (clusterName: ClusterName, form: TopicFormData): Promise<Topic> => {
const {
name,
partitions,
@ -46,9 +46,9 @@ export const postTopic = (clusterId: ClusterId, form: TopicFormData): Promise<To
'min.insync.replicas': minInSyncReplicas,
}
});
return fetch(`${BASE_URL}/clusters/${clusterId}/topics`, {
return fetch(`${BASE_URL}/clusters/${clusterName}/topics`, {
...BASE_PARAMS,
method: 'POST',
body,
}).then(res => res.json());
}
};

View file

@ -3,11 +3,11 @@ export enum ClusterStatus {
Offline = 'offline',
}
export type ClusterId = string;
export type ClusterName = string;
export interface Cluster {
id: ClusterId;
name: string;
id: string;
name: ClusterName;
defaultCluster: boolean;
status: ClusterStatus;
brokerCount: number;