فهرست منبع

Cleanup styling (#365)

Oleg Shur 4 سال پیش
والد
کامیت
d471759b79
27فایلهای تغییر یافته به همراه1033 افزوده شده و 504 حذف شده
  1. 46 1
      kafka-ui-react-app/src/components/App.scss
  2. 55 9
      kafka-ui-react-app/src/components/App.tsx
  3. 5 1
      kafka-ui-react-app/src/components/AppContainer.tsx
  4. 10 5
      kafka-ui-react-app/src/components/Brokers/Brokers.tsx
  5. 26 33
      kafka-ui-react-app/src/components/Brokers/BrokersContainer.ts
  6. 21 19
      kafka-ui-react-app/src/components/Connect/List/List.tsx
  7. 24 22
      kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx
  8. 25 23
      kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx
  9. 1 1
      kafka-ui-react-app/src/components/Nav/Nav.tsx
  10. 0 14
      kafka-ui-react-app/src/components/Nav/NavContainer.ts
  11. 21 0
      kafka-ui-react-app/src/components/Nav/__tests__/Nav.spec.tsx
  12. 48 0
      kafka-ui-react-app/src/components/Nav/__tests__/__snapshots__/Nav.spec.tsx.snap
  13. 16 14
      kafka-ui-react-app/src/components/Schemas/Details/Details.tsx
  14. 18 16
      kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx
  15. 88 76
      kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/Details.spec.tsx.snap
  16. 33 29
      kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/LatestVersionItem.spec.tsx.snap
  17. 19 17
      kafka-ui-react-app/src/components/Schemas/List/List.tsx
  18. 27 25
      kafka-ui-react-app/src/components/Topics/List/List.tsx
  19. 1 1
      kafka-ui-react-app/src/components/Topics/List/ListItem.tsx
  20. 26 24
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx
  21. 45 41
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/__snapshots__/MessagesTable.spec.tsx.snap
  22. 21 19
      kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx
  23. 16 14
      kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/Settings.tsx
  24. 62 44
      kafka-ui-react-app/src/components/__tests__/App.spec.tsx
  25. 377 54
      kafka-ui-react-app/src/components/__tests__/__snapshots__/App.spec.tsx.snap
  26. 1 1
      kafka-ui-react-app/src/components/common/Dashboard/Indicator.tsx
  27. 1 1
      kafka-ui-react-app/src/components/common/Dashboard/__tests__/__snapshots__/Indicator.spec.tsx.snap

+ 46 - 1
kafka-ui-react-app/src/components/App.scss

@@ -17,7 +17,7 @@ $navbar-width: 250px;
     z-index: 20;
   }
 
