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 ac7b0e7  UI integration of instance and segment operations (#6148)
ac7b0e7 is described below

commit ac7b0e76304f278dfbb2d29dbd8383090e708601
Author: Sanket Shah <[email protected]>
AuthorDate: Wed Oct 21 22:15:40 2020 +0530

    UI integration of instance and segment operations (#6148)
    
    * UI integration of instance and segment operations
    
    * able to add tags even without making a chip of tag text
---
 pinot-controller/src/main/resources/.eslintrc      |   7 +-
 pinot-controller/src/main/resources/app/App.tsx    |   2 +-
 .../main/resources/app/components/AppLoader.tsx    |   2 +-
 .../main/resources/app/components/Breadcrumbs.tsx  |  52 ++---
 .../src/main/resources/app/components/Confirm.tsx  |  12 +-
 .../components/{AppLoader.tsx => CustomButton.tsx} |  48 ++--
 .../resources/app/components/CustomCodemirror.tsx  |  19 +-
 .../main/resources/app/components/CustomDialog.tsx |  87 +++++++
 .../resources/app/components/CustomMultiSelect.tsx | 123 ++++++++++
 .../app/components/CustomNotification.tsx          |  69 ++++++
 .../src/main/resources/app/components/Header.tsx   |   2 +-
 .../app/components/Homepage/InstanceTable.tsx      |   6 +-
 .../Homepage/Operations/EditConfigOp.tsx           |  59 +++++
 .../components/Homepage/Operations/EditTagsOp.tsx  |  59 +++++
 .../src/main/resources/app/components/Layout.tsx   |   8 +-
 .../main/resources/app/components/MaterialTree.tsx |  14 +-
 .../src/main/resources/app/components/SideBar.tsx  |  10 +-
 .../resources/app/components/SimpleAccordion.tsx   |   3 +-
 .../app/components/SvgIcons/ClusterManagerIcon.tsx |   8 +-
 .../resources/app/components/SvgIcons/Logo.tsx     |  30 +--
 .../app/components/SvgIcons/QueryConsoleIcon.tsx   |   3 +-
 .../app/components/SvgIcons/SwaggerIcon.tsx        |   6 +-
 .../app/components/SvgIcons/ZookeeperIcon.tsx      |  14 +-
 .../src/main/resources/app/components/TabPanel.tsx |   2 +-
 .../src/main/resources/app/components/Table.tsx    |  13 +-
 .../app/components/Zookeeper/TreeDirectory.tsx     |  65 +++---
 .../src/main/resources/app/interfaces/types.d.ts   |  11 +-
 .../src/main/resources/app/pages/HomePage.tsx      |   6 +-
 .../main/resources/app/pages/InstanceDetails.tsx   | 260 ++++++++++++++++++++-
 .../resources/app/pages/InstanceListingPage.tsx    |  10 +-
 .../src/main/resources/app/pages/Query.tsx         |  11 +-
 .../main/resources/app/pages/SegmentDetails.tsx    | 107 ++++++++-
 .../main/resources/app/pages/TablesListingPage.tsx |  12 +-
 .../src/main/resources/app/pages/TenantDetails.tsx |   1 -
 .../resources/app/pages/TenantsListingPage.tsx     |   8 +-
 .../src/main/resources/app/pages/ZookeeperPage.tsx |  58 ++---
 .../src/main/resources/app/requests/index.ts       |  36 ++-
 pinot-controller/src/main/resources/app/router.tsx |  28 +--
 .../src/main/resources/app/styles/styles.css       |   4 +
 .../main/resources/app/utils/PinotMethodUtils.ts   | 110 ++++++---
 .../src/main/resources/app/utils/Utils.tsx         |   6 +-
 pinot-controller/src/main/resources/package.json   |  11 +-
 .../src/main/resources/webpack.config.js           |   3 +
 43 files changed, 1115 insertions(+), 290 deletions(-)

diff --git a/pinot-controller/src/main/resources/.eslintrc 
b/pinot-controller/src/main/resources/.eslintrc
index f737717..2eee0b4 100644
--- a/pinot-controller/src/main/resources/.eslintrc
+++ b/pinot-controller/src/main/resources/.eslintrc
@@ -37,12 +37,7 @@
         "ignoreComments": true
       }
     ],
-    "no-plusplus": [
-      "error",
-      {
-        "allowForLoopAfterthoughts": true
-      }
-    ],
+    "no-plusplus": "off",
     "func-names": "off",
     "no-param-reassign": "off",
     "react/no-array-index-key": "off",
diff --git a/pinot-controller/src/main/resources/app/App.tsx 
b/pinot-controller/src/main/resources/app/App.tsx
index 0c21fd7..63e3c85 100644
--- a/pinot-controller/src/main/resources/app/App.tsx
+++ b/pinot-controller/src/main/resources/app/App.tsx
@@ -57,7 +57,7 @@ const App = () => {
         </Switch>
       </Router>
     </MuiThemeProvider>
-  )
+  );
 };
 
 ReactDOM.render(<App />, document.getElementById('app'));
