Browse Source

[UI] Topics Ui

Oleg Shuralev 5 years ago
parent
commit
3bfa1a7d1e

+ 6 - 8
frontend/src/components/App.tsx

@@ -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>
-              <Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
-              <Route exact path="/">
-                Dashboard
-              </Route>
-            </Switch>
-          </section>
+          <Switch>
+            <Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
+            <Route exact path="/">
+              Dashboard
+            </Route>
+          </Switch>
         ) : (
         ) : (
           <PageLoader />
           <PageLoader />
         )}
         )}

+ 4 - 1
frontend/src/components/Nav/ClusterMenu.tsx

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

+ 2 - 2
frontend/src/components/Topics/Details/Details.tsx

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

+ 3 - 3
frontend/src/components/Topics/Details/Replica.tsx

@@ -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')}>
-            {in_sync ? 'IN SYNC' : 'OUT OF SYNC'}
+          <span className={cx('tag', inSync ? 'is-success' : 'is-danger')}>
+            {inSync ? 'IN SYNC' : 'OUT OF SYNC'}
           </span>
           </span>
         </div>
         </div>
       </div>
       </div>

+ 20 - 40
frontend/src/components/Topics/List/List.tsx

@@ -1,56 +1,36 @@
 import React from 'react';
 import React from 'react';
 import { Topic } from 'types';
 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 {
 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="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>
-            </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 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>
-      </section>
-
-      <div className="container is-fluid">
-        <ul className="tile is-parent is-vertical">
-          {topics.map((topic) => <ListItem {...topic} key={topic.name} />)}
-        </ul>
       </div>
       </div>
     </>
     </>
   );
   );

+ 1 - 1
frontend/src/components/Topics/List/ListContainer.ts

@@ -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 - 0
frontend/src/components/Topics/List/ListItem.tsx

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

+ 3 - 3
frontend/src/components/Topics/Topics.tsx

@@ -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="/topics" component={ListContainer} />
+        <Route exact path="/clusters/:clusterId/topics/:topicName" component={DetailsContainer} />
+        <Route exact path="/clusters/:clusterId/topics" component={ListContainer} />
       </Switch>
       </Switch>
     );
     );
   }
   }

+ 1 - 1
frontend/src/lib/api/topics.ts

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

+ 1 - 2
frontend/src/redux/reducers/topics/thunks.ts

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

+ 0 - 2
frontend/src/theme/index.scss

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

+ 6 - 3
frontend/src/types/topic.ts

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