-  &__navbar {
+  &__sidebar{
     width: $navbar-width;
     display: flex;
     flex-direction: column;
@@ -28,6 +28,19 @@ $navbar-width: 250px;
     bottom: 0;
     padding: 20px 20px;
     overflow-y: scroll;
+    transition: width .25s,opacity .25s,transform .25s,-webkit-transform .25s;
+
+    &Overlay {
+      position: fixed;
+      top: 0;
+      height: 120vh;
+      z-index: 99;
+      display: block;
+      visibility: hidden;
+      opacity: 0;
+      -webkit-transition: all .5s ease;
+      transition: all .5s ease;
+    }
   }
 
   &__alerts {
@@ -47,3 +60,35 @@ $navbar-width: 250px;
 .react-datepicker-popper {
   z-index: 30 !important;
 }
+
+@media screen and (max-width: 1023px) {
+  .Layout {
+    &__container {
+      margin-left: initial;
+      margin-top: 1.5rem;
+    }
+
+    &__sidebar {
+      left: -$navbar-width;
+      z-index: 100;
+    }
+
+    &__alerts {
+      max-width: initial;
+    }
+
+    &--sidebarVisible {
+      .Layout__sidebar {
+        transform: translate3d($navbar-width,0,0);
+
+        &Overlay {
+          background-color: rgba(34,41,47,.5);
+          left: 0;
+          right: 0;
+          opacity: 1;
+          visibility: visible;
+        }
+      }
+    }
+  }
+}

+ 55 - 9
kafka-ui-react-app/src/components/App.tsx

@@ -1,51 +1,97 @@
 import './App.scss';
 import React from 'react';
-import { Switch, Route } from 'react-router-dom';
+import cx from 'classnames';
+import { Cluster } from 'generated-sources';
+import { Switch, Route, useLocation } from 'react-router-dom';
 import { GIT_TAG, GIT_COMMIT } from 'lib/constants';
 import { Alerts } from 'redux/interfaces';
-import NavContainer from './Nav/NavContainer';
+import Nav from './Nav/Nav';
 import PageLoader from './common/PageLoader/PageLoader';
 import Dashboard from './Dashboard/Dashboard';
-import Cluster from './Cluster/Cluster';
+import ClusterPage from './Cluster/Cluster';
 import Version from './Version/Version';
 import Alert from './Alert/Alert';
 
 export interface AppProps {
   isClusterListFetched?: boolean;
   alerts: Alerts;
+  clusters: Cluster[];
   fetchClustersList: () => void;
 }
 
 const App: React.FC<AppProps> = ({
   isClusterListFetched,
   alerts,
+  clusters,
   fetchClustersList,
 }) => {
+  const [isSidebarVisible, setIsSidebarVisible] = React.useState(false);
+
+  const onBurgerClick = React.useCallback(
+    () => setIsSidebarVisible(!isSidebarVisible),
+    [isSidebarVisible]
+  );
+
+  const closeSidebar = React.useCallback(() => setIsSidebarVisible(false), []);
+
+  const location = useLocation();
+
+  React.useEffect(() => {
+    closeSidebar();
+  }, [location]);
+
   React.useEffect(() => {
     fetchClustersList();
   }, [fetchClustersList]);
 
   return (
-    <div className="Layout">
+    <div
+      className={cx('Layout', { 'Layout--sidebarVisible': isSidebarVisible })}
+    >
       <nav
         className="navbar is-fixed-top is-white Layout__header"
         role="navigation"
         aria-label="main navigation"
       >
         <div className="navbar-brand">
+          <div
+            className={cx('navbar-burger', 'ml-0', {
+              'is-active': isSidebarVisible,
+            })}
+            onClick={onBurgerClick}
+            onKeyDown={onBurgerClick}
+            role="button"
+            tabIndex={0}
+          >
+            <span />
+            <span />
+            <span />
+          </div>
+
           <a className="navbar-item title is-5 is-marginless" href="/ui">
             Kafka UI
           </a>
-        </div>
-        <div className="navbar-end">
-          <div className="navbar-item mr-2">
+
+          <div className="navbar-item">
             <Version tag={GIT_TAG} commit={GIT_COMMIT} />
           </div>
         </div>
       </nav>
 
       <main className="Layout__container">
-        <NavContainer className="Layout__navbar" />
+        <div className="Layout__sidebar has-shadow has-background-white">
+          <Nav
+            clusters={clusters}
+            isClusterListFetched={isClusterListFetched}
+          />
+        </div>
+        <div
+          className="Layout__sidebarOverlay is-overlay"
+          onClick={closeSidebar}
+          onKeyDown={closeSidebar}
+          tabIndex={-1}
+          aria-hidden="true"
+        />
         {isClusterListFetched ? (
           <Switch>
             <Route
@@ -53,7 +99,7 @@ const App: React.FC<AppProps> = ({
               path={['/', '/ui', '/ui/clusters']}
               component={Dashboard}
             />
-            <Route path="/ui/clusters/:clusterName" component={Cluster} />
+            <Route path="/ui/clusters/:clusterName" component={ClusterPage} />
           </Switch>
         ) : (
           <PageLoader fullHeight />

+ 5 - 1
kafka-ui-react-app/src/components/AppContainer.tsx

@@ -1,6 +1,9 @@
 import { connect } from 'react-redux';
 import { fetchClustersList } from 'redux/actions';
-import { getIsClusterListFetched } from 'redux/reducers/clusters/selectors';
+import {
+  getClusterList,
+  getIsClusterListFetched,
+} from 'redux/reducers/clusters/selectors';
 import { getAlerts } from 'redux/reducers/alerts/selectors';
 import { RootState } from 'redux/interfaces';
 import App from './App';
@@ -8,6 +11,7 @@ import App from './App';
 const mapStateToProps = (state: RootState) => ({
   isClusterListFetched: getIsClusterListFetched(state),
   alerts: getAlerts(state),
+  clusters: getClusterList(state),
 });
 
 const mapDispatchToProps = {

+ 10 - 5
kafka-ui-react-app/src/components/Brokers/Brokers.tsx

@@ -7,16 +7,15 @@ import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
 import Indicator from 'components/common/Dashboard/Indicator';
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
+import { useParams } from 'react-router';
 
 interface Props extends ClusterStats {
-  clusterName: ClusterName;
   isFetched: boolean;
   fetchClusterStats: (clusterName: ClusterName) => void;
   fetchBrokers: (clusterName: ClusterName) => void;
 }
 
 const Brokers: React.FC<Props> = ({
-  clusterName,
   brokerCount,
   activeControllers,
   zooKeeperStatus,
@@ -29,6 +28,8 @@ const Brokers: React.FC<Props> = ({
   fetchClusterStats,
   fetchBrokers,
 }) => {
+  const { clusterName } = useParams<{ clusterName: ClusterName }>();
+
   React.useEffect(() => {
     fetchClusterStats(clusterName);
     fetchBrokers(clusterName);
@@ -44,9 +45,13 @@ const Brokers: React.FC<Props> = ({
     <div className="section">
       <Breadcrumb>Brokers overview</Breadcrumb>
       <MetricsWrapper title="Uptime">
-        <Indicator label="Total Brokers">{brokerCount}</Indicator>
-        <Indicator label="Active Controllers">{activeControllers}</Indicator>
-        <Indicator label="Zookeeper Status">
+        <Indicator className="is-one-third" label="Total Brokers">
+          {brokerCount}
+        </Indicator>
+        <Indicator className="is-one-third" label="Active Controllers">
+          {activeControllers}
+        </Indicator>
+        <Indicator className="is-one-third" label="Zookeeper Status">
           <span className={cx('tag', zkOnline ? 'is-primary' : 'is-danger')}>
             {zkOnline ? 'Online' : 'Offline'}
           </span>

+ 26 - 33
kafka-ui-react-app/src/components/Brokers/BrokersContainer.ts

@@ -1,43 +1,36 @@
 import { connect } from 'react-redux';
 import { fetchClusterStats, fetchBrokers } from 'redux/actions';
-import * as brokerSelectors from 'redux/reducers/brokers/selectors';
-import { RootState, ClusterName } from 'redux/interfaces';
-import { RouteComponentProps } from 'react-router-dom';
+import { RootState } from 'redux/interfaces';
+import {
+  getIsBrokerListFetched,
+  getBrokerCount,
+  getZooKeeperStatus,
+  getActiveControllers,
+  getOnlinePartitionCount,
+  getOfflinePartitionCount,
+  getInSyncReplicasCount,
+  getOutOfSyncReplicasCount,
+  getUnderReplicatedPartitionCount,
+  getDiskUsage,
+} from 'redux/reducers/brokers/selectors';
 import Brokers from './Brokers';
 
-interface RouteProps {
-  clusterName: ClusterName;
-}
-
-type OwnProps = RouteComponentProps<RouteProps>;
-
-const mapStateToProps = (
-  state: RootState,
-  {
-    match: {
-      params: { clusterName },
-    },
-  }: OwnProps
-) => ({
-  isFetched: brokerSelectors.getIsBrokerListFetched(state),
-  clusterName,
-  brokerCount: brokerSelectors.getBrokerCount(state),
-  zooKeeperStatus: brokerSelectors.getZooKeeperStatus(state),
-  activeControllers: brokerSelectors.getActiveControllers(state),
-  onlinePartitionCount: brokerSelectors.getOnlinePartitionCount(state),
-  offlinePartitionCount: brokerSelectors.getOfflinePartitionCount(state),
-  inSyncReplicasCount: brokerSelectors.getInSyncReplicasCount(state),
-  outOfSyncReplicasCount: brokerSelectors.getOutOfSyncReplicasCount(state),
-  underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(
-    state
-  ),
-  diskUsage: brokerSelectors.getDiskUsage(state),
+const mapStateToProps = (state: RootState) => ({
+  isFetched: getIsBrokerListFetched(state),
+  brokerCount: getBrokerCount(state),
+  zooKeeperStatus: getZooKeeperStatus(state),
+  activeControllers: getActiveControllers(state),
+  onlinePartitionCount: getOnlinePartitionCount(state),
+  offlinePartitionCount: getOfflinePartitionCount(state),
+  inSyncReplicasCount: getInSyncReplicasCount(state),
+  outOfSyncReplicasCount: getOutOfSyncReplicasCount(state),
+  underReplicatedPartitionCount: getUnderReplicatedPartitionCount(state),
+  diskUsage: getDiskUsage(state),
 });
 
 const mapDispatchToProps = {
-  fetchClusterStats: (clusterName: ClusterName) =>
-    fetchClusterStats(clusterName),
-  fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
+  fetchClusterStats,
+  fetchBrokers,
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(Brokers);

+ 21 - 19
kafka-ui-react-app/src/components/Connect/List/List.tsx

@@ -57,27 +57,29 @@ const List: React.FC<ListProps> = ({
         <PageLoader />
       ) : (
         <div className="box">
-          <table className="table is-fullwidth">
-            <thead>
-              <tr>
-                <th>Name</th>
-                <th>Connect</th>
-                <th>Type</th>
-                <th>Plugin</th>
-                <th>Topics</th>
-                <th>Status</th>
-                <th>Tasks</th>
-                <th> </th>
-              </tr>
-            </thead>
-            <tbody>
-              {connectors.length === 0 && (
+          <div className="table-container">
+            <table className="table is-fullwidth">
+              <thead>
                 <tr>
-                  <td colSpan={10}>No connectors found</td>
+                  <th>Name</th>
+                  <th>Connect</th>
+                  <th>Type</th>
+                  <th>Plugin</th>
+                  <th>Topics</th>
+                  <th>Status</th>
+                  <th>Tasks</th>
+                  <th> </th>
                 </tr>
-              )}
-            </tbody>
-          </table>
+              </thead>
+              <tbody>
+                {connectors.length === 0 && (
+                  <tr>
+                    <td colSpan={10}>No connectors found</td>
+                  </tr>
+                )}
+              </tbody>
+            </table>
+          </div>
         </div>
       )}
     </div>

+ 24 - 22
kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx

@@ -54,28 +54,30 @@ const Details: React.FC<Props> = ({
 
       {isFetched ? (
         <div className="box">
-          <table className="table is-striped is-fullwidth">
-            <thead>
-              <tr>
-                <th>Consumer ID</th>
-                <th>Host</th>
-                <th>Topic</th>
-                <th>Partition</th>
-                <th>Messages behind</th>
-                <th>Current offset</th>
-                <th>End offset</th>
-              </tr>
-            </thead>
-            <tbody>
-              {items.map((consumer) => (
-                <ListItem
-                  key={consumer.consumerId}
-                  clusterName={clusterName}
-                  consumer={consumer}
-                />
-              ))}
-            </tbody>
-          </table>
+          <div className="table-container">
+            <table className="table is-striped is-fullwidth">
+              <thead>
+                <tr>
+                  <th>Consumer ID</th>
+                  <th>Host</th>
+                  <th>Topic</th>
+                  <th>Partition</th>
+                  <th>Messages behind</th>
+                  <th>Current offset</th>
+                  <th>End offset</th>
+                </tr>
+              </thead>
+              <tbody>
+                {items.map((consumer) => (
+                  <ListItem
+                    key={consumer.consumerId}
+                    clusterName={clusterName}
+                    consumer={consumer}
+                  />
+                ))}
+              </tbody>
+            </table>
+          </div>
         </div>
       ) : (
         <PageLoader />

+ 25 - 23
kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx

@@ -36,29 +36,31 @@ const List: React.FC<Props> = ({ consumerGroups }) => {
                 />
               </div>
             </div>
-            <table className="table is-striped is-fullwidth is-hoverable">
-              <thead>
-                <tr>
-                  <th>Consumer group ID</th>
-                  <th>Num of consumers</th>
-                  <th>Num of topics</th>
-                </tr>
-              </thead>
-              <tbody>
-                {consumerGroups
-                  .filter(
-                    (consumerGroup) =>
-                      !searchText ||
-                      consumerGroup?.consumerGroupId?.indexOf(searchText) >= 0
-                  )
-                  .map((consumerGroup) => (
-                    <ListItem
-                      key={consumerGroup.consumerGroupId}
-                      consumerGroup={consumerGroup}
-                    />
-                  ))}
-              </tbody>
-            </table>
+            <div className="table-container">
+              <table className="table is-striped is-fullwidth is-hoverable">
+                <thead>
+                  <tr>
+                    <th>Consumer group ID</th>
+                    <th>Num of consumers</th>
+                    <th>Num of topics</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {consumerGroups
+                    .filter(
+                      (consumerGroup) =>
+                        !searchText ||
+                        consumerGroup?.consumerGroupId?.indexOf(searchText) >= 0
+                    )
+                    .map((consumerGroup) => (
+                      <ListItem
+                        key={consumerGroup.consumerGroupId}
+                        consumerGroup={consumerGroup}
+                      />
+                    ))}
+                </tbody>
+              </table>
+            </div>
           </div>
         ) : (
           'No active consumer groups'

+ 1 - 1
kafka-ui-react-app/src/components/Nav/Nav.tsx

@@ -5,7 +5,7 @@ import { Cluster } from 'generated-sources';
 import ClusterMenu from './ClusterMenu';
 
 interface Props {
-  isClusterListFetched: boolean;
+  isClusterListFetched?: boolean;
   clusters: Cluster[];
   className?: string;
 }

+ 0 - 14
kafka-ui-react-app/src/components/Nav/NavContainer.ts

@@ -1,14 +0,0 @@
-import { connect } from 'react-redux';
-import {
-  getIsClusterListFetched,
-  getClusterList,
-} from 'redux/reducers/clusters/selectors';
-import { RootState } from 'redux/interfaces';
-import Nav from './Nav';
-
-const mapStateToProps = (state: RootState) => ({
-  isClusterListFetched: getIsClusterListFetched(state),
-  clusters: getClusterList(state),
-});
-
-export default connect(mapStateToProps)(Nav);

+ 21 - 0
kafka-ui-react-app/src/components/Nav/__tests__/Nav.spec.tsx

@@ -0,0 +1,21 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { onlineClusterPayload } from 'redux/reducers/clusters/__test__/fixtures';
+import Nav from '../Nav';
+
+describe('Nav', () => {
+  it('renders loader', () => {
+    const wrapper = shallow(<Nav clusters={[]} />);
+    expect(wrapper.find('.loader')).toBeTruthy();
+    expect(wrapper.exists('ClusterMenu')).toBeFalsy();
+  });
+
+  it('renders ClusterMenu', () => {
+    const wrapper = shallow(
+      <Nav clusters={[onlineClusterPayload]} isClusterListFetched />
+    );
+    expect(wrapper.exists('.loader')).toBeFalsy();
+    expect(wrapper.exists('ClusterMenu')).toBeTruthy();
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 48 - 0
kafka-ui-react-app/src/components/Nav/__tests__/__snapshots__/Nav.spec.tsx.snap

@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Nav renders ClusterMenu 1`] = `
+<aside
+  className="menu has-shadow has-background-white"
+>
+  <p
+    className="menu-label"
+  >
+    General
+  </p>
+  <ul
+    className="menu-list"
+  >
+    <li>
+      <NavLink
+        activeClassName="is-active"
+        exact={true}
+        title="Dashboard"
+        to="/ui"
+      >
+        Dashboard
+      </NavLink>
+    </li>
+  </ul>
+  <p
+    className="menu-label"
+  >
+    Clusters
+  </p>
+  <ClusterMenu
+    cluster={
+      Object {
+        "brokerCount": 1,
+        "bytesInPerSec": 1.55,
+        "bytesOutPerSec": 9.314,
+        "defaultCluster": true,
+        "features": Array [],
+        "name": "secondLocal",
+        "onlinePartitionCount": 6,
+        "status": "online",
+        "topicCount": 3,
+      }
+    }
+    key="secondLocal"
+  />
+</aside>
+`;

+ 16 - 14
kafka-ui-react-app/src/components/Schemas/Details/Details.tsx

@@ -85,20 +85,22 @@ const Details: React.FC<DetailsProps> = ({
             <LatestVersionItem schema={schema} />
           </div>
           <div className="box">
-            <table className="table is-striped is-fullwidth">
-              <thead>
-                <tr>
-                  <th>Version</th>
-                  <th>ID</th>
-                  <th>Schema</th>
-                </tr>
-              </thead>
-              <tbody>
-                {versions.map((version) => (
-                  <SchemaVersion key={version.id} version={version} />
-                ))}
-              </tbody>
-            </table>
+            <div className="table-container">
+              <table className="table is-striped is-fullwidth">
+                <thead>
+                  <tr>
+                    <th>Version</th>
+                    <th>ID</th>
+                    <th>Schema</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {versions.map((version) => (
+                    <SchemaVersion key={version.id} version={version} />
+                  ))}
+                </tbody>
+              </table>
+            </div>
           </div>
         </>
       ) : (

+ 18 - 16
kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx

@@ -12,22 +12,24 @@ const LatestVersionItem: React.FC<LatestVersionProps> = ({
   <div className="tile is-ancestor mt-1">
     <div className="tile is-4 is-parent">
       <div className="tile is-child">
-        <table className="table is-fullwidth">
-          <tbody>
-            <tr>
-              <td>ID</td>
-              <td>{id}</td>
-            </tr>
-            <tr>
-              <td>Subject</td>
-              <td>{subject}</td>
-            </tr>
-            <tr>
-              <td>Compatibility</td>
-              <td>{compatibilityLevel}</td>
-            </tr>
-          </tbody>
-        </table>
+        <div className="table-container">
+          <table className="table is-fullwidth">
+            <tbody>
+              <tr>
+                <td>ID</td>
+                <td>{id}</td>
+              </tr>
+              <tr>
+                <td>Subject</td>
+                <td>{subject}</td>
+              </tr>
+              <tr>
+                <td>Compatibility</td>
+                <td>{compatibilityLevel}</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
       </div>
     </div>
     <div className="tile is-parent">

+ 88 - 76
kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/Details.spec.tsx.snap

@@ -85,24 +85,28 @@ exports[`Details View Initial state matches snapshot 1`] = `
   <div
     className="box"
   >
-    <table
-      className="table is-striped is-fullwidth"
+    <div
+      className="table-container"
     >
-      <thead>
-        <tr>
-          <th>
-            Version
-          </th>
-          <th>
-            ID
-          </th>
-          <th>
-            Schema
-          </th>
-        </tr>
-      </thead>
-      <tbody />
-    </table>
+      <table
+        className="table is-striped is-fullwidth"
+      >
+        <thead>
+          <tr>
+            <th>
+              Version
+            </th>
+            <th>
+              ID
+            </th>
+            <th>
+              Schema
+            </th>
+          </tr>
+        </thead>
+        <tbody />
+      </table>
+    </div>
   </div>
 </div>
 `;
@@ -216,51 +220,55 @@ exports[`Details View when page with schema versions loaded when schema has vers
   <div
     className="box"
   >
-    <table
-      className="table is-striped is-fullwidth"
+    <div
+      className="table-container"
     >
-      <thead>
-        <tr>
-          <th>
-            Version
-          </th>
-          <th>
-            ID
-          </th>
-          <th>
-            Schema
-          </th>
-        </tr>
-      </thead>
-      <tbody>
-        <SchemaVersion
-          key="1"
-          version={
-            Object {
-              "compatibilityLevel": "BACKWARD",
-              "id": 1,
-              "schema": "{\\"type\\":\\"record\\",\\"name\\":\\"MyRecord1\\",\\"namespace\\":\\"com.mycompany\\",\\"fields\\":[{\\"name\\":\\"id\\",\\"type\\":\\"long\\"}]}",
-              "schemaType": "JSON",
-              "subject": "test",
-              "version": "1",
+      <table
+        className="table is-striped is-fullwidth"
+      >
+        <thead>
+          <tr>
+            <th>
+              Version
+            </th>
+            <th>
+              ID
+            </th>
+            <th>
+              Schema
+            </th>
+          </tr>
+        </thead>
+        <tbody>
+          <SchemaVersion
+            key="1"
+            version={
+              Object {
+                "compatibilityLevel": "BACKWARD",
+                "id": 1,
+                "schema": "{\\"type\\":\\"record\\",\\"name\\":\\"MyRecord1\\",\\"namespace\\":\\"com.mycompany\\",\\"fields\\":[{\\"name\\":\\"id\\",\\"type\\":\\"long\\"}]}",
+                "schemaType": "JSON",
+                "subject": "test",
+                "version": "1",
+              }
             }
-          }
-        />
-        <SchemaVersion
-          key="2"
-          version={
-            Object {
-              "compatibilityLevel": "BACKWARD",
-              "id": 2,
-              "schema": "{\\"type\\":\\"record\\",\\"name\\":\\"MyRecord2\\",\\"namespace\\":\\"com.mycompany\\",\\"fields\\":[{\\"name\\":\\"id\\",\\"type\\":\\"long\\"}]}",
-              "schemaType": "JSON",
-              "subject": "test",
-              "version": "2",
+          />
+          <SchemaVersion
+            key="2"
+            version={
+              Object {
+                "compatibilityLevel": "BACKWARD",
+                "id": 2,
+                "schema": "{\\"type\\":\\"record\\",\\"name\\":\\"MyRecord2\\",\\"namespace\\":\\"com.mycompany\\",\\"fields\\":[{\\"name\\":\\"id\\",\\"type\\":\\"long\\"}]}",
+                "schemaType": "JSON",
+                "subject": "test",
+                "version": "2",
+              }
             }
-          }
-        />
-      </tbody>
-    </table>
+          />
+        </tbody>
+      </table>
+    </div>
   </div>
 </div>
 `;
@@ -350,24 +358,28 @@ exports[`Details View when page with schema versions loaded when versions are em
   <div
     className="box"
   >
-    <table
-      className="table is-striped is-fullwidth"
+    <div
+      className="table-container"
     >
-      <thead>
-        <tr>
-          <th>
-            Version
-          </th>
-          <th>
-            ID
-          </th>
-          <th>
-            Schema
-          </th>
-        </tr>
-      </thead>
-      <tbody />
-    </table>
+      <table
+        className="table is-striped is-fullwidth"
+      >
+        <thead>
+          <tr>
+            <th>
+              Version
+            </th>
+            <th>
+              ID
+            </th>
+            <th>
+              Schema
+            </th>
+          </tr>
+        </thead>
+        <tbody />
+      </table>
+    </div>
   </div>
 </div>
 `;

+ 33 - 29
kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/LatestVersionItem.spec.tsx.snap

@@ -10,36 +10,40 @@ exports[`LatestVersionItem matches snapshot 1`] = `
     <div
       className="tile is-child"
     >
-      <table
-        className="table is-fullwidth"
+      <div
+        className="table-container"
       >
-        <tbody>
-          <tr>
-            <td>
-              ID
-            </td>
-            <td>
-              1
-            </td>
-          </tr>
-          <tr>
-            <td>
-              Subject
-            </td>
-            <td>
-              test
-            </td>
-          </tr>
-          <tr>
-            <td>
-              Compatibility
-            </td>
-            <td>
-              BACKWARD
-            </td>
-          </tr>
-        </tbody>
-      </table>
+        <table
+          className="table is-fullwidth"
+        >
+          <tbody>
+            <tr>
+              <td>
+                ID
+              </td>
+              <td>
+                1
+              </td>
+            </tr>
+            <tr>
+              <td>
+                Subject
+              </td>
+              <td>
+                test
+              </td>
+            </tr>
+            <tr>
+              <td>
+                Compatibility
+              </td>
+              <td>
+                BACKWARD
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
     </div>
   </div>
   <div

+ 19 - 17
kafka-ui-react-app/src/components/Schemas/List/List.tsx

@@ -48,25 +48,27 @@ const List: React.FC<ListProps> = ({
         <PageLoader />
       ) : (
         <div className="box">
-          <table className="table is-striped is-fullwidth">
-            <thead>
-              <tr>
-                <th>Schema Name</th>
-                <th>Version</th>
-                <th>Compatibility</th>
-              </tr>
-            </thead>
-            <tbody>
-              {schemas.length === 0 && (
+          <div className="table-container">
+            <table className="table is-striped is-fullwidth">
+              <thead>
                 <tr>
-                  <td colSpan={10}>No schemas found</td>
+                  <th>Schema Name</th>
+                  <th>Version</th>
+                  <th>Compatibility</th>
                 </tr>
-              )}
-              {schemas.map((subject) => (
-                <ListItem key={subject.id} subject={subject} />
-              ))}
-            </tbody>
-          </table>
+              </thead>
+              <tbody>
+                {schemas.length === 0 && (
+                  <tr>
+                    <td colSpan={10}>No schemas found</td>
+                  </tr>
+                )}
+                {schemas.map((subject) => (
+                  <ListItem key={subject.id} subject={subject} />
+                ))}
+              </tbody>
+            </table>
+          </div>
         </div>
       )}
     </div>

+ 27 - 25
kafka-ui-react-app/src/components/Topics/List/List.tsx

@@ -81,33 +81,35 @@ const List: React.FC<Props> = ({
         <PageLoader />
       ) : (
         <div className="box">
-          <table className="table is-fullwidth">
-            <thead>
-              <tr>
-                <th>Topic Name</th>
-                <th>Total Partitions</th>
-                <th>Out of sync replicas</th>
-                <th>Type</th>
-                <th> </th>
-              </tr>
-            </thead>
-            <tbody>
-              {items.map((topic) => (
-                <ListItem
-                  clusterName={clusterName}
-                  key={topic.name}
-                  topic={topic}
-                  deleteTopic={deleteTopic}
-                />
-              ))}
-              {items.length === 0 && (
+          <div className="table-container">
+            <table className="table is-fullwidth">
+              <thead>
                 <tr>
-                  <td colSpan={10}>No topics found</td>
+                  <th>Topic Name</th>
+                  <th>Total Partitions</th>
+                  <th>Out of sync replicas</th>
+                  <th>Type</th>
+                  <th> </th>
                 </tr>
-              )}
-            </tbody>
-          </table>
-          <Pagination totalPages={totalPages} />
+              </thead>
+              <tbody>
+                {items.map((topic) => (
+                  <ListItem
+                    clusterName={clusterName}
+                    key={topic.name}
+                    topic={topic}
+                    deleteTopic={deleteTopic}
+                  />
+                ))}
+                {items.length === 0 && (
+                  <tr>
+                    <td colSpan={10}>No topics found</td>
+                  </tr>
+                )}
+              </tbody>
+            </table>
+            <Pagination totalPages={totalPages} />
+          </div>
         </div>
       )}
     </div>

+ 1 - 1
kafka-ui-react-app/src/components/Topics/List/ListItem.tsx

@@ -37,7 +37,7 @@ const ListItem: React.FC<ListItemProps> = ({
 
   return (
     <tr>
-      <td>
+      <td className="has-text-overflow-ellipsis">
         <NavLink
           exact
           to={`topics/${name}`}

+ 26 - 24
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx

@@ -15,30 +15,32 @@ const MessagesTable: React.FC<MessagesTableProp> = ({ messages, onNext }) => {
 
   return (
     <>
-      <table className="table is-fullwidth">
-        <thead>
-          <tr>
-            <th>Timestamp</th>
-            <th>Offset</th>
-            <th>Partition</th>
-            <th>Content</th>
-            <th> </th>
-          </tr>
-        </thead>
-        <tbody>
-          {messages.map(
-            ({ partition, offset, timestamp, content }: TopicMessage) => (
-              <MessageItem
-                key={`message-${timestamp.getTime()}-${offset}`}
-                partition={partition}
-                offset={offset}
-                timestamp={timestamp}
-                content={content}
-              />
-            )
-          )}
-        </tbody>
-      </table>
+      <div className="table-container">
+        <table className="table is-fullwidth">
+          <thead>
+            <tr>
+              <th>Timestamp</th>
+              <th>Offset</th>
+              <th>Partition</th>
+              <th>Content</th>
+              <th> </th>
+            </tr>
+          </thead>
+          <tbody>
+            {messages.map(
+              ({ partition, offset, timestamp, content }: TopicMessage) => (
+                <MessageItem
+                  key={`message-${timestamp.getTime()}-${offset}`}
+                  partition={partition}
+                  offset={offset}
+                  timestamp={timestamp}
+                  content={content}
+                />
+              )
+            )}
+          </tbody>
+        </table>
+      </div>
       <div className="columns">
         <div className="column is-full">
           <CustomParamButton

+ 45 - 41
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/__snapshots__/MessagesTable.spec.tsx.snap

@@ -2,49 +2,53 @@
 
 exports[`MessagesTable when topic contains messages matches snapshot 1`] = `
 <Fragment>
-  <table
-    className="table is-fullwidth"
+  <div
+    className="table-container"
   >
-    <thead>
-      <tr>
-        <th>
-          Timestamp
-        </th>
-        <th>
-          Offset
-        </th>
-        <th>
-          Partition
-        </th>
-        <th>
-          Content
-        </th>
-        <th>
-           
-        </th>
-      </tr>
-    </thead>
-    <tbody>
-      <MessageItem
-        content={
-          Object {
-            "foo": "bar",
-            "key": "val",
+    <table
+      className="table is-fullwidth"
+    >
+      <thead>
+        <tr>
+          <th>
+            Timestamp
+          </th>
+          <th>
+            Offset
+          </th>
+          <th>
+            Partition
+          </th>
+          <th>
+            Content
+          </th>
+          <th>
+             
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+        <MessageItem
+          content={
+            Object {
+              "foo": "bar",
+              "key": "val",
+            }
           }
-        }
-        key="message-802310400000-2"
-        offset={2}
-        partition={1}
-        timestamp={1995-06-05T00:00:00.000Z}
-      />
-      <MessageItem
-        key="message-1596585600000-20"
-        offset={20}
-        partition={2}
-        timestamp={2020-08-05T00:00:00.000Z}
-      />
-    </tbody>
-  </table>
+          key="message-802310400000-2"
+          offset={2}
+          partition={1}
+          timestamp={1995-06-05T00:00:00.000Z}
+        />
+        <MessageItem
+          key="message-1596585600000-20"
+          offset={20}
+          partition={2}
+          timestamp={2020-08-05T00:00:00.000Z}
+        />
+      </tbody>
+    </table>
+  </div>
   <div
     className="columns"
   >

+ 21 - 19
kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx

@@ -43,26 +43,28 @@ const Overview: React.FC<Props> = ({
       <Indicator label="Segment count">{segmentCount}</Indicator>
     </MetricsWrapper>
     <div className="box">
-      <table className="table is-striped is-fullwidth">
-        <thead>
-          <tr>
-            <th>Partition ID</th>
-            <th>Broker leader</th>
-            <th>Min offset</th>
-            <th>Max offset</th>
-          </tr>
-        </thead>
-        <tbody>
-          {partitions?.map(({ partition, leader, offsetMin, offsetMax }) => (
-            <tr key={`partition-list-item-key-${partition}`}>
-              <td>{partition}</td>
-              <td>{leader}</td>
-              <td>{offsetMin}</td>
-              <td>{offsetMax}</td>
+      <div className="table-container">
+        <table className="table is-striped is-fullwidth">
+          <thead>
+            <tr>
+              <th>Partition ID</th>
+              <th>Broker leader</th>
+              <th>Min offset</th>
+              <th>Max offset</th>
             </tr>
-          ))}
-        </tbody>
-      </table>
+          </thead>
+          <tbody>
+            {partitions?.map(({ partition, leader, offsetMin, offsetMax }) => (
+              <tr key={`partition-list-item-key-${partition}`}>
+                <td>{partition}</td>
+                <td>{leader}</td>
+                <td>{offsetMin}</td>
+                <td>{offsetMax}</td>
+              </tr>
+            ))}
+          </tbody>
+        </table>
+      </div>
     </div>
   </>
 );

+ 16 - 14
kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/Settings.tsx

@@ -47,20 +47,22 @@ const Settings: React.FC<Props> = ({
 
   return (
     <div className="box">
-      <table className="table is-striped is-fullwidth">
-        <thead>
-          <tr>
-            <th>Key</th>
-            <th>Value</th>
-            <th>Default Value</th>
-          </tr>
-        </thead>
-        <tbody>
-          {config.map((item) => (
-            <ConfigListItem key={item.name} config={item} />
-          ))}
-        </tbody>
-      </table>
+      <div className="table-container">
+        <table className="table is-striped is-fullwidth">
+          <thead>
+            <tr>
+              <th>Key</th>
+              <th>Value</th>
+              <th>Default Value</th>
+            </tr>
+          </thead>
+          <tbody>
+            {config.map((item) => (
+              <ConfigListItem key={item.name} config={item} />
+            ))}
+          </tbody>
+        </table>
+      </div>
     </div>
   );
 };

+ 62 - 44
kafka-ui-react-app/src/components/__tests__/App.spec.tsx

@@ -1,61 +1,79 @@
 import React from 'react';
-import { mount, shallow } from 'enzyme';
+import { mount } from 'enzyme';
 import { Provider } from 'react-redux';
 import { StaticRouter } from 'react-router-dom';
+import { Alert } from 'redux/interfaces';
 import configureStore from 'redux/store/configureStore';
-import App, { AppProps } from '../App';
+import App, { AppProps } from 'components/App';
+import AppContainer from 'components/AppContainer';
 
 const fetchClustersList = jest.fn();
 const store = configureStore();
 
 describe('App', () => {
-  const setupComponent = (props: Partial<AppProps> = {}) => (
-    <App
-      isClusterListFetched
-      alerts={[]}
-      fetchClustersList={fetchClustersList}
-      {...props}
-    />
-  );
-
-  it('handles fetchClustersList', () => {
-    const wrapper = mount(
+  describe('container', () => {
+    it('renders view', () => {
+      const wrapper = mount(
+        <Provider store={store}>
+          <StaticRouter>
+            <AppContainer />
+          </StaticRouter>
+        </Provider>
+      );
+      expect(wrapper.exists('App')).toBeTruthy();
+    });
+  });
+  describe('view', () => {
+    const setupComponent = (props: Partial<AppProps> = {}) => (
       <Provider store={store}>
-        <StaticRouter>{setupComponent()}</StaticRouter>
+        <StaticRouter>
+          <App
+            isClusterListFetched
+            alerts={[]}
+            clusters={[]}
+            fetchClustersList={fetchClustersList}
+            {...props}
+          />
+        </StaticRouter>
       </Provider>
     );
-    expect(wrapper.exists()).toBeTruthy();
-    expect(fetchClustersList).toHaveBeenCalledTimes(1);
-  });
 
-  it('shows PageLoader until cluster list is fetched', () => {
-    const component = shallow(setupComponent({ isClusterListFetched: false }));
-    expect(component.exists('.Layout__container PageLoader')).toBeTruthy();
-    expect(component.exists('.Layout__container Switch')).toBeFalsy();
-    component.setProps({ isClusterListFetched: true });
-    expect(component.exists('.Layout__container PageLoader')).toBeFalsy();
-    expect(component.exists('.Layout__container Switch')).toBeTruthy();
-  });
+    it('handles fetchClustersList', () => {
+      const wrapper = mount(setupComponent());
+      expect(wrapper.exists()).toBeTruthy();
+      expect(fetchClustersList).toHaveBeenCalledTimes(1);
+    });
 
-  it('correctly renders alerts', () => {
-    const alert = {
-      id: 'alert-id',
-      type: 'success',
-      title: 'My Custom Title',
-      message: 'My Custom Message',
-      createdAt: 1234567890,
-    };
-    const wrapper = shallow(setupComponent());
-    expect(wrapper.exists('.Layout__alerts')).toBeTruthy();
-    expect(wrapper.exists('Alert')).toBeFalsy();
-
-    wrapper.setProps({ alerts: [alert] });
-    expect(wrapper.exists('Alert')).toBeTruthy();
-    expect(wrapper.find('Alert').length).toEqual(1);
-  });
+    it('shows PageLoader until cluster list is fetched', () => {
+      let component = mount(setupComponent({ isClusterListFetched: false }));
+      expect(component.exists('.Layout__container PageLoader')).toBeTruthy();
+      expect(component.exists('.Layout__container Switch')).toBeFalsy();
+
+      component = mount(setupComponent({ isClusterListFetched: true }));
+      expect(component.exists('.Layout__container PageLoader')).toBeFalsy();
+      expect(component.exists('.Layout__container Switch')).toBeTruthy();
+    });
+
+    it('correctly renders alerts', () => {
+      const alert: Alert = {
+        id: 'alert-id',
+        type: 'success',
+        title: 'My Custom Title',
+        message: 'My Custom Message',
+        createdAt: 1234567890,
+      };
+      let wrapper = mount(setupComponent());
+      expect(wrapper.exists('.Layout__alerts')).toBeTruthy();
+      expect(wrapper.exists('Alert')).toBeFalsy();
+
+      wrapper = mount(setupComponent({ alerts: [alert] }));
+      expect(wrapper.exists('Alert')).toBeTruthy();
+      expect(wrapper.find('Alert').length).toEqual(1);
+    });
 
-  it('matches snapshot', () => {
-    const component = shallow(setupComponent());
-    expect(component).toMatchSnapshot();
+    it('matches snapshot', () => {
+      const wrapper = mount(setupComponent());
+      expect(wrapper).toMatchSnapshot();
+    });
   });
 });

+ 377 - 54
kafka-ui-react-app/src/components/__tests__/__snapshots__/App.spec.tsx.snap

@@ -1,60 +1,383 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`App matches snapshot 1`] = `
-<div
-  className="Layout"
+exports[`App view matches snapshot 1`] = `
+<Provider
+  store={
+    Object {
+      "dispatch": [Function],
+      "getState": [Function],
+      "replaceReducer": [Function],
+      "subscribe": [Function],
+      Symbol(observable): [Function],
+    }
+  }
 >
-  <nav
-    aria-label="main navigation"
-    className="navbar is-fixed-top is-white Layout__header"
-    role="navigation"
-  >
-    <div
-      className="navbar-brand"
-    >
-      <a
-        className="navbar-item title is-5 is-marginless"
-        href="/ui"
-      >
-        Kafka UI
-      </a>
-    </div>
-    <div
-      className="navbar-end"
+  <StaticRouter>
+    <Router
+      history={
+        Object {
+          "action": "POP",
+          "block": [Function],
+          "createHref": [Function],
+          "go": [Function],
+          "goBack": [Function],
+          "goForward": [Function],
+          "listen": [Function],
+          "location": Object {
+            "hash": "",
+            "pathname": "/",
+            "search": "",
+            "state": undefined,
+          },
+          "push": [Function],
+          "replace": [Function],
+        }
+      }
+      staticContext={Object {}}
     >
-      <div
-        className="navbar-item mr-2"
-      >
-        <Version />
-      </div>
-    </div>
-  </nav>
-  <main
-    className="Layout__container"
-  >
-    <Connect(Nav)
-      className="Layout__navbar"
-    />
-    <Switch>
-      <Route
-        component={[Function]}
-        exact={true}
-        path={
-          Array [
-            "/",
-            "/ui",
-            "/ui/clusters",
-          ]
+      <App
+        alerts={Array []}
+        clusters={Array []}
+        fetchClustersList={
+          [MockFunction] {
+            "calls": Array [
+              Array [],
+            ],
+            "results": Array [
+              Object {
+                "type": "return",
+                "value": undefined,
+              },
+            ],
+          }
         }
-      />
-      <Route
-        component={[Function]}
-        path="/ui/clusters/:clusterName"
-      />
-    </Switch>
-  </main>
-  <div
-    className="Layout__alerts"
-  />
-</div>
+        isClusterListFetched={true}
+      >
+        <div
+          className="Layout"
+        >
+          <nav
+            aria-label="main navigation"
+            className="navbar is-fixed-top is-white Layout__header"
+            role="navigation"
+          >
+            <div
+              className="navbar-brand"
+            >
+              <div
+                className="navbar-burger ml-0"
+                onClick={[Function]}
+                onKeyDown={[Function]}
+                role="button"
+                tabIndex={0}
+              >
+                <span />
+                <span />
+                <span />
+              </div>
+              <a
+                className="navbar-item title is-5 is-marginless"
+                href="/ui"
+              >
+                Kafka UI
+              </a>
+              <div
+                className="navbar-item"
+              >
+                <Version />
+              </div>
+            </div>
+          </nav>
+          <main
+            className="Layout__container"
+          >
+            <div
+              className="Layout__sidebar has-shadow has-background-white"
+            >
+              <Nav
+                clusters={Array []}
+                isClusterListFetched={true}
+              >
+                <aside
+                  className="menu has-shadow has-background-white"
+                >
+                  <p
+                    className="menu-label"
+                  >
+                    General
+                  </p>
+                  <ul
+                    className="menu-list"
+                  >
+                    <li>
+                      <NavLink
+                        activeClassName="is-active"
+                        exact={true}
+                        title="Dashboard"
+                        to="/ui"
+                      >
+                        <Link
+                          aria-current={null}
+                          title="Dashboard"
+                          to={
+                            Object {
+                              "hash": "",
+                              "pathname": "/ui",
+                              "search": "",
+                              "state": null,
+                            }
+                          }
+                        >
+                          <LinkAnchor
+                            aria-current={null}
+                            href="/ui"
+                            navigate={[Function]}
+                            title="Dashboard"
+                          >
+                            <a
+                              aria-current={null}
+                              href="/ui"
+                              onClick={[Function]}
+                              title="Dashboard"
+                            >
+                              Dashboard
+                            </a>
+                          </LinkAnchor>
+                        </Link>
+                      </NavLink>
+                    </li>
+                  </ul>
+                  <p
+                    className="menu-label"
+                  >
+                    Clusters
+                  </p>
+                </aside>
+              </Nav>
+            </div>
+            <div
+              aria-hidden="true"
+              className="Layout__sidebarOverlay is-overlay"
+              onClick={[Function]}
+              onKeyDown={[Function]}
+              tabIndex={-1}
+            />
+            <Switch>
+              <Route
+                component={[Function]}
+                computedMatch={
+                  Object {
+                    "isExact": true,
+                    "params": Object {},
+                    "path": "/",
+                    "url": "/",
+                  }
+                }
+                exact={true}
+                location={
+                  Object {
+                    "hash": "",
+                    "pathname": "/",
+                    "search": "",
+                    "state": undefined,
+                  }
+                }
+                path={
+                  Array [
+                    "/",
+                    "/ui",
+                    "/ui/clusters",
+                  ]
+                }
+              >
+                <Dashboard
+                  history={
+                    Object {
+                      "action": "POP",
+                      "block": [Function],
+                      "createHref": [Function],
+                      "go": [Function],
+                      "goBack": [Function],
+                      "goForward": [Function],
+                      "listen": [Function],
+                      "location": Object {
+                        "hash": "",
+                        "pathname": "/",
+                        "search": "",
+                        "state": undefined,
+                      },
+                      "push": [Function],
+                      "replace": [Function],
+                    }
+                  }
+                  location={
+                    Object {
+                      "hash": "",
+                      "pathname": "/",
+                      "search": "",
+                      "state": undefined,
+                    }
+                  }
+                  match={
+                    Object {
+                      "isExact": true,
+                      "params": Object {},
+                      "path": "/",
+                      "url": "/",
+                    }
+                  }
+                  staticContext={Object {}}
+                >
+                  <div
+                    className="section"
+                  >
+                    <div
+                      className="level"
+                    >
+                      <div
+                        className="level-item level-left"
+                      >
+                        <Breadcrumb>
+                          <nav
+                            aria-label="breadcrumbs"
+                            className="breadcrumb"
+                          >
+                            <ul>
+                              <li
+                                className="is-active"
+                              >
+                                <span
+                                  className=""
+                                >
+                                  Dashboard
+                                </span>
+                              </li>
+                            </ul>
+                          </nav>
+                        </Breadcrumb>
+                      </div>
+                    </div>
+                    <Connect(ClustersWidget)>
+                      <ClustersWidget
+                        clusters={Array []}
+                        dispatch={[Function]}
+                        offlineClusters={Array []}
+                        onlineClusters={Array []}
+                      >
+                        <div>
+                          <h5
+                            className="title is-5"
+                          >
+                            Clusters
+                          </h5>
+                          <MetricsWrapper>
+                            <div
+                              className="box"
+                            >
+                              <div
+                                className="level"
+                              >
+                                <Indicator
+                                  label="Online Clusters"
+                                >
+                                  <div
+                                    className="level-item"
+                                  >
+                                    <div
+                                      title="Online Clusters"
+                                    >
+                                      <p
+                                        className="heading"
+                                      >
+                                        Online Clusters
+                                      </p>
+                                      <p
+                                        className="title has-text-centered"
+                                      >
+                                        <span
+                                          className="tag is-primary"
+                                        >
+                                          0
+                                        </span>
+                                      </p>
+                                    </div>
+                                  </div>
+                                </Indicator>
+                                <Indicator
+                                  label="Offline Clusters"
+                                >
+                                  <div
+                                    className="level-item"
+                                  >
+                                    <div
+                                      title="Offline Clusters"
+                                    >
+                                      <p
+                                        className="heading"
+                                      >
+                                        Offline Clusters
+                                      </p>
+                                      <p
+                                        className="title has-text-centered"
+                                      >
+                                        <span
+                                          className="tag is-danger"
+                                        >
+                                          0
+                                        </span>
+                                      </p>
+                                    </div>
+                                  </div>
+                                </Indicator>
+                                <Indicator
+                                  label="Hide online clusters"
+                                >
+                                  <div
+                                    className="level-item"
+                                  >
+                                    <div
+                                      title="Hide online clusters"
+                                    >
+                                      <p
+                                        className="heading"
+                                      >
+                                        Hide online clusters
+                                      </p>
+                                      <p
+                                        className="title has-text-centered"
+                                      >
+                                        <input
+                                          checked={false}
+                                          className="switch is-rounded"
+                                          id="switchRoundedDefault"
+                                          name="switchRoundedDefault"
+                                          onChange={[Function]}
+                                          type="checkbox"
+                                        />
+                                        <label
+                                          htmlFor="switchRoundedDefault"
+                                        />
+                                      </p>
+                                    </div>
+                                  </div>
+                                </Indicator>
+                              </div>
+                            </div>
+                          </MetricsWrapper>
+                        </div>
+                      </ClustersWidget>
+                    </Connect(ClustersWidget)>
+                  </div>
+                </Dashboard>
+              </Route>
+            </Switch>
+          </main>
+          <div
+            className="Layout__alerts"
+          />
+        </div>
+      </App>
+    </Router>
+  </StaticRouter>
+</Provider>
 `;

+ 1 - 1
kafka-ui-react-app/src/components/common/Dashboard/Indicator.tsx

@@ -16,7 +16,7 @@ const Indicator: React.FC<Props> = ({
   children,
 }) => {
   return (
-    <div className={cx('level-item', 'level-left', className)}>
+    <div className={cx('level-item', className)}>
       <div title={title || label}>
         <p className="heading">{label}</p>
         <p className="title has-text-centered">

+ 1 - 1
kafka-ui-react-app/src/components/common/Dashboard/__tests__/__snapshots__/Indicator.spec.tsx.snap

@@ -6,7 +6,7 @@ exports[`Indicator matches the snapshot 1`] = `
   title="title"
 >
   <div
-    className="level-item level-left"
+    className="level-item"
   >
     <div
       title="title"