diff --git a/pinot-controller/src/main/resources/app/components/AppLoader.tsx 
b/pinot-controller/src/main/resources/app/components/AppLoader.tsx
index be3dc26..535c107 100644
--- a/pinot-controller/src/main/resources/app/components/AppLoader.tsx
+++ b/pinot-controller/src/main/resources/app/components/AppLoader.tsx
@@ -39,7 +39,7 @@ const useStyles = makeStyles((theme: Theme) =>
 const AppLoader = () => {
   const classes = useStyles();
   return (
-    <CircularProgress className={classes.root}/>
+    <CircularProgress className={classes.root} />
   );
 };
 
diff --git a/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx 
b/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
index b77104f..5de4ec4 100644
--- a/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
+++ b/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
@@ -87,33 +87,33 @@ const BreadcrumbsComponent = ({ ...props }) => {
   const generateBreadcrumb = () => {
     if(!pathNames.length){
       return getLabel(breadcrumbNameMap['/']);
-    } else {
-      const breadcrumbs = [getClickableLabel(breadcrumbNameMap['/'], '/')];
-      const paramsKeys = _.keys(props.match.params);
-      if(paramsKeys.length){
-        const {tenantName, tableName, segmentName, instanceName} = 
props.match.params;
-        if((tenantName || instanceName) && tableName){
-          breadcrumbs.push(
-            getClickableLabel(
-              tenantName || instanceName,
-              (tenantName ? `/tenants/${tenantName}` : 
`/instance/${instanceName}`)
-            )
-          );
-        }
-        if((tenantName || instanceName) && tableName && segmentName){
-          breadcrumbs.push(
-            getClickableLabel(
-              tableName || instanceName,
-              (tenantName ? `/tenants/${tenantName}/table/${tableName}` : 
`/instance/${instanceName}/table/${tableName}`)
-            )
-          );
-        }
-        breadcrumbs.push(getLabel(segmentName || tableName || tenantName || 
instanceName));
-      } else {
-        breadcrumbs.push(getLabel(breadcrumbNameMap[location.pathname]));
+    }
+    const breadcrumbs = [getClickableLabel(breadcrumbNameMap['/'], '/')];
+    const paramsKeys = _.keys(props.match.params);
+    if(paramsKeys.length){
+      const {tenantName, tableName, segmentName, instanceName} = 
props.match.params;
+      if((tenantName || instanceName) && tableName){
+        breadcrumbs.push(
+          getClickableLabel(
+            tenantName || instanceName,
+            (tenantName ? `/tenants/${tenantName}` : 
`/instance/${instanceName}`)
+          )
+        );
       }
-      return breadcrumbs;
+      if((tenantName || instanceName) && tableName && segmentName){
+        breadcrumbs.push(
+          getClickableLabel(
+            tableName || instanceName,
+            (tenantName ? `/tenants/${tenantName}/table/${tableName}` : 
`/instance/${instanceName}/table/${tableName}`)
+          )
+        );
+      }
+      breadcrumbs.push(getLabel(segmentName || tableName || tenantName || 
instanceName));
+    } else {
+      breadcrumbs.push(getLabel(breadcrumbNameMap[location.pathname]));
     }
+    return breadcrumbs;
+
   };
 
   return (
@@ -126,6 +126,6 @@ const BreadcrumbsComponent = ({ ...props }) => {
       </Breadcrumbs>
     </Box>
   );
-}
+};
 
 export default BreadcrumbsComponent;
diff --git a/pinot-controller/src/main/resources/app/components/Confirm.tsx 
b/pinot-controller/src/main/resources/app/components/Confirm.tsx
index bb11f99..1b4d18c 100644
--- a/pinot-controller/src/main/resources/app/components/Confirm.tsx
+++ b/pinot-controller/src/main/resources/app/components/Confirm.tsx
@@ -69,7 +69,7 @@ const Confirm = ({openDialog, dialogTitle, dialogContent, 
successCallback, close
 
   useEffect(()=>{
     setOpen(openDialog);
-  }, [openDialog])
+  }, [openDialog]);
 
   const isStringDialog = typeof dialogContent === 'string';
 
@@ -83,19 +83,19 @@ const Confirm = ({openDialog, dialogTitle, dialogContent, 
successCallback, close
         maxWidth={false}
       >
         {dialogTitle && <DialogTitle 
id="alert-dialog-title">{dialogTitle}</DialogTitle>}
-        <DialogContent className={`${!isStringDialog ? classes.dialogContent : 
""}`}>
+        <DialogContent className={`${!isStringDialog ? classes.dialogContent : 
''}`}>
           {isStringDialog ?
             <DialogContentText id="alert-dialog-description" 
className={classes.dialogTextContent}>
               {dialogContent}
             </DialogContentText>
-          : dialogContent}
+            : dialogContent}
         </DialogContent>
-        <DialogActions style={{paddingBottom: 20}} 
className={`${isStringDialog ? classes.dialogActions : ""}`}>
+        <DialogActions style={{paddingBottom: 20}} 
className={`${isStringDialog ? classes.dialogActions : ''}`}>
           <Button variant="outlined" onClick={closeDialog} color="secondary" 
className={classes.red}>
-            {dialogNoLabel || "No"}
+            {dialogNoLabel || 'No'}
           </Button>
           <Button variant="outlined" onClick={successCallback} color="primary" 
autoFocus className={classes.green}>
-            {dialogYesLabel || "Yes"}
+            {dialogYesLabel || 'Yes'}
           </Button>
         </DialogActions>
       </Dialog>
diff --git a/pinot-controller/src/main/resources/app/components/AppLoader.tsx 
b/pinot-controller/src/main/resources/app/components/CustomButton.tsx
similarity index 61%
copy from pinot-controller/src/main/resources/app/components/AppLoader.tsx
copy to pinot-controller/src/main/resources/app/components/CustomButton.tsx
index be3dc26..6461cc8 100644
--- a/pinot-controller/src/main/resources/app/components/AppLoader.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomButton.tsx
@@ -17,30 +17,36 @@
  * under the License.
  */
 
-import * as React from 'react';
+import React from 'react';
+import { Button, makeStyles } from '@material-ui/core';
 
-import CircularProgress from '@material-ui/core/CircularProgress';
-import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
+const useStyles = makeStyles((theme) => ({
+  button: {
+    margin: theme.spacing(1),
+    textTransform: 'none'
+  }
+}));
 
-const useStyles = makeStyles((theme: Theme) =>
-  createStyles({
-    root: {
-      position: 'fixed',
-      right: '30px',
-      top: '15px',
-      zIndex: 1200,
-      '& svg > circle': {
-        stroke: 'white'
-      }
-    },
-  })
-);
+type Props = {
+  children: any;
+  onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+};
 
-const AppLoader = () => {
+export default function CustomButton({
+  children,
+  onClick
+}: Props) {
   const classes = useStyles();
+
   return (
-    <CircularProgress className={classes.root}/>
+    <Button
+      variant="contained"
+      color="primary"
+      className={classes.button}
+      size="small"
+      onClick={onClick}
+    >
+      {children}
+    </Button>
   );
-};
-
-export default AppLoader;
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx 
b/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx
index 4e55dec..f5244db 100644
--- a/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx
@@ -22,9 +22,20 @@ import React, {  } from 'react';
 import { UnControlled as CodeMirror } from 'react-codemirror2';
 import 'codemirror/lib/codemirror.css';
 import 'codemirror/theme/material.css';
-import 'codemirror/mode/javascript/javascript'
+import 'codemirror/addon/lint/lint.css';
+import 'codemirror/mode/javascript/javascript';
+import 'codemirror/addon/lint/lint';
+import 'codemirror/addon/lint/json-lint';
 import { makeStyles } from '@material-ui/core';
 
+declare global {
+  interface Window {
+    jsonlint:any;
+  }
+}
+
+window.jsonlint = require('jsonlint');
+
 type Props = {
   data: Object,
   isEditable?: Object,
@@ -45,7 +56,7 @@ const CustomCodemirror = ({data, isEditable, 
returnCodemirrorValue}: Props) => {
     mode: 'application/json',
     styleActiveLine: true,
     gutters: ['CodeMirror-lint-markers'],
-    lint: true,
+    lint: isEditable || false,
     theme: 'default',
     readOnly: !isEditable
   };
@@ -53,10 +64,10 @@ const CustomCodemirror = ({data, isEditable, 
returnCodemirrorValue}: Props) => {
   return (
     <CodeMirror
       options={jsonoptions}
-      value={JSON.stringify(data, null , 2)}
+      value={typeof data === 'string' ? data : JSON.stringify(data, null, 2)}
       className={classes.codeMirror}
       autoCursor={false}
-      onChange={(editor, data, value) => {
+      onChange={(editor, d, value) => {
         returnCodemirrorValue && returnCodemirrorValue(value);
       }}
     />
diff --git 
a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx 
b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
new file mode 100644
index 0000000..cefcac9
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
@@ -0,0 +1,87 @@
+/**
+ * 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 { Button, Dialog, DialogActions, DialogTitle, makeStyles, withStyles } 
from '@material-ui/core';
+import { red } from '@material-ui/core/colors';
+
+const useStyles = makeStyles((theme) => ({
+  root: {
+    '& .MuiDialog-container > .MuiPaper-root':{
+      minWidth: '600px'
+    }
+  },
+  dialogTitle: {
+    padding: '10px 24px',
+  }
+}));
+
+const CancelButton = withStyles(() => ({
+  root: {
+    color: red[500],
+    borderColor: red[500],
+    '&:hover': {
+      borderColor: red[700],
+    },
+  },
+}))(Button);
+
+type Props = {
+  open: boolean,
+  handleClose: (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
+};
+
+export default function CustomDialog({
+  open,
+  handleClose,
+  handleSave,
+  title,
+  children,
+  btnCancelText,
+  btnOkText,
+  showCancelBtn = true,
+  showOkBtn = true
+}: Props) {
+
+  const classes = useStyles();
+
+  return (
+    <Dialog open={open} onClose={handleClose} 
aria-labelledby="form-dialog-title" className={classes.root}>
+      <DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
+      {children}
+      <DialogActions>
+        {showCancelBtn &&
+        <CancelButton onClick={handleClose} variant="outlined">
+          {btnCancelText || 'Cancel'}
+        </CancelButton>}
+        {showOkBtn &&
+        <Button onClick={handleSave} variant="outlined" color="primary">
+          {btnOkText || 'Save'}
+        </Button>}
+      </DialogActions>
+    </Dialog>
+  );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/CustomMultiSelect.tsx 
b/pinot-controller/src/main/resources/app/components/CustomMultiSelect.tsx
new file mode 100644
index 0000000..2c9cd0a
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/CustomMultiSelect.tsx
@@ -0,0 +1,123 @@
+/**
+ * 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, { forwardRef, useEffect, useState } from 'react';
+import Chip from '@material-ui/core/Chip';
+import Autocomplete, { AutocompleteRenderInputParams } from 
'@material-ui/lab/Autocomplete';
+import TextField from '@material-ui/core/TextField';
+import { withStyles } from '@material-ui/core';
+import _ from 'lodash';
+
+interface MyInputProps {
+  onKeyDown: (event: object) => void;
+}
+interface MyParams extends AutocompleteRenderInputParams {
+  inputProps: MyInputProps;
+}
+
+const StyledAutoComplete = withStyles(() => ({
+  tag: {
+    margin: '10px 5px 5px 0',
+    border: '1px solid #4285F4',
+    color: '#4285F4',
+    '& .MuiChip-deleteIcon': {
+      color: 'rgba(66, 133, 244, 0.6)',
+      '&:hover':{
+        color: 'rgb(66, 133, 244)'
+      }
+    }
+  },
+  option:{
+    '&[data-focus="true"]': {
+      backgroundColor: 'rgba(143, 182, 249, 0.3)',
+      color: '#4285F4',
+    },
+    '&[aria-selected="true"]':{
+      backgroundColor: '#4285F4',
+      color: 'white'
+    },
+  },
+  inputRoot: {
+    paddingTop: '0 !important',
+    backgroundColor: 'white !important',
+    '&:hover':{
+      backgroundColor: 'white !important'
+    }
+  },
+}))(Autocomplete);
+
+type Props = {
+  options?: Array<string | Object>,
+  value?: Array<string>,
+  handleChange: (e: React.ChangeEvent<HTMLInputElement>, value: 
Array<string>|null) => void,
+  error?: {isError: boolean, errorMessage: string}
+};
+
+const CustomMultiSelect = forwardRef(({
+  options,
+  value,
+  handleChange,
+  error
+}: Props, ref) => {
+  const [currentValue, setCurrentValue] = useState(value);
+
+  useEffect(() => {
+    setCurrentValue(value);
+  }, [value]);
+
+  const handleKeyDown = event => {
+    switch (event.key) {
+      case ",":
+      case " ": {
+        event.preventDefault();
+        event.stopPropagation();
+        if (event.target.value.length > 0) {
+          handleChange(event, [...value, event.target.value]);
+        }
+        break;
+      }
+      default:
+    }
+  };
+
+  return (
+    <StyledAutoComplete
+      multiple
+      options={options || []}
+      value={currentValue}
+      freeSolo
+      onChange={handleChange}
+      renderTags={(values, getTagProps) =>
+        values.map((option, index) => (
+          <Chip key={index} variant="outlined" label={option} 
{...getTagProps({ index })} />
+        ))}
+      renderInput={(params: MyParams) => {{
+        params.inputProps.onKeyDown = handleKeyDown;
+        return (
+        <TextField
+          inputRef={ref}
+          error={error && error.isError}
+          helperText={error && error.errorMessage}
+          {...params} variant="filled" placeholder="Enter Tags ..."
+        />
+      )}}}
+    />
+  );
+});
+export default CustomMultiSelect;
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/CustomNotification.tsx 
b/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
new file mode 100644
index 0000000..6668cb7
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
@@ -0,0 +1,69 @@
+/**
+ * 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 { Snackbar } from '@material-ui/core';
+import MuiAlert from '@material-ui/lab/Alert';
+
+
+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);
+  };
+
+  return (
+    <Snackbar
+      anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
+      open={showNotification}
+      onClose={hideNotification}
+      key="notification"
+      autoHideDuration={3000}
+    >
+      <Alert 
severity={notificationData.type}>{notificationData.message}</Alert>
+    </Snackbar>
+  );
+};
+
+export default CustomNotification;
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Header.tsx 
b/pinot-controller/src/main/resources/app/components/Header.tsx
index 89532a8..141d22833 100644
--- a/pinot-controller/src/main/resources/app/components/Header.tsx
+++ b/pinot-controller/src/main/resources/app/components/Header.tsx
@@ -40,7 +40,7 @@ const Header = ({ highlightSidebarLink, 
showHideSideBarHandler, openSidebar, ...
         <Box marginY="auto" padding="0.25rem 0 0.25rem 1.5rem" display="flex" 
style={{cursor: 'pointer'}}>
           <MenuIcon onClick={() => showHideSideBarHandler()} />
         </Box>
-        <BreadcrumbsComponent {...props}/>
+        <BreadcrumbsComponent {...props} />
       </Box>
     </Box>
   </AppBar>
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx 
b/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx
index 60b9018..9a62c71 100644
--- 
a/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx
@@ -41,8 +41,8 @@ const InstaceTable = ({ name, instances, clusterName }: 
Props) => {
     fetchLiveInstance(clusterName);
   };
 
-  const fetchLiveInstance = async (clusterName) => {
-    const liveInstanceArr = await 
PinotMethodUtils.getLiveInstance(clusterName);
+  const fetchLiveInstance = async (cluster) => {
+    const liveInstanceArr = await PinotMethodUtils.getLiveInstance(cluster);
     fetchData(liveInstanceArr.data);
   };
 
@@ -64,7 +64,7 @@ const InstaceTable = ({ name, instances, clusterName }: 
Props) => {
       showSearchBox={true}
       inAccordionFormat={true}
       addLinks
-      baseURL={'/instance/'}
+      baseURL="/instance/"
     />
   );
 };
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
new file mode 100644
index 0000000..4a32e0e
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
@@ -0,0 +1,59 @@
+/**
+ * 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} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import CustomCodemirror from '../../CustomCodemirror';
+
+type Props = {
+  showModal: boolean,
+  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+  saveConfig: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+  config: string,
+  handleConfigChange: (value: string) => void,
+};
+
+export default function CustomModal({
+  showModal,
+  hideModal,
+  saveConfig,
+  handleConfigChange,
+  config
+}: Props) {
+
+  return (
+    <Dialog
+      open={showModal}
+      handleClose={hideModal}
+      title="Edit Config"
+      handleSave={saveConfig}
+    >
+      <DialogContent>
+        <CustomCodemirror
+          data={config}
+          isEditable={true}
+          returnCodemirrorValue={(newValue)=>{
+            handleConfigChange(newValue);
+          }}
+        />
+      </DialogContent>
+    </Dialog>
+  );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditTagsOp.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditTagsOp.tsx
new file mode 100644
index 0000000..b7e7f04
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditTagsOp.tsx
@@ -0,0 +1,59 @@
+/**
+ * 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} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import CustomMultiSelect from '../../CustomMultiSelect';
+
+type Props = {
+  showModal: boolean,
+  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+  saveTags: (event: React.MouseEvent<HTMLElement, MouseEvent>, tag: string) => 
void,
+  tags: Array<string>,
+  handleTagsChange: (e: React.ChangeEvent<HTMLInputElement>, value: 
Array<string>|null) => void,
+  error: {isError: boolean, errorMessage: string}
+};
+
+export default function CustomModal({
+  showModal,
+  hideModal,
+  saveTags,
+  handleTagsChange,
+  tags,
+  error
+}: Props) {
+
+  const tagsRef = React.createRef<HTMLInputElement>();
+
+  return (
+    <Dialog
+      open={showModal}
+      handleClose={hideModal}
+      title="Edit Tags"
+      handleSave={(event)=>{
+        saveTags(event, tagsRef.current.value);
+      }}
+    >
+      <DialogContent>
+        <CustomMultiSelect ref={tagsRef} handleChange={handleTagsChange} 
value={tags} error={error}/>
+      </DialogContent>
+    </Dialog>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Layout.tsx 
b/pinot-controller/src/main/resources/app/components/Layout.tsx
index bf84e04..13b6233 100644
--- a/pinot-controller/src/main/resources/app/components/Layout.tsx
+++ b/pinot-controller/src/main/resources/app/components/Layout.tsx
@@ -27,10 +27,10 @@ import ClusterManagerIcon from 
'./SvgIcons/ClusterManagerIcon';
 import ZookeeperIcon from './SvgIcons/ZookeeperIcon';
 
 const navigationItems = [
-  { id: 1, name: 'Cluster Manager', link: '/', icon: <ClusterManagerIcon/> },
-  { id: 2, name: 'Query Console', link: '/query', icon: <QueryConsoleIcon/> },
-  { id: 3, name: 'Zookeeper Browser', link: '/zookeeper', icon: 
<ZookeeperIcon/> },
-  { id: 4, name: 'Swagger REST API', link: 'help', target: '_blank', icon: 
<SwaggerIcon/> }
+  { id: 1, name: 'Cluster Manager', link: '/', icon: <ClusterManagerIcon /> },
+  { id: 2, name: 'Query Console', link: '/query', icon: <QueryConsoleIcon /> },
+  { id: 3, name: 'Zookeeper Browser', link: '/zookeeper', icon: <ZookeeperIcon 
/> },
+  { id: 4, name: 'Swagger REST API', link: 'help', target: '_blank', icon: 
<SwaggerIcon /> }
 ];
 
 const Layout = (props) => {
diff --git 
a/pinot-controller/src/main/resources/app/components/MaterialTree.tsx 
b/pinot-controller/src/main/resources/app/components/MaterialTree.tsx
index 2ecb900..ac739f6 100644
--- a/pinot-controller/src/main/resources/app/components/MaterialTree.tsx
+++ b/pinot-controller/src/main/resources/app/components/MaterialTree.tsx
@@ -71,16 +71,16 @@ type Props = {
   selected: any;
   handleToggle: any;
   handleSelect: any;
-}
+};
 
 type CustomTreeProps = {
   itemObj: any;
   showChildEvent: (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void;
-}
+};
 
 const CustomTreeItem = ({ itemObj, showChildEvent }: CustomTreeProps) => {
   const nestedComments = (itemObj.child || []).map(item => {
-    return <CustomTreeItem key={item.nodeId} itemObj={item} 
showChildEvent={showChildEvent}/>
+    return <CustomTreeItem key={item.nodeId} itemObj={item} 
showChildEvent={showChildEvent} />;
   });
 
   return (
@@ -94,7 +94,7 @@ const CustomTreeItem = ({ itemObj, showChildEvent }: 
CustomTreeProps) => {
     >
       {nestedComments}
     </StyledTreeItem>
-  )
+  );
 };
 
 export default function CustomizedTreeView({treeData, showChildEvent, 
expanded, selected, handleToggle, handleSelect}: Props) {
@@ -103,7 +103,7 @@ export default function CustomizedTreeView({treeData, 
showChildEvent, expanded,
   return (
     <TreeView
       className={classes.root}
-      defaultExpanded={["1"]}
+      defaultExpanded={['1']}
       expanded={expanded}
       selected={selected}
       onNodeToggle={handleToggle}
@@ -114,8 +114,8 @@ export default function CustomizedTreeView({treeData, 
showChildEvent, expanded,
     >
       {treeData.map((itemObj) => {
         return (
-          <CustomTreeItem key={"parent_"+itemObj.nodeId} itemObj={itemObj} 
showChildEvent={showChildEvent} />
-        )
+          <CustomTreeItem key={`parent_${itemObj.nodeId}`} itemObj={itemObj} 
showChildEvent={showChildEvent} />
+        );
       })}
     </TreeView>
   );
diff --git a/pinot-controller/src/main/resources/app/components/SideBar.tsx 
b/pinot-controller/src/main/resources/app/components/SideBar.tsx
index 402a109..bf3deaf 100644
--- a/pinot-controller/src/main/resources/app/components/SideBar.tsx
+++ b/pinot-controller/src/main/resources/app/components/SideBar.tsx
@@ -147,12 +147,13 @@ const Sidebar = ({ showMenu, list, selectedId, 
highlightSidebarLink }: Props) =>
                     <ListItem color="white" button 
className={`${classes.itemContainer} ${selectedId === id ? classes.selectedItem 
: ''}`} selected={selectedId === id} onClick={(event) => 
highlightSidebarLink(id)}>
                       {icon}
                       <Typography
-                        className={clsx('menu-item',{
+                        className={clsx('menu-item', {
                           [classes.sidebarLabel]: showMenu,
                           [classes.sidebarLabelClose]: !showMenu,
                         })}
                         component="span"
-                      >{name} &ensp;</Typography>
+                      >{name} &ensp;
+                      </Typography>
                     </ListItem>
                   </NavLink>
                   :
@@ -160,12 +161,13 @@ const Sidebar = ({ showMenu, list, selectedId, 
highlightSidebarLink }: Props) =>
                     <ListItem color="white" button 
className={`${classes.itemContainer}`}>
                       {icon}
                       <Typography
-                        className={clsx('menu-item',{
+                        className={clsx('menu-item', {
                           [classes.sidebarLabel]: showMenu,
                           [classes.sidebarLabelClose]: !showMenu,
                         })}
                         component="span"
-                      >{name} &ensp;</Typography>
+                      >{name} &ensp;
+                      </Typography>
                     </ListItem>
                   </a>}
               </Box>
diff --git 
a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx 
b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
index 3366f20..16e51f4 100644
--- a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
+++ b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
@@ -87,8 +87,7 @@ export default function SimpleAccordion({
             value={searchValue}
             onChange={(e) => handleSearch(e.target.value)}
           />
-          : null
-        }
+          : null}
         {children}
       </AccordionDetails>
     </Accordion>
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
 
b/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
index 71da8b3..675c35c 100644
--- 
a/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
@@ -23,10 +23,10 @@ import SvgIcon, { SvgIconProps } from 
'@material-ui/core/SvgIcon';
 export default (props: SvgIconProps) => (
   <SvgIcon style={{ width: 24, height: 24, verticalAlign: 'middle' }} 
viewBox="0 0 512 512" fill="none" {...props}>
     <g>
-      <path d="m63.623 
367.312h-62.815v144.688h148.689v-116.926h-68.752zm55.971 
114.786h-88.884v-84.883h16.222l17.123 27.761h55.539z"/>
-      <path d="m244.47 
367.312h-62.815v144.688h148.689v-116.926h-68.751zm55.972 
114.786h-88.884v-84.883h16.222l17.123 27.761h55.539z"/>
-      <path d="m442.44 
395.074-17.122-27.761h-62.815v144.687h148.689v-116.926zm38.85 
87.024h-88.884v-84.883h16.222l17.122 27.761h55.54z"/>
-      <path d="m90.104 
292.958h150.945v42.267h29.902v-42.267h150.945v42.267h29.902v-72.169h-180.847v-64.555h80.049c31.767
 0 57.612-25.926 57.612-57.793 
0-26.955-18.608-49.646-43.653-55.903-4.476-22.152-24.093-38.882-47.545-38.882-1.967
 0-3.917.116-5.842.347-4.829-26.287-27.911-46.27-55.572-46.27s-50.744 
19.983-55.572 46.269c-1.925-.23-3.876-.347-5.843-.347-23.452 0-43.069 
16.73-47.544 38.882-25.044 6.256-43.653 28.948-43.653 55.903 0 31.867 25.844 
57.793 57.612 57.793h80.049v64.555h-180. [...]
+      <path d="m63.623 
367.312h-62.815v144.688h148.689v-116.926h-68.752zm55.971 
114.786h-88.884v-84.883h16.222l17.123 27.761h55.539z" />
+      <path d="m244.47 
367.312h-62.815v144.688h148.689v-116.926h-68.751zm55.972 
114.786h-88.884v-84.883h16.222l17.123 27.761h55.539z" />
+      <path d="m442.44 
395.074-17.122-27.761h-62.815v144.687h148.689v-116.926zm38.85 
87.024h-88.884v-84.883h16.222l17.122 27.761h55.54z" />
+      <path d="m90.104 
292.958h150.945v42.267h29.902v-42.267h150.945v42.267h29.902v-72.169h-180.847v-64.555h80.049c31.767
 0 57.612-25.926 57.612-57.793 
0-26.955-18.608-49.646-43.653-55.903-4.476-22.152-24.093-38.882-47.545-38.882-1.967
 0-3.917.116-5.842.347-4.829-26.287-27.911-46.27-55.572-46.27s-50.744 
19.983-55.572 46.269c-1.925-.23-3.876-.347-5.843-.347-23.452 0-43.069 
16.73-47.544 38.882-25.044 6.256-43.653 28.948-43.653 55.903 0 31.867 25.844 
57.793 57.612 57.793h80.049v64.555h-180. [...]
     </g>
   </SvgIcon>
 );
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/Logo.tsx 
b/pinot-controller/src/main/resources/app/components/SvgIcons/Logo.tsx
index 9ebc38d..291ac41 100644
--- a/pinot-controller/src/main/resources/app/components/SvgIcons/Logo.tsx
+++ b/pinot-controller/src/main/resources/app/components/SvgIcons/Logo.tsx
@@ -36,13 +36,13 @@ export default (props: any) => {
         <path d="M49.4189 24.8855V33.1498L51.4062 33.1048L53.378 
33.0598L53.4246 24.8405L53.4556 16.6361H51.4373H49.4189V24.8855Z" fill="white" 
/>
         <path d="M34.932 16.9102C34.0837 17.0461 32.7508 17.5293 32.251 
17.8766C31.7966 18.1938 31.7057 18.1636 31.7057 
17.6501V17.1971H29.7366H27.7676L27.7676 
28.5985V40H29.7366H31.7057V36.4663V32.9175L32.5842 33.3705C33.8111 33.9897 
35.038 34.2615 36.6284 34.2615C39.2639 34.2615 41.3087 33.4461 43.0506 
31.6943C44.7925 29.9577 45.5044 28.1908 45.5044 25.5481C45.5195 22.9356 44.7925 
21.1536 42.99 19.3566C40.8695 17.2424 38.0673 16.3816 34.932 16.9102ZM38.1743 
20.4091C40.297 20.9406 41.60 [...]
       </SvgIcon>
-    )
-  } else {
-    return (
-      <SvgIcon style={{ width: 40, height: 40, fill: "#ffffff" }} viewBox="0 0 
166.2 326.7" {...props}>
-        <path id="Path" 
d="M100.8,2.3c-3,3-3.2,6.4-0.4,9.9c4.1,5.3,10.9,2.3,10.9-4.7S105.4-2.4,100.8,2.3z"/>
-        <path 
d="M49.9,20.4C46,24.3,47.1,31,52,32.9c5.9,2.2,11.2-7.3,6.7-12.2C56.6,18.4,52,18.2,49.9,20.4z"/>
-        <path 
d="M100.5,29.4c-2.2,1.5-2.2,1.7-2.2,30.7c0,32.4-0.2,33.7-6,33.7c-5.5,0-6-1.6-6-18.6c0-15.3,0-15.5-2.6-18
+    );
+  }
+  return (
+    <SvgIcon style={{ width: 40, height: 40, fill: '#ffffff' }} viewBox="0 0 
166.2 326.7" {...props}>
+      <path id="Path" 
d="M100.8,2.3c-3,3-3.2,6.4-0.4,9.9c4.1,5.3,10.9,2.3,10.9-4.7S105.4-2.4,100.8,2.3z"
 />
+      <path 
d="M49.9,20.4C46,24.3,47.1,31,52,32.9c5.9,2.2,11.2-7.3,6.7-12.2C56.6,18.4,52,18.2,49.9,20.4z"
 />
+      <path 
d="M100.5,29.4c-2.2,1.5-2.2,1.7-2.2,30.7c0,32.4-0.2,33.7-6,33.7c-5.5,0-6-1.6-6-18.6c0-15.3,0-15.5-2.6-18
           
c-1.9-2-3.1-2.4-5.2-1.9c-5.3,1.3-5.2,0.5-5.2,34v31.2l5.6-2.9c4.7-2.3,7-2.8,13-2.8c14.1,0,24.6,8.3,28,22.2c1.5,5.9,1.5,7,0,12.8
           
c-2.7,10.4-10.3,18.5-20,21.1c-6.4,1.7-15.6,0.7-21.5-2.3l-5.1-2.7v14.5c0,14.1-0.1,14.6-2.5,16.9c-1.3,1.4-3.1,2.5-4,2.5
           
s-2.7-1.1-4-2.5l-2.5-2.4V121V47.1L57.8,46c-2.7-1.2-5-0.8-7.7,1.6c-1.4,1.1-1.8,3.1-1.8,7.9v6.3H36.8H25.2L23.4,66
@@ -50,11 +50,13 @@ export default (props: any) => {
           
c12.5,6.3,15.3,8.6,18.8,15.7c3.7,7.6,5.3,15.8,6.7,34.8c1,13.3,0.9,16.1-0.2,17.1c-0.8,0.7-10.4,4.1-21.4,7.5
           
c-11,3.5-20.2,6.5-20.4,6.7c-0.2,0.2,20.9,0.4,47,0.3c28,0,46.4-0.4,44.9-0.9c-1.4-0.5-11.2-3.7-21.9-7L87,312.6l0.6-14.2
           
c0.7-16.6,1.9-24.8,4.9-33.4c3.4-9.7,7-13.1,21.4-20.4c27-13.6,42.4-29.8,49.4-52.3c3.2-10,3.8-28.4,1.5-43
-          
c-3.2-20.7-20.9-83.9-24.2-86.2c-1.1-0.9-6-1.3-15.5-1.3h-13.8v-15c0-13.7-0.2-15.2-2-17C106.9,27.4,103.6,27.2,100.5,29.4z"/>
-        <path 
d="M75,30.6c-2.3,2.5-2.2,8.5,0.1,10.5c1.9,1.8,5.9,2.2,7.8,0.9c2-1.3,3.6-5.4,3-7.7C84.6,29,78.3,26.9,75,30.6z"/>
-        <path 
d="M84.3,130c-5.5,3.8-7.5,7.3-7.5,13.3c0,8.5,5.6,14.5,14.2,15.2c9.1,0.7,16.3-6.2,16.3-15.7C107.3,131.2,93.6,123.7,84.3,130
-          z"/>
-      </SvgIcon>
-    )
-  }
+          
c-3.2-20.7-20.9-83.9-24.2-86.2c-1.1-0.9-6-1.3-15.5-1.3h-13.8v-15c0-13.7-0.2-15.2-2-17C106.9,27.4,103.6,27.2,100.5,29.4z"
+      />
+      <path 
d="M75,30.6c-2.3,2.5-2.2,8.5,0.1,10.5c1.9,1.8,5.9,2.2,7.8,0.9c2-1.3,3.6-5.4,3-7.7C84.6,29,78.3,26.9,75,30.6z"
 />
+      <path 
d="M84.3,130c-5.5,3.8-7.5,7.3-7.5,13.3c0,8.5,5.6,14.5,14.2,15.2c9.1,0.7,16.3-6.2,16.3-15.7C107.3,131.2,93.6,123.7,84.3,130
+          z"
+      />
+    </SvgIcon>
+  );
+
 };
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
 
b/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
index 2870583..becb511 100644
--- 
a/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
@@ -33,6 +33,7 @@ export default (props: SvgIconProps) => (
        
c-21-7.5-25.6-15.1-25.6-16.5v-48.5c4.5,2.3,9.6,4.5,15.5,6.6c24.5,8.8,56.7,13.6,90.8,13.6s66.3-4.8,90.8-13.6
        c5.8-2.1,11-4.3,15.5-6.6v48.5h0C242.6,363.1,238,370.6,217,378.2z 
M275.3,282.5c-68.2,0-123.8-55.5-123.8-123.8S207.1,35,275.3,35
        s123.8,55.5,123.8,123.8C399.1,227,343.5,282.5,275.3,282.5z 
M354.5,290.6c9.8-5.9,18.8-12.8,27-20.7L470,390.3l-27.4,20.2
-       L354.5,290.6z M200.3,143.8h30v30h-30V143.8z 
M260.3,143.8h30v30h-30V143.8z M320.3,143.8h30v30h-30V143.8z"/>
+        L354.5,290.6z M200.3,143.8h30v30h-30V143.8z 
M260.3,143.8h30v30h-30V143.8z M320.3,143.8h30v30h-30V143.8z"
+    />
   </SvgIcon>
 );
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/SwaggerIcon.tsx 
b/pinot-controller/src/main/resources/app/components/SvgIcons/SwaggerIcon.tsx
index 6b10d92..e6dd936 100644
--- 
a/pinot-controller/src/main/resources/app/components/SvgIcons/SwaggerIcon.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/SvgIcons/SwaggerIcon.tsx
@@ -23,7 +23,8 @@ import SvgIcon, { SvgIconProps } from 
'@material-ui/core/SvgIcon';
 export default (props: SvgIconProps) => (
   <SvgIcon style={{ width: 24, height: 24, verticalAlign: 'middle' }} 
viewBox="0 0 100 100" fill="none" {...props}>
     <path 
d="M50,4.8C75,4.8,95.3,25,95.3,50C95.3,75,75,95.3,50,95.3C25,95.3,4.8,75,4.8,50C4.8,25,25,4.8,50,4.8
 M50,0
-      C22.4,0,0,22.4,0,50s22.4,50,50,50s50-22.4,50-50S77.6,0,50,0L50,0z"/>
+      C22.4,0,0,22.4,0,50s22.4,50,50,50s50-22.4,50-50S77.6,0,50,0L50,0z"
+    />
     <path 
d="M31.5,33.9c-0.2,1.7,0.1,3.5-0.1,5.2c-0.1,1.7-0.3,3.4-0.7,5.2c-0.5,2.4-2,4.3-4.1,5.8c4.1,2.7,4.5,6.8,4.8,10.9
       
c0.1,2.3,0.1,4.5,0.3,6.8c0.2,1.7,0.8,2.2,2.6,2.2c0.7,0,1.5,0,2.3,0v5.3c-5.3,0.9-9.7-0.6-10.7-5.1c-0.3-1.6-0.6-3.3-0.7-5
       
c-0.1-1.8,0.1-3.6-0.1-5.4c-0.4-4.9-1-6.6-5.7-6.8V47c0.3-0.1,0.7-0.1,1-0.2c2.6-0.1,3.7-0.9,4.2-3.5c0.3-1.4,0.4-2.9,0.5-4.3
@@ -35,6 +36,7 @@ export default (props: SvgIconProps) => (
       
c-0.2-2.6-0.1-5.2-0.3-7.8c-0.4-6.1-4.8-8.3-11.3-7.2v5.3c1,0,1.8,0,2.6,0c1.4,0,2.4,0.5,2.5,2.1c0.1,1.4,0.1,2.8,0.3,4.2
       
c0.3,2.8,0.4,5.6,0.9,8.4c0.4,2.3,2,4,3.9,5.3c-3.4,2.3-4.4,5.6-4.6,9.2c-0.1,2.5-0.2,5.1-0.3,7.6c-0.1,2.3-0.9,3.1-3.3,3.1
       
c-0.7,0-1.3,0.1-2,0.1v5.4c1.4,0,2.6,0.1,3.9,0c3.9-0.2,6.2-2.1,7-5.9c0.3-2.1,0.5-4.2,0.6-6.3c0.1-1.9,0.1-3.9,0.3-5.8
-      
c0.3-3,1.7-4.2,4.6-4.4c0.3,0,0.6-0.1,0.8-0.2v-6.1C80,46.9,79.6,46.8,79.3,46.8z"/>
+      
c0.3-3,1.7-4.2,4.6-4.4c0.3,0,0.6-0.1,0.8-0.2v-6.1C80,46.9,79.6,46.8,79.3,46.8z"
+    />
   </SvgIcon>
 );
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/ZookeeperIcon.tsx 
b/pinot-controller/src/main/resources/app/components/SvgIcons/ZookeeperIcon.tsx
index ee501b5..f710678 100644
--- 
a/pinot-controller/src/main/resources/app/components/SvgIcons/ZookeeperIcon.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/SvgIcons/ZookeeperIcon.tsx
@@ -23,22 +23,26 @@ import SvgIcon, { SvgIconProps } from 
'@material-ui/core/SvgIcon';
 export default (props: SvgIconProps) => (
   <SvgIcon style={{ width: 24, height: 24, verticalAlign: 'middle', transform: 
'scale(1.5)' }} viewBox="0 0 980.2 980.2" fill="none" {...props}>
     <g>
-      <path 
d="M207.5,594.4c0,0,338.6,37.9,471.6,108.1s250.9,79.3,129.7-12.7C687.5,597.8,403.4,494.9,207.5,594.4z"/>
+      <path 
d="M207.5,594.4c0,0,338.6,37.9,471.6,108.1s250.9,79.3,129.7-12.7C687.5,597.8,403.4,494.9,207.5,594.4z"
 />
       <path 
d="M730.8,269.1C572.7,203.8,381.1,220.6,290,244C198.8,267.3,9.8,345.1,0.5,443.8c-5.2,55.3,28.3,94.7,77.7,122.5
         
c-1-3.1-1.8-6.1-2.8-9.1l-5.7-18.1l16-10.5c29.2-19.1,78.2-46.2,145.8-67.3c65.9-20.6,135.1-31.1,205.6-31.1c1.6,0,3.2,0,4.9,0.1
         
l14.6-12.6l-77.7-6.9l3.5-21.5l134.4,15.6l-31.7,27c16.8,1.1,33.8,2.9,50.6,5.2c7.8-7,18.3-13.3,32.7-18.2
         
c29.5-10.1,71,3.8,89.5,38.2c1.7,3.2,3.2,6.5,4.4,9.8c11.4,4,22.7,8.4,33.7,13.2c6.8-7.3,19.5-16.6,35.6-16.4
         
c18,0.2,38,13.1,46.9,22.8c8.2,9.1,22.5,30.6,24.7,55.5c2.8,2.2,5.6,4.2,8.3,6.4c49.7,39.6,72.4,74.4,73.3,76l6.2,9.6l-3.1,10.9
-        
c-0.8,2.5-1.7,5.4-3.6,9c35.5-20.3,79.5-55.1,92.8-107.9C1000.2,453,889,334.2,730.8,269.1z"/>
+        
c-0.8,2.5-1.7,5.4-3.6,9c35.5-20.3,79.5-55.1,92.8-107.9C1000.2,453,889,334.2,730.8,269.1z"
+      />
       <path 
d="M799.9,571.4c-5.6,16.5-18.2,28.4-40.6,29c-31.9,0.9-58-28.5-70.1-52.3c-7.7-15-8-31.8-4.6-45.5c-5.7-2.4-11.5-4.7-17.5-7
         
c-0.8,23.3-13.2,44.3-37.5,50.9c-37.7,10.1-76.9-6.7-97.3-31.6c-9.9-12.1-18.2-33.7-11.8-54.4c-21.6-2.6-42.7-4.1-63.2-4.7
         
l-48.1,40.8l85.8,9.3l-4.6,22l-143.9-14.5l66.2-57.5c-137.2,5-246.6,50.4-313.2,93.9c0,0,21.8,68.7,20.1,82.9c0,0,6-4.2,38.3-14.7
         
l-5.7-23.4l44.1-22.4c56.7-28.8,123.4-43.5,198.1-43.5c167.3,0,340,73.1,429.6,141.2c3.5,2.6,6.7,5.2,9.9,7.8
-        c12.9-13.2,26.6-29,29.8-39.7C863.4,637.8,844,607.3,799.9,571.4z"/>
+        c12.9-13.2,26.6-29,29.8-39.7C863.4,637.8,844,607.3,799.9,571.4z"
+      />
       <path 
d="M635.6,503.2c-7.6,16.9-28.9,29.1-56.5,17.4c-27.6-11.8-31.7-39.4-26.8-53c4.5-12.4,16.5-21.9,26.5-25.2
-        c10-3.3,34.3-1.7,46.5,12S643.2,486.2,635.6,503.2z"/>
+        c10-3.3,34.3-1.7,46.5,12S643.2,486.2,635.6,503.2z"
+      />
       <path 
d="M768.8,569c-15,14.1-40.2,1.9-50.6-14.6c-10.5-16.5-9.8-36-5.4-47.6c0,0,10.7-21.3,31.3-17.4s31.1,24.2,33.9,35.7
-        C780.8,536.8,783.8,555,768.8,569z"/>
+        C780.8,536.8,783.8,555,768.8,569z"
+      />
     </g>
   </SvgIcon>
 );
diff --git a/pinot-controller/src/main/resources/app/components/TabPanel.tsx 
b/pinot-controller/src/main/resources/app/components/TabPanel.tsx
index 47e9a8b..4c239d2 100644
--- a/pinot-controller/src/main/resources/app/components/TabPanel.tsx
+++ b/pinot-controller/src/main/resources/app/components/TabPanel.tsx
@@ -18,7 +18,7 @@
  */
 
 import React, {  } from 'react';
-import { Box } from "@material-ui/core";
+import { Box } from '@material-ui/core';
 
 interface TabPanelProps {
   children?: React.ReactNode;
diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx 
b/pinot-controller/src/main/resources/app/components/Table.tsx
index 7dac1f3..9f021f5 100644
--- a/pinot-controller/src/main/resources/app/components/Table.tsx
+++ b/pinot-controller/src/main/resources/app/components/Table.tsx
@@ -387,8 +387,8 @@ export default function CustomizedTables({
                       {Object.values(row).map((cell, idx) =>{
                         let url = baseURL;
                         if(regexReplace){
-                          let regex = /\:.*?:/;
-                          let matches = baseURL.match(regex);
+                          const regex = /\:.*?:/;
+                          const matches = baseURL.match(regex);
                           url = baseURL.replace(matches[0], 
row[matches[0].replace(/:/g, '')]);
                         }
                         return addLinks && !idx ? (
@@ -408,7 +408,7 @@ export default function CustomizedTables({
                           >
                             {styleCell(cell.toString())}
                           </StyledTableCell>
-                        )
+                        );
                       })}
                     </StyledTableRow>
                   ))
@@ -462,15 +462,14 @@ export default function CustomizedTables({
         </SimpleAccordion>
       </>
     );
-  }
+  };
 
   return (
     <div className={highlightBackground ? classes.highlightBackground : 
classes.root}>
       {inAccordionFormat ?
         renderTableInAccordion()
-      :
-        renderTable()
-      }
+        :
+        renderTable()}
     </div>
   );
 }
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 10e5e6f..b869125 100644
--- 
a/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
@@ -32,14 +32,10 @@ import Confirm from '../Confirm';
 import CustomCodemirror from '../CustomCodemirror';
 import PinotMethodUtils from '../../utils/PinotMethodUtils';
 import Utils from '../../utils/Utils';
-import MuiAlert from '@material-ui/lab/Alert';
+import CustomNotification from '../CustomNotification';
 
 const drawerWidth = 400;
 
-const Alert = (props) => {
-  return <MuiAlert elevation={6} variant="filled" {...props} />;
-}
-
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
     drawer: {
@@ -137,14 +133,14 @@ const TreeDirectory = ({
     if(!isLeafNodeSelected){
       return;
     }
-    setDialogTitle("Update Node Data");
+    setDialogTitle('Update Node Data');
     setDialogContent(<CustomCodemirror
       data={currentNodeData}
       isEditable={true}
       returnCodemirrorValue={(val)=>{ newCodeMirrorData = val;}}
-    />)
-    setDialogYesLabel("Update");
-    setDialogNoLabel("Cancel");
+    />);
+    setDialogYesLabel('Update');
+    setDialogNoLabel('Cancel');
     setDialogSuccessCb(() => confirmUpdate);
     setConfirmDialog(true);
   };
@@ -153,17 +149,17 @@ const TreeDirectory = ({
     if(!isLeafNodeSelected){
       return;
     }
-    setDialogContent("Delete this node?");
+    setDialogContent('Delete this node?');
     setDialogSuccessCb(() => deleteNode);
     setConfirmDialog(true);
   };
 
   const confirmUpdate = () => {
-    setDialogYesLabel("Yes");
-    setDialogNoLabel("No");
-    setDialogContent("Are you sure want to update this node?");
+    setDialogYesLabel('Yes');
+    setDialogNoLabel('No');
+    setDialogContent('Are you sure want to update this node?');
     setDialogSuccessCb(() => updateNode);
-  }
+  };
 
   const updateNode = async () => {
     const nodeData = {
@@ -171,32 +167,32 @@ const TreeDirectory = ({
       data: newCodeMirrorData.trim(),
       expectedVersion: currentNodeMetadata.version,
       accessOption: currentNodeMetadata.ephemeralOwner === 0 ? 1 : 10
-    }
+    };
     const result = await PinotMethodUtils.putNodeData(nodeData);
     if(result.data.status){
-      setNotificationData({type: 'success', message: result.data.status})
+      setNotificationData({type: 'success', message: result.data.status});
       showInfoEvent(selectedNode);
     } else {
-      setNotificationData({type: 'error', message: result.data.error})
+      setNotificationData({type: 'error', message: result.data.error});
     }
     setShowNotification(true);
     closeDialog();
-  }
+  };
 
   const deleteNode = async () => {
     const parentPath = selectedNode.split('/').slice(0, 
selectedNode.split('/').length-1).join('/');
     const treeObj = Utils.findNestedObj(treeData, 'fullPath', parentPath);
     const result = await PinotMethodUtils.deleteNode(selectedNode);
     if(result.data.status){
-      setNotificationData({type: 'success', message: result.data.status})
+      setNotificationData({type: 'success', message: result.data.status});
       showInfoEvent(selectedNode);
       fetchInnerPath(treeObj);
     } else {
-      setNotificationData({type: 'error', message: result.data.error})
+      setNotificationData({type: 'error', message: result.data.error});
     }
     setShowNotification(true);
     closeDialog();
-  }
+  };
 
   const closeDialog = () => {
     setConfirmDialog(false);
@@ -206,10 +202,6 @@ const TreeDirectory = ({
     setDialogNoLabel(null);
   };
 
-  const hideNotification = () => {
-    setShowNotification(false);
-  }
-
   const open = Boolean(anchorEl);
   const id = open ? 'simple-popover' : undefined;
 
@@ -229,16 +221,16 @@ const TreeDirectory = ({
             <div className={classes.buttonGrpDiv}>
               <ButtonGroup color="primary" aria-label="outlined primary button 
group" className={classes.btnGroup}>
                 <Tooltip title="Refresh">
-                  <Button 
onClick={(e)=>{showInfoEvent(selectedNode);}}><RefreshOutlinedIcon/></Button>
+                  <Button 
onClick={(e)=>{showInfoEvent(selectedNode);}}><RefreshOutlinedIcon /></Button>
                 </Tooltip>
                 <Tooltip title="Add">
-                  <Button onClick={handleClick}><NoteAddOutlinedIcon/></Button>
+                  <Button onClick={handleClick}><NoteAddOutlinedIcon 
/></Button>
                 </Tooltip>
                 <Tooltip title="Delete" open={false}>
-                  <Button onClick={handleDeleteClick} 
disabled={!isLeafNodeSelected}><DeleteOutlineOutlinedIcon/></Button>
+                  <Button onClick={handleDeleteClick} 
disabled={!isLeafNodeSelected}><DeleteOutlineOutlinedIcon /></Button>
                 </Tooltip>
                 <Tooltip title="Edit" open={false}>
-                  <Button onClick={handleEditClick} 
disabled={!isLeafNodeSelected}><EditOutlinedIcon/></Button>
+                  <Button onClick={handleEditClick} 
disabled={!isLeafNodeSelected}><EditOutlinedIcon /></Button>
                 </Tooltip>
               </ButtonGroup>
             </div>
@@ -279,15 +271,12 @@ const TreeDirectory = ({
         dialogYesLabel={dialogYesLabel}
         dialogNoLabel={dialogNoLabel}
       />
-      <Snackbar
-        anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
-        open={showNotification}
-        onClose={hideNotification}
-        key="notification"
-        autoHideDuration={3000}
-      >
-        <Alert 
severity={notificationData.type}>{notificationData.message}</Alert>
-      </Snackbar>
+      <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 7cc6211..02256fe 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -111,12 +111,15 @@ declare module 'Models' {
 
   export type ClusterName = {
     clusterName: string
-  }
+  };
 
-  export type ZKGetList = Array<string>
+  export type ZKGetList = Array<string>;
 
-  export type ZKConfig = Object;
-  export type ZKOperationResponsne = any;
+  export type ZKConfig = {
+    ctime: any,
+    mtime: any
+  };
+  export type OperationResponse = any;
 
   export type DataTable = {
     [name: string]: Array<string>
diff --git a/pinot-controller/src/main/resources/app/pages/HomePage.tsx 
b/pinot-controller/src/main/resources/app/pages/HomePage.tsx
index 5c2c69a..c61ef3f 100644
--- a/pinot-controller/src/main/resources/app/pages/HomePage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/HomePage.tsx
@@ -20,12 +20,12 @@
 import React, {useState, useEffect} from 'react';
 import { Grid, makeStyles, Paper } from '@material-ui/core';
 import { TableData, DataTable } from 'Models';
+import { Link } from 'react-router-dom';
 import AppLoader from '../components/AppLoader';
 import PinotMethodUtils from '../utils/PinotMethodUtils';
 import TenantsListing from '../components/Homepage/TenantsListing';
 import Instances from '../components/Homepage/InstancesTables';
 import ClusterConfig from '../components/Homepage/ClusterConfig';
-import { Link } from 'react-router-dom';
 
 const useStyles = makeStyles((theme) => ({
   paper:{
@@ -138,8 +138,8 @@ const HomePage = () => {
           </Link>
         </Grid>
       </Grid>
-      <TenantsListing tenantsData={tenantsData}/>
-      <Instances instances={instances} clusterName={clusterName}/>
+      <TenantsListing tenantsData={tenantsData} />
+      <Instances instances={instances} clusterName={clusterName} />
       <ClusterConfig />
     </Grid>
   );
diff --git a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx 
b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
index 691b9a3..ae2f05d 100644
--- a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
@@ -18,17 +18,23 @@
  */
 
 import React, { useState, useEffect } from 'react';
-import { Grid, makeStyles } from '@material-ui/core';
+import { Button, FormControlLabel, Grid, makeStyles, Switch } from 
'@material-ui/core';
 import { UnControlled as CodeMirror } from 'react-codemirror2';
 import 'codemirror/lib/codemirror.css';
 import 'codemirror/theme/material.css';
 import 'codemirror/mode/javascript/javascript';
 import { TableData } from 'Models';
+import { RouteComponentProps } from 'react-router-dom';
 import PinotMethodUtils from '../utils/PinotMethodUtils';
 import AppLoader from '../components/AppLoader';
-import { RouteComponentProps } from 'react-router-dom';
 import CustomizedTables from '../components/Table';
 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 _ from 'lodash';
+import Confirm from '../components/Confirm';
 
 const useStyles = makeStyles((theme) => ({
   codeMirrorDiv: {
@@ -38,6 +44,11 @@ const useStyles = makeStyles((theme) => ({
   },
   codeMirror: {
     '& .CodeMirror': { maxHeight: 430, border: '1px solid #BDCCD9' },
+  },
+  operationDiv: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: 20,
   }
 }));
 
@@ -46,7 +57,6 @@ const jsonoptions = {
   mode: 'application/json',
   styleActiveLine: true,
   gutters: ['CodeMirror-lint-markers'],
-  lint: true,
   theme: 'default',
   readOnly: true
 };
@@ -57,22 +67,49 @@ type Props = {
 
 const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
   const classes = useStyles();
-  const instanceName = match.params.instanceName;
+  const {instanceName} = match.params;
   const clutserName = localStorage.getItem('pinot_ui:clusterName');
   const [fetching, setFetching] = useState(true);
+  const [instanceType] = 
React.useState(instanceName.toLowerCase().startsWith('broker') ? 'BROKER' : 
'SERVER');
+  const [confirmDialog, setConfirmDialog] = React.useState(false);
+  const [dialogDetails, setDialogDetails] = React.useState(null);
+
   const [instanceConfig, setInstanceConfig] = useState(null);
   const [liveConfig, setLiveConfig] = useState(null);
+  const [instanceDetails, setInstanceDetails] = useState(null);
   const [tableData, setTableData] = useState<TableData>({
     columns: [],
     records: []
   });
-  
+  const [tagsList, setTagsList] = useState([]);
+  const [tagsErrorObj, setTagsErrorObj] = useState({isError: false, 
errorMessage: null})
+  const [config, setConfig] = useState('{}');
+
+  const [state, setState] = React.useState({
+    enabled: true,
+  });
+
+  const [showEditTag, setShowEditTag] = useState(false);
+  const [showEditConfig, setShowEditConfig] = useState(false);
+  const [notificationData, setNotificationData] = React.useState({type: '', 
message: ''});
+  const [showNotification, setShowNotification] = React.useState(false);
+
   const fetchData = async () => {
     const configResponse = await 
PinotMethodUtils.getInstanceConfig(clutserName, instanceName);
     const liveConfigResponse = await 
PinotMethodUtils.getLiveInstanceConfig(clutserName, instanceName);
-    const tenantListResponse = await 
PinotMethodUtils.getTenantsFromInstance(instanceName);
-    setInstanceConfig(JSON.stringify(configResponse, null , 2));
-    setLiveConfig(JSON.stringify(liveConfigResponse, null , 2));
+    const instanceDetails = await 
PinotMethodUtils.getInstanceDetails(instanceName);
+    const tenantListResponse = getTenants(instanceDetails);
+    setInstanceConfig(JSON.stringify(configResponse, null, 2));
+    const instanceHost = 
instanceDetails.hostName.replace(`${_.startCase(instanceType.toLowerCase())}_`, 
'');
+    const instancePutObj = {
+      host: instanceHost,
+      port: instanceDetails.port,
+      type: instanceType,
+      tags: instanceDetails.tags
+    };
+    setState({enabled: instanceDetails.enabled});
+    setInstanceDetails(JSON.stringify(instancePutObj, null, 2));
+    setLiveConfig(JSON.stringify(liveConfigResponse, null, 2));
     if(tenantListResponse){
       fetchTableDetails(tenantListResponse);
     } else {
@@ -103,6 +140,139 @@ const InstanceDetails = ({ match }: 
RouteComponentProps<Props>) => {
     });
   };
 
+  const getTenants = (instanceDetails) => {
+    const tenantsList = [];
+    instanceDetails.tags.forEach((tag) => {
+      if(tag.search('_BROKER') !== -1 ||
+        tag.search('_REALTIME') !== -1 ||
+        tag.search('_OFFLINE') !== -1
+      ){
+        tenantsList.push(tag.split('_')[0]);
+      }
+    });
+    return _.uniq(tenantsList);
+  };
+
+  const handleTagsChange = (e: React.ChangeEvent<HTMLInputElement>, tags: 
Array<string>|null) => {
+    isTagsValid(tags);
+    setTagsList(tags);
+  };
+
+  const isTagsValid = (_tagsList) => {
+    let isValid = true;
+    setTagsErrorObj({isError: false, errorMessage: null});
+    _tagsList.map((tag)=>{
+      if(!isValid){
+        return;
+      }
+      if(instanceType === 'BROKER'){
+        if(!tag.endsWith('_BROKER')){
+          isValid = false;
+          setTagsErrorObj({
+            isError: true,
+            errorMessage: "Tags should end with _BROKER."
+          });
+        }
+      } else if(instanceType === 'SERVER'){
+        if(!tag.endsWith('_REALTIME') &&
+          !tag.endsWith('_OFFLINE')
+        ){
+          isValid = false;
+          setTagsErrorObj({
+            isError: true,
+            errorMessage: "Tags should end with _OFFLINE or _REALTIME."
+          });
+        }
+      }
+    });
+    return isValid;
+  }
+
+  const saveTagsAction = async (event, typedTag) => {
+    let newTagsList = [...tagsList];
+    if(typedTag.length > 0){
+      newTagsList.push(typedTag);
+    }
+    if(!isTagsValid(newTagsList)){
+      return;
+    }
+    const result = await PinotMethodUtils.updateTags(instanceName, 
newTagsList);
+    if(result.status){
+      setNotificationData({type: 'success', message: result.status});
+      fetchData();
+    } else {
+      setNotificationData({type: 'error', message: result.error});
+    }
+    setShowNotification(true);
+    setShowEditTag(false);
+  };
+
+  const handleDropAction = () => {
+    setDialogDetails({
+      title: 'Drop Instance',
+      content: 'Are you sure want to drop this instance?',
+      successCb: () => dropInstance()
+    });
+    setConfirmDialog(true);
+  };
+
+  const dropInstance = async () => {
+    const result = await PinotMethodUtils.deleteInstance(instanceName);
+    if(result.status){
+      setNotificationData({type: 'success', message: result.status});
+      fetchData();
+    } else {
+      setNotificationData({type: 'error', message: result.error});
+    }
+    setShowNotification(true);
+    closeDialog();
+  };
+
+  const handleSwitchChange = (event) => {
+    setDialogDetails({
+      title: state.enabled ? 'Disable Instance' : 'Enable Instance',
+      content: `Are you sure want to ${state.enabled ? 'disable' : 'enable'} 
this instance?`,
+      successCb: () => toggleInstanceState()
+    });
+    setConfirmDialog(true);
+  };
+
+  const toggleInstanceState = async () => {
+    const result = await PinotMethodUtils.toggleInstanceState(instanceName, 
state.enabled ? 'DISABLE' : 'ENABLE');
+    if(result.status){
+      setNotificationData({type: 'success', message: result.status});
+      fetchData();
+    } else {
+      setNotificationData({type: 'error', message: result.error});
+    }
+    setShowNotification(true);
+    setState({ enabled: !state.enabled });
+    closeDialog();
+  };
+
+  const handleConfigChange = (value: string) => {
+    setConfig(value);
+  };
+
+  const saveConfigAction = async () => {
+    if(JSON.parse(config)){
+      const result = await 
PinotMethodUtils.updateInstanceDetails(instanceName, config);
+      if(result.status){
+        setNotificationData({type: 'success', message: result.status});
+        fetchData();
+      } else {
+        setNotificationData({type: 'error', message: result.error});
+      }
+      setShowNotification(true);
+      setShowEditConfig(false);
+    }
+  };
+
+  const closeDialog = () => {
+    setConfirmDialog(false);
+    setDialogDetails(null);
+  };
+
   return (
     fetching ? <AppLoader /> :
     <Grid
@@ -115,6 +285,46 @@ const InstanceDetails = ({ match }: 
RouteComponentProps<Props>) => {
         overflowY: 'auto',
       }}
     >
+      {!instanceName.toLowerCase().startsWith('controller') &&
+        <div className={classes.operationDiv}>
+          <SimpleAccordion
+            headerTitle="Operations"
+            showSearchBox={false}
+          >
+            <div>
+              <CustomButton
+                onClick={()=>{
+                  setTagsList(JSON.parse(instanceConfig)?.listFields?.TAG_LIST 
|| []);
+                  setShowEditTag(true);
+                }}
+              >
+                Edit Tags
+              </CustomButton>
+              <CustomButton
+                onClick={()=>{
+                  setConfig(instanceDetails);
+                  setShowEditConfig(true);
+                }}
+              >
+                Edit Config
+              </CustomButton>
+              <CustomButton onClick={handleDropAction}>
+                Drop
+              </CustomButton>
+              <FormControlLabel
+                control={
+                  <Switch
+                    checked={state.enabled}
+                    onChange={handleSwitchChange}
+                    name="enabled"
+                    color="primary"
+                  />
+                }
+                label="Enable"
+              />
+            </div>
+          </SimpleAccordion>
+        </div>}
       <Grid container spacing={2}>
         <Grid item xs={liveConfig ? 6 : 12}>
           <div className={classes.codeMirrorDiv}>
@@ -147,7 +357,7 @@ const InstanceDetails = ({ match }: 
RouteComponentProps<Props>) => {
               </SimpleAccordion>
             </div>
           </Grid>
-        : null}
+          : null}
       </Grid>
       {tableData.columns.length ?
         <CustomizedTables
@@ -159,7 +369,37 @@ const InstanceDetails = ({ match }: 
RouteComponentProps<Props>) => {
           showSearchBox={true}
           inAccordionFormat={true}
         />
-      : null}
+        : null}
+      <EditTagsOp
+        showModal={showEditTag}
+        hideModal={()=>{setShowEditTag(false);}}
+        saveTags={saveTagsAction}
+        tags={tagsList}
+        handleTagsChange={handleTagsChange}
+        error={tagsErrorObj}
+      />
+      <EditConfigOp
+        showModal={showEditConfig}
+        hideModal={()=>{setShowEditConfig(false);}}
+        saveConfig={saveConfigAction}
+        config={config}
+        handleConfigChange={handleConfigChange}
+      />
+      {confirmDialog && dialogDetails && <Confirm
+        openDialog={confirmDialog}
+        dialogTitle={dialogDetails.title}
+        dialogContent={dialogDetails.content}
+        successCallback={dialogDetails.successCb}
+        closeDialog={closeDialog}
+        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/InstanceListingPage.tsx 
b/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx
index 30ddf79..7152800 100644
--- a/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx
@@ -44,7 +44,7 @@ const InstanceListingPage = () => {
 
   const fetchData = async () => {
     const instanceResponse = await PinotMethodUtils.getAllInstances();
-    const instanceType = _.startCase(location.hash.split('/')[1].slice(0, -1));
+    const instanceType = 
_.startCase(window.location.hash.split('/')[1].slice(0, -1));
     setInstances(_.pick(instanceResponse, instanceType));
     let clusterNameRes = localStorage.getItem('pinot_ui:clusterName');
     if(!clusterNameRes){
@@ -52,19 +52,19 @@ const InstanceListingPage = () => {
     }
     setClusterName(clusterNameRes);
     setFetching(false);
-  }
+  };
 
   useEffect(() => {
     fetchData();
   }, []);
 
   return fetching ? (
-    <AppLoader/>
+    <AppLoader />
   ) : (
     <Grid item xs className={classes.gridContainer}>
-      <Instances instances={instances} clusterName={clusterName}/>
+      <Instances instances={instances} clusterName={clusterName} />
     </Grid>
-  )
+  );
 };
 
 export default InstanceListingPage;
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx 
b/pinot-controller/src/main/resources/app/pages/Query.tsx
index 84a1b11..f304982 100644
--- a/pinot-controller/src/main/resources/app/pages/Query.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Query.tsx
@@ -92,7 +92,6 @@ const jsonoptions = {
   mode: 'application/json',
   styleActiveLine: true,
   gutters: ['CodeMirror-lint-markers'],
-  lint: true,
   theme: 'default',
   readOnly: true,
 };
@@ -110,11 +109,11 @@ const sqloptions = {
 };
 
 const sqlFuntionsList = [
-  "COUNT", "MIN", "MAX", "SUM", "AVG", "MINMAXRANGE", "DISTINCTCOUNT", 
"DISTINCTCOUNTBITMAP",
-  "SEGMENTPARTITIONEDDISTINCTCOUNT", "DISTINCTCOUNTHLL", 
"DISTINCTCOUNTRAWHLL", "FASTHLL",
-  "DISTINCTCOUNTTHETASKETCH", "DISTINCTCOUNTRAWTHETASKETCH", "COUNTMV", 
"MINMV", "MAXMV",
-  "SUMMV", "AVGMV", "MINMAXRANGEMV", "DISTINCTCOUNTMV", 
"DISTINCTCOUNTBITMAPMV", "DISTINCTCOUNTHLLMV",
-  "DISTINCTCOUNTRAWHLLMV", "DISTINCT", "ST_UNION"];
+  'COUNT', 'MIN', 'MAX', 'SUM', 'AVG', 'MINMAXRANGE', 'DISTINCTCOUNT', 
'DISTINCTCOUNTBITMAP',
+  'SEGMENTPARTITIONEDDISTINCTCOUNT', 'DISTINCTCOUNTHLL', 
'DISTINCTCOUNTRAWHLL', 'FASTHLL',
+  'DISTINCTCOUNTTHETASKETCH', 'DISTINCTCOUNTRAWTHETASKETCH', 'COUNTMV', 
'MINMV', 'MAXMV',
+  'SUMMV', 'AVGMV', 'MINMAXRANGEMV', 'DISTINCTCOUNTMV', 
'DISTINCTCOUNTBITMAPMV', 'DISTINCTCOUNTHLLMV',
+  'DISTINCTCOUNTRAWHLLMV', 'DISTINCT', 'ST_UNION'];
 
 const QueryPage = () => {
   const classes = useStyles();
diff --git a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx 
b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
index 731b8b4..31c5a1a 100644
--- a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
@@ -20,7 +20,7 @@
 import React, { useState, useEffect } from 'react';
 import { makeStyles } from '@material-ui/core/styles';
 import { Grid } from '@material-ui/core';
-import { RouteComponentProps } from 'react-router-dom';
+import { RouteComponentProps, useHistory, useLocation } from 
'react-router-dom';
 import { UnControlled as CodeMirror } from 'react-codemirror2';
 import AppLoader from '../components/AppLoader';
 import TableToolbar from '../components/TableToolbar';
@@ -31,6 +31,9 @@ import 'codemirror/mode/sql/sql';
 import SimpleAccordion from '../components/SimpleAccordion';
 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';
 
 const useStyles = makeStyles((theme) => ({
   root: {
@@ -59,6 +62,11 @@ const useStyles = makeStyles((theme) => ({
     borderRadius: 4,
     marginBottom: '20px',
   },
+  operationDiv: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: 20
+  }
 }));
 
 const jsonoptions = {
@@ -66,7 +74,6 @@ const jsonoptions = {
   mode: 'application/json',
   styleActiveLine: true,
   gutters: ['CodeMirror-lint-markers'],
-  lint: true,
   theme: 'default',
 };
 
@@ -84,9 +91,16 @@ type Summary = {
 
 const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
   const classes = useStyles();
+  const history = useHistory();
+  const location = useLocation();
   const { tableName, segmentName } = match.params;
 
   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 [segmentSummary, setSegmentSummary] = useState<Summary>({
     segmentName,
     totalDocs: '',
@@ -109,6 +123,65 @@ const SegmentDetails = ({ match }: 
RouteComponentProps<Props>) => {
   useEffect(() => {
     fetchData();
   }, []);
+
+  const closeDialog = () => {
+    setConfirmDialog(false);
+    setDialogDetails(null);
+  };
+
+  const handleDeleteSegmentClick = () => {
+    setDialogDetails({
+      title: 'Delete Segment',
+      content: 'Are you sure want to delete this instance? Data from this 
segment will be permanently deleted.',
+      successCb: () => handleDeleteSegment()
+    });
+    setConfirmDialog(true);
+  };
+
+  const handleDeleteSegment = async () => {
+    const result = await PinotMethodUtils.deleteSegmentOp(tableName, 
segmentName);
+    if(result.status){
+      setNotificationData({type: 'success', message: result.status});
+      fetchData();
+    } else {
+      setNotificationData({type: 'error', message: result.error});
+    }
+    setShowNotification(true);
+    closeDialog();
+    setTimeout(()=>{
+      navigateToPreviousPage();
+    }, 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',
+      content: 'Are you sure want to reload this segment?',
+      successCb: () => handleReloadOp()
+    });
+    setConfirmDialog(true);
+  };
+
+  const handleReloadOp = async () => {
+    const result = await PinotMethodUtils.reloadSegmentOp(tableName, 
segmentName);
+    if(result.status){
+      setNotificationData({type: 'success', message: result.status});
+      fetchData();
+    } else {
+      setNotificationData({type: 'error', message: result.error});
+    }
+    setShowNotification(true);
+    closeDialog();
+  }
+
   return fetching ? (
     <AppLoader />
   ) : (
@@ -122,6 +195,21 @@ const SegmentDetails = ({ match }: 
RouteComponentProps<Props>) => {
         overflowY: 'auto',
       }}
     >
+      <div className={classes.operationDiv}>
+        <SimpleAccordion
+          headerTitle="Operations"
+          showSearchBox={false}
+        >
+          <div>
+            <CustomButton onClick={()=>{handleDeleteSegmentClick()}}>
+              Delete Segment
+            </CustomButton>
+            <CustomButton onClick={()=>{handleReloadSegmentClick()}}>
+              Reload Segment
+            </CustomButton>
+          </div>
+        </SimpleAccordion>
+      </div>
       <div className={classes.highlightBackground}>
         <TableToolbar name="Summary" showSearchBox={false} />
         <Grid container className={classes.body}>
@@ -163,6 +251,21 @@ const SegmentDetails = ({ match }: 
RouteComponentProps<Props>) => {
           </div>
         </Grid>
       </Grid>
+      {confirmDialog && dialogDetails && <Confirm
+        openDialog={confirmDialog}
+        dialogTitle={dialogDetails.title}
+        dialogContent={dialogDetails.content}
+        successCallback={dialogDetails.successCb}
+        closeDialog={closeDialog}
+        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 fe29f61..2aa8e5e 100644
--- a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
@@ -47,7 +47,7 @@ const TablesListingPage = () => {
 
   const fetchData = async () => {
     const tenantsDataResponse = await PinotMethodUtils.getTenantsData();
-    let promiseArr = [];
+    const promiseArr = [];
     tenantsDataResponse.records.map((tenantRecord)=>{
       promiseArr.push(PinotMethodUtils.getTenantTableData(tenantRecord[0]));
     });
@@ -55,21 +55,21 @@ const TablesListingPage = () => {
       results.map((result, index)=>{
         const tenantName = tenantsDataResponse.records[index][0];
         records.push(...result.records.map((record)=>{
-          record.splice(1,0,tenantName);
+          record.splice(1, 0, tenantName);
           return record;
         }));
       });
       setTableData({columns: columnHeaders, records});
       setFetching(false);
     });
-  }
+  };
 
   useEffect(() => {
     fetchData();
   }, []);
 
   return fetching ? (
-    <AppLoader/>
+    <AppLoader />
   ) : (
     <Grid item xs className={classes.gridContainer}>
       <CustomizedTables
@@ -77,13 +77,13 @@ const TablesListingPage = () => {
         data={tableData}
         isPagination
         addLinks
-        baseURL={`/tenants/:Tenant Name:/table/`} // TODO
+        baseURL="/tenants/:Tenant Name:/table/" // TODO
         regexReplace={true}
         showSearchBox={true}
         inAccordionFormat={true}
       />
     </Grid>
-  )
+  );
 };
 
 export default TablesListingPage;
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx 
b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
index 240033f..561a547 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
@@ -68,7 +68,6 @@ const jsonoptions = {
   mode: 'application/json',
   styleActiveLine: true,
   gutters: ['CodeMirror-lint-markers'],
-  lint: true,
   theme: 'default',
 };
 
diff --git 
a/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx 
b/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx
index 0b78ce2..531d35e 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx
@@ -44,19 +44,19 @@ const TenantsListingPage = () => {
     const tenantsDataResponse = await PinotMethodUtils.getTenantsData();
     setTenantsData(tenantsDataResponse);
     setFetching(false);
-  }
+  };
 
   useEffect(() => {
     fetchData();
   }, []);
 
   return fetching ? (
-    <AppLoader/>
+    <AppLoader />
   ) : (
     <Grid item xs className={classes.gridContainer}>
-      <TenantsListing tenantsData={tenantsData}/>
+      <TenantsListing tenantsData={tenantsData} />
     </Grid>
-  )
+  );
 };
 
 export default TenantsListingPage;
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx 
b/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx
index fa83c33..abaf4a2 100644
--- a/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx
@@ -63,8 +63,8 @@ const ZookeeperPage = () => {
   const [leafNode, setLeafNode] = useState(false);
 
   // states and handlers for toggle and select of tree
-  const [expanded, setExpanded] = React.useState<string[]>(["1"]);
-  const [selected, setSelected] = React.useState<string[]>(["1"]);
+  const [expanded, setExpanded] = React.useState<string[]>(['1']);
+  const [selected, setSelected] = React.useState<string[]>(['1']);
   const [lastRefresh, setLastRefresh] = React.useState(null);
 
   const handleToggle = (event: React.ChangeEvent<{}>, nodeIds: string[]) => {
@@ -85,11 +85,11 @@ const ZookeeperPage = () => {
 
   // on select, show node data and node metadata
   const showInfoEvent = async (fullPath) => {
-    const { currentNodeData, currentNodeMetadata } = await 
PinotMethodUtils.getNodeData(fullPath);
-    setCurrentNodeData(currentNodeData);
-    setCurrentNodeMetadata(currentNodeMetadata);
+    const nodeDataObj = await PinotMethodUtils.getNodeData(fullPath);
+    setCurrentNodeData(nodeDataObj.currentNodeData);
+    setCurrentNodeMetadata(nodeDataObj.currentNodeMetadata);
     setLastRefresh(new Date());
-  }
+  };
 
   // handlers for Tabs
   const [value, setValue] = React.useState(0);
@@ -102,33 +102,33 @@ const ZookeeperPage = () => {
     if(!pathObj.hasChildRendered){
       fetchInnerPath(pathObj);
     }
-  }
+  };
 
   const fetchInnerPath = async (pathObj) => {
-    const {newTreeData, currentNodeData, currentNodeMetadata, counter } = 
await PinotMethodUtils.getZookeeperData(pathObj.fullPath, count);
-    pathObj.child = newTreeData[0].child;
-    pathObj.isLeafNode = newTreeData[0].child.length === 0;
+    const ZKDataObj = await 
PinotMethodUtils.getZookeeperData(pathObj.fullPath, count);
+    pathObj.child = ZKDataObj.newTreeData[0].child;
+    pathObj.isLeafNode = ZKDataObj.newTreeData[0].child.length === 0;
     pathObj.hasChildRendered = true;
     // setting the old treeData again here since pathObj has the reference of 
old treeData
     // and newTreeData is not useful here.
     setTreeData(treeData);
-    setCurrentNodeData(currentNodeData);
-    setCurrentNodeMetadata(currentNodeMetadata);
-    setCount(counter);
+    setCurrentNodeData(ZKDataObj.currentNodeData);
+    setCurrentNodeMetadata(ZKDataObj.currentNodeMetadata);
+    setCount(ZKDataObj.counter);
     setExpanded([...expanded, pathObj.nodeId]);
   };
 
   const fetchData = async () => {
     setFetching(true);
     const path = '/';
-    const {newTreeData, currentNodeData, currentNodeMetadata, counter } = 
await PinotMethodUtils.getZookeeperData(path, 1);
-    setTreeData(newTreeData);
+    const ZKDataObj = await PinotMethodUtils.getZookeeperData(path, 1);
+    setTreeData(ZKDataObj.newTreeData);
     setSelectedNode(path);
-    setCurrentNodeData(currentNodeData || {});
-    setCurrentNodeMetadata(currentNodeMetadata);
-    setCount(counter);
-    setExpanded(["1"]);
-    setSelected(["1"]);
+    setCurrentNodeData(ZKDataObj.currentNodeData || {});
+    setCurrentNodeMetadata(ZKDataObj.currentNodeMetadata);
+    setCount(ZKDataObj.counter);
+    setExpanded(['1']);
+    setSelected(['1']);
     setLastRefresh(new Date());
     setFetching(false);
   };
@@ -140,16 +140,16 @@ const ZookeeperPage = () => {
   const renderLastRefresh = () => (
     <div className={classes.lastRefreshDiv}>
       <p>
-        {`Last Refreshed: ${lastRefresh.toLocaleTimeString("en-US",{
-            hour12: true,
-            hour: 'numeric',
-            minute: '2-digit',
-            second: '2-digit'
-          })}
+        {`Last Refreshed: ${lastRefresh.toLocaleTimeString('en-US', {
+          hour12: true,
+          hour: 'numeric',
+          minute: '2-digit',
+          second: '2-digit'
+        })}
         `}
       </p>
     </div>
-  )
+  );
 
   return fetching ? (
     <AppLoader />
@@ -193,13 +193,13 @@ const ZookeeperPage = () => {
             >
               {lastRefresh && renderLastRefresh()}
               <div className={classes.codeMirrorDiv}>
-                <CustomCodemirror data={currentNodeData}/>
+                <CustomCodemirror data={currentNodeData} />
               </div>
             </TabPanel>
             <TabPanel value={value} index={1} dir={theme.direction}>
               {lastRefresh && renderLastRefresh()}
               <div className={classes.codeMirrorDiv}>
-                <CustomCodemirror data={currentNodeMetadata}/>
+                <CustomCodemirror data={currentNodeMetadata} />
               </div>
             </TabPanel>
           </Grid>
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts 
b/pinot-controller/src/main/resources/app/requests/index.ts
index 60e5ad6..ade67a1 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -19,9 +19,15 @@
 
 import { AxiosResponse } from 'axios';
 import { TableData, Instances, Instance, Tenants, ClusterConfig, TableName, 
TableSize,
-  IdealState, QueryTables, TableSchema, SQLResult, ClusterName, ZKGetList, 
ZKConfig, ZKOperationResponsne,
+  IdealState, QueryTables, TableSchema, SQLResult, ClusterName, ZKGetList, 
ZKConfig, OperationResponse,
   BrokerList, ServerList
 } from 'Models';
+
+const headers = {
+  'Content-Type': 'application/json; charset=UTF-8',
+  'Accept': 'text/plain, */*; q=0.01'
+};
+
 import { baseApi } from '../utils/axios-config';
 
 export const getTenants = (): Promise<AxiosResponse<Tenants>> =>
@@ -54,17 +60,29 @@ export const getInstances = (): 
Promise<AxiosResponse<Instances>> =>
 export const getInstance = (name: string): Promise<AxiosResponse<Instance>> =>
   baseApi.get(`/instances/${name}`);
 
+export const putInstance = (name: string, params: string): 
Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.put(`/instances/${name}`, params, { headers });
+
+export const updateInstanceTags = (name: string, params: string): 
Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.put(`/instances/${name}/updateTags?tags=${params}`, null, { headers 
});
+
+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 dropInstance = (name: string): 
Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.delete(`instances/${name}`, { headers });
+
 export const getClusterConfig = (): Promise<AxiosResponse<ClusterConfig>> =>
   baseApi.get('/cluster/configs');
 
 export const getQueryTables = (type?: string): 
Promise<AxiosResponse<QueryTables>> =>
-  baseApi.get(`/tables${type ? "?type="+type: ""}`);
+  baseApi.get(`/tables${type ? `?type=${type}`: ''}`);
 
 export const getTableSchema = (name: string): 
Promise<AxiosResponse<TableSchema>> =>
   baseApi.get(`/tables/${name}/schema`);
 
 export const getQueryResult = (params: Object, url: string): 
Promise<AxiosResponse<SQLResult>> =>
-  baseApi.post(`/${url}`, params, { headers: { 'Content-Type': 
'application/json; charset=UTF-8', 'Accept': 'text/plain, */*; q=0.01' } });
+  baseApi.post(`/${url}`, params, {headers});
 
 export const getClusterInfo = (): Promise<AxiosResponse<ClusterName>> =>
   baseApi.get('/cluster/info');
@@ -81,10 +99,10 @@ export const zookeeperGetStat = (params: string): 
Promise<AxiosResponse<ZKConfig
 export const zookeeperGetListWithStat = (params: string): 
Promise<AxiosResponse<ZKConfig>> =>
   baseApi.get(`/zk/lsl?path=${params}`);
 
-export const zookeeperPutData = (params: string): 
Promise<AxiosResponse<ZKOperationResponsne>> =>
-  baseApi.put(`/zk/put?${params}`, null, { headers: { 'Content-Type': 
'application/json; charset=UTF-8', 'Accept': 'text/plain, */*; q=0.01' } });
+export const zookeeperPutData = (params: string): 
Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.put(`/zk/put?${params}`, null, { headers });
 
-export const zookeeperDeleteNode = (params: string): 
Promise<AxiosResponse<ZKOperationResponsne>> =>
+export const zookeeperDeleteNode = (params: string): 
Promise<AxiosResponse<OperationResponse>> =>
   baseApi.delete(`/zk/delete?path=${params}`);
 
 export const getBrokerListOfTenant = (name: string): 
Promise<AxiosResponse<BrokerList>> =>
@@ -92,3 +110,9 @@ export const getBrokerListOfTenant = (name: string): 
Promise<AxiosResponse<Broke
 
 export const getServerListOfTenant = (name: string): 
Promise<AxiosResponse<ServerList>> =>
   baseApi.get(`/tenants/${name}?type=server`);
+
+export const reloadSegment = (tableName: string, instanceName: string): 
Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.post(`/segments/${tableName}/${instanceName}/reload`, null, 
{headers});
+
+export const deleteSegment = (tableName: string, instanceName: string): 
Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.delete(`/segments/${tableName}/${instanceName}`, {headers});
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/router.tsx 
b/pinot-controller/src/main/resources/app/router.tsx
index 86b700c..1ced230 100644
--- a/pinot-controller/src/main/resources/app/router.tsx
+++ b/pinot-controller/src/main/resources/app/router.tsx
@@ -29,18 +29,18 @@ import InstanceDetails from './pages/InstanceDetails';
 import ZookeeperPage from './pages/ZookeeperPage';
 
 export default [
-  { path: "/", Component: HomePage },
-  { path: "/query", Component: QueryPage },
-  { path: "/tenants", Component: TenantsListingPage },
-  { path: "/controllers", Component: InstanceListingPage },
-  { path: "/brokers", Component: InstanceListingPage },
-  { path: "/servers", Component: InstanceListingPage },
-  { path: "/tables", Component: TablesListingPage },
-  { path: "/tenants/:tenantName", Component: TenantsPage },
-  { path: "/tenants/:tenantName/table/:tableName", Component: 
TenantPageDetails },
-  { path: "/tenants/:tenantName/table/:tableName/:segmentName", Component: 
SegmentDetails },
-  { path: "/instance/:instanceName", Component: InstanceDetails },
-  { path: "/instance/:instanceName/table/:tableName", Component: 
TenantPageDetails },
-  { path: "/instance/:instanceName/table/:tableName/:segmentName", Component: 
SegmentDetails },
-  { path: "/zookeeper", Component: ZookeeperPage },
+  { path: '/', Component: HomePage },
+  { path: '/query', Component: QueryPage },
+  { path: '/tenants', Component: TenantsListingPage },
+  { path: '/controllers', Component: InstanceListingPage },
+  { path: '/brokers', Component: InstanceListingPage },
+  { path: '/servers', Component: InstanceListingPage },
+  { path: '/tables', Component: TablesListingPage },
+  { path: '/tenants/:tenantName', Component: TenantsPage },
+  { path: '/tenants/:tenantName/table/:tableName', Component: 
TenantPageDetails },
+  { path: '/tenants/:tenantName/table/:tableName/:segmentName', Component: 
SegmentDetails },
+  { path: '/instance/:instanceName', Component: InstanceDetails },
+  { path: '/instance/:instanceName/table/:tableName', Component: 
TenantPageDetails },
+  { path: '/instance/:instanceName/table/:tableName/:segmentName', Component: 
SegmentDetails },
+  { path: '/zookeeper', Component: ZookeeperPage },
 ];
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/styles/styles.css 
b/pinot-controller/src/main/resources/app/styles/styles.css
index 5415efc..a2a7a6d 100644
--- a/pinot-controller/src/main/resources/app/styles/styles.css
+++ b/pinot-controller/src/main/resources/app/styles/styles.css
@@ -85,4 +85,8 @@ li.codemirror-func.CodeMirror-hint::before {
 
 li.CodeMirror-hint-active {
   background-color: #dae2f2;
+}
+
+.CodeMirror-lint-tooltip{
+  z-index: 9999 !important;
 }
\ 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 165ab76..f279284 100644
--- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -24,6 +24,10 @@ import {
   getTenants,
   getInstances,
   getInstance,
+  putInstance,
+  setInstanceState,
+  dropInstance,
+  updateInstanceTags,
   getClusterConfig,
   getQueryTables,
   getTableSchema,
@@ -34,6 +38,7 @@ import {
   getExternalView,
   getTenantTableDetails,
   getSegmentMetadata,
+  reloadSegment,
   getClusterInfo,
   zookeeperGetList,
   zookeeperGetData,
@@ -42,7 +47,8 @@ import {
   zookeeperPutData,
   zookeeperDeleteNode,
   getBrokerListOfTenant,
-  getServerListOfTenant
+  getServerListOfTenant,
+  deleteSegment
 } from '../requests';
 import Utils from './Utils';
 
@@ -52,8 +58,8 @@ import Utils from './Utils';
 const getTenantsData = () => {
   return getTenants().then(({ data }) => {
     const records = _.union(data.SERVER_TENANTS, data.BROKER_TENANTS);
-    let promiseArr = [];
-    let finalResponse = {
+    const promiseArr = [];
+    const finalResponse = {
       columns: ['Tenant Name', 'Server', 'Broker', 'Tables'],
       records: []
     };
@@ -88,7 +94,7 @@ const getAllInstances = () => {
       r[key] = [...(r[key] || []), a];
       return r;
     }, initialVal);
-    return {"Controller": groupedData.Controller, ...groupedData};
+    return {'Controller': groupedData.Controller, ...groupedData};
   });
 };
 
@@ -127,7 +133,7 @@ const getClusterName = () => {
 // API: /zk/ls?path=:ClusterName/LIVEINSTANCES
 // Expected Output: []
 const getLiveInstance = (clusterName) => {
-  const params = encodeURIComponent(`/${clusterName}/LIVEINSTANCES`)
+  const params = encodeURIComponent(`/${clusterName}/LIVEINSTANCES`);
   return zookeeperGetList(params).then((data) => {
     return data;
   });
@@ -149,13 +155,13 @@ const getClusterConfigData = () => {
 // API: /tables
 // Expected Output: {columns: [], records: []}
 const getQueryTablesList = ({bothType = false}) => {
-  let promiseArr = bothType ? [getQueryTables('realtime'), 
getQueryTables('offline')] : [getQueryTables()];
+  const promiseArr = bothType ? [getQueryTables('realtime'), 
getQueryTables('offline')] : [getQueryTables()];
   
   return Promise.all(promiseArr).then((results) => {
-    let responseObj = {
+    const responseObj = {
       columns: ['Tables'],
       records:  []
-    }
+    };
     results.map((result)=>{
       result.data.tables.map((table)=>{
         responseObj.records.push([table]);
@@ -329,7 +335,7 @@ const getTenantTableData = (tenantName) => {
       });
 
       return Promise.all(promiseArr).then((results) => {
-        let finalRecordsArr = [];
+        const finalRecordsArr = [];
         let singleTableData = [];
         let idealStateObj = null;
         let externalViewObj = null;
@@ -442,7 +448,7 @@ const getSegmentDetails = (tableName, segmentName) => {
     const obj = results[0].data.OFFLINE || results[0].data.REALTIME;
     const segmentMetaData = results[1].data;
 
-    let result = [];
+    const result = [];
     for (const prop in obj[segmentName]) {
       if (obj[segmentName]) {
         result.push([prop, obj[segmentName][prop]]);
@@ -473,7 +479,7 @@ const getLiveInstanceConfig = (clusterName, instanceName) 
=> {
   const params = 
encodeURIComponent(`/${clusterName}/LIVEINSTANCES/${instanceName}`);
   return zookeeperGetData(params).then((res) => {
     return res.data;
-  })
+  });
 };
 
 // This method is used to fetch the instance config
@@ -483,24 +489,20 @@ const getInstanceConfig = (clusterName, instanceName) => {
   const params = 
encodeURIComponent(`/${clusterName}/CONFIGS/PARTICIPANT/${instanceName}`);
   return zookeeperGetData(params).then((res) => {
     return res.data;
-  })
+  });
 };
 
-// This method is used to get tenants from tags using instance info
+// This method is used to get instance info
 // API: /instances/:instanceName
-// Expected Output: Unique array of tenants names
-const getTenantsFromInstance = (instanceName) => {
+const getInstanceDetails = (instanceName) => {
   return getInstance(instanceName).then((res)=>{
-    const tenantsList = [];
-    res.data.tags.forEach((tag) => {
-      if(tag.search('_BROKER') !== -1 ||
-        tag.search('_REALTIME') !== -1 ||
-        tag.search('_OFFLINE') !== -1
-      ){
-        tenantsList.push(tag.split('_')[0]);
-      }
-    });
-    return _.uniq(tenantsList);
+    return res.data;
+  });
+};
+
+const updateInstanceDetails = (instanceName, instanceDetails) => {
+  return putInstance(instanceName, instanceDetails).then((res)=>{
+    return res.data;
   })
 };
 
@@ -509,20 +511,20 @@ const getTenantsFromInstance = (instanceName) => {
 const getZookeeperData = (path, count) => {
   let counter = count;
   const newTreeData = [{
-    nodeId: ''+counter++,
+    nodeId: `${counter++}`,
     label: path,
     child: [],
     isLeafNode: false,
     hasChildRendered: true
-  }]
+  }];
   return getNodeData(path).then((obj)=>{
     const { currentNodeData, currentNodeMetadata, currentNodeListStat } = obj;
-    let pathNames = Object.keys(currentNodeListStat);
+    const pathNames = Object.keys(currentNodeListStat);
     pathNames.map((pathName)=>{
       newTreeData[0].child.push({
-        nodeId: ''+counter++,
+        nodeId: `${counter++}`,
         label: pathName,
-        fullPath: path === '/' ? path+pathName : path+'/'+pathName,
+        fullPath: path === '/' ? path+pathName : `${path}/${pathName}`,
         child: [],
         isLeafNode: currentNodeListStat[pathName].numChildren === 0,
         hasChildRendered: false
@@ -538,7 +540,7 @@ const getZookeeperData = (path, count) => {
 // API: /zk/get => Get node stats
 const getNodeData = (path) => {
   const params = encodeURIComponent(path);
-  let promiseArr = [
+  const promiseArr = [
     zookeeperGetData(params),
     zookeeperGetListWithStat(params),
     zookeeperGetStat(params)
@@ -548,11 +550,11 @@ const getNodeData = (path) => {
     const currentNodeListStat = results[1].data;
     const currentNodeMetadata = results[2].data;
 
-    if(currentNodeMetadata['ctime'] || currentNodeMetadata['mtime']){
-      currentNodeMetadata['ctime'] = 
moment(+currentNodeMetadata['ctime']).format(
+    if(currentNodeMetadata.ctime || currentNodeMetadata.mtime){
+      currentNodeMetadata.ctime = moment(+currentNodeMetadata.ctime).format(
         'MMMM Do YYYY, h:mm:ss'
       );
-      currentNodeMetadata['mtime'] = 
moment(+currentNodeMetadata['mtime']).format(
+      currentNodeMetadata.mtime = moment(+currentNodeMetadata.mtime).format(
         'MMMM Do YYYY, h:mm:ss'
       );
     }
@@ -586,6 +588,36 @@ const getServerOfTenant = (tenantName) => {
   });
 };
 
+const updateTags = (instanceName, tagsList) => {
+  return updateInstanceTags(instanceName, 
tagsList.toString()).then((response)=>{
+    return response.data;
+  });
+};
+
+const toggleInstanceState = (instanceName, state) => {
+  return setInstanceState(instanceName, state).then((response)=>{
+    return response.data;
+  });
+};
+
+const deleteInstance = (instanceName) => {
+  return dropInstance(instanceName).then((response)=>{
+    return response.data;
+  });
+};
+
+const reloadSegmentOp = (tableName, segmentName) => {
+  return reloadSegment(tableName, segmentName).then((response)=>{
+    return response.data;
+  });
+};
+
+const deleteSegmentOp = (tableName, segmentName) => {
+  return deleteSegment(tableName, segmentName).then((response)=>{
+    return response.data;
+  });
+};
+
 export default {
   getTenantsData,
   getAllInstances,
@@ -603,11 +635,17 @@ export default {
   getLiveInstance,
   getLiveInstanceConfig,
   getInstanceConfig,
-  getTenantsFromInstance,
+  getInstanceDetails,
+  updateInstanceDetails,
   getZookeeperData,
   getNodeData,
   putNodeData,
   deleteNode,
   getBrokerOfTenant,
-  getServerOfTenant
+  getServerOfTenant,
+  updateTags,
+  toggleInstanceState,
+  deleteInstance,
+  deleteSegmentOp,
+  reloadSegmentOp
 };
diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx 
b/pinot-controller/src/main/resources/app/utils/Utils.tsx
index c859a96..aaf698e 100644
--- a/pinot-controller/src/main/resources/app/utils/Utils.tsx
+++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx
@@ -82,7 +82,7 @@ const getSegmentStatus = (idealStateObj, externalViewObj) => {
 
 const findNestedObj = (entireObj, keyToFind, valToFind) => {
   let foundObj;
-  JSON.stringify(entireObj, (_, nestedValue) => {
+  JSON.stringify(entireObj, (a, nestedValue) => {
     if (nestedValue && nestedValue[keyToFind] === valToFind) {
       foundObj = nestedValue;
     }
@@ -231,7 +231,7 @@ const codeMirrorOptionsTemplate = (el, data) => {
 const serialize = (obj: any, prefix?: any) => {
   let str = [], p;
   for (p in obj) {
-    if (obj.hasOwnProperty(p)) {
+    if (Object.prototype.hasOwnProperty.call(obj, p)) {
       var k = prefix ? prefix + "[" + p + "]" : p,
         v = obj[p];
       str.push((v !== null && typeof v === "object") ?
@@ -240,7 +240,7 @@ const serialize = (obj: any, prefix?: any) => {
     }
   }
   return str.join("&");
-}
+};
 
 export default {
   sortArray,
diff --git a/pinot-controller/src/main/resources/package.json 
b/pinot-controller/src/main/resources/package.json
index 6006a25..791309e 100644
--- a/pinot-controller/src/main/resources/package.json
+++ b/pinot-controller/src/main/resources/package.json
@@ -1,7 +1,7 @@
 {
   "name": "pinot-controller-ui",
   "version": "1.0.0",
-  "description": "",
+  "description": "Pinot Controller UI",
   "scripts": {
     "dev": "webpack-dev-server --config ./webpack.config.js --mode 
development",
     "start": "npm-run-all --parallel lint dev",
@@ -57,17 +57,21 @@
     "@material-ui/icons": "^4.9.1",
     "@material-ui/lab": "^4.0.0-alpha.51",
     "@types/codemirror": "0.0.97",
+    "@types/react": "16.9.34",
     "@types/react-dom": "16.9.6",
-    "@types/react-router-dom": "^5.1.5",
     "@types/react-router": "^5.1.8",
-    "@types/react": "16.9.34",
+    "@types/react-router-dom": "^5.1.5",
     "axios": "^0.19.2",
+    "clsx": "^1.1.1",
     "codemirror": "^5.55.0",
     "cross-fetch": "^3.0.4",
     "export-from-json": "^1.3.0",
+    "file": "^0.2.2",
     "file-loader": "^6.0.0",
+    "fs": "0.0.1-security",
     "html-loader": "0.5.5",
     "html-webpack-plugin": "^4.2.1",
+    "jsonlint": "^1.6.3",
     "lodash": "^4.17.17",
     "moment": "^2.27.0",
     "prop-types": "^15.7.2",
@@ -76,6 +80,7 @@
     "react-dom": "16.13.1",
     "react-router-dom": "^5.2.0",
     "react-spring": "^8.0.27",
+    "system": "^2.0.1",
     "url-loader": "^4.1.0",
     "webpack-dev-server": "3.2.1"
   }
diff --git a/pinot-controller/src/main/resources/webpack.config.js 
b/pinot-controller/src/main/resources/webpack.config.js
index 04c60e0..82aa04a 100644
--- a/pinot-controller/src/main/resources/webpack.config.js
+++ b/pinot-controller/src/main/resources/webpack.config.js
@@ -25,6 +25,9 @@ module.exports = (env, argv) => {
 
   return {
     mode: !devMode ? 'production' : 'development',
+    node: {
+      fs: 'empty'
+    },
 
     // Enable sourcemaps for debugging webpack's output.
     devtool: 'source-map',


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to