[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 NavConatiner from './Nav/NavConatiner';
|
||||
import PageLoader from './common/PageLoader/PageLoader';
|
||||
import Dashboard from './Dashboard/Dashboard';
|
||||
|
||||
interface AppProps {
|
||||
isClusterListFetched: boolean;
|
||||
|
@ -34,10 +35,7 @@ const App: React.FC<AppProps> = ({
|
|||
<NavConatiner className="Layout__navbar" />
|
||||
{isClusterListFetched ? (
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
Dashboard
|
||||
</Route>
|
||||
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
|
||||
<Route path="/clusters/:clusterId/brokers" component={BrokersContainer} />
|
||||
<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 { Cluster, RootState, FetchStatus } from 'lib/interfaces';
|
||||
import { Cluster, RootState, FetchStatus, ClusterStatus } from 'lib/interfaces';
|
||||
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
||||
|
||||
const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
|
||||
|
@ -12,3 +12,17 @@ export const getIsClusterListFetched = createSelector(
|
|||
);
|
||||
|
||||
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,11 +7,19 @@
|
|||
@import "../../node_modules/bulma/sass/layout/_all.sass";
|
||||
@import "../../node_modules/bulma-switch/src/sass/index.sass";
|
||||
|
||||
.has-text-overflow-ellipsis {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
.has {
|
||||
&-text-overflow-ellipsis {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-margin {
|
||||
&-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb li {
|
||||
|
@ -32,6 +40,30 @@
|
|||
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 {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
|
|
Loading…
Add table
Reference in a new issue