[UI] Topics Ui

This commit is contained in:
Oleg Shuralev 2020-01-05 14:40:18 +03:00
parent ed95095254
commit 3bfa1a7d1e
No known key found for this signature in database
GPG key ID: 0459DF80E1A2FD1B
12 changed files with 81 additions and 66 deletions

View file

@ -31,14 +31,12 @@ const App: React.FC<AppProps> = ({
<main className="Layout__container">
<NavConatiner className="Layout__navbar" />
{isClusterListFetched ? (
<section className="section">
<Switch>
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
<Route exact path="/">
Dashboard
</Route>
</Switch>
</section>
) : (
<PageLoader />
)}

View file

@ -31,7 +31,10 @@ const ClusterMenu: React.FC<Props> = ({
{name}
</NavLink>
<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
</NavLink>
</ul>

View file

@ -1,15 +1,15 @@
import React from 'react';
import { Topic } from 'types';
import { Topic, TopicConfigs } from 'types';
import ConfigRow from './ConfigRow';
import Partition from './Partition';
const Details: React.FC<{ topic: Topic }> = ({
topic: {
name,
configs,
partitions,
}
}) => {
const configs: TopicConfigs = {[ 'key-config']: '1' };
const configKeys = Object.keys(configs);
return (

View file

@ -7,7 +7,7 @@ interface Props extends TopicReplica {
}
const Replica: React.FC<Props> = ({
in_sync,
inSync,
leader,
broker,
index,
@ -22,8 +22,8 @@ const Replica: React.FC<Props> = ({
LEADER
</span>
)}
<span className={cx('tag', in_sync ? 'is-success' : 'is-danger')}>
{in_sync ? 'IN SYNC' : 'OUT OF SYNC'}
<span className={cx('tag', inSync ? 'is-success' : 'is-danger')}>
{inSync ? 'IN SYNC' : 'OUT OF SYNC'}
</span>
</div>
</div>

View file

@ -1,57 +1,37 @@
import React from 'react';
import { Topic } from 'types';
import { NavLink } from 'react-router-dom';
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>
);
}
import ListItem from './ListItem';
interface Props {
topics: Topic[];
totalBrokers?: number;
}
const List: React.FC<Props> = ({
topics,
totalBrokers,
}) => {
return (
<>
<section className="hero is-info is-bold">
<div className="hero-body">
<div className="level has-text-white is-mobile">
<div className="level-item has-text-centered ">
<div>
<p className="heading">Brokers</p>
<p className="title has-text-white">{totalBrokers}</p>
<div className="section">
<div className="box">
<table className="table is-striped is-fullwidth">
<thead>
<tr>
<th>Topic Name</th>
<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 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>
</>
);
}

View file

@ -4,7 +4,6 @@ import {
getTotalBrokers,
} from 'redux/reducers/topics/selectors';
import { RootState } from 'types';
import List from './List';
const mapStateToProps = (state: RootState) => ({
@ -12,4 +11,5 @@ const mapStateToProps = (state: RootState) => ({
totalBrokers: getTotalBrokers(state),
});
export default connect(mapStateToProps)(List);

View 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;

View file

@ -22,13 +22,13 @@ const Topics: React.FC<Props> = ({
fetchTopicList,
}) => {
React.useEffect(() => { fetchTopicList(clusterId); }, [fetchTopicList, clusterId]);
React.useEffect(() => { fetchBrokers(clusterId); }, [fetchBrokers, clusterId]);
// React.useEffect(() => { fetchBrokers(clusterId); }, [fetchBrokers, clusterId]);
if (isFetched) {
return (
<Switch>
<Route exact path="/topics/:topicName" component={DetailsContainer} />
<Route exact path="/topics" component={ListContainer} />
<Route exact path="/clusters/:clusterId/topics/:topicName" component={DetailsContainer} />
<Route exact path="/clusters/:clusterId/topics" component={ListContainer} />
</Switch>
);
}

View file

@ -13,7 +13,7 @@ export const getTopic = (name: TopicName): Promise<Topic> =>
fetch(`${BASE_URL}/topics/${name}`, { ...BASE_PARAMS })
.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 })
.then(res => res.json());

View file

@ -15,9 +15,8 @@ export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => asyn
try {
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) {
dispatch(fetchTopicListAction.failure());
}

View file

@ -1,9 +1,7 @@
@import './bulma_overrides.scss';
#root, body, html {
height: 100%;
width: 100%;
overflow: hidden;
position: relative;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

View file

@ -8,7 +8,7 @@ export interface TopicConfigs {
export interface TopicReplica {
broker: number;
leader: boolean;
in_sync: true;
inSync: true;
}
export interface TopicPartition {
@ -19,7 +19,7 @@ export interface TopicPartition {
export interface Topic {
name: TopicName;
configs: TopicConfigs;
internal: boolean;
partitions: TopicPartition[];
}
@ -29,4 +29,7 @@ export interface TopicsState {
brokers?: Broker[];
}
export type Broker = number;
export interface Broker {
id: 1,
host: "broker",
};