[UI] Brokers Dashboard
This commit is contained in:
parent
88335f94a8
commit
74b0c000da
8 changed files with 209 additions and 50 deletions
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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="section">
|
<div className="box">
|
||||||
<div className="box">
|
<div className="field">
|
||||||
<div className="field">
|
<input
|
||||||
<input
|
id="switchRoundedDefault"
|
||||||
id="switchRoundedDefault"
|
type="checkbox"
|
||||||
type="checkbox"
|
name="switchRoundedDefault"
|
||||||
name="switchRoundedDefault"
|
className="switch is-rounded"
|
||||||
className="switch is-rounded"
|
checked={showInternal}
|
||||||
checked={showInternal}
|
onChange={handleSwitch}
|
||||||
onChange={handleSwitch}
|
/>
|
||||||
/>
|
<label htmlFor="switchRoundedDefault">
|
||||||
<label htmlFor="switchRoundedDefault">
|
Show Internal Topics
|
||||||
Show Internal Topics
|
</label>
|
||||||
</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>
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,22 @@ export const getIsBrokerListFetched = createSelector(
|
||||||
|
|
||||||
const getBrokerList = createSelector(brokersState, ({ items }) => items);
|
const getBrokerList = createSelector(brokersState, ({ items }) => items);
|
||||||
|
|
||||||
export const getTotalBrokers = createSelector(
|
export const getBrokerCount = createSelector(brokersState, ({ brokerCount }) => brokerCount);
|
||||||
getIsBrokerListFetched,
|
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,
|
getBrokerList,
|
||||||
(isFetched, brokers) => (isFetched && brokers !== undefined ? brokers.length : undefined),
|
(brokers) => Math.min(...brokers.map(({ segmentSize }) => segmentSize)),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getMaxDiskUsage = createSelector(
|
||||||
|
getBrokerList,
|
||||||
|
(brokers) => Math.max(...brokers.map(({ segmentSize }) => segmentSize)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
const payload = await getBrokers(clusterId);
|
||||||
dispatch(fetchBrokersAction.success(brokers));
|
dispatch(fetchBrokersAction.success(payload));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(fetchBrokersAction.failure());
|
dispatch(fetchBrokersAction.failure());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue