[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"> <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 />
)} )}

View file

@ -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>

View file

@ -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 (

View file

@ -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>

View file

@ -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>
</> </>
); );
} }

View file

@ -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);

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, 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>
); );
} }

View file

@ -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());

View file

@ -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());
} }

View file

@ -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',

View file

@ -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",
};