diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx index 752ee4bcff..8900761c81 100644 --- a/frontend/src/components/App.tsx +++ b/frontend/src/components/App.tsx @@ -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 = ({ {isClusterListFetched ? ( - - Dashboard - - + diff --git a/frontend/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx b/frontend/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx new file mode 100644 index 0000000000..1e0847daeb --- /dev/null +++ b/frontend/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx @@ -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 = ({ + id, + name, + status, + topicCount, + brokerCount, + bytesInPerSec, + bytesOutPerSec, + onlinePartitionCount, +}) => ( + +
+
+
+ {status} +
+ {name} +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Brokers{brokerCount}
Partitions{onlinePartitionCount}
Topics{topicCount}
Production{formatBytes(bytesInPerSec)}
Consumption{formatBytes(bytesOutPerSec)}
+
+
+); + +export default ClusterWidget; diff --git a/frontend/src/components/Dashboard/ClustersWidget/ClustersWidget.tsx b/frontend/src/components/Dashboard/ClustersWidget/ClustersWidget.tsx new file mode 100644 index 0000000000..0195ed92b5 --- /dev/null +++ b/frontend/src/components/Dashboard/ClustersWidget/ClustersWidget.tsx @@ -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 = ({ + clusters, + onlineClusters, + offlineClusters, +}) => { + const [showOfflineOnly, setShowOfflineOnly] = React.useState(false); + + const clusterList: Array = React.useMemo(() => { + let list = clusters; + + if (showOfflineOnly) { + list = offlineClusters; + } + + return chunk(list, 2); + }, + [clusters, offlineClusters, showOfflineOnly], + ); + + const handleSwitch = () => setShowOfflineOnly(!showOfflineOnly); + + return ( +
+
+ Clusters +
+ + + + + {onlineClusters.length} + + + + + {offlineClusters.length} + + + + + + + + + {clusterList.map((chunk, idx) => ( +
+ {chunk.map((cluster, idx) => ( + + ))} +
+ ))} +
+ ) +}; + +export default ClustersWidget; diff --git a/frontend/src/components/Dashboard/ClustersWidget/ClustersWidgetContainer.ts b/frontend/src/components/Dashboard/ClustersWidget/ClustersWidgetContainer.ts new file mode 100644 index 0000000000..7111ce4fc7 --- /dev/null +++ b/frontend/src/components/Dashboard/ClustersWidget/ClustersWidgetContainer.ts @@ -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); diff --git a/frontend/src/components/Dashboard/Dashboard.tsx b/frontend/src/components/Dashboard/Dashboard.tsx new file mode 100644 index 0000000000..e5d89b8c83 --- /dev/null +++ b/frontend/src/components/Dashboard/Dashboard.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb'; +import ClustersWidgetContainer from './ClustersWidget/ClustersWidgetContainer'; + +const Dashboard: React.FC = () => ( +
+
+
+ Dashboard +
+
+ + +
+); + +export default Dashboard; diff --git a/frontend/src/redux/reducers/clusters/selectors.ts b/frontend/src/redux/reducers/clusters/selectors.ts index 5f7080cccb..2df487f575 100644 --- a/frontend/src/redux/reducers/clusters/selectors.ts +++ b/frontend/src/redux/reducers/clusters/selectors.ts @@ -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, + ), +); diff --git a/frontend/src/theme/bulma_overrides.scss b/frontend/src/theme/bulma_overrides.scss index db34f13ba5..407d9b7b7f 100644 --- a/frontend/src/theme/bulma_overrides.scss +++ b/frontend/src/theme/bulma_overrides.scss @@ -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; }