[UI] Dashboard
This commit is contained in:
parent
870971f879
commit
fad704f377
7 changed files with 223 additions and 10 deletions
|
@ -9,6 +9,7 @@ import BrokersContainer from './Brokers/BrokersContainer';
|
||||||
import TopicsContainer from './Topics/TopicsContainer';
|
import TopicsContainer from './Topics/TopicsContainer';
|
||||||
import NavConatiner from './Nav/NavConatiner';
|
import NavConatiner from './Nav/NavConatiner';
|
||||||
import PageLoader from './common/PageLoader/PageLoader';
|
import PageLoader from './common/PageLoader/PageLoader';
|
||||||
|
import Dashboard from './Dashboard/Dashboard';
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
isClusterListFetched: boolean;
|
isClusterListFetched: boolean;
|
||||||
|
@ -34,10 +35,7 @@ const App: React.FC<AppProps> = ({
|
||||||
<NavConatiner className="Layout__navbar" />
|
<NavConatiner className="Layout__navbar" />
|
||||||
{isClusterListFetched ? (
|
{isClusterListFetched ? (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/">
|
<Route exact path="/" component={Dashboard} />
|
||||||
Dashboard
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
|
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
|
||||||
<Route path="/clusters/:clusterId/brokers" component={BrokersContainer} />
|
<Route path="/clusters/:clusterId/brokers" component={BrokersContainer} />
|
||||||
<Redirect from="/clusters/:clusterId" to="/clusters/:clusterId/brokers" />
|
<Redirect from="/clusters/:clusterId" to="/clusters/:clusterId/brokers" />
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Cluster, ClusterStatus } from 'lib/interfaces';
|
||||||
|
import formatBytes from 'lib/utils/formatBytes';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { clusterBrokersPath } from 'lib/paths';
|
||||||
|
|
||||||
|
const ClusterWidget: React.FC<Cluster> = ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
status,
|
||||||
|
topicCount,
|
||||||
|
brokerCount,
|
||||||
|
bytesInPerSec,
|
||||||
|
bytesOutPerSec,
|
||||||
|
onlinePartitionCount,
|
||||||
|
}) => (
|
||||||
|
<NavLink to={clusterBrokersPath(id)} className="column is-full-modile is-6">
|
||||||
|
<div className="box is-hoverable">
|
||||||
|
<div
|
||||||
|
className="title is-6 has-text-overflow-ellipsis"
|
||||||
|
title={name}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`tag has-margin-right ${status === ClusterStatus.Online ? 'is-primary' : 'is-danger'}`}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</div>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table className="table is-fullwidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Brokers</th>
|
||||||
|
<td>{brokerCount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Partitions</th>
|
||||||
|
<td>{onlinePartitionCount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Topics</th>
|
||||||
|
<td>{topicCount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Production</th>
|
||||||
|
<td>{formatBytes(bytesInPerSec)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Consumption</th>
|
||||||
|
<td>{formatBytes(bytesOutPerSec)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ClusterWidget;
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { chunk } from 'lodash';
|
||||||
|
import { Cluster } from 'lib/interfaces';
|
||||||
|
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
|
||||||
|
import Indicator from 'components/common/Dashboard/Indicator';
|
||||||
|
import ClusterWidget from './ClusterWidget';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
clusters: Cluster[];
|
||||||
|
onlineClusters: Cluster[];
|
||||||
|
offlineClusters: Cluster[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClustersWidget: React.FC<Props> = ({
|
||||||
|
clusters,
|
||||||
|
onlineClusters,
|
||||||
|
offlineClusters,
|
||||||
|
}) => {
|
||||||
|
const [showOfflineOnly, setShowOfflineOnly] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const clusterList: Array<Cluster[]> = React.useMemo(() => {
|
||||||
|
let list = clusters;
|
||||||
|
|
||||||
|
if (showOfflineOnly) {
|
||||||
|
list = offlineClusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk(list, 2);
|
||||||
|
},
|
||||||
|
[clusters, offlineClusters, showOfflineOnly],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSwitch = () => setShowOfflineOnly(!showOfflineOnly);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="title is-5">
|
||||||
|
Clusters
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<MetricsWrapper>
|
||||||
|
<Indicator label="Online Clusters" >
|
||||||
|
<span className="tag is-primary">
|
||||||
|
{onlineClusters.length}
|
||||||
|
</span>
|
||||||
|
</Indicator>
|
||||||
|
<Indicator label="Offline Clusters">
|
||||||
|
<span className="tag is-danger">
|
||||||
|
{offlineClusters.length}
|
||||||
|
</span>
|
||||||
|
</Indicator>
|
||||||
|
<Indicator label="Hide online clusters">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="switch is-rounded"
|
||||||
|
name="switchRoundedDefault"
|
||||||
|
id="switchRoundedDefault"
|
||||||
|
checked={showOfflineOnly}
|
||||||
|
onChange={handleSwitch}
|
||||||
|
/>
|
||||||
|
<label htmlFor="switchRoundedDefault">
|
||||||
|
</label>
|
||||||
|
</Indicator>
|
||||||
|
</MetricsWrapper>
|
||||||
|
|
||||||
|
{clusterList.map((chunk, idx) => (
|
||||||
|
<div className="columns" key={`dashboard-cluster-list-row-key-${idx}`}>
|
||||||
|
{chunk.map((cluster, idx) => (
|
||||||
|
<ClusterWidget {...cluster} key={`dashboard-cluster-list-item-key-${idx}`}/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClustersWidget;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ClustersWidget from './ClustersWidget';
|
||||||
|
import {
|
||||||
|
getClusterList,
|
||||||
|
getOnlineClusters,
|
||||||
|
getOfflineClusters,
|
||||||
|
} from 'redux/reducers/clusters/selectors';
|
||||||
|
import { RootState } from 'lib/interfaces';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
clusters: getClusterList(state),
|
||||||
|
onlineClusters: getOnlineClusters(state),
|
||||||
|
offlineClusters: getOfflineClusters(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ClustersWidget);
|
17
frontend/src/components/Dashboard/Dashboard.tsx
Normal file
17
frontend/src/components/Dashboard/Dashboard.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
|
import ClustersWidgetContainer from './ClustersWidget/ClustersWidgetContainer';
|
||||||
|
|
||||||
|
const Dashboard: React.FC = () => (
|
||||||
|
<div className="section">
|
||||||
|
<div className="level">
|
||||||
|
<div className="level-item level-left">
|
||||||
|
<Breadcrumb>Dashboard</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ClustersWidgetContainer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Dashboard;
|
|
@ -1,5 +1,5 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Cluster, RootState, FetchStatus } from 'lib/interfaces';
|
import { Cluster, RootState, FetchStatus, ClusterStatus } from 'lib/interfaces';
|
||||||
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
||||||
|
|
||||||
const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
|
const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
|
||||||
|
@ -12,3 +12,17 @@ export const getIsClusterListFetched = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getClusterList = createSelector(clustersState, (clusters) => clusters);
|
export const getClusterList = createSelector(clustersState, (clusters) => clusters);
|
||||||
|
|
||||||
|
export const getOnlineClusters = createSelector(
|
||||||
|
getClusterList,
|
||||||
|
(clusters) => clusters.filter(
|
||||||
|
({ status }) => status === ClusterStatus.Online,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getOfflineClusters = createSelector(
|
||||||
|
getClusterList,
|
||||||
|
(clusters) => clusters.filter(
|
||||||
|
({ status }) => status === ClusterStatus.Offline,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
@ -7,13 +7,21 @@
|
||||||
@import "../../node_modules/bulma/sass/layout/_all.sass";
|
@import "../../node_modules/bulma/sass/layout/_all.sass";
|
||||||
@import "../../node_modules/bulma-switch/src/sass/index.sass";
|
@import "../../node_modules/bulma-switch/src/sass/index.sass";
|
||||||
|
|
||||||
.has-text-overflow-ellipsis {
|
.has {
|
||||||
|
&-text-overflow-ellipsis {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-margin {
|
||||||
|
&-right {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.breadcrumb li {
|
.breadcrumb li {
|
||||||
&.is-active > span {
|
&.is-active > span {
|
||||||
padding: 0 0.75em;
|
padding: 0 0.75em;
|
||||||
|
@ -32,6 +40,30 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
&.is-light {
|
||||||
|
&.is-primary {
|
||||||
|
background-color: #ebfffc;
|
||||||
|
color: #00947e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-danger {
|
||||||
|
background-color: #feecf0;
|
||||||
|
color: #cc0f35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
&.is-hoverable {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.2), 0 0px 0 1px rgba(10, 10, 10, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
to { opacity: 1; }
|
to { opacity: 1; }
|
||||||
|
|
Loading…
Add table
Reference in a new issue