[UI] Topics Ui
This commit is contained in:
parent
ed95095254
commit
3bfa1a7d1e
12 changed files with 81 additions and 66 deletions
|
@ -31,14 +31,12 @@ const App: React.FC<AppProps> = ({
|
||||||
<main className="Layout__container">
|
<main className="Layout__container">
|
||||||
<NavConatiner className="Layout__navbar" />
|
<NavConatiner className="Layout__navbar" />
|
||||||
{isClusterListFetched ? (
|
{isClusterListFetched ? (
|
||||||
<section className="section">
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
|
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
|
||||||
<Route exact path="/">
|
<Route exact path="/">
|
||||||
Dashboard
|
Dashboard
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</section>
|
|
||||||
) : (
|
) : (
|
||||||
<PageLoader />
|
<PageLoader />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -31,7 +31,10 @@ const ClusterMenu: React.FC<Props> = ({
|
||||||
{name}
|
{name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<ul>
|
<ul>
|
||||||
<NavLink to={`/clusters/${id}/topics`} activeClassName="is-active" title="Dashboard">
|
<NavLink to={`/clusters/${id}/brokers`} activeClassName="is-active" title="Brokers">
|
||||||
|
Brokers
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={`/clusters/${id}/topics`} activeClassName="is-active" title="Topics">
|
||||||
Topics
|
Topics
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Topic } from 'types';
|
import { Topic, TopicConfigs } from 'types';
|
||||||
import ConfigRow from './ConfigRow';
|
import ConfigRow from './ConfigRow';
|
||||||
import Partition from './Partition';
|
import Partition from './Partition';
|
||||||
|
|
||||||
const Details: React.FC<{ topic: Topic }> = ({
|
const Details: React.FC<{ topic: Topic }> = ({
|
||||||
topic: {
|
topic: {
|
||||||
name,
|
name,
|
||||||
configs,
|
|
||||||
partitions,
|
partitions,
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
|
const configs: TopicConfigs = {[ 'key-config']: '1' };
|
||||||
const configKeys = Object.keys(configs);
|
const configKeys = Object.keys(configs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface Props extends TopicReplica {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Replica: React.FC<Props> = ({
|
const Replica: React.FC<Props> = ({
|
||||||
in_sync,
|
inSync,
|
||||||
leader,
|
leader,
|
||||||
broker,
|
broker,
|
||||||
index,
|
index,
|
||||||
|
@ -22,8 +22,8 @@ const Replica: React.FC<Props> = ({
|
||||||
LEADER
|
LEADER
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className={cx('tag', in_sync ? 'is-success' : 'is-danger')}>
|
<span className={cx('tag', inSync ? 'is-success' : 'is-danger')}>
|
||||||
{in_sync ? 'IN SYNC' : 'OUT OF SYNC'}
|
{inSync ? 'IN SYNC' : 'OUT OF SYNC'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,57 +1,37 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Topic } from 'types';
|
import { Topic } from 'types';
|
||||||
import { NavLink } from 'react-router-dom';
|
import ListItem from './ListItem';
|
||||||
|
|
||||||
const ListItem: React.FC<Topic> = ({
|
|
||||||
name,
|
|
||||||
partitions,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<li className="tile is-child box">
|
|
||||||
<NavLink exact to={`/topics/${name}`} activeClassName="is-active" className="title is-6">
|
|
||||||
{name} <i className="fas fa-arrow-circle-right has-text-link"></i>
|
|
||||||
</NavLink>
|
|
||||||
<p>Partitions: {partitions.length}</p>
|
|
||||||
<p>Replications: {partitions ? partitions[0].replicas.length : 0}</p>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
topics: Topic[];
|
topics: Topic[];
|
||||||
totalBrokers?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const List: React.FC<Props> = ({
|
const List: React.FC<Props> = ({
|
||||||
topics,
|
topics,
|
||||||
totalBrokers,
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="hero is-info is-bold">
|
<div className="section">
|
||||||
<div className="hero-body">
|
<div className="box">
|
||||||
<div className="level has-text-white is-mobile">
|
<table className="table is-striped is-fullwidth">
|
||||||
<div className="level-item has-text-centered ">
|
<thead>
|
||||||
<div>
|
<tr>
|
||||||
<p className="heading">Brokers</p>
|
<th>Topic Name</th>
|
||||||
<p className="title has-text-white">{totalBrokers}</p>
|
<th>Total Partitions</th>
|
||||||
|
<th>Out of sync replicas</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{topics.map((topic) => (
|
||||||
|
<ListItem
|
||||||
|
key={topic.name}
|
||||||
|
{...topic}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p className="heading">Topics</p>
|
|
||||||
<p className="title has-text-white">{topics.length}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="container is-fluid">
|
|
||||||
<ul className="tile is-parent is-vertical">
|
|
||||||
{topics.map((topic) => <ListItem {...topic} key={topic.name} />)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
getTotalBrokers,
|
getTotalBrokers,
|
||||||
} from 'redux/reducers/topics/selectors';
|
} from 'redux/reducers/topics/selectors';
|
||||||
import { RootState } from 'types';
|
import { RootState } from 'types';
|
||||||
|
|
||||||
import List from './List';
|
import List from './List';
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => ({
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
@ -12,4 +11,5 @@ const mapStateToProps = (state: RootState) => ({
|
||||||
totalBrokers: getTotalBrokers(state),
|
totalBrokers: getTotalBrokers(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(List);
|
export default connect(mapStateToProps)(List);
|
||||||
|
|
34
frontend/src/components/Topics/List/ListItem.tsx
Normal file
34
frontend/src/components/Topics/List/ListItem.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { Topic } from 'types';
|
||||||
|
|
||||||
|
|
||||||
|
const ListItem: React.FC<Topic> = ({
|
||||||
|
name,
|
||||||
|
partitions,
|
||||||
|
}) => {
|
||||||
|
const outOfSyncReplicas = React.useMemo(() => {
|
||||||
|
if (partitions === undefined || partitions.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return partitions.reduce((memo: number, { replicas }) => {
|
||||||
|
const outOfSync = replicas.filter(({ inSync }) => !inSync)
|
||||||
|
return memo + outOfSync.length;
|
||||||
|
}, 0);
|
||||||
|
}, [partitions])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<NavLink exact to={`topics/${name}`} activeClassName="is-active" className="title is-6">
|
||||||
|
{name}
|
||||||
|
</NavLink>
|
||||||
|
</td>
|
||||||
|
<td>{partitions.length}</td>
|
||||||
|
<td>{outOfSyncReplicas}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListItem;
|
|
@ -22,13 +22,13 @@ const Topics: React.FC<Props> = ({
|
||||||
fetchTopicList,
|
fetchTopicList,
|
||||||
}) => {
|
}) => {
|
||||||
React.useEffect(() => { fetchTopicList(clusterId); }, [fetchTopicList, clusterId]);
|
React.useEffect(() => { fetchTopicList(clusterId); }, [fetchTopicList, clusterId]);
|
||||||
React.useEffect(() => { fetchBrokers(clusterId); }, [fetchBrokers, clusterId]);
|
// React.useEffect(() => { fetchBrokers(clusterId); }, [fetchBrokers, clusterId]);
|
||||||
|
|
||||||
if (isFetched) {
|
if (isFetched) {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/topics/:topicName" component={DetailsContainer} />
|
<Route exact path="/clusters/:clusterId/topics/:topicName" component={DetailsContainer} />
|
||||||
<Route exact path="/topics" component={ListContainer} />
|
<Route exact path="/clusters/:clusterId/topics" component={ListContainer} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const getTopic = (name: TopicName): Promise<Topic> =>
|
||||||
fetch(`${BASE_URL}/topics/${name}`, { ...BASE_PARAMS })
|
fetch(`${BASE_URL}/topics/${name}`, { ...BASE_PARAMS })
|
||||||
.then(res => res.json());
|
.then(res => res.json());
|
||||||
|
|
||||||
export const getTopics = (clusterId: ClusterId): Promise<TopicName[]> =>
|
export const getTopics = (clusterId: ClusterId): Promise<Topic[]> =>
|
||||||
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
|
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
|
||||||
.then(res => res.json());
|
.then(res => res.json());
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,8 @@ export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => asyn
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const topics = await getTopics(clusterId);
|
const topics = await getTopics(clusterId);
|
||||||
const detailedList = await Promise.all(topics.map((topic: TopicName): Promise<Topic> => getTopic(topic)));
|
|
||||||
|
|
||||||
dispatch(fetchTopicListAction.success(detailedList));
|
dispatch(fetchTopicListAction.success(topics));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(fetchTopicListAction.failure());
|
dispatch(fetchTopicListAction.failure());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
@import './bulma_overrides.scss';
|
@import './bulma_overrides.scss';
|
||||||
|
|
||||||
#root, body, html {
|
#root, body, html {
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
|
|
@ -8,7 +8,7 @@ export interface TopicConfigs {
|
||||||
export interface TopicReplica {
|
export interface TopicReplica {
|
||||||
broker: number;
|
broker: number;
|
||||||
leader: boolean;
|
leader: boolean;
|
||||||
in_sync: true;
|
inSync: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopicPartition {
|
export interface TopicPartition {
|
||||||
|
@ -19,7 +19,7 @@ export interface TopicPartition {
|
||||||
|
|
||||||
export interface Topic {
|
export interface Topic {
|
||||||
name: TopicName;
|
name: TopicName;
|
||||||
configs: TopicConfigs;
|
internal: boolean;
|
||||||
partitions: TopicPartition[];
|
partitions: TopicPartition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,4 +29,7 @@ export interface TopicsState {
|
||||||
brokers?: Broker[];
|
brokers?: Broker[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Broker = number;
|
export interface Broker {
|
||||||
|
id: 1,
|
||||||
|
host: "broker",
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue