소스 검색

[UI] Brokers Dashboard

Oleg Shuralev 5 년 전
부모
커밋
74b0c000da

+ 139 - 3
frontend/src/components/Brokers/Brokers.tsx

@@ -1,11 +1,15 @@
 import React from 'react';
 import React from 'react';
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import PageLoader from 'components/common/PageLoader/PageLoader';
-import { ClusterId } from 'types';
+import { ClusterId, BrokerMetrics, ZooKeeperStatus } from 'types';
 import useInterval from 'lib/hooks/useInterval';
 import useInterval from 'lib/hooks/useInterval';
+import formatBytes from 'lib/utils/formatBytes';
+import cx from 'classnames';
 
 
-interface Props {
+interface Props extends BrokerMetrics {
   clusterId: string;
   clusterId: string;
   isFetched: boolean;
   isFetched: boolean;
+  minDiskUsage: number;
+  maxDiskUsage: number;
   fetchBrokers: (clusterId: ClusterId) => void;
   fetchBrokers: (clusterId: ClusterId) => void;
   fetchBrokerMetrics: (clusterId: ClusterId) => void;
   fetchBrokerMetrics: (clusterId: ClusterId) => void;
 }
 }
@@ -13,6 +17,17 @@ interface Props {
 const Topics: React.FC<Props> = ({
 const Topics: React.FC<Props> = ({
   clusterId,
   clusterId,
   isFetched,
   isFetched,
+  brokerCount,
+  activeControllers,
+  zooKeeperStatus,
+  onlinePartitionCount,
+  offlinePartitionCount,
+  underReplicatedPartitionCount,
+  diskUsageDistribution,
+  minDiskUsage,
+  maxDiskUsage,
+  networkPoolUsage,
+  requestPoolUsage,
   fetchBrokers,
   fetchBrokers,
   fetchBrokerMetrics,
   fetchBrokerMetrics,
 }) => {
 }) => {
@@ -27,8 +42,129 @@ const Topics: React.FC<Props> = ({
   useInterval(() => { fetchBrokerMetrics(clusterId); }, 5000);
   useInterval(() => { fetchBrokerMetrics(clusterId); }, 5000);
 
 
   if (isFetched) {
   if (isFetched) {
+    const [minDiskUsageValue, minDiskUsageSize] = formatBytes(minDiskUsage);
+    const [maxDiskUsageValue, maxDiskUsageSize] = formatBytes(maxDiskUsage);
+
     return (
     return (
-      <div>Brokers of {clusterId}</div>
+      <div className="section">
+        <div className="box">
+          <h5 className="title is-5">Uptime</h5>
+          <div className="level">
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Total Brokers</p>
+                <p className="title">{brokerCount}</p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Active Controllers</p>
+                <p className="title">{activeControllers}</p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Zookeeper Status</p>
+                <p className="title">
+                  {zooKeeperStatus === ZooKeeperStatus.online ? (
+                    <span className="tag is-primary">Online</span>
+                  ) : (
+                    <span className="tag is-danger">Offline</span>
+                  )}
+                </p>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div className="box">
+          <h5 className="title is-5">Partitions</h5>
+          <div className="level">
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Online</p>
+                <p>
+                  <span className={cx('title', {'has-text-danger': offlinePartitionCount !== 0})}>
+                    {onlinePartitionCount}
+                  </span>
+                  <span className="subtitle"> of {onlinePartitionCount + offlinePartitionCount}</span>
+                </p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Under Replicated</p>
+                <p className="title">{underReplicatedPartitionCount}</p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">In Sync Replicas</p>
+                <p className="title has-text-grey-lighter">Soon</p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Out of Sync Replicas</p>
+                <p className="title has-text-grey-lighter">Soon</p>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div className="box">
+          <h5 className="title is-5">Disk</h5>
+          <div className="level">
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Max usage</p>
+                <p>
+                  <span className="title">{maxDiskUsageValue}</span>
+                  <span className="subtitle"> {maxDiskUsageSize}</span>
+                </p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Min Usage</p>
+                <p>
+                  <span className="title">{minDiskUsageValue}</span>
+                  <span className="subtitle"> {minDiskUsageSize}</span>
+                </p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Distribution</p>
+                <p className="title">{diskUsageDistribution}</p>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div className="box">
+          <h5 className="title is-5">System</h5>
+          <div className="level">
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Network pool usage</p>
+                <p className="title">
+                  {Math.round(networkPoolUsage * 10000) / 100}
+                  <span className="subtitle">%</span>
+                </p>
+              </div>
+            </div>
+            <div className="level-item level-left">
+              <div>
+                <p className="heading">Request pool usage</p>
+                <p className="title">
+                  {Math.round(requestPoolUsage * 10000) / 100}
+                  <span className="subtitle">%</span>
+                </p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
     );
     );
   }
   }
 
 

+ 13 - 2
frontend/src/components/Brokers/BrokersContainer.ts

@@ -4,7 +4,7 @@ import {
   fetchBrokerMetrics,
   fetchBrokerMetrics,
 } from 'redux/reducers/brokers/thunks';
 } from 'redux/reducers/brokers/thunks';
 import Brokers from './Brokers';
 import Brokers from './Brokers';
-import { getIsBrokerListFetched } from 'redux/reducers/brokers/selectors';
+import * as brokerSelectors from 'redux/reducers/brokers/selectors';
 import { RootState, ClusterId } from 'types';
 import { RootState, ClusterId } from 'types';
 import { RouteComponentProps } from 'react-router-dom';
 import { RouteComponentProps } from 'react-router-dom';
 
 
@@ -15,8 +15,19 @@ interface RouteProps {
 interface OwnProps extends RouteComponentProps<RouteProps> { }
 interface OwnProps extends RouteComponentProps<RouteProps> { }
 
 
 const mapStateToProps = (state: RootState, { match: { params: { clusterId } }}: OwnProps) => ({
 const mapStateToProps = (state: RootState, { match: { params: { clusterId } }}: OwnProps) => ({
-  isFetched: getIsBrokerListFetched(state),
+  isFetched: brokerSelectors.getIsBrokerListFetched(state),
   clusterId,
   clusterId,
+  brokerCount: brokerSelectors.getBrokerCount(state),
+  zooKeeperStatus: brokerSelectors.getZooKeeperStatus(state),
+  activeControllers: brokerSelectors.getActiveControllers(state),
+  networkPoolUsage: brokerSelectors.getNetworkPoolUsage(state),
+  requestPoolUsage: brokerSelectors.getRequestPoolUsage(state),
+  onlinePartitionCount: brokerSelectors.getOnlinePartitionCount(state),
+  offlinePartitionCount: brokerSelectors.getOfflinePartitionCount(state),
+  underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(state),
+  diskUsageDistribution: brokerSelectors.getDiskUsageDistribution(state),
+  minDiskUsage: brokerSelectors.getMinDiskUsage(state),
+  maxDiskUsage: brokerSelectors.getMaxDiskUsage(state),
 });
 });
 
 
 const mapDispatchToProps = {
 const mapDispatchToProps = {

+ 34 - 36
frontend/src/components/Topics/List/List.tsx

@@ -18,44 +18,42 @@ const List: React.FC<Props> = ({
   const items = showInternal ? topics : externalTopics;
   const items = showInternal ? topics : externalTopics;
 
 
   return (
   return (
-    <>
-      <div className="section">
-        <div className="box">
-          <div className="field">
-            <input
-              id="switchRoundedDefault"
-              type="checkbox"
-              name="switchRoundedDefault"
-              className="switch is-rounded"
-              checked={showInternal}
-              onChange={handleSwitch}
-            />
-            <label htmlFor="switchRoundedDefault">
-              Show Internal Topics
-            </label>
-          </div>
-        </div>
-        <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>
-              {items.map((topic) => (
-                <ListItem
-                  key={topic.name}
-                  {...topic}
-                />
-              ))}
-            </tbody>
-          </table>
+    <div className="section">
+      <div className="box">
+        <div className="field">
+          <input
+            id="switchRoundedDefault"
+            type="checkbox"
+            name="switchRoundedDefault"
+            className="switch is-rounded"
+            checked={showInternal}
+            onChange={handleSwitch}
+          />
+          <label htmlFor="switchRoundedDefault">
+            Show Internal Topics
+          </label>
         </div>
         </div>
       </div>
       </div>
-    </>
+      <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>
+            {items.map((topic) => (
+              <ListItem
+                key={topic.name}
+                {...topic}
+              />
+            ))}
+          </tbody>
+        </table>
+      </div>
+    </div>
   );
   );
 }
 }
 
 

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

@@ -8,7 +8,7 @@ import {
   BASE_PARAMS,
   BASE_PARAMS,
 } from 'lib/constants';
 } from 'lib/constants';
 
 
-export const getBrokers = (clusterId: ClusterId): Promise<{ brokers: Broker[] }> =>
+export const getBrokers = (clusterId: ClusterId): Promise<Broker[]> =>
   fetch(`${BASE_URL}/clusters/${clusterId}/brokers`, { ...BASE_PARAMS })
   fetch(`${BASE_URL}/clusters/${clusterId}/brokers`, { ...BASE_PARAMS })
     .then(res => res.json());
     .then(res => res.json());
 
 

+ 1 - 1
frontend/src/redux/reducers/brokers/reducer.ts

@@ -9,8 +9,8 @@ export const initialState: BrokersState =  {
   networkPoolUsage: 0,
   networkPoolUsage: 0,
   requestPoolUsage: 0,
   requestPoolUsage: 0,
   onlinePartitionCount: 0,
   onlinePartitionCount: 0,
-  underReplicatedPartitionCount: 0,
   offlinePartitionCount: 0,
   offlinePartitionCount: 0,
+  underReplicatedPartitionCount: 0,
   diskUsageDistribution: undefined,
   diskUsageDistribution: undefined,
 };
 };
 
 

+ 17 - 3
frontend/src/redux/reducers/brokers/selectors.ts

@@ -13,8 +13,22 @@ export const getIsBrokerListFetched = createSelector(
 
 
 const getBrokerList = createSelector(brokersState, ({ items }) => items);
 const getBrokerList = createSelector(brokersState, ({ items }) => items);
 
 
-export const getTotalBrokers = createSelector(
-  getIsBrokerListFetched,
+export const getBrokerCount = createSelector(brokersState, ({ brokerCount }) => brokerCount);
+export const getZooKeeperStatus = createSelector(brokersState, ({ zooKeeperStatus }) => zooKeeperStatus);
+export const getActiveControllers = createSelector(brokersState, ({ activeControllers }) => activeControllers);
+export const getNetworkPoolUsage = createSelector(brokersState, ({ networkPoolUsage }) => networkPoolUsage);
+export const getRequestPoolUsage = createSelector(brokersState, ({ requestPoolUsage }) => requestPoolUsage);
+export const getOnlinePartitionCount = createSelector(brokersState, ({ onlinePartitionCount }) => onlinePartitionCount);
+export const getOfflinePartitionCount = createSelector(brokersState, ({ offlinePartitionCount }) => offlinePartitionCount);
+export const getDiskUsageDistribution = createSelector(brokersState, ({ diskUsageDistribution }) => diskUsageDistribution);
+export const getUnderReplicatedPartitionCount = createSelector(brokersState, ({ underReplicatedPartitionCount }) => underReplicatedPartitionCount);
+
+export const getMinDiskUsage = createSelector(
+  getBrokerList,
+  (brokers) => Math.min(...brokers.map(({ segmentSize }) => segmentSize)),
+);
+
+export const getMaxDiskUsage = createSelector(
   getBrokerList,
   getBrokerList,
-  (isFetched, brokers) => (isFetched && brokers !== undefined ? brokers.length : undefined),
+  (brokers) => Math.max(...brokers.map(({ segmentSize }) => segmentSize)),
 );
 );

+ 2 - 2
frontend/src/redux/reducers/brokers/thunks.ts

@@ -9,8 +9,8 @@ import { PromiseThunk, ClusterId } from 'types';
 export const fetchBrokers = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
 export const fetchBrokers = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
   dispatch(fetchBrokersAction.request());
   dispatch(fetchBrokersAction.request());
   try {
   try {
-    const { brokers } = await getBrokers(clusterId);
-    dispatch(fetchBrokersAction.success(brokers));
+    const payload = await getBrokers(clusterId);
+    dispatch(fetchBrokersAction.success(payload));
   } catch (e) {
   } catch (e) {
     dispatch(fetchBrokersAction.failure());
     dispatch(fetchBrokersAction.failure());
   }
   }

+ 2 - 2
frontend/src/types/broker.ts

@@ -8,7 +8,7 @@ export interface Broker {
   bytesOutPerSec: number;
   bytesOutPerSec: number;
 };
 };
 
 
-export enum ZooKeeperStatus { online, offline };
+export enum ZooKeeperStatus { offline, online };
 
 
 export interface BrokerDiskUsage {
 export interface BrokerDiskUsage {
   brokerId: BrokerId;
   brokerId: BrokerId;
@@ -22,8 +22,8 @@ export interface BrokerMetrics {
   networkPoolUsage: number;
   networkPoolUsage: number;
   requestPoolUsage: number;
   requestPoolUsage: number;
   onlinePartitionCount: number;
   onlinePartitionCount: number;
-  underReplicatedPartitionCount: number;
   offlinePartitionCount: number;
   offlinePartitionCount: number;
+  underReplicatedPartitionCount: number;
   diskUsageDistribution?: string;
   diskUsageDistribution?: string;
 }
 }