You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ne...@apache.org on 2020/10/29 22:12:48 UTC

[incubator-pinot] branch master updated: Adding operation in table details page (#6198)

This is an automated email from the ASF dual-hosted git repository.

nehapawar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new ce43288  Adding operation in table details page (#6198)
ce43288 is described below

commit ce4328896e93740dc9f000e56584d27125c0df6b
Author: Sanket Shah <sh...@users.noreply.github.com>
AuthorDate: Fri Oct 30 03:42:22 2020 +0530

    Adding operation in table details page (#6198)
---
 pinot-controller/src/main/resources/app/App.tsx    |  45 +--
 .../main/resources/app/components/CustomButton.tsx |  33 +-
 .../main/resources/app/components/CustomDialog.tsx |  10 +-
 .../app/components/CustomNotification.tsx          |  59 ++--
 .../Homepage/Operations/EditConfigOp.tsx           |   2 +-
 .../Homepage/Operations/RebalanceServerTableOp.tsx | 166 +++++++++
 ...ditConfigOp.tsx => RebalanceServerTenantOp.tsx} |  35 +-
 .../Homepage/Operations/ReloadStatusOp.tsx         | 125 +++++++
 .../NotificationContext.tsx}                       |  40 +--
 .../Notification/NotificationContextProvider.tsx   |  61 ++++
 .../resources/app/components/SimpleAccordion.tsx   |  33 +-
 .../src/main/resources/app/components/Table.tsx    |  17 +-
 .../app/components/Zookeeper/TreeDirectory.tsx     |  38 +-
 .../src/main/resources/app/interfaces/types.d.ts   |  10 +-
 .../main/resources/app/pages/InstanceDetails.tsx   |  41 +--
 .../src/main/resources/app/pages/Query.tsx         |   5 +-
 .../main/resources/app/pages/SegmentDetails.tsx    |  47 ++-
 .../main/resources/app/pages/TablesListingPage.tsx |  26 +-
 .../src/main/resources/app/pages/TenantDetails.tsx | 390 ++++++++++++++++++++-
 .../src/main/resources/app/pages/Tenants.tsx       |  56 ++-
 .../src/main/resources/app/requests/index.ts       |  29 +-
 .../main/resources/app/utils/PinotMethodUtils.ts   | 241 ++++++++-----
 .../src/main/resources/app/utils/Utils.tsx         |  46 ++-
 23 files changed, 1218 insertions(+), 337 deletions(-)

diff --git a/pinot-controller/src/main/resources/app/App.tsx b/pinot-controller/src/main/resources/app/App.tsx
index 63e3c85..27680cb 100644
--- a/pinot-controller/src/main/resources/app/App.tsx
+++ b/pinot-controller/src/main/resources/app/App.tsx
@@ -25,6 +25,8 @@ import theme from './theme';
 import Layout from './components/Layout';
 import RouterData from './router';
 import PinotMethodUtils from './utils/PinotMethodUtils';
+import CustomNotification from './components/CustomNotification';
+import { NotificationContextProvider } from './components/Notification/NotificationContextProvider';
 
 const App = () => {
   const fetchClusterName = async () => {
@@ -36,26 +38,29 @@ const App = () => {
   }, []);
   return (
     <MuiThemeProvider theme={theme}>
-      <Router>
-        <Switch>
-          {RouterData.map(({ path, Component }, key) => (
-            <Route
-              exact
-              path={path}
-              key={key}
-              render={props => {
-                return (
-                  <div className="p-8">
-                    <Layout {...props}>
-                      <Component {...props} />
-                    </Layout>
-                  </div>
-                );
-              }}
-            />
-          ))}
-        </Switch>
-      </Router>
+      <NotificationContextProvider>
+        <CustomNotification />
+        <Router>
+          <Switch>
+            {RouterData.map(({ path, Component }, key) => (
+              <Route
+                exact
+                path={path}
+                key={key}
+                render={props => {
+                  return (
+                    <div className="p-8">
+                      <Layout {...props}>
+                        <Component {...props} />
+                      </Layout>
+                    </div>
+                  );
+                }}
+              />
+            ))}
+          </Switch>
+        </Router>
+      </NotificationContextProvider>
     </MuiThemeProvider>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/components/CustomButton.tsx b/pinot-controller/src/main/resources/app/components/CustomButton.tsx
index 6461cc8..db62618 100644
--- a/pinot-controller/src/main/resources/app/components/CustomButton.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomButton.tsx
@@ -18,7 +18,7 @@
  */
 
 import React from 'react';
-import { Button, makeStyles } from '@material-ui/core';
+import { Button, makeStyles, Tooltip } from '@material-ui/core';
 
 const useStyles = makeStyles((theme) => ({
   button: {
@@ -29,24 +29,33 @@ const useStyles = makeStyles((theme) => ({
 
 type Props = {
   children: any;
-  onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+  onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+  isDisabled?: boolean,
+  tooltipTitle?: string,
+  enableTooltip?: boolean
 };
 
 export default function CustomButton({
   children,
-  onClick
+  isDisabled,
+  onClick,
+  tooltipTitle = '',
+  enableTooltip = false
 }: Props) {
   const classes = useStyles();
 
   return (
-    <Button
-      variant="contained"
-      color="primary"
-      className={classes.button}
-      size="small"
-      onClick={onClick}
-    >
-      {children}
-    </Button>
+    <Tooltip title={tooltipTitle} disableHoverListener={!enableTooltip} placement="top" arrow>
+      <Button
+        variant="contained"
+        color="primary"
+        className={classes.button}
+        size="small"
+        onClick={onClick}
+        disabled={isDisabled}
+      >
+        {children}
+      </Button>
+    </Tooltip>
   );
 }
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
index cefcac9..8bfe279 100644
--- a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
@@ -45,13 +45,14 @@ const CancelButton = withStyles(() => ({
 type Props = {
   open: boolean,
   handleClose: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
-  handleSave: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+  handleSave?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
   title: string,
   children: any,
   btnCancelText?: string,
   btnOkText?: string,
   showCancelBtn?: boolean,
-  showOkBtn?: boolean
+  showOkBtn?: boolean,
+  largeSize?: boolean
 };
 
 export default function CustomDialog({
@@ -63,13 +64,14 @@ export default function CustomDialog({
   btnCancelText,
   btnOkText,
   showCancelBtn = true,
-  showOkBtn = true
+  showOkBtn = true,
+  largeSize = false
 }: Props) {
 
   const classes = useStyles();
 
   return (
-    <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" className={classes.root}>
+    <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" className={classes.root} maxWidth={largeSize ? "lg" : false} fullWidth={largeSize}>
       <DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
       {children}
       <DialogActions>
diff --git a/pinot-controller/src/main/resources/app/components/CustomNotification.tsx b/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
index 6668cb7..8b35063 100644
--- a/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
@@ -20,49 +20,34 @@
 import React, {  } from 'react';
 import { Snackbar } from '@material-ui/core';
 import MuiAlert from '@material-ui/lab/Alert';
-
+import { NotificationContext } from './Notification/NotificationContext';
 
 const Alert = (props) => {
   return <MuiAlert elevation={6} variant="filled" {...props} />;
 };
 
-type Props = {
-  type: string,
-  message: string,
-  show: boolean,
-  hide: Function
-};
-
-const CustomNotification = ({
-  type, message, show, hide
-}: Props) => {
-
-  const [notificationData, setNotificationData] = React.useState({type, message});
-  const [showNotification, setShowNotification] = React.useState(show);
-
-  React.useEffect(()=>{
-    setShowNotification(show);
-  }, [show]);
-
-  React.useEffect(()=>{
-    setNotificationData({type, message});
-  }, [type, message]);
-
-  const hideNotification = () => {
-    hide();
-    setShowNotification(false);
-  };
-
+const CustomNotification = () => {
   return (
-    <Snackbar
-      anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
-      open={showNotification}
-      onClose={hideNotification}
-      key="notification"
-      autoHideDuration={3000}
-    >
-      <Alert severity={notificationData.type}>{notificationData.message}</Alert>
-    </Snackbar>
+    <NotificationContext.Consumer>
+      {context => 
+        <Snackbar
+          anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
+          open={context && context.show}
+          onClose={()=>{
+            context.dispatch({type: '', message: "", show:false});
+            context.hide && context.hide()
+          }}
+          autoHideDuration={10000}
+        >
+          <Alert
+            onClose={context.hide && context.hide()}
+            severity={context && context.type}
+          >
+            {context && context.message}
+          </Alert>
+        </Snackbar>
+        }
+    </NotificationContext.Consumer>
   );
 };
 
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
index 4a32e0e..c856ce9 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
@@ -30,7 +30,7 @@ type Props = {
   handleConfigChange: (value: string) => void,
 };
 
-export default function CustomModal({
+export default function EditConfigOp({
   showModal,
   hideModal,
   saveConfig,
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx
new file mode 100644
index 0000000..fb882a1
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx
@@ -0,0 +1,166 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { DialogContent, DialogContentText, FormControl, FormControlLabel, Grid, Input, InputLabel, Switch} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import PinotMethodUtils from '../../../utils/PinotMethodUtils';
+import CustomCodemirror from '../../CustomCodemirror';
+
+type Props = {
+  tableType: string,
+  tableName: string,
+  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+};
+
+export default function RebalanceServerTableOp({
+  hideModal,
+  tableName,
+  tableType
+}: Props) {
+  const [rebalanceResponse, setRebalanceResponse] = React.useState(null)
+  const [dryRun, setDryRun] = React.useState(false);
+  const [reassignInstances, setReassignInstances] = React.useState(false);
+  const [includeConsuming, setIncludeConsuming] = React.useState(false);
+  const [bootstrap, setBootstrap] = React.useState(false);
+  const [downtime, setDowntime] = React.useState(false);
+  const [minAvailableReplicas, setMinAvailableReplicas] = React.useState("1");
+  const [bestEfforts, setBestEfforts] = React.useState(false);
+
+  const getData = () => {
+    return {
+      type: tableType,
+      dryRun, reassignInstances, includeConsuming, bootstrap, downtime, bestEfforts,
+      minAvailableReplicas: parseInt(minAvailableReplicas, 10)
+    }
+  };
+
+  const handleSave = async (event) => {
+    const data = getData();
+    const response = await PinotMethodUtils.rebalanceServersForTableOp(tableName, data);
+    setRebalanceResponse(response);
+  };
+
+  return (
+    <Dialog
+      open={true}
+      handleClose={hideModal}
+      title="Rebalance Server"
+      handleSave={handleSave}
+      showOkBtn={!rebalanceResponse}
+    >
+      <DialogContent>
+        {!rebalanceResponse ?
+          <Grid container spacing={2}>
+            <Grid item xs={6}>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={dryRun}
+                    onChange={() => setDryRun(!dryRun)}
+                    name="dryRun"
+                    color="primary"
+                  />
+                }
+                label="Dry Run"
+              />
+              <br/>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={includeConsuming}
+                    onChange={() => setIncludeConsuming(!includeConsuming)} 
+                    name="includeConsuming"
+                    color="primary"
+                  />
+                }
+                label="Include Consuming"
+              />
+              <br/>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={downtime}
+                    onChange={() => setDowntime(!downtime)} 
+                    name="downtime"
+                    color="primary"
+                  />
+                }
+                label="Downtime"
+              />
+            </Grid>
+            <Grid item xs={6}>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={reassignInstances}
+                    onChange={() => setReassignInstances(!reassignInstances)} 
+                    name="reassignInstances"
+                    color="primary"
+                  />
+                }
+                label="Reassign Instances"
+              />
+              <br/>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={bootstrap}
+                    onChange={() => setBootstrap(!bootstrap)}
+                    name="bootstrap"
+                    color="primary"
+                  />
+                }
+                label="Bootstrap"
+              />
+              <br/>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={bestEfforts}
+                    onChange={() => setBestEfforts(!bestEfforts)} 
+                    name="bestEfforts"
+                    color="primary"
+                  />
+                }
+                label="Best Efforts"
+              />
+            </Grid>
+            <Grid item xs={6}>
+              <FormControl fullWidth={true}>
+                <InputLabel htmlFor="my-input">Minimum Available Replicas</InputLabel>
+                <Input id="my-input" type="number" value={minAvailableReplicas} onChange={(e)=> setMinAvailableReplicas(e.target.value)}/>
+              </FormControl>
+            </Grid>
+          </Grid>
+        : 
+          <React.Fragment>
+            <DialogContentText>
+              Operation Status:
+            </DialogContentText>
+            <CustomCodemirror
+              data={rebalanceResponse}
+              isEditable={false}
+            />
+          </React.Fragment>
+        }
+      </DialogContent>
+    </Dialog>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTenantOp.tsx
similarity index 65%
copy from pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
copy to pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTenantOp.tsx
index 4a32e0e..3810647 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTenantOp.tsx
@@ -18,41 +18,36 @@
  */
 
 import React from 'react';
-import { DialogContent} from '@material-ui/core';
+import { DialogContent, DialogContentText, makeStyles } from '@material-ui/core';
 import Dialog from '../../CustomDialog';
-import CustomCodemirror from '../../CustomCodemirror';
+
+const useStyles = makeStyles((theme) => ({
+  
+}));
 
 type Props = {
   showModal: boolean,
-  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
-  saveConfig: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
-  config: string,
-  handleConfigChange: (value: string) => void,
+  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
 };
 
-export default function CustomModal({
+export default function RebalanceServerTenantOp({
   showModal,
-  hideModal,
-  saveConfig,
-  handleConfigChange,
-  config
+  hideModal
 }: Props) {
+  const classes = useStyles();
 
   return (
     <Dialog
       open={showModal}
       handleClose={hideModal}
-      title="Edit Config"
-      handleSave={saveConfig}
+      handleSave={(e)=>{console.log('save clicked');}}
+      title="Rebalance Server Tenant"
     >
       <DialogContent>
-        <CustomCodemirror
-          data={config}
-          isEditable={true}
-          returnCodemirrorValue={(newValue)=>{
-            handleConfigChange(newValue);
-          }}
-        />
+        <DialogContentText>
+          To subscribe to this website, please enter your email address here. We will send updates
+          occasionally.
+        </DialogContentText>
       </DialogContent>
     </Dialog>
   );
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/ReloadStatusOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/ReloadStatusOp.tsx
new file mode 100644
index 0000000..5d2c465
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/ReloadStatusOp.tsx
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { CircularProgress, createStyles, DialogContent, makeStyles, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Theme, withStyles} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import CloseIcon from '@material-ui/icons/Close';
+import CheckIcon from '@material-ui/icons/Check';
+import { red } from '@material-ui/core/colors';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      textAlign: 'center'
+    },
+    container: {
+      maxHeight: 540,
+    },
+    greenColor: {
+      color: theme.palette.success.main
+    },
+    redColor: {
+      color: theme.palette.error.main
+    },
+  })
+);
+
+const StyledTableCell = withStyles((theme: Theme) =>
+  createStyles({
+    head: {
+      backgroundColor: '#ecf3fe',
+      color: theme.palette.primary.main,
+      fontWeight: 600
+    }
+  }),
+)(TableCell);
+
+type Props = {
+  data: any,
+  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+};
+
+export default function ReloadStatusOp({
+  data,
+  hideModal
+}: Props) {
+  const classes = useStyles();
+  const segmentNames = data && Object.keys(data);
+  const indexes = data && data[segmentNames[0]]?.indexes;
+  const indexesKeys = indexes && Object.keys(indexes);
+  const indexObjKeys = indexes && indexes[indexesKeys[0]] && Object.keys(indexes[indexesKeys[0]]);
+  return (
+    <Dialog
+      open={true}
+      handleClose={hideModal}
+      title="Reload Status"
+      showOkBtn={false}
+      largeSize={true}
+    >
+      {!data ?
+        <div className={classes.root}><CircularProgress/></div>
+      :
+        <DialogContent>
+          <TableContainer component={Paper} className={classes.container}>
+            <Table stickyHeader aria-label="sticky table" size="small">
+              <TableHead>
+                <TableRow>
+                  <StyledTableCell></StyledTableCell>
+                  {indexObjKeys.map((o, i)=>{
+                    return (
+                      <StyledTableCell key={i} align="right">{o}</StyledTableCell>
+                    );
+                  })}
+                </TableRow>
+              </TableHead>
+              <TableBody>
+                {indexesKeys.map((indexName, i) => {
+                  const indexObj = indexes[indexName];
+                  return (
+                    <TableRow key={i}>
+                      <StyledTableCell component="th" scope="row">
+                        {indexName}
+                      </StyledTableCell>
+                      {indexObjKeys.map((o, i)=>{
+                        let iconElement = null;
+                        if(indexObj[o].toLowerCase() === 'yes'){
+                          iconElement = <CheckIcon className={classes.greenColor}/>;
+                        } else if(indexObj[o].toLowerCase() === 'no'){
+                          iconElement = <CloseIcon className={classes.redColor}/>;
+                        } else {
+                          iconElement = indexObj[o];
+                        }
+                        return (
+                          <StyledTableCell align="center" key={i}>
+                            {iconElement}
+                          </StyledTableCell>
+                        )
+                      })}
+                    </TableRow>
+                  )
+                })}
+              </TableBody>
+            </Table>
+          </TableContainer>
+        </DialogContent>
+      }
+    </Dialog>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/CustomButton.tsx b/pinot-controller/src/main/resources/app/components/Notification/NotificationContext.tsx
similarity index 57%
copy from pinot-controller/src/main/resources/app/components/CustomButton.tsx
copy to pinot-controller/src/main/resources/app/components/Notification/NotificationContext.tsx
index 6461cc8..46ec205 100644
--- a/pinot-controller/src/main/resources/app/components/CustomButton.tsx
+++ b/pinot-controller/src/main/resources/app/components/Notification/NotificationContext.tsx
@@ -17,36 +17,14 @@
  * under the License.
  */
 
-import React from 'react';
-import { Button, makeStyles } from '@material-ui/core';
+import * as React from 'react';
 
-const useStyles = makeStyles((theme) => ({
-  button: {
-    margin: theme.spacing(1),
-    textTransform: 'none'
-  }
-}));
+export interface NotificationContextInterface {
+  type: string,
+  message: string,
+  show: boolean,
+  hide: Function,
+  dispatch: React.Dispatch<any>;
+}
 
-type Props = {
-  children: any;
-  onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
-};
-
-export default function CustomButton({
-  children,
-  onClick
-}: Props) {
-  const classes = useStyles();
-
-  return (
-    <Button
-      variant="contained"
-      color="primary"
-      className={classes.button}
-      size="small"
-      onClick={onClick}
-    >
-      {children}
-    </Button>
-  );
-}
\ No newline at end of file
+export const NotificationContext = React.createContext<NotificationContextInterface>(null);
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Notification/NotificationContextProvider.tsx b/pinot-controller/src/main/resources/app/components/Notification/NotificationContextProvider.tsx
new file mode 100644
index 0000000..c7ff2c9
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Notification/NotificationContextProvider.tsx
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as React from 'react';
+import { NotificationContext } from "./NotificationContext";
+import { useReducer } from "react";
+
+
+type NotificationContextReducers = {
+  type: string,
+  message: string,
+  show: boolean,
+  hide: Function
+};
+
+const NotificationContextValue : NotificationContextReducers = {
+  type: "",
+  message: "",
+  show: false,
+  hide: ()=>{}
+};
+
+
+const notificationReducer = (state) => {
+  return state;
+}
+
+const mainReducer = ({ type, message, show, hide }, action) => ({
+  type: notificationReducer(action.type),
+  message: notificationReducer(action.message),
+  show: notificationReducer(action.show),
+  hide: notificationReducer(action.hide)
+});
+  
+
+const NotificationContextProvider: React.FC  = (props) =>{
+  const [state, dispatch] = useReducer(mainReducer, NotificationContextValue);
+  return(
+    <NotificationContext.Provider value={{...state,dispatch}}>
+      {props.children}
+    </NotificationContext.Provider>
+  )
+ }
+
+ export { NotificationContextProvider };
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
index 16e51f4..c8e10d8 100644
--- a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
+++ b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
@@ -25,6 +25,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
 import Typography from '@material-ui/core/Typography';
 import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
 import SearchBar from './SearchBar';
+import { FormControlLabel, Switch } from '@material-ui/core';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -33,7 +34,8 @@ const useStyles = makeStyles((theme: Theme) =>
       borderBottom: '1px #BDCCD9 solid',
       minHeight: '0 !important',
       '& .MuiAccordionSummary-content.Mui-expanded':{
-        margin: 0
+        margin: 0,
+        alignItems: 'center',
       }
     },
     heading: {
@@ -45,6 +47,11 @@ const useStyles = makeStyles((theme: Theme) =>
     details: {
       flexDirection: 'column',
       padding: '0'
+    },
+    formControl: {
+      marginRight: 0,
+      marginLeft: 'auto',
+      zoom: 0.85
     }
   }),
 );
@@ -54,8 +61,13 @@ type Props = {
   showSearchBox: boolean;
   searchValue?: string;
   handleSearch?: Function;
-  recordCount?: number
+  recordCount?: number;
   children: any;
+  accordionToggleObject?: {
+    toggleChangeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void;
+    toggleName: string;
+    toggleValue: boolean;
+  }
 };
 
 export default function SimpleAccordion({
@@ -64,7 +76,8 @@ export default function SimpleAccordion({
   searchValue,
   handleSearch,
   recordCount,
-  children
+  children,
+  accordionToggleObject
 }: Props) {
   const classes = useStyles();
 
@@ -79,6 +92,20 @@ export default function SimpleAccordion({
         className={classes.root}
       >
         <Typography className={classes.heading}>{`${headerTitle.toUpperCase()} ${recordCount !== undefined ? ` - (${recordCount})` : ''}`}</Typography>
+        {accordionToggleObject &&
+          <FormControlLabel
+            className={classes.formControl}
+            control={
+              <Switch
+                checked={accordionToggleObject.toggleValue}
+                onChange={accordionToggleObject.toggleChangeHandler} 
+                name={accordionToggleObject.toggleName}
+                color="primary"
+              />
+            }
+            label={accordionToggleObject.toggleName}
+          />
+        }
       </AccordionSummary>
       <AccordionDetails className={classes.details}>
         {showSearchBox ?
diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx b/pinot-controller/src/main/resources/app/components/Table.tsx
index 9f021f5..9d4e01a 100644
--- a/pinot-controller/src/main/resources/app/components/Table.tsx
+++ b/pinot-controller/src/main/resources/app/components/Table.tsx
@@ -62,7 +62,12 @@ type Props = {
   recordsCount?: number,
   showSearchBox: boolean,
   inAccordionFormat?: boolean,
-  regexReplace?: boolean
+  regexReplace?: boolean,
+  accordionToggleObject?: {
+    toggleChangeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void;
+    toggleName: string;
+    toggleValue: boolean;
+  }
 };
 
 const StyledTableRow = withStyles((theme) =>
@@ -251,7 +256,8 @@ export default function CustomizedTables({
   recordsCount,
   showSearchBox,
   inAccordionFormat,
-  regexReplace
+  regexReplace,
+  accordionToggleObject
 }: Props) {
   const [finalData, setFinalData] = React.useState(Utils.tableFormat(data));
 
@@ -261,7 +267,7 @@ export default function CustomizedTables({
   const classes = useStyles();
   const [rowsPerPage, setRowsPerPage] = React.useState(noOfRows || 10);
   const [page, setPage] = React.useState(0);
-
+ 
   const handleChangeRowsPerPage = (
     event: React.ChangeEvent<HTMLInputElement>
   ) => {
@@ -305,6 +311,10 @@ export default function CustomizedTables({
     };
   }, [search, timeoutId, filterSearchResults]);
 
+  React.useCallback(()=>{
+    setFinalData(Utils.tableFormat(data));
+  }, [data]);
+
   const styleCell = (str: string) => {
     if (str === 'Good' || str.toLowerCase() === 'online' || str.toLowerCase() === 'alive') {
       return (
@@ -457,6 +467,7 @@ export default function CustomizedTables({
           searchValue={search}
           handleSearch={(val: string) => setSearch(val)}
           recordCount={recordsCount}
+          accordionToggleObject={accordionToggleObject}
         >
           {renderTableComponent()}
         </SimpleAccordion>
diff --git a/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx b/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
index b869125..e04234a 100644
--- a/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
+++ b/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
@@ -32,7 +32,7 @@ import Confirm from '../Confirm';
 import CustomCodemirror from '../CustomCodemirror';
 import PinotMethodUtils from '../../utils/PinotMethodUtils';
 import Utils from '../../utils/Utils';
-import CustomNotification from '../CustomNotification';
+import { NotificationContext } from '../Notification/NotificationContext';
 
 const drawerWidth = 400;
 
@@ -118,8 +118,7 @@ const TreeDirectory = ({
   const [dialogYesLabel, setDialogYesLabel] = React.useState(null);
   const [dialogNoLabel, setDialogNoLabel] = React.useState(null);
   const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
-  const [notificationData, setNotificationData] = React.useState({type: '', message: ''});
-  const [showNotification, setShowNotification] = React.useState(false);
+  const {dispatch} = React.useContext(NotificationContext);
 
   const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
     setAnchorEl(event.currentTarget);
@@ -133,6 +132,7 @@ const TreeDirectory = ({
     if(!isLeafNodeSelected){
       return;
     }
+    newCodeMirrorData = JSON.stringify(currentNodeData);
     setDialogTitle('Update Node Data');
     setDialogContent(<CustomCodemirror
       data={currentNodeData}
@@ -170,12 +170,19 @@ const TreeDirectory = ({
     };
     const result = await PinotMethodUtils.putNodeData(nodeData);
     if(result.data.status){
-      setNotificationData({type: 'success', message: result.data.status});
+      dispatch({
+        type: 'success',
+        message: result.data.status,
+        show: true
+      });
       showInfoEvent(selectedNode);
     } else {
-      setNotificationData({type: 'error', message: result.data.error});
+      dispatch({
+        type: 'error',
+        message: result.data.error,
+        show: true
+      });
     }
-    setShowNotification(true);
     closeDialog();
   };
 
@@ -184,13 +191,20 @@ const TreeDirectory = ({
     const treeObj = Utils.findNestedObj(treeData, 'fullPath', parentPath);
     const result = await PinotMethodUtils.deleteNode(selectedNode);
     if(result.data.status){
-      setNotificationData({type: 'success', message: result.data.status});
+      dispatch({
+        type: 'success',
+        message: result.data.status,
+        show: true
+      });
       showInfoEvent(selectedNode);
       fetchInnerPath(treeObj);
     } else {
-      setNotificationData({type: 'error', message: result.data.error});
+      dispatch({
+        type: 'error',
+        message: result.data.error,
+        show: true
+      });
     }
-    setShowNotification(true);
     closeDialog();
   };
 
@@ -271,12 +285,6 @@ const TreeDirectory = ({
         dialogYesLabel={dialogYesLabel}
         dialogNoLabel={dialogNoLabel}
       />
-      <CustomNotification
-        type={notificationData.type}
-        message={notificationData.message}
-        show={showNotification}
-        hide={()=>{setShowNotification(false)}}
-      />
     </>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/interfaces/types.d.ts b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
index 02256fe..2acbc7e 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -21,6 +21,7 @@ declare module 'Models' {
   export type TableData = {
     records: Array<Array<string | number | boolean>>;
     columns: Array<string>;
+    error?: string;
   };
 
   export type Tenants = {
@@ -77,6 +78,7 @@ declare module 'Models' {
     dimensionFieldSpecs: Array<schema>;
     metricFieldSpecs?: Array<schema>;
     dateTimeFieldSpecs?: Array<schema>;
+    error?: string;
   };
 
   type schema = {
@@ -125,10 +127,14 @@ declare module 'Models' {
     [name: string]: Array<string>
   };
 
-  export type BrokerList = Array<string>;
+  export type BrokerList = {
+    error?: string,
+    Array
+  };
 
   export type ServerList = {
     ServerInstances: Array<string>,
-    tenantName: string
+    tenantName: string,
+    error: string
   }
 }
diff --git a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
index ae2f05d..cbbc423 100644
--- a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
@@ -32,7 +32,7 @@ import SimpleAccordion from '../components/SimpleAccordion';
 import CustomButton from '../components/CustomButton';
 import EditTagsOp from '../components/Homepage/Operations/EditTagsOp';
 import EditConfigOp from '../components/Homepage/Operations/EditConfigOp';
-import CustomNotification from '../components/CustomNotification';
+import { NotificationContext } from '../components/Notification/NotificationContext';
 import _ from 'lodash';
 import Confirm from '../components/Confirm';
 
@@ -91,8 +91,7 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
 
   const [showEditTag, setShowEditTag] = useState(false);
   const [showEditConfig, setShowEditConfig] = useState(false);
-  const [notificationData, setNotificationData] = React.useState({type: '', message: ''});
-  const [showNotification, setShowNotification] = React.useState(false);
+  const {dispatch} = React.useContext(NotificationContext);
 
   const fetchData = async () => {
     const configResponse = await PinotMethodUtils.getInstanceConfig(clutserName, instanceName);
@@ -198,12 +197,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
     }
     const result = await PinotMethodUtils.updateTags(instanceName, newTagsList);
     if(result.status){
-      setNotificationData({type: 'success', message: result.status});
+      dispatch({type: 'success', message: result.status, show: true});
       fetchData();
     } else {
-      setNotificationData({type: 'error', message: result.error});
+      dispatch({type: 'error', message: result.error, show: true});
     }
-    setShowNotification(true);
     setShowEditTag(false);
   };
 
@@ -219,12 +217,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
   const dropInstance = async () => {
     const result = await PinotMethodUtils.deleteInstance(instanceName);
     if(result.status){
-      setNotificationData({type: 'success', message: result.status});
+      dispatch({type: 'success', message: result.status, show: true});
       fetchData();
     } else {
-      setNotificationData({type: 'error', message: result.error});
+      dispatch({type: 'error', message: result.error, show: true});
     }
-    setShowNotification(true);
     closeDialog();
   };
 
@@ -240,12 +237,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
   const toggleInstanceState = async () => {
     const result = await PinotMethodUtils.toggleInstanceState(instanceName, state.enabled ? 'DISABLE' : 'ENABLE');
     if(result.status){
-      setNotificationData({type: 'success', message: result.status});
+      dispatch({type: 'success', message: result.status, show: true});
       fetchData();
     } else {
-      setNotificationData({type: 'error', message: result.error});
+      dispatch({type: 'error', message: result.error, show: true});
     }
-    setShowNotification(true);
     setState({ enabled: !state.enabled });
     closeDialog();
   };
@@ -258,12 +254,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
     if(JSON.parse(config)){
       const result = await PinotMethodUtils.updateInstanceDetails(instanceName, config);
       if(result.status){
-        setNotificationData({type: 'success', message: result.status});
+        dispatch({type: 'success', message: result.status, show: true});
         fetchData();
       } else {
-        setNotificationData({type: 'error', message: result.error});
+        dispatch({type: 'error', message: result.error, show: true});
       }
-      setShowNotification(true);
       setShowEditConfig(false);
     }
   };
@@ -297,6 +292,8 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
                   setTagsList(JSON.parse(instanceConfig)?.listFields?.TAG_LIST || []);
                   setShowEditTag(true);
                 }}
+                tooltipTitle="Edit Tags"
+                enableTooltip={true}
               >
                 Edit Tags
               </CustomButton>
@@ -305,10 +302,16 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
                   setConfig(instanceDetails);
                   setShowEditConfig(true);
                 }}
+                tooltipTitle="Edit Config"
+                enableTooltip={true}
               >
                 Edit Config
               </CustomButton>
-              <CustomButton onClick={handleDropAction}>
+              <CustomButton
+                onClick={handleDropAction}
+                tooltipTitle="Drop"
+                enableTooltip={true}
+              >
                 Drop
               </CustomButton>
               <FormControlLabel
@@ -394,12 +397,6 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
         dialogYesLabel='Yes'
         dialogNoLabel='No'
       />}
-      <CustomNotification
-        type={notificationData.type}
-        message={notificationData.message}
-        show={showNotification}
-        hide={()=>{setShowNotification(false)}}
-      />
     </Grid>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx b/pinot-controller/src/main/resources/app/pages/Query.tsx
index f304982..3ed7f89 100644
--- a/pinot-controller/src/main/resources/app/pages/Query.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Query.tsx
@@ -193,8 +193,9 @@ const QueryPage = () => {
   };
 
   const fetchSQLData = async (tableName) => {
-    const result = await PinotMethodUtils.getTableSchemaData(tableName, false);
-    setTableSchema(result);
+    const result = await PinotMethodUtils.getTableSchemaData(tableName);
+    const tableSchema = Utils.syncTableSchemaData(result, false);
+    setTableSchema(tableSchema);
 
     const query = `select * from ${tableName} limit 10`;
     setInputQuery(query);
diff --git a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
index 31c5a1a..c2f7065 100644
--- a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
@@ -33,7 +33,8 @@ import CustomizedTables from '../components/Table';
 import PinotMethodUtils from '../utils/PinotMethodUtils';
 import CustomButton from '../components/CustomButton';
 import Confirm from '../components/Confirm';
-import CustomNotification from '../components/CustomNotification';
+import { NotificationContext } from '../components/Notification/NotificationContext';
+import Utils from '../utils/Utils';
 
 const useStyles = makeStyles((theme) => ({
   root: {
@@ -75,6 +76,7 @@ const jsonoptions = {
   styleActiveLine: true,
   gutters: ['CodeMirror-lint-markers'],
   theme: 'default',
+  readOnly: true
 };
 
 type Props = {
@@ -98,8 +100,7 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
   const [fetching, setFetching] = useState(true);
   const [confirmDialog, setConfirmDialog] = React.useState(false);
   const [dialogDetails, setDialogDetails] = React.useState(null);
-  const [notificationData, setNotificationData] = React.useState({type: '', message: ''});
-  const [showNotification, setShowNotification] = React.useState(false);
+  const {dispatch} = React.useContext(NotificationContext);
 
   const [segmentSummary, setSegmentSummary] = useState<Summary>({
     segmentName,
@@ -140,27 +141,18 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
 
   const handleDeleteSegment = async () => {
     const result = await PinotMethodUtils.deleteSegmentOp(tableName, segmentName);
-    if(result.status){
-      setNotificationData({type: 'success', message: result.status});
+    if(result && result.status){
+      dispatch({type: 'success', message: result.status, show: true});
       fetchData();
     } else {
-      setNotificationData({type: 'error', message: result.error});
+      dispatch({type: 'error', message: result.error, show: true});
     }
-    setShowNotification(true);
     closeDialog();
     setTimeout(()=>{
-      navigateToPreviousPage();
+      history.push(Utils.navigateToPreviousPage(location, false));
     }, 1000);
   };
 
-  const navigateToPreviousPage = () => {
-    const hasharr = location.pathname.split('/');
-    hasharr.pop();
-    const path = hasharr.join('/');
-    console.log(path)
-    history.push(path);
-  };
-
   const handleReloadSegmentClick = () => {
     setDialogDetails({
       title: 'Reload Segment',
@@ -173,12 +165,11 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
   const handleReloadOp = async () => {
     const result = await PinotMethodUtils.reloadSegmentOp(tableName, segmentName);
     if(result.status){
-      setNotificationData({type: 'success', message: result.status});
+      dispatch({type: 'success', message: result.status, show: true});
       fetchData();
     } else {
-      setNotificationData({type: 'error', message: result.error});
+      dispatch({type: 'error', message: result.error, show: true});
     }
-    setShowNotification(true);
     closeDialog();
   }
 
@@ -201,10 +192,18 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
           showSearchBox={false}
         >
           <div>
-            <CustomButton onClick={()=>{handleDeleteSegmentClick()}}>
+            <CustomButton
+              onClick={()=>{handleDeleteSegmentClick()}}
+              tooltipTitle="Delete Segment"
+              enableTooltip={true}
+            >
               Delete Segment
             </CustomButton>
-            <CustomButton onClick={()=>{handleReloadSegmentClick()}}>
+            <CustomButton
+              onClick={()=>{handleReloadSegmentClick()}}
+              tooltipTitle="Reload Segment"
+              enableTooltip={true}
+            >
               Reload Segment
             </CustomButton>
           </div>
@@ -260,12 +259,6 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
         dialogYesLabel='Yes'
         dialogNoLabel='No'
       />}
-      <CustomNotification
-        type={notificationData.type}
-        message={notificationData.message}
-        show={showNotification}
-        hide={()=>{setShowNotification(false)}}
-      />
     </Grid>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
index 2aa8e5e..4abdb31 100644
--- a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
@@ -38,30 +38,20 @@ const TablesListingPage = () => {
   const classes = useStyles();
 
   const [fetching, setFetching] = useState(true);
-  const columnHeaders = ['Table Name', 'Tenant Name', 'Reported Size', 'Estimated Size', 'Number of Segments', 'Status'];
-  const records = [];
   const [tableData, setTableData] = useState<TableData>({
-    columns: columnHeaders,
+    columns: [],
     records: []
   });
 
   const fetchData = async () => {
-    const tenantsDataResponse = await PinotMethodUtils.getTenantsData();
-    const promiseArr = [];
-    tenantsDataResponse.records.map((tenantRecord)=>{
-      promiseArr.push(PinotMethodUtils.getTenantTableData(tenantRecord[0]));
-    });
-    Promise.all(promiseArr).then((results)=>{
-      results.map((result, index)=>{
-        const tenantName = tenantsDataResponse.records[index][0];
-        records.push(...result.records.map((record)=>{
-          record.splice(1, 0, tenantName);
-          return record;
-        }));
-      });
-      setTableData({columns: columnHeaders, records});
-      setFetching(false);
+    const tablesResponse = await PinotMethodUtils.getQueryTablesList({bothType: true});
+    const tablesList = [];
+    tablesResponse.records.map((record)=>{
+      tablesList.push(...record);
     });
+    const tableDetails = await PinotMethodUtils.getAllTableDetails(tablesList);
+    setTableData(tableDetails);
+    setFetching(false);
   };
 
   useEffect(() => {
diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
index 561a547..5e2e7e2 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
@@ -17,10 +17,10 @@
  * under the License.
  */
 
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import { makeStyles } from '@material-ui/core/styles';
-import { Grid } from '@material-ui/core';
-import { RouteComponentProps } from 'react-router-dom';
+import { FormControlLabel, Grid, Switch } from '@material-ui/core';
+import { RouteComponentProps, useHistory, useLocation } from 'react-router-dom';
 import { UnControlled as CodeMirror } from 'react-codemirror2';
 import { TableData } from 'Models';
 import _ from 'lodash';
@@ -33,6 +33,13 @@ import 'codemirror/mode/javascript/javascript';
 import 'codemirror/mode/sql/sql';
 import SimpleAccordion from '../components/SimpleAccordion';
 import PinotMethodUtils from '../utils/PinotMethodUtils';
+import CustomButton from '../components/CustomButton';
+import EditConfigOp from '../components/Homepage/Operations/EditConfigOp';
+import ReloadStatusOp from '../components/Homepage/Operations/ReloadStatusOp';
+import RebalanceServerTableOp from '../components/Homepage/Operations/RebalanceServerTableOp';
+import Confirm from '../components/Confirm';
+import { NotificationContext } from '../components/Notification/NotificationContext';
+import Utils from '../utils/Utils';
 
 const useStyles = makeStyles((theme) => ({
   root: {
@@ -61,6 +68,11 @@ const useStyles = makeStyles((theme) => ({
     borderRadius: 4,
     marginBottom: '20px',
   },
+  operationDiv: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: 20
+  }
 }));
 
 const jsonoptions = {
@@ -69,6 +81,7 @@ const jsonoptions = {
   styleActiveLine: true,
   gutters: ['CodeMirror-lint-markers'],
   theme: 'default',
+  readOnly: true
 };
 
 type Props = {
@@ -86,6 +99,8 @@ type Summary = {
 const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
   const { tenantName, tableName, instanceName } = match.params;
   const classes = useStyles();
+  const history = useHistory();
+  const location = useLocation();
   const [fetching, setFetching] = useState(true);
   const [tableSummary, setTableSummary] = useState<Summary>({
     tableName: match.params.tableName,
@@ -93,6 +108,21 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
     estimatedSize: '',
   });
 
+  const [state, setState] = React.useState({
+    enabled: true,
+  });
+
+  const [confirmDialog, setConfirmDialog] = React.useState(false);
+  const [dialogDetails, setDialogDetails] = React.useState(null);
+  const {dispatch} = React.useContext(NotificationContext);
+
+  const [showEditConfig, setShowEditConfig] = useState(false);
+  const [config, setConfig] = useState('{}');
+  const [instanceCountData, setInstanceCountData] = useState<TableData>({
+    columns: [],
+    records: [],
+  });
+
   const [segmentList, setSegmentList] = useState<TableData>({
     columns: [],
     records: [],
@@ -102,9 +132,17 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
     columns: [],
     records: [],
   });
-  const [value, setValue] = useState('');
+  const [tableType, setTableType] = useState('');
+  const [tableConfig, setTableConfig] = useState('');
+  const [schemaJSON, setSchemaJSON] = useState(null);
+  const [actionType, setActionType] = useState(null);
+  const [showReloadStatusModal, setShowReloadStatusModal] = useState(false);
+  const [reloadStatusData, setReloadStatusData] = useState(null);
+  const [showRebalanceServerModal, setShowRebalanceServerModal] = useState(false);
+  const [schemaJSONFormat, setSchemaJSONFormat] = useState(false);
 
   const fetchTableData = async () => {
+    setFetching(true);
     const result = await PinotMethodUtils.getTableSummaryData(tableName);
     setTableSummary(result);
     fetchSegmentData();
@@ -112,24 +150,193 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
 
   const fetchSegmentData = async () => {
     const result = await PinotMethodUtils.getSegmentList(tableName);
-    setSegmentList(result);
+    const {columns, records, externalViewObj} = result;
+    const instanceObj = {};
+    Object.keys(externalViewObj).map((segmentName)=>{
+      const instanceKeys = Object.keys(externalViewObj[segmentName]);
+      instanceKeys.map((instanceName)=>{
+        if(!instanceObj[instanceName]){
+          instanceObj[instanceName] = 0;
+        }
+        instanceObj[instanceName] += 1;
+      })
+    });
+    const instanceRecords = [];
+    Object.keys(instanceObj).map((instanceName)=>{
+      instanceRecords.push([instanceName, instanceObj[instanceName]]);
+    })
+    setInstanceCountData({
+      columns: ["Instance Name", "# of segments"],
+      records: instanceRecords
+    });
+    setSegmentList({columns, records});
     fetchTableSchema();
   };
 
   const fetchTableSchema = async () => {
-    const result = await PinotMethodUtils.getTableSchemaData(tableName, true);
-    setTableSchema(result);
+    const result = await PinotMethodUtils.getTableSchemaData(tableName);
+    if(result.error){
+      setSchemaJSON(null);
+      setTableSchema({
+        columns: ['Column', 'Type', 'Field Type'],
+        records: []
+      });
+    } else {
+      setSchemaJSON(JSON.parse(JSON.stringify(result)));
+      const tableSchema = Utils.syncTableSchemaData(result, true);
+      setTableSchema(tableSchema);
+    }
     fetchTableJSON();
   };
 
   const fetchTableJSON = async () => {
     const result = await PinotMethodUtils.getTableDetails(tableName);
-    setValue(JSON.stringify(result, null, 2));
+    const tableObj:any = result.OFFLINE || result.REALTIME;
+    setTableType(tableObj.tableType);
+    setTableConfig(JSON.stringify(result, null, 2));
     setFetching(false);
   };
   useEffect(() => {
     fetchTableData();
   }, []);
+
+  const handleSwitchChange = (event) => {
+    setDialogDetails({
+      title: state.enabled ? 'Disable Table' : 'Enable Table',
+      content: `Are you sure want to ${state.enabled ? 'disable' : 'enable'} this table?`,
+      successCb: () => toggleTableState()
+    });
+    setConfirmDialog(true);
+  };
+
+  const toggleTableState = async () => {
+    const result = await PinotMethodUtils.toggleTableState(tableName, state.enabled ? 'disable' : 'enable', tableType.toLowerCase());
+    if(result[0].state){
+      if(result[0].state.successful){
+        dispatch({type: 'success', message: result[0].state.message, show: true});
+        setState({ enabled: !state.enabled });
+        fetchTableData();
+      } else {
+        dispatch({type: 'error', message: result[0].state.message, show: true});
+      }
+      closeDialog();
+    } else {
+      syncResponse(result);
+    }
+  };
+
+  const handleConfigChange = (value: string) => {
+    setConfig(value);
+  };
+
+  const saveConfigAction = async () => {
+    let configObj = JSON.parse(config);
+    if(actionType === 'editTable'){
+      if(configObj.OFFLINE || configObj.REALTIME){
+        configObj = configObj.OFFLINE || configObj.REALTIME;
+      }
+      const result = await PinotMethodUtils.updateTable(tableName, configObj);
+      syncResponse(result);
+    } else if(actionType === 'editSchema'){
+      const result = await PinotMethodUtils.updateSchema(schemaJSON.schemaName, configObj);
+      syncResponse(result);
+    }
+  };
+
+  const syncResponse = (result) => {
+    if(result.status){
+      dispatch({type: 'success', message: result.status, show: true});
+      fetchTableData();
+      setShowEditConfig(false);
+    } else {
+      dispatch({type: 'error', message: result.error, show: true});
+    }
+    closeDialog();
+  };
+
+  const handleDeleteTableAction = () => {
+    setDialogDetails({
+      title: 'Delete Table',
+      content: 'Are you sure want to delete this table? All data and configs will be deleted.',
+      successCb: () => deleteTable()
+    });
+    setConfirmDialog(true);
+  };
+
+  const deleteTable = async () => {
+    const result = await PinotMethodUtils.deleteTableOp(tableName);
+    if(result.status){
+      dispatch({type: 'success', message: result.status, show: true});
+    } else {
+      dispatch({type: 'error', message: result.error, show: true});
+    }
+    closeDialog();
+    if(result.status){
+      setTimeout(()=>{
+        history.push(Utils.navigateToPreviousPage(location, true));
+      }, 1000);
+    }
+  };
+
+  const handleDeleteSchemaAction = () => {
+    setDialogDetails({
+      title: 'Delete Schema',
+      content: 'Are you sure want to delete this schema? Any tables using this schema might not function correctly.',
+      successCb: () => deleteSchema()
+    });
+    setConfirmDialog(true);
+  };
+
+  const deleteSchema = async () => {
+    const result = await PinotMethodUtils.deleteSchemaOp(schemaJSON.schemaName);
+    syncResponse(result);
+  };
+
+  const handleReloadSegments = () => {
+    setDialogDetails({
+      title: 'Reload all segments',
+      content: 'Are you sure want to reload all the segments?',
+      successCb: () => reloadSegments()
+    });
+    setConfirmDialog(true);
+  };
+
+  const reloadSegments = async () => {
+    const result = await PinotMethodUtils.reloadAllSegmentsOp(tableName, tableType);
+    syncResponse(result);
+  };
+
+  const handleReloadStatus = async () => {
+    setShowReloadStatusModal(true);
+    const result = await PinotMethodUtils.reloadStatusOp(tableName, tableType);
+    if(result.error){
+      setShowReloadStatusModal(false);
+      dispatch({type: 'error', message: result.error, show: true});
+      setShowReloadStatusModal(false);
+      return;
+    }
+    setReloadStatusData(result);
+  };
+
+  const handleRebalanceBrokers = () => {
+    setDialogDetails({
+      title: 'Rebalance brokers',
+      content: 'Are you sure want to rebalance the brokers?',
+      successCb: () => rebalanceBrokers()
+    });
+    setConfirmDialog(true);
+  };
+  
+  const rebalanceBrokers = async () => {
+    const result = await PinotMethodUtils.rebalanceBrokersForTableOp(tableName);
+    syncResponse(result);
+  };
+
+  const closeDialog = () => {
+    setConfirmDialog(false);
+    setDialogDetails(null);
+  };
+
   return fetching ? (
     <AppLoader />
   ) : (
@@ -143,6 +350,98 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
         overflowY: 'auto',
       }}
     >
+      <div className={classes.operationDiv}>
+        <SimpleAccordion
+          headerTitle="Operations"
+          showSearchBox={false}
+        >
+          <div>
+            <CustomButton
+              onClick={()=>{
+                setActionType('editTable');
+                setConfig(tableConfig);
+                setShowEditConfig(true);
+              }}
+              tooltipTitle="Edit Table"
+              enableTooltip={true}
+            >
+              Edit Table
+            </CustomButton>
+            <CustomButton
+              onClick={handleDeleteTableAction}
+              tooltipTitle="Delete Table"
+              enableTooltip={true}
+            >
+              Delete Table
+            </CustomButton>
+            <CustomButton
+              onClick={()=>{
+                setActionType('editSchema');
+                setConfig(JSON.stringify(schemaJSON, null, 2));
+                setShowEditConfig(true);
+              }}
+              tooltipTitle="Edit Schema"
+              enableTooltip={true}
+            >
+              Edit Schema
+            </CustomButton>
+            <CustomButton
+              isDisabled={!schemaJSON} onClick={handleDeleteSchemaAction}
+              tooltipTitle="Delete Schema"
+              enableTooltip={true}
+            >
+              Delete Schema
+            </CustomButton>
+            <CustomButton
+              isDisabled={true} onClick={()=>{console.log('truncate table');}}
+              // tooltipTitle="Truncate Table"
+              // enableTooltip={true}
+            >
+              Truncate Table
+            </CustomButton>
+            <CustomButton
+              onClick={handleReloadSegments}
+              tooltipTitle="Reload All Segments"
+              enableTooltip={true}
+            >
+              Reload All Segments
+            </CustomButton>
+            <CustomButton
+              onClick={handleReloadStatus}
+              tooltipTitle="Reload Status"
+              enableTooltip={true}
+            >
+              Reload Status
+            </CustomButton>
+            <CustomButton
+              onClick={()=>{setShowRebalanceServerModal(true);}}
+              tooltipTitle="Rebalance Servers"
+              enableTooltip={true}
+            >
+              Rebalance Servers
+            </CustomButton>
+            <CustomButton
+              onClick={handleRebalanceBrokers}
+              tooltipTitle="Rebalance Brokers"
+              enableTooltip={true}
+            >
+              Rebalance Brokers
+            </CustomButton>
+            <FormControlLabel
+              control={
+                <Switch
+                  checked={state.enabled}
+                  onChange={handleSwitchChange}
+                  name="enabled"
+                  color="primary"
+                  disabled={true}
+                />
+              }
+              label="Enable"
+            />
+          </div>
+        </SimpleAccordion>
+      </div>
       <div className={classes.highlightBackground}>
         <TableToolbar name="Summary" showSearchBox={false} />
         <Grid container className={classes.body}>
@@ -168,7 +467,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
             >
               <CodeMirror
                 options={jsonoptions}
-                value={value}
+                value={tableConfig}
                 className={classes.queryOutput}
                 autoCursor={false}
               />
@@ -180,23 +479,88 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
             isPagination={false}
             noOfRows={segmentList.records.length}
             baseURL={
-              tenantName ? `/tenants/${tenantName}/table/${tableName}/` :  `/instance/${instanceName}/table/${tableName}/`}
+              tenantName ? `/tenants/${tenantName}/table/${tableName}/` :  `/instance/${instanceName}/table/${tableName}/`
+            }
             addLinks
             showSearchBox={true}
             inAccordionFormat={true}
           />
         </Grid>
         <Grid item xs={6}>
+          {!schemaJSONFormat ?
+            <CustomizedTables
+              title="Table Schema"
+              data={tableSchema}
+              isPagination={false}
+              noOfRows={tableSchema.records.length}
+              showSearchBox={true}
+              inAccordionFormat={true}
+              accordionToggleObject={{
+                toggleName: "JSON Format",
+                toggleValue: schemaJSONFormat,
+                toggleChangeHandler: ()=>{setSchemaJSONFormat(!schemaJSONFormat);}
+              }}
+            />
+          :
+          <div className={classes.sqlDiv}>
+            <SimpleAccordion
+              headerTitle="Table Schema"
+              showSearchBox={false}
+              accordionToggleObject={{
+                toggleName: "JSON Format",
+                toggleValue: schemaJSONFormat,
+                toggleChangeHandler: ()=>{setSchemaJSONFormat(!schemaJSONFormat);}
+              }}
+            >
+              <CodeMirror
+                options={jsonoptions}
+                value={JSON.stringify(schemaJSON, null, 2)}
+                className={classes.queryOutput}
+                autoCursor={false}
+              />
+            </SimpleAccordion>
+          </div>
+          }
           <CustomizedTables
-            title="Table Schema"
-            data={tableSchema}
+            title="Instance Count"
+            data={instanceCountData}
             isPagination={false}
-            noOfRows={tableSchema.records.length}
+            noOfRows={instanceCountData.records.length}
             showSearchBox={true}
             inAccordionFormat={true}
           />
         </Grid>
       </Grid>
+      <EditConfigOp
+        showModal={showEditConfig}
+        hideModal={()=>{setShowEditConfig(false);}}
+        saveConfig={saveConfigAction}
+        config={config}
+        handleConfigChange={handleConfigChange}
+      />
+      {
+        showReloadStatusModal &&
+        <ReloadStatusOp
+          hideModal={()=>{setShowReloadStatusModal(false); setReloadStatusData(null)}}
+          data={reloadStatusData}
+        />
+      }
+      {showRebalanceServerModal &&
+        <RebalanceServerTableOp
+          hideModal={()=>{setShowRebalanceServerModal(false)}}
+          tableType={tableType.toUpperCase()}
+          tableName={tableName}
+        />
+      }
+      {confirmDialog && dialogDetails && <Confirm
+        openDialog={confirmDialog}
+        dialogTitle={dialogDetails.title}
+        dialogContent={dialogDetails.content}
+        successCallback={dialogDetails.successCb}
+        closeDialog={closeDialog}
+        dialogYesLabel='Yes'
+        dialogNoLabel='No'
+      />}
     </Grid>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/pages/Tenants.tsx b/pinot-controller/src/main/resources/app/pages/Tenants.tsx
index 9c87678..33286de 100644
--- a/pinot-controller/src/main/resources/app/pages/Tenants.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Tenants.tsx
@@ -18,12 +18,22 @@
  */
 
 import React, { useState, useEffect } from 'react';
-import { Grid } from '@material-ui/core';
+import { Grid, makeStyles } from '@material-ui/core';
 import { TableData } from 'Models';
 import { RouteComponentProps } from 'react-router-dom';
 import CustomizedTables from '../components/Table';
 import AppLoader from '../components/AppLoader';
 import PinotMethodUtils from '../utils/PinotMethodUtils';
+import SimpleAccordion from '../components/SimpleAccordion';
+import CustomButton from '../components/CustomButton';
+
+const useStyles = makeStyles((theme) => ({
+  operationDiv: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: 20
+  }
+}));
 
 type Props = {
   tenantName: string
@@ -31,14 +41,14 @@ type Props = {
 
 const TenantPage = ({ match }: RouteComponentProps<Props>) => {
 
-  const tenantName = match.params.tenantName;
+  const {tenantName} = match.params;
   const columnHeaders = ['Table Name', 'Reported Size', 'Estimated Size', 'Number of Segments', 'Status'];
   const [fetching, setFetching] = useState(true);
   const [tableData, setTableData] = useState<TableData>({
     columns: columnHeaders,
     records: []
   });
-  const [brokerData, setBrokerData] = useState([]);
+  const [brokerData, setBrokerData] = useState(null);
   const [serverData, setServerData] = useState([]);
 
   const fetchData = async () => {
@@ -46,16 +56,44 @@ const TenantPage = ({ match }: RouteComponentProps<Props>) => {
     const brokersData = await PinotMethodUtils.getBrokerOfTenant(tenantName);
     const serversData = await PinotMethodUtils.getServerOfTenant(tenantName);
     setTableData(tenantData);
-    setBrokerData(brokersData);
-    setServerData(serversData);
+    setBrokerData(brokersData || []);
+    setServerData(serversData || []);
     setFetching(false);
   };
   useEffect(() => {
     fetchData();
   }, []);
+
+  const classes = useStyles();
+
   return (
     fetching ? <AppLoader /> :
     <Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', overflowY: 'auto' }}>
+      <div className={classes.operationDiv}>
+        <SimpleAccordion
+          headerTitle="Operations"
+          showSearchBox={false}
+        >
+          <div>
+            <CustomButton
+              onClick={()=>{console.log('rebalance');}}
+              // tooltipTitle="Rebalance Server Tenant - Coming soon"
+              // enableTooltip={true}
+              isDisabled={true}
+            >
+              Rebalance Server Tenant
+            </CustomButton>
+            <CustomButton
+              onClick={()=>{console.log('rebuild');}}
+              // tooltipTitle="Rebuild Broker Resource - Coming soon"
+              // enableTooltip={true}
+              isDisabled={true}
+            >
+              Rebuild Broker Resource
+            </CustomButton>
+          </div>
+        </SimpleAccordion>
+      </div>
       <CustomizedTables
         title={tenantName}
         data={tableData}
@@ -71,11 +109,11 @@ const TenantPage = ({ match }: RouteComponentProps<Props>) => {
             title="Brokers"
             data={{
               columns: ['Instance Name'],
-              records: [brokerData]
+              records: brokerData.length > 0 ? [brokerData] : []
             }}
             isPagination
             addLinks
-            baseURL={'/instance/'}
+            baseURL="/instance/"
             showSearchBox={true}
             inAccordionFormat={true}
           />
@@ -85,11 +123,11 @@ const TenantPage = ({ match }: RouteComponentProps<Props>) => {
             title="Servers"
             data={{
               columns: ['Instance Name'],
-              records: [serverData]
+              records: serverData.length > 0 ? [serverData] : []
             }}
             isPagination
             addLinks
-            baseURL={'/instance/'}
+            baseURL="/instance/"
             showSearchBox={true}
             inAccordionFormat={true}
           />
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts b/pinot-controller/src/main/resources/app/requests/index.ts
index ade67a1..03e7a41 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -42,6 +42,12 @@ export const getTenantTable = (name: string): Promise<AxiosResponse<TableName>>
 export const getTenantTableDetails = (tableName: string): Promise<AxiosResponse<IdealState>> =>
   baseApi.get(`/tables/${tableName}`);
 
+export const putTable = (name: string, params: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.put(`/tables/${name}`, params, { headers });
+
+export const putSchema = (name: string, params: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.put(`/schemas/${name}`, params, { headers });
+
 export const getSegmentMetadata = (tableName: string, segmentName: string): Promise<AxiosResponse<IdealState>> =>
   baseApi.get(`/segments/${tableName}/${segmentName}/metadata`);
 
@@ -69,6 +75,9 @@ export const updateInstanceTags = (name: string, params: string): Promise<AxiosR
 export const setInstanceState = (name: string, stateName: string): Promise<AxiosResponse<OperationResponse>> =>
   baseApi.post(`/instances/${name}/state`, stateName, { headers: {'Content-Type': 'text/plain', 'Accept': 'application/json'} });
 
+export const setTableState = (name: string, stateName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.get(`/tables/${name}?state=${stateName}&type=${tableType}`);
+
 export const dropInstance = (name: string): Promise<AxiosResponse<OperationResponse>> =>
   baseApi.delete(`instances/${name}`, { headers });
 
@@ -114,5 +123,23 @@ export const getServerListOfTenant = (name: string): Promise<AxiosResponse<Serve
 export const reloadSegment = (tableName: string, instanceName: string): Promise<AxiosResponse<OperationResponse>> =>
   baseApi.post(`/segments/${tableName}/${instanceName}/reload`, null, {headers});
 
+export const reloadAllSegments = (tableName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.post(`/segments/${tableName}/reload?type=${tableType}`, null, {headers});
+
+export const reloadStatus = (tableName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.get(`/segments/${tableName}/metadata?type=${tableType}`);
+
 export const deleteSegment = (tableName: string, instanceName: string): Promise<AxiosResponse<OperationResponse>> =>
-  baseApi.delete(`/segments/${tableName}/${instanceName}`, {headers});
\ No newline at end of file
+  baseApi.delete(`/segments/${tableName}/${instanceName}`, {headers});
+
+export const deleteTable = (tableName: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.delete(`/tables/${tableName}`, {headers});
+
+export const deleteSchema = (schemaName: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.delete(`/schemas/${schemaName}`, {headers});
+
+export const rebalanceServersForTable = (tableName: string, qParams: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.post(`/tables/${tableName}/rebalance?${qParams}`, null, {headers});
+
+export const rebalanceBrokersForTable = (tableName: string): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.post(`/tables/${tableName}/rebuildBrokerResourceFromHelixTags`, null, {headers});
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
index f279284..e733401 100644
--- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -26,6 +26,7 @@ import {
   getInstance,
   putInstance,
   setInstanceState,
+  setTableState,
   dropInstance,
   updateInstanceTags,
   getClusterConfig,
@@ -48,7 +49,15 @@ import {
   zookeeperDeleteNode,
   getBrokerListOfTenant,
   getServerListOfTenant,
-  deleteSegment
+  deleteSegment,
+  putTable,
+  putSchema,
+  deleteTable,
+  deleteSchema,
+  reloadAllSegments,
+  reloadStatus,
+  rebalanceServersForTable,
+  rebalanceBrokersForTable
 } from '../requests';
 import Utils from './Utils';
 
@@ -173,39 +182,9 @@ const getQueryTablesList = ({bothType = false}) => {
 
 // This method is used to display particular table schema on query page
 // API: /tables/:tableName/schema
-// Expected Output: {columns: [], records: []}
-const getTableSchemaData = (tableName, showFieldType) => {
+const getTableSchemaData = (tableName) => {
   return getTableSchema(tableName).then(({ data }) => {
-    const dimensionFields = data.dimensionFieldSpecs || [];
-    const metricFields = data.metricFieldSpecs || [];
-    const dateTimeField = data.dateTimeFieldSpecs || [];
-
-    dimensionFields.map((field) => {
-      field.fieldType = 'Dimension';
-    });
-
-    metricFields.map((field) => {
-      field.fieldType = 'Metric';
-    });
-
-    dateTimeField.map((field) => {
-      field.fieldType = 'Date-Time';
-    });
-    const columnList = [...dimensionFields, ...metricFields, ...dateTimeField];
-    if (showFieldType) {
-      return {
-        columns: ['column', 'type', 'Field Type'],
-        records: columnList.map((field) => {
-          return [field.name, field.dataType, field.fieldType];
-        }),
-      };
-    }
-    return {
-      columns: ['column', 'type'],
-      records: columnList.map((field) => {
-        return [field.name, field.dataType];
-      }),
-    };
+    return data;
   });
 };
 
@@ -317,6 +296,13 @@ const getQueryResults = (params, url, checkedOptions) => {
 //      /tables/:tableName/externalview
 // Expected Output: {columns: [], records: []}
 const getTenantTableData = (tenantName) => {
+  return getTenantTable(tenantName).then(({ data }) => {
+    const tableArr = data.tables.map((table) => table);
+    return getAllTableDetails(tableArr);
+  });
+};
+
+const getAllTableDetails = (tablesList) => {
   const columnHeaders = [
     'Table Name',
     'Reported Size',
@@ -324,67 +310,64 @@ const getTenantTableData = (tenantName) => {
     'Number of Segments',
     'Status',
   ];
-  return getTenantTable(tenantName).then(({ data }) => {
-    const tableArr = data.tables.map((table) => table);
-    if (tableArr.length) {
-      const promiseArr = [];
-      tableArr.map((name) => {
-        promiseArr.push(getTableSize(name));
-        promiseArr.push(getIdealState(name));
-        promiseArr.push(getExternalView(name));
-      });
+  if (tablesList.length) {
+    const promiseArr = [];
+    tablesList.map((name) => {
+      promiseArr.push(getTableSize(name));
+      promiseArr.push(getIdealState(name));
+      promiseArr.push(getExternalView(name));
+    });
 
-      return Promise.all(promiseArr).then((results) => {
-        const finalRecordsArr = [];
-        let singleTableData = [];
-        let idealStateObj = null;
-        let externalViewObj = null;
-        results.map((result, index) => {
-          // since we have 3 promises, we are using mod 3 below
-          if (index % 3 === 0) {
-            // response of getTableSize API
-            const {
-              tableName,
-              reportedSizeInBytes,
-              estimatedSizeInBytes,
-            } = result.data;
-            singleTableData.push(
-              tableName,
-              reportedSizeInBytes,
-              estimatedSizeInBytes
-            );
-          } else if (index % 3 === 1) {
-            // response of getIdealState API
-            idealStateObj = result.data.OFFLINE || result.data.REALTIME;
-          } else if (index % 3 === 2) {
-            // response of getExternalView API
-            externalViewObj = result.data.OFFLINE || result.data.REALTIME;
-            const externalSegmentCount = Object.keys(externalViewObj).length;
-            const idealSegmentCount = Object.keys(idealStateObj).length;
-            // Generating data for the record
-            singleTableData.push(
-              `${externalSegmentCount} / ${idealSegmentCount}`,
-              Utils.getSegmentStatus(idealStateObj, externalViewObj)
-            );
-            // saving into records array
-            finalRecordsArr.push(singleTableData);
-            // resetting the required variables
-            singleTableData = [];
-            idealStateObj = null;
-            externalViewObj = null;
-          }
-        });
-        return {
-          columns: columnHeaders,
-          records: finalRecordsArr,
-        };
+    return Promise.all(promiseArr).then((results) => {
+      const finalRecordsArr = [];
+      let singleTableData = [];
+      let idealStateObj = null;
+      let externalViewObj = null;
+      results.map((result, index) => {
+        // since we have 3 promises, we are using mod 3 below
+        if (index % 3 === 0) {
+          // response of getTableSize API
+          const {
+            tableName,
+            reportedSizeInBytes,
+            estimatedSizeInBytes,
+          } = result.data;
+          singleTableData.push(
+            tableName,
+            reportedSizeInBytes,
+            estimatedSizeInBytes
+          );
+        } else if (index % 3 === 1) {
+          // response of getIdealState API
+          idealStateObj = result.data.OFFLINE || result.data.REALTIME;
+        } else if (index % 3 === 2) {
+          // response of getExternalView API
+          externalViewObj = result.data.OFFLINE || result.data.REALTIME;
+          const externalSegmentCount = Object.keys(externalViewObj).length;
+          const idealSegmentCount = Object.keys(idealStateObj).length;
+          // Generating data for the record
+          singleTableData.push(
+            `${externalSegmentCount} / ${idealSegmentCount}`,
+            Utils.getSegmentStatus(idealStateObj, externalViewObj)
+          );
+          // saving into records array
+          finalRecordsArr.push(singleTableData);
+          // resetting the required variables
+          singleTableData = [];
+          idealStateObj = null;
+          externalViewObj = null;
+        }
       });
-    }
-    return {
-      columns: columnHeaders,
-      records: []
-    };
-  });
+      return {
+        columns: columnHeaders,
+        records: finalRecordsArr,
+      };
+    });
+  }
+  return {
+    columns: columnHeaders,
+    records: []
+  };
 };
 
 // This method is used to display summary of a particular tenant table
@@ -403,7 +386,7 @@ const getTableSummaryData = (tableName) => {
 // This method is used to display segment list of a particular tenant table
 // API: /tables/:tableName/idealstate
 //      /tables/:tableName/externalview
-// Expected Output: {columns: [], records: []}
+// Expected Output: {columns: [], records: [], externalViewObject: {}}
 const getSegmentList = (tableName) => {
   const promiseArr = [];
   promiseArr.push(getIdealState(tableName));
@@ -421,6 +404,7 @@ const getSegmentList = (tableName) => {
           _.isEqual(idealStateObj[key], externalViewObj[key]) ? 'Good' : 'Bad',
         ];
       }),
+      externalViewObj
     };
   });
 };
@@ -578,13 +562,13 @@ const deleteNode = (path) => {
 
 const getBrokerOfTenant = (tenantName) => {
   return getBrokerListOfTenant(tenantName).then((response)=>{
-    return response.data;
+    return !response.data.error ? response.data : [];
   });
 };
 
 const getServerOfTenant = (tenantName) => {
   return getServerListOfTenant(tenantName).then((response)=>{
-    return response.data.ServerInstances;
+    return !response.data.error ? response.data.ServerInstances : [];
   });
 };
 
@@ -600,6 +584,12 @@ const toggleInstanceState = (instanceName, state) => {
   });
 };
 
+const toggleTableState = (tableName, state, tableType) => {
+  return setTableState(tableName, state, tableType).then((response)=>{
+    return response.data;
+  });
+};
+
 const deleteInstance = (instanceName) => {
   return dropInstance(instanceName).then((response)=>{
     return response.data;
@@ -612,12 +602,61 @@ const reloadSegmentOp = (tableName, segmentName) => {
   });
 };
 
+const reloadAllSegmentsOp = (tableName, tableType) => {
+  return reloadAllSegments(tableName, tableType).then((response)=>{
+    return response.data;
+  });
+};
+
+const reloadStatusOp = (tableName, tableType) => {
+  return reloadStatus(tableName, tableType).then((response)=>{
+    return response.data;
+  });
+}
+
 const deleteSegmentOp = (tableName, segmentName) => {
   return deleteSegment(tableName, segmentName).then((response)=>{
     return response.data;
   });
 };
 
+const updateTable = (tableName: string, table: string) => {
+  return putTable(tableName, table).then((res)=>{
+    return res.data;
+  })
+};
+
+const updateSchema = (schemaName: string, schema: string) => {
+  return putSchema(schemaName, schema).then((res)=>{
+    return res.data;
+  })
+};
+
+const deleteTableOp = (tableName) => {
+  return deleteTable(tableName).then((response)=>{
+    return response.data;
+  });
+};
+
+const deleteSchemaOp = (tableName) => {
+  return deleteSchema(tableName).then((response)=>{
+    return response.data;
+  });
+};
+
+const rebalanceServersForTableOp = (tableName, queryParams) => {
+  const q_params = Utils.serialize(queryParams);
+  return rebalanceServersForTable(tableName, q_params).then((response)=>{
+    return  response.data;
+  });
+};
+
+const rebalanceBrokersForTableOp = (tableName) => {
+  return rebalanceBrokersForTable(tableName).then((response)=>{
+    return response.data;
+  });
+};
+
 export default {
   getTenantsData,
   getAllInstances,
@@ -627,6 +666,7 @@ export default {
   getTableSchemaData,
   getQueryResults,
   getTenantTableData,
+  getAllTableDetails,
   getTableSummaryData,
   getSegmentList,
   getTableDetails,
@@ -645,7 +685,16 @@ export default {
   getServerOfTenant,
   updateTags,
   toggleInstanceState,
+  toggleTableState,
   deleteInstance,
   deleteSegmentOp,
-  reloadSegmentOp
+  reloadSegmentOp,
+  reloadStatusOp,
+  reloadAllSegmentsOp,
+  updateTable,
+  updateSchema,
+  deleteTableOp,
+  deleteSchemaOp,
+  rebalanceServersForTableOp,
+  rebalanceBrokersForTableOp
 };
diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx
index aaf698e..0b77e5f 100644
--- a/pinot-controller/src/main/resources/app/utils/Utils.tsx
+++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx
@@ -242,11 +242,55 @@ const serialize = (obj: any, prefix?: any) => {
   return str.join("&");
 };
 
+const navigateToPreviousPage = (location, popTwice) => {
+  const hasharr = location.pathname.split('/');
+  hasharr.pop();
+  if(popTwice){
+    hasharr.pop();
+  }
+  return hasharr.join('/');
+};
+
+const syncTableSchemaData = (data, showFieldType) => {
+  const dimensionFields = data.dimensionFieldSpecs || [];
+  const metricFields = data.metricFieldSpecs || [];
+  const dateTimeField = data.dateTimeFieldSpecs || [];
+
+  dimensionFields.map((field) => {
+    field.fieldType = 'Dimension';
+  });
+
+  metricFields.map((field) => {
+    field.fieldType = 'Metric';
+  });
+
+  dateTimeField.map((field) => {
+    field.fieldType = 'Date-Time';
+  });
+  const columnList = [...dimensionFields, ...metricFields, ...dateTimeField];
+  if (showFieldType) {
+    return {
+      columns: ['Column', 'Type', 'Field Type'],
+      records: columnList.map((field) => {
+        return [field.name, field.dataType, field.fieldType];
+      }),
+    };
+  }
+  return {
+    columns: ['Column', 'Type'],
+    records: columnList.map((field) => {
+      return [field.name, field.dataType];
+    }),
+  };
+};
+
 export default {
   sortArray,
   tableFormat,
   getSegmentStatus,
   findNestedObj,
   generateCodeMirrorOptions,
-  serialize
+  serialize,
+  navigateToPreviousPage,
+  syncTableSchemaData
 };


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org