Repository: GoogleCloudDataproc/dataproc-jupyter-plugin (language: python)

Issue:
Merging code-review to main



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 68bc17f9..5ea04662 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,24 @@
+*.bundle.*
+lib/
+node_modules/
+*.log
+.eslintcache
+.stylelintcache
+*.egg-info/
+.ipynb_checkpoints
+*.tsbuildinfo
+dataproc_plugin/labextension
+# Version file is handled by hatchling
+dataproc_plugin/_version.py
+
+# Integration tests
+ui-tests/test-results/
+ui-tests/playwright-report/
+
+# Created by https://www.gitignore.io/api/python
+# Edit at https://www.gitignore.io/?templates=python
+
+### Python ###
 # Byte-compiled / optimized / DLL files
 __pycache__/
 *.py[cod]
@@ -20,8 +41,8 @@ parts/
 sdist/
 var/
 wheels/
+pip-wheel-metadata/
 share/python-wheels/
-*.egg-info/
 .installed.cfg
 *.egg
 MANIFEST
@@ -44,27 +65,16 @@ htmlcov/
 .coverage.*
 .cache
 nosetests.xml
+coverage/
 coverage.xml
 *.cover
-*.py,cover
 .hypothesis/
 .pytest_cache/
-cover/
 
 # Translations
 *.mo
 *.pot
 
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
 # Scrapy stuff:
 .scrapy
 
@@ -72,62 +82,17 @@ instance/
 docs/_build/
 
 # PyBuilder
-.pybuilder/
 target/
 
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
 # pyenv
-#   For a library or package, you might want to ignore these files since the code is
-#   intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-#   However, in case of collaboration, if having platform-specific dependencies or dependencies
-#   having no cross-platform support, pipenv may install dependencies that don't work, or not
-#   install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-#   This is especially recommended for binary packages to ensure reproducibility, and is more
-#   commonly ignored for libraries.
-#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-#   in version control.
-#   https://pdm.fming.dev/#use-with-ide
-.pdm.toml
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
+.python-version
+
+# celery beat schedule file
 celerybeat-schedule
-celerybeat.pid
 
 # SageMath parsed files
 *.sage.py
 
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
 # Spyder project settings
 .spyderproject
 .spyproject
@@ -135,6 +100,11 @@ venv.bak/
 # Rope project settings
 .ropeproject
 
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
 # mkdocs documentation
 /site
 
@@ -146,15 +116,10 @@ dmypy.json
 # Pyre type checker
 .pyre/
 
-# pytype static type analyzer
-.pytype/
+# End of https://www.gitignore.io/api/python
 
-# Cython debug symbols
-cython_debug/
+# OSX files
+.DS_Store
 
-# PyCharm
-#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-#  and can be added to the global gitignore or merged into this file.  For a more nuclear
-#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
+# Yarn cache
+.yarn/
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..391b0424
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+node_modules
+**/node_modules
+**/lib
+**/package.json
+!/package.json
+dataproc_plugin
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 00000000..fe1125f5
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1,3 @@
+enableImmutableInstalls: false
+
+nodeLinker: node-modules
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..2d352af4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+<!-- <START NEW CHANGELOG ENTRY> -->
+
+<!-- <END NEW CHANGELOG ENTRY> -->
diff --git a/README.md b/README.md
index 8ed54b9c..0e9a782b 100644
--- a/README.md
+++ b/README.md
@@ -1,1 +1,137 @@
-# dataproc-jupyter-plugin
\ No newline at end of file
+# dataproc_plugin
+
+It is a plugin to work with dataproc services in Jupyterlab
+
+This extension is composed of a Python package named `dataproc_plugin`
+for the server extension and a NPM package named `dataproc_plugin`
+for the frontend extension.
+
+## Requirements
+
+- JupyterLab >= 4.0.0
+
+## Install
+
+To install the extension, execute:
+
+```bash
+pip install dataproc_plugin
+```
+
+## Uninstall
+
+To remove the extension, execute:
+
+```bash
+pip uninstall dataproc_plugin
+```
+
+## Troubleshoot
+
+If you are seeing the frontend extension, but it is not working, check
+that the server extension is enabled:
+
+```bash
+jupyter server extension list
+```
+
+If the server extension is installed and enabled, but you are not seeing
+the frontend extension, check the frontend extension is installed:
+
+```bash
+jupyter labextension list
+```
+
+## Contributing
+
+### Development install
+
+Note: You will need NodeJS to build the extension package.
+
+The `jlpm` command is JupyterLab's pinned version of
+[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use
+`yarn` or `npm` in lieu of `jlpm` below.
+
+```bash
+# Clone the repo to your local environment
+# Change directory to the dataproc_plugin directory
+# Install package in development mode
+pip install -e ".[test]"
+# Link your development version of the extension with JupyterLab
+jupyter labextension develop . --overwrite
+# Server extension must be manually installed in develop mode
+jupyter server extension enable dataproc_plugin
+# Rebuild extension Typescript source after making changes
+jlpm build
+```
+
+You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.
+
+```bash
+# Watch the source directory in one terminal, automatically rebuilding when needed
+jlpm watch
+# Run JupyterLab in another terminal
+jupyter lab
+```
+
+With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt).
+
+By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command:
+
+```bash
+jupyter lab build --minimize=False
+```
+
+### Development uninstall
+
+```bash
+# Server extension must be manually disabled in develop mode
+jupyter server extension disable dataproc_plugin
+pip uninstall dataproc_plugin
+```
+
+In development mode, you will also need to remove the symlink created by `jupyter labextension develop`
+command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions`
+folder is located. Then you can remove the symlink named `dataproc_plugin` within that folder.
+
+### Testing the extension
+
+#### Server tests
+
+This extension is using [Pytest](https://docs.pytest.org/) for Python code testing.
+
+Install test dependencies (needed only once):
+
+```sh
+pip install -e ".[test]"
+# Each time you install the Python package, you need to restore the front-end extension link
+jupyter labextension develop . --overwrite
+```
+
+To execute them, run:
+
+```sh
+pytest -vv -r ap --cov dataproc_plugin
+```
+
+#### Frontend tests
+
+This extension is using [Jest](https://jestjs.io/) for JavaScript code testing.
+
+To execute them, execute:
+
+```sh
+jlpm
+jlpm test
+```
+
+#### Integration tests
+
+This extension uses [Playwright](https://playwright.dev/docs/intro/) for the integration tests (aka user level tests).
+More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab.
+
+More information are provided within the [ui-tests](./ui-tests/README.md) README.
+
+### Packaging the extension
+
+See [RELEASE](RELEASE.md)
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 00000000..531198b7
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,77 @@
+# Making a new release of dataproc_plugin
+
+The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser).
+
+## Manual release
+
+### Python package
+
+This extension can be distributed as Python packages. All of the Python
+packaging instructions are in the `pyproject.toml` file to wrap your extension in a
+Python package. Before generating a package, you first need to install some tools:
+
+```bash
+pip install build twine hatch
+```
+
+Bump the version using `hatch`. By default this will create a tag.
+See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details.
+
+```bash
+hatch version <new-version>
+```
+
+Make sure to clean up all the development files before building the package:
+
+```bash
+jlpm clean:all
+```
+
+You could also clean up the local git repository:
+
+```bash
+git clean -dfX
+```
+
+To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do:
+
+```bash
+python -m build
+```
+
+> `python setup.py sdist bdist_wheel` is deprecated and will not work for this package.
+
+Then to upload the package to PyPI, do:
+
+```bash
+twine upload dist/*
+```
+
+### NPM package
+
+To publish the frontend part of the extension as a NPM package, do:
+
+```bash
+npm login
+npm publish --access public
+```
+
+## Automated releases with the Jupyter Releaser
+
+The extension repository should already be compatible with the Jupyter Releaser.
+
+Check out the [workflow documentation](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html) for more information.
+
+Here is a summary of the steps to cut a new release:
+
+- Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the [Github Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the repository
+- Go to the Actions panel
+- Run the "Step 1: Prep Release" workflow
+- Check the draft changelog
+- Run the "Step 2: Publish Release" workflow
+
+## Publishing to `conda-forge`
+
+If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html
+
+Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically.
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 00000000..bbe1464d
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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.
+ */
+
+module.exports = require('@jupyterlab/testutils/lib/babel.config');
diff --git a/binder/environment.yml b/binder/environment.yml
new file mode 100644
index 00000000..3bb8505e
--- /dev/null
+++ b/binder/environment.yml
@@ -0,0 +1,21 @@
+# a mybinder.org-ready environment for demoing dataproc_plugin
+# this environment may also be used locally on Linux/MacOS/Windows, e.g.
+#
+#   conda env update --file binder/environment.yml
+#   conda activate dataproc-plugin-demo
+#
+name: dataproc-plugin-demo
+
+channels:
+  - conda-forge
+
+dependencies:
+  # runtime dependencies
+  - python >=3.10,<3.11.0a0
+  - jupyterlab >=4.0.0,<5
+  # labextension build dependencies
+  - nodejs >=18,<19
+  - pip
+  - wheel
+  # additional packages for demos
+  # - ipywidgets
diff --git a/binder/postBuild b/binder/postBuild
new file mode 100755
index 00000000..ed944b78
--- /dev/null
+++ b/binder/postBuild
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+""" perform a development install of dataproc_plugin
+
+    On Binder, this will run _after_ the environment has been fully created from
+    the environment.yml in this directory.
+
+    This script should also run locally on Linux/MacOS/Windows:
+
+        python3 binder/postBuild
+"""
+import subprocess
+import sys
+from pathlib import Path
+
+
+ROOT = Path.cwd()
+
+def _(*args, **kwargs):
+    """ Run a command, echoing the args
+
+        fails hard if something goes wrong
+    """
+    print("\n\t", " ".join(args), "\n")
+    return_code = subprocess.call(args, **kwargs)
+    if return_code != 0:
+        print("\nERROR", return_code, " ".join(args))
+        sys.exit(return_code)
+
+# verify the environment is self-consistent before even starting
+_(sys.executable, "-m", "pip", "check")
+
+# install the labextension
+_(sys.executable, "-m", "pip", "install", "-e", ".")
+_(sys.executable, "-m", "jupyter", "labextension", "develop", "--overwrite", ".")
+_(
+    sys.executable,
+    "-m",
+    "jupyter",
+    "server",
+    "extension",
+    "enable",
+    "dataproc_plugin",
+)
+
+# verify the environment the extension didn't break anything
+_(sys.executable, "-m", "pip", "check")
+
+# list the extensions
+_("jupyter", "server", "extension", "list")
+
+# initially list installed extensions to determine if there are any surprises
+_("jupyter", "labextension", "list")
+
+
+print("JupyterLab with dataproc_plugin is ready to run with:\n")
+print("\tjupyter lab\n")
diff --git a/contributing.md b/contributing.md
new file mode 100644
index 00000000..8956a61b
--- /dev/null
+++ b/contributing.md
@@ -0,0 +1,32 @@
+# How to Contribute
+
+We would love to accept your patches and contributions to this project.
+
+## Before you begin
+
+### Sign our Contributor License Agreement
+
+Contributions to this project must be accompanied by a
+[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
+You (or your employer) retain the copyright to your contribution; this simply
+gives us permission to use and redistribute your contributions as part of the
+project.
+
+If you or your current employer have already signed the Google CLA (even if it
+was for a different project), you probably don't need to do it again.
+
+Visit <https://cla.developers.google.com/> to see your current agreements or to
+sign a new one.
+
+### Review our Community Guidelines
+
+This project follows [Google's Open Source Community
+Guidelines](https://opensource.google/conduct/).
+
+## Contribution process
+
+### Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests)
+for this purpose.
diff --git a/dataproc_plugin/__init__.py b/dataproc_plugin/__init__.py
new file mode 100644
index 00000000..68eef899
--- /dev/null
+++ b/dataproc_plugin/__init__.py
@@ -0,0 +1,70 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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.from ._version import __version__
+
+from google.cloud.jupyter_config.tokenrenewer import CommandTokenRenewer
+
+from kernels_mixer.kernelspecs import MixingKernelSpecManager
+from kernels_mixer.kernels import MixingMappingKernelManager
+from kernels_mixer.websockets import DelegatingWebsocketConnection
+
+from jupyter_server.services.sessions.sessionmanager import SessionManager
+
+
+from .handlers import setup_handlers, update_gateway_client_url
+
+
+def _jupyter_labextension_paths():
+    return [{
+        "src": "labextension",
+        "dest": "dataproc_plugin"
+    }]
+
+
+def _jupyter_server_extension_points():
+    return [{
+        "module": "dataproc_plugin"
+    }]
+
+
+def _link_jupyter_server_extension(server_app):
+    c = server_app.config
+    c.ServerApp.kernel_spec_manager_class = MixingKernelSpecManager
+    c.ServerApp.kernel_manager_class = MixingMappingKernelManager
+    c.ServerApp.session_manager_class = SessionManager
+    c.ServerApp.kernel_websocket_connection_class = (
+        DelegatingWebsocketConnection)
+    c.GatewayClient.auth_scheme = 'Bearer'
+    c.GatewayClient.headers = '{"Cookie": "_xsrf=XSRF", "X-XSRFToken": "XSRF"}'
+    c.GatewayClient.gateway_token_renewer_class = CommandTokenRenewer
+    c.CommandTokenRenewer.token_command = (
+        'gcloud config config-helper --format="value(credential.access_token)"')
+
+    update_gateway_client_url(c, server_app.log)
+
+
+def _load_jupyter_server_extension(server_app):
+    """Registers the API handler to receive HTTP requests from the frontend extension.
+
+    Parameters
+    ----------
+    server_app: jupyterlab.labapp.LabApp
+        JupyterLab application instance
+    """
+    setup_handlers(server_app.web_app)
+    name = "dataproc_plugin"
+    server_app.log.info(f"Registered {name} server extension")
+
+
+# For backward compatibility with notebook server - useful for Binder/JupyterHub
+load_jupyter_server_extension = _load_jupyter_server_extension
diff --git a/dataproc_plugin/handlers.py b/dataproc_plugin/handlers.py
new file mode 100644
index 00000000..5a925476
--- /dev/null
+++ b/dataproc_plugin/handlers.py
@@ -0,0 +1,198 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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 json
+from jupyter_server.base.handlers import APIHandler
+from jupyter_server.utils import url_path_join
+import tornado
+import subprocess
+from datetime import datetime
+from datetime import datetime
+from cachetools import TTLCache
+
+from google.cloud.jupyter_config.config import gcp_kernel_gateway_url
+
+
+def update_gateway_client_url(c, log):
+    try:
+        kernel_gateway_url = gcp_kernel_gateway_url()
+    except subprocess.SubprocessError as e:
+        log.warning(f"Error constructing the kernel gateway URL; configure your project, region, and credentials using gcloud: {e}")
+        return
+    log.info(f"Updating remote kernel gateway URL to {kernel_gateway_url}")
+    c.GatewayClient.url = kernel_gateway_url
+
+
+credentials_cache = None
+
+def get_cached_credentials():
+    global credentials_cache
+    
+    try:
+        cmd = "gcloud config config-helper --format=json"
+        process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+        output, error = process.communicate()
+        if process.returncode == 0:
+            config_data = json.loads(output)
+            credentials = {
+                'project_id': config_data['configuration']['properties']['core']['project'],
+                'region_id': config_data['configuration']['properties']['compute']['region'],
+                'access_token': config_data['credential']['access_token']
+            }
+
+            token_expiry = config_data['credential']['token_expiry']
+            utc_datetime = datetime.strptime(token_expiry, '%Y-%m-%dT%H:%M:%SZ')
+            credentials_cache = TTLCache(maxsize=1, ttl=(utc_datetime - datetime.utcnow()).total_seconds())
+            credentials_cache['credentials'] = credentials
+            return credentials
+        else:
+            cmd = "gcloud config get-value account"
+            process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+            output, error = process.communicate()
+            if output == '':
+                cmd = "gcloud config get-value project"
+                process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+                project, error = process.communicate()
+                if project == '':
+                    credentials = {
+                'project_id': '',
+                'region_id': '',
+                'access_token': '',
+                'config_error': 1,
+                'login_error': 0
+                }
+                else:
+                    cmd = "gcloud config get-value compute/region"
+                    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+                    region, error = process.communicate()
+                    if region == '':
+                        credentials = {
+                        'project_id': '',
+                        'region_id': '',
+                        'access_token': '',
+                        'config_error': 1,
+                        'login_error': 0
+                     }
+                    else:
+                        credentials = {
+                        'project_id': '',
+                        'region_id': '',
+                        'access_token': '',
+                        'config_error': 0,
+                        'login_error': 1
+                        }
+            else:
+                credentials = {
+                'project_id': '',
+                'region_id': '',
+                'access_token': '',
+                'config_error': 1,
+                'login_error': 0
+                }
+            credentials_cache = TTLCache(maxsize=1, ttl=5)
+            credentials_cache['credentials'] = credentials
+            return credentials
+    except Exception:
+        credentials = {
+            'access_token': '',
+            'config_error': 1
+        }
+        credentials_cache = TTLCache(maxsize=1, ttl=2)
+        credentials_cache['credentials'] = credentials
+
+        return credentials
+
+class TestHandler(APIHandler):
+    # The following decorator should be present on all verb methods (head, get, post,
+    # patch, put, delete, options) to ensure only authorized user can request the
+    # Jupyter server
+    @tornado.web.authenticated
+    def get(self):
+        self.finish(json.dumps({
+            "data": "This is /dataproc-plugin/get-example endpoint!"
+        }))
+
+class RouteHandler(APIHandler):
+    # The following decorator should be present on all verb methods (head, get, post,
+    # patch, put, delete, options) to ensure only authorized user can request the
+    # Jupyter server
+    credentials_cache = None
+    @tornado.web.authenticated
+    def get(self):
+        try:
+            if credentials_cache is None or 'credentials' not in credentials_cache:
+                cached_credentials = get_cached_credentials()
+                self.finish(json.dumps(cached_credentials))
+               
+            else:
+                self.finish(json.dumps(credentials_cache['credentials']))
+        except Exception:
+            print("exception gcloud")
+            cached_credentials = get_cached_credentials()
+            self.finish(json.dumps(cached_credentials))
+
+class LoginHandler(APIHandler):
+    @tornado.web.authenticated
+    def get(self):
+        cmd = "gcloud auth login"
+        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+        output, _ = process.communicate()
+        # Check if the authentication was successful
+        if process.returncode == 0:
+            self.finish({'login' : 'SUCCEEDED'})
+        else:
+            self.finish({'login' : 'FAILED'})
+
+class ConfigHandler(APIHandler):
+    @tornado.web.authenticated
+    def post(self):
+        ERROR_MESSAGE = "Project and region update "
+        global credentials_cache
+        input_data = self.get_json_body()
+        project_id = input_data["projectId"]
+        region = input_data["region"]
+        cmd = 'gcloud config set project '+project_id
+        project_set = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+        output, _ = project_set.communicate()
+        if project_set.returncode == 0:
+            cmd = 'gcloud config set compute/region '+region
+            region_set = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+            output, _ = region_set.communicate()
+            if region_set.returncode == 0:
+                credentials_cache = None
+                update_gateway_client_url(self.config, self.log)
+                self.finish({'config' : ERROR_MESSAGE + 'successful'})
+            else:
+                self.finish({'config' : ERROR_MESSAGE + 'failed'})
+        else:
+            self.finish({'config' : ERROR_MESSAGE + 'failed'})
+
+
+def setup_handlers(web_app):
+    host_pattern = ".*$"
+
+    base_url = web_app.settings["base_url"]
+
+    # Prepend the base_url so that it works in a JupyterHub setting
+    route_pattern = url_path_join(base_url, "dataproc-plugin", "credentials")
+    handlers = [(route_pattern, RouteHandler)]
+    web_app.add_handlers(host_pattern, handlers)
+    
+    route_pattern = url_path_join(base_url, "dataproc-plugin", "login")
+    handlers = [(route_pattern, LoginHandler)]
+    web_app.add_handlers(host_pattern, handlers)
+
+    route_pattern = url_path_join(base_url, "dataproc-plugin", "configuration")
+    handlers = [(route_pattern, ConfigHandler)]
+    web_app.add_handlers(host_pattern, handlers)
diff --git a/install.json b/install.json
new file mode 100644
index 00000000..4611bbd4
--- /dev/null
+++ b/install.json
@@ -0,0 +1,5 @@
+{
+  "packageManager": "python",
+  "packageName": "dataproc_plugin",
+  "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package dataproc_plugin"
+}
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..f1773263
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,45 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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.
+ */
+
+const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config');
+
+const esModules = [
+  '@codemirror',
+  '@jupyter/ydoc',
+  '@jupyterlab/',
+  'lib0',
+  'nanoid',
+  'vscode-ws-jsonrpc',
+  'y-protocols',
+  'y-websocket',
+  'yjs'
+].join('|');
+
+const baseConfig = jestJupyterLab(__dirname);
+
+module.exports = {
+  ...baseConfig,
+  automock: false,
+  collectCoverageFrom: [
+    'src/**/*.{ts,tsx}',
+    '!src/**/*.d.ts',
+    '!src/**/.ipynb_checkpoints/*'
+  ],
+  coverageReporters: ['lcov', 'text'],
+  testRegex: 'src/.*/.*.spec.ts[x]?$',
+  transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`]
+};
diff --git a/jupyter-config/nb-config/dataproc_plugin.json b/jupyter-config/nb-config/dataproc_plugin.json
new file mode 100644
index 00000000..cf22466a
--- /dev/null
+++ b/jupyter-config/nb-config/dataproc_plugin.json
@@ -0,0 +1,7 @@
+{
+  "NotebookApp": {
+    "nbserver_extensions": {
+      "dataproc_plugin": true
+    }
+  }
+}
diff --git a/jupyter-config/server-config/dataproc_plugin.json b/jupyter-config/server-config/dataproc_plugin.json
new file mode 100644
index 00000000..66828bc7
--- /dev/null
+++ b/jupyter-config/server-config/dataproc_plugin.json
@@ -0,0 +1,7 @@
+{
+  "ServerApp": {
+    "jpserver_extensions": {
+      "dataproc_plugin": true
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..562ea5bb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,213 @@
+{
+  "name": "dataproc_plugin",
+  "version": "1.0.8",
+  "description": "It is a plugin to work with dataproc services in Jupyterlab",
+  "keywords": [
+    "jupyter",
+    "jupyterlab",
+    "jupyterlab-extension"
+  ],
+  "homepage": "https://github.com/GoogleCloudDataproc/dataproc-jupyter-plugin",
+  "bugs": {
+    "url": "https://github.com/GoogleCloudDataproc/dataproc-jupyter-plugin/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "google",
+    "email": "dataproc-plugins@google.com"
+  },
+  "files": [
+    "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
+    "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/*.json"
+  ],
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "style": "style/index.css",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/GoogleCloudDataproc/dataproc-jupyter-plugin.git"
+  },
+  "workspaces": [
+    "ui-tests"
+  ],
+  "scripts": {
+    "build": "jlpm build:lib && jlpm build:labextension:dev",
+    "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
+    "build:labextension": "jupyter labextension build .",
+    "build:labextension:dev": "jupyter labextension build --development True .",
+    "build:lib": "tsc --sourceMap",
+    "build:lib:prod": "tsc",
+    "clean": "jlpm clean:lib",
+    "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
+    "clean:lintcache": "rimraf .eslintcache .stylelintcache",
+    "clean:labextension": "rimraf dataproc_plugin/labextension dataproc_plugin/_version.py",
+    "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
+    "eslint": "jlpm eslint:check --fix",
+    "eslint:check": "eslint . --cache --ext .ts,.tsx",
+    "install:extension": "jlpm build",
+    "lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
+    "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
+    "prettier": "jlpm prettier:base --write --list-different",
+    "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
+    "prettier:check": "jlpm prettier:base --check",
+    "stylelint": "jlpm stylelint:check --fix",
+    "stylelint:check": "stylelint --cache \"style/**/*.css\"",
+    "test": "jest --coverage",
+    "watch": "run-p watch:src watch:labextension",
+    "watch:src": "tsc -w --sourceMap",
+    "watch:labextension": "jupyter labextension watch ."
+  },
+  "dependencies": {
+    "@jupyterlab/application": "^4.0.0",
+    "@jupyterlab/coreutils": "^6.0.0",
+    "@jupyterlab/launcher": "^4.0.0",
+    "@jupyterlab/mainmenu": "^4.0.0",
+    "@jupyterlab/services": "^7.0.0",
+    "@jupyterlab/settingregistry": "^4.0.0",
+    "@types/uuid": "^9.0.2",
+    "react-arborist": "^3.1.0",
+    "react-spinners": "^0.13.8",
+    "react-table": "^7.8.0",
+    "react-tagsinput": "^3.20.3",
+    "react-toastify": "^9.1.3",
+    "semantic-ui-css": "^2.5.0",
+    "semantic-ui-react": "^2.1.4"
+  },
+  "devDependencies": {
+    "@jupyterlab/builder": "^4.0.0",
+    "@jupyterlab/testutils": "^4.0.0",
+    "@types/hoist-non-react-statics": "^3.3.1",
+    "@types/jest": "^29.2.0",
+    "@types/json-schema": "^7.0.11",
+    "@types/react": "^18.0.26",
+    "@types/react-table": "^7.7.14",
+    "@types/react-tagsinput": "^3.20.0",
+    "@types/react-window": "^1.8.5",
+    "@typescript-eslint/eslint-plugin": "^5.55.0",
+    "@typescript-eslint/parser": "^5.55.0",
+    "css-loader": "^6.7.1",
+    "eslint": "^8.36.0",
+    "eslint-config-prettier": "^8.7.0",
+    "eslint-plugin-prettier": "^4.2.1",
+    "generate-license-file": "^2.0.0",
+    "jest": "^29.2.0",
+    "mkdirp": "^1.0.3",
+    "npm-run-all": "^4.1.5",
+    "prettier": "^2.8.7",
+    "rimraf": "^4.4.1",
+    "source-map-loader": "^1.0.2",
+    "style-loader": "^3.3.1",
+    "stylelint": "^14.9.1",
+    "stylelint-config-prettier": "^9.0.4",
+    "stylelint-config-recommended": "^8.0.0",
+    "stylelint-config-standard": "^26.0.0",
+    "stylelint-prettier": "^2.0.0",
+    "typescript": "~5.0.2",
+    "yjs": "^13.5.0"
+  },
+  "sideEffects": [
+    "style/*.css",
+    "style/index.js"
+  ],
+  "styleModule": "style/index.js",
+  "publishConfig": {
+    "access": "public"
+  },
+  "jupyterlab": {
+    "discovery": {
+      "server": {
+        "managers": [
+          "pip"
+        ],
+        "base": {
+          "name": "dataproc_plugin"
+        }
+      }
+    },
+    "extension": true,
+    "outputDir": "dataproc_plugin/labextension",
+    "schemaDir": "schema"
+  },
+  "eslintIgnore": [
+    "node_modules",
+    "dist",
+    "coverage",
+    "**/*.d.ts",
+    "tests",
+    "**/__tests__",
+    "ui-tests"
+  ],
+  "eslintConfig": {
+    "extends": [
+      "eslint:recommended",
+      "plugin:@typescript-eslint/eslint-recommended",
+      "plugin:@typescript-eslint/recommended",
+      "plugin:prettier/recommended"
+    ],
+    "parser": "@typescript-eslint/parser",
+    "parserOptions": {
+      "project": "tsconfig.json",
+      "sourceType": "module"
+    },
+    "plugins": [
+      "@typescript-eslint"
+    ],
+    "rules": {
+      "@typescript-eslint/naming-convention": [
+        "error",
+        {
+          "selector": "interface",
+          "format": [
+            "PascalCase"
+          ],
+          "custom": {
+            "regex": "^I[A-Z]",
+            "match": true
+          }
+        }
+      ],
+      "@typescript-eslint/no-unused-vars": [
+        "warn",
+        {
+          "args": "none"
+        }
+      ],
+      "@typescript-eslint/no-explicit-any": "off",
+      "@typescript-eslint/no-namespace": "off",
+      "@typescript-eslint/no-use-before-define": "off",
+      "@typescript-eslint/quotes": [
+        "error",
+        "single",
+        {
+          "avoidEscape": true,
+          "allowTemplateLiterals": false
+        }
+      ],
+      "curly": [
+        "error",
+        "all"
+      ],
+      "eqeqeq": "error",
+      "prefer-arrow-callback": "error"
+    }
+  },
+  "prettier": {
+    "singleQuote": true,
+    "trailingComma": "none",
+    "arrowParens": "avoid",
+    "endOfLine": "auto"
+  },
+  "stylelint": {
+    "extends": [
+      "stylelint-config-recommended",
+      "stylelint-config-standard",
+      "stylelint-prettier/recommended"
+    ],
+    "rules": {
+      "property-no-vendor-prefix": null,
+      "selector-no-vendor-prefix": null,
+      "value-no-vendor-prefix": null
+    }
+  }
+}
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..47d16242
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,93 @@
+[build-system]
+requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5", "hatch-nodejs-version"]
+build-backend = "hatchling.build"
+
+[project]
+name = "dataproc_plugin"
+readme = "README.md"
+license = { file = "LICENSE" }
+requires-python = ">=3.8"
+classifiers = [
+    "Framework :: Jupyter",
+    "Framework :: Jupyter :: JupyterLab",
+    "Framework :: Jupyter :: JupyterLab :: 4",
+    "Framework :: Jupyter :: JupyterLab :: Extensions",
+    "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
+    "License :: OSI Approved :: BSD License",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+]
+dependencies = [
+    "jupyter_server>=2.0.1,<3",
+    "cachetools>=5.0.0",
+    "kernels-mixer",
+]
+dynamic = ["version", "description", "authors", "urls", "keywords"]
+
+[project.optional-dependencies]
+test = [
+    "coverage",
+    "pytest",
+    "pytest-asyncio",
+    "pytest-cov",
+    "pytest-jupyter[server]>=0.6.0"
+]
+
+[tool.hatch.version]
+source = "nodejs"
+
+[tool.hatch.metadata.hooks.nodejs]
+fields = ["description", "authors", "urls"]
+
+[tool.hatch.metadata]
+allow-direct-references = true
+
+[tool.hatch.build.targets.sdist]
+artifacts = ["dataproc_plugin/labextension"]
+exclude = [".github", "binder"]
+
+[tool.hatch.build.targets.wheel.shared-data]
+"dataproc_plugin/labextension" = "share/jupyter/labextensions/dataproc_plugin"
+"install.json" = "share/jupyter/labextensions/dataproc_plugin/install.json"
+"jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d"
+"jupyter-config/nb-config" = "etc/jupyter/jupyter_notebook_config.d"
+
+[tool.hatch.build.hooks.version]
+path = "dataproc_plugin/_version.py"
+
+[tool.hatch.build.hooks.jupyter-builder]
+dependencies = ["hatch-jupyter-builder>=0.5"]
+build-function = "hatch_jupyter_builder.npm_builder"
+ensured-targets = [
+    "dataproc_plugin/labextension/static/style.js",
+    "dataproc_plugin/labextension/package.json",
+]
+skip-if-exists = ["dataproc_plugin/labextension/static/style.js"]
+
+[tool.hatch.build.hooks.jupyter-builder.build-kwargs]
+build_cmd = "build:prod"
+npm = ["jlpm"]
+
+[tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs]
+build_cmd = "install:extension"
+npm = ["jlpm"]
+source_dir = "src"
+build_dir = "dataproc_plugin/labextension"
+
+[tool.jupyter-releaser.options]
+version_cmd = "hatch version"
+
+[tool.jupyter-releaser.hooks]
+before-build-npm = [
+    "python -m pip install 'jupyterlab>=4.0.0,<5'",
+    "jlpm",
+    "jlpm build:prod"
+]
+before-build-python = ["jlpm clean:all"]
+
+[tool.check-wheel-contents]
+ignore = ["W002"]
diff --git a/schema/plugin.json b/schema/plugin.json
new file mode 100644
index 00000000..3cfd3d2c
--- /dev/null
+++ b/schema/plugin.json
@@ -0,0 +1,8 @@
+{
+  "jupyter.lab.shortcuts": [],
+  "title": "dataproc_plugin",
+  "description": "dataproc_plugin settings.",
+  "type": "object",
+  "properties": {},
+  "additionalProperties": false
+}
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..b81a9b58
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,15 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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__('setuptools').setup()
diff --git a/src/batches/batchDetails.tsx b/src/batches/batchDetails.tsx
new file mode 100644
index 00000000..2dc98d50
--- /dev/null
+++ b/src/batches/batchDetails.tsx
@@ -0,0 +1,502 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useState, useEffect, useRef } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import LeftArrowIcon from '../../style/icons/left_arrow_icon.svg';
+import CloneJobIcon from '../../style/icons/clone_job_icon.svg';
+import ViewLogs from '../utils/viewLogs';
+import DeleteClusterIcon from '../../style/icons/delete_cluster_icon.svg';
+
+import { ClipLoader } from 'react-spinners';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  NETWORK_KEY,
+  NETWORK_LABEL,
+  SERVICE_ACCOUNT_KEY,
+  SERVICE_ACCOUNT_LABEL,
+  SUBNETWORK_KEY,
+  SUBNETWORK_LABEL
+} from '../utils/const';
+import {
+  BatchTypeValue,
+  authApi,
+  convertToDCUHours,
+  convertToGBMonths,
+  elapsedTime,
+  jobTimeFormat,
+  statusMessageBatch
+} from '../utils/utils';
+import DeletePopup from '../utils/deletePopup';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { deleteBatchAPI } from '../utils/batchService';
+import { statusDisplay } from '../utils/statusDisplay';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconLeftArrow = new LabIcon({
+  name: 'launcher:left-arrow-icon',
+  svgstr: LeftArrowIcon
+});
+const iconCloneJob = new LabIcon({
+  name: 'launcher:clone-job-icon',
+  svgstr: CloneJobIcon
+});
+const iconDeleteCluster = new LabIcon({
+  name: 'launcher:delete-cluster-icon',
+  svgstr: DeleteClusterIcon
+});
+
+type BatchDetailsProps = {
+  batchSelected: string;
+  setDetailedBatchView: (flag: boolean) => void;
+};
+
+function BatchDetails({
+  batchSelected,
+  setDetailedBatchView
+}: BatchDetailsProps) {
+  const [batchInfoResponse, setBatchInfoResponse] = useState({
+    uuid: '',
+    state: '',
+    createTime: '',
+    runtimeInfo: {
+      approximateUsage: { milliDcuSeconds: '', shuffleStorageGbSeconds: '' }
+    },
+    creator: '',
+    runtimeConfig: {
+      version: '',
+      properties: {
+        'spark:spark.executor.instances': '',
+        'spark:spark.driver.cores': '',
+        'spark:spark.driver.memory': '',
+        'spark:spark.executor.cores': '',
+        'spark:spark.executor.memory': '',
+        'spark:spark.dynamicAllocation.executorAllocationRatio': '',
+        'spark:spark.app.name': ''
+      }
+    },
+    sparkBatch: {
+      mainJarFileUri: '',
+      mainClass: '',
+      jarFileUris: ''
+    },
+    pysparkBatch: {
+      mainPythonFileUri: ''
+    },
+    sparkRBatch: {
+      mainRFileUri: ''
+    },
+    sparkSqlBatch: {
+      queryFileUri: ''
+    },
+    environmentConfig: {
+      executionConfig: {
+        serviceAccount: '',
+        subnetworkUri: ''
+      }
+    },
+    stateHistory: [{ state: '', stateStartTime: '' }],
+    stateTime: ''
+  });
+  const [regionName, setRegionName] = useState('');
+  const [labelDetail, setLabelDetail] = useState(['']);
+  const [isLoading, setIsLoading] = useState(true);
+  const [deletePopupOpen, setDeletePopupOpen] = useState(false);
+  const [selectedBatch, setSelectedBatch] = useState('');
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+
+  const pollingBatchDetails = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const handleDetailedBatchView = () => {
+    pollingBatchDetails(getBatchDetails, true);
+    setDetailedBatchView(false);
+  };
+
+  useEffect(() => {
+    getBatchDetails();
+    pollingBatchDetails(getBatchDetails, false);
+
+    return () => {
+      pollingBatchDetails(getBatchDetails, true);
+    };
+  }, []);
+
+  const getBatchDetails = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      setRegionName(credentials.region_id || '');
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/batches/${batchSelected}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              setBatchInfoResponse(responseResult);
+              if (responseResult.labels) {
+                const labelValue = Object.entries(responseResult.labels).map(
+                  ([key, value]) => `${key}:${value}`
+                );
+                setLabelDetail(labelValue);
+              }
+              setIsLoading(false);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error in getting Batch details', err);
+          toast.error(`Failed to fetch batch details ${batchSelected}`);
+        });
+    }
+  };
+
+  const statusMsg = statusMessageBatch(batchInfoResponse);
+  const startTime = jobTimeFormat(batchInfoResponse.createTime);
+  const startTimeElapsed = new Date(batchInfoResponse.createTime);
+
+  const endTime = new Date(batchInfoResponse.stateTime);
+
+  let jobStartTime: Date;
+  let runTimeString = '';
+
+  if (batchInfoResponse.stateHistory) {
+    const lastStateHistory =
+      batchInfoResponse.stateHistory[batchInfoResponse.stateHistory.length - 1];
+    jobStartTime = new Date(lastStateHistory.stateStartTime);
+    runTimeString = elapsedTime(endTime, jobStartTime);
+  }
+
+  const batch = BatchTypeValue(batchInfoResponse);
+
+  const elapsedTimeString = elapsedTime(
+    new Date(batchInfoResponse.stateTime),
+    startTimeElapsed
+  );
+
+  const handleDeleteBatch = (batchSelected: string) => {
+    setSelectedBatch(batchSelected);
+    setDeletePopupOpen(true);
+  };
+  const handleCancelDelete = () => {
+    setDeletePopupOpen(false);
+  };
+
+  const handleDelete = async () => {
+    await deleteBatchAPI(selectedBatch);
+    handleDetailedBatchView();
+    setDeletePopupOpen(false);
+  };
+
+  return (
+    <div>
+      <ToastContainer />
+      {batchInfoResponse.uuid === '' && (
+        <div className="loader-full-style">
+          {isLoading && (
+            <div>
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={20}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+              Loading Batch Details
+            </div>
+          )}
+        </div>
+      )}
+      {deletePopupOpen && (
+        <DeletePopup
+          onCancel={() => handleCancelDelete()}
+          onDelete={() => handleDelete()}
+          deletePopupOpen={deletePopupOpen}
+          DeleteMsg={
+            'This will delete ' + selectedBatch + ' and cannot be undone.'
+          }
+        />
+      )}
+      {batchInfoResponse.uuid !== '' && (
+        <div className="scroll-comp">
+          <div className="cluster-details-header">
+            <div
+              role="button"
+              className="back-arrow-icon"
+              onClick={() => handleDetailedBatchView()}
+            >
+              <iconLeftArrow.react tag="div" />
+            </div>
+            <div className="cluster-details-title">{batchSelected}</div>
+            <div className="action-disabled action-cluster-section">
+              <div className="action-cluster-icon">
+                <iconCloneJob.react tag="div" />
+              </div>
+              <div className="action-cluster-text">CLONE</div>
+            </div>
+
+            <div
+              role="button"
+              className="action-cluster-section"
+              onClick={() => handleDeleteBatch(batchSelected)}
+            >
+              <div className="action-cluster-icon">
+                <iconDeleteCluster.react tag="div" />
+              </div>
+              <div className="action-cluster-text">DELETE</div>
+            </div>
+            <ViewLogs batchInfoResponse={batchInfoResponse} />
+          </div>
+
+          <div className="batch-details-container-top">
+            <div className="row-details"></div>
+            <div className="row-details">
+              <div className="details-label">Batch ID</div>
+              <div className="details-value">{batchSelected}</div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Batch UUID</div>
+              <div className="details-value">{batchInfoResponse.uuid}</div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Resource type</div>
+              <div className="details-value">Batch</div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Status</div>
+              {statusDisplay(statusMsg)}
+            </div>
+          </div>
+          <div className="cluster-details-header">
+            <div className="cluster-details-title">Details</div>
+          </div>
+          <div className="batch-details-container">
+            <div className="row-details">
+              <div className="details-label">Start time</div>
+              <div className="details-value">{startTime}</div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Elapsed time</div>
+              <div className="details-value">{elapsedTimeString}</div>
+            </div>
+            {runTimeString !== '' && (
+              <div className="row-details">
+                <div className="details-label">Run time</div>
+                <div className="details-value">{runTimeString}</div>
+              </div>
+            )}
+            {batchInfoResponse.runtimeInfo.approximateUsage &&
+              batchInfoResponse.runtimeInfo.approximateUsage
+                .milliDcuSeconds && (
+                <div className="row-details">
+                  <div className="details-label">Approximate DCU usage</div>
+                  <div className="details-value">
+                    {convertToDCUHours(
+                      batchInfoResponse.runtimeInfo.approximateUsage
+                        .milliDcuSeconds
+                    )}
+                  </div>
+                </div>
+              )}
+            {batchInfoResponse.runtimeInfo.approximateUsage &&
+              batchInfoResponse.runtimeInfo.approximateUsage
+                .shuffleStorageGbSeconds && (
+                <div className="row-details">
+                  <div className="details-label">
+                    Approximate shuffle storage usage
+                  </div>
+                  <div className="details-value">
+                    {convertToGBMonths(
+                      batchInfoResponse.runtimeInfo.approximateUsage
+                        .shuffleStorageGbSeconds
+                    )}
+                  </div>
+                </div>
+              )}
+            <div className="row-details">
+              <div className="details-label">Creator</div>
+              <div className="details-value">{batchInfoResponse.creator}</div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Version</div>
+              <div className="details-value">
+                {batchInfoResponse.runtimeConfig.version}
+              </div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Region</div>
+              <div className="details-value">{regionName}</div>
+            </div>
+            <div className="row-details">
+              <div className="details-label">Batch type</div>
+              <div className="details-value">{batch}</div>
+            </div>
+            {batch === 'Spark' &&
+              batchInfoResponse.sparkBatch.mainJarFileUri && (
+                <div className="row-details">
+                  <div className="details-label">Main jar</div>
+                  <div className="details-value">
+                    {batchInfoResponse.sparkBatch.mainJarFileUri}
+                  </div>
+                </div>
+              )}
+            {batch === 'Spark' && batchInfoResponse.sparkBatch.mainClass && (
+              <>
+                <div className="row-details">
+                  <div className="details-label">Main class</div>
+                  <div className="details-value">
+                    {batchInfoResponse.sparkBatch.mainClass}
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="details-label">Jar files</div>
+                  <div className="details-value">
+                    {batchInfoResponse.sparkBatch.jarFileUris ? (
+                      <div className="cluster-details-value">
+                        {batchInfoResponse.sparkBatch.jarFileUris}
+                      </div>
+                    ) : (
+                      <div className="cluster-details-value">None</div>
+                    )}
+                  </div>
+                </div>
+              </>
+            )}
+            {batch === 'PySpark' && (
+              <div className="row-details">
+                <div className="details-label">Main python file</div>
+                <div className="details-value">
+                  {batchInfoResponse.pysparkBatch.mainPythonFileUri}
+                </div>
+              </div>
+            )}
+            {batch === 'SparkR' && (
+              <div className="row-details">
+                <div className="details-label">Main R file</div>
+                <div className="details-value">
+                  {batchInfoResponse.sparkRBatch.mainRFileUri}
+                </div>
+              </div>
+            )}
+            {batch === 'SparkSQL' && (
+              <div className="row-details">
+                <div className="details-label">Query file</div>
+                <div className="details-value">
+                  {batchInfoResponse.sparkSqlBatch.queryFileUri}
+                </div>
+              </div>
+            )}
+            <div className="row-details">
+              <div className="details-label">Properties</div>
+              <div className="details-value"></div>
+            </div>
+            {Object.entries(batchInfoResponse.runtimeConfig.properties).map(
+              ([key, value]) => (
+                <div className="row-details" key={key}>
+                  <div className="batch-details-label-level-one">{key}</div>
+                  <div className="details-value">{value}</div>
+                </div>
+              )
+            )}
+            <div className="row-details">
+              <div className="details-label">Environment config</div>
+              <div className="details-value"></div>
+            </div>
+            <div className="row-details">
+              <div className="batch-details-label-level-one">
+                Execution config
+              </div>
+              <div className="details-value"></div>
+            </div>
+            {Object.entries(
+              batchInfoResponse.environmentConfig.executionConfig
+            ).map(([key, value]) => {
+              let label;
+              if (key === SERVICE_ACCOUNT_KEY) {
+                label = SERVICE_ACCOUNT_LABEL;
+              } else if (key === NETWORK_KEY) {
+                label = NETWORK_LABEL;
+              } else if (key === SUBNETWORK_KEY) {
+                label = SUBNETWORK_LABEL;
+              } else {
+                label = '';
+              }
+              if (
+                key === SERVICE_ACCOUNT_KEY ||
+                key === NETWORK_KEY ||
+                key === SUBNETWORK_KEY
+              ) {
+                return (
+                  <div className="row-details" key={key}>
+                    <div className="batch-details-label-level-two">{label}</div>
+                    <div className="details-value">{value}</div>
+                  </div>
+                );
+              }
+            })}
+
+            <div className="row-details">
+              <div className="details-label">Encryption type</div>
+              <div className="details-value">Google-managed</div>
+            </div>
+            <div className="batch-details-row-label">
+              <div className="details-label">Labels</div>
+              <div className="details-value">
+                <div className="job-label-style-parent">
+                  {labelDetail.length > 0
+                    ? labelDetail.map(label => {
+                        const labelParts = label.split(':');
+                        return (
+                          <div key={label} className="job-label-style">
+                            {labelParts[0]} : {labelParts[1]}
+                          </div>
+                        );
+                      })
+                    : ''}
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default BatchDetails;
diff --git a/src/batches/batches.tsx b/src/batches/batches.tsx
new file mode 100644
index 00000000..89107e36
--- /dev/null
+++ b/src/batches/batches.tsx
@@ -0,0 +1,331 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ReactWidget } from '@jupyterlab/apputils';
+import React, { useState, useEffect, useRef } from 'react';
+import {
+  authApi,
+  checkConfig,
+  elapsedTime,
+  jobTimeFormat,
+  jobTypeDisplay
+} from '../utils/utils';
+import {
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  API_HEADER_BEARER,
+  LOGIN_STATE
+} from '../utils/const';
+import ListBatches from './listBatches';
+import { LabIcon } from '@jupyterlab/ui-components';
+import deleteIcon from '../../style/icons/delete_icon.svg';
+import BatchDetails from './batchDetails';
+import ListSessions from '../sessions/listSessions';
+import { ClipLoader } from 'react-spinners';
+import DeletePopup from '../utils/deletePopup';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { deleteBatchAPI } from '../utils/batchService';
+import CreateBatch from './createBatch';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconDelete = new LabIcon({
+  name: 'launcher:delete-icon',
+  svgstr: deleteIcon
+});
+
+const BatchesComponent = (): React.JSX.Element => {
+  const [batchesList, setBatchesList] = useState([]);
+  const [selectedMode, setSelectedMode] = useState('Batches');
+  const [isLoading, setIsLoading] = useState(true);
+  const [batchSelected, setBatchSelected] = useState('');
+  const [pollingDisable, setPollingDisable] = useState(false);
+  const [detailedBatchView, setDetailedBatchView] = useState(false);
+  const [loggedIn, setLoggedIn] = useState(false);
+  const [configError, setConfigError] = useState(false);
+  const [loginError, setLoginError] = useState(false);
+  const [configLoading, setConfigLoading] = useState(true);
+
+  const [deletePopupOpen, setDeletePopupOpen] = useState(false);
+  const [selectedBatch, setSelectedBatch] = useState('');
+  const [regionName, setRegionName] = useState('');
+  const [projectName, setProjectName] = useState('');
+
+  const [createBatchView, setCreateBatchView] = useState(false);
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+
+  const pollingBatches = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const selectedModeChange = (mode: 'Sessions' | 'Batches') => {
+    setSelectedMode(mode);
+  };
+
+  const listBatchAPI = async (
+    nextPageToken?: string,
+    previousBatchesList?: object
+  ) => {
+    const credentials = await authApi();
+    const pageToken = nextPageToken ?? '';
+    if (credentials) {
+      setRegionName(credentials.region_id || '');
+      setProjectName(credentials.project_id || '');
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/batches?orderBy=create_time desc&&pageSize=50&pageToken=${pageToken}`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              let transformBatchListData = [];
+              if (responseResult && responseResult.batches) {
+                transformBatchListData = responseResult.batches.map(
+                  (data: any) => {
+                    const startTimeDisplay = jobTimeFormat(data.createTime);
+                    const startTime = new Date(data.createTime);
+                    const elapsedTimeString = elapsedTime(
+                      data.stateTime,
+                      startTime
+                    );
+                    const batchType = Object.keys(data).filter(key =>
+                      key.endsWith('Batch')
+                    );
+                    /*
+                     Extracting batchID, location from batchInfo.name
+                      Example: "projects/{project}/locations/{location}/batches/{batchID}"
+                    */
+                    const batchTypeDisplay = jobTypeDisplay(
+                      batchType[0].split('Batch')[0]
+                    );
+                    return {
+                      batchID: data.name.split('/')[5],
+                      status: data.state,
+                      location: data.name.split('/')[3],
+                      creationTime: startTimeDisplay,
+                      type: batchTypeDisplay,
+                      elapsedTime: elapsedTimeString,
+                      actions: renderActions(data)
+                    };
+                  }
+                );
+              }
+
+              const existingBatchData = previousBatchesList ?? [];
+              //setStateAction never type issue
+              let allBatchesData: any = [
+                ...(existingBatchData as []),
+                ...transformBatchListData
+              ];
+
+              if (responseResult.nextPageToken) {
+                listBatchAPI(responseResult.nextPageToken, allBatchesData);
+              } else {
+                setBatchesList(allBatchesData);
+                setIsLoading(false);
+                setLoggedIn(true);
+              }
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error listing batches', err);
+          toast.error('Failed to fetch batches');
+        });
+    }
+  };
+  const handleBatchDetails = (selectedName: string) => {
+    pollingBatches(listBatchAPI, true);
+    setBatchSelected(selectedName);
+    setDetailedBatchView(true);
+  };
+
+  const handleDeleteBatch = (batch: string) => {
+    setSelectedBatch(batch);
+    setDeletePopupOpen(true);
+  };
+  const handleCancelDelete = () => {
+    setDeletePopupOpen(false);
+  };
+
+  const handleDelete = async () => {
+    await deleteBatchAPI(selectedBatch);
+    listBatchAPI();
+    setDetailedBatchView(false);
+    setDeletePopupOpen(false);
+  };
+
+  const renderActions = (data: { name: string }) => {
+    return (
+      <div className="actions-icon" role="button" aria-label="Delete Job">
+        <div
+          className="icon-buttons-style"
+          title="Delete Batch"
+          onClick={() => handleDeleteBatch(data.name.split('/')[5])}
+        >
+          <iconDelete.react tag="div" />
+        </div>
+      </div>
+    );
+  };
+
+  const toggleStyleSelection = (toggleItem: string) => {
+    if (selectedMode === toggleItem) {
+      return 'selected-header';
+    } else {
+      return 'unselected-header';
+    }
+  };
+
+  useEffect(() => {
+    checkConfig(setLoggedIn, setConfigError, setLoginError);
+    const localstorageGetInformation = localStorage.getItem('loginState');
+    setLoggedIn(localstorageGetInformation === LOGIN_STATE);
+    if (loggedIn) {
+      setConfigLoading(false);
+    }
+    listBatchAPI();
+    if (!detailedBatchView && selectedMode === 'Batches') {
+      pollingBatches(listBatchAPI, pollingDisable);
+    }
+
+    return () => {
+      pollingBatches(listBatchAPI, true);
+    };
+  }, [pollingDisable, detailedBatchView, selectedMode]);
+
+  return (
+    <div className="component-level">
+      {configLoading && !loggedIn && !configError && !loginError && (
+        <div className="spin-loaderMain">
+          <ClipLoader
+            color="#8A8A8A"
+            loading={true}
+            size={18}
+            aria-label="Loading Spinner"
+            data-testid="loader"
+          />
+          Loading Batches
+        </div>
+      )}
+      {deletePopupOpen && (
+        <DeletePopup
+          onCancel={() => handleCancelDelete()}
+          onDelete={() => handleDelete()}
+          deletePopupOpen={deletePopupOpen}
+          DeleteMsg={
+            'This will delete ' + selectedBatch + ' and cannot be undone.'
+          }
+        />
+      )}
+      {loggedIn && !configError ? (
+        <>
+          {detailedBatchView && (
+            <BatchDetails
+              batchSelected={batchSelected}
+              setDetailedBatchView={setDetailedBatchView}
+            />
+          )}
+          {createBatchView && (
+            <CreateBatch
+              setCreateBatchView={setCreateBatchView}
+              regionName={regionName}
+              projectName={projectName}
+            />
+          )}
+
+          {!detailedBatchView && !createBatchView && (
+            <div className="clusters-list-component" role="tablist">
+              {
+                <div className="clusters-list-overlay" role="tab">
+                  <div
+                    role="tabpanel"
+                    className={toggleStyleSelection('Batches')}
+                    onClick={() => selectedModeChange('Batches')}
+                  >
+                    Batches
+                  </div>
+                  <div
+                    role="tabpanel"
+                    className={toggleStyleSelection('Sessions')}
+                    onClick={() => selectedModeChange('Sessions')}
+                  >
+                    Sessions
+                  </div>
+                </div>
+              }
+              <div>
+                {selectedMode === 'Sessions' ? (
+                  <ListSessions />
+                ) : (
+                  <ListBatches
+                    batchesList={batchesList}
+                    isLoading={isLoading}
+                    setPollingDisable={setPollingDisable}
+                    listBatchAPI={listBatchAPI}
+                    handleBatchDetails={handleBatchDetails}
+                    setCreateBatchView={setCreateBatchView}
+                    createBatchView={createBatchView}
+                  />
+                )}
+              </div>
+              <ToastContainer />
+            </div>
+          )}
+        </>
+      ) : (
+        loginError && (
+          <div role="alert" className="login-error">
+            Please login to continue
+          </div>
+        )
+      )}
+      {configError && (
+        <div role="alert" className="login-error">
+          Please configure gcloud with account, project-id and region
+        </div>
+      )}
+    </div>
+  );
+};
+
+export class Batches extends ReactWidget {
+  constructor() {
+    super();
+  }
+
+  render(): React.JSX.Element {
+    return <BatchesComponent />;
+  }
+}
diff --git a/src/batches/createBatch.tsx b/src/batches/createBatch.tsx
new file mode 100644
index 00000000..481bba14
--- /dev/null
+++ b/src/batches/createBatch.tsx
@@ -0,0 +1,1476 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useEffect, useState } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import LeftArrowIcon from '../../style/icons/left_arrow_icon.svg';
+import 'semantic-ui-css/semantic.min.css';
+import 'react-toastify/dist/ReactToastify.css';
+import { Input, Radio, Select, Dropdown } from 'semantic-ui-react';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  ARCHIVE_FILES_MESSAGE,
+  ARGUMENTS_MESSAGE,
+  ARTIFACT_REGISTERY,
+  BASE_URL,
+  BASE_URL_META,
+  BASE_URL_NETWORKS,
+  CONTAINER_REGISTERY,
+  CUSTOM_CONTAINERS,
+  CUSTOM_CONTAINER_MESSAGE,
+  FILES_MESSAGE,
+  JAR_FILE_MESSAGE,
+  METASTORE_MESSAGE,
+  PROJECT_LIST_URL,
+  QUERY_FILE_MESSAGE,
+  REGION_URL,
+  // SECURITY_KEY,
+  SELF_MANAGED_CLUSTER,
+  SERVICE_ACCOUNT,
+  STATUS_RUNNING
+} from '../utils/const';
+import TagsInput from 'react-tagsinput';
+import 'react-tagsinput/react-tagsinput.css';
+import LabelProperties from '../jobs/labelProperties';
+import { authApi } from '../utils/utils';
+import { ClipLoader } from 'react-spinners';
+import { toast } from 'react-toastify';
+import ErrorPopup from '../utils/errorPopup';
+import errorIcon from '../../style/icons/error_icon.svg';
+
+type Project = {
+  projectId: string;
+};
+
+type Cluster = {
+  clusterName: string;
+  status: {
+    state: string;
+  };
+};
+
+type Network = {
+  selfLink: any;
+  network: string;
+  subnetworks: string;
+};
+
+type Service = {
+  name: string;
+};
+
+const iconLeftArrow = new LabIcon({
+  name: 'launcher:left-arrow-icon',
+  svgstr: LeftArrowIcon
+});
+
+const iconError = new LabIcon({
+  name: 'launcher:error-icon',
+  svgstr: errorIcon
+});
+
+interface ICreateBatchProps {
+  setCreateBatchView: (value: boolean) => void;
+  regionName: string;
+  projectName: string;
+}
+let jarFileUris: string[] = [];
+let fileUris: string[] = [];
+let archiveFileUris: string[] = [];
+let argumentsUris: string[] = [];
+let networkUris: string[] = [];
+let key: string[] | (() => string[]) = [];
+let value: string[] | (() => string[]) = [];
+let pythonFileUris: string[] = [];
+
+function CreateBatch({
+  setCreateBatchView,
+  regionName,
+  projectName
+}: ICreateBatchProps) {
+  const [batchTypeList, setBatchTypeList] = useState([{}]);
+  const [versionList, setVersionList] = useState([{}]);
+  const [generationCompleted, setGenerationCompleted] = useState(false);
+  const [hexNumber, setHexNumber] = useState('');
+  const [batchIdSelected, setBatchIdSelected] = useState('');
+  const [batchTypeSelected, setBatchTypeSelected] = useState('spark');
+  const [versionSelected, setVersionSelected] = useState('2.1');
+  const [selectedRadio, setSelectedRadio] = useState('mainClass');
+  const [mainClassSelected, setMainClassSelected] = useState('');
+  const [mainJarSelected, setMainJarSelected] = useState('');
+  const [mainRSelected, setMainRSelected] = useState('');
+  const [containerImageSelected, setContainerImageSelected] = useState('');
+  const [jarFilesSelected, setJarFilesSelected] = useState([...jarFileUris]);
+  const [filesSelected, setFilesSelected] = useState([...fileUris]);
+  const [queryFileSelected, setQueryFileSelected] = useState('');
+  const [ArchiveFilesSelected, setArchiveFileSelected] = useState([
+    ...archiveFileUris
+  ]);
+  const [argumentsSelected, setArgumentsSelected] = useState([
+    ...argumentsUris
+  ]);
+  const [serviceAccountSelected, setServiceAccountSelected] = useState('');
+  const [networkTagSelected, setNetworkTagSelected] = useState([
+    ...networkUris
+  ]);
+  const [propertyDetail, setPropertyDetail] = useState(['']);
+  const [propertyDetailUpdated, setPropertyDetailUpdated] = useState(['']);
+  const [keyValidation, setKeyValidation] = useState(-1);
+  const [valueValidation, setvalueValidation] = useState(-1);
+  const [duplicateKeyError, setDuplicateKeyError] = useState(-1);
+  const [labelDetail, setLabelDetail] = useState(key);
+  const [labelDetailUpdated, setLabelDetailUpdated] = useState(value);
+  const [servicesList, setServicesList] = useState<
+    Array<{ key: string; value: string; text: string }>
+  >([]);
+  const [servicesSelected, setServicesSelected] = useState('None');
+
+  const [clusterSelected, setClusterSelected] = useState('');
+  const [projectId, setProjectId] = useState('');
+  const [region, setRegion] = useState('');
+  const [projectList, setProjectList] = useState([{}]);
+  const [regionList, setRegionList] = useState<{ value: string; key: string; text: string; }[]>([]);
+  const [networkList, setNetworklist] = useState([{}]);
+  const [isLoadingRegion, setIsLoadingRegion] = useState(false);
+  const [networkSelected, setNetworkSelected] = useState('default');
+  const [subNetworkSelected, setSubNetworkSelected] = useState('default');
+  const [isLoadingService, setIsLoadingService] = useState(false);
+  const [error, setError] = useState({ isOpen: false, message: '' });
+  const [parameterDetail, setParameterDetail] = useState(['']);
+  const [parameterDetailUpdated, setParameterDetailUpdated] = useState(['']);
+  const [additionalPythonFileSelected, setAdditionalPythonFileSelected] =
+    useState([...pythonFileUris]);
+  const [mainPythonSelected, setMainPythonSelected] = useState('');
+  const [clustersList, setClustersList] = useState<
+    Array<{ key: string; value: string; text: string }>
+  >([]);
+  const [additionalPythonFileValidation, setAdditionalPythonFileValidation] =
+    useState(true);
+  const [jarFileValidation, setJarFileValidation] = useState(true);
+  const [fileValidation, setFileValidation] = useState(true);
+  const [archieveFileValidation, setArchieveFileValidation] = useState(true);
+  const [mainPythonValidation, setMainPythonValidation] = useState(true);
+  const [queryFileValidation, setQueryFileValidation] = useState(true);
+  const [mainRValidation, setMainRValidation] = useState(true);
+  const [batchIdValidation, setBatchIdValidation] = useState(false);
+  const [mainJarValidation, setMainJarValidation] = useState(true);
+
+  const handleCreateBatchBackView = () => {
+    setCreateBatchView(false);
+  };
+  const handleMainClassRadio = () => {
+    setSelectedRadio('mainClass');
+    setMainJarSelected('');
+    setMainClassSelected('');
+  };
+  const handleMainJarRadio = () => {
+    setSelectedRadio('mainJarURI');
+    setMainClassSelected('');
+    setMainJarSelected('');
+  };
+  useEffect(() => {
+    const batchTypeData = [
+      { key: 'spark', value: 'spark', text: 'Spark' },
+      { key: 'sparkR', value: 'sparkR', text: 'SparkR' },
+      { key: 'sparkSql', value: 'sparkSql', text: 'SparkSql' },
+      { key: 'pySpark', value: 'pySpark', text: 'PySpark' }
+    ];
+
+    const runTimeVersionData = [
+      {
+        key: '2.1',
+        value: '2.1',
+        text: '2.1 (Spark 3.4, Java 17, Scala 2.13)'
+      },
+      {
+        key: '2.0',
+        value: '2.0',
+        text: '2.0 (Spark 3.3, Java 17, Scala 2.13)'
+      },
+      {
+        key: '1.1',
+        value: '1.1',
+        text: '1.1 (Spark 3.3, Java 11, Scala 2.12)'
+      }
+    ];
+    setVersionList(runTimeVersionData);
+    setBatchTypeList(batchTypeData);
+    projectListAPI();
+    listClustersAPI();
+    listNetworksAPI();
+  }, [clusterSelected]);
+
+  useEffect(() => {
+    generateRandomHex();
+  }, [
+    batchIdSelected,
+    mainClassSelected,
+    mainRSelected,
+    mainPythonSelected,
+    queryFileSelected,
+    jarFileValidation,
+    mainJarValidation,
+    archieveFileValidation,
+    mainRValidation,
+    fileValidation,
+    mainPythonValidation,
+    queryFileValidation,
+    keyValidation,
+    valueValidation,
+    batchIdValidation,
+    duplicateKeyError
+  ]);
+
+  function isSubmitDisabled() {
+    const commonConditions = batchIdSelected === '' || regionName === '';
+
+    switch (batchTypeSelected) {
+      case 'spark':
+        return (
+          commonConditions ||
+          (selectedRadio === 'mainClass' && mainClassSelected === '') ||
+          (selectedRadio === 'mainJarURI' && mainJarSelected === '') ||
+          !mainJarValidation ||
+          !fileValidation ||
+          !archieveFileValidation
+        );
+      case 'sparkR':
+        return (
+          commonConditions ||
+          mainRSelected === '' ||
+          !mainRValidation ||
+          !fileValidation ||
+          !archieveFileValidation
+        );
+      case 'pySpark':
+        return (
+          commonConditions ||
+          mainPythonSelected === '' ||
+          !mainPythonValidation ||
+          !additionalPythonFileValidation ||
+          !fileValidation ||
+          !archieveFileValidation
+        );
+      case 'sparkSql':
+        return (
+          commonConditions ||
+          queryFileSelected === '' ||
+          !queryFileValidation ||
+          !jarFileValidation
+        );
+      default:
+        return false;
+    }
+  }
+
+  const handleValidationFiles = (
+    listOfFiles: any,
+    setValuesPart: any,
+    setValidationPart: any
+  ) => {
+    if (typeof listOfFiles === 'string') {
+      if (
+        listOfFiles.startsWith('file://') ||
+        listOfFiles.startsWith('gs://') ||
+        listOfFiles.startsWith('hdfs://')
+      ) {
+        setValidationPart(true);
+      } else {
+        setValidationPart(false);
+      }
+      setValuesPart(listOfFiles);
+    } else {
+      if (listOfFiles.length === 0) {
+        setValidationPart(true);
+      } else {
+        listOfFiles.forEach((fileName: any) => {
+          if (
+            fileName.startsWith('file://') ||
+            fileName.startsWith('gs://') ||
+            fileName.startsWith('hdfs://')
+          ) {
+            setValidationPart(true);
+          } else {
+            setValidationPart(false);
+          }
+        });
+      }
+      setValuesPart(listOfFiles);
+    }
+  };
+
+  const listClustersAPI = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters?pageSize=100`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: { clusters: Cluster[] }) => {
+              let transformClusterListData = [];
+
+              transformClusterListData = responseResult.clusters.filter(
+                (data: Cluster) => {
+                  if (data.status.state === STATUS_RUNNING) {
+                    return {
+                      clusterName: data.clusterName
+                    };
+                  }
+                }
+              );
+
+              const keyLabelStructure = transformClusterListData.map(
+                (obj) => ({
+                  key: obj.clusterName,
+                  value: obj.clusterName,
+                  text: obj.clusterName
+                })
+              );
+              setClustersList(keyLabelStructure);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error listing clusters', err);
+        });
+    }
+  };
+  const listNetworksAPI = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL_NETWORKS}/projects/${credentials.project_id}/regions/${credentials.region_id}/subnetworks`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: { items: Network[] }) => {
+              let transformedNetworkList = [];
+               /*
+         Extracting network, subnetwork from items
+         Example: "https://www.googleapis.com/compute/v1/projects/{projectName}/global/networks/",
+      */
+              
+              transformedNetworkList = responseResult.items.map((data: Network) => {
+                return {
+                  network: data.network.split('/')[9],
+                  subnetworks: data.selfLink.split('/')[10]
+                };
+              });
+              const keyLabelStructureNetwork = transformedNetworkList.map(
+                (obj) => ({
+                  key: obj.network,
+                  value: obj.network,
+                  text: obj.network
+                })
+              );
+              setNetworklist(keyLabelStructureNetwork);
+            })
+
+            .catch((e: Error) => {
+              console.log(e);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error listing Networks', err);
+        });
+    }
+  };
+  const listMetaStoreAPI = async (data: undefined) => {
+    setIsLoadingService(true);
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL_META}/projects/${projectId}/locations/${data}/services`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: { services: Service[] }) => {
+              let transformClusterListData = [];
+
+              transformClusterListData = responseResult.services.filter(
+                (data: Service) => {
+                  return {
+                    name: data.name
+                  };
+                }
+              );
+
+              const keyLabelStructure = transformClusterListData.map(
+                (obj) => ({
+                  key: obj.name,
+                  value: obj.name,
+                  text: obj.name
+                })
+              );
+              const noneOption = { key: 'None', value: 'None', text: 'None' };
+              setServicesList([noneOption, ...keyLabelStructure]);
+              setIsLoadingService(false);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoadingService(false);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error listing services', err);
+          setIsLoadingService(false);
+        });
+    }
+  };
+  const projectListAPI = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(PROJECT_LIST_URL, {
+        method: 'GET',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      })
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: { projects: Project[] }) => {
+              let transformedProjectList = [];
+              transformedProjectList = responseResult.projects.map(
+                (data: Project) => {
+                  return {
+                    value: data.projectId,
+                    key: data.projectId,
+                    text: data.projectId
+                  };
+                }
+              );
+              setProjectList(transformedProjectList);
+            })
+            .catch((e: Error) => console.log(e));
+        })
+        .catch((err: Error) => {
+          console.error('Error fetching project list', err);
+        });
+    }
+  };
+
+  type Region = {
+    name: string;
+  };
+
+  const regionListAPI = async (projectId: string) => {
+    setIsLoadingRegion(true);
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(`${REGION_URL}${projectId}/regions`, {
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      })
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: { items: Region[] }) => {
+              let transformedRegionList = [];
+              transformedRegionList = responseResult.items.map((data: Region) => {
+                return {
+                  value: data.name,
+                  key: data.name,
+                  text: data.name
+                };
+              });
+              setRegionList(transformedRegionList);
+              setIsLoadingRegion(false);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoadingRegion(false);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error listing regions', err);
+          setIsLoadingRegion(false);
+        });
+    }
+  };
+
+  type Payload = {
+    [key: string]: any;
+  };
+  
+  type RuntimeConfig = {
+    version: string;
+    containerImage?: string;
+    properties?: object;
+  };
+  
+  type ExecutionConfig = {
+    serviceAccount?: string;
+    subnetworkUri: string;
+    networkTags?: string[];
+  };
+  
+  type SparkHistoryServerConfig = {
+    dataprocCluster: string;
+  };
+  
+  const createPayload = (
+    batchTypeSelected: string,
+    mainJarSelected: string,
+    mainClassSelected: string,
+    propertyObject: object,
+    ArchiveFilesSelected: string[],
+    filesSelected: string[],
+    jarFilesSelected: string[],
+    argumentsSelected: string[],
+    networkTagSelected: string[],
+    labelObject: object,
+    projectName: string,
+    regionName: string,
+    clusterSelected: string,
+    batchIdSelected: string,
+    parameterObject: object,
+    mainRSelected: string,
+    additionalPythonFileSelected: string[],
+    mainPythonSelected: string,
+    queryFileSelected: string
+  ): Payload => {
+    const payload: Payload = {};
+  
+    if (batchTypeSelected === 'spark') {
+      payload.sparkBatch = {
+        ...(mainJarSelected !== '' && { mainJarFileUri: mainJarSelected }),
+        ...(mainClassSelected !== '' && { mainClass: mainClassSelected }),
+      };
+    }
+    if (batchTypeSelected === 'sparkR') {
+      payload.sparkRBatch = {
+        ...(mainRSelected !== '' && { mainRFileUri: mainRSelected }),
+      };
+    }
+    if (batchTypeSelected === 'pySpark') {
+      payload.pysparkBatch = {
+        ...(additionalPythonFileSelected.length > 0 && {
+          pythonFileUris: additionalPythonFileSelected,
+        }),
+        ...(mainPythonSelected !== '' && {
+          mainPythonFileUri: mainPythonSelected,
+        }),
+      };
+    }
+    if (batchTypeSelected === 'sparkSql') {
+      payload.sparkSqlBatch = {
+        ...(queryFileSelected !== '' && { queryFileUri: queryFileSelected }),
+        ...(parameterObject && { queryVariables: { query: parameterObject } }),
+      };
+    }
+  
+    payload.labels = labelObject;
+  
+    payload.name = `projects/${projectName}/locations/${regionName}/batches/${batchIdSelected}`;
+  
+    payload.runtimeConfig = {
+      version: versionSelected,
+      ...(containerImageSelected !== '' && {
+        containerImage: containerImageSelected,
+      }),
+      ...(propertyObject && { properties: propertyObject }),
+    } as RuntimeConfig;
+  
+    payload.environmentConfig = {
+      executionConfig: {
+        ...(serviceAccountSelected !== '' && {
+          serviceAccount: serviceAccountSelected,
+        }),
+        subnetworkUri: networkSelected,
+        ...(networkTagSelected.length > 0 && {
+          networkTags: networkTagSelected,
+        }),
+      } as ExecutionConfig,
+    };
+  
+    if (servicesSelected !== 'None' || clusterSelected !== '') {
+      payload.peripheralsConfig = {
+        ...(servicesSelected !== 'None' && {
+          metastoreService: servicesSelected,
+        }),
+        ...(clusterSelected !== '' && {
+          sparkHistoryServerConfig: {
+            dataprocCluster: `projects/${projectName}/locations/${regionName}/clusters/${clusterSelected}`,
+          } as SparkHistoryServerConfig,
+        }),
+      };
+    }
+  
+    payload.archiveUris =
+      ArchiveFilesSelected.length > 0 ? ArchiveFilesSelected : undefined;
+    payload.fileUris = filesSelected.length > 0 ? filesSelected : undefined;
+    payload.jarFileUris = jarFilesSelected.length > 0 ? jarFilesSelected : undefined;
+    payload.args = argumentsSelected.length > 0 ? argumentsSelected : undefined;
+  
+    return payload;
+  };
+  
+
+  const handleSubmit = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      const labelObject: { [key: string]: string } = {};
+      labelDetailUpdated.forEach((label: string) => {
+        const labelSplit = label.split(':');
+        const key = labelSplit[0];
+        const value = labelSplit[1];
+        labelObject[key] = value;
+      });
+      const propertyObject: { [key: string]: string } = {};
+      propertyDetailUpdated.forEach((label: string) => {
+        const labelSplit = label.split(':');
+        const key = labelSplit[0];
+        const value = labelSplit[1];
+        propertyObject[key] = value;
+      });
+      const parameterObject: { [key: string]: string } = {};
+      parameterDetailUpdated.forEach((label: string) => {
+        const labelSplit = label.split(':');
+        const key = labelSplit[0];
+        const value = labelSplit[1];
+        parameterObject[key] = value;
+      });
+      const payload = createPayload(
+        batchTypeSelected,
+        mainJarSelected,
+        mainClassSelected,
+        propertyObject,
+        ArchiveFilesSelected,
+        filesSelected,
+        jarFilesSelected,
+        argumentsSelected,
+        networkTagSelected,
+        labelObject,
+        projectName,
+        regionName,
+        clusterSelected,
+        batchIdSelected,
+        parameterObject,
+        mainRSelected,
+        additionalPythonFileSelected,
+        mainPythonSelected,
+        queryFileSelected
+      );
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/batches?batchId=${batchIdSelected}`,
+        {
+          method: 'POST',
+          body: JSON.stringify(payload),
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then(async (response: Response) => {
+          if (response.ok) {
+            const responseResult = await response.json();
+            console.log(responseResult);
+            setCreateBatchView(false);
+          } else {
+            const errorResponse = await response.json();
+            console.log(errorResponse);
+            setError({ isOpen: true, message: errorResponse.error.message });
+          }
+        })
+        .catch((err: Error) => {
+          console.error('Error submitting Batch', err);
+          toast.error('Failed to submit the Batch');
+        });
+    }
+  };
+  const generateRandomHex = () => {
+    if (!generationCompleted) {
+      const crypto = window.crypto || window.Crypto;
+      const array = new Uint32Array(1);
+      crypto.getRandomValues(array);
+      const hex = array[0].toString(16);
+      const paddedHex = hex.padStart(6, '0');
+      setHexNumber('batch-' + paddedHex);
+      setBatchIdSelected('batch-' + paddedHex);
+      setGenerationCompleted(true);
+    }
+  };
+
+  const handleInputChange = (event: any) => {
+    setHexNumber(event.target.value);
+    event.target.value.length > 0
+      ? setBatchIdValidation(false)
+      : setBatchIdValidation(true);
+    const newBatchId = event.target.value;
+    setBatchIdSelected(newBatchId);
+  };
+  const handleBatchTypeSelected = (event: any, data: any) => {
+    setBatchTypeSelected(data.value);
+    setFilesSelected([]);
+    setJarFilesSelected([]);
+    setAdditionalPythonFileSelected([]);
+    setArchiveFileSelected([]);
+    setArgumentsSelected([]);
+    setMainPythonSelected('');
+    setMainRSelected('');
+    setQueryFileSelected('');
+    setMainClassSelected('');
+  };
+  const handleVersionSelected = (event: any, data: any) => {
+    setVersionSelected(data.value);
+  };
+
+  const handleServiceSelected = (event: any, data: any) => {
+    setServicesSelected(data.value);
+  };
+  const handleProjectIdChange = (event: any, data: any) => {
+    regionListAPI(data.value);
+    setProjectId(data.value);
+  };
+  const handleRegionChange = (event: any, data: any) => {
+    setRegion(data.value);
+    listMetaStoreAPI(data.value);
+  };
+  const handleNetworkChange = (event: any, data: any) => {
+    setNetworkSelected(data.value);
+    setSubNetworkSelected(data.value);
+  };
+  const handleSubNetworkChange = (event: any, data: any) => {
+    setSubNetworkSelected(data.value);
+  };
+
+  const handleClusterSelected = (event: any, data: any) => {
+    setClusterSelected(data.value);
+  };
+
+  
+  return (
+    <div>
+      <div className="scroll-comp">
+        <div className="cluster-details-header">
+          <div
+            className="back-arrow-icon"
+            onClick={() => handleCreateBatchBackView()}
+          >
+            <iconLeftArrow.react tag="div" />
+          </div>
+          <div className="cluster-details-title">Create batch</div>
+        </div>
+        <div className="submit-job-container">
+          <form onSubmit={handleSubmit}>
+            <div className="submit-job-label-header">Batch info</div>
+            <div className="create-batches-message">Batch ID*</div>
+            <Input
+              className="create-batch-style "
+              value={hexNumber}
+              onChange={e => handleInputChange(e)}
+              type="text"
+            />
+            {batchIdValidation && (
+              <div className="error-key-parent">
+                <iconError.react tag="div" />
+                <div className="error-key-missing">ID is required</div>
+              </div>
+            )}
+            <div className="create-batches-message">Region*</div>
+            <Select
+              className="select-job-style"
+              value={regionName}
+              type="text"
+              disabled={true}
+              options={[]}
+              placeholder={regionName}
+            />
+            <div className="submit-job-label-header">Container</div>
+            <div className="create-batches-message">Batch type*</div>
+            <Select
+              className="select-job-style"
+              value={batchTypeSelected}
+              type="text"
+              options={batchTypeList}
+              onChange={handleBatchTypeSelected}
+            />
+            <div className="create-batches-message">Runtime version*</div>
+            <Select
+              className="select-job-style"
+              value={versionSelected}
+              type="text"
+              options={versionList}
+              onChange={handleVersionSelected}
+            />
+            {batchTypeSelected === 'spark' && (
+              <div>
+                <div>
+                  <div className="create-batch-radio">
+                    <Radio
+                      className="select-batch-radio-style"
+                      value="mainClass"
+                      checked={selectedRadio === 'mainClass'}
+                      onChange={handleMainClassRadio}
+                    />
+                    <div className="create-batch-message">Main class</div>
+                  </div>
+                  <div className="create-batch-sub-message">
+                    The fully qualified name of a class in a provided or
+                    standard jar file, for example, com.example.wordcount.
+                  </div>
+                </div>
+                {selectedRadio === 'mainClass' && (
+                  <div className="create-batch-input">
+                    <div className="create-batch-message">Main class*</div>
+                    <Input
+                      className="create-batch-style "
+                      value={mainClassSelected}
+                      onChange={e => setMainClassSelected(e.target.value)}
+                      type="text"
+                    />
+                    {selectedRadio === 'mainClass' &&
+                      mainClassSelected === '' && (
+                        <div className="error-key-parent">
+                          <iconError.react tag="div" />
+                          <div className="error-key-missing">
+                            Main class is required
+                          </div>
+                        </div>
+                      )}
+                  </div>
+                )}
+              </div>
+            )}
+            {batchTypeSelected === 'spark' && (
+              <div>
+                <div>
+                  <div className="create-batch-radio">
+                    <Radio
+                      className="select-batch-radio-style"
+                      value="mainJarURI"
+                      checked={selectedRadio === 'mainJarURI'}
+                      onChange={handleMainJarRadio}
+                    />
+                    <div className="create-batch-message">Main jar URI</div>
+                  </div>
+                  <div className="create-batch-sub-message">
+                    A provided jar file to use the main class of that jar file.
+                  </div>
+                </div>
+                {selectedRadio === 'mainJarURI' && (
+                  <div className="create-batch-input">
+                    <div className="create-batch-message">Main jar*</div>
+                    <Input
+                      className="create-batch-style "
+                      value={mainJarSelected}
+                      onChange={e =>
+                        handleValidationFiles(
+                          e.target.value,
+                          setMainJarSelected,
+                          setMainJarValidation
+                        )
+                      }
+                      type="text"
+                    />
+                    {selectedRadio === 'mainJarURI' &&
+                      mainJarSelected === '' && (
+                        <div className="error-key-parent">
+                          <iconError.react tag="div" />
+                          <div className="error-key-missing">
+                            Main jar is required
+                          </div>
+                        </div>
+                      )}
+                    {!mainJarValidation && (
+                      <div className="error-key-parent">
+                        <iconError.react tag="div" />
+                        <div className="error-key-missing">
+                          File must include a valid scheme prefix: 'file://',
+                          'gs://', or 'hdfs://'
+                        </div>
+                      </div>
+                    )}
+                  </div>
+                )}
+              </div>
+            )}
+
+            {batchTypeSelected === 'sparkR' && (
+              <>
+                <div className="create-batch-message">Main R file*</div>
+                <Input
+                  className="create-batch-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e.target.value,
+                      setMainRSelected,
+                      setMainRValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={mainRSelected}
+                />
+                {!mainRValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      File must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+                {mainRValidation && (
+                  <div className="submit-job-message">{QUERY_FILE_MESSAGE}</div>
+                )}
+              </>
+            )}
+            {batchTypeSelected === 'pySpark' && (
+              <>
+                <div className="create-batch-message">Main python file*</div>
+                <Input
+                  //placeholder="Main R file*"
+                  className="create-batch-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e.target.value,
+                      setMainPythonSelected,
+                      setMainPythonValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={mainPythonSelected}
+                />
+                {!mainPythonValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      File must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+                {mainPythonValidation && (
+                  <div className="submit-job-message">{QUERY_FILE_MESSAGE}</div>
+                )}
+              </>
+            )}
+            {batchTypeSelected === 'pySpark' && (
+              <>
+                <div className="create-batches-message">
+                  Additional python files
+                </div>
+                <TagsInput
+                  className="select-job-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e,
+                      setAdditionalPythonFileSelected,
+                      setAdditionalPythonFileValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={additionalPythonFileSelected}
+                  inputProps={{ placeholder: '' }}
+                />
+                {!additionalPythonFileValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      All files must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+              </>
+            )}
+            {batchTypeSelected === 'sparkSql' && (
+              <>
+                <div className="create-batch-message">Query file*</div>
+                <Input
+                  //placeholder="Main R file*"
+                  className="create-batch-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e.target.value,
+                      setQueryFileSelected,
+                      setQueryFileValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={queryFileSelected}
+                />
+                {!queryFileValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      File must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+                {queryFileValidation && (
+                  <div className="create-messagelist">{QUERY_FILE_MESSAGE}</div>
+                )}
+              </>
+            )}
+            <div className="create-batches-message">Custom container image</div>
+            <Input
+              className="create-batch-style "
+              value={containerImageSelected}
+              onChange={e => setContainerImageSelected(e.target.value)}
+              type="text"
+              placeholder=""
+            />
+            <div className="create-custom-messagelist">
+             {CUSTOM_CONTAINER_MESSAGE}
+              <div className="create-container-message">
+              <div
+                className="create-batch-learn-more"
+                onClick={() => {
+                  window.open(`${CONTAINER_REGISTERY}`, '_blank');
+                }}
+              >
+                Container Registry
+              </div>
+              {' or '}
+              <div
+                className="create-batch-learn-more"
+                onClick={() => {
+                  window.open(`${ARTIFACT_REGISTERY}`, '_blank');
+                }}
+              >
+                 Artifact Registry
+              </div>
+               {' . '}
+              <div
+                className="submit-job-learn-more"
+                onClick={() => {
+                  window.open(`${CUSTOM_CONTAINERS}`, '_blank');
+                }}
+              >
+                Learn more
+              </div>
+              </div>
+            </div>
+            {batchTypeSelected === 'spark' ||
+              (batchTypeSelected === 'sparkSql' && (
+                <>
+                  <div className="create-batches-message">Jar files</div>
+                  <TagsInput
+                    className="select-job-style"
+                    onChange={e =>
+                      handleValidationFiles(
+                        e,
+                        setJarFilesSelected,
+                        setJarFileValidation
+                      )
+                    }
+                    addOnBlur={true}
+                    value={jarFilesSelected}
+                    inputProps={{ placeholder: '' }}
+                  />
+                  {!jarFileValidation && (
+                    <div className="error-key-parent">
+                      <iconError.react tag="div" />
+                      <div className="error-key-missing">
+                        All files must include a valid scheme prefix: 'file://',
+                        'gs://', or 'hdfs://'
+                      </div>
+                    </div>
+                  )}
+                  {jarFileValidation && (
+                    <div className="create-messagelist">{JAR_FILE_MESSAGE}</div>
+                  )}
+                </>
+              ))}
+            {batchTypeSelected !== 'sparkSql' && (
+              <>
+                <div className="create-batches-message">Files</div>
+                <TagsInput
+                  className="select-job-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e,
+                      setFilesSelected,
+                      setFileValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={filesSelected}
+                  inputProps={{ placeholder: '' }}
+                />
+                {!fileValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      All files must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+                {fileValidation && (
+                  <div className="create-messagelist">{FILES_MESSAGE}</div>
+                )}
+              </>
+            )}
+            {batchTypeSelected !== 'sparkSql' && (
+              <>
+                <div className="create-batches-message">Archive files</div>
+                <TagsInput
+                  className="select-job-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e,
+                      setArchiveFileSelected,
+                      setArchieveFileValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={ArchiveFilesSelected}
+                  inputProps={{ placeholder: '' }}
+                />
+                {!archieveFileValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      All files must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+                {archieveFileValidation && (
+                  <div className="create-messagelist">
+                    {ARCHIVE_FILES_MESSAGE}
+                  </div>
+                )}
+              </>
+            )}
+            {batchTypeSelected !== 'sparkSql' && (
+              <>
+                <div className="create-batches-message">Arguments</div>
+                <TagsInput
+                  className="select-job-style"
+                  onChange={e => setArgumentsSelected(e)}
+                  addOnBlur={true}
+                  value={argumentsSelected}
+                  inputProps={{ placeholder: '' }}
+                />
+                <div className="create-messagelist">{ARGUMENTS_MESSAGE}</div>
+              </>
+            )}
+            {batchTypeSelected === 'sparkSql' && (
+              <>
+                <div className="submit-job-label-header">Query parameters</div>
+                <LabelProperties
+                  labelDetail={parameterDetail}
+                  setLabelDetail={setParameterDetail}
+                  labelDetailUpdated={parameterDetailUpdated}
+                  setLabelDetailUpdated={setParameterDetailUpdated}
+                  buttonText="ADD PARAMETER"
+                  keyValidation={keyValidation}
+                  setKeyValidation={setKeyValidation}
+                  valueValidation={valueValidation}
+                  setvalueValidation={setvalueValidation}
+                  duplicateKeyError={duplicateKeyError}
+                  setDuplicateKeyError={setDuplicateKeyError}
+                />
+              </>
+            )}
+            <div className="submit-job-label-header">
+              Execution Configuration
+            </div>
+            <div className="create-batches-message">Service account</div>
+            <Input
+              className="create-batch-style "
+              value={serviceAccountSelected}
+              onChange={e => setServiceAccountSelected(e.target.value)}
+              type="text"
+              placeholder=""
+            />
+            <div className="create-messagelist">
+              If not provided, the default GCE service account will be used.
+              <div
+                className="create-batch-learn-more"
+                onClick={() => {
+                  window.open(`${SERVICE_ACCOUNT}`, '_blank');
+                }}
+              >
+                Learn more
+              </div>
+            </div>
+            <div className="submit-job-label-header">Network Configuration</div>
+            <div className="create-batches-message">
+              Establishes connectivity for the VM instances in this cluster.
+            </div>
+            <div className="create-batches-message">
+              Networks in this project
+            </div>
+            <div className="create-batch-network">
+              <div className="create-batch-network-message">
+                Primary network*
+              </div>
+              <div className="create-batch-network-message">Subnetwork</div>
+            </div>
+            <div>
+              <div className="create-batch-network">
+                <Select
+                  className="select-primary-network-style"
+                  value={networkSelected}
+                  onChange={handleNetworkChange}
+                  type="text"
+                  options={networkList}
+                />
+
+                <Select
+                  className="select-sub-network-style"
+                  value={subNetworkSelected}
+                  onChange={handleSubNetworkChange}
+                  type="text"
+                  options={[]}
+                  placeholder={networkSelected}
+                />
+              </div>
+            </div>
+            <div className="create-batches-message">Network tags*</div>
+            <TagsInput
+              className="select-job-style"
+              onChange={e => setNetworkTagSelected(e)}
+              addOnBlur={true}
+              value={networkTagSelected}
+              inputProps={{ placeholder: '' }}
+            />
+            <div>
+              <div className="submit-job-label-header">Encryption</div>
+              <div>
+                <div className="create-batch-radio">
+                  {/* <Radio
+                    className="select-batch-radio-style"
+                    value="googleManaged"
+                    checked={selectedEncryptionRadio === 'googleManaged'}
+                    onChange={() => setSelectedEncryptionRadio('googleManaged')}
+                  /> */}
+                  <div className="create-batch-message">
+                    Google-managed encryption key
+                  </div>
+                </div>
+                <div className="create-batch-sub-message">
+                  No configuration required
+                </div>
+              </div>
+              {/* <div>
+                <div className="create-batch-radio">
+                  <Radio
+                    className="select-batch-radio-style"
+                    value="googleManaged"
+                    checked={selectedEncryptionRadio === 'customerManaged'}
+                    onChange={() =>
+                      setSelectedEncryptionRadio('customerManaged')
+                    }
+                  />
+                  <div className="create-batch-message">
+                    Customer-managed encryption key (CMEK)
+                  </div>
+                </div>
+                <div className="create-batch-sub-message">
+                  Manage via{' '}
+                  <div
+                    className="create-batch-learn-more"
+                    onClick={() => {
+                      window.open(`${SECURITY_KEY}`, '_blank');
+                    }}
+                  >
+                    Google Cloud Key Management Service
+                  </div>
+                </div>
+                {/* {selectedEncryptionRadio === 'customerManaged' && (
+                  <div className="create-batch-input">
+                    <Select
+                      className="select-job-style"
+                      placeholder="Select an option"
+                      options={[]}
+                    />
+                  </div>
+                )} 
+              </div> */}
+            </div>
+
+            <div className="submit-job-label-header">
+              Peripheral Configuration
+            </div>
+            <div className="create-batches-message">
+              Configure Dataproc to use Dataproc Metastore as its Hive
+              metastore.
+              <div
+                className="create-batch-learn-more"
+                onClick={() => {
+                  window.open(`${SELF_MANAGED_CLUSTER}`, '_blank');
+                }}
+              >
+                Learn more
+              </div>
+            </div>
+            <div className="create-messagelist">
+              {METASTORE_MESSAGE}
+            </div>
+            <div className="create-batches-message">Metastore project</div>
+            <Select
+              placeholder={projectId}
+              className="select-job-style"
+              value={projectId}
+              onChange={handleProjectIdChange}
+              options={projectList}
+            />
+            <div className="create-batches-message">Metastore region</div>
+            {isLoadingRegion ? (
+              <ClipLoader
+                loading={true}
+                size={25}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+            ) : (
+              <Select
+                placeholder={region}
+                className="select-job-style"
+                value={region}
+                onChange={handleRegionChange}
+                options={regionList}
+              />
+            )}
+
+            <div className="create-batches-message">Metastore service</div>
+            {isLoadingService ? (
+              <ClipLoader
+                loading={true}
+                size={25}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+            ) : (
+              <Select
+                className="select-job-style"
+                value={servicesSelected}
+                type="text"
+                options={servicesList}
+                onChange={handleServiceSelected}
+              />
+            )}
+
+            <div className="submit-job-label-header">
+              History server cluster
+            </div>
+            <div className="create-batches-message">
+              Choose a history server cluster to store logs in.{' '}
+            </div>
+            <div className="create-batches-message">History server cluster</div>
+
+            <Dropdown
+              className="select-job-style"
+              search
+              selection
+              value={clusterSelected}
+              onChange={handleClusterSelected}
+              options={clustersList}
+              placeholder="Search..."
+            />
+            <div className="submit-job-label-header">Properties</div>
+            <LabelProperties
+              labelDetail={propertyDetail}
+              setLabelDetail={setPropertyDetail}
+              labelDetailUpdated={propertyDetailUpdated}
+              setLabelDetailUpdated={setPropertyDetailUpdated}
+              buttonText="ADD PROPERTY"
+              keyValidation={keyValidation}
+              setKeyValidation={setKeyValidation}
+              valueValidation={valueValidation}
+              setvalueValidation={setvalueValidation}
+              duplicateKeyError={duplicateKeyError}
+              setDuplicateKeyError={setDuplicateKeyError}
+            />
+            <div className="submit-job-label-header">Labels</div>
+            <LabelProperties
+              labelDetail={labelDetail}
+              setLabelDetail={setLabelDetail}
+              labelDetailUpdated={labelDetailUpdated}
+              setLabelDetailUpdated={setLabelDetailUpdated}
+              buttonText="ADD LABEL"
+              keyValidation={keyValidation}
+              setKeyValidation={setKeyValidation}
+              valueValidation={valueValidation}
+              setvalueValidation={setvalueValidation}
+              duplicateKeyError={duplicateKeyError}
+              setDuplicateKeyError={setDuplicateKeyError}
+            />
+            <div className="job-button-style-parent">
+              <div
+                className={
+                  isSubmitDisabled()
+                    ? 'submit-button-disable-style'
+                    : 'submit-button-style'
+                }
+                aria-label="submit Batch"
+              >
+                <div
+                  onClick={() => {
+                    if (!isSubmitDisabled()) {
+                      handleSubmit();
+                    }
+                  }}
+                >
+                  SUBMIT
+                </div>
+              </div>
+              <div className="job-cancel-button-style" aria-label="cancel Batch">
+                <div onClick={() => handleCreateBatchBackView()}>CANCEL</div>
+              </div>
+              {error.isOpen && (
+                <ErrorPopup
+                  onCancel={() => setError({ isOpen: false, message: '' })}
+                  errorPopupOpen={error.isOpen}
+                  DeleteMsg={error.message}
+                />
+              )}
+            </div>
+          </form>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default CreateBatch;
diff --git a/src/batches/listBatches.tsx b/src/batches/listBatches.tsx
new file mode 100644
index 00000000..c8b8e612
--- /dev/null
+++ b/src/batches/listBatches.tsx
@@ -0,0 +1,285 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { useTable, useGlobalFilter, usePagination } from 'react-table';
+import { LabIcon } from '@jupyterlab/ui-components';
+import createClusterIcon from '../../style/icons/create_cluster_icon.svg';
+import filterIcon from '../../style/icons/filter_icon.svg';
+import SucceededIcon from '../../style/icons/succeeded_icon.svg';
+import clusterRunningIcon from '../../style/icons/cluster_running_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import { ClipLoader } from 'react-spinners';
+import GlobalFilter from '../utils/globalFilter';
+import {
+  // CREATE_BATCH_URL,
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_FAIL,
+  STATUS_PENDING,
+  STATUS_PROVISIONING,
+  STATUS_RUNNING,
+  STATUS_SUCCESS
+} from '../utils/const';
+import TableData from '../utils/tableData';
+import { PaginationView } from '../utils/paginationView';
+import { ICellProps } from '../utils/utils';
+
+const iconCreateCluster = new LabIcon({
+  name: 'launcher:create-cluster-icon',
+  svgstr: createClusterIcon
+});
+const iconFilter = new LabIcon({
+  name: 'launcher:filter-icon',
+  svgstr: filterIcon
+});
+const iconSucceeded = new LabIcon({
+  name: 'launcher:succeeded-icon',
+  svgstr: SucceededIcon
+});
+
+const iconClusterRunning = new LabIcon({
+  name: 'launcher:cluster-running-icon',
+  svgstr: clusterRunningIcon
+});
+const iconClusterError = new LabIcon({
+  name: 'launcher:cluster-error-icon',
+  svgstr: clusterErrorIcon
+});
+
+interface IBatch {
+  batchID: string;
+  status: string;
+  location: string;
+  creationTime: string;
+  elapsedTime: string;
+  type: string;
+  actions: JSX.Element;
+  setCreateBatchView: (value: boolean) => void;
+  createBatchView: boolean;
+}
+
+interface IListBatchesProps {
+  batchesList: IBatch[];
+  isLoading: boolean;
+  setPollingDisable: (value: boolean) => void;
+  listBatchAPI: () => void;
+  handleBatchDetails: (batchID: string) => void;
+  setCreateBatchView: (value: boolean) => void;
+  createBatchView: boolean;
+}
+
+function ListBatches({
+  batchesList,
+  isLoading,
+  setPollingDisable,
+  listBatchAPI,
+  handleBatchDetails,
+  setCreateBatchView,
+  createBatchView
+}: IListBatchesProps) {
+  const data = batchesList;
+  const columns = React.useMemo(
+    () => [
+      {
+        Header: 'Batch ID',
+        accessor: 'batchID'
+      },
+      {
+        Header: 'Status',
+        accessor: 'status'
+      },
+      {
+        Header: 'Location',
+        accessor: 'location'
+      },
+      {
+        Header: 'Creation time',
+        accessor: 'creationTime'
+      },
+      {
+        Header: 'Elapsed time',
+        accessor: 'elapsedTime'
+      },
+      {
+        Header: 'Type',
+        accessor: 'type'
+      },
+      {
+        Header: 'Actions',
+        accessor: 'actions'
+      }
+    ],
+    []
+  );
+
+  const {
+    getTableProps,
+    getTableBodyProps,
+    headerGroups,
+    rows,
+    prepareRow,
+    state,
+    preGlobalFilteredRows,
+    setGlobalFilter,
+    page,
+    canPreviousPage,
+    canNextPage,
+    nextPage,
+    previousPage,
+    setPageSize,
+    state: { pageIndex, pageSize }
+  } = useTable(
+    //@ts-ignore react-table 'columns' which is declared here on type 'TableOptions<ICluster>'
+    { columns, data, autoResetPage: false, initialState: { pageSize: 50 } },
+    useGlobalFilter,
+    usePagination
+  );
+
+  const handleCreateBatchOpen = () => {
+    setCreateBatchView(true);
+  };
+
+  const tableDataCondition = (cell: ICellProps) => {
+    if (cell.column.Header === 'Batch ID') {
+      return (
+        <td
+          role="button"
+          {...cell.getCellProps()}
+          className="cluster-name"
+          onClick={() => handleBatchDetails(cell.value)}
+        >
+          {cell.value}
+        </td>
+      );
+    } else if (cell.column.Header === 'Status') {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          <div key="Status" className="cluster-status-parent">
+            {cell.value === STATUS_RUNNING && (
+              <iconClusterRunning.react tag="div" />
+            )}
+            {cell.value === STATUS_FAIL && <iconClusterError.react tag="div" />}
+            {cell.value === STATUS_SUCCESS && <iconSucceeded.react tag="div" />}
+            {(cell.value === STATUS_PROVISIONING ||
+              cell.value === STATUS_CREATING ||
+              cell.value === STATUS_PENDING ||
+              cell.value === STATUS_DELETING) && (
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={15}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+            )}
+            <div className="cluster-status">
+              {cell.value && cell.value.toLowerCase()}
+            </div>
+          </div>
+        </td>
+      );
+    } else {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          {cell.render('Cell')}
+        </td>
+      );
+    }
+  };
+
+  return (
+    <div>
+      {batchesList.length > 0 && !createBatchView ? (
+        <div>
+          <div className="batch-header-part">
+            <div
+              className="create-batch-overlay"
+              onClick={() => {
+                handleCreateBatchOpen();
+              }}
+            >
+              <div className="create-cluster-icon">
+                <iconCreateCluster.react tag="div" />
+              </div>
+              <div className="create-cluster-text">Create Batch</div>
+            </div>
+          </div>
+          <div className="filter-cluster-overlay">
+            <div className="filter-cluster-icon">
+              <iconFilter.react tag="div" />
+            </div>
+            <div className="filter-cluster-text"></div>
+            <div className="filter-cluster-section">
+              <GlobalFilter
+                preGlobalFilteredRows={preGlobalFilteredRows}
+                globalFilter={state.globalFilter}
+                setGlobalFilter={setGlobalFilter}
+                setPollingDisable={setPollingDisable}
+              />
+            </div>
+          </div>
+          <div className="clusters-list-table-parent">
+            <TableData
+              getTableProps={getTableProps}
+              headerGroups={headerGroups}
+              getTableBodyProps={getTableBodyProps}
+              isLoading={isLoading}
+              rows={rows}
+              page={page}
+              prepareRow={prepareRow}
+              tableDataCondition={tableDataCondition}
+              fromPage="Batches"
+            />
+            {batchesList.length > 50 && (
+              <PaginationView
+                pageSize={pageSize}
+                setPageSize={setPageSize}
+                pageIndex={pageIndex}
+                allData={batchesList}
+                previousPage={previousPage}
+                nextPage={nextPage}
+                canPreviousPage={canPreviousPage}
+                canNextPage={canNextPage}
+              />
+            )}
+          </div>
+        </div>
+      ) : (
+        <div>
+          {isLoading && (
+            <div className="spin-loaderMain">
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={18}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+              Loading Batches
+            </div>
+          )}
+          {!isLoading && (
+            <div className="no-data-style">No rows to display</div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default ListBatches;
diff --git a/src/cluster/cluster.tsx b/src/cluster/cluster.tsx
new file mode 100644
index 00000000..7f1988d1
--- /dev/null
+++ b/src/cluster/cluster.tsx
@@ -0,0 +1,494 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ReactWidget } from '@jupyterlab/apputils';
+import React, { useState, useEffect, useRef } from 'react';
+import JobComponent from '../jobs/jobs';
+import ClusterDetails from './clusterDetails';
+import { authApi, checkConfig, statusValue } from '../utils/utils';
+import { LabIcon } from '@jupyterlab/ui-components';
+import startIcon from '../../style/icons/start_icon.svg';
+import stopIcon from '../../style/icons/stop_icon.svg';
+import restartIcon from '../../style/icons/restart_icon.svg';
+import startDisableIcon from '../../style/icons/start_icon_disable.svg';
+import stopDisableIcon from '../../style/icons/stop_icon_disable.svg';
+import restartDisableIcon from '../../style/icons/restart_icon_disable.svg';
+import {
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  POLLING_TIME_LIMIT,
+  API_HEADER_BEARER,
+  LOGIN_STATE,
+  ClusterStatus
+} from '../utils/const';
+import ListCluster from './listCluster';
+import { ClipLoader } from 'react-spinners';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { startClusterApi, stopClusterApi } from '../utils/clusterServices';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconStart = new LabIcon({
+  name: 'launcher:start-icon',
+  svgstr: startIcon
+});
+const iconStop = new LabIcon({
+  name: 'launcher:stop-icon',
+  svgstr: stopIcon
+});
+const iconRestart = new LabIcon({
+  name: 'launcher:restart-icon',
+  svgstr: restartIcon
+});
+
+const iconStartDisable = new LabIcon({
+  name: 'launcher:start-disable-icon',
+  svgstr: startDisableIcon
+});
+const iconStopDisable = new LabIcon({
+  name: 'launcher:stop-disable-icon',
+  svgstr: stopDisableIcon
+});
+const iconRestartDisable = new LabIcon({
+  name: 'launcher:restart-disable-icon',
+  svgstr: restartDisableIcon
+});
+
+const ClusterComponent = (): React.JSX.Element => {
+  type Mode = 'Clusters' | 'Serverless' | 'Jobs';
+  const [clustersList, setclustersList] = useState([]);
+  const [clusterResponse, setClusterResponse] = useState([]);
+  const [detailedJobView, setDetailedJobView] = useState(false);
+  const [submitJobView, setSubmitJobView] = useState(false);
+  const [restartEnabled, setRestartEnabled] = useState(false);
+  const [selectedMode, setSelectedMode] = useState<Mode>('Clusters');
+  const [isLoading, setIsLoading] = useState(true);
+  const [clusterSelected, setClusterSelected] = useState<string>('');
+  const [pollingDisable, setPollingDisable] = useState(false);
+  const [detailedView, setDetailedView] = useState(false);
+  const [loggedIn, setLoggedIn] = useState(false);
+  const [configError, setConfigError] = useState(false);
+  const [loginError, setLoginError] = useState(false);
+  const [configLoading, setConfigLoading] = useState(true);
+  const [projectId, setProjectId] = useState('');
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+  const [selectedJobClone, setSelectedJobClone] = useState({});
+
+  const pollingClusters = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const selectedModeChange = (mode: Mode) => {
+    setSelectedMode(mode);
+  };
+
+  const listClustersAPI = async (
+    nextPageToken?: string,
+    previousClustersList?: object
+  ) => {
+    const credentials = await authApi();
+    const pageToken = nextPageToken ?? '';
+    if (credentials) {
+      setProjectId(credentials.project_id || '');
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters?pageSize=50&pageToken=${pageToken}`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              let transformClusterListData = [];
+              if (responseResult && responseResult.clusters) {
+                setClusterResponse(responseResult);
+                transformClusterListData = responseResult.clusters.map(
+                  (data: any) => {
+                    const statusVal = statusValue(data);
+                    /*
+                     Extracting zone from zoneUri
+                      Example: "projects/{project}/zones/{zone}"
+                  */
+                    const zoneUri =
+                      data.config.gceClusterConfig.zoneUri.split('/');
+
+                    return {
+                      clusterUuid: data.clusterUuid,
+                      status: statusVal,
+                      clusterName: data.clusterName,
+                      clusterImage: data.config.softwareConfig.imageVersion,
+                      region: data.labels['goog-dataproc-location'],
+                      zone: zoneUri[zoneUri.length - 1],
+                      totalWorkersNode: data.config.workerConfig
+                        ? data.config.workerConfig.numInstances
+                        : 0,
+                      schedulesDeletion: data.config.lifecycleConfig
+                        ? 'On'
+                        : 'Off',
+                      actions: renderActions(data)
+                    };
+                  }
+                );
+              }
+              const existingClusterData = previousClustersList ?? [];
+              //setStateAction never type issue
+              const allClustersData: any = [
+                ...(existingClusterData as []),
+                ...transformClusterListData
+              ];
+
+              if (responseResult.nextPageToken) {
+                listClustersAPI(responseResult.nextPageToken, allClustersData);
+              } else {
+                setclustersList(allClustersData);
+                setIsLoading(false);
+                setLoggedIn(true);
+              }
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error listing clusters', err);
+          toast.error('Failed to fetch clusters');
+        });
+    }
+  };
+  const handleClusterDetails = (selectedName: string) => {
+    pollingClusters(listClustersAPI, true);
+    setClusterSelected(selectedName);
+    setDetailedView(true);
+  };
+
+  const statusApi = async (selectedcluster: string) => {
+    const credentials = await authApi();
+    if (credentials) {
+      await fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${selectedcluster}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then(
+              (responseResult: {
+                status: { state: ClusterStatus };
+                clusterName: string;
+              }) => {
+                if (
+                  responseResult.status.state === ClusterStatus.STATUS_STOPPED
+                ) {
+                  startClusterApi(selectedcluster);
+                  clearInterval(timer.current);
+                }
+              }
+            )
+            .catch((e: Error) => console.log(e));
+        })
+        .catch((err: Error) => {
+          console.error('Error fetching status', err);
+          toast.error(`Failed to fetch the status ${selectedcluster}`);
+        });
+
+      listClustersAPI();
+    }
+  };
+
+  const restartClusterApi = async (selectedcluster: string) => {
+    setRestartEnabled(true);
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${selectedcluster}:stop`,
+        {
+          method: 'POST',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: Response) => {
+              console.log(responseResult);
+              listClustersAPI();
+              timer.current = setInterval(() => {
+                statusApi(selectedcluster);
+              }, POLLING_TIME_LIMIT);
+            })
+            .catch((e: Error) => console.log(e));
+        })
+        .catch((err: Error) => {
+          console.error('Error restarting cluster', err);
+          toast.error(`Failed to restart the cluster ${selectedcluster}`);
+        });
+
+      listClustersAPI();
+      setRestartEnabled(false);
+    }
+  };
+
+  const startButton = (data: {
+    status: { state: ClusterStatus };
+    clusterName: string;
+  }) => {
+    return (
+      <div
+        role="button"
+        aria-disabled={
+          data.status.state !== ClusterStatus.STATUS_STOPPED &&
+          restartEnabled !== true
+        }
+        className={
+          data.status.state === ClusterStatus.STATUS_STOPPED &&
+          restartEnabled !== true
+            ? 'icon-buttons-style'
+            : 'icon-buttons-style-disable'
+        }
+        title="Start Cluster"
+        onClick={
+          data.status.state === ClusterStatus.STATUS_STOPPED
+            ? () => startClusterApi(data.clusterName)
+            : undefined
+        }
+      >
+        {data.status.state === ClusterStatus.STATUS_STOPPED ? (
+          <iconStart.react tag="div" />
+        ) : (
+          <iconStartDisable.react tag="div" />
+        )}
+      </div>
+    );
+  };
+
+  const stopButton = (data: {
+    status: { state: ClusterStatus };
+    clusterName: string;
+  }) => {
+    return (
+      <div
+        role="button"
+        aria-disabled={data.status.state !== ClusterStatus.STATUS_RUNNING}
+        className={
+          data.status.state === ClusterStatus.STATUS_RUNNING
+            ? 'icon-buttons-style'
+            : 'icon-buttons-style-disable'
+        }
+        title="Stop Cluster"
+        onClick={
+          data.status.state === ClusterStatus.STATUS_RUNNING
+            ? () => stopClusterApi(data.clusterName)
+            : undefined
+        }
+      >
+        {data.status.state === ClusterStatus.STATUS_RUNNING ? (
+          <iconStop.react tag="div" />
+        ) : (
+          <iconStopDisable.react tag="div" />
+        )}
+      </div>
+    );
+  };
+
+  const restartButton = (data: {
+    status: { state: ClusterStatus };
+    clusterName: string;
+  }) => {
+    return (
+      <div
+        role="button"
+        aria-disabled={data.status.state !== ClusterStatus.STATUS_RUNNING}
+        className={
+          data.status.state === ClusterStatus.STATUS_RUNNING
+            ? 'icon-buttons-style'
+            : 'icon-buttons-style-disable'
+        }
+        title="Restart Cluster"
+        onClick={
+          data.status.state === ClusterStatus.STATUS_RUNNING
+            ? () => restartClusterApi(data.clusterName)
+            : undefined
+        }
+      >
+        {data.status.state === ClusterStatus.STATUS_RUNNING ? (
+          <iconRestart.react tag="div" />
+        ) : (
+          <iconRestartDisable.react tag="div" />
+        )}
+      </div>
+    );
+  };
+  const renderActions = (data: {
+    status: { state: ClusterStatus };
+    clusterName: string;
+  }) => {
+    return (
+      <div className="actions-icon">
+        {startButton(data)}
+        {stopButton(data)}
+        {restartButton(data)}
+      </div>
+    );
+  };
+
+  const toggleStyleSelection = (toggleItem: string) => {
+    if (selectedMode === toggleItem) {
+      return 'selected-header';
+    } else {
+      return 'unselected-header';
+    }
+  };
+
+  useEffect(() => {
+    checkConfig(setLoggedIn, setConfigError, setLoginError);
+    const localstorageGetInformation = localStorage.getItem('loginState');
+    setLoggedIn(localstorageGetInformation === LOGIN_STATE);
+    if (loggedIn) {
+      setConfigLoading(false);
+    }
+    listClustersAPI();
+    if (!detailedView && selectedMode === 'Clusters') {
+      pollingClusters(listClustersAPI, pollingDisable);
+    }
+
+    return () => {
+      pollingClusters(listClustersAPI, true);
+    };
+  }, [pollingDisable, detailedView, selectedMode]);
+
+  return (
+    <div className="component-level">
+      {configLoading && !loggedIn && !configError && !loginError && (
+        <div className="spin-loaderMain">
+          <ClipLoader
+            color="#8A8A8A"
+            loading={true}
+            size={18}
+            aria-label="Loading Spinner"
+            data-testid="loader"
+          />
+          Loading Clusters
+        </div>
+      )}
+      {loggedIn && !configError ? (
+        <>
+          {detailedView && (
+            <ClusterDetails
+              clusterSelected={clusterSelected}
+              setDetailedView={setDetailedView}
+              detailedJobView={detailedJobView}
+              setDetailedJobView={setDetailedJobView}
+              submitJobView={submitJobView}
+              clusterResponse={clusterResponse}
+              selectedJobClone={selectedJobClone}
+              setSelectedJobClone={setSelectedJobClone}
+              setSubmitJobView={setSubmitJobView}
+            />
+          )}
+          {!detailedView && (
+            <div className="clusters-list-component" role="tablist">
+              {!detailedJobView && !submitJobView && (
+                <div className="clusters-list-overlay" role="tab">
+                  <div
+                    role="tabpanel"
+                    className={toggleStyleSelection('Clusters')}
+                    onClick={() => selectedModeChange('Clusters')}
+                  >
+                    Clusters
+                  </div>
+                  <div
+                    role="tabpanel"
+                    className={toggleStyleSelection('Jobs')}
+                    onClick={() => selectedModeChange('Jobs')}
+                  >
+                    Jobs
+                  </div>
+                </div>
+              )}
+              <div>
+                {selectedMode === 'Jobs' ? (
+                  <JobComponent
+                    clustersList={clustersList}
+                    detailedJobView={detailedJobView}
+                    setDetailedJobView={setDetailedJobView}
+                    submitJobView={submitJobView}
+                    setSubmitJobView={setSubmitJobView}
+                    setDetailedView={setDetailedView}
+                    clusterResponse={clusterResponse}
+                    selectedJobClone={selectedJobClone}
+                    setSelectedJobClone={setSelectedJobClone}
+                  />
+                ) : (
+                  <ListCluster
+                    clustersList={clustersList}
+                    isLoading={isLoading}
+                    setPollingDisable={setPollingDisable}
+                    listClustersAPI={listClustersAPI}
+                    handleClusterDetails={handleClusterDetails}
+                    project_id={projectId}
+                  />
+                )}
+              </div>
+              <ToastContainer />
+            </div>
+          )}
+        </>
+      ) : (
+        loginError && (
+          <div className="login-error">Please login to continue</div>
+        )
+      )}
+      {configError && (
+        <div className="login-error">
+          Please configure gcloud with account, project-id and region
+        </div>
+      )}
+    </div>
+  );
+};
+
+export class Cluster extends ReactWidget {
+  constructor() {
+    super();
+  }
+
+  render(): React.JSX.Element {
+    return <ClusterComponent />;
+  }
+}
diff --git a/src/cluster/clusterDetails.tsx b/src/cluster/clusterDetails.tsx
new file mode 100644
index 00000000..146e8c63
--- /dev/null
+++ b/src/cluster/clusterDetails.tsx
@@ -0,0 +1,434 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useState, useEffect, useRef } from 'react';
+import JobComponent from '../jobs/jobs';
+import { LabIcon } from '@jupyterlab/ui-components';
+import LeftArrowIcon from '../../style/icons/left_arrow_icon.svg';
+import StartClusterIcon from '../../style/icons/start_cluster_icon.svg';
+import StartClusterDisableIcon from '../../style/icons/start_cluster_icon_disable.svg';
+import StopClusterIcon from '../../style/icons/stop_cluster_icon.svg';
+import StopClusterDisableIcon from '../../style/icons/stop_cluster_disable_icon.svg';
+import DeleteClusterIcon from '../../style/icons/delete_cluster_icon.svg';
+import errorIcon from '../../style/icons/error_icon.svg';
+import clusterRunningIcon from '../../style/icons/cluster_running_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import stopIcon from '../../style/icons/stop_icon.svg';
+import {
+  deleteClusterApi,
+  startClusterApi,
+  stopClusterApi
+} from '../utils/clusterServices';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_ERROR,
+  STATUS_PROVISIONING,
+  STATUS_RUNNING,
+  STATUS_STARTING,
+  STATUS_STOPPED,
+  STATUS_STOPPING
+} from '../utils/const';
+import { authApi } from '../utils/utils';
+import ClipLoader from 'react-spinners/ClipLoader';
+import ViewLogs from '../utils/viewLogs';
+import DeletePopup from '../utils/deletePopup';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import SubmitJob from '../jobs/submitJob';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconLeftArrow = new LabIcon({
+  name: 'launcher:left-arrow-icon',
+  svgstr: LeftArrowIcon
+});
+const iconStartCluster = new LabIcon({
+  name: 'launcher:start-cluster-icon',
+  svgstr: StartClusterIcon
+});
+const iconStartClusterDisable = new LabIcon({
+  name: 'launcher:start-cluster-disable-icon',
+  svgstr: StartClusterDisableIcon
+});
+const iconStopCluster = new LabIcon({
+  name: 'launcher:stop-cluster-icon',
+  svgstr: StopClusterIcon
+});
+const iconStopClusterDisable = new LabIcon({
+  name: 'launcher:stop-cluster-disable-icon',
+  svgstr: StopClusterDisableIcon
+});
+const iconDeleteCluster = new LabIcon({
+  name: 'launcher:delete-cluster-icon',
+  svgstr: DeleteClusterIcon
+});
+const iconClusterRunning = new LabIcon({
+  name: 'launcher:cluster-running-icon',
+  svgstr: clusterRunningIcon
+});
+const iconClusterError = new LabIcon({
+  name: 'launcher:cluster-error-icon',
+  svgstr: clusterErrorIcon
+});
+const iconStop = new LabIcon({
+  name: 'launcher:stop-icon',
+  svgstr: stopIcon
+});
+const iconError = new LabIcon({
+  name: 'launcher:error-icon',
+  svgstr: errorIcon
+});
+interface IClusterDetailsProps {
+  clusterSelected: string;
+  setDetailedView: (value: boolean) => void;
+  setDetailedClusterView?: (value: boolean) => void;
+  detailedJobView?: boolean;
+  setDetailedJobView?: (value: boolean) => void;
+  setSubmitJobView?: (value: boolean) => void;
+  submitJobView: boolean;
+  clusterResponse: any;
+  selectedJobClone: any;
+  setSelectedJobClone?: (value: boolean) => void;
+}
+function ClusterDetails({
+  clusterSelected,
+  setDetailedView,
+  setDetailedClusterView,
+  detailedJobView,
+  setSubmitJobView,
+  setDetailedJobView,
+  submitJobView,
+  clusterResponse,
+  selectedJobClone,
+  setSelectedJobClone
+}: IClusterDetailsProps) {
+  const [clusterInfo, setClusterInfo] = useState({
+    status: { state: '' },
+    clusterName: '',
+    clusterUuid: ''
+  });
+  const [isLoading, setIsLoading] = useState(true);
+  const [errorView, setErrorView] = useState(false);
+  const [projectName, setProjectName] = useState('');
+  const [deletePopupOpen, setDeletePopupOpen] = useState(false);
+  const [selectedCluster, setSelectedCluster] = useState('');
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+
+  const pollingClusterDetails = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const handleDetailedView = () => {
+    pollingClusterDetails(getClusterDetails, true);
+    setDetailedView(false);
+    setDetailedClusterView?.(false);
+    setDetailedJobView?.(false);
+  };
+
+  const handleDeleteCluster = (clustername: string) => {
+    setSelectedCluster(clustername);
+    setDeletePopupOpen(true);
+  };
+  const handleCancelDelete = () => {
+    setDeletePopupOpen(false);
+  };
+
+  const handleDelete = async () => {
+    await deleteClusterApi(selectedCluster);
+
+    setDeletePopupOpen(false);
+    handleDetailedView();
+  };
+
+  const getClusterDetails = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      setProjectName(credentials.project_id || '');
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${clusterSelected}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              if (responseResult.error && responseResult.error.code === 404) {
+                setErrorView(true);
+              }
+              setClusterInfo(responseResult);
+              setIsLoading(false);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error listing clusters Details', err);
+          toast.error(`Failed to fetch cluster details ${clusterSelected}`);
+        });
+    }
+  };
+
+  const clusterDetailsAction = () => {
+    return (
+      <div className="cluster-details-action-parent">
+        <div
+          role="button"
+          aria-disabled={clusterInfo.status.state !== STATUS_STOPPED}
+          className={
+            clusterInfo.status.state === STATUS_STOPPED
+              ? 'action-cluster-section'
+              : 'action-cluster-section disabled'
+          }
+          onClick={() =>
+            clusterInfo.status.state === STATUS_STOPPED &&
+            startClusterApi(clusterInfo.clusterName)
+          }
+        >
+          <div
+            className="action-cluster-icon"
+            role="button"
+            aria-disabled={clusterInfo.status.state !== STATUS_STOPPED}
+          >
+            {clusterInfo.status.state === STATUS_STOPPED ? (
+              <iconStartCluster.react tag="div" />
+            ) : (
+              <iconStartClusterDisable.react tag="div" />
+            )}
+          </div>
+          <div className="action-cluster-text">START</div>
+        </div>
+        <div
+          role="button"
+          aria-disabled={clusterInfo.status.state !== STATUS_RUNNING}
+          className={
+            clusterInfo.status.state === STATUS_RUNNING
+              ? 'action-cluster-section'
+              : 'action-cluster-section disabled'
+          }
+          onClick={() =>
+            clusterInfo.status.state === STATUS_RUNNING &&
+            stopClusterApi(clusterInfo.clusterName)
+          }
+        >
+          <div className="action-cluster-icon">
+            {clusterInfo.status.state === STATUS_RUNNING ? (
+              <iconStopCluster.react tag="div" />
+            ) : (
+              <iconStopClusterDisable.react tag="div" />
+            )}
+          </div>
+          <div className="action-cluster-text">STOP</div>
+        </div>
+        <div
+          role="button"
+          className="action-cluster-section"
+          onClick={() => handleDeleteCluster(clusterInfo.clusterName)}
+        >
+          <div className="action-cluster-icon">
+            <iconDeleteCluster.react tag="div" />
+          </div>
+          <div className="action-cluster-text">DELETE</div>
+        </div>
+      </div>
+    );
+  };
+
+  useEffect(() => {
+    getClusterDetails();
+    pollingClusterDetails(getClusterDetails, false);
+
+    return () => {
+      pollingClusterDetails(getClusterDetails, true);
+    };
+  }, []);
+
+  return (
+    <div>
+      {errorView && (
+        <div className="error-view-parent">
+          <div
+            role="button"
+            aria-label="back-arrow-icon"
+            className="back-arrow-icon"
+            onClick={() => handleDetailedView()}
+          >
+            <iconLeftArrow.react tag="div" />
+          </div>
+          <div className="error-view-message-parent">
+            <iconError.react tag="div" />
+            <div role="alert" className="error-view-message">
+              Unable to find the resource you requested
+            </div>
+          </div>
+        </div>
+      )}
+      {submitJobView && !detailedJobView && (
+        <SubmitJob
+          setSubmitJobView={setSubmitJobView}
+          selectedJobClone={selectedJobClone}
+          clusterResponse={clusterResponse}
+        />
+      )}
+      {deletePopupOpen && (
+        <DeletePopup
+          onCancel={() => handleCancelDelete()}
+          onDelete={() => handleDelete()}
+          deletePopupOpen={deletePopupOpen}
+          DeleteMsg={
+            'This will delete ' + selectedCluster + ' and cannot be undone.'
+          }
+        />
+      )}
+      {!submitJobView && (
+        <div className="scroll-comp">
+          <ToastContainer />
+          {!errorView && clusterInfo.clusterName !== '' ? (
+            <div>
+              {!detailedJobView && (
+                <div>
+                  <div className="cluster-details-header">
+                    <div
+                      role="button"
+                      aria-label="back-arrow-icon"
+                      className="back-arrow-icon"
+                      onClick={() => handleDetailedView()}
+                    >
+                      <iconLeftArrow.react tag="div" />
+                    </div>
+                    <div className="cluster-details-title">Cluster details</div>
+                    {clusterDetailsAction()}
+                    <ViewLogs
+                      clusterInfo={clusterInfo}
+                      projectName={projectName}
+                    />
+                  </div>
+                  <div className="cluster-details-container">
+                    <div className="row-details"></div>
+                    <div className="row-details">
+                      <div className="cluster-details-label">Name</div>
+                      <div className="cluster-details-value">
+                        {clusterInfo.clusterName}
+                      </div>
+                    </div>
+                    <div className="row-details">
+                      <div className="cluster-details-label">Cluster UUID</div>
+                      <div className="cluster-details-value">
+                        {clusterInfo.clusterUuid}
+                      </div>
+                    </div>
+                    <div className="row-details">
+                      <div className="cluster-details-label">Type</div>
+                      <div className="cluster-details-value">
+                        Dataproc Cluster
+                      </div>
+                    </div>
+                    <div className="row-details">
+                      <div className="cluster-details-label">Status</div>
+                      <div
+                        className="cluster-detail-status-parent"
+                        aria-status={clusterInfo.status.state}
+                        aria-label={clusterInfo.status.state}
+                      >
+                        {clusterInfo.status.state === STATUS_RUNNING && (
+                          <iconClusterRunning.react tag="div" />
+                        )}
+                        {clusterInfo.status.state === STATUS_STOPPED && (
+                          <iconStop.react tag="div" />
+                        )}
+                        {clusterInfo.status.state === STATUS_ERROR && (
+                          <iconClusterError.react tag="div" />
+                        )}
+                        {(clusterInfo.status.state === STATUS_PROVISIONING ||
+                          clusterInfo.status.state === STATUS_CREATING ||
+                          clusterInfo.status.state === STATUS_STARTING ||
+                          clusterInfo.status.state === STATUS_STOPPING ||
+                          clusterInfo.status.state === STATUS_DELETING) && (
+                          <div>
+                            <ClipLoader
+                              color="#8A8A8A"
+                              loading={true}
+                              size={15}
+                              aria-label="Loading Spinner"
+                              data-testid="loader"
+                            />
+                          </div>
+                        )}
+                        <div className="cluster-status">
+                          {clusterInfo.status.state === STATUS_CREATING
+                            ? STATUS_PROVISIONING
+                            : clusterInfo.status.state.toLowerCase()}
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div className="cluster-details-header">
+                    <div className="cluster-details-title">Jobs</div>
+                  </div>
+                </div>
+              )}
+              <JobComponent
+                clusterSelected={clusterSelected}
+                setDetailedView={setDetailedView}
+                detailedJobView={detailedJobView}
+                setDetailedJobView={setDetailedJobView}
+                setSubmitJobView={setSubmitJobView}
+                setSelectedJobClone={setSelectedJobClone}
+                clusterResponse={clusterResponse}
+              />
+            </div>
+          ) : (
+            <div className="loader-full-style">
+              {isLoading && (
+                <div>
+                  <ClipLoader
+                    color="#8A8A8A"
+                    loading={true}
+                    size={20}
+                    aria-label="Loading Spinner"
+                    data-testid="loader"
+                  />
+                  Loading Cluster Details
+                </div>
+              )}
+            </div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default ClusterDetails;
diff --git a/src/cluster/listCluster.tsx b/src/cluster/listCluster.tsx
new file mode 100644
index 00000000..0dd3ce89
--- /dev/null
+++ b/src/cluster/listCluster.tsx
@@ -0,0 +1,298 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { useTable, useGlobalFilter, usePagination } from 'react-table';
+import { LabIcon } from '@jupyterlab/ui-components';
+import createClusterIcon from '../../style/icons/create_cluster_icon.svg';
+import filterIcon from '../../style/icons/filter_icon.svg';
+import stopIcon from '../../style/icons/stop_icon.svg';
+import clusterRunningIcon from '../../style/icons/cluster_running_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import { ClipLoader } from 'react-spinners';
+import {
+  CREATE_CLUSTER_URL,
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_ERROR,
+  STATUS_PROVISIONING,
+  STATUS_RUNNING,
+  STATUS_STARTING,
+  STATUS_STOPPED,
+  STATUS_STOPPING
+} from '../utils/const';
+import GlobalFilter from '../utils/globalFilter';
+import TableData from '../utils/tableData';
+import { PaginationView } from '../utils/paginationView';
+import { ICellProps } from '../utils/utils';
+
+const iconCreateCluster = new LabIcon({
+  name: 'launcher:create-cluster-icon',
+  svgstr: createClusterIcon
+});
+const iconFilter = new LabIcon({
+  name: 'launcher:filter-icon',
+  svgstr: filterIcon
+});
+
+const iconStop = new LabIcon({
+  name: 'launcher:stop-icon',
+  svgstr: stopIcon
+});
+
+const iconClusterRunning = new LabIcon({
+  name: 'launcher:cluster-running-icon',
+  svgstr: clusterRunningIcon
+});
+const iconClusterError = new LabIcon({
+  name: 'launcher:cluster-error-icon',
+  svgstr: clusterErrorIcon
+});
+interface ICluster {
+  clusterName: string;
+  status: string;
+  clusterImage: string;
+  region: string;
+  zone: string;
+  totalWorkersNode: string;
+  schedulesDeletion: string;
+  actions: React.ReactNode;
+}
+
+interface IListClusterProps {
+  clustersList: ICluster[];
+  isLoading: boolean;
+  setPollingDisable: (value: boolean) => void;
+  listClustersAPI: () => void;
+  handleClusterDetails: (clusterName: string) => void;
+  project_id: string;
+}
+function ListCluster({
+  clustersList,
+  isLoading,
+  setPollingDisable,
+  listClustersAPI,
+  handleClusterDetails,
+  project_id
+}: IListClusterProps) {
+  const data = clustersList;
+  const columns = React.useMemo(
+    () => [
+      {
+        Header: 'Name',
+        accessor: 'clusterName'
+      },
+      {
+        Header: 'Status',
+        accessor: 'status'
+      },
+      {
+        Header: 'Cluster image name',
+        accessor: 'clusterImage'
+      },
+      {
+        Header: 'Region',
+        accessor: 'region'
+      },
+      {
+        Header: 'Zone',
+        accessor: 'zone'
+      },
+      {
+        Header: 'Total worker nodes',
+        accessor: 'totalWorkersNode'
+      },
+      {
+        Header: 'Scheduled deletion',
+        accessor: 'schedulesDeletion'
+      },
+      {
+        Header: 'Actions',
+        accessor: 'actions'
+      }
+    ],
+    []
+  );
+
+  const tableDataCondition = (cell: ICellProps) => {
+    if (cell.column.Header === 'Name') {
+      return (
+        <td
+          {...cell.getCellProps()}
+          role="button"
+          className="cluster-name"
+          onClick={() =>
+            cell.row.original.status !== STATUS_DELETING &&
+            handleClusterDetails(cell.value)
+          }
+        >
+          {cell.value}
+        </td>
+      );
+    } else if (cell.column.Header === 'Status') {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          <div
+            key="Status"
+            className="cluster-status-parent"
+            role="status"
+            aria-labels={cell.value}
+          >
+            {cell.value === STATUS_RUNNING && (
+              <iconClusterRunning.react tag="div" />
+            )}
+            {cell.value === STATUS_STOPPED && <iconStop.react tag="div" />}
+            {cell.value === STATUS_ERROR && (
+              <iconClusterError.react tag="div" />
+            )}
+            {(cell.value === STATUS_PROVISIONING ||
+              cell.value === STATUS_CREATING ||
+              cell.value === STATUS_STARTING ||
+              cell.value === STATUS_STOPPING ||
+              cell.value === STATUS_DELETING) && (
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={15}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+            )}
+            <div className="cluster-status">
+              {cell.value && cell.value.toLowerCase()}
+            </div>
+          </div>
+        </td>
+      );
+    } else {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          {cell.render('Cell')}
+        </td>
+      );
+    }
+  };
+
+  const {
+    getTableProps,
+    getTableBodyProps,
+    headerGroups,
+    rows,
+    prepareRow,
+    state,
+    preGlobalFilteredRows,
+    setGlobalFilter,
+    page,
+    canPreviousPage,
+    canNextPage,
+    nextPage,
+    previousPage,
+    setPageSize,
+    state: { pageIndex, pageSize }
+  } = useTable(
+    //@ts-ignore react-table 'columns' which is declared here on type 'TableOptions<ICluster>'
+    { columns, data, autoResetPage: false, initialState: { pageSize: 50 } },
+    useGlobalFilter,
+    usePagination
+  );
+
+  return (
+    <div>
+      <div className="create-cluster-overlay">
+        <div
+          role="button"
+          className="create-cluster-sub-overlay"
+          onClick={() => {
+            window.open(
+              `${CREATE_CLUSTER_URL}?project=${project_id}`,
+              '_blank'
+            );
+          }}
+        >
+          <div className="create-cluster-icon">
+            <iconCreateCluster.react tag="div" />
+          </div>
+          <div className="create-cluster-text">Create cluster</div>
+        </div>
+      </div>
+
+      {clustersList.length > 0 ? (
+        <div>
+          <div className="filter-cluster-overlay">
+            <div className="filter-cluster-icon">
+              <iconFilter.react tag="div" />
+            </div>
+            <div className="filter-cluster-text"></div>
+            <div className="filter-cluster-section">
+              <GlobalFilter
+                preGlobalFilteredRows={preGlobalFilteredRows}
+                globalFilter={state.globalFilter}
+                setGlobalFilter={setGlobalFilter}
+                setPollingDisable={setPollingDisable}
+              />
+            </div>
+          </div>
+          <div className="clusters-list-table-parent">
+            <TableData
+              getTableProps={getTableProps}
+              headerGroups={headerGroups}
+              getTableBodyProps={getTableBodyProps}
+              isLoading={isLoading}
+              rows={rows}
+              page={page}
+              prepareRow={prepareRow}
+              tableDataCondition={tableDataCondition}
+              fromPage="Clusters"
+            />
+            {clustersList.length > 50 && (
+              <PaginationView
+                pageSize={pageSize}
+                setPageSize={setPageSize}
+                pageIndex={pageIndex}
+                allData={clustersList}
+                previousPage={previousPage}
+                nextPage={nextPage}
+                canPreviousPage={canPreviousPage}
+                canNextPage={canNextPage}
+              />
+            )}
+          </div>
+        </div>
+      ) : (
+        <div>
+          {isLoading && (
+            <div className="spin-loaderMain">
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={18}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+              Loading Clusters
+            </div>
+          )}
+          {!isLoading && (
+            <div className="no-data-style">No rows to display</div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default ListCluster;
diff --git a/src/dpms/databaseInfo.tsx b/src/dpms/databaseInfo.tsx
new file mode 100644
index 00000000..ee746862
--- /dev/null
+++ b/src/dpms/databaseInfo.tsx
@@ -0,0 +1,89 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ReactWidget } from '@jupyterlab/apputils';
+import React from 'react';
+
+interface IDatabaseProps {
+  title: string;
+  dataprocMetastoreServices: string;
+  databaseDetails: Record<string, string>;
+}
+
+const DatabaseInfo = ({
+  title,
+  dataprocMetastoreServices,
+  databaseDetails
+}: IDatabaseProps): React.ReactElement => {
+  const database = {
+    dname: title,
+    description: databaseDetails[title],
+    instanceName: dataprocMetastoreServices
+  };
+  const renderTable = () => {
+    return (
+      <div className="table-container">
+        <table className="db-table">
+          <tbody>
+            {Object.entries(database).map(([key, value], index) => (
+              <tr
+                key={key}
+                className={index % 2 === 0 ? 'tr-row-even' : 'tr-row-odd'}
+              >
+                <td className="bold-column">{key}</td>
+                <td>{value}</td>
+              </tr>
+            ))}
+          </tbody>
+        </table>
+      </div>
+    );
+  };
+
+  return (
+    <div>
+      <div className="title-overlay">{title}</div>
+      <div className="db-title">Database info</div>
+      {renderTable()}
+    </div>
+  );
+};
+
+export class Database extends ReactWidget {
+  dataprocMetastoreServices: string;
+  databaseDetails: Record<string, string>;
+  constructor(
+    title: string,
+    dataprocMetastoreServices: string,
+    databaseDetails: Record<string, string>
+  ) {
+    super();
+    this.title.label = title;
+    this.dataprocMetastoreServices = dataprocMetastoreServices;
+    this.databaseDetails = databaseDetails;
+  }
+
+  render(): React.ReactElement {
+    return (
+      <DatabaseInfo
+        title={this.title.label}
+        dataprocMetastoreServices={this.dataprocMetastoreServices}
+        databaseDetails={this.databaseDetails}
+      />
+    );
+  }
+}
diff --git a/src/dpms/dpmsWidget.tsx b/src/dpms/dpmsWidget.tsx
new file mode 100644
index 00000000..b9987dd3
--- /dev/null
+++ b/src/dpms/dpmsWidget.tsx
@@ -0,0 +1,586 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ReactWidget } from '@jupyterlab/apputils';
+import { JupyterLab } from '@jupyterlab/application';
+import React, { useEffect, useState } from 'react';
+import { Tree, NodeRendererProps } from 'react-arborist';
+import { LabIcon } from '@jupyterlab/ui-components';
+import databaseIcon from '../../style/icons/database_icon.svg';
+import tableIcon from '../../style/icons/table_icon.svg';
+import columnsIcon from '../../style/icons/columns_icon.svg';
+import refreshIcon from '../../style/icons/refresh_icon.svg';
+import databaseWidgetIcon from '../../style/icons/database_widget_icon.svg';
+import datasetsIcon from '../../style/icons/datasets_icon.svg';
+import searchIcon from '../../style/icons/search_icon.svg';
+import rightArrowIcon from '../../style/icons/right_arrow_icon.svg';
+import downArrowIcon from '../../style/icons/down_arrow_icon.svg';
+import { Database } from './databaseInfo';
+import { MainAreaWidget } from '@jupyterlab/apputils';
+import { v4 as uuidv4 } from 'uuid';
+import 'semantic-ui-css/semantic.min.css';
+import {
+  BASE_URL,
+  API_HEADER_CONTENT_TYPE,
+  API_HEADER_BEARER,
+  CATALOG_SEARCH,
+  COLUMN_API,
+  QUERY_DATABASE,
+  QUERY_TABLE
+} from '../utils/const';
+import { authApi } from '../utils/utils';
+import { Table } from './tableInfo';
+import { ClipLoader } from 'react-spinners';
+import { ToastContainer, toast } from 'react-toastify';
+
+const iconDatabase = new LabIcon({
+  name: 'launcher:database-icon',
+  svgstr: databaseIcon
+});
+const iconTable = new LabIcon({
+  name: 'launcher:table-icon',
+  svgstr: tableIcon
+});
+const iconColumns = new LabIcon({
+  name: 'launcher:columns-icon',
+  svgstr: columnsIcon
+});
+const iconDatasets = new LabIcon({
+  name: 'launcher:datasets-icon',
+  svgstr: datasetsIcon
+});
+const iconDatabaseWidget = new LabIcon({
+  name: 'launcher:databse-widget-icon',
+  svgstr: databaseWidgetIcon
+});
+const iconRefresh = new LabIcon({
+  name: 'launcher:refresh-icon',
+  svgstr: refreshIcon
+});
+const iconSearch = new LabIcon({
+  name: 'launcher:search-icon',
+  svgstr: searchIcon
+});
+const iconRightArrow = new LabIcon({
+  name: 'launcher:right-arrow-icon',
+  svgstr: rightArrowIcon
+});
+const iconDownArrow = new LabIcon({
+  name: 'launcher:down-arrow-icon',
+  svgstr: downArrowIcon
+});
+const calculateDepth = (node: any): number => {
+  let depth = 0;
+  let currentNode = node;
+  while (currentNode.parent) {
+    depth++;
+    currentNode = currentNode.parent;
+  }
+  return depth;
+};
+
+const DpmsComponent = ({ app }: { app: JupyterLab }): JSX.Element => {
+  const [searchTerm, setSearchTerm] = useState('');
+  const [clusterValue, setClusterValue] = useState<string>('');
+  const [dataprocMetastoreServices, setDataprocMetastoreServices] =
+    useState('');
+  const [isLoading, setIsLoading] = useState(true);
+  const [noCluster, setNoCluster] = useState(false);
+  const [entries, setEntries] = useState<string[]>([]);
+  const [columnResponse, setColumnResponse] = useState<string[]>([]);
+  const [databaseDetails, setDatabaseDetails] = useState({});
+  const [tableDescription, setTableDescription] = useState({});
+  const getColumnDetails = async (name: string) => {
+    const credentials = await authApi();
+    if (credentials && clusterValue) {
+      fetch(`${COLUMN_API}${name}`, {
+        method: 'GET',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token,
+          'X-Goog-User-Project': credentials.project_id || ''
+        }
+      })
+        .then((response: Response) => {
+          response
+            .json()
+            .then(async (responseResult: unknown) => {
+              setColumnResponse((prevResponse: any) => [
+                ...prevResponse,
+                responseResult
+              ]);
+              if (data) {
+                setIsLoading(false);
+              }
+            })
+            .catch((e: Error) => {
+              console.log(e);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error getting column details', err);
+          toast.error('Error getting column details');
+        });
+    }
+  };
+  const getTableDetails = async (database: string) => {
+    const credentials = await authApi();
+    if (credentials && clusterValue) {
+      const requestBody = {
+        query: `${QUERY_TABLE}${credentials.project_id}.${credentials.region_id}.${dataprocMetastoreServices}.${database}`,
+        scope: {
+          includeProjectIds: [credentials.project_id]
+        }
+      };
+      fetch(`${CATALOG_SEARCH}`, {
+        method: 'POST',
+        body: JSON.stringify(requestBody),
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token,
+          'X-Goog-User-Project': credentials.project_id || ''
+        }
+      })
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              const filteredEntries = responseResult.results.filter(
+                (entry: { displayName: string }) => entry.displayName
+              );
+              const tableNames: string[] = [];
+              const entryNames: string[] = [];
+              const updatedTableDetails: { [key: string]: string } = {};
+              filteredEntries.forEach(
+                (entry: {
+                  displayName: string;
+                  relativeResourceName: string;
+                  description: string;
+                }) => {
+                  tableNames.push(entry.displayName);
+                  entryNames.push(entry.relativeResourceName);
+                  const description = entry.description || 'None';
+                  updatedTableDetails[entry.displayName] = description;
+                }
+              );
+              setEntries(entryNames);
+              setTableDescription(updatedTableDetails);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error getting table details', err);
+          toast.error('Error getting table details');
+        });
+    }
+  };
+  const database: { [dbName: string]: { [tableName: string]: string[] } } = {};
+  columnResponse.forEach((res: any) => {
+    /* fullyQualifiedName : dataproc_metastore:projectId.location.metastore_instance.database_name.table_name
+    fetching database name from fully qualified name structure */
+    const dbName = res.fullyQualifiedName.split('.').slice(-2, -1)[0];
+    const tableName = res.displayName;
+    const columns = res.schema.columns.map(
+      (column: {
+        column: string;
+        type: string;
+        mode: string;
+        description: string;
+      }) => ({
+        name: column.column,
+        type: column.type.toUpperCase(),
+        mode: column.mode,
+        description: column?.description || 'None'
+      })
+    );
+
+    if (!database[dbName]) {
+      database[dbName] = {};
+    }
+
+    if (!database[dbName][tableName]) {
+      database[dbName][tableName] = [];
+    }
+
+    database[dbName][tableName].push(...columns);
+  });
+  const data = Object.entries(database).map(([dbName, tables]) => ({
+    id: uuidv4(),
+    name: dbName,
+    children: Object.entries(tables).map(([tableName, columns]) => ({
+      id: uuidv4(),
+      name: tableName,
+      desciption: '',
+      children: columns.map((column: any) => ({
+        id: uuidv4(),
+        name: column.name,
+        type: column.type,
+        mode: column.mode,
+        description: column.description
+      }))
+    }))
+  }));
+
+  const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setSearchTerm(event.target.value);
+  };
+  const searchMatch = (node: { data: { name: string } }, term: string) => {
+    return node.data.name.toLowerCase().includes(term.toLowerCase());
+  };
+
+  const handleNodeClick = (node: any) => {
+    const depth = calculateDepth(node);
+    if (depth === 1) {
+      const content = new Database(
+        node.data.name,
+        dataprocMetastoreServices,
+        databaseDetails
+      );
+      const widget = new MainAreaWidget<Database>({ content });
+      const widgetId = `node-widget-${uuidv4()}`;
+      widget.id = widgetId;
+      widget.title.label = node.data.name;
+      widget.title.closable = true;
+      widget.title.icon = iconDatabaseWidget;
+      app.shell.add(widget, 'main');
+    } else if (depth === 2) {
+      const database = node.parent.data.name;
+      const column = node.data.children;
+      const content = new Table(
+        node.data.name,
+        dataprocMetastoreServices,
+        database,
+        column,
+        tableDescription
+      );
+      const widget = new MainAreaWidget<Table>({ content });
+      const widgetId = `node-widget-${uuidv4()}`;
+      widget.id = widgetId;
+      widget.title.label = node.data.name;
+      widget.title.closable = true;
+      widget.title.icon = iconDatasets;
+      app.shell.add(widget, 'main');
+    }
+  };
+  const clearState = () => {
+    setSearchTerm('');
+    setClusterValue('');
+    setDataprocMetastoreServices('');
+    setIsLoading(true);
+    setEntries([]);
+    setColumnResponse([]);
+    setDatabaseDetails({});
+  };
+  const handleRefreshClick = () => {
+    clearState();
+    setIsLoading(true);
+    getActiveNotebook();
+  };
+
+  type NodeProps = NodeRendererProps<any> & {
+    onClick: (node: any) => void;
+  };
+
+  function Node({ node, style, dragHandle, onClick }: NodeProps) {
+    const [expanded, setExpanded] = useState(false);
+
+    const handleToggle = () => {
+      setExpanded(!expanded);
+      node.toggle();
+    };
+    const handleIconClick = (event: React.MouseEvent) => {
+      if (event.currentTarget.classList.contains('caret-icon')) {
+        handleToggle();
+      }
+    };
+    const handleTextClick = (event: React.MouseEvent) => {
+      event.stopPropagation();
+      onClick(node);
+    };
+    const renderNodeIcon = () => {
+      const depth = calculateDepth(node);
+
+      const hasChildren = node.children && node.children.length > 0;
+
+      const arrowIcon = hasChildren ? (
+        expanded ? (
+          <>
+            <div
+              role="treeitem"
+              className="caret-icon right"
+              onClick={handleIconClick}
+            >
+              <iconDownArrow.react tag="div" />
+            </div>
+          </>
+        ) : (
+          <div
+            role="treeitem"
+            className="caret-icon down"
+            onClick={handleIconClick}
+          >
+            <iconRightArrow.react tag="div" />
+          </div>
+        )
+      ) : null;
+
+      if (depth === 1) {
+        return (
+          <>
+            {arrowIcon}
+            <div role="img" className="db-icon" onClick={handleIconClick}>
+              <iconDatabase.react tag="div" />
+            </div>
+          </>
+        );
+      } else if (depth === 2) {
+        return (
+          <>
+            {arrowIcon}
+            <div role="img" className="table-icon" onClick={handleIconClick}>
+              <iconTable.react tag="div" />
+            </div>
+          </>
+        );
+      }
+
+      return (
+        <>
+          {arrowIcon}
+          <iconColumns.react tag="div" />
+        </>
+      );
+    };
+
+    return (
+      <div style={style}>
+        {renderNodeIcon()}
+        <div role="treeitem" onClick={handleTextClick}>
+          {node.data.name}
+        </div>
+      </div>
+    );
+  }
+  const getDatabaseDetails = async () => {
+    const credentials = await authApi();
+    if (credentials && clusterValue) {
+      const requestBody = {
+        query: `${QUERY_DATABASE}${credentials.project_id}.${credentials.region_id}.${dataprocMetastoreServices}`,
+        scope: {
+          includeProjectIds: [credentials.project_id]
+        }
+      };
+      fetch(`${CATALOG_SEARCH}`, {
+        method: 'POST',
+        body: JSON.stringify(requestBody),
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token,
+          'X-Goog-User-Project': credentials.project_id || ''
+        }
+      })
+        .then((response: Response) => {
+          response
+            .json()
+            .then(async (responseResult: any) => {
+              const filteredEntries = responseResult.results.filter(
+                (entry: { displayName: string }) => entry.displayName
+              );
+              const databaseNames: string[] = [];
+              const updatedDatabaseDetails: { [key: string]: string } = {};
+              filteredEntries.forEach(
+                (entry: { description: string; displayName: string }) => {
+                  databaseNames.push(entry.displayName);
+                  const description = entry.description || 'None';
+                  updatedDatabaseDetails[entry.displayName] = description;
+                }
+              );
+              setDatabaseDetails(updatedDatabaseDetails);
+              databaseNames.map(async (db: string) => {
+                await getTableDetails(db);
+              });
+            })
+            .catch((e: Error) => {
+              console.log(e);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error getting database details', err);
+          toast.error('Error getting database details');
+        });
+    }
+  };
+  const getClusterDetails = async () => {
+    const credentials = await authApi();
+    if (credentials && clusterValue) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${clusterValue}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then(async (responseResult: any) => {
+              const metastoreServices =
+                responseResult.config?.metastoreConfig
+                  ?.dataprocMetastoreService;
+              if (metastoreServices) {
+                const lastIndex = metastoreServices.lastIndexOf('/');
+                const instanceName =
+                  lastIndex !== -1
+                    ? metastoreServices.substring(lastIndex + 1)
+                    : '';
+                setDataprocMetastoreServices(instanceName);
+                setNoCluster(false);
+              } else {
+                setNoCluster(true);
+              }
+            })
+            .catch((e: Error) => {
+              console.log(e);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error listing clusters details', err);
+          toast.error('Failed to fetch cluster details');
+        });
+    }
+  };
+  const getActiveNotebook = () => {
+    const clusterVal = localStorage.getItem('clusterValue');
+    if (clusterVal) {
+      setClusterValue(clusterVal);
+      getClusterDetails();
+    } else {
+      setNoCluster(true);
+    }
+  };
+  useEffect(() => {
+    getActiveNotebook();
+    return () => {
+      setClusterValue('');
+    };
+  }, [clusterValue]);
+  useEffect(() => {
+    getDatabaseDetails();
+  }, [dataprocMetastoreServices]);
+  useEffect(() => {
+    entries.forEach(async (entry: string) => {
+      await getColumnDetails(entry);
+    });
+  }, [entries]);
+
+  return (
+    <>
+      <div>
+        <div className="dpms-title">Dataproc Metastore</div>
+        <div
+          role="button"
+          aria-label="refresh"
+          className="refresh-icon"
+          data-tip="Refresh"
+          onClick={handleRefreshClick}
+        >
+          <iconRefresh.react tag="div" />
+        </div>
+      </div>
+      {!noCluster ? (
+        <>
+          <div>
+            {isLoading ? (
+              <div className="database-loader">
+                <div>
+                  <ClipLoader
+                    color="#8A8A8A"
+                    loading={true}
+                    size={20}
+                    aria-label="Loading Spinner"
+                    data-testid="loader"
+                  />
+                </div>
+                Loading databases
+              </div>
+            ) : (
+              <>
+                <div className="ui category search">
+                  <div className="ui icon input">
+                    <input
+                      className="search-field"
+                      type="text"
+                      value={searchTerm}
+                      onChange={handleSearch}
+                    />
+                    <div className="search-icon">
+                      <iconSearch.react tag="div" />
+                    </div>
+                  </div>
+                </div>
+                <div className="tree-container">
+                  <Tree
+                    data={data}
+                    openByDefault={false}
+                    width={600}
+                    height={1000}
+                    indent={24}
+                    rowHeight={36}
+                    overscanCount={1}
+                    paddingTop={30}
+                    paddingBottom={10}
+                    padding={25}
+                    searchTerm={searchTerm}
+                    searchMatch={searchMatch}
+                  >
+                    {(props: NodeRendererProps<any>) => (
+                      <Node {...props} onClick={handleNodeClick} />
+                    )}
+                  </Tree>
+                  <ToastContainer />
+                </div>
+              </>
+            )}
+          </div>
+        </>
+      ) : (
+        <div className="dpms-error">
+          Please select cluster notebook attached to metastore and refresh
+        </div>
+      )}
+    </>
+  );
+};
+
+export class dpmsWidget extends ReactWidget {
+  app: JupyterLab;
+
+  constructor(app: JupyterLab) {
+    super();
+    this.app = app;
+  }
+
+  render(): JSX.Element {
+    return <DpmsComponent app={this.app} />;
+  }
+}
diff --git a/src/dpms/tableInfo.tsx b/src/dpms/tableInfo.tsx
new file mode 100644
index 00000000..c5ff8c3a
--- /dev/null
+++ b/src/dpms/tableInfo.tsx
@@ -0,0 +1,180 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ReactWidget } from '@jupyterlab/apputils';
+import { useTable } from 'react-table';
+interface IColumn {
+  name: string;
+  type: string;
+  mode: string;
+  description: string;
+}
+
+interface IDatabaseProps {
+  title: string;
+  dataprocMetastoreServices: string;
+  database: string;
+  column: IColumn[];
+  tableDescription: Record<string, string>;
+}
+const TableInfo = ({
+  title,
+  dataprocMetastoreServices,
+  database,
+  column,
+  tableDescription
+}: IDatabaseProps): React.JSX.Element => {
+  const table = {
+    'Table name': title,
+    Description: tableDescription[title],
+    'By column name': '',
+    'By Database': database,
+    'By Dataproc Metastore Instance': dataprocMetastoreServices
+  };
+
+  const renderColumnTable = () => {
+    const columns = React.useMemo(
+      () => [
+        {
+          Header: 'Field name',
+          accessor: 'name'
+        },
+        {
+          Header: 'Type',
+          accessor: 'type'
+        },
+        {
+          Header: 'Mode',
+          accessor: 'mode'
+        },
+        {
+          Header: 'Description',
+          accessor: 'description'
+        }
+      ],
+      []
+    );
+
+    const data = React.useMemo(() => {
+      return column.map((column: IColumn) => ({
+        name: column.name,
+        type: column.type || '',
+        mode: column.mode || '',
+        description: column.description
+      }));
+    }, [column]);
+
+    const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
+      // @ts-ignore  react-table 'columns' which is declared here on type 'TableOptions<IColumns>'
+      useTable({ columns, data });
+
+    return (
+      <div className="table-container">
+        <table className="schema-table" {...getTableProps()}>
+          <thead>
+            {headerGroups.map(headerGroup => (
+              <tr {...headerGroup.getHeaderGroupProps()}>
+                {headerGroup.headers.map(column => (
+                  <th {...column.getHeaderProps()}>
+                    {column.render('Header')}
+                  </th>
+                ))}
+              </tr>
+            ))}
+          </thead>
+          <tbody {...getTableBodyProps()}>
+            {rows.map(row => {
+              prepareRow(row);
+              return (
+                <tr {...row.getRowProps()}>
+                  {row.cells.map((cell, index) => {
+                    return (
+                      <td
+                        className={index === 0 ? 'bold-column' : ''}
+                        {...cell.getCellProps()}
+                      >
+                        {cell.render('Cell')}
+                      </td>
+                    );
+                  })}
+                </tr>
+              );
+            })}
+          </tbody>
+        </table>
+      </div>
+    );
+  };
+
+  return (
+    <div className="table-info-overlay">
+      <div className="title-overlay">{title}</div>
+      <div className="db-title">Table info</div>
+      <div className="table-container">
+        <table className="db-table">
+          <tbody>
+            {Object.entries(table).map(([key, value], index) => (
+              <tr
+                key={key}
+                className={index % 2 === 0 ? 'tr-row-even' : 'tr-row-odd'}
+              >
+                <td className="bold-column">{key}</td>
+                <td>{value}</td>
+              </tr>
+            ))}
+          </tbody>
+        </table>
+      </div>
+      <div className="db-title">Schema</div>
+      {renderColumnTable()}
+    </div>
+  );
+};
+
+export class Table extends ReactWidget {
+  dataprocMetastoreServices: string;
+  database!: string;
+  column: IColumn[];
+  tableDescription: Record<string, string>;
+
+  constructor(
+    title: string,
+    dataprocMetastoreServices: string,
+    database: string,
+    column: IColumn[],
+    tableDescription: Record<string, string>
+  ) {
+    super();
+    this.dataprocMetastoreServices = dataprocMetastoreServices;
+    this.database = database;
+    this.column = column;
+    this.tableDescription = tableDescription;
+  }
+
+  render(): React.JSX.Element {
+    return (
+      <TableInfo
+        title={this.title.label}
+        dataprocMetastoreServices={this.dataprocMetastoreServices}
+        database={this.database}
+        column={this.column}
+        tableDescription={this.tableDescription}
+      />
+    );
+  }
+}
diff --git a/src/handler/handler.ts b/src/handler/handler.ts
new file mode 100644
index 00000000..091b1858
--- /dev/null
+++ b/src/handler/handler.ts
@@ -0,0 +1,58 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { URLExt } from '@jupyterlab/coreutils';
+import { ServerConnection } from '@jupyterlab/services';
+
+/**
+ * Call the API extension
+ *
+ * @param endPoint API REST end point for the extension
+ * @param init Initial values for the request
+ * @returns The response body interpreted as JSON
+ */
+export async function requestAPI<T>(
+  endPoint = '',
+  init: RequestInit = {}
+): Promise<T> {
+  // Make request to Jupyter API
+  const settings = ServerConnection.makeSettings();
+  const requestUrl = URLExt.join(settings.baseUrl, 'dataproc-plugin', endPoint);
+
+  let response: Response;
+  try {
+    response = await ServerConnection.makeRequest(requestUrl, init, settings);
+  } catch (error: any) {
+    throw new ServerConnection.NetworkError(error);
+  }
+
+  let data: any = await response.text();
+
+  if (data.length > 0) {
+    try {
+      data = JSON.parse(data);
+    } catch (error) {
+      console.log('Not a JSON response body.', response);
+    }
+  }
+
+  if (!response.ok) {
+    throw new ServerConnection.ResponseError(response, data.message);
+  }
+
+  return data;
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 00000000..74eac8c6
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,258 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 {
+  JupyterFrontEnd,
+  JupyterFrontEndPlugin,
+  JupyterLab,
+  ILabShell
+} from '@jupyterlab/application';
+import { MainAreaWidget } from '@jupyterlab/apputils';
+import { ILauncher } from '@jupyterlab/launcher';
+import { LabIcon } from '@jupyterlab/ui-components';
+import { IMainMenu } from '@jupyterlab/mainmenu';
+import { Cluster } from './cluster/cluster';
+import { Batches } from './batches/batches';
+import clusterIcon from '../style/icons/cluster_icon.svg';
+import serverlessIcon from '../style/icons/serverless_icon.svg';
+import { Menu, Panel, Title, Widget } from '@lumino/widgets';
+import { AuthLogin } from './login/authLogin';
+import { Kernel, KernelSpecAPI } from '@jupyterlab/services';
+import { iconDisplay } from './utils/utils';
+import { dpmsWidget } from './dpms/dpmsWidget';
+import dpmsIcon from '../style/icons/dpms_icon.svg';
+import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
+const iconDpms = new LabIcon({
+  name: 'launcher:dpms-icon',
+  svgstr: dpmsIcon
+});
+import { TITLE_LAUNCHER_CATEGORY } from './utils/const';
+
+const extension: JupyterFrontEndPlugin<void> = {
+  id: 'cluster',
+  autoStart: true,
+  optional: [ILauncher, IMainMenu, ILabShell, INotebookTracker],
+  activate: async (
+    app: JupyterFrontEnd,
+    launcher: ILauncher,
+    mainMenu: IMainMenu,
+    labShell: ILabShell,
+    notebookTracker: INotebookTracker
+  ) => {
+    const { commands } = app;
+    const iconCluster = new LabIcon({
+      name: 'launcher:clusters-icon',
+      svgstr: clusterIcon
+    });
+    const iconServerless = new LabIcon({
+      name: 'launcher:serverless-icon',
+      svgstr: serverlessIcon
+    });
+    window.addEventListener('beforeunload', () => {
+      localStorage.removeItem('clusterValue');
+    });
+    const onTitleChanged = async (title: Title<Widget>) => {
+      const widget = title.owner as NotebookPanel;
+      if (widget && widget instanceof NotebookPanel) {
+        const kernel = widget.sessionContext.session?.kernel;
+        if (kernel) {
+          const kernelName = kernel.name;
+          const kernelSpec = kernels[kernelName];
+          if (
+            kernelSpec?.resources.endpointParentResource.includes('/clusters/')
+          ) {
+            const parts =
+              kernelSpec?.resources.endpointParentResource.split('/');
+            const clusterValue = parts[parts.length - 1];
+            localStorage.setItem('clusterValue', clusterValue);
+          }
+        } else {
+          localStorage.removeItem('clusterValue');
+        }
+        document.title = title.label;
+      } else {
+        document.title = title.label;
+      }
+      console.log(Kernel);
+    };
+    labShell.currentChanged.connect((_, change) => {
+      const { oldValue, newValue } = change;
+      // Clean up after the old value if it exists,
+      // listen for changes to the title of the activity
+      if (oldValue) {
+        // Check if the old value is an instance of NotebookPanel
+        if (oldValue instanceof NotebookPanel) {
+          oldValue.title.changed.disconnect(onTitleChanged);
+        }
+      }
+      if (newValue) {
+        // Check if the new value is an instance of NotebookPanel
+        if (newValue instanceof NotebookPanel) {
+          newValue.title.changed.connect(onTitleChanged);
+        }
+      }
+    });
+
+    const kernelSpecs = await KernelSpecAPI.getSpecs();
+    const kernels = kernelSpecs.kernelspecs;
+
+    const createClusterComponentCommand = 'create-cluster-component';
+    commands.addCommand(createClusterComponentCommand, {
+      caption: 'Create a new Cluster Component',
+      label: 'Clusters',
+      // @ts-ignore jupyter lab icon command issue
+      icon: args => (args['isPalette'] ? null : iconCluster),
+      execute: () => {
+        const content = new Cluster();
+        const widget = new MainAreaWidget<Cluster>({ content });
+        widget.title.label = 'Clusters';
+        widget.title.icon = iconCluster;
+        app.shell.add(widget, 'main');
+      }
+    });
+
+    const createBatchesComponentCommand = 'create-batches-component';
+    commands.addCommand(createBatchesComponentCommand, {
+      caption: 'Create a new Serverless Component',
+      label: 'Serverless',
+      // @ts-ignore jupyter lab icon command issue
+      icon: args => (args['isPalette'] ? null : iconServerless),
+      execute: () => {
+        const content = new Batches();
+        const widget = new MainAreaWidget<Batches>({ content });
+        widget.title.label = 'Serverless';
+        widget.title.icon = iconServerless;
+        app.shell.add(widget, 'main');
+      }
+    });
+
+    const createAuthLoginComponentCommand = 'create-authlogin-component';
+    commands.addCommand(createAuthLoginComponentCommand, {
+      label: 'Setup',
+      caption: 'Setup',
+      execute: () => {
+        const content = new AuthLogin();
+        const widget = new MainAreaWidget<AuthLogin>({ content });
+        widget.title.label = 'Config Setup';
+        widget.title.icon = iconCluster;
+        app.shell.add(widget, 'main');
+      }
+    });
+
+    const snippetMenu = new Menu({ commands });
+    snippetMenu.title.label = 'Dataproc';
+    snippetMenu.addItem({ command: createAuthLoginComponentCommand });
+    mainMenu.addMenu(snippetMenu);
+
+    if (launcher) {
+      Object.values(kernels).forEach((kernelsData, index) => {
+        if (
+          kernelsData?.resources.endpointParentResource &&
+          kernelsData?.resources.endpointParentResource.includes('/sessions')
+        ) {
+          const commandNotebook = `notebook:create-${kernelsData?.display_name}`;
+          commands.addCommand(commandNotebook, {
+            caption: kernelsData?.display_name,
+            label: kernelsData?.display_name,
+            icon: iconDisplay(kernelsData),
+            execute: async () => {
+              const model = await app.commands.execute(
+                'docmanager:new-untitled',
+                {
+                  type: 'notebook',
+                  path: '',
+                  kernel: { name: kernelsData?.name }
+                }
+              );
+              await app.commands.execute('docmanager:open', {
+                kernel: { name: kernelsData?.name },
+                path: model.path,
+                factory: 'notebook'
+              });
+            }
+          });
+
+          launcher.add({
+            command: commandNotebook,
+            category: 'Dataproc Serverless Notebooks',
+            //@ts-ignore jupyter lab Launcher type issue
+            metadata: kernelsData?.metadata,
+            rank: index + 1,
+            //@ts-ignore jupyter lab Launcher type issue
+            args: kernelsData?.argv
+          });
+        }
+      });
+      Object.values(kernels).forEach((kernelsData, index) => {
+        if (
+          kernelsData?.resources.endpointParentResource &&
+          !kernelsData?.resources.endpointParentResource.includes('/sessions')
+        ) {
+          const commandNotebook = `notebook:create-${kernelsData?.display_name}`;
+          commands.addCommand(commandNotebook, {
+            caption: kernelsData?.display_name,
+            label: kernelsData?.display_name,
+            icon: iconDisplay(kernelsData),
+            execute: async () => {
+              const model = await app.commands.execute(
+                'docmanager:new-untitled',
+                {
+                  type: 'notebook',
+                  path: '',
+                  kernel: { name: kernelsData?.name }
+                }
+              );
+              await app.commands.execute('docmanager:open', {
+                kernel: { name: kernelsData?.name },
+                path: model.path,
+                factory: 'notebook'
+              });
+            }
+          });
+
+          launcher.add({
+            command: commandNotebook,
+            category: 'Dataproc Cluster Notebooks',
+            //@ts-ignore jupyter lab Launcher type issue
+            metadata: kernelsData?.metadata,
+            rank: index + 1,
+            //@ts-ignore jupyter lab Launcher type issue
+            args: kernelsData?.argv
+          });
+        }
+      });
+      const panel = new Panel();
+      panel.id = 'dpms-tab';
+      panel.title.icon = iconDpms; // svg import
+      panel.addWidget(new dpmsWidget(app as JupyterLab));
+      app.shell.add(panel, 'left');
+
+      launcher.add({
+        command: createClusterComponentCommand,
+        category: TITLE_LAUNCHER_CATEGORY,
+        rank: 1
+      });
+      launcher.add({
+        command: createBatchesComponentCommand,
+        category: TITLE_LAUNCHER_CATEGORY,
+        rank: 2
+      });
+    }
+  }
+};
+
+export default extension;
diff --git a/src/jobs/jobDetails.tsx b/src/jobs/jobDetails.tsx
new file mode 100644
index 00000000..8e3b9596
--- /dev/null
+++ b/src/jobs/jobDetails.tsx
@@ -0,0 +1,719 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useEffect, useRef, useState } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import LeftArrowIcon from '../../style/icons/left_arrow_icon.svg';
+import 'semantic-ui-css/semantic.min.css';
+import CloneJobIcon from '../../style/icons/clone_job_icon.svg';
+import StopClusterIcon from '../../style/icons/stop_cluster_icon.svg';
+import StopClusterDisableIcon from '../../style/icons/stop_cluster_disable_icon.svg';
+import DeleteClusterIcon from '../../style/icons/delete_cluster_icon.svg';
+import EditIcon from '../../style/icons/edit_icon.svg';
+import EditIconDisable from '../../style/icons/edit_icon_disable.svg';
+import DeletePopup from '../utils/deletePopup';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  LABEL_TEXT,
+  STATUS_RUNNING
+} from '../utils/const';
+import {
+  authApi,
+  elapsedTime,
+  jobTimeFormat,
+  jobTypeDisplay,
+  jobTypeValue,
+  jobTypeValueArguments,
+  statusMessage
+} from '../utils/utils';
+
+import ClusterDetails from '../cluster/clusterDetails';
+import { ClipLoader } from 'react-spinners';
+import LabelProperties from './labelProperties';
+import SubmitJob from './submitJob';
+import ViewLogs from '../utils/viewLogs';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { statusDisplay } from '../utils/statusDisplay';
+import { stopJobApi, deleteJobApi } from '../utils/jobServices';
+import errorIcon from '../../style/icons/error_icon.svg';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconLeftArrow = new LabIcon({
+  name: 'launcher:left-arrow-icon',
+  svgstr: LeftArrowIcon
+});
+const iconCloneJob = new LabIcon({
+  name: 'launcher:clone-job-icon',
+  svgstr: CloneJobIcon
+});
+const iconStopClusterDisable = new LabIcon({
+  name: 'launcher:stop-cluster-disable-icon',
+  svgstr: StopClusterDisableIcon
+});
+const iconStopCluster = new LabIcon({
+  name: 'launcher:stop-cluster-icon',
+  svgstr: StopClusterIcon
+});
+const iconDeleteCluster = new LabIcon({
+  name: 'launcher:delete-cluster-icon',
+  svgstr: DeleteClusterIcon
+});
+const iconEdit = new LabIcon({
+  name: 'launcher:edit-icon',
+  svgstr: EditIcon
+});
+const iconEditDisable = new LabIcon({
+  name: 'launcher:edit-disable-icon',
+  svgstr: EditIconDisable
+});
+const iconError = new LabIcon({
+  name: 'launcher:error-icon',
+  svgstr: errorIcon
+});
+interface IJobDetailsProps {
+  jobSelected: any;
+  setDetailedJobView: (value: boolean) => void;
+  stopJobApi: (jobId: string) => Promise<void>;
+  deleteJobApi: (jobId: string) => Promise<void>;
+  region: any;
+  setDetailedView: (value: boolean) => void;
+  clusterResponse: any;
+  clustersList: object;
+}
+function JobDetails({
+  jobSelected,
+  setDetailedJobView,
+  region,
+  setDetailedView,
+  clusterResponse,
+  clustersList
+}: IJobDetailsProps) {
+  const [jobInfo, setjobInfo] = useState({
+    status: { state: '', stateStartTime: '' },
+    statusHistory: [{ stateStartTime: '' }],
+    labels: '',
+    reference: { jobId: '' },
+    jobUuid: '',
+    pysparkJob: { args: [], mainPythonFileUri: '' },
+    sparkRJob: { args: [], mainRFileUri: '' },
+    sparkJob: { args: [], mainJarFileUri: '', mainClass: '', jarFileUris: '' },
+    sparkSqlJob: { queryFileUri: '', queryList: { queries: '' }, args: [] },
+    placement: { clusterName: '' }
+  });
+  const [jobInfoResponse, setjobInfoResponse] = useState({
+    status: { state: '', stateStartTime: '' },
+    statusHistory: [{ stateStartTime: '' }],
+    labels: {},
+    reference: { jobId: '' },
+    jobUuid: '',
+    pysparkJob: { args: [], mainPythonFileUri: '' },
+    sparkRJob: { args: [], mainRFileUri: '' },
+    sparkJob: { args: [], mainJarFileUri: '', mainClass: '' },
+    sparkSqlJob: { queryFileUri: '', queryList: { queries: '' }, args: [] },
+
+    placement: { clusterName: '' }
+  });
+  const key: any[] | (() => any[]) = [];
+  const value: any[] | (() => any[]) = [];
+  const [labelEditMode, setLabelEditMode] = useState(false);
+  const [labelDetail, setLabelDetail] = useState(key); //Final label value is stored
+  const [labelDetailUpdated, setLabelDetailUpdated] = useState(value); //temporary storage to validate the label data being typed
+  const [detailedClusterView, setDetailedClusterView] = useState(false);
+  const [isLoading, setIsLoading] = useState(true);
+  const [selectedJobClone, setSelectedJobClone] = useState({});
+  const [submitJobView, setSubmitJobView] = useState(false);
+  const [keyValidation, setKeyValidation] = useState(-1);
+  const [valueValidation, setValueValidation] = useState(-1);
+  const [duplicateKeyError, setDuplicateKeyError] = useState(-1);
+
+  const [deletePopupOpen, setDeletePopupOpen] = useState(false);
+  const [selectedJobId, setSelectedJobId] = useState('');
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+  const [errorView, setErrorView] = useState(false);
+
+  const pollingJobDetails = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const handleDetailedJobView = () => {
+    pollingJobDetails(getJobDetails, true);
+    setDetailedJobView(false);
+  };
+
+  const handleDetailedClusterView = () => {
+    pollingJobDetails(getJobDetails, true);
+    if (!clustersList) {
+      setDetailedJobView(false);
+    }
+    setDetailedClusterView(true);
+  };
+
+  useEffect(() => {
+    if (labelEditMode) {
+      pollingJobDetails(getJobDetails, true);
+    } else {
+      getJobDetails();
+      pollingJobDetails(getJobDetails, false);
+    }
+
+    return () => {
+      pollingJobDetails(getJobDetails, true);
+    };
+  }, [labelEditMode]);
+
+  const handleJobLabelEdit = () => {
+    setLabelEditMode(true);
+    setLabelDetailUpdated(labelDetail);
+  };
+  const handleDeleteJob = (jobId: string) => {
+    setSelectedJobId(jobId);
+    setDeletePopupOpen(true);
+  };
+  const handleStopJob = async (jobId: string) => {
+    setSelectedJobId(jobId);
+    await stopJobApi(selectedJobId);
+  };
+  const handleCancelDelete = () => {
+    setDeletePopupOpen(false);
+  };
+
+  const handleDelete = async () => {
+    await deleteJobApi(selectedJobId);
+    setDeletePopupOpen(false);
+    handleDetailedJobView();
+  };
+
+  const updateJobDetails = async (payloadJob: object) => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/jobs/${jobSelected}?updateMask=${LABEL_TEXT}`,
+        {
+          method: 'PATCH',
+          body: JSON.stringify(payloadJob),
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResultJob: Response) => {
+              toast.success(`Request to update job ${jobSelected} submitted`);
+              console.log(responseResultJob);
+            })
+            .catch((e: Error) => console.error(e));
+        })
+        .catch((err: Error) => {
+          console.error('Error in updating job', err);
+          toast.error(`Failed to update the job ${jobSelected}`);
+        });
+    }
+  };
+
+  const handleSaveEdit = () => {
+    const payload = jobInfoResponse;
+    const labelObject: { [key: string]: string } = {};
+    labelDetailUpdated.forEach((label: string) => {
+      /*
+         Extracting key, value from label
+         Example: "{client:dataproc_plugin}"
+      */
+      const labelParts = label.split(':');
+
+      const key = labelParts[0];
+      const value = labelParts[1];
+      labelObject[key] = value;
+    });
+    payload.labels = labelObject;
+    updateJobDetails(payload);
+    setLabelEditMode(false);
+    getJobDetails();
+  };
+
+  const handleCancelEdit = () => {
+    setLabelEditMode(false);
+  };
+
+  const getJobDetails = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/jobs/${jobSelected}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              setjobInfoResponse(responseResult);
+              const labelValue: string[] = [];
+              if (responseResult.labels) {
+                for (const [key, value] of Object.entries(
+                  responseResult.labels
+                )) {
+                  labelValue.push(`${key}:${value}`);
+                }
+              }
+              setjobInfo(responseResult);
+              setLabelDetail(labelValue);
+              setIsLoading(false);
+              setSelectedJobClone(responseResult);
+            })
+            .catch((e: Error) => {
+              console.error(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error in getting job details', err);
+          toast.error(`Failed to fetch job details ${jobSelected}`);
+        });
+    }
+  };
+
+  const startTime = jobTimeFormat(jobInfo.statusHistory[0].stateStartTime);
+  const job = jobTypeValue(jobInfo);
+  const jobType = jobTypeDisplay(job);
+  const jobArgument = jobTypeValueArguments(jobInfo);
+  const jobTypeConcat = jobArgument + 'Job';
+  //@ts-ignore string used as index
+  const argumentsList = jobInfo[jobTypeConcat].args;
+  const statusMsg = statusMessage(jobInfo);
+  const endTime = new Date(jobInfo.status.stateStartTime);
+  const jobStartTime = new Date(
+    jobInfo.statusHistory[jobInfo.statusHistory.length - 1].stateStartTime
+  );
+  const elapsedTimeString = elapsedTime(endTime, jobStartTime);
+
+  const statusStyleSelection = (jobInfo: any) => {
+    if (jobInfo.status.state === STATUS_RUNNING) {
+      return 'action-cluster-section'; //CSS class
+    } else {
+      return 'action-cluster-section disabled';
+    }
+  };
+
+  const handleCloneJob = () => {
+    setSubmitJobView(true);
+  };
+
+  const styleJobEdit = (labelEditMode: boolean) => {
+    if (labelEditMode) {
+      return 'job-edit-button-disabled';
+    } else {
+      return 'job-edit-button';
+    }
+  };
+
+  const styleIconColor = (labelEditMode: boolean) => {
+    if (labelEditMode) {
+      return 'color-icon-disabled';
+    } else {
+      return 'color-icon';
+    }
+  };
+
+  return (
+    <div>
+      {errorView && (
+        <div className="error-view-parent">
+          <div
+            role="button"
+            className="back-arrow-icon"
+            onClick={() => setErrorView(false)}
+          >
+            <iconLeftArrow.react tag="div" />
+          </div>
+          <div className="error-view-message-parent">
+            <iconError.react tag="div" />
+            <div className="error-view-message">
+              Unable to find the resource you requested
+            </div>
+          </div>
+        </div>
+      )}
+      {submitJobView && !detailedClusterView && (
+        <SubmitJob
+          setSubmitJobView={setSubmitJobView}
+          selectedJobClone={selectedJobClone}
+          clusterResponse={clusterResponse}
+        />
+      )}
+      {deletePopupOpen && (
+        <DeletePopup
+          onCancel={() => handleCancelDelete()}
+          onDelete={() => handleDelete()}
+          deletePopupOpen={deletePopupOpen}
+          DeleteMsg={
+            'This will delete ' + selectedJobId + ' and cannot be undone.'
+          }
+        />
+      )}
+      {detailedClusterView && (
+        <ClusterDetails
+          clusterSelected={jobInfo.placement.clusterName}
+          setDetailedView={setDetailedView}
+          setDetailedClusterView={setDetailedClusterView}
+          submitJobView={submitJobView}
+          selectedJobClone={selectedJobClone}
+          clusterResponse={clusterResponse}
+          setSubmitJobView={setSubmitJobView}
+          setDetailedJobView={setDetailedJobView}
+          setSelectedJobClone={setSelectedJobClone}
+        />
+      )}
+      {!submitJobView && !detailedClusterView && !errorView && (
+        <div className="scroll-comp">
+          {jobInfo.jobUuid !== '' && (
+            <div>
+              <div className="cluster-details-header">
+                <div
+                  className="back-arrow-icon"
+                  role="button"
+                  aria-label="Delete Job"
+                  onClick={() => handleDetailedJobView()}
+                >
+                  <iconLeftArrow.react tag="div" />
+                </div>
+                <div className="cluster-details-title">Job details</div>
+                <div
+                  role="button"
+                  className="action-cluster-section"
+                  onClick={() => handleCloneJob()}
+                >
+                  <div className="action-cluster-icon">
+                    <iconCloneJob.react tag="div" />
+                  </div>
+                  <div className="action-cluster-text">CLONE</div>
+                </div>
+
+                <div
+                  role="button"
+                  className={statusStyleSelection(jobInfo)}
+                  onClick={() =>
+                    jobInfo.status.state === STATUS_RUNNING &&
+                    handleStopJob(jobInfo.reference.jobId)
+                  }
+                >
+                  <div className="action-cluster-icon">
+                    {jobInfo.status.state === STATUS_RUNNING ? (
+                      <iconStopCluster.react tag="div" />
+                    ) : (
+                      <iconStopClusterDisable.react tag="div" />
+                    )}
+                  </div>
+                  <div className="action-cluster-text">STOP</div>
+                </div>
+                <div
+                  role="button"
+                  className="action-cluster-section"
+                  onClick={() => handleDeleteJob(jobInfo.reference.jobId)}
+                >
+                  <div className="action-cluster-icon">
+                    <iconDeleteCluster.react tag="div" />
+                  </div>
+                  <div className="action-cluster-text">DELETE</div>
+                </div>
+                <ViewLogs
+                  clusterName={jobInfo.placement.clusterName}
+                  setErrorView={setErrorView}
+                />
+              </div>
+
+              <div className="cluster-details-container">
+                <div className="row-details"></div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Job ID</div>
+                  <div className="cluster-details-value">
+                    {jobInfo.reference.jobId}
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Job UUID</div>
+                  <div className="cluster-details-value">{jobInfo.jobUuid}</div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Type</div>
+                  <div className="cluster-details-value">Dataproc Job</div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Status</div>
+                  {statusDisplay(statusMsg)}
+                </div>
+                <ToastContainer />
+              </div>
+              <div className="cluster-details-header">
+                <div className="cluster-details-title">Configuration</div>
+              </div>
+              <div className="job-edit-header">
+                <div
+                  role="button"
+                  className={styleJobEdit(labelEditMode)}
+                  onClick={() => (labelEditMode ? '' : handleJobLabelEdit())}
+                >
+                  {labelEditMode ? (
+                    <iconEditDisable.react
+                      tag="div"
+                      className={styleIconColor(labelEditMode)}
+                    />
+                  ) : (
+                    <iconEdit.react
+                      tag="div"
+                      className={styleIconColor(labelEditMode)}
+                    />
+                  )}
+                  <div
+                    className={
+                      labelEditMode ? 'job-edit-text-disabled' : 'job-edit-text'
+                    }
+                  >
+                    EDIT
+                  </div>
+                </div>
+              </div>
+              <div className="cluster-details-container">
+                <div className="row-details">
+                  <div className="cluster-details-label">Start time:</div>
+                  <div className="cluster-details-value">{startTime}</div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Elapsed time:</div>
+                  <div className="cluster-details-value">
+                    {elapsedTimeString}
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Status:</div>
+                  {statusDisplay(statusMsg)}
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Region</div>
+                  <div className="cluster-details-value">{region.region}</div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Cluster</div>
+
+                  <div
+                    role="button"
+                    className="cluster-details-value-job"
+                    onClick={() => handleDetailedClusterView()}
+                  >
+                    {jobInfo.placement.clusterName}
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Job type</div>
+                  <div className="cluster-details-value">{jobType}</div>
+                </div>
+                {job === 'SparkR' && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">Spark R files</div>
+                    <div className="cluster-details-value">
+                      {jobInfo.sparkRJob.mainRFileUri}
+                    </div>
+                  </div>
+                )}
+                {job === 'SparkSQL' && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">
+                      Query source type
+                    </div>
+                    {jobInfo.sparkSqlJob.queryFileUri ? (
+                      <div className="cluster-details-value">Query file</div>
+                    ) : (
+                      <div className="cluster-details-value">Query script</div>
+                    )}
+                  </div>
+                )}
+                {job === 'PySpark' && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">
+                      Main python file
+                    </div>
+                    <div className="cluster-details-value">
+                      {jobInfo.pysparkJob.mainPythonFileUri}
+                    </div>
+                  </div>
+                )}
+                {job === 'Spark' && (
+                  <>
+                    <div className="row-details">
+                      <div className="cluster-details-label">
+                        Main class or jar
+                      </div>
+                      <div className="cluster-details-value">
+                        {jobInfo.sparkJob.mainJarFileUri}
+                        {jobInfo.sparkJob.mainClass}
+                      </div>
+                    </div>
+                    <div className="row-details">
+                      <div className="cluster-details-label">Jar files</div>
+                      {jobInfo.sparkJob.jarFileUris ? (
+                        <div className="cluster-details-value">
+                          {jobInfo.sparkJob.jarFileUris}
+                        </div>
+                      ) : (
+                        <div className="cluster-details-value">None</div>
+                      )}
+                    </div>
+                  </>
+                )}
+                {job === 'SparkSQL' && jobInfo.sparkSqlJob.queryFileUri && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">Query file</div>
+                    <div className="cluster-details-value">
+                      {jobInfo.sparkSqlJob.queryFileUri}
+                    </div>
+                  </div>
+                )}
+                {job === 'SparkSQL' && jobInfo.sparkSqlJob.queryList && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">Query script</div>
+                    <div className="cluster-details-value">
+                      {jobInfo.sparkSqlJob.queryList.queries}
+                    </div>
+                  </div>
+                )}
+                {argumentsList && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">Arguments</div>
+                    <div className="cluster-details-value">
+                      {argumentsList.length > 0
+                        ? argumentsList.map((argument: string) => {
+                            return (
+                              <div
+                                key={argument}
+                                className="job-argument-style"
+                              >
+                                {argument}
+                              </div>
+                            );
+                          })
+                        : ''}
+                    </div>
+                  </div>
+                )}
+                <div className="job-details-label-row">
+                  <div className="cluster-details-label">Labels</div>
+                  {!labelEditMode ? (
+                    <div className="job-label-style-parent">
+                      {labelDetail.length > 0
+                        ? labelDetail.map(label => {
+                            /*
+                            Extracting key, value from label
+                               Example: "{client:dataproc_plugin}"
+                         */
+                            const labelParts = label.split(':');
+
+                            return (
+                              <div key={label} className="job-label-style">
+                                {labelParts[0]} : {labelParts[1]}
+                              </div>
+                            );
+                          })
+                        : 'None'}
+                    </div>
+                  ) : (
+                    <LabelProperties
+                      labelDetail={labelDetail}
+                      setLabelDetail={setLabelDetail}
+                      labelDetailUpdated={labelDetailUpdated}
+                      setLabelDetailUpdated={setLabelDetailUpdated}
+                      selectedJobClone={!!selectedJobClone}
+                      buttonText="ADD LABEL"
+                      keyValidation={keyValidation}
+                      setKeyValidation={setKeyValidation}
+                      valueValidation={valueValidation}
+                      setValueValidation={setValueValidation}
+                      labelEditMode={labelEditMode}
+                      duplicateKeyError={duplicateKeyError}
+                      setDuplicateKeyError={setDuplicateKeyError}
+                    />
+                  )}
+                </div>
+                {labelEditMode && (
+                  <div className="job-button-style-parent">
+                    <div className="job-save-button-style">
+                      <div
+                        role="button"
+                        onClick={() => {
+                          handleSaveEdit();
+                        }}
+                      >
+                        SAVE
+                      </div>
+                    </div>
+                    <div className="job-cancel-button-style">
+                      <div
+                        role="button"
+                        onClick={() => {
+                          handleCancelEdit();
+                        }}
+                      >
+                        CANCEL
+                      </div>
+                    </div>
+                  </div>
+                )}
+              </div>
+              <ToastContainer />
+            </div>
+          )}
+
+          {jobInfo.jobUuid === '' && (
+            <div className="loader-full-style">
+              {isLoading && (
+                <div>
+                  <ClipLoader
+                    color="#8A8A8A"
+                    loading={true}
+                    size={20}
+                    aria-label="Loading Spinner"
+                    data-testid="loader"
+                  />
+                  Loading Job Details
+                </div>
+              )}
+            </div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default JobDetails;
diff --git a/src/jobs/jobs.tsx b/src/jobs/jobs.tsx
new file mode 100644
index 00000000..81e1a686
--- /dev/null
+++ b/src/jobs/jobs.tsx
@@ -0,0 +1,581 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useState, useEffect, useRef } from 'react';
+import { useTable, useGlobalFilter, usePagination } from 'react-table';
+import {
+  authApi,
+  jobTimeFormat,
+  jobTypeValue,
+  elapsedTime,
+  statusMessage,
+  jobTypeDisplay,
+  ICellProps
+} from '../utils/utils';
+import { LabIcon } from '@jupyterlab/ui-components';
+import filterIcon from '../../style/icons/filter_icon.svg';
+import cloneIcon from '../../style/icons/clone_icon.svg';
+import stopIcon from '../../style/icons/stop_icon.svg';
+import JobDetails from './jobDetails';
+import stopDisableIcon from '../../style/icons/stop_disable_icon.svg';
+import deleteIcon from '../../style/icons/delete_icon.svg';
+import clusterRunningIcon from '../../style/icons/cluster_running_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import SucceededIcon from '../../style/icons/succeeded_icon.svg';
+import SubmitJobIcon from '../../style/icons/submit_job_icon.svg';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  ClusterStatus,
+  STATUS_CANCELLED,
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_FAIL,
+  STATUS_PROVISIONING,
+  STATUS_STARTING,
+  STATUS_STOPPING,
+  STATUS_SUCCESS
+} from '../utils/const';
+import ClipLoader from 'react-spinners/ClipLoader';
+import SubmitJob from './submitJob';
+import GlobalFilter from '../utils/globalFilter';
+import TableData from '../utils/tableData';
+import DeletePopup from '../utils/deletePopup';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { stopJobApi, deleteJobApi } from '../utils/jobServices';
+import { PaginationView } from '../utils/paginationView';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconFilter = new LabIcon({
+  name: 'launcher:filter-icon',
+  svgstr: filterIcon
+});
+const iconClone = new LabIcon({
+  name: 'launcher:clone-icon',
+  svgstr: cloneIcon
+});
+const iconStop = new LabIcon({
+  name: 'launcher:stop-icon',
+  svgstr: stopIcon
+});
+const iconStopDisable = new LabIcon({
+  name: 'launcher:stop-disable-icon',
+  svgstr: stopDisableIcon
+});
+const iconDelete = new LabIcon({
+  name: 'launcher:delete-icon',
+  svgstr: deleteIcon
+});
+const iconClusterRunning = new LabIcon({
+  name: 'launcher:cluster-running-icon',
+  svgstr: clusterRunningIcon
+});
+const iconClusterError = new LabIcon({
+  name: 'launcher:cluster-error-icon',
+  svgstr: clusterErrorIcon
+});
+const iconSucceeded = new LabIcon({
+  name: 'launcher:succeeded-icon',
+  svgstr: SucceededIcon
+});
+const iconSubmitJob = new LabIcon({
+  name: 'launcher:submit-job-icon',
+  svgstr: SubmitJobIcon
+});
+
+function JobComponent({
+  clusterSelected,
+  detailedJobView,
+  setDetailedJobView,
+  submitJobView,
+  setSubmitJobView,
+  setDetailedView,
+  clusterResponse,
+  selectedJobClone,
+  setSelectedJobClone,
+  clustersList
+}: any) {
+  const [jobsList, setjobsList] = useState([]);
+  const [jobSelected, setjobSelected] = useState({});
+  const [isLoading, setIsLoading] = useState(true);
+  const [pollingDisable, setPollingDisable] = useState(false);
+  const [region, setRegion] = useState('');
+  const [deletePopupOpen, setDeletePopupOpen] = useState(false);
+  const [selectedJobId, setSelectedJobId] = useState('');
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+
+  const pollingJobs = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const data = jobsList;
+
+  const columns = React.useMemo(
+    () => [
+      {
+        Header: 'Job ID',
+        accessor: 'jobid'
+      },
+      {
+        Header: 'Status',
+        accessor: 'status'
+      },
+      {
+        Header: 'Region',
+        accessor: 'region'
+      },
+      {
+        Header: 'Type',
+        accessor: 'type'
+      },
+      {
+        Header: 'Start time',
+        accessor: 'starttime'
+      },
+
+      {
+        Header: 'Elapsed time',
+        accessor: 'elapsedtime'
+      },
+      {
+        Header: 'Labels',
+        accessor: 'labels'
+      },
+      {
+        Header: 'Actions',
+        accessor: 'actions'
+      }
+    ],
+    []
+  );
+
+  const jobDetails = (selectedName: string) => {
+    pollingJobs(listJobsAPI, true);
+    const filteredJobDetails = jobsList.filter((jobInfo: any) => {
+      return jobInfo.jobid === selectedName;
+    });
+    const region = filteredJobDetails[0];
+    setRegion(region);
+    setjobSelected(selectedName);
+    setDetailedJobView(true);
+  };
+
+  const handleSubmitJobOpen = () => {
+    setSubmitJobView(true);
+    setSelectedJobClone('');
+  };
+  const handleDeleteJob = (jobId: string) => {
+    setSelectedJobId(jobId);
+    setDeletePopupOpen(true);
+  };
+  const handleStopJob = async (jobId: string) => {
+    setSelectedJobId(jobId);
+    await stopJobApi(jobId);
+  };
+  const handleCancelDelete = () => {
+    setDeletePopupOpen(false);
+  };
+
+  const handleDelete = async () => {
+    await deleteJobApi(selectedJobId);
+    setDeletePopupOpen(false);
+  };
+
+  const listJobsAPI = async (
+    nextPageToken?: string,
+    previousJobsList?: object
+  ) => {
+    const credentials = await authApi();
+    const clusterName = clusterSelected ?? '';
+    const pageToken = nextPageToken ?? '';
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/jobs?pageSize=50&pageToken=${pageToken}&&clusterName=${clusterName}`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              let transformJobListData = [];
+              if (responseResult && responseResult.jobs) {
+                transformJobListData = responseResult.jobs.map((data: any) => {
+                  const startTime = jobTimeFormat(
+                    data.statusHistory[0].stateStartTime
+                  );
+                  const job = jobTypeValue(data);
+                  const jobType = jobTypeDisplay(job);
+                  const endTime = data.status.stateStartTime;
+                  const jobStartTime = new Date(
+                    data.statusHistory[0].stateStartTime
+                  );
+
+                  const elapsedTimeString = elapsedTime(endTime, jobStartTime);
+
+                  const statusMsg = statusMessage(data);
+
+                  const labelvalue = [];
+                  if (data.labels) {
+                    for (const [key, value] of Object.entries(data.labels)) {
+                      labelvalue.push(`${key} : ${value}`);
+                    }
+                  } else {
+                    labelvalue.push('None');
+                  }
+                  return {
+                    jobid: data.reference.jobId,
+                    status: statusMsg,
+                    region: credentials.region_id,
+                    type: jobType,
+                    starttime: startTime,
+                    elapsedtime: elapsedTimeString,
+                    labels: labelvalue,
+                    actions: renderActions(data)
+                  };
+                });
+              }
+              const existingJobsData = previousJobsList ?? [];
+              //setStateAction never type issue
+              let allJobsData: any = [
+                ...(existingJobsData as []),
+                ...transformJobListData
+              ];
+
+              if (responseResult.nextPageToken) {
+                listJobsAPI(responseResult.nextPageToken, allJobsData);
+              } else {
+                setjobsList(allJobsData);
+                setIsLoading(false);
+              }
+            })
+            .catch((e: Error) => {
+              console.error(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error listing jobs', err);
+          toast.error('Failed to fetch jobs');
+        });
+    }
+  };
+
+  const handleCloneJob = (data: object) => {
+    setSubmitJobView(true);
+    setSelectedJobClone(data);
+  };
+  const renderActions = (data: {
+    reference: { jobId: string };
+    status: { state: ClusterStatus };
+    clusterName: string;
+  }) => {
+    const jobId = data.reference.jobId;
+    return (
+      <div className="actions-icon">
+        <div
+          role="button"
+          className="icon-buttons-style"
+          title="Clone Job"
+          onClick={() => handleCloneJob(data)}
+        >
+          <iconClone.react tag="div" />
+        </div>
+        <div
+          role="button"
+          aria-disabled={data.status.state !== ClusterStatus.STATUS_RUNNING}
+          className={
+            data.status.state === ClusterStatus.STATUS_RUNNING
+              ? 'icon-buttons-style'
+              : 'icon-buttons-style-disable'
+          }
+          title="Stop Job"
+          onClick={
+            data.status.state === ClusterStatus.STATUS_RUNNING
+              ? () => handleStopJob(jobId)
+              : undefined
+          }
+        >
+          {data.status.state === ClusterStatus.STATUS_RUNNING ? (
+            <iconStop.react tag="div" />
+          ) : (
+            <iconStopDisable.react tag="div" />
+          )}
+        </div>
+        <div
+          role="button"
+          aria-disabled={data.status.state !== ClusterStatus.STATUS_RUNNING}
+          className={
+            data.status.state === ClusterStatus.STATUS_RUNNING
+              ? 'icon-buttons-style-disable'
+              : 'icon-buttons-style'
+          }
+          title="Delete Job"
+          onClick={
+            data.status.state !== ClusterStatus.STATUS_RUNNING
+              ? () => handleDeleteJob(jobId)
+              : undefined
+          }
+        >
+          {data.status.state === ClusterStatus.STATUS_RUNNING ? (
+            <iconDelete.react tag="div" />
+          ) : (
+            <iconDelete.react tag="div" />
+          )}
+        </div>
+      </div>
+    );
+  };
+
+  useEffect(() => {
+    listJobsAPI();
+    if (!detailedJobView) {
+      pollingJobs(listJobsAPI, pollingDisable);
+    }
+
+    return () => {
+      pollingJobs(listJobsAPI, true);
+    };
+  }, [pollingDisable, detailedJobView]);
+
+  const tableDataCondition = (cell: ICellProps) => {
+    if (cell.column.Header === 'Job ID') {
+      return (
+        <td
+          role="button"
+          {...cell.getCellProps()}
+          className="cluster-name"
+          onClick={() => jobDetails(cell.value)}
+        >
+          {cell.value}
+        </td>
+      );
+    }
+    if (cell.column.Header === 'Status' && cell.value) {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          <div key="Status" className="cluster-status-parent">
+            {cell.value === ClusterStatus.STATUS_RUNNING && (
+              <iconClusterRunning.react tag="div" />
+            )}
+            {cell.value === STATUS_CANCELLED && <iconStop.react tag="div" />}
+            {cell.value === STATUS_FAIL && <iconClusterError.react tag="div" />}
+            {cell.value === STATUS_SUCCESS && <iconSucceeded.react tag="div" />}
+            {(cell.value === STATUS_PROVISIONING ||
+              cell.value === STATUS_CREATING ||
+              cell.value === STATUS_STARTING ||
+              cell.value === STATUS_STOPPING ||
+              cell.value === STATUS_DELETING) && (
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={15}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+            )}
+            <div className="cluster-status">{cell.value.toLowerCase()}</div>
+          </div>
+        </td>
+      );
+    }
+    if (cell.column.Header === 'Labels') {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          {cell.value.map((label: string) => {
+            return (
+              <div
+                key={label}
+                className={label !== 'None' ? 'job-label-style-list' : ''}
+              >
+                {label}
+              </div>
+            );
+          })}
+        </td>
+      );
+    } else {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          {cell.render('Cell')}
+        </td>
+      );
+    }
+  };
+
+  const {
+    getTableProps,
+    getTableBodyProps,
+    headerGroups,
+    rows,
+    prepareRow,
+    state,
+    preGlobalFilteredRows,
+    setGlobalFilter,
+    page,
+    canPreviousPage,
+    canNextPage,
+    nextPage,
+    previousPage,
+    setPageSize,
+    state: { pageIndex, pageSize }
+  } = useTable(
+    { columns, data, autoResetPage: false, initialState: { pageSize: 50 } },
+    useGlobalFilter,
+    usePagination
+  );
+
+  return (
+    <div>
+      <ToastContainer />
+      {submitJobView && !detailedJobView && (
+        <SubmitJob
+          setSubmitJobView={setSubmitJobView}
+          selectedJobClone={selectedJobClone}
+          clusterResponse={clusterResponse}
+        />
+      )}
+      {deletePopupOpen && (
+        <DeletePopup
+          onCancel={() => handleCancelDelete()}
+          onDelete={() => handleDelete()}
+          deletePopupOpen={deletePopupOpen}
+          DeleteMsg={
+            'This will delete ' + selectedJobId + ' and cannot be undone.'
+          }
+        />
+      )}
+      {!submitJobView && detailedJobView && (
+        <JobDetails
+          jobSelected={jobSelected}
+          setDetailedJobView={setDetailedJobView}
+          stopJobApi={stopJobApi}
+          deleteJobApi={deleteJobApi}
+          region={region}
+          setDetailedView={setDetailedView}
+          clusterResponse={clusterResponse}
+          clustersList={clustersList}
+        />
+      )}
+      {!submitJobView && !detailedJobView && (
+        <div>
+          {clusterResponse &&
+            clusterResponse.clusters &&
+            clusterResponse.clusters.length > 0 && (
+              <div className="create-cluster-overlay">
+                <div
+                  role="button"
+                  className="create-cluster-sub-overlay"
+                  onClick={() => {
+                    handleSubmitJobOpen();
+                  }}
+                >
+                  <div className="create-cluster-icon">
+                    <iconSubmitJob.react tag="div" />
+                  </div>
+                  <div className="create-cluster-text">SUBMIT JOB</div>
+                </div>
+              </div>
+            )}
+          {jobsList.length > 0 ? (
+            <div>
+              <div className="filter-cluster-overlay">
+                <div className="filter-cluster-icon">
+                  <iconFilter.react tag="div" />
+                </div>
+                <div className="filter-cluster-text"></div>
+                <div className="filter-cluster-section">
+                  <GlobalFilter
+                    preGlobalFilteredRows={preGlobalFilteredRows}
+                    globalFilter={state.globalFilter}
+                    setGlobalFilter={setGlobalFilter}
+                    setPollingDisable={setPollingDisable}
+                  />
+                </div>
+              </div>
+              <div
+                className={
+                  clusterResponse
+                    ? 'jobs-list-table-parent'
+                    : 'jobs-list-table-parent-small'
+                }
+              >
+                <TableData
+                  getTableProps={getTableProps}
+                  headerGroups={headerGroups}
+                  getTableBodyProps={getTableBodyProps}
+                  isLoading={isLoading}
+                  page={page}
+                  rows={rows}
+                  prepareRow={prepareRow}
+                  tableDataCondition={tableDataCondition}
+                  fromPage="Jobs"
+                />
+                {jobsList.length > 50 && (
+                  <PaginationView
+                    pageSize={pageSize}
+                    setPageSize={setPageSize}
+                    pageIndex={pageIndex}
+                    allData={jobsList}
+                    previousPage={previousPage}
+                    nextPage={nextPage}
+                    canPreviousPage={canPreviousPage}
+                    canNextPage={canNextPage}
+                  />
+                )}
+              </div>
+            </div>
+          ) : (
+            <div>
+              {isLoading && (
+                <div className="spin-loaderMain">
+                  <ClipLoader
+                    color="#8A8A8A"
+                    loading={true}
+                    size={20}
+                    aria-label="Loading Spinner"
+                    data-testid="loader"
+                  />
+                  Loading Jobs
+                </div>
+              )}
+              {!isLoading && (
+                <div className="no-data-style">No rows to display</div>
+              )}
+            </div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default JobComponent;
diff --git a/src/jobs/labelProperties.tsx b/src/jobs/labelProperties.tsx
new file mode 100644
index 00000000..22316f50
--- /dev/null
+++ b/src/jobs/labelProperties.tsx
@@ -0,0 +1,320 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useEffect } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import plusIcon from '../../style/icons/plus_icon.svg';
+import plusIconDisable from '../../style/icons/plus_icon_disable.svg';
+import deleteIcon from '../../style/icons/delete_icon.svg';
+import errorIcon from '../../style/icons/error_icon.svg';
+import { Input } from 'semantic-ui-react';
+import { DEFAULT_LABEL_DETAIL } from '../utils/const';
+
+const iconPlus = new LabIcon({
+  name: 'launcher:plus-icon',
+  svgstr: plusIcon
+});
+const iconPlusDisable = new LabIcon({
+  name: 'launcher:plus-disable-icon',
+  svgstr: plusIconDisable
+});
+const iconDelete = new LabIcon({
+  name: 'launcher:delete-icon',
+  svgstr: deleteIcon
+});
+const iconError = new LabIcon({
+  name: 'launcher:error-icon',
+  svgstr: errorIcon
+});
+
+function LabelProperties({
+  labelDetail,
+  setLabelDetail,
+  labelDetailUpdated,
+  setLabelDetailUpdated,
+  selectedJobClone,
+  buttonText,
+  keyValidation,
+  setKeyValidation,
+  valueValidation,
+  setValueValidation,
+  duplicateKeyError,
+  setDuplicateKeyError,
+  labelEditMode
+}: any) {
+  /* 
+  labelDetail used to store the permanent label details when onblur 
+  labelDetailUpdated used to store the temporay label details when onchange 
+  */
+  useEffect(() => {
+    if (!labelEditMode) {
+      if (buttonText === 'ADD LABEL' && !selectedJobClone) {
+        setLabelDetail([DEFAULT_LABEL_DETAIL]);
+        setLabelDetailUpdated([DEFAULT_LABEL_DETAIL]);
+      } else {
+        setLabelDetail([]);
+      }
+    }
+  }, []);
+
+  const handleAddLabel = () => {
+    const labelAdd = [...labelDetail];
+    labelAdd.push(':');
+    setLabelDetailUpdated(labelAdd);
+    setLabelDetail(labelAdd);
+  };
+
+  const handleDeleteLabel = (index: number, value: string) => {
+    const labelDelete = [...labelDetail];
+    labelDelete.splice(index, 1);
+    setLabelDetailUpdated(labelDelete);
+    setLabelDetail(labelDelete);
+    setDuplicateKeyError(-1);
+    setKeyValidation(-1);
+    setValueValidation(-1);
+  };
+
+  const handleEditLabelSwitch = () => {
+    if (duplicateKeyError === -1) {
+      setLabelDetail(labelDetailUpdated);
+    }
+  };
+  const handleEditLabel = (
+    value: string,
+    index: number,
+    keyValue: string
+  ) => {
+    const labelEdit = [...labelDetail];
+
+    labelEdit.forEach((data, dataNumber: any) => {
+      if (index === dataNumber) {
+        /*
+          allowed aplhanumeric and spaces and underscores
+        */
+        const regexp = /^[a-z0-9-_]+$/;
+        if (keyValue === 'key') {
+          if (
+            value.search(regexp) === -1 ||
+            value.charAt(0) !== value.charAt(0).toLowerCase()
+          ) {
+            setKeyValidation(index);
+          } else {
+            setKeyValidation(-1);
+          }
+
+          // Check for duplicate key when editing
+          const newKey = value;
+          const duplicateIndex = labelEdit.findIndex(
+            (label, i) => i !== index && label.split(':')[0] === newKey
+          );
+          if (duplicateIndex !== -1) {
+            setDuplicateKeyError(index);
+          } else {
+            setDuplicateKeyError(-1);
+          }
+
+          data = data.replace(data.split(':')[0], value);
+        } else {
+          if (value.search(regexp) === -1) {
+            setValueValidation(index);
+          } else {
+            setValueValidation(-1);
+          }
+          /*
+          value is split from labels 
+          Example:"client:dataproc_plugin"
+          */
+          if (data.split(':')[1] === '') {
+            data = data + value;
+          } else {
+            data = data.replace(data.split(':')[1], value);
+          }
+        }
+      }
+      labelEdit[dataNumber] = data;
+    });
+
+    setLabelDetailUpdated(labelEdit);
+  };
+
+  const styleAddLabelButton = (buttonText: string, labelDetail: any) => {
+    if (
+      buttonText === 'ADD LABEL' &&
+      (labelDetail.length === 0 ||
+        labelDetail[labelDetail.length - 1].split(':')[0].length > 0) &&
+      duplicateKeyError === -1
+    ) {
+      return 'job-add-label-button';
+    } else if (
+      buttonText === 'ADD LABEL' &&
+      (labelDetail.length === 0 ||
+        labelDetail[labelDetail.length - 1].split(':')[0].length === 0) &&
+      duplicateKeyError !== -1
+    ) {
+      return 'job-add-label-button-disabled';
+    } else if (
+      buttonText !== 'ADD LABEL' &&
+      (labelDetail.length === 0 ||
+        labelDetail[labelDetail.length - 1].split(':')[0].length > 0)
+    ) {
+      return 'job-add-property-button';
+    } else {
+      return 'job-add-property-button-disabled';
+    }
+  };
+
+  return (
+    <div>
+      <div className="job-label-edit-parent">
+        {labelDetail.length > 0 &&
+          labelDetail.map((label: any, index: number) => {
+            /*
+                     Extracting key, value from label
+                      Example: "{client:dataProc_plugin}"
+                  */
+            const labelSplit = label.split(':');
+
+            return (
+              <div key={label}>
+                <div className="job-label-edit-row">
+                  <div className="key-message-wrapper">
+                    <Input
+                      placeholder={`Key ${index + 1}*`}
+                      className="edit-input-style"
+                      disabled={
+                        labelSplit[0] === '' ||
+                        buttonText !== 'ADD LABEL' ||
+                        duplicateKeyError !== -1
+                          ? false
+                          : true
+                      }
+                      onBlur={() => handleEditLabelSwitch()}
+                      onChange={e =>
+                        handleEditLabel(
+                          e.target.value,
+                          index,
+                          'key'
+                        )
+                      }
+                      defaultValue={labelSplit[0]}
+                    />
+
+                    {labelDetailUpdated[index].split(':')[0] === '' ? (
+                      <div role="alert" className="error-key-parent">
+                        <iconError.react tag="div" />
+                        <div className="error-key-missing">key is required</div>
+                      </div>
+                    ) : (
+                      keyValidation === index &&
+                      buttonText === 'ADD LABEL' && (
+                        <div className="error-key-parent">
+                          <iconError.react tag="div" />
+                          <div className="error-key-missing">
+                            Only hyphens (-), underscores (_), lowercase
+                            characters, and numbers are allowed. Keys must start
+                            with a lowercase character. International characters
+                            are allowed.
+                          </div>
+                        </div>
+                      )
+                    )}
+                    {duplicateKeyError === index &&
+                      buttonText === 'ADD LABEL' && (
+                        <div className="error-key-parent">
+                          <iconError.react tag="div" />
+                          <div className="error-key-missing">
+                            The key is already present
+                          </div>
+                        </div>
+                      )}
+                  </div>
+                  <div className="key-message-wrapper">
+                    <Input
+                      placeholder={`Value ${index + 1}`}
+                      className="edit-input-style"
+                      onBlur={() => handleEditLabelSwitch()}
+                      onChange={e =>
+                        handleEditLabel(
+                          e.target.value,
+                          index,
+                          'value'
+                        )
+                      }
+                      disabled={
+                        label=== DEFAULT_LABEL_DETAIL && buttonText === 'ADD LABEL'
+                      }
+                      defaultValue={labelSplit[1]}
+                    />
+                    {valueValidation === index &&
+                      buttonText === 'ADD LABEL' && (
+                        <div className="error-key-parent">
+                          <iconError.react tag="div" />
+                          <div className="error-key-missing">
+                            Only hyphens (-), underscores (_), lowercase
+                            characters, and numbers are allowed. International
+                            characters are allowed.
+                          </div>
+                        </div>
+                      )}
+                  </div>
+                  
+                  {label=== DEFAULT_LABEL_DETAIL && buttonText === 'ADD LABEL' ?"":
+                      
+                  <div
+                    role="button"
+                    className="labels-delete-icon"
+                    onClick={() => handleDeleteLabel(index, labelSplit[0])}
+                  >
+                    <iconDelete.react tag="div" />
+                  </div>}
+                  <></>
+                </div>
+              </div>
+            );
+          })}
+        <div
+          role="button"
+          className={styleAddLabelButton(buttonText, labelDetail)}
+          onClick={() => {
+            (labelDetail.length === 0 ||
+              labelDetail[labelDetail.length - 1].split(':')[0].length > 0) &&
+              handleAddLabel();
+          }}
+        >
+          {labelDetail.length === 0 ||
+          labelDetail[labelDetail.length - 1].split(':')[0].length > 0 ? (
+            <iconPlus.react tag="div" />
+          ) : (
+            <iconPlusDisable.react tag="div" />
+          )}
+          <div
+            className={
+              labelDetail.length === 0 ||
+              labelDetail[labelDetail.length - 1].split(':')[0].length > 0
+                ? 'job-edit-text'
+                : 'job-edit-text-disabled'
+            }
+          >
+            {buttonText}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default LabelProperties;
diff --git a/src/jobs/submitJob.tsx b/src/jobs/submitJob.tsx
new file mode 100644
index 00000000..7fd8337f
--- /dev/null
+++ b/src/jobs/submitJob.tsx
@@ -0,0 +1,1110 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useEffect, useState } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import LeftArrowIcon from '../../style/icons/left_arrow_icon.svg';
+import { Input, Select } from 'semantic-ui-react';
+import 'semantic-ui-css/semantic.min.css';
+import LabelProperties from './labelProperties';
+import { authApi } from '../utils/utils';
+import TagsInput from 'react-tagsinput';
+import 'react-tagsinput/react-tagsinput.css';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  ARCHIVE_FILES_MESSAGE,
+  ARGUMENTS_MESSAGE,
+  BASE_URL,
+  FILES_MESSAGE,
+  JAR_FILE_MESSAGE,
+  MAIN_CLASS_MESSAGE,
+  MAX_RESTART_MESSAGE,
+  QUERY_FILE_MESSAGE,
+  RESTART_JOB_URL,
+  STATUS_RUNNING
+} from '../utils/const';
+import errorIcon from '../../style/icons/error_icon.svg';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+
+const iconLeftArrow = new LabIcon({
+  name: 'launcher:left-arrow-icon',
+  svgstr: LeftArrowIcon
+});
+const iconError = new LabIcon({
+  name: 'launcher:error-icon',
+  svgstr: errorIcon
+});
+
+function jobKey(selectedJobClone: any) {
+  const jobKeys: string[] = [];
+
+  for (const key in selectedJobClone) {
+    if (key.endsWith('Job')) {
+      jobKeys.push(key);
+    }
+  }
+  return jobKeys;
+}
+function jobTypeFunction(jobKey: string) {
+  let jobType = 'spark';
+  switch (jobKey) {
+    case 'sparkRJob':
+      jobType = 'sparkR';
+      return jobType;
+    case 'pysparkJob':
+      jobType = 'pySpark';
+      return jobType;
+    case 'sparkSqlJob':
+      jobType = 'sparkSql';
+      return jobType;
+    default:
+      return jobType;
+  }
+}
+const handleOptionalFields = (selectedJobClone: any, jobTypeKey: any) => {
+  let args: string[] = [];
+  let jarFileUris: string[] = [];
+  let archiveUris: string[] = [];
+  let fileUris: string[] = [];
+  let maxFailuresPerHour = '';
+  let pythonFileUris: string[] = [];
+  if (selectedJobClone[jobTypeKey].hasOwnProperty('fileUris')) {
+    fileUris = [selectedJobClone[jobTypeKey].fileUris];
+  }
+
+  if (selectedJobClone[jobTypeKey].hasOwnProperty('jarFileUris')) {
+    jarFileUris = [selectedJobClone[jobTypeKey].jarFileUris];
+  }
+
+  if (selectedJobClone[jobTypeKey].hasOwnProperty('args')) {
+    args = [selectedJobClone[jobTypeKey].args];
+  }
+
+  if (selectedJobClone[jobTypeKey].hasOwnProperty('archiveUris')) {
+    archiveUris = [selectedJobClone[jobTypeKey].archiveUris];
+  }
+
+  if (selectedJobClone[jobTypeKey].hasOwnProperty('pythonFileUris')) {
+    pythonFileUris = [selectedJobClone[jobTypeKey].pythonFileUris];
+  }
+
+  if (selectedJobClone.hasOwnProperty('scheduling')) {
+    maxFailuresPerHour = selectedJobClone.scheduling.maxFailuresPerHour;
+  }
+  return {
+    fileUris,
+    jarFileUris,
+    args,
+    archiveUris,
+    pythonFileUris,
+    maxFailuresPerHour
+  };
+};
+function SubmitJob(
+  this: any,
+  { setSubmitJobView, selectedJobClone, clusterResponse }: any
+) {
+  const [clusterList, setClusterList] = useState([{}]);
+  const [jobTypeList, setJobTypeList] = useState([{}]);
+  const [querySourceTypeList, setQuerySourceTypeList] = useState([{}]);
+  const [clusterSelected, setClusterSelected] = useState('');
+  const [jobIdSelected, setJobIdSelected] = useState('');
+  const [propertyDetail, setPropertyDetail] = useState(['']);
+  const [propertyDetailUpdated, setPropertyDetailUpdated] = useState(['']);
+  const [parameterDetail, setParameterDetail] = useState(['']);
+  const [parameterDetailUpdated, setParameterDetailUpdated] = useState(['']);
+  const [hexNumber, setHexNumber] = useState('');
+  const [submitDisabled, setSubmitDisabled] = useState(true);
+  let mainJarFileUri = '';
+  let args: string[] = [];
+  let jarFileUris: string[] = [];
+  let archiveUris: string[] = [];
+  let fileUris: string[] = [];
+  let pythonFileUris: string[] = [];
+  let maxFailuresPerHour = '';
+  let mainRFileUri = '';
+  let jobType = 'spark';
+  let mainPythonFileUri = '';
+  let queryFileUri = '';
+  let queryType = '';
+  let queryList = '';
+  let mainClass = '';
+  let key: string[] | (() => string[]) = [];
+  let value: string[] | (() => string[]) = [];
+  let jobKeys: string[] = [];
+  if (Object.keys(selectedJobClone).length !== 0) {
+    jobKeys = jobKey(selectedJobClone);
+    const jobTypeKey = jobKeys[0];
+    jobType = jobTypeFunction(jobKeys[0]);
+
+    if (selectedJobClone[jobTypeKey].hasOwnProperty('queryFileUri')) {
+      queryFileUri = selectedJobClone[jobTypeKey].queryFileUri;
+      queryType = 'queryFile';
+    }
+    if (selectedJobClone[jobTypeKey].hasOwnProperty('queryList')) {
+      queryList = selectedJobClone[jobTypeKey].queryList.queries[0];
+      queryType = 'queryText';
+    }
+    mainJarFileUri = selectedJobClone[jobKeys[0]].mainJarFileUri;
+    mainClass = selectedJobClone[jobKeys[0]].mainClass;
+    mainRFileUri = selectedJobClone[jobKeys[0]].mainRFileUri;
+    mainPythonFileUri = selectedJobClone[jobKeys[0]].mainPythonFileUri;
+    ({
+      fileUris,
+      jarFileUris,
+      args,
+      archiveUris,
+      pythonFileUris,
+      maxFailuresPerHour
+    } = handleOptionalFields(selectedJobClone, jobTypeKey));
+  }
+  const initialMainClassSelected =
+    mainJarFileUri && mainJarFileUri !== '' ? mainJarFileUri : mainClass;
+  const [mainClassSelected, setMainClassSelected] = useState(
+    initialMainClassSelected
+  );
+  const [mainRSelected, setMainRSelected] = useState(mainRFileUri);
+  const [mainPythonSelected, setMainPythonSelected] =
+    useState(mainPythonFileUri);
+  const [jarFileSelected, setJarFileSelected] = useState([...jarFileUris]);
+  const [fileSelected, setFileSelected] = useState([...fileUris]);
+  const [archieveFileSelected, setArchieveFileSelected] = useState([
+    ...archiveUris
+  ]);
+  const [argumentSelected, setArgumentSelected] = useState([...args]);
+  const [maxRestartSelected, setMaxRestartSelected] =
+    useState(maxFailuresPerHour);
+  const [jobTypeSelected, setJobTypeSelected] = useState(jobType);
+  const [additionalPythonFileSelected, setAdditionalPythonFileSelected] =
+    useState([...pythonFileUris]);
+  const [queryFileSelected, setQueryFileSelected] = useState(queryFileUri);
+  const [querySourceSelected, setQuerySourceSelected] = useState(queryType);
+  const [queryTextSelected, setQueryTextSelected] = useState(queryList);
+  const [labelDetail, setLabelDetail] = useState(key);
+  const [labelDetailUpdated, setLabelDetailUpdated] = useState(value);
+
+  const [additionalPythonFileValidation, setAdditionalPythonFileValidation] =
+    useState(true);
+  const [jarFileValidation, setJarFileValidation] = useState(true);
+  const [fileValidation, setFileValidation] = useState(true);
+  const [archieveFileValidation, setArchieveFileValidation] = useState(true);
+  const [mainPythonValidation, setMainPythonValidation] = useState(true);
+  const [queryFileValidation, setQueryFileValidation] = useState(true);
+  const [mainRValidation, setMainRValidation] = useState(true);
+  const [mainClassValidation, setMainClassValidation] = useState(true);
+  const [generationCompleted, setGenerationCompleted] = useState(false);
+  const [keyValidation, setKeyValidation] = useState(-1);
+  const [valueValidation, setValueValidation] = useState(-1);
+  const [jobIdValidation, setjobIdValidation] = useState(true);
+  const [duplicateKeyError, setDuplicateKeyError] = useState(-1);
+  const [mainClassActive, setMainClassActive] = useState(false);
+
+  const handleCancelJobButton = () => {
+    setSubmitJobView(false);
+  };
+  const handleSubmitJobView = () => {
+    if (!submitDisabled) {
+      submitJob();
+      setSubmitJobView(false);
+    }
+  };
+
+  const handleSubmitJobBackView = () => {
+    setSubmitJobView(false);
+  };
+
+  const handleClusterSelected = (event: any, data: any) => {
+    setClusterSelected(data.value);
+  };
+
+  const handleJobTypeSelected = (event: any, data: any) => {
+    setJobTypeSelected(data.value);
+    setFileSelected([]);
+    setJarFileSelected([]);
+    setAdditionalPythonFileSelected([]);
+    setArchieveFileSelected([]);
+    setArgumentSelected([]);
+    setMainPythonSelected('');
+    setMainRSelected('');
+    setQueryTextSelected('');
+    setQueryFileSelected('');
+    setMainClassSelected('');
+  };
+  const handleQuerySourceTypeSelected = (event: any, data: any) => {
+    setQuerySourceSelected(data.value);
+  };
+
+  useEffect(() => {
+    let transformClusterListData = [];
+    transformClusterListData = clusterResponse.clusters.filter((data: any) => {
+      if (data.status.state === STATUS_RUNNING) {
+        return {
+          clusterName: data.clusterName
+        };
+      }
+    });
+
+    const keyLabelStructure = transformClusterListData.map((obj: any) => ({
+      key: obj.clusterName,
+      value: obj.clusterName,
+      text: obj.clusterName
+    }));
+
+    setClusterList(keyLabelStructure);
+    const jobTypeData = [
+      { key: 'spark', value: 'spark', text: 'Spark' },
+      { key: 'sparkR', value: 'sparkR', text: 'SparkR' },
+      { key: 'sparkSql', value: 'sparkSql', text: 'SparkSql' },
+      { key: 'pySpark', value: 'pySpark', text: 'PySpark' }
+    ];
+    const querySourceData = [
+      { key: 'queryFile', value: 'queryFile', text: 'Query file' },
+      { key: 'queryText', value: 'queryText', text: 'Query text' }
+    ];
+    setJobTypeList(jobTypeData);
+    setQuerySourceTypeList(querySourceData);
+  }, []);
+  useEffect(() => {
+    disableSubmitButtonIfInvalid();
+    generateRandomHex();
+  }, [
+    clusterSelected,
+    jobIdSelected,
+    mainClassSelected,
+    mainRSelected,
+    mainPythonSelected,
+    queryFileSelected,
+    queryTextSelected,
+    mainClassValidation,
+    jarFileValidation,
+    archieveFileValidation,
+    mainRValidation,
+    fileValidation,
+    mainPythonValidation,
+    queryFileValidation,
+    keyValidation,
+    valueValidation,
+    jobIdValidation,
+    duplicateKeyError
+  ]);
+  const disableSubmitButtonIfInvalid  = () => {
+    const isSparkJob = jobTypeSelected === 'spark';
+    const isSparkRJob = jobTypeSelected === 'sparkR';
+    const isPySparkJob = jobTypeSelected === 'pySpark';
+    const isSparkSqlJob = jobTypeSelected === 'sparkSql';
+    if (
+      clusterSelected !== '' &&
+      jobIdSelected !== '' &&
+      ((isSparkJob &&
+        mainClassSelected.length !== 0 &&
+        jarFileValidation &&
+        fileValidation &&
+        archieveFileValidation &&
+        keyValidation === -1 &&
+        valueValidation === -1 &&
+        jobIdValidation &&
+        duplicateKeyError === -1) ||
+        (isSparkRJob &&
+          mainRSelected !== '' &&
+          mainRValidation &&
+          fileValidation &&
+          keyValidation === -1 &&
+          valueValidation === -1 &&
+          jobIdValidation &&
+          duplicateKeyError === -1) ||
+        (isPySparkJob &&
+          mainPythonSelected !== '' &&
+          mainPythonValidation &&
+          additionalPythonFileValidation &&
+          jarFileValidation &&
+          fileValidation &&
+          archieveFileValidation &&
+          keyValidation === -1 &&
+          valueValidation === -1 &&
+          jobIdValidation &&
+          duplicateKeyError === -1) ||
+        (isSparkSqlJob &&
+          queryFileSelected !== '' &&
+          querySourceSelected === 'queryFile' &&
+          queryFileValidation &&
+          jarFileValidation &&
+          keyValidation === -1 &&
+          valueValidation === -1 &&
+          jobIdValidation &&
+          duplicateKeyError === -1) ||
+        (isSparkSqlJob &&
+          queryTextSelected !== '' &&
+          querySourceSelected === 'queryText' &&
+          jarFileValidation &&
+          keyValidation === -1 &&
+          valueValidation === -1 &&
+          jobIdValidation &&
+          duplicateKeyError === -1))
+    ) {
+      setSubmitDisabled(false);
+    } else {
+      setSubmitDisabled(true);
+    }
+  };
+
+  useEffect(() => {
+    let jobKeys: string[] = [];
+    if (Object.keys(selectedJobClone).length !== 0) {
+      if (selectedJobClone.hasOwnProperty('labels')) {
+        const updatedLabelDetail = Object.entries(selectedJobClone.labels).map(
+          ([k, v]) => `${k}:${v}`
+        );
+        setLabelDetail(prevLabelDetail => [
+          ...prevLabelDetail,
+          ...updatedLabelDetail
+        ]);
+        setLabelDetailUpdated(prevLabelDetailUpdated => [
+          ...prevLabelDetailUpdated,
+          ...updatedLabelDetail
+        ]);
+        for (const key in selectedJobClone) {
+          if (key.endsWith('Job')) {
+            jobKeys.push(key);
+          }
+        }
+        jobKeys = jobKey(selectedJobClone);
+        if (selectedJobClone[jobKeys[0]].hasOwnProperty('properties')) {
+          const updatedPropertyDetail = Object.entries(
+            selectedJobClone[jobKeys[0]].properties
+          ).map(([k, v]) => `${k}:${v}`);
+          setPropertyDetail(prevPropertyDetail => [
+            ...prevPropertyDetail,
+            ...updatedPropertyDetail
+          ]);
+          setPropertyDetailUpdated(prevPropertyDetailUpdated => [
+            ...prevPropertyDetailUpdated,
+            ...updatedPropertyDetail
+          ]);
+        }
+      }
+    }
+  }, []);
+
+  const generateRandomHex = () => {
+    if (!generationCompleted) {
+      const crypto = window.crypto || window.Crypto;
+      const array = new Uint32Array(1);
+      crypto.getRandomValues(array);
+      const hex = array[0].toString(16);
+      const paddedHex = hex.padStart(8, '0');
+      setHexNumber('job-' + paddedHex);
+      setJobIdSelected('job-' + paddedHex);
+      setGenerationCompleted(true);
+    }
+  };
+
+  const createPySparkPayload = (
+    mainPythonSelected: any,
+    propertyObject: any,
+    jarFileSelected: any,
+    fileSelected: any,
+    archieveFileSelected: any,
+    argumentSelected: any,
+    additionalPythonFileSelected: any
+  ) => {
+    return {
+      pysparkJob: {
+        mainPythonFileUri: mainPythonSelected,
+        ...(propertyObject && {
+          properties: propertyObject
+        }),
+        ...(jarFileSelected !== '' && {
+          jarFileUris: jarFileSelected
+        }),
+        ...(fileSelected !== '' && {
+          fileUris: fileSelected
+        }),
+        ...(archieveFileSelected !== '' && {
+          archiveUris: archieveFileSelected
+        }),
+        ...(argumentSelected !== '' && {
+          args: [argumentSelected]
+        }),
+        ...(additionalPythonFileSelected !== '' && {
+          pythonFileUris: [additionalPythonFileSelected]
+        })
+      }
+    };
+  };
+
+  const createSparkPayload = (
+    mainClassSelected: any,
+    propertyObject: any,
+    archieveFileSelected: any,
+    fileSelected: any,
+    jarFileSelected: any,
+    argumentSelected: any
+  ) => {
+    return {
+      sparkJob: {
+        mainJarFileUri: mainClassSelected,
+        ...(propertyObject && {
+          properties: propertyObject
+        }),
+        ...(archieveFileSelected !== '' && {
+          archiveUris: archieveFileSelected
+        }),
+        ...(fileSelected !== '' && {
+          fileUris: [fileSelected]
+        }),
+        ...(jarFileSelected !== '' && {
+          jarFileUris: jarFileSelected
+        }),
+        ...(argumentSelected !== '' && {
+          args: [argumentSelected]
+        })
+      }
+    };
+  };
+
+  const createSparkRPayload = (
+    mainRSelected: any,
+    propertyObject: any,
+    fileSelected: any,
+    argumentSelected: any
+  ) => {
+    return {
+      sparkRJob: {
+        mainRFileUri: mainRSelected,
+        ...(propertyObject && {
+          properties: propertyObject
+        }),
+        ...(fileSelected !== '' && {
+          fileUris: [fileSelected]
+        }),
+        ...(argumentSelected !== '' && {
+          args: [argumentSelected]
+        })
+      }
+    };
+  };
+
+  const createSparkSqlPayload = (
+    propertyObject: any,
+    jarFileSelected: any,
+    querySourceSelected: string,
+    queryFileSelected: any,
+    queryTextSelected: any
+  ) => {
+    return {
+      sparkSqlJob: {
+        ...(propertyObject && {
+          properties: propertyObject
+        }),
+        ...(jarFileSelected !== '' && {
+          jarFileUris: [jarFileSelected]
+        }),
+        scriptVariables: {},
+        ...(querySourceSelected === 'queryFile' && {
+          queryFileUri: queryFileSelected
+        }),
+        ...(querySourceSelected === 'queryText' && {
+          queryList: { queries: [queryTextSelected] }
+        })
+      }
+    };
+  };
+
+  const submitJob = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      const labelObject: { [key: string]: string } = {};
+      labelDetailUpdated.forEach((label: string) => {
+        const key = label.split(':')[0];
+        const value = label.split(':')[1];
+        labelObject[key] = value;
+      });
+      const propertyObject: { [key: string]: string } = {};
+      propertyDetailUpdated.forEach((label: string) => {
+        const key = label.split(':')[0];
+        const value = label.split(':')[1];
+        propertyObject[key] = value;
+      });
+      const parameterObject: { [key: string]: string } = {};
+      parameterDetailUpdated.forEach((label: string) => {
+        const key = label.split(':')[0];
+        const value = label.split(':')[1];
+        parameterObject[key] = value;
+      });
+      const payload = {
+        projectId: credentials.project_id,
+        region: credentials.region_id,
+        job: {
+          placement: { clusterName: clusterSelected },
+          statusHistory: [],
+          reference: { jobId: jobIdSelected, projectId: '' },
+          ...(maxRestartSelected !== '' && {
+            scheduling: { maxFailuresPerHour: maxRestartSelected }
+          }),
+          ...(labelObject && {
+            labels: labelObject
+          }),
+          ...(jobTypeSelected === 'pySpark' &&
+            createPySparkPayload(
+              mainPythonSelected,
+              propertyObject,
+              jarFileSelected,
+              fileSelected,
+              archieveFileSelected,
+              argumentSelected,
+              additionalPythonFileSelected
+            )),
+          ...(jobTypeSelected === 'spark' &&
+            createSparkPayload(
+              mainClassSelected,
+              propertyObject,
+              archieveFileSelected,
+              fileSelected,
+              jarFileSelected,
+              argumentSelected
+            )),
+          ...(jobTypeSelected === 'sparkR' &&
+            createSparkRPayload(
+              mainRSelected,
+              propertyObject,
+              fileSelected,
+              argumentSelected
+            )),
+          ...(jobTypeSelected === 'sparkSql' &&
+            createSparkSqlPayload(
+              propertyObject,
+              jarFileSelected,
+              querySourceSelected,
+              queryFileSelected,
+              queryTextSelected
+            ))
+        }
+      };
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/jobs:submit`,
+        {
+          method: 'POST',
+          body: JSON.stringify(payload),
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: any) => {
+          if (response.status === 200) {
+            response
+              .json()
+              .then((responseResult: any) => {
+                console.log(responseResult);
+                toast.success(`Job ${jobIdSelected} successfully submitted`);
+              })
+              .catch((e: any) => {
+                console.log(e);
+              });
+          } else {
+            throw new Error(`API failed with status: ${response.status}`);
+          }
+        })
+        .catch((err: any) => {
+          console.error('Error submitting job', err);
+          toast.error('Failed to submit the job');
+        });
+    }
+  };
+  const handleInputChange = (event: any) => {
+    event.target.value.length > 0
+      ? setjobIdValidation(true)
+      : setjobIdValidation(false);
+    setHexNumber(event.target.value);
+    const newJobId = event.target.value;
+    setJobIdSelected(newJobId);
+  };
+
+  const handleValidationFiles = (
+    listOfFiles: any,
+    setValuesPart: any,
+    setValidationPart: any
+  ) => {
+    if (typeof listOfFiles === 'string') {
+      if (
+        listOfFiles.startsWith('file://') ||
+        listOfFiles.startsWith('gs://') ||
+        listOfFiles.startsWith('hdfs://')
+      ) {
+        setValidationPart(true);
+      } else {
+        setValidationPart(false);
+      }
+      setValuesPart(listOfFiles);
+    } else {
+      if (listOfFiles.length === 0) {
+        setValidationPart(true);
+      } else {
+        listOfFiles.forEach((fileName: any) => {
+          if (
+            fileName.startsWith('file://') ||
+            fileName.startsWith('gs://') ||
+            fileName.startsWith('hdfs://')
+          ) {
+            setValidationPart(true);
+          } else {
+            setValidationPart(false);
+          }
+        });
+      }
+      setValuesPart(listOfFiles);
+    }
+  };
+
+  return (
+    <div>
+      <div className="scroll-comp">
+        <div className="cluster-details-header">
+          <div
+            role="button"
+            className="back-arrow-icon"
+            onClick={() => handleSubmitJobBackView()}
+          >
+            <iconLeftArrow.react tag="div" />
+          </div>
+          <div className="cluster-details-title">Submit a job</div>
+        </div>
+        <div className="submit-job-container">
+          <div className="submit-job-label-header">Cluster</div>
+          <div className="submit-job-cluster-message">
+            Choose a cluster to run your job in.
+          </div>
+          {clusterList.length === 0 ? (
+            <Input
+              className="input-style"
+              value="No clusters running"
+              readOnly
+            />
+          ) : (
+            <Select
+              placeholder="Cluster*"
+              onChange={handleClusterSelected}
+              className="select-job-style"
+              options={clusterList}
+              value={clusterSelected}
+            />
+          )}
+          <div className="submit-job-label-header">Job</div>
+          <div className="submit-job-cluster-message">Job ID*</div>
+          <Input
+            className="input-style"
+            onChange={e => handleInputChange(e)}
+            type="text"
+            value={hexNumber}
+          />
+          {!jobIdValidation && (
+            <div className="error-key-parent">
+              <iconError.react tag="div" />
+              <div className="error-key-missing">ID is required</div>
+            </div>
+          )}
+          <div className="submit-job-cluster-message">Job type*</div>
+
+          <Select
+            onChange={handleJobTypeSelected}
+            className="select-job-style"
+            options={jobTypeList}
+            value={jobTypeSelected}
+          />
+
+          {jobTypeSelected === 'sparkSql' && (
+            <>
+              <div className="submit-job-cluster-message">
+                Query source type*
+              </div>
+              <Select
+                onChange={handleQuerySourceTypeSelected}
+                className="select-job-style"
+                options={querySourceTypeList}
+                value={querySourceSelected}
+              />
+            </>
+          )}
+          {querySourceSelected === 'queryFile' &&
+            jobTypeSelected === 'sparkSql' && (
+              <>
+                <div className="submit-job-cluster-message">Query file*</div>
+                <Input
+                  className="input-style"
+                  onChange={e =>
+                    handleValidationFiles(
+                      e.target.value,
+                      setQueryFileSelected,
+                      setQueryFileValidation
+                    )
+                  }
+                  addOnBlur={true}
+                  value={queryFileSelected}
+                />
+                {!queryFileValidation && (
+                  <div className="error-key-parent">
+                    <iconError.react tag="div" />
+                    <div className="error-key-missing">
+                      File must include a valid scheme prefix: 'file://',
+                      'gs://', or 'hdfs://'
+                    </div>
+                  </div>
+                )}
+                {queryFileValidation && (
+                  <div className="submit-job-message">{QUERY_FILE_MESSAGE}</div>
+                )}
+              </>
+            )}
+          {querySourceSelected === 'queryText' &&
+            jobTypeSelected === 'sparkSql' && (
+              <>
+                <div className="submit-job-cluster-message">Query text*</div>
+                <Input
+                  className="input-style"
+                  onChange={e => setQueryTextSelected(e.target.value)}
+                  value={queryTextSelected}
+                />
+                <div className="submit-job-message">The query to execute</div>
+              </>
+            )}
+          {jobTypeSelected === 'spark' && (
+            <>
+              <div className="submit-job-cluster-message">
+                Main class or jar*
+              </div>
+              <Input
+                className="input-style"
+                onChange={e =>
+                  handleValidationFiles(
+                    e.target.value,
+                    setMainClassSelected,
+                    setMainClassValidation
+                  )
+                }
+                onBlur={() => setMainClassActive(true)}
+                addOnBlur={true}
+                value={mainClassSelected}
+              />
+              {mainClassSelected === '' && mainClassActive && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    Main class or jar is required
+                  </div>
+                </div>
+              )}
+              {(mainClassSelected !== '' || !mainClassActive) && (
+                <div className="submit-job-message">{MAIN_CLASS_MESSAGE}</div>
+              )}
+            </>
+          )}
+          {jobTypeSelected === 'sparkR' && (
+            <>
+              <div className="submit-job-cluster-message">Main R file*</div>
+              <Input
+                className="input-style"
+                onChange={e =>
+                  handleValidationFiles(
+                    e.target.value,
+                    setMainRSelected,
+                    setMainRValidation
+                  )
+                }
+                addOnBlur={true}
+                value={mainRSelected}
+              />
+              {!mainRValidation && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    File must include a valid scheme prefix: 'file://', 'gs://',
+                    or 'hdfs://'
+                  </div>
+                </div>
+              )}
+              {mainRValidation && (
+                <div className="submit-job-message">{QUERY_FILE_MESSAGE}</div>
+              )}
+            </>
+          )}
+          {jobTypeSelected === 'pySpark' && (
+            <>
+              <div className="submit-job-cluster-message">
+                Main Python file*
+              </div>
+              <Input
+                className="input-style"
+                onChange={e =>
+                  handleValidationFiles(
+                    e.target.value,
+                    setMainPythonSelected,
+                    setMainPythonValidation
+                  )
+                }
+                addOnBlur={true}
+                value={mainPythonSelected}
+              />
+              {!mainPythonValidation && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    File must include a valid scheme prefix: 'file://', 'gs://',
+                    or 'hdfs://'
+                  </div>
+                </div>
+              )}
+              {mainPythonValidation && (
+                <div className="submit-job-message">{QUERY_FILE_MESSAGE}</div>
+              )}
+            </>
+          )}
+          {jobTypeSelected === 'pySpark' && (
+            <>
+              <div className="submit-job-cluster-message">
+                Additional python files
+              </div>
+              <TagsInput
+                className="input-style"
+                onChange={e =>
+                  handleValidationFiles(
+                    e,
+                    setAdditionalPythonFileSelected,
+                    setAdditionalPythonFileValidation
+                  )
+                }
+                addOnBlur={true}
+                value={additionalPythonFileSelected}
+                inputProps={{ placeholder: '' }}
+              />
+              {!additionalPythonFileValidation && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    All files must include a valid scheme prefix: 'file://',
+                    'gs://', or 'hdfs://'
+                  </div>
+                </div>
+              )}
+            </>
+          )}
+          {jobTypeSelected !== 'sparkR' && (
+            <>
+              <div className="submit-job-cluster-message">Jar files</div>
+              <TagsInput
+                className="select-job-style"
+                onChange={e =>
+                  handleValidationFiles(
+                    e,
+                    setJarFileSelected,
+                    setJarFileValidation
+                  )
+                }
+                addOnBlur={true}
+                value={jarFileSelected}
+                inputProps={{ placeholder: '' }}
+              />
+              {!jarFileValidation && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    All files must include a valid scheme prefix: 'file://',
+                    'gs://', or 'hdfs://'
+                  </div>
+                </div>
+              )}
+              {jarFileValidation && (
+                <div className="submit-job-message">{JAR_FILE_MESSAGE}</div>
+              )}
+            </>
+          )}
+          {jobTypeSelected !== 'sparkSql' && (
+            <>
+              <div className="submit-job-cluster-message">Files</div>
+              <TagsInput
+                className="select-job-style"
+                onChange={e =>
+                  handleValidationFiles(e, setFileSelected, setFileValidation)
+                }
+                addOnBlur={true}
+                value={fileSelected}
+                inputProps={{ placeholder: '' }}
+              />
+              {!fileValidation && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    All files must include a valid scheme prefix: 'file://',
+                    'gs://', or 'hdfs://'
+                  </div>
+                </div>
+              )}
+              {fileValidation && (
+                <div className="submit-job-message">{FILES_MESSAGE}</div>
+              )}
+            </>
+          )}
+          {(jobTypeSelected === 'spark' || jobTypeSelected === 'pySpark') && (
+            <>
+              <div className="submit-job-cluster-message">Archive files</div>
+              <TagsInput
+                className="select-job-style"
+                onChange={e =>
+                  handleValidationFiles(
+                    e,
+                    setArchieveFileSelected,
+                    setArchieveFileValidation
+                  )
+                }
+                addOnBlur={true}
+                value={archieveFileSelected}
+                inputProps={{ placeholder: '' }}
+              />
+              {!archieveFileValidation && (
+                <div className="error-key-parent">
+                  <iconError.react tag="div" />
+                  <div className="error-key-missing">
+                    All files must include a valid scheme prefix: 'file://',
+                    'gs://', or 'hdfs://'
+                  </div>
+                </div>
+              )}
+              {archieveFileValidation && (
+                <div className="submit-job-message">{ARCHIVE_FILES_MESSAGE}</div>
+              )}
+            </>
+          )}
+          {jobTypeSelected !== 'sparkSql' && (
+            <>
+              <div className="submit-job-cluster-message">Arguments</div>
+              <TagsInput
+                className="select-job-style"
+                onChange={e => setArgumentSelected(e)}
+                value={argumentSelected}
+                inputProps={{ placeholder: '' }}
+              />
+              <div className="submit-job-message">{ARGUMENTS_MESSAGE}</div>
+            </>
+          )}
+          {querySourceSelected === 'queryFile' &&
+            jobTypeSelected === 'sparkSql' && (
+              <>
+                <div className="submit-job-label-header">Query parameters</div>
+                <LabelProperties
+                  labelDetail={parameterDetail}
+                  setLabelDetail={setParameterDetail}
+                  labelDetailUpdated={parameterDetailUpdated}
+                  setLabelDetailUpdated={setParameterDetailUpdated}
+                  selectedJobClone={selectedJobClone ? true : false}
+                  buttonText="ADD PARAMETER"
+                  keyValidation={keyValidation}
+                  setKeyValidation={setKeyValidation}
+                  valueValidation={valueValidation}
+                  setValueValidation={setValueValidation}
+                  duplicateKeyError={duplicateKeyError}
+                  setDuplicateKeyError={setDuplicateKeyError}
+                />
+              </>
+            )}
+          <div className="submit-job-cluster-message">
+            Max restarts per hour
+          </div>
+          <Input
+            className="input-style"
+            onChange={e => setMaxRestartSelected(e.target.value)}
+            value={maxRestartSelected}
+          />
+          <div className="submit-job-message-with-link">
+            {MAX_RESTART_MESSAGE}
+            <div
+              className="submit-job-learn-more"
+              onClick={() => {
+                window.open(`${RESTART_JOB_URL}`, '_blank');
+              }}
+            >
+              Learn more
+            </div>
+          </div>
+
+          <div className="submit-job-label-header">Properties</div>
+          <LabelProperties
+            labelDetail={propertyDetail}
+            setLabelDetail={setPropertyDetail}
+            labelDetailUpdated={propertyDetailUpdated}
+            setLabelDetailUpdated={setPropertyDetailUpdated}
+            selectedJobClone={selectedJobClone ? true : false}
+            buttonText="ADD PROPERTY"
+            keyValidation={keyValidation}
+            setKeyValidation={setKeyValidation}
+            valueValidation={valueValidation}
+            setValueValidation={setValueValidation}
+            duplicateKeyError={duplicateKeyError}
+            setDuplicateKeyError={setDuplicateKeyError}
+          />
+          <div className="submit-job-label-header">Labels</div>
+          <LabelProperties
+            labelDetail={labelDetail}
+            setLabelDetail={setLabelDetail}
+            labelDetailUpdated={labelDetailUpdated}
+            setLabelDetailUpdated={setLabelDetailUpdated}
+            selectedJobClone={selectedJobClone ? true : false}
+            buttonText="ADD LABEL"
+            keyValidation={keyValidation}
+            setKeyValidation={setKeyValidation}
+            valueValidation={valueValidation}
+            setValueValidation={setValueValidation}
+            duplicateKeyError={duplicateKeyError}
+            setDuplicateKeyError={setDuplicateKeyError}
+          />
+          <div className="job-button-style-parent">
+            <div
+              className={
+                submitDisabled
+                  ? 'submit-button-disable-style'
+                  : 'submit-button-style'
+              }
+            >
+              <div
+                role="button"
+                onClick={() => {
+                  handleSubmitJobView();
+                }}
+              >
+                SUBMIT
+              </div>
+            </div>
+            <div className="job-cancel-button-style">
+              <div
+                role="button"
+                onClick={() => {
+                  handleCancelJobButton();
+                }}
+              >
+                CANCEL
+              </div>
+            </div>
+          </div>
+          <ToastContainer />
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default SubmitJob;
diff --git a/src/login/authLogin.tsx b/src/login/authLogin.tsx
new file mode 100644
index 00000000..48a19352
--- /dev/null
+++ b/src/login/authLogin.tsx
@@ -0,0 +1,122 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ReactWidget } from '@jupyterlab/apputils';
+import React, { useEffect, useState } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import signinGoogleIcon from '../../style/icons/signin_google_icon.svg';
+import { requestAPI } from '../handler/handler';
+import ConfigSelection from './configSelection';
+import { LOGIN_STATE, STATUS_SUCCESS } from '../utils/const';
+import { checkConfig } from '../utils/utils';
+import { ClipLoader } from 'react-spinners';
+
+// Create the LabIcon instance outside of the component
+const IconsigninGoogle = new LabIcon({
+  name: 'launcher:signin_google_icon',
+  svgstr: signinGoogleIcon
+});
+
+const AuthLoginComponent = (): React.JSX.Element => {
+  const [loginState, setLoginState] = useState(false);
+  const [isloginDisabled, setIsloginDisabled] = useState(false);
+  const [configError, setConfigError] = useState(false);
+  const [loginError, setLoginError] = useState(false);
+  const [configLoading, setConfigLoading] = useState(true);
+
+  const login = async () => {
+    setIsloginDisabled(true);
+    const data = await requestAPI('login');
+    if (typeof data === 'object' && data !== null) {
+      const loginStatus = (data as { login: string }).login;
+      if (loginStatus === STATUS_SUCCESS) {
+        setLoginState(true);
+        setLoginError(false);
+        localStorage.setItem('loginState', LOGIN_STATE);
+      } else {
+        setLoginState(false);
+        localStorage.removeItem('loginState');
+      }
+    }
+  };
+
+  useEffect(() => {
+    checkConfig(setLoginState, setConfigError, setLoginError);
+    const localstorageGetInformation = localStorage.getItem('loginState');
+    setLoginState(localstorageGetInformation === LOGIN_STATE);
+    if (loginState) {
+      setConfigLoading(false);
+    }
+  }, []);
+
+  return (
+    <div>
+      {configLoading && !loginState && !configError && !loginError && (
+        <div className="spin-loaderMain">
+          <ClipLoader
+            color="#8A8A8A"
+            loading={true}
+            size={18}
+            aria-label="Loading Spinner"
+            data-testid="loader"
+          />
+          Loading Config Setup
+        </div>
+      )}
+      {!loginError && loginState && (
+        <ConfigSelection
+          loginState={loginState}
+          configError={configError}
+          setConfigError={setConfigError}
+        />
+      )}
+      {loginError && (
+        <>
+          <div className="login-error">Please login to continue</div>
+          <div style={{ alignItems: 'center' }}>
+            <div
+              role="button"
+              className={
+                isloginDisabled
+                  ? 'signin-google-icon disabled'
+                  : 'signin-google-icon'
+              }
+              onClick={isloginDisabled ? undefined : login}
+            >
+              <IconsigninGoogle.react tag="div" />
+            </div>
+          </div>
+        </>
+      )}
+      {configError && (
+        <div className="login-error">
+          Please configure gcloud with account, project-id and region
+        </div>
+      )}
+    </div>
+  );
+};
+
+export class AuthLogin extends ReactWidget {
+  constructor() {
+    super();
+  }
+
+  render(): React.JSX.Element {
+    return <AuthLoginComponent />;
+  }
+}
diff --git a/src/login/configSelection.tsx b/src/login/configSelection.tsx
new file mode 100644
index 00000000..e1a7ac51
--- /dev/null
+++ b/src/login/configSelection.tsx
@@ -0,0 +1,392 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useState, useEffect } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import settingsIcon from '../../style/icons/settings_icon.svg';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  PROJECT_LIST_URL,
+  REGION_URL,
+  USER_INFO_URL
+} from '../utils/const';
+import { authApi } from '../utils/utils';
+import { Select } from 'semantic-ui-react';
+import 'semantic-ui-css/semantic.min.css';
+import { requestAPI } from '../handler/handler';
+import ClipLoader from 'react-spinners/ClipLoader';
+import { ToastContainer, ToastOptions, toast } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+
+function ConfigSelection({ loginState, configError, setConfigError }: any) {
+  const Iconsettings = new LabIcon({
+    name: 'launcher:settings_icon',
+    svgstr: settingsIcon
+  });
+
+  const [projectId, setProjectId] = useState('');
+  const [region, setRegion] = useState('');
+  const [projectList, setProjectList] = useState([{}]);
+  const [regionList, setRegionList] = useState([{}]);
+  const [isLoading, setIsLoading] = useState(false);
+  const [isLoadingUser, setIsLoadingUser] = useState(true);
+  const [isLoadingProject, setIsLoadingProject] = useState(true);
+  const [isLoadingRegion, setIsLoadingRegion] = useState(true);
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+  const [regionEmpty, SetRegionEmpty] = useState(true);
+  const [isSaveDisabled, setIsSaveDisabled] = useState(true);
+  const [userInfo, setUserInfo] = useState({
+    email: '',
+    picture: ''
+  });
+
+  const handleProjectIdChange = (event: any, data: any) => {
+    setRegionList([]);
+    SetRegionEmpty(true);
+    if (data.value === undefined) {
+      regionListAPI(data);
+    } else {
+      regionListAPI(data.value);
+      setProjectId(data.value);
+    }
+    if (projectId.length !== 0) {
+      setIsSaveDisabled(false);
+    }
+  };
+
+  const handleRegionChange = (event: any, data: any) => {
+    setRegion(data.value);
+    if (projectId) {
+      setIsSaveDisabled(false);
+    }
+    setIsDropdownOpen(false);
+  };
+
+  const handleSave = async () => {
+    setIsLoading(true);
+    setIsSaveDisabled(true);
+    const dataToSend = { projectId: projectId, region: region };
+    try {
+      const data = await requestAPI<any>('configuration', {
+        body: JSON.stringify(dataToSend),
+        method: 'POST'
+      });
+      if (typeof data === 'object' && data !== null) {
+        const configStatus = (data as { config: string }).config;
+        setIsLoading(false);
+        if (configStatus && !toast.isActive('custom-toast')) {
+          const toastifyCustomStyle: ToastOptions<{}> = {
+            hideProgressBar: true,
+            autoClose: false,
+            theme: 'dark',
+            position: toast.POSITION.BOTTOM_CENTER,
+            toastId: 'custom-toast'
+          };
+          if (configStatus.includes('Failed')) {
+            toast.error(configStatus, toastifyCustomStyle);
+          } else {
+            toast.success(configStatus, toastifyCustomStyle);
+          }
+        }
+      }
+    } catch (reason) {
+      console.error(`Error on POST {dataToSend}.\n${reason}`);
+    }
+  };
+
+  const displayUserInfo = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(USER_INFO_URL, {
+        method: 'GET',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      })
+        .then((response: any) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              setUserInfo(responseResult);
+              setIsLoadingUser(false);
+            })
+            .catch((e: any) => console.log(e));
+        })
+        .catch((err: any) => {
+          setIsLoadingUser(false);
+          console.error('Error displaying user info', err);
+          toast.error('Failed to fetch user information');
+        });
+    }
+  };
+
+  const projectListAPI = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(PROJECT_LIST_URL, {
+        method: 'GET',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      })
+        .then((response: any) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              let transformedProjectList = [];
+              transformedProjectList = responseResult.projects.map(
+                (data: any) => {
+                  return {
+                    value: data.projectId,
+                    key: data.projectId,
+                    text: data.projectId
+                  };
+                }
+              );
+              setProjectList(transformedProjectList);
+              setIsLoadingProject(false);
+            })
+            .catch((e: any) => console.log(e));
+        })
+        .catch((err: any) => {
+          setIsLoadingProject(false);
+          console.error('Error fetching project list', err);
+          toast.error('Failed to fetch the projects');
+        });
+    }
+  };
+  const regionListAPI = async (projectId: any) => {
+    try {
+      const credentials = await authApi();
+      if (credentials) {
+        fetch(`${REGION_URL}${projectId}/regions`, {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        })
+          .then((response: any) => {
+            if (response.ok) {
+              SetRegionEmpty(true);
+              return response.json();
+            } else {
+              setIsDropdownOpen(false);
+              setIsSaveDisabled(true);
+              SetRegionEmpty(false);
+              if (!toast.isActive('custom-toast-error')) {
+                toast.error(response.status + ' Permission Denied', {
+                  position: toast.POSITION.BOTTOM_CENTER,
+                  toastId: 'custom-toast-error'
+                });
+              }
+              throw new Error(`Request failed with status ${response.status}`);
+            }
+          })
+          .then((responseResult: any) => {
+            let transformedRegionList = [];
+            transformedRegionList = responseResult.items.map((data: any) => {
+              return {
+                value: data.name,
+                key: data.name,
+                text: data.name
+              };
+            });
+            setRegionList(transformedRegionList);
+            setIsDropdownOpen(false);
+            SetRegionEmpty(false);
+            setIsLoadingRegion(false);
+          })
+          .catch((error: any) => {
+            setIsLoadingRegion(false);
+            console.error('Error fetching region list:', error.message);
+          });
+      }
+    } catch (error) {
+      setIsLoadingRegion(false);
+      console.error('Error fetching region list:');
+      toast.error('Failed to fetch the regions');
+    }
+  };
+  const handleDropdownOpen = () => {
+    if (regionEmpty) {
+      setIsDropdownOpen(true);
+    }
+  };
+
+  const fetchProjectRegion = async () => {
+    const credentials = await authApi();
+    if (credentials && credentials.project_id && credentials.region_id) {
+      handleProjectIdChange(Event, credentials.project_id);
+      setProjectId(credentials.project_id);
+      setRegion(credentials.region_id);
+      setConfigError(false);
+    } else if (credentials && credentials.config_error === 1) {
+      setConfigError(true);
+    }
+  };
+  useEffect(() => {
+    if (loginState) {
+      fetchProjectRegion();
+      projectListAPI();
+      displayUserInfo();
+    } else {
+      projectListAPI();
+      displayUserInfo();
+    }
+  }, []);
+  return (
+    <div className="Settings-component">
+      <ToastContainer />
+      {isLoadingUser && isLoadingProject && isLoadingRegion && !configError ? (
+        <div className="spin-loaderMain">
+          <ClipLoader
+            color="#8A8A8A"
+            loading={true}
+            size={20}
+            aria-label="Loading Spinner"
+            data-testid="loader"
+          />
+          Loading Config Setup
+        </div>
+      ) : (
+        !configError && (
+          <div>
+            <div className="settings-overlay">
+              <div className="settings-icon">
+                <Iconsettings.react tag="div" />
+              </div>
+              <div className="settings-text">Settings</div>
+            </div>
+            <div className="settings-seperator"></div>
+            <div className="config-overlay">
+              <div className="configForm">
+                <div className="project-overlay">
+                  <label className="project-text" htmlFor="project-id">
+                    Project ID
+                  </label>
+                  <Select
+                    placeholder={projectId}
+                    className="project-select"
+                    value={projectId}
+                    onChange={handleProjectIdChange}
+                    options={projectList}
+                  />
+                </div>
+
+                <div className="region-overlay">
+                  <label className="region-text" htmlFor="region-id">
+                    Region
+                  </label>
+
+                  <Select
+                    onClick={handleDropdownOpen}
+                    placeholder={region}
+                    className="region-select"
+                    value={region}
+                    onChange={handleRegionChange}
+                    options={regionList}
+                    isDisabled={isDropdownOpen}
+                  />
+                </div>
+                <div className="save-overlay">
+                  <button
+                    className={
+                      isSaveDisabled ? 'save-button disabled' : 'save-button'
+                    }
+                    disabled={isSaveDisabled}
+                    onClick={handleSave}
+                  >
+                    Save
+                  </button>
+                  {isLoading && (
+                    <div className="save-loader">
+                      <ClipLoader
+                        loading={true}
+                        size={25}
+                        aria-label="Loading Spinner"
+                        data-testid="loader"
+                      />
+                    </div>
+                  )}
+                </div>
+              </div>
+              {isDropdownOpen && (
+                <div className="region-loader">
+                  <ClipLoader
+                    loading={true}
+                    size={15}
+                    aria-label="Loading Spinner"
+                    data-testid="loader"
+                  />
+                </div>
+              )}
+              <div className="user-info-card">
+                <div className="google-header">
+                  This account is managed by google.com
+                </div>
+                <div className="seperator"></div>
+                <div className="user-overlay">
+                  <div className="user-image-overlay">
+                    <img
+                      src={userInfo.picture}
+                      alt="User Image"
+                      className="user-image"
+                    />
+                  </div>
+                  <div className="user-details">
+                    <div className="user-email">{userInfo.email}</div>
+                  </div>
+                </div>
+                <div className="google-header">
+                  <a
+                    href="https://policies.google.com/privacy?hl=en"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    Privacy Policy
+                  </a>
+                  <span className="footer-divider"> • </span>
+                  <a
+                    href="https://policies.google.com/terms?hl=en"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    Terms of Service
+                  </a>
+                  <span className="footer-divider"> • </span>
+                  <a
+                    href="https://raw.githubusercontent.com/GoogleCloudDataproc/dataproc-jupyter-plugin/main/third-party-licenses.txt"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    Licenses
+                  </a>
+                </div>
+              </div>
+            </div>
+          </div>
+        )
+      )}
+    </div>
+  );
+}
+
+export default ConfigSelection;
diff --git a/src/react-table-config.d.ts b/src/react-table-config.d.ts
new file mode 100644
index 00000000..744d6578
--- /dev/null
+++ b/src/react-table-config.d.ts
@@ -0,0 +1,120 @@
+import {
+    UseColumnOrderInstanceProps,
+    UseColumnOrderState,
+    UseExpandedHooks,
+    UseExpandedInstanceProps,
+    UseExpandedOptions,
+    UseExpandedRowProps,
+    UseExpandedState,
+    UseFiltersColumnOptions,
+    UseFiltersColumnProps,
+    UseFiltersInstanceProps,
+    UseFiltersOptions,
+    UseFiltersState,
+    UseGlobalFiltersColumnOptions,
+    UseGlobalFiltersInstanceProps,
+    UseGlobalFiltersOptions,
+    UseGlobalFiltersState,
+    UseGroupByCellProps,
+    UseGroupByColumnOptions,
+    UseGroupByColumnProps,
+    UseGroupByHooks,
+    UseGroupByInstanceProps,
+    UseGroupByOptions,
+    UseGroupByRowProps,
+    UseGroupByState,
+    UsePaginationInstanceProps,
+    UsePaginationOptions,
+    UsePaginationState,
+    UseResizeColumnsColumnOptions,
+    UseResizeColumnsColumnProps,
+    UseResizeColumnsOptions,
+    UseResizeColumnsState,
+    UseRowSelectHooks,
+    UseRowSelectInstanceProps,
+    UseRowSelectOptions,
+    UseRowSelectRowProps,
+    UseRowSelectState,
+    UseRowStateCellProps,
+    UseRowStateInstanceProps,
+    UseRowStateOptions,
+    UseRowStateRowProps,
+    UseRowStateState,
+    UseSortByColumnOptions,
+    UseSortByColumnProps,
+    UseSortByHooks,
+    UseSortByInstanceProps,
+    UseSortByOptions,
+    UseSortByState
+  } from 'react-table'
+  
+  declare module 'react-table' {
+    // take this file as-is, or comment out the sections that don't apply to your plugin configuration
+  
+    export interface TableOptions<D extends Record<string, unknown>>
+      extends UseExpandedOptions<D>,
+        UseFiltersOptions<D>,
+        UseGlobalFiltersOptions<D>,
+        UseGroupByOptions<D>,
+        UsePaginationOptions<D>,
+        UseResizeColumnsOptions<D>,
+        UseRowSelectOptions<D>,
+        UseRowStateOptions<D>,
+        UseSortByOptions<D>,
+        // note that having Record here allows you to add anything to the options, this matches the spirit of the
+        // underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
+        // feature set, this is a safe default.
+        Record<string, any> {}
+  
+    export interface Hooks<D extends Record<string, unknown> = Record<string, unknown>>
+      extends UseExpandedHooks<D>,
+        UseGroupByHooks<D>,
+        UseRowSelectHooks<D>,
+        UseSortByHooks<D> {}
+  
+    export interface TableInstance<D extends Record<string, unknown> = Record<string, unknown>>
+      extends UseColumnOrderInstanceProps<D>,
+        UseExpandedInstanceProps<D>,
+        UseFiltersInstanceProps<D>,
+        UseGlobalFiltersInstanceProps<D>,
+        UseGroupByInstanceProps<D>,
+        UsePaginationInstanceProps<D>,
+        UseRowSelectInstanceProps<D>,
+        UseRowStateInstanceProps<D>,
+        UseSortByInstanceProps<D> {}
+  
+    export interface TableState<D extends Record<string, unknown> = Record<string, unknown>>
+      extends UseColumnOrderState<D>,
+        UseExpandedState<D>,
+        UseFiltersState<D>,
+        UseGlobalFiltersState<D>,
+        UseGroupByState<D>,
+        UsePaginationState<D>,
+        UseResizeColumnsState<D>,
+        UseRowSelectState<D>,
+        UseRowStateState<D>,
+        UseSortByState<D> {}
+  
+    export interface ColumnInterface<D extends Record<string, unknown> = Record<string, unknown>>
+      extends UseFiltersColumnOptions<D>,
+        UseGlobalFiltersColumnOptions<D>,
+        UseGroupByColumnOptions<D>,
+        UseResizeColumnsColumnOptions<D>,
+        UseSortByColumnOptions<D> {}
+  
+    export interface ColumnInstance<D extends Record<string, unknown> = Record<string, unknown>>
+      extends UseFiltersColumnProps<D>,
+        UseGroupByColumnProps<D>,
+        UseResizeColumnsColumnProps<D>,
+        UseSortByColumnProps<D> {}
+  
+    export interface Cell<D extends Record<string, unknown> = Record<string, unknown>, V = any>
+      extends UseGroupByCellProps<D>,
+        UseRowStateCellProps<D> {}
+  
+    export interface Row<D extends Record<string, unknown> = Record<string, unknown>>
+      extends UseExpandedRowProps<D>,
+        UseGroupByRowProps<D>,
+        UseRowSelectRowProps<D>,
+        UseRowStateRowProps<D> {}
+  }
\ No newline at end of file
diff --git a/src/sessions/listSessions.tsx b/src/sessions/listSessions.tsx
new file mode 100644
index 00000000..1dcf9e64
--- /dev/null
+++ b/src/sessions/listSessions.tsx
@@ -0,0 +1,446 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useState, useEffect, useRef } from 'react';
+import { useTable, useGlobalFilter, usePagination } from 'react-table';
+import { LabIcon } from '@jupyterlab/ui-components';
+import filterIcon from '../../style/icons/filter_icon.svg';
+import SucceededIcon from '../../style/icons/succeeded_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import stopIcon from '../../style/icons/stop_icon.svg';
+import stopDisableIcon from '../../style/icons/stop_disable_icon.svg';
+import deleteIcon from '../../style/icons/delete_icon.svg';
+import { ClipLoader } from 'react-spinners';
+import GlobalFilter from '../utils/globalFilter';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  ClusterStatus,
+  STATUS_ACTIVE,
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_FAIL,
+  STATUS_PENDING,
+  STATUS_PROVISIONING,
+  STATUS_TERMINATED,
+  STATUS_TERMINATING
+} from '../utils/const';
+import TableData from '../utils/tableData';
+import {
+  ICellProps,
+  authApi,
+  elapsedTime,
+  jobTimeFormat
+} from '../utils/utils';
+import SessionDetails from './sessionDetails';
+import DeletePopup from '../utils/deletePopup';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { deleteSessionAPI, terminateSessionAPI } from '../utils/sessionService';
+import { PaginationView } from '../utils/paginationView';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconFilter = new LabIcon({
+  name: 'launcher:filter-icon',
+  svgstr: filterIcon
+});
+const iconSucceeded = new LabIcon({
+  name: 'launcher:succeeded-icon',
+  svgstr: SucceededIcon
+});
+
+const iconClusterError = new LabIcon({
+  name: 'launcher:cluster-error-icon',
+  svgstr: clusterErrorIcon
+});
+const iconStop = new LabIcon({
+  name: 'launcher:stop-icon',
+  svgstr: stopIcon
+});
+const iconStopDisable = new LabIcon({
+  name: 'launcher:stop-disable-icon',
+  svgstr: stopDisableIcon
+});
+const iconDelete = new LabIcon({
+  name: 'launcher:delete-icon',
+  svgstr: deleteIcon
+});
+
+function ListSessions() {
+  const [sessionsList, setSessionsList] = useState([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [pollingDisable, setPollingDisable] = useState(false);
+  const [sessionSelected, setSessionSelected] = useState('');
+  const [detailedSessionView, setDetailedSessionView] = useState(false);
+
+  const [deletePopupOpen, setDeletePopupOpen] = useState(false);
+  const [selectedSessionValue, setSelectedSessionValue] = useState('');
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+
+  const pollingSessions = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const data = sessionsList;
+
+  const columns = React.useMemo(
+    () => [
+      {
+        Header: 'Session ID',
+        accessor: 'sessionID'
+      },
+      {
+        Header: 'Status',
+        accessor: 'status'
+      },
+      {
+        Header: 'Location',
+        accessor: 'location'
+      },
+      {
+        Header: 'Creation time',
+        accessor: 'creationTime'
+      },
+      {
+        Header: 'Elapsed time',
+        accessor: 'elapsedTime'
+      },
+      {
+        Header: 'Actions',
+        accessor: 'actions'
+      }
+    ],
+    []
+  );
+
+  const listSessionsAPI = async (
+    nextPageToken?: string,
+    previousSessionsList?: object
+  ) => {
+    const credentials = await authApi();
+    const pageToken = nextPageToken ?? '';
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/sessions?pageSize=50&pageToken=${pageToken}`,
+        {
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              let transformSessionListData = [];
+              if (responseResult && responseResult.sessions) {
+                let sessionsListNew = responseResult.sessions;
+                sessionsListNew.sort(
+                  (a: { createTime: string }, b: { createTime: string }) => {
+                    const dateA = new Date(a.createTime);
+                    const dateB = new Date(b.createTime);
+                    return Number(dateB) - Number(dateA);
+                  }
+                );
+                transformSessionListData = sessionsListNew.map((data: any) => {
+                  const startTimeDisplay = jobTimeFormat(data.createTime);
+                  const startTime = new Date(data.createTime);
+                  let elapsedTimeString = '';
+                  if (
+                    data.state === STATUS_TERMINATED ||
+                    data.state === STATUS_FAIL
+                  ) {
+                    elapsedTimeString = elapsedTime(data.stateTime, startTime);
+                  }
+
+                  return {
+                    sessionID: data.name.split('/')[5],
+                    status: data.state,
+                    location: data.name.split('/')[3],
+                    creationTime: startTimeDisplay,
+                    elapsedTime: elapsedTimeString,
+                    actions: renderActions(data)
+                  };
+                });
+              }
+
+              const existingSessionsData = previousSessionsList ?? [];
+              //setStateAction never type issue
+              let allSessionsData: any = [
+                ...(existingSessionsData as []),
+                ...transformSessionListData
+              ];
+
+              if (responseResult.nextPageToken) {
+                listSessionsAPI(responseResult.nextPageToken, allSessionsData);
+              } else {
+                setSessionsList(allSessionsData);
+                setIsLoading(false);
+              }
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error listing Sessions', err);
+          toast.error('Failed to fetch sessions');
+        });
+    }
+  };
+
+  const handleDeleteSession = (session: string) => {
+    setSelectedSessionValue(session);
+    setDeletePopupOpen(true);
+  };
+  const handleCancelDelete = () => {
+    setDeletePopupOpen(false);
+  };
+
+  const handleDelete = async () => {
+    await deleteSessionAPI(selectedSessionValue);
+    setDeletePopupOpen(false);
+  };
+  const {
+    getTableProps,
+    getTableBodyProps,
+    headerGroups,
+    rows,
+    prepareRow,
+    state,
+    preGlobalFilteredRows,
+    setGlobalFilter,
+    page,
+    canPreviousPage,
+    canNextPage,
+    nextPage,
+    previousPage,
+    setPageSize,
+    state: { pageIndex, pageSize }
+  } = useTable(
+    { columns, data, autoResetPage: false, initialState: { pageSize: 50 } },
+    useGlobalFilter,
+    usePagination
+  );
+
+  useEffect(() => {
+    listSessionsAPI();
+    if (!detailedSessionView) {
+      pollingSessions(listSessionsAPI, pollingDisable);
+    }
+
+    return () => {
+      pollingSessions(listSessionsAPI, true);
+    };
+  }, [pollingDisable, detailedSessionView]);
+
+  const renderActions = (data: { state: ClusterStatus; name: string }) => {
+    /*
+      Extracting sessionId from sessionInfo
+      Example: "projects/{project}/locations/{location}/sessions/{name}"
+    */
+    let sessionValue = data.name.split('/')[5];
+
+    return (
+      <div className="actions-icon">
+        <div
+          role="button"
+          aria-disabled={data.state !== ClusterStatus.STATUS_ACTIVE}
+          className={
+            data.state === ClusterStatus.STATUS_ACTIVE
+              ? 'icon-buttons-style'
+              : 'icon-buttons-style-disable'
+          }
+          title="Terminate Session"
+          onClick={
+            data.state === ClusterStatus.STATUS_ACTIVE
+              ? () => terminateSessionAPI(sessionValue)
+              : undefined
+          }
+        >
+          {data.state === ClusterStatus.STATUS_ACTIVE ? (
+            <iconStop.react tag="div" />
+          ) : (
+            <iconStopDisable.react tag="div" />
+          )}
+        </div>
+        <div
+          role="button"
+          className="icon-buttons-style"
+          title="Delete Session"
+          onClick={() => handleDeleteSession(sessionValue)}
+        >
+          <iconDelete.react tag="div" />
+        </div>
+      </div>
+    );
+  };
+  const handleSessionDetails = (selectedName: string) => {
+    pollingSessions(listSessionsAPI, true);
+    setSessionSelected(selectedName);
+    setDetailedSessionView(true);
+  };
+
+  const tableDataCondition = (cell: ICellProps) => {
+    if (cell.column.Header === 'Session ID') {
+      return (
+        <td
+          role="button"
+          {...cell.getCellProps()}
+          className="cluster-name"
+          onClick={() => handleSessionDetails(cell.value)}
+        >
+          {cell.value}
+        </td>
+      );
+    }
+    if (cell.column.Header === 'Status') {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          <div key="Status" className="cluster-status-parent">
+            {cell.value === STATUS_FAIL && <iconClusterError.react tag="div" />}
+            {cell.value === STATUS_TERMINATED && (
+              <iconSucceeded.react tag="div" />
+            )}
+            {cell.value === STATUS_ACTIVE && <iconSucceeded.react tag="div" />}
+            {(cell.value === STATUS_PROVISIONING ||
+              cell.value === STATUS_CREATING ||
+              cell.value === STATUS_PENDING ||
+              cell.value === STATUS_TERMINATING ||
+              cell.value === STATUS_DELETING) && (
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={15}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+            )}
+            <div className="cluster-status">
+              {cell.value && cell.value.toLowerCase()}
+            </div>
+          </div>
+        </td>
+      );
+    } else {
+      return (
+        <td {...cell.getCellProps()} className="clusters-table-data">
+          {cell.render('Cell')}
+        </td>
+      );
+    }
+  };
+
+  return (
+    <div>
+      <ToastContainer />
+      {deletePopupOpen && (
+        <DeletePopup
+          onCancel={() => handleCancelDelete()}
+          onDelete={() => handleDelete()}
+          deletePopupOpen={deletePopupOpen}
+          DeleteMsg={
+            'This will delete ' +
+            selectedSessionValue +
+            ' and cannot be undone.'
+          }
+        />
+      )}
+      {detailedSessionView && (
+        <SessionDetails
+          sessionSelected={sessionSelected}
+          setDetailedSessionView={setDetailedSessionView}
+          detailedSessionView={detailedSessionView}
+        />
+      )}
+      {sessionsList.length > 0 && !detailedSessionView ? (
+        <div>
+          <div className="filter-cluster-overlay">
+            <div className="filter-cluster-icon">
+              <iconFilter.react tag="div" />
+            </div>
+            <div className="filter-cluster-text"></div>
+            <div className="filter-cluster-section">
+              <GlobalFilter
+                preGlobalFilteredRows={preGlobalFilteredRows}
+                globalFilter={state.globalFilter}
+                setGlobalFilter={setGlobalFilter}
+                setPollingDisable={setPollingDisable}
+              />
+            </div>
+          </div>
+          <div className="session-list-table-parent">
+            <TableData
+              getTableProps={getTableProps}
+              headerGroups={headerGroups}
+              getTableBodyProps={getTableBodyProps}
+              isLoading={isLoading}
+              rows={rows}
+              page={page}
+              prepareRow={prepareRow}
+              tableDataCondition={tableDataCondition}
+              fromPage="Sessions"
+            />
+            {sessionsList.length > 50 && (
+              <PaginationView
+                pageSize={pageSize}
+                setPageSize={setPageSize}
+                pageIndex={pageIndex}
+                allData={sessionsList}
+                previousPage={previousPage}
+                nextPage={nextPage}
+                canPreviousPage={canPreviousPage}
+                canNextPage={canNextPage}
+              />
+            )}
+          </div>
+        </div>
+      ) : (
+        <div>
+          {isLoading && (
+            <div className="spin-loaderMain">
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={18}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+              Loading Sessions
+            </div>
+          )}
+          {!isLoading && !detailedSessionView && (
+            <div className="no-data-style">No rows to display</div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default ListSessions;
diff --git a/src/sessions/sessionDetails.tsx b/src/sessions/sessionDetails.tsx
new file mode 100644
index 00000000..e5e2bf11
--- /dev/null
+++ b/src/sessions/sessionDetails.tsx
@@ -0,0 +1,401 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, { useState, useEffect, useRef } from 'react';
+import { LabIcon } from '@jupyterlab/ui-components';
+import LeftArrowIcon from '../../style/icons/left_arrow_icon.svg';
+import StopClusterIcon from '../../style/icons/stop_cluster_icon.svg';
+import StopClusterDisableIcon from '../../style/icons/stop_cluster_disable_icon.svg';
+import SucceededIcon from '../../style/icons/succeeded_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  NETWORK_KEY,
+  NETWORK_LABEL,
+  SERVICE_ACCOUNT_KEY,
+  SERVICE_ACCOUNT_LABEL,
+  STATUS_ACTIVE,
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_ERROR,
+  STATUS_FAIL,
+  STATUS_PROVISIONING,
+  STATUS_STARTING,
+  STATUS_STOPPING,
+  STATUS_TERMINATED,
+  STATUS_TERMINATING,
+  SUBNETWORK_KEY,
+  SUBNETWORK_LABEL
+} from '../utils/const';
+import { authApi, elapsedTime, jobTimeFormat } from '../utils/utils';
+import ClipLoader from 'react-spinners/ClipLoader';
+import ViewLogs from '../utils/viewLogs';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { terminateSessionAPI } from '../utils/sessionService';
+import PollingTimer from '../utils/pollingTimer';
+
+const iconLeftArrow = new LabIcon({
+  name: 'launcher:left-arrow-icon',
+  svgstr: LeftArrowIcon
+});
+const iconStopCluster = new LabIcon({
+  name: 'launcher:stop-cluster-icon',
+  svgstr: StopClusterIcon
+});
+const iconStopClusterDisable = new LabIcon({
+  name: 'launcher:stop-cluster-disable-icon',
+  svgstr: StopClusterDisableIcon
+});
+const iconSucceeded = new LabIcon({
+  name: 'launcher:succeeded-icon',
+  svgstr: SucceededIcon
+});
+
+const iconClusterError = new LabIcon({
+  name: 'launcher:cluster-error-icon',
+  svgstr: clusterErrorIcon
+});
+
+interface ISessionDetailsProps {
+  sessionSelected: string;
+  setDetailedSessionView: (value: boolean) => void;
+  detailedSessionView: boolean;
+}
+function SessionDetails({
+  sessionSelected,
+  setDetailedSessionView,
+  detailedSessionView
+}: ISessionDetailsProps) {
+  const [sessionInfo, setSessionInfo] = useState({
+    state: '',
+    name: '',
+    uuid: '',
+    elapsedTime: '',
+    createTime: '',
+    stateTime: '',
+    stateHistory: [{ stateStartTime: '' }],
+    runtimeConfig: { properties: [] },
+    environmentConfig: {
+      executionConfig: []
+    }
+  });
+  const [isLoading, setIsLoading] = useState(true);
+  const [labelDetail, setLabelDetail] = useState(['']);
+  const timer = useRef<NodeJS.Timeout | undefined>(undefined);
+
+  const pollingSessionDetails = async (
+    pollingFunction: () => void,
+    pollingDisable: boolean
+  ) => {
+    timer.current = PollingTimer(
+      pollingFunction,
+      pollingDisable,
+      timer.current
+    );
+  };
+
+  const handleDetailedView = () => {
+    pollingSessionDetails(getSessionDetails, true);
+    setDetailedSessionView(false);
+  };
+  const getSessionDetails = async () => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/sessions/${sessionSelected}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              setSessionInfo(responseResult);
+              const labelValue: string[] = [];
+              if (responseResult.labels) {
+                for (const [key, value] of Object.entries(
+                  responseResult.labels
+                )) {
+                  labelValue.push(`${key}:${value}`);
+                }
+              }
+              setLabelDetail(labelValue);
+              setIsLoading(false);
+            })
+            .catch((e: Error) => {
+              console.log(e);
+              setIsLoading(false);
+            });
+        })
+        .catch((err: Error) => {
+          setIsLoading(false);
+          console.error('Error loading session details', err);
+          toast.error(`Failed to fetch session details ${sessionSelected}`);
+        });
+    }
+  };
+
+  useEffect(() => {
+    getSessionDetails();
+    pollingSessionDetails(getSessionDetails, false);
+
+    return () => {
+      pollingSessionDetails(getSessionDetails, true);
+    };
+  }, []);
+
+  const createTimeDisplay = jobTimeFormat(sessionInfo.createTime);
+  const parts = createTimeDisplay.split(',');
+  const createTimeString = parts.slice(0, 2).join(',');
+  const sessionStartTime = new Date(sessionInfo.createTime);
+  let elapsedTimeString = '';
+  if (
+    sessionInfo.state === STATUS_TERMINATED ||
+    sessionInfo.state === STATUS_FAIL
+  ) {
+    const sessionStateTime = new Date(sessionInfo.stateTime); // Convert string to Date
+    elapsedTimeString = elapsedTime(sessionStateTime, sessionStartTime);
+  }
+  const sessionActiveTime =
+    sessionInfo.stateHistory &&
+    sessionInfo.stateHistory.length > 1 &&
+    sessionInfo.stateHistory[1].stateStartTime
+      ? new Date(sessionInfo.stateHistory[1].stateStartTime)
+      : '';
+  let runTimeString = '';
+  if (sessionActiveTime !== '') {
+    const sessionInfoStateTime = new Date(sessionInfo.stateTime);
+    runTimeString = elapsedTime(sessionInfoStateTime, sessionActiveTime);
+  }
+
+  return (
+    <div>
+      <ToastContainer />
+      {sessionInfo.name !== '' ? (
+        <div className="scroll-comp">
+          {detailedSessionView && (
+            <div>
+              <div className="cluster-details-header">
+                <div
+                  role="button"
+                  className="back-arrow-icon"
+                  onClick={() => handleDetailedView()}
+                >
+                  <iconLeftArrow.react tag="div" />
+                </div>
+                <div className="cluster-details-title">Session details</div>
+                <div
+                  role="button"
+                  className={
+                    sessionInfo.state === STATUS_ACTIVE
+                      ? 'action-cluster-section'
+                      : 'action-cluster-section disabled'
+                  }
+                  onClick={() =>
+                    sessionInfo.state === STATUS_ACTIVE &&
+                    terminateSessionAPI(sessionInfo.name.split('/')[5])
+                  }
+                >
+                  <div className="action-cluster-icon">
+                    {sessionInfo.state === STATUS_ACTIVE ? (
+                      <iconStopCluster.react tag="div" />
+                    ) : (
+                      <iconStopClusterDisable.react tag="div" />
+                    )}
+                  </div>
+                  <div className="action-cluster-text">TERMINATE</div>
+                </div>
+                <ViewLogs sessionInfo={sessionInfo} />
+              </div>
+              <div className="cluster-details-container">
+                <div className="row-details"></div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Name</div>
+                  <div className="session-details-value">
+                    {sessionInfo.name.split('/')[5]}
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">UUID</div>
+                  <div className="session-details-value">
+                    {sessionInfo.uuid}
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Status</div>
+                  <div className="session-detail-status-parent">
+                    {sessionInfo.state === STATUS_ACTIVE && (
+                      <iconSucceeded.react tag="div" />
+                    )}
+                    {sessionInfo.state === STATUS_TERMINATED && (
+                      <iconSucceeded.react tag="div" />
+                    )}
+                    {sessionInfo.state === STATUS_ERROR && (
+                      <iconClusterError.react tag="div" />
+                    )}
+                    {sessionInfo.state === STATUS_FAIL && (
+                      <iconClusterError.react tag="div" />
+                    )}
+                    {(sessionInfo.state === STATUS_PROVISIONING ||
+                      sessionInfo.state === STATUS_CREATING ||
+                      sessionInfo.state === STATUS_STARTING ||
+                      sessionInfo.state === STATUS_STOPPING ||
+                      sessionInfo.state === STATUS_TERMINATING ||
+                      sessionInfo.state === STATUS_DELETING) && (
+                      <div>
+                        <ClipLoader
+                          color="#8A8A8A"
+                          loading={true}
+                          size={15}
+                          aria-label="Loading Spinner"
+                          data-testid="loader"
+                        />
+                      </div>
+                    )}
+                    <div className="cluster-status">
+                      {sessionInfo.state === STATUS_CREATING
+                        ? STATUS_PROVISIONING
+                        : sessionInfo.state.toLowerCase()}
+                    </div>
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Create time</div>
+                  <div className="session-details-value">
+                    {createTimeString}
+                  </div>
+                </div>
+              </div>
+              <div className="cluster-details-header">
+                <div className="cluster-details-title">Details</div>
+              </div>
+              <div className="cluster-details-container">
+                <div className="row-details">
+                  <div className="cluster-details-label">Elapsed time</div>
+                  <div className="session-details-value">
+                    {elapsedTimeString}
+                  </div>
+                </div>
+                {(sessionInfo.state === STATUS_ACTIVE ||
+                  sessionInfo.state === STATUS_TERMINATED) && (
+                  <div className="row-details">
+                    <div className="cluster-details-label">Run time</div>
+                    <div className="session-details-value">{runTimeString}</div>
+                  </div>
+                )}
+                <div className="row-details">
+                  <div className="cluster-details-label">Properties</div>
+                </div>
+                {Object.entries(sessionInfo.runtimeConfig.properties).map(
+                  ([key, value]) => (
+                    <div className="row-details" key={key}>
+                      <div className="session-details-label">{key}</div>
+                      <div className="session-details-value">{value}</div>
+                    </div>
+                  )
+                )}
+                <div className="row-details">
+                  <div className="cluster-details-label">
+                    Environment config
+                  </div>
+                </div>
+                <div className="row-details">
+                  <div className="session-details-label">Execution config</div>
+                </div>
+                {Object.entries(
+                  sessionInfo.environmentConfig.executionConfig
+                ).map(([key, value]) => {
+                  let label;
+                  if (key === SERVICE_ACCOUNT_KEY) {
+                    label = SERVICE_ACCOUNT_LABEL;
+                  } else if (key === NETWORK_KEY) {
+                    label = NETWORK_LABEL;
+                  } else if (key === SUBNETWORK_KEY) {
+                    label = SUBNETWORK_LABEL;
+                  } else {
+                    label = '';
+                  }
+                  if (
+                    key === SERVICE_ACCOUNT_KEY ||
+                    key === NETWORK_KEY ||
+                    key === SUBNETWORK_KEY
+                  ) {
+                    return (
+                      <div className="row-details" key={key}>
+                        <div className="session-env-details-label">{label}</div>
+                        <div className="session-env-details-value">{value}</div>
+                      </div>
+                    );
+                  }
+                })}
+                <div className="row-details">
+                  <div className="session-env-details-label">Network tags</div>
+                  <div className="session-env-details-value"></div>
+                </div>
+                <div className="row-details">
+                  <div className="cluster-details-label">Labels</div>
+                  <div className="session-label-style-parent">
+                    {labelDetail.length > 0
+                      ? labelDetail.map(label => {
+                          /*
+                            Extracting key, value from label
+                               Example: "{client:dataproc_plugin}"
+                         */
+                          const labelParts = label.split(':');
+
+                          return (
+                            <div key={label} className="job-label-style">
+                              {labelParts[0]} : {labelParts[1]}
+                            </div>
+                          );
+                        })
+                      : 'None'}
+                  </div>
+                </div>
+              </div>
+            </div>
+          )}
+        </div>
+      ) : (
+        <div className="loader-full-style">
+          {isLoading && (
+            <div className="session-loader">
+              <ClipLoader
+                color="#8A8A8A"
+                loading={true}
+                size={20}
+                aria-label="Loading Spinner"
+                data-testid="loader"
+              />
+              Loading Session Details
+            </div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default SessionDetails;
diff --git a/src/svg.d.ts b/src/svg.d.ts
new file mode 100644
index 00000000..c0926f98
--- /dev/null
+++ b/src/svg.d.ts
@@ -0,0 +1,21 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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.
+ */
+
+declare module '*.svg' {
+    const value: string;
+    export default value;
+}
\ No newline at end of file
diff --git a/src/utils/batchService.tsx b/src/utils/batchService.tsx
new file mode 100644
index 00000000..dcd93a91
--- /dev/null
+++ b/src/utils/batchService.tsx
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 {
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  API_HEADER_BEARER
+} from '../utils/const';
+import { authApi } from '../utils/utils';
+import { toast } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+
+export const deleteBatchAPI = async (selectedBatch: string) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/batches/${selectedBatch}`,
+      {
+        method: 'DELETE',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        response
+          .json()
+          .then((responseResult: Response) => {
+            console.log(responseResult);
+          })
+          .catch((e: Error) => console.log(e));
+      })
+      .catch((err: Error) => {
+        console.error('Error deleting batches', err);
+        toast.error(`Failed to delete the batch ${selectedBatch}`);
+      });
+  }
+};
diff --git a/src/utils/clusterServices.tsx b/src/utils/clusterServices.tsx
new file mode 100644
index 00000000..44fa2d5b
--- /dev/null
+++ b/src/utils/clusterServices.tsx
@@ -0,0 +1,89 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { toast } from 'react-toastify';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL
+} from '../utils/const';
+import { authApi } from '../utils/utils';
+
+export const deleteClusterApi = async (selectedcluster: string) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${selectedcluster}`,
+      {
+        method: 'DELETE',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        response
+          .json()
+          .then((responseResult: Response) => {
+            console.log(responseResult);
+          })
+          .catch((e: Error) => console.log(e));
+      })
+      .catch((err: Error) => {
+        console.error('Error deleting cluster', err);
+      });
+  }
+};
+
+export const startStopAPI = async (
+  selectedcluster: string,
+  operation: 'start' | 'stop'
+) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${selectedcluster}:${operation}`,
+      {
+        method: 'POST',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        response
+          .json()
+          .then((responseResult: Response) => {
+            console.log(responseResult);
+          })
+          .catch((e: Error) => console.log(e));
+      })
+      .catch((err: Error) => {
+        console.error(`Error ${operation} cluster`, err);
+        toast.error(`Failed to ${operation} the cluster ${selectedcluster}`);
+      });
+  }
+};
+
+export const startClusterApi = async (selectedcluster: string) => {
+  startStopAPI(selectedcluster, 'start');
+};
+export const stopClusterApi = async (selectedcluster: string) => {
+  startStopAPI(selectedcluster, 'stop');
+};
diff --git a/src/utils/const.ts b/src/utils/const.ts
new file mode 100644
index 00000000..d250ebd6
--- /dev/null
+++ b/src/utils/const.ts
@@ -0,0 +1,128 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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.
+ */
+
+export const CREATE_CLUSTER_URL =
+  'https://console.cloud.google.com/dataproc/clusters';
+export const CREATE_BATCH_URL =
+  'https://console.cloud.google.com/dataproc/batches/create';
+export const BASE_URL = 'https://dataproc.googleapis.com/v1';
+export const BASE_URL_NETWORKS = 'https://compute.googleapis.com/compute/v1';
+export const BASE_URL_META = 'https://metastore.googleapis.com/v1';
+export const VIEW_LOGS_URL = 'https://console.cloud.google.com/logs';
+export const POLLING_TIME_LIMIT = 10000;
+export const API_HEADER_CONTENT_TYPE = 'application/json';
+export enum ClusterStatus {
+  STATUS_RUNNING = 'RUNNING',
+  STATUS_STOPPED = 'STOPPED',
+  STATUS_ACTIVE = 'ACTIVE'
+}
+export const STATUS_RUNNING = 'RUNNING';
+export const STATUS_ERROR = 'ERROR';
+export const STATUS_DONE = 'DONE';
+export const STATUS_CANCELLED = 'CANCELLED';
+export const STATUS_SUCCESS = 'SUCCEEDED';
+export const STATUS_FAIL = 'FAILED';
+export const STATUS_STOPPED = 'STOPPED';
+export const STATUS_STARTING = 'STARTING';
+export const STATUS_STOPPING = 'STOPPING';
+export const STATUS_DELETING = 'DELETING';
+export const STATUS_SETUP_DONE = 'SETUP_DONE';
+export const STATUS_CREATING = 'CREATING';
+export const STATUS_PENDING = 'PENDING';
+export const STATUS_PROVISIONING = 'Provisioning';
+export const STATUS_ACTIVE = 'ACTIVE';
+export const STATUS_TERMINATED = 'TERMINATED';
+export const STATUS_TERMINATING = 'TERMINATING';
+export const LABEL_TEXT = 'labels';
+export const API_HEADER_BEARER = 'Bearer ';
+export const SERVICE_ACCOUNT_KEY = 'serviceAccount';
+export const SERVICE_ACCOUNT_LABEL = 'Service account';
+export const NETWORK_KEY = 'networkUri';
+export const NETWORK_LABEL = 'Network';
+export const SUBNETWORK_KEY = 'subnetworkUri';
+export const SUBNETWORK_LABEL = 'Sub network';
+export const PROJECT_LIST_URL =
+  'https://cloudresourcemanager.googleapis.com/v1/projects';
+export const USER_INFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo';
+export const REGION_URL = 'https://compute.googleapis.com/compute/v1/projects/';
+export const LOGIN_STATE = '1';
+export const QUERY_TABLE = 'system=dataproc_metastore AND type=TABLE parent=';
+export const QUERY_DATABASE =
+  'system=dataproc_metastore AND type=DATABASE parent=';
+export const CATALOG_SEARCH =
+  'https://datacatalog.googleapis.com/v1/catalog:search';
+export const COLUMN_API = 'https://datacatalog.googleapis.com/v1/';
+export const MONTH_NAMES = [
+  'Jan',
+  'Feb',
+  'Mar',
+  'Apr',
+  'May',
+  'Jun',
+  'Jul',
+  'Aug',
+  'Sep',
+  'Oct',
+  'Nov',
+  'Dec'
+];
+export const QUERY_FILE_MESSAGE =
+  'Can be a GCS file with the gs:// prefix, an HDFS file on the cluster with the hdfs:// prefix, or a local file on the cluster with the file:// prefix';
+export const MAIN_CLASS_MESSAGE =
+  'The fully qualified name of a class in a provided or standard jar file, for example, com.example.wordcount, or a provided jar file to use the main class of that jar file';
+export const JAR_FILE_MESSAGE =
+  'Jar files are included in the CLASSPATH. Can be a GCS file with the gs:// prefix, an HDFS file on the cluster with the hdfs:// prefix, or a local file on the cluster with the file:// prefix.';
+export const FILES_MESSAGE =
+  'Files are included in the working directory of each executor. Can be a GCS file with the gs:// prefix, an HDFS file on the cluster with the hdfs:// prefix, or a local file on the cluster with the file:// prefix.';
+export const ARCHIVE_FILES_MESSAGE =
+  'Archive files are extracted in the Spark working directory. Can be a GCS file with the gs:// prefix, an HDFS file on the cluster with the hdfs:// prefix, or a local file on the cluster with the file:// prefix. Supported file types: .jar, .tar, .tar.gz, .tgz, .zip';
+export const ARGUMENTS_MESSAGE =
+  'Additional arguments to pass to the main class. Press Return after each argument.';
+export const MAX_RESTART_MESSAGE =
+  'Leave blank if you do not want to allow automatic restarts on job failure. ';
+export const VIEW_LOGS_CLUSTER_URL =
+  'https://console.cloud.google.com/logs/query;query=resource.type="cloud_dataproc_cluster" resource.labels.cluster_name=';
+export const VIEW_LOGS_SESSION_URL =
+  'https://console.cloud.google.com/logs/query;query=resource.type="cloud_dataproc_session"';
+export const VIEW_LOGS_BATCH_URL =
+  'https://console.cloud.google.com/logs/query;query=resource.type="cloud_dataproc_batch"';
+export const RESTART_JOB_URL =
+  'https://cloud.google.com/dataproc/docs/concepts/jobs/restartable-jobs';
+export const SELF_MANAGED_CLUSTER =
+  'https://cloud.google.com/dataproc-metastore/docs/attach-dataproc';
+export const SECURITY_KEY =
+  'https://console.cloud.google.com/security/kms/keyrings';
+export const SERVICE_ACCOUNT =
+  'https://cloud.google.com/compute/docs/access/service-accounts';
+export const CUSTOM_CONTAINERS =
+  'https://cloud.google.com/dataproc-serverless/docs/guides/custom-containers';
+
+export const CUSTOM_CONTAINERS_MESSAGE =
+  'Specify a custom container image to add Java or Python dependencies not provided by the default container image. You must host your custom container on Container Registry or Artifact Registry .';
+export const CONTAINER_REGISTERY = 'https://console.cloud.google.com/gcr';
+export const ARTIFACT_REGISTERY = 'https://console.cloud.google.com/artifacts';
+export const METASTORE_MESSAGE='We recommend this option to persist table metadata when the batch finishes processing. A metastore can be shared across many serverless batches in different projects and GCP regions.';
+export const CUSTOM_CONTAINER_MESSAGE=' Specify a custom container image to add Java or Python dependencies not provided by the default container image. You must host your custom container on';
+export const SPARK = 'Spark';
+export const SPARKSQL = 'SparkSQL';
+export const SPARKR = 'SparkR';
+export const PYSPARK = 'PySpark';
+export const DCU_HOURS = 3600000;
+export const GB_MONTHS = 2592000;
+export const TITLE_LAUNCHER_CATEGORY = 'Dataproc Jobs and Sessions';
+export const SPARK_HISTORY_SERVER = 'Spark History Server';
+export const DEFAULT_LABEL_DETAIL = 'client:dataproc-jupyter-plugin';
diff --git a/src/utils/deletePopup.tsx b/src/utils/deletePopup.tsx
new file mode 100644
index 00000000..a54f8351
--- /dev/null
+++ b/src/utils/deletePopup.tsx
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { Modal } from 'semantic-ui-react';
+
+function DeletePopup({ onCancel, onDelete, deletePopupOpen, DeleteMsg }: any) {
+  const modalStyle = {
+    width: 'auto', // Set the desired width
+    backgroundColor: 'white' // Set the desired background color
+  };
+  return (
+    <Modal open={deletePopupOpen} onClose={onCancel} style={modalStyle}>
+      <div className="popup-main">
+        <div className="popup-header">Confirm deletion</div>
+
+        <div className="popup-text">{DeleteMsg}</div>
+
+        <div className="popup-buttons">
+          <div role="button" className="popup-button-style" onClick={onCancel}>
+            CANCEL
+          </div>
+          <div role="button" className="popup-button-style" onClick={onDelete}>
+            DELETE
+          </div>
+        </div>
+      </div>
+    </Modal>
+  );
+}
+
+export default DeletePopup;
diff --git a/src/utils/errorPopup.tsx b/src/utils/errorPopup.tsx
new file mode 100644
index 00000000..75b17824
--- /dev/null
+++ b/src/utils/errorPopup.tsx
@@ -0,0 +1,39 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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, Modal } from 'semantic-ui-react';
+
+function ErrorPopup({ onCancel, errorPopupOpen, DeleteMsg }: any) {
+    const modalStyle = {
+        width: 'auto', // Set the desired width
+        backgroundColor: 'white' // Set the desired background color
+      };
+  return (
+    <Modal open={errorPopupOpen} onClose={onCancel} style={modalStyle}>
+    <Modal.Header>Error</Modal.Header>
+    <Modal.Content>
+      <p>{DeleteMsg}</p>
+    </Modal.Content>
+    <Modal.Actions>
+      <Button onClick={onCancel}>Close</Button>
+    </Modal.Actions>
+  </Modal>
+  );
+}
+
+export default ErrorPopup;
diff --git a/src/utils/globalFilter.tsx b/src/utils/globalFilter.tsx
new file mode 100644
index 00000000..f10e57cc
--- /dev/null
+++ b/src/utils/globalFilter.tsx
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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';
+
+function GlobalFilter({
+  globalFilter,
+  setGlobalFilter,
+  setPollingDisable
+}: any) {
+  const [value, setValue] = React.useState(globalFilter);
+  const onChange = (value: string) => {
+    setGlobalFilter(value || undefined);
+  };
+  if (value !== undefined) {
+    if (value.length !== 0) {
+      setPollingDisable(true);
+    } else {
+      setPollingDisable(false);
+    }
+  }
+
+  return (
+    <span>
+      <input
+        value={value || ''}
+        onChange={e => {
+          setValue(e.target.value);
+          onChange(e.target.value);
+        }}
+        placeholder={'Filter Table'}
+        aria-label="filterd value"
+        className="filter-section-part"
+      />
+    </span>
+  );
+}
+
+export default GlobalFilter;
diff --git a/src/utils/jobServices.tsx b/src/utils/jobServices.tsx
new file mode 100644
index 00000000..70d9270e
--- /dev/null
+++ b/src/utils/jobServices.tsx
@@ -0,0 +1,75 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { toast } from 'react-toastify';
+import { BASE_URL, API_HEADER_CONTENT_TYPE, API_HEADER_BEARER } from './const';
+import { authApi } from './utils';
+
+export const stopJobApi = async (jobId: string) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/jobs/${jobId}:cancel`,
+      {
+        method: 'POST',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        response
+          .json()
+          .then((responseResult: Response) => {
+            console.log(responseResult);
+          })
+          .catch((e: Error) => console.log(e));
+      })
+      .catch((err: Error) => {
+        console.error('Error to  stop job', err);
+        toast.error(`Failed to stop job ${jobId}`);
+      });
+  }
+};
+export const deleteJobApi = async (jobId: string) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/jobs/${jobId}`,
+      {
+        method: 'DELETE',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        response
+          .json()
+          .then((responseResult: Response) => {
+            console.log(responseResult);
+          })
+          .catch((e: Error) => console.log(e));
+      })
+      .catch((err: Error) => {
+        console.error('Error Deleting Job', err);
+        toast.error(`Failed to delete the job ${jobId}`);
+      });
+  }
+};
diff --git a/src/utils/paginationView.tsx b/src/utils/paginationView.tsx
new file mode 100644
index 00000000..bcd664c9
--- /dev/null
+++ b/src/utils/paginationView.tsx
@@ -0,0 +1,104 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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';
+
+interface IBatch {
+  batchID: string;
+  status: string;
+  location: string;
+  creationTime: string;
+  elapsedTime: string;
+  type: string;
+  actions: JSX.Element;
+}
+interface ICluster {
+  clusterName: string;
+  status: string;
+  clusterImage: string;
+  region: string;
+  zone: string;
+  totalWorkersNode: string;
+  schedulesDeletion: string;
+  actions: React.ReactNode;
+}
+interface IPaginationViewProps {
+  pageSize: number;
+  setPageSize: (value: number) => void;
+  pageIndex: number;
+  allData: IBatch[] | ICluster[];
+  previousPage: () => void;
+  nextPage:  () => void;
+  canPreviousPage: boolean;
+  canNextPage: boolean;
+}
+
+export const PaginationView = ({
+  pageSize,
+  setPageSize,
+  pageIndex,
+  allData,
+  previousPage,
+  nextPage,
+  canPreviousPage,
+  canNextPage
+}: IPaginationViewProps) => {
+  return (
+    <div className="pagination-parent-view">
+      <div>Rows per page: </div>
+      <select
+        className="page-size-selection"
+        value={pageSize}
+        onChange={e => {
+          setPageSize(Number(e.target.value));
+        }}
+      >
+        {[50, 100, 200].map(pageSize => (
+          <option key={pageSize} value={pageSize}>
+            {pageSize}
+          </option>
+        ))}
+      </select>
+      {(pageIndex + 1) * pageSize > allData.length ? (
+        <div className="page-display-part">
+          {pageIndex * pageSize + 1} -{' '} {allData.length} of {allData.length}
+        </div>
+      ) : (
+        <div className="page-display-part">
+          {pageIndex * pageSize + 1} -{' '} {(pageIndex + 1) * pageSize} of{' '}
+          {allData.length}
+        </div>
+      )}
+      <div role="button"
+        className={
+          !canPreviousPage ? 'page-move-button disabled' : 'page-move-button'
+        }
+        onClick={() => previousPage()}
+      >
+        {'<'}
+      </div>
+      <div role="button"
+        onClick={() => nextPage()}
+        className={
+          !canNextPage ? 'page-move-button disabled' : 'page-move-button'
+        }
+      >
+        {'>'}
+      </div>
+    </div>
+  );
+};
diff --git a/src/utils/pollingTimer.ts b/src/utils/pollingTimer.ts
new file mode 100644
index 00000000..218302f8
--- /dev/null
+++ b/src/utils/pollingTimer.ts
@@ -0,0 +1,16 @@
+import { POLLING_TIME_LIMIT } from './const';
+
+const PollingTimer = (
+  pollingFunction: () => void,
+  pollingDisable: boolean,
+  interval: NodeJS.Timeout | undefined
+) => {
+  if (pollingDisable) {
+    clearInterval(interval);
+  } else {
+    interval = setInterval(pollingFunction, POLLING_TIME_LIMIT);
+    return interval;
+  }
+};
+
+export default PollingTimer;
diff --git a/src/utils/sessionService.tsx b/src/utils/sessionService.tsx
new file mode 100644
index 00000000..75a15893
--- /dev/null
+++ b/src/utils/sessionService.tsx
@@ -0,0 +1,75 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 {
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  API_HEADER_BEARER
+} from '../utils/const';
+import { authApi } from '../utils/utils';
+import { toast } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+
+export const deleteSessionAPI = async (selectedSession: string) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/sessions/${selectedSession}`,
+      {
+        method: 'DELETE',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        console.log(response);
+      })
+      .catch((err: Error) => {
+        console.error('Error deleting session', err);
+        toast.error(`Failed to delete the session ${selectedSession}`);
+      });
+  }
+};
+export const terminateSessionAPI = async (selectedSession: string) => {
+  const credentials = await authApi();
+  if (credentials) {
+    fetch(
+      `${BASE_URL}/projects/${credentials.project_id}/locations/${credentials.region_id}/sessions/${selectedSession}:terminate`,
+      {
+        method: 'POST',
+        headers: {
+          'Content-Type': API_HEADER_CONTENT_TYPE,
+          Authorization: API_HEADER_BEARER + credentials.access_token
+        }
+      }
+    )
+      .then((response: Response) => {
+        response
+          .json()
+          .then((responseResult: Response) => {
+            console.log(responseResult);
+          })
+          .catch((e: Error) => console.log(e));
+      })
+      .catch((err: Error) => {
+        console.error('Error terminating session', err);
+        toast.error(`Failed to terminate session ${selectedSession}`);
+      });
+  }
+};
diff --git a/src/utils/statusDisplay.tsx b/src/utils/statusDisplay.tsx
new file mode 100644
index 00000000..34684836
--- /dev/null
+++ b/src/utils/statusDisplay.tsx
@@ -0,0 +1,79 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ClipLoader } from 'react-spinners';
+import {
+  STATUS_CREATING,
+  STATUS_DELETING,
+  STATUS_FAIL,
+  STATUS_PROVISIONING,
+  STATUS_RUNNING,
+  STATUS_STARTING,
+  STATUS_STOPPING,
+  STATUS_CANCELLED,
+  STATUS_PENDING,
+  STATUS_SUCCESS
+} from '../utils/const';
+import { LabIcon } from '@jupyterlab/ui-components';
+import stopIcon from '../../style/icons/stop_icon.svg';
+import clusterRunningIcon from '../../style/icons/cluster_running_icon.svg';
+import clusterErrorIcon from '../../style/icons/cluster_error_icon.svg';
+import SucceededIcon from '../../style/icons/succeeded_icon.svg';
+
+export const statusDisplay = (statusMsg: string) => {
+  const iconClusterRunning = new LabIcon({
+    name: 'launcher:cluster-running-icon',
+    svgstr: clusterRunningIcon
+  });
+  const iconClusterError = new LabIcon({
+    name: 'launcher:cluster-error-icon',
+    svgstr: clusterErrorIcon
+  });
+  const iconSucceeded = new LabIcon({
+    name: 'launcher:succeeded-icon',
+    svgstr: SucceededIcon
+  });
+  const iconStop = new LabIcon({
+    name: 'launcher:stop-icon',
+    svgstr: stopIcon
+  });
+
+  return (
+    <div className="cluster-detail-status-parent">
+      {statusMsg === STATUS_CANCELLED && <iconStop.react tag="div" />}
+      {statusMsg === STATUS_RUNNING && <iconClusterRunning.react tag="div" />}
+      {statusMsg === STATUS_SUCCESS && <iconSucceeded.react tag="div" />}
+      {statusMsg === STATUS_FAIL && <iconClusterError.react tag="div" />}
+      {(statusMsg === STATUS_PROVISIONING ||
+        statusMsg === STATUS_CREATING ||
+        statusMsg === STATUS_STARTING ||
+        statusMsg === STATUS_STOPPING ||
+        statusMsg === STATUS_PENDING ||
+        statusMsg === STATUS_DELETING) && (
+        <ClipLoader
+          color="#8A8A8A"
+          loading={true}
+          size={15}
+          aria-label="Loading Spinner"
+          data-testid="loader"
+        />
+      )}
+      <div className="cluster-status">{statusMsg.toLowerCase()}</div>
+    </div>
+  );
+};
diff --git a/src/utils/tableData.tsx b/src/utils/tableData.tsx
new file mode 100644
index 00000000..fcb8a7e1
--- /dev/null
+++ b/src/utils/tableData.tsx
@@ -0,0 +1,81 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { ClipLoader } from 'react-spinners';
+
+function TableData({
+  getTableProps,
+  headerGroups,
+  getTableBodyProps,
+  isLoading,
+  rows,
+  page,
+  prepareRow,
+  tableDataCondition,
+  fromPage
+}: any) {
+  const displayData = page ? page: rows
+  return (
+    <table {...getTableProps()} className="clusters-list-table">
+      <thead>
+        {headerGroups.map((headerGroup: any) => (
+          <tr
+            {...headerGroup.getHeaderGroupProps()}
+            className="cluster-list-table-header"
+          >
+            {headerGroup.headers.map((column: any) => (
+              <th
+                {...column.getHeaderProps()}
+                className="clusters-table-header"
+              >
+                {column.render('Header')}
+              </th>
+            ))}
+          </tr>
+        ))}
+      </thead>
+      <tbody {...getTableBodyProps()} className="clusters-table-body">
+        {isLoading ? (
+          <div className="spin-loader">
+            <ClipLoader
+              color="#8A8A8A"
+              loading={true}
+              size={18}
+              aria-label="Loading Spinner"
+              data-testid="loader"
+            />
+            Loading {fromPage}
+          </div>
+        ) : (
+          displayData.map((row: any) => {
+            prepareRow(row);
+            return (
+              <tr {...row.getRowProps()} className="cluster-list-data-parent">
+                {row.cells.map((cell: any) => {
+                  return tableDataCondition(cell);
+                })}
+              </tr>
+            );
+          })
+        )}
+      </tbody>
+    </table>
+  );
+}
+
+export default TableData;
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
new file mode 100644
index 00000000..1c25f67e
--- /dev/null
+++ b/src/utils/utils.ts
@@ -0,0 +1,270 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { requestAPI } from '../handler/handler';
+import {
+  DCU_HOURS,
+  GB_MONTHS,
+  PYSPARK,
+  SPARK,
+  SPARKR,
+  SPARKSQL,
+  STATUS_CREATING,
+  STATUS_DONE,
+  STATUS_ERROR,
+  STATUS_FAIL,
+  STATUS_PROVISIONING,
+  STATUS_SETUP_DONE,
+  STATUS_STARTING,
+  STATUS_SUCCESS
+} from './const';
+import pysparkLogo from '../../third_party/icons/pyspark_logo.svg';
+import pythonLogo from '../../third_party/icons/python_logo.svg';
+import sparkrLogo from '../../third_party/icons/sparkr_logo.svg';
+import scalaLogo from '../../third_party/icons/scala_logo.svg';
+import { LabIcon } from '@jupyterlab/ui-components';
+interface IAuthCredentials {
+  access_token?: string;
+  project_id?: string;
+  region_id?: string;
+  config_error?: number;
+  login_error?: number;
+}
+
+export const authApi = async (): Promise<IAuthCredentials | undefined> => {
+  try {
+    const data = await requestAPI('credentials');
+    if (typeof data === 'object' && data !== null) {
+      const credentials: IAuthCredentials = {
+        access_token: (data as { access_token: string }).access_token,
+        project_id: (data as { project_id: string }).project_id,
+        region_id: (data as { region_id: string }).region_id,
+        config_error: (data as { config_error: number }).config_error,
+        login_error: (data as { login_error: number }).login_error
+      };
+      return credentials;
+    } else {
+      console.error('Invalid data format.');
+    }
+  } catch (reason) {
+    console.error(`Error on GET credentials.\n${reason}`);
+  }
+};
+
+export const jobTimeFormat = (startTime: string) => {
+  const date = new Date(startTime);
+
+  const formattedDate = date.toLocaleString('en-US', {
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+    hour: 'numeric',
+    minute: 'numeric',
+    second: 'numeric',
+    hour12: true
+  });
+
+  return formattedDate;
+};
+interface IJobData {
+  [key: string]: any;
+}
+
+export const jobTypeValue = (data: IJobData): string | undefined => {
+  const result = Object.keys(data).filter(key => key.endsWith('Job'));
+  const jobTypeName = result[0].split('Job')[0];
+  switch (jobTypeName) {
+    case 'spark':
+      return SPARK;
+    case 'sparkR':
+      return SPARKR;
+    case 'sparkSql':
+      return SPARKSQL;
+    case 'pyspark':
+      return PYSPARK;
+    default:
+      return jobTypeName;
+  }
+};
+export const jobTypeValueArguments = (data: IJobData): string => {
+  const result = Object.keys(data).filter(key => key.endsWith('Job'));
+  return result[0].split('Job')[0];
+};
+
+export const BatchTypeValue = (data: IJobData): string | undefined => {
+  const result = Object.keys(data).filter(key => key.endsWith('Batch'));
+  const batchTypeName = result[0].split('Batch')[0];
+  switch (batchTypeName) {
+    case 'spark':
+      return SPARK;
+    case 'sparkR':
+      return SPARKR;
+    case 'sparkSql':
+      return SPARKSQL;
+    case 'pyspark':
+      return PYSPARK;
+    default:
+      return batchTypeName;
+  }
+};
+
+export const jobTypeDisplay = (data: string | undefined) => {
+  switch (data) {
+    case 'spark':
+      return 'Spark';
+    case 'sparkR':
+      return 'SparkR';
+    case 'sparkSql':
+      return 'Spark SQL';
+    case 'pyspark':
+      return 'PySpark';
+    case 'hive':
+      return 'Hive';
+    default:
+      return data;
+  }
+};
+
+export const elapsedTime = (endTime: Date, jobStartTime: Date): string => {
+  const jobEndTime = new Date(endTime);
+  const elapsedMilliseconds = jobEndTime.getTime() - jobStartTime.getTime();
+  const elapsedSeconds = Math.round(elapsedMilliseconds / 1000) % 60;
+  const elapsedMinutes = Math.floor(elapsedMilliseconds / (1000 * 60)) % 60;
+  const elapsedHours = Math.floor(elapsedMilliseconds / (1000 * 60 * 60));
+  let elapsedTimeString = '';
+  if (elapsedHours > 0) {
+    elapsedTimeString += `${elapsedHours} hr `;
+  }
+
+  if (elapsedMinutes > 0) {
+    elapsedTimeString += `${elapsedMinutes} min `;
+  }
+  if (elapsedSeconds > 0) {
+    elapsedTimeString += `${elapsedSeconds} sec `;
+  }
+  return elapsedTimeString;
+};
+
+export const statusMessage = (data: any) => {
+  if (data.status.state === STATUS_DONE) {
+    return STATUS_SUCCESS;
+  } else if (data.status.state === STATUS_ERROR) {
+    return STATUS_FAIL;
+  } else if (data.status.state === STATUS_SETUP_DONE) {
+    return STATUS_STARTING;
+  } else {
+    return data.status.state;
+  }
+};
+
+export const statusValue = (data: any) => {
+  if (data.status.state === STATUS_CREATING) {
+    return STATUS_PROVISIONING;
+  } else {
+    return data.status.state;
+  }
+};
+
+export const checkConfig = async (
+  setLoginState: React.Dispatch<React.SetStateAction<boolean>>,
+  setConfigError: React.Dispatch<React.SetStateAction<boolean>>,
+  setLoginError: React.Dispatch<React.SetStateAction<boolean>>
+): Promise<void> => {
+  const credentials = await authApi();
+  if (credentials) {
+    if (credentials.access_token === '') {
+      localStorage.removeItem('loginState');
+      if (credentials.config_error === 1) {
+        setConfigError(true);
+      }
+      if (credentials.login_error === 1) {
+        setLoginError(true);
+      }
+    } else {
+      setLoginState(true);
+    }
+  }
+};
+export const statusMessageBatch = (data: any) => {
+  if (data.state === STATUS_DONE) {
+    return STATUS_SUCCESS;
+  } else if (data.state === STATUS_ERROR) {
+    return STATUS_FAIL;
+  } else if (data.state === STATUS_SETUP_DONE) {
+    return STATUS_STARTING;
+  } else {
+    return data.state;
+  }
+};
+
+export const convertToDCUHours = (milliDcu: string) => {
+  return `~ ${(Number(milliDcu) / DCU_HOURS).toFixed(3)} DCU-hours`;
+};
+
+export const convertToGBMonths = (milliDcu: string) => {
+  return `~ ${(Number(milliDcu) / GB_MONTHS).toFixed(3)} GB-months`;
+};
+
+const iconPysparkLogo = new LabIcon({
+  name: 'launcher:pyspark-logo-icon',
+  svgstr: pysparkLogo
+});
+const iconPythonLogo = new LabIcon({
+  name: 'launcher:python-logo-icon',
+  svgstr: pythonLogo
+});
+const iconSparkRLogo = new LabIcon({
+  name: 'launcher:sparkr-logo-icon',
+  svgstr: sparkrLogo
+});
+const iconScalaLogo = new LabIcon({
+  name: 'launcher:scala-logo-icon',
+  svgstr: scalaLogo
+});
+
+export const iconDisplay = (kernelType: any) => {
+  if (
+    kernelType?.name.includes('spylon') ||
+    kernelType?.name.includes('apache')
+  ) {
+    return iconScalaLogo;
+  } else if (kernelType?.name.includes('ir')) {
+    return iconSparkRLogo;
+  } else if (
+    kernelType?.name.includes('pyspark') ||
+    kernelType?.resources.endpointParentResource.includes('/sessions')
+  ) {
+    return iconPysparkLogo;
+  } else {
+    return iconPythonLogo;
+  }
+};
+
+
+export interface ICellProps {
+  getCellProps: () => React.TdHTMLAttributes<HTMLTableDataCellElement>;
+  value: string | any;
+  column: {
+    Header: string;
+  };
+  row: {
+    original: {
+      status: string;
+    };
+  };
+  render: (value: string) => React.ReactNode;
+}
diff --git a/src/utils/viewLogs.tsx b/src/utils/viewLogs.tsx
new file mode 100644
index 00000000..5b0c38dd
--- /dev/null
+++ b/src/utils/viewLogs.tsx
@@ -0,0 +1,175 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { LabIcon } from '@jupyterlab/ui-components';
+import ViewLogsIcon from '../../style/icons/view_logs_icon.svg';
+import {
+  API_HEADER_BEARER,
+  API_HEADER_CONTENT_TYPE,
+  BASE_URL,
+  SPARK_HISTORY_SERVER,
+  VIEW_LOGS_BATCH_URL,
+  VIEW_LOGS_CLUSTER_URL,
+  VIEW_LOGS_SESSION_URL
+} from './const';
+import { authApi } from './utils';
+
+const iconViewLogs = new LabIcon({
+  name: 'launcher:view-logs-icon',
+  svgstr: ViewLogsIcon
+});
+
+function ViewLogs({
+  clusterInfo,
+  projectName,
+  clusterName,
+  setErrorView,
+  batchInfoResponse,
+  sessionInfo
+}: any) {
+  const handleJobDetailsViewLogs = async (clusterName: string) => {
+    const credentials = await authApi();
+    if (credentials) {
+      fetch(
+        `${BASE_URL}/projects/${credentials.project_id}/regions/${credentials.region_id}/clusters/${clusterName}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': API_HEADER_CONTENT_TYPE,
+            Authorization: API_HEADER_BEARER + credentials.access_token
+          }
+        }
+      )
+        .then((response: Response) => {
+          response
+            .json()
+            .then((responseResult: any) => {
+              if (responseResult.error && responseResult.error.code === 404) {
+                setErrorView(true);
+              } else {
+                window.open(
+                  responseResult.config.endpointConfig.httpPorts[
+                    SPARK_HISTORY_SERVER
+                  ]
+                );
+              }
+            })
+            .catch((e: Error) => {
+              console.error(e);
+            });
+        })
+        .catch((err: Error) => {
+          console.error('Error listing clusters Details', err);
+        });
+    }
+  };
+
+  return (
+    <>
+      <div
+        role="button"
+        className={
+          (batchInfoResponse?.runtimeInfo?.endpoints &&
+            batchInfoResponse?.runtimeInfo?.endpoints[SPARK_HISTORY_SERVER]) ||
+          (sessionInfo?.runtimeInfo?.endpoints &&
+            sessionInfo?.runtimeInfo?.endpoints[SPARK_HISTORY_SERVER]) ||
+          (clusterName) ||
+          (clusterInfo)
+            ? 'action-cluster-section'
+            : 'action-disabled action-cluster-section'
+        }
+        onClick={() => {
+          if (clusterInfo) {
+            window.open(
+              `${VIEW_LOGS_CLUSTER_URL}"${clusterInfo.clusterName}" resource.labels.cluster_uuid="${clusterInfo.clusterUuid}"?project=${projectName}`,
+              '_blank'
+            );
+          } else if (
+            batchInfoResponse &&
+            batchInfoResponse?.runtimeInfo?.endpoints &&
+            batchInfoResponse?.runtimeInfo?.endpoints[SPARK_HISTORY_SERVER]
+          ) {
+            window.open(
+              batchInfoResponse.runtimeInfo.endpoints[SPARK_HISTORY_SERVER],
+              '_blank'
+            );
+          } else if (
+            sessionInfo &&
+            sessionInfo?.runtimeInfo?.endpoints &&
+            sessionInfo?.runtimeInfo?.endpoints[SPARK_HISTORY_SERVER]
+          ) {
+            window.open(
+              sessionInfo.runtimeInfo.endpoints[SPARK_HISTORY_SERVER],
+              '_blank'
+            );
+          } else if (clusterName) {
+            handleJobDetailsViewLogs(clusterName);
+          }
+        }}
+      >
+        <div className="action-cluster-icon">
+          <iconViewLogs.react tag="div" />
+        </div>
+        {clusterInfo ? (
+          <div className="action-cluster-text">VIEW CLOUD LOGS</div>
+        ) : (
+          <div className="action-cluster-text">VIEW SPARK LOGS</div>
+        )}
+      </div>
+      <div
+        role="button"
+        className="action-cluster-section"
+        onClick={() => {
+          /*
+            Extracting project, location, session_id from sessionInfo.name
+            Example: "projects/{project}/locations/{location}/sessionTemplates/{session_id}"
+            */
+          const sessionValueUri = sessionInfo.name.split('/');
+
+          if (sessionInfo) {
+            window.open(
+              `${VIEW_LOGS_SESSION_URL} resource.labels.project_id="${sessionValueUri[1]}" resource.labels.location="${sessionValueUri[3]}" resource.labels.session_id="${sessionValueUri[5]}";cursorTimestamp=${sessionInfo.createTime};?project=${sessionValueUri[1]}`,
+              '_blank'
+            );
+          } else {
+            /*
+            Extracting project, location, batch_id from batchInfoResponse.name 
+            Example: "projects/{project}/locations/{location}/batches/{batch_id}"
+            */
+            const batchValueUri = batchInfoResponse.name.split('/');
+
+            window.open(
+              `${VIEW_LOGS_BATCH_URL} resource.labels.project_id="${batchValueUri[1]}" resource.labels.location="${batchValueUri[3]}" resource.labels.batch_id="${batchValueUri[5]}";cursorTimestamp=${batchInfoResponse.createTime};?project=${batchValueUri[1]}`,
+              '_blank'
+            );
+          }
+        }}
+      >
+        {!clusterInfo && !clusterName && (
+          <>
+            <div className="action-cluster-icon">
+              <iconViewLogs.react tag="div" />
+            </div>
+            <div className="action-cluster-text">VIEW CLOUD LOGS</div>
+          </>
+        )}
+      </div>
+    </>
+  );
+}
+export default ViewLogs;
diff --git a/style/authLogin.css b/style/authLogin.css
new file mode 100644
index 00000000..a809eadf
--- /dev/null
+++ b/style/authLogin.css
@@ -0,0 +1,258 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+.signin-google-icon {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  margin-top: -20px;
+  cursor: pointer;
+}
+.signin-google-icon:disabled {
+  opacity: 0.5;
+}
+.settings-overlay {
+  width: 100%;
+  height: 80px;
+  display: flex;
+  flex-direction: row;
+  align-items: flex-end;
+  padding-bottom: 10px;
+}
+
+.settings-text {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 18px;
+  line-height: 22px;
+  color: #000000;
+  padding-left: 10px;
+  padding-bottom: 5px;
+}
+
+.settings-icon {
+  padding-left: 70px;
+}
+.settings-seperator {
+  border: 1px solid #e0e0e0;
+  width: 100%;
+  height: 0px;
+  margin-left: 70px;
+}
+.configForm {
+  display: flex;
+  flex-direction: column;
+  padding-left: 70px;
+  padding-top: 60px;
+  width: 100%;
+  width: 630px;
+}
+.project-overlay {
+  position: relative;
+}
+.region-overlay {
+  position: relative;
+  margin-top: 50px;
+}
+.project-text {
+  width: 60px;
+  height: 13px;
+  font-style: normal;
+  font-weight: 1000;
+  font-size: 12px;
+  line-height: 13px;
+  display: flex;
+  align-items: center;
+  color: #000000;
+  position: absolute;
+  background-color: #fff;
+  bottom: 32px;
+  padding-left: 2px;
+  left: 7px;
+}
+
+.project-select {
+  box-sizing: border-box;
+  background: #ffffff !important;
+  border: 1px solid rgba(0, 0, 0, 0.38) !important;
+  display: block !important;
+  height: 36px;
+  width: 100%;
+  border-radius: 4px !important;
+  appearance: none !important;
+  -webkit-appearance: none !important;
+  -moz-appearance: none;
+  position: unset !important;
+  transform: none !important;
+}
+
+.region-text {
+  height: 13px;
+  font-style: normal;
+  font-weight: 1000;
+  font-size: 12px;
+  line-height: 13px;
+  display: flex;
+  align-items: center;
+  color: #000000;
+  position: absolute;
+  background-color: #fff;
+  padding-right: 4px;
+  bottom: 34px;
+  padding-left: 2px;
+  left: 7px;
+}
+
+.region-select {
+  box-sizing: border-box;
+  background: #ffffff !important;
+  border: 1px solid rgba(0, 0, 0, 0.38) !important;
+  display: block !important;
+  height: 40px;
+  width: 100%;
+  border-radius: 4px !important;
+  appearance: none !important;
+  -webkit-appearance: none !important;
+  -moz-appearance: none;
+  transform: none !important;
+  position: unset !important;
+}
+.region-loader {
+  margin-left: 1px;
+  margin-top: 160px;
+}
+.save-overlay {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+  padding: 32px 0px 0px;
+  gap: 12px;
+  height: 64px;
+}
+.save-button {
+  height: 32px;
+  background: #3367d6;
+  box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.12), 0px 2px 2px rgba(0, 0, 0, 0.2),
+    0px 1px 1px rgba(0, 0, 0, 0.14);
+  border-radius: 4px;
+  flex: none;
+  width: 65px;
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 32px;
+  align-items: center;
+  text-align: center;
+  text-transform: uppercase;
+  color: #ffffff;
+  border: none;
+  cursor: pointer;
+}
+.save-button:disabled {
+  background: lightgrey;
+  color: black;
+}
+.save-loader {
+  margin-top: 5px;
+}
+.user-info-card {
+  box-sizing: border-box;
+  width: 344px;
+  height: fit-content;
+  background: #ffffff;
+  border: 1px solid #b0b0b0;
+  border-radius: 27px;
+  margin-left: 21%;
+  margin-top: 30px;
+  display: flex;
+  flex-direction: column;
+  margin-right: 25px;
+}
+.user-image-overlay {
+  width: 63px;
+  height: 62px;
+  border-radius: 33.5px;
+}
+.user-image {
+  height: 62px;
+  border-radius: 33.5px;
+}
+.config-overlay {
+  display: flex;
+  flex-direction: row;
+}
+.google-header {
+  align-items: center;
+  justify-content: center;
+  padding-top: 8px;
+  padding-bottom: 8px;
+  display: flex;
+  font-size: 12px;
+}
+.seperator {
+  width: 344px;
+  height: 0px;
+  left: 1316.5px;
+  top: 217px;
+  border: 1px solid #e1e3e1;
+}
+.user-overlay {
+  display: flex;
+  padding: 30px 30px;
+}
+
+.google-footer {
+  width: 344px;
+  height: 0px;
+  border: 1px solid #e1e3e1;
+  text-align: center;
+}
+.user-details {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 12px;
+  line-height: 17px;
+  color: #000000;
+  padding-left: 30px;
+  padding-top: 20px;
+  justify-content: center;
+  align-items: center;
+  overflow-wrap: anywhere;
+}
+.add-account {
+  display: flex;
+  justify-content: center;
+  height: 32px;
+}
+.account-icon {
+  padding-left: 20px;
+  margin-top: 5px;
+}
+.account-dropdown {
+  width: 250px;
+  height: 32px;
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 32px;
+  display: flex;
+  align-items: center;
+  text-align: center;
+  text-transform: uppercase;
+  color: #1f1f1f;
+  border: none;
+}
+.footer-text {
+  padding-top: 15px;
+}
+
+.footer-divider {
+  margin: 0 5px;
+}
+.ui.selection.active.dropdown .menu {
+  border: none !important;
+}
+.ui.dropdown .menu .selected.item,
+.ui.dropdown.selected {
+  background: rgb(230, 236, 250) !important;
+  color: rgb(28, 58, 169) !important;
+}
diff --git a/style/base.css b/style/base.css
new file mode 100644
index 00000000..8bcfc73f
--- /dev/null
+++ b/style/base.css
@@ -0,0 +1,81 @@
+/*
+    See the JupyterLab Developer Guide for useful CSS Patterns:
+
+    https://jupyterlab.readthedocs.io/en/stable/developer/css.html
+*/
+
+/*Hiding Notebook section cards with name Remote for Remote kernels*/
+.jp-LauncherCard[data-category='Notebook'][title~='(Remote)'] {
+  display: none;
+}
+
+/*Hiding Console section cards with name Remote for Remote kernels*/
+.jp-LauncherCard[data-category='Console'][title~='(Remote)'] {
+  display: none;
+}
+
+/*Changing Icon for category with image pyspark*/
+.f1hgkb35 svg[data-icon="launcher:pyspark-logo-icon"] {
+  content-visibility: hidden;
+  display: block;
+  height: 32px;
+  margin: 0 auto;
+  width: 32px;
+  background-image: url('./icons/dataproc_icon.svg');
+}
+
+/*Changing Icon for category with image cluster*/
+.f1hgkb35 svg[data-icon="launcher:clusters-icon"] {
+  content-visibility: hidden;
+  display: block;
+  height: 32px;
+  margin: 0 auto;
+  width: 32px;
+  background-image: url('./icons/dataproc_icon.svg');
+}
+
+/*Changing Icon for category with image python*/
+.f1hgkb35 svg[data-icon="launcher:python-logo-icon"] {
+  content-visibility: hidden;
+  display: block;
+  height: 32px;
+  margin: 0 auto;
+  width: 32px;
+  background-image: url('./icons/dataproc_icon.svg');
+}
+
+/*Changing Icon for category with image sparkR*/
+.f1hgkb35 svg[data-icon="launcher:sparkr-logo-icon"] {
+  content-visibility: hidden;
+  display: block;
+  height: 32px;
+  margin: 0 auto;
+  width: 32px;
+  background-image: url('./icons/dataproc_icon.svg');
+}
+
+/*Changing Icon for category with image scala*/
+.f1hgkb35 svg[data-icon="launcher:scala-logo-icon"] {
+  content-visibility: hidden;
+  display: block;
+  height: 32px;
+  margin: 0 auto;
+  width: 32px;
+  background-image: url('./icons/dataproc_icon.svg');
+}
+
+h2 {
+  display: flex;
+  align-items: center;
+  margin: 15px 0px !important;
+}
+
+.ui.dropdown:not(.button)>.default.text {
+  color: rgba(0, 0, 0, 0.66) !important;
+}
+
+/* Highlight select dropdown field when focus */
+.ui.selection.active.dropdown:focus {
+  border: 3px solid #3367D6 !important;
+}
+
diff --git a/style/batches.css b/style/batches.css
new file mode 100644
index 00000000..fb339de9
--- /dev/null
+++ b/style/batches.css
@@ -0,0 +1,72 @@
+.batch-header-part {
+  display: flex;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.create-batch-overlay {
+  height: 48px;
+  padding-left: 15px;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+}
+
+.batch-details-label-level-two {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  padding-left: 30px;
+  color: #000000;
+  overflow-wrap: anywhere;
+}
+
+.batch-details-container {
+  width: 80%;
+  padding-left: 15px;
+}
+.batch-details-container-top {
+  width: 50%;
+  padding-left: 15px;
+}
+.details-label {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  color: #000000;
+}
+.batch-details-label-level-one {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  padding-left: 15px;
+  color: #000000;
+  overflow-wrap: anywhere;
+}
+.details-value {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 20px;
+  width: 35%;
+  color: rgba(0, 0, 0, 0.66);
+}
+.batch-details-row-label {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  display: flex;
+  justify-content: flex-start;
+  align-items: flex-start;
+  min-height: 18px;
+  margin-bottom: 6px;
+}
+.scroll-comp-batch{
+ 
+    overflow-y: auto;
+    max-height: 85vh;
+  
+}
\ No newline at end of file
diff --git a/style/cluster.css b/style/cluster.css
new file mode 100644
index 00000000..c81983e9
--- /dev/null
+++ b/style/cluster.css
@@ -0,0 +1,281 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+.clusters-list-overlay {
+  height: 37px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.selected-header {
+  cursor: pointer;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 8px 24px;
+
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 20px;
+  
+  text-transform: uppercase;
+
+  color: #3367D6;
+  box-shadow: inset 0px -2px 0px #3367D6;
+}
+
+.unselected-header {
+  cursor: pointer;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 8px 24px;
+
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 20px;
+  
+  text-transform: uppercase;
+
+  color: rgba(0, 0, 0, 0.66);
+}
+
+.create-cluster-overlay {
+  height: 48px;
+  padding-left: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+
+  display: flex;
+  align-items: center;
+}
+.create-cluster-sub-overlay{
+  width:20%;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+}
+
+.cluster-status {
+  padding-left: 5px;
+  text-transform: capitalize;
+}
+
+ .actions-icon {
+  display: flex;
+}
+.cluster-status-parent{
+  display: flex;
+  align-items: center;
+  height: 29px;
+  color: rgba(0, 0, 0, 0.66);
+}
+
+.cluster-detail-status-parent{
+  display: flex;
+  align-items: center;
+  height: 29px;
+  color: rgba(0, 0, 0, 0.66);
+}
+
+.icon-buttons-style {
+  cursor: pointer;
+  padding-right: 5px;
+}
+
+.icon-buttons-style-disable{
+  /* cursor: not-allowed; */
+  padding-right: 5px;
+  color:rgb(245,245,245);
+  opacity: 0.5;
+}
+
+.spin-loader{
+  display: flex;
+  margin-top: 10px;
+  margin-left: 10px;
+}
+.spin-loaderMain{
+  display: flex;
+  margin-top: 20%;
+  margin-left: 50%;
+}
+
+.filter-cluster-overlay {
+  height: 42px;
+  padding-left: 15px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.filter-section-part {
+  border: 0px;
+  width: 100%;
+}
+
+.create-cluster-text {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 32px;
+  
+  display: flex;
+  align-items: center;
+  text-align: center;
+  text-transform: uppercase;
+
+  color: #3367D6;
+}
+
+.filter-cluster-text {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 20px;
+  
+  display: flex;
+  align-items: center;
+  text-align: center;
+}
+
+.create-cluster-icon {
+  display: flex;
+  height: 18px;
+  width: 18px;
+  color: #3367D6;
+}
+
+.filter-cluster-icon {
+  display: flex;
+  height: 18px;
+  width: 18px;
+}
+
+.filter-cluster-section {
+  width: 100%;
+  padding-left: 8px;
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 20px;
+  
+  color: #757575;
+}
+
+.clusters-list-table-parent {
+  height: 62vh;
+  overflow: auto;
+}
+
+.clusters-list-table {
+  width: 100%;
+  border-spacing: 0;
+}
+
+.cluster-list-table-header {
+  background: rgba(0, 0, 0, 0.04);
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  height: 29px;
+  line-height: 18px;
+  color: #000000;
+}
+
+.cluster-list-data-parent {
+  background: #ffffff;
+  height: 29px;
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  
+  color: rgba(0, 0, 0, 0.66);
+}
+
+.clusters-table-header {
+  text-align: left;
+  padding-left: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.clusters-table-body {
+  overflow-y: auto;
+  max-height: 55vh;
+}
+
+.clusters-table-data {
+  padding-left: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.clusters-table-data:nth-child(1) {
+  color: #3367D6;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.cluster-name {
+  color: #3367D6;
+  text-decoration: underline;
+  cursor: pointer;
+  height: 29px;
+  padding-left: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  align-items: center;
+}
+
+.clusters-table-data:nth-child(2) {
+  text-transform: capitalize;
+}
+
+.clusters-list-start-icons {
+  display: flex;
+  justify-content: center;
+}
+
+.clusters-list-start-buttons-padding {
+  padding-left: 3px;
+  padding-right: 3px;
+}
+
+.component-level{
+  max-height: 85vh;
+}
+.scroll-comp{
+  overflow-y: auto;
+  max-height: 79vh;
+}
+.login-error{
+  align-items: center;
+  justify-content: center;
+  display: flex;
+  font-size: 20px;
+  font-weight: 600;
+  padding: 50px;
+}
+.no-data-style {
+  margin: 10px 15px
+}
+
+.error-view-parent{
+  display: flex;
+  align-items: center;
+  padding: 0px 30px;
+  height: 50px;
+  background-color: rgb(251 233 231);
+  color: #000000;
+}
+
+.error-view-message-parent{
+  display: flex;
+  margin-left: 35%;
+}
+
+.error-view-message{
+  margin-left: 30px;
+}
diff --git a/style/clusterDetails.css b/style/clusterDetails.css
new file mode 100644
index 00000000..6402a61c
--- /dev/null
+++ b/style/clusterDetails.css
@@ -0,0 +1,120 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+.cluster-details-header {
+  height: 48px;
+  display: flex;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+.scrollable-content {
+  overflow: auto;
+  max-height: calc(100vh - 48px);
+}
+
+.cluster-details-title {
+  padding: 0px 15px;
+  height: 48px;
+  font-style: normal;
+  font-weight: 400;
+  font-size: 18px;
+  line-height: 24px;
+  
+  display: flex;
+  align-items: center;
+  color: #000000;
+}
+
+.action-cluster-text {
+  padding-left: 5px;
+}
+
+.action-cluster-section.disabled {
+  color: rgba(0, 0, 0, 0.54);
+  cursor: context-menu;
+}
+
+.loader-full-style {
+  display: flex;
+  align-items: center;
+  text-align: center;
+  margin-top: 20%;
+  margin-left: 50%;
+}
+
+.back-arrow-icon {
+  padding-left: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+}
+
+.action-cluster-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.action-disabled {
+  opacity: 0.5;
+  cursor: context-menu !important;
+}
+.action-cluster-section {
+  cursor: pointer;
+  padding-left: 20px;
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 32px;
+  
+  display: flex;
+  align-items: center;
+  text-align: center;
+  color: #3367D6;
+}
+
+.cluster-details-container {
+  width: 50%;
+  padding-left: 15px;
+}
+
+.cluster-details-label {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  min-width: 35%;
+  color: #000000;
+  overflow-wrap: anywhere;
+}
+
+.cluster-details-value {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  color: rgba(0, 0, 0, 0.66);
+}
+
+.row-details {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  min-height: 18px;
+  margin-bottom: 6px;
+}
+.cluster-details-value-job {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  color: #3367D6;
+  text-decoration: underline;
+  cursor: pointer;
+  height: 20px;
+}
+
+.cluster-details-action-parent{
+  display: flex;
+}
\ No newline at end of file
diff --git a/style/createBatch.css b/style/createBatch.css
new file mode 100644
index 00000000..8611488d
--- /dev/null
+++ b/style/createBatch.css
@@ -0,0 +1,139 @@
+.create-batch-radio {
+  display: flex;
+  align-items: center;
+}
+.select-batch-radio-style {
+  margin-top: 10px;
+}
+.create-batch-message {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 10px;
+  padding-top: 10px;
+  padding-left: 10px;
+  color: #000000;
+}
+.create-batch-sub-message {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 10px;
+  line-height: 15px;
+  padding-left: 25px;
+  color: #575757;
+  width: 50%;
+  overflow: hidden;
+  word-wrap: break-word;
+  margin-top: 5px;
+  display: flex;
+}
+.create-batch-input {
+  width: 98%;
+  padding-left: 25px;
+  margin-top: 5px;
+}
+.create-batch-network {
+  display: flex;
+  width: 50%;
+}
+
+.select-sub-network-style {
+  background: #ffffff;
+  border: 1px solid black !important;
+  border-radius: 4px;
+  width: 50%;
+  margin-top: 10px;
+  min-height: 40px;
+  margin-left: 15px;
+}
+.select-primary-network-style {
+  background: #ffffff;
+  border: 1px solid black !important;
+  border-radius: 4px;
+  width: 50%;
+  margin-top: 10px;
+  min-height: 40px;
+}
+.create-batch-style {
+  background: #ffffff;
+  width: 50%;
+  margin-top: 10px;
+  min-height: 40px;
+  outline: none;
+}
+.create-batch-learn-more {
+  color: #7b1fa2;
+  text-decoration: underline;
+  cursor: pointer;
+  padding-left: 3px;
+}
+
+.create-messagelist {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 10px;
+  line-height: 15px;
+  padding-left: 5px;
+  color: #575757;
+  width: 50%;
+  overflow: hidden;
+  word-wrap: break-word;
+  display: flex;
+  margin-top: 5px;
+}
+.create-custom-messagelist {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 10px;
+  line-height: 15px;
+  padding-left: 5px;
+  color: #575757;
+  width: 50%;
+  overflow: hidden;
+  word-wrap: break-word;
+  margin-top: 5px;
+}
+.create-container-message{
+  display: flex;
+}
+.create-batch-network-message {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 10px;
+  padding-top: 13px;
+  padding-left: 5px;
+  color: #000000;
+  width: 50%;
+}
+
+.create-batches-message {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 10px;
+  padding-top: 13px;
+  padding-left: 5px;
+  color: #000000;
+  display: flex;
+}
+
+.ui.radio.checkbox input:checked~label:after{
+  background-color:#3367d6 !important;
+  border-color: #3367d6 !important;
+  border-width: 2px !important;
+}
+.ui.checkbox input:checked~label:before {
+  background: #fff;
+  border-color: grey;
+  border-width: 2px !important;
+}
+.ui.radio.checkbox label:before {
+  content: '';
+  transform: none;
+  width: 16px;
+  height: 16px;
+  border-radius: 500rem;
+  top: 1px;
+  left: 0;
+}
\ No newline at end of file
diff --git a/style/databaseInfo.css b/style/databaseInfo.css
new file mode 100644
index 00000000..d881c919
--- /dev/null
+++ b/style/databaseInfo.css
@@ -0,0 +1,87 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+.db-title{
+    width: 124px;
+    height: 28px;
+    font-size: 20px;
+    line-height: 28px;
+    font-weight: 600;
+    margin-top: 50px;
+    margin-left: 20px;
+}
+
+.title-overlay{
+  height: 37px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  padding-left: 20px;
+  font-size: 15px;
+  font-weight: 600;
+}
+.tr-row-even {
+    background-color: #F5F5F5;
+  }
+  
+  .tr-row-odd {
+    background-color: white;
+  }
+  .db-table {
+    width: 100%;
+    border-collapse: collapse; 
+  }
+  
+  .db-table td {
+    padding-left: 10px;
+    height: 29px;
+    width: 10px;
+    font-size: 13px;
+    line-height: 20px;
+  }
+  .table-container {
+    width: 100%;
+    overflow-y: auto;
+    margin-top: 25px;
+    margin-left: 20px;
+    padding-right: 30px;
+    display: flex;
+  }
+.db-table td:nth-child(2) {
+  width: 70%;
+}
+
+.db-table th {
+  padding-left: 10px;
+  text-align: left;
+  background-color: #F5F5F5;
+}
+ .schema-td {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+.schema-table {
+  width: 50%;
+  border-collapse: collapse;
+}
+
+.schema-table td {
+  padding-left: 10px; 
+  height: 29px;
+  font-size: 13px;
+  line-height: 18px;
+}
+.schema-table th {
+  padding-left: 10px;
+  text-align: left;
+  background-color: #F5F5F5;
+}
+.bold-column {
+  font-weight: 700; 
+}
+.table-info-overlay{
+  max-height: 100%;
+overflow-y: auto;
+overflow-x: hidden;
+}
+
diff --git a/style/dpms.css b/style/dpms.css
new file mode 100644
index 00000000..2d6f9af5
--- /dev/null
+++ b/style/dpms.css
@@ -0,0 +1,91 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+
+.dpms-title{
+    line-height: 20px;
+    width: 150px;
+    height: 20px;
+    margin-top: 19px;
+    margin-left: 20px;
+    font-weight: 550;
+    font-size: 15px;
+}
+.refresh-icon{
+    margin-left: 230px;
+    margin-top: -20px;
+    width: 20px;
+    cursor: pointer;
+}
+.search-icon {
+  position: relative;
+  top: 30px;
+  right: 220px;
+}
+.search-field{
+    border-radius: 4px !important;
+    border: solid 1px !important;
+    height: 36px !important;
+    margin-top: 20px !important;
+    margin-left: 20px !important;
+    width: 225px !important;
+    padding-left: 35px !important;
+}
+  .columns.icon{
+    margin-left: 20px;
+  }
+  div[role="treeitem"][aria-level="0"] > div {
+    display: flex;
+  }
+  
+  div[role="treeitem"][aria-level="0"] > div > div > div >svg {
+    margin-right: 5px;
+    margin-top:-2px;
+    cursor: pointer;
+  }
+
+  div[role="treeitem"][aria-level="1"] > div {
+    display: flex;
+  }
+  
+  div[role="treeitem"][aria-level="1"] > div >  div > div > svg {
+    margin-right: 10px;
+    cursor: pointer;
+  }
+  
+  div[role="treeitem"][aria-level="2"] > div {
+    display: flex;
+    margin-left: 20px;
+  }
+  
+  div[role="treeitem"][aria-level="2"] > div > div > svg {
+    margin-right: 10px;
+  }
+  div[role="treeitem"] > div > div:last-child {
+    cursor: pointer;
+  }
+  .db-icon{
+    pointer-events: none;
+  }
+  .table-icon{
+    pointer-events: none;
+  }
+  .caret-icon.right{
+  margin-top: -2px;
+  }
+  .dpms-error{
+    margin-top: 50px;
+    margin-left: 20px;
+    font-weight: 550;
+    font-size: 15px;
+  }
+  .tree-container {
+    height: 600px;
+    overflow-y: auto;
+    margin-left: 20px;
+    overflow-x: hidden;
+  }
+  .database-loader{
+    display: flex;
+    margin-top: 50px;
+    margin-left: 20px;
+  }
\ No newline at end of file
diff --git a/style/icons/add_account_icon.svg b/style/icons/add_account_icon.svg
new file mode 100644
index 00000000..e672d664
--- /dev/null
+++ b/style/icons/add_account_icon.svg
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391666 6.9 -9.53674e-07 8 -9.53674e-07C9.1 -9.53674e-07 10.0417 0.391666 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM1.19209e-07 16V13.2C1.19209e-07 12.6333 0.141667 12.1167 0.425 11.65C0.725 11.1667 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.65 4.75 9.4C5.81667 9.13333 6.9 9 8 9C9.1 9 10.1833 9.13333 11.25 9.4C12.3167 9.65 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2667 11.1667 15.55 11.65C15.85 12.1167 16 12.6333 16 13.2V16H1.19209e-07ZM2 14H14V13.2C14 13.0167 13.95 12.85 13.85 12.7C13.7667 12.55 13.65 12.4333 13.5 12.35C12.6 11.9 11.6917 11.5667 10.775 11.35C9.85833 11.1167 8.93333 11 8 11C7.06667 11 6.14167 11.1167 5.225 11.35C4.30833 11.5667 3.4 11.9 2.5 12.35C2.35 12.4333 2.225 12.55 2.125 12.7C2.04167 12.85 2 13.0167 2 13.2V14ZM8 6C8.55 6 9.01667 5.80833 9.4 5.425C9.8 5.025 10 4.55 10 4C10 3.45 9.8 2.98333 9.4 2.6C9.01667 2.2 8.55 2 8 2C7.45 2 6.975 2.2 6.575 2.6C6.19167 2.98333 6 3.45 6 4C6 4.55 6.19167 5.025 6.575 5.425C6.975 5.80833 7.45 6 8 6Z" fill="#444746"/>
+</svg>
diff --git a/style/icons/clone_icon.svg b/style/icons/clone_icon.svg
new file mode 100644
index 00000000..89fb7a2d
--- /dev/null
+++ b/style/icons/clone_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2582)">
+<path d="M7.5 12C7.08333 12 6.72917 11.8542 6.4375 11.5625C6.14583 11.2708 6 10.9167 6 10.5V4.5C6 4.08333 6.14583 3.72917 6.4375 3.4375C6.72917 3.14583 7.08333 3 7.5 3H13.5C13.9167 3 14.2708 3.14583 14.5625 3.4375C14.8542 3.72917 15 4.08333 15 4.5V10.5C15 10.9167 14.8542 11.2708 14.5625 11.5625C14.2708 11.8542 13.9167 12 13.5 12H7.5ZM7.5 4.5V10.5H13.5V4.5H7.5ZM4.5 15C4.08333 15 3.72917 14.8542 3.4375 14.5625C3.14583 14.2708 3 13.9167 3 13.5V7H4.5V13.5H11V15H4.5ZM7.5 4.5V10.5V4.5Z" fill="#444746"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2582">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/clone_icon_disable.svg b/style/icons/clone_icon_disable.svg
new file mode 100644
index 00000000..529aeacf
--- /dev/null
+++ b/style/icons/clone_icon_disable.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2582)">
+<path d="M7.5 12C7.08333 12 6.72917 11.8542 6.4375 11.5625C6.14583 11.2708 6 10.9167 6 10.5V4.5C6 4.08333 6.14583 3.72917 6.4375 3.4375C6.72917 3.14583 7.08333 3 7.5 3H13.5C13.9167 3 14.2708 3.14583 14.5625 3.4375C14.8542 3.72917 15 4.08333 15 4.5V10.5C15 10.9167 14.8542 11.2708 14.5625 11.5625C14.2708 11.8542 13.9167 12 13.5 12H7.5ZM7.5 4.5V10.5H13.5V4.5H7.5ZM4.5 15C4.08333 15 3.72917 14.8542 3.4375 14.5625C3.14583 14.2708 3 13.9167 3 13.5V7H4.5V13.5H11V15H4.5ZM7.5 4.5V10.5V4.5Z" fill="#8A8A8A"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2582">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/clone_job_icon.svg b/style/icons/clone_job_icon.svg
new file mode 100644
index 00000000..6fa78131
--- /dev/null
+++ b/style/icons/clone_job_icon.svg
@@ -0,0 +1,3 @@
+<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1C0 0.447715 0.447714 0 0.999999 0H8.93233V2H1.98496V10H0V1ZM4 3C3.44772 3 3 3.44772 3 4V13C3 13.5523 3.44772 14 4 14H11C11.5523 14 12 13.5523 12 13V4C12 3.44772 11.5523 3 11 3H4ZM5 12V5H10V12H5Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/clone_job_icon_disable.svg b/style/icons/clone_job_icon_disable.svg
new file mode 100644
index 00000000..a003ac8c
--- /dev/null
+++ b/style/icons/clone_job_icon_disable.svg
@@ -0,0 +1,3 @@
+<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1C0 0.447715 0.447714 0 0.999999 0H8.93233V2H1.98496V10H0V1ZM4 3C3.44772 3 3 3.44772 3 4V13C3 13.5523 3.44772 14 4 14H11C11.5523 14 12 13.5523 12 13V4C12 3.44772 11.5523 3 11 3H4ZM5 12V5H10V12H5Z" fill="#8A8A8A"/>
+</svg>
diff --git a/style/icons/cluster_error_icon.svg b/style/icons/cluster_error_icon.svg
new file mode 100644
index 00000000..3435bc39
--- /dev/null
+++ b/style/icons/cluster_error_icon.svg
@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.49219 0.5C11.3582 0.5 14.4922 3.63401 14.4922 7.5C14.4922 11.366 11.3582 14.5 7.49219 14.5C3.62619 14.5 0.492188 11.366 0.492188 7.5C0.492188 3.63401 3.62619 0.5 7.49219 0.5ZM8.49219 8.5V3.5H6.49219V8.5H8.49219ZM8.49219 9.5V11.5H6.49219V9.5H8.49219Z" fill="#D50000"/>
+</svg>
diff --git a/style/icons/cluster_icon.svg b/style/icons/cluster_icon.svg
new file mode 100644
index 00000000..43e77fca
--- /dev/null
+++ b/style/icons/cluster_icon.svg
@@ -0,0 +1,4 @@
+<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="52" height="52" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M30.2222 15.4444C30.2222 17.7763 28.3318 19.6666 25.9999 19.6666C23.6681 19.6666 21.7777 17.7763 21.7777 15.4444C21.7777 13.1125 23.6681 11.2222 25.9999 11.2222C28.3318 11.2222 30.2222 13.1125 30.2222 15.4444ZM15.4444 30.2222C17.7763 30.2222 19.6666 28.3318 19.6666 25.9999C19.6666 23.6681 17.7763 21.7777 15.4444 21.7777C13.1125 21.7777 11.2222 23.6681 11.2222 25.9999C11.2222 28.3318 13.1125 30.2222 15.4444 30.2222ZM36.5555 30.2222C38.8874 30.2222 40.7777 28.3318 40.7777 25.9999C40.7777 23.6681 38.8874 21.7777 36.5555 21.7777C34.2236 21.7777 32.3333 23.6681 32.3333 25.9999C32.3333 28.3318 34.2236 30.2222 36.5555 30.2222ZM25.9999 40.7777C28.3318 40.7777 30.2222 38.8874 30.2222 36.5555C30.2222 34.2236 28.3318 32.3333 25.9999 32.3333C23.6681 32.3333 21.7777 34.2236 21.7777 36.5555C21.7777 38.8874 23.6681 40.7777 25.9999 40.7777ZM29.1666 25.9999C29.1666 27.7488 27.7488 29.1666 25.9999 29.1666C24.251 29.1666 22.8333 27.7488 22.8333 25.9999C22.8333 24.251 24.251 22.8333 25.9999 22.8333C27.7488 22.8333 29.1666 24.251 29.1666 25.9999ZM17.5555 19.6666C18.7214 19.6666 19.6666 18.7214 19.6666 17.5555C19.6666 16.3896 18.7214 15.4444 17.5555 15.4444C16.3896 15.4444 15.4444 16.3896 15.4444 17.5555C15.4444 18.7214 16.3896 19.6666 17.5555 19.6666ZM19.6666 34.4444C19.6666 35.6103 18.7214 36.5555 17.5555 36.5555C16.3896 36.5555 15.4444 35.6103 15.4444 34.4444C15.4444 33.2785 16.3896 32.3333 17.5555 32.3333C18.7214 32.3333 19.6666 33.2785 19.6666 34.4444ZM34.4444 19.6666C35.6103 19.6666 36.5555 18.7214 36.5555 17.5555C36.5555 16.3896 35.6103 15.4444 34.4444 15.4444C33.2785 15.4444 32.3333 16.3896 32.3333 17.5555C32.3333 18.7214 33.2785 19.6666 34.4444 19.6666ZM36.5555 34.4444C36.5555 35.6103 35.6103 36.5555 34.4444 36.5555C33.2785 36.5555 32.3333 35.6103 32.3333 34.4444C32.3333 33.2785 33.2785 32.3333 34.4444 32.3333C35.6103 32.3333 36.5555 33.2785 36.5555 34.4444Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/cluster_running_icon.svg b/style/icons/cluster_running_icon.svg
new file mode 100644
index 00000000..2530a9a0
--- /dev/null
+++ b/style/icons/cluster_running_icon.svg
@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4922 7.5C14.4922 6.33973 14.2099 5.2454 13.7103 4.28193L12.1948 5.79737C12.3873 6.3288 12.4922 6.90215 12.4922 7.5C12.4922 10.2614 10.2536 12.5 7.49219 12.5C4.73076 12.5 2.49219 10.2614 2.49219 7.5C2.49219 4.73857 4.73076 2.5 7.49219 2.5V7.5L11.0277 3.96446L11.4922 3.5L12.4419 2.55025C11.1752 1.2835 9.42519 0.5 7.49219 0.5C3.62619 0.5 0.492188 3.634 0.492188 7.5C0.492188 11.366 3.62619 14.5 7.49219 14.5C11.3582 14.5 14.4922 11.366 14.4922 7.5Z" fill="#188038"/>
+</svg>
diff --git a/style/icons/columns_icon.svg b/style/icons/columns_icon.svg
new file mode 100644
index 00000000..26da1054
--- /dev/null
+++ b/style/icons/columns_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> 
+<g clip-path="url(#clip0_3065_14754)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.375 4.125H2.625V13.875H6.375V4.125ZM7.125 4.125H10.875V13.875H7.125V4.125ZM11.625 4.125H15.375V13.875H11.625V4.125Z" fill="black" fill-opacity="0.66"/>
+</g>
+<defs>
+<clipPath id="clip0_3065_14754">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/create_cluster_icon.svg b/style/icons/create_cluster_icon.svg
new file mode 100644
index 00000000..c46cf96e
--- /dev/null
+++ b/style/icons/create_cluster_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12.25H1.75V1.75H7V0.25H1.75C0.9175 0.25 0.25 0.925 0.25 1.75V12.25C0.25 13.075 0.9175 13.75 1.75 13.75H12.25C13.075 13.75 13.75 13.075 13.75 12.25V7H12.25V12.25ZM8.5 0.25V1.75H11.1925L3.82 9.1225L4.8775 10.18L12.25 2.8075V5.5H13.75V0.25H8.5Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/database_icon.svg b/style/icons/database_icon.svg
new file mode 100644
index 00000000..123e9c60
--- /dev/null
+++ b/style/icons/database_icon.svg
@@ -0,0 +1,10 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3065_14721)">
+<path d="M12 21C9.48333 21 7.35 20.6167 5.6 19.85C3.86667 19.0667 3 18.1167 3 17V7C3 5.9 3.875 4.95833 5.625 4.175C7.39167 3.39167 9.51667 3 12 3C14.4833 3 16.6 3.39167 18.35 4.175C20.1167 4.95833 21 5.9 21 7V17C21 18.1167 20.125 19.0667 18.375 19.85C16.6417 20.6167 14.5167 21 12 21ZM12 9.025C13.4833 9.025 14.975 8.81667 16.475 8.4C17.975 7.96667 18.8167 7.50833 19 7.025C18.8167 6.54167 17.975 6.08333 16.475 5.65C14.9917 5.21667 13.5 5 12 5C10.4833 5 8.99167 5.21667 7.525 5.65C6.075 6.06667 5.23333 6.525 5 7.025C5.23333 7.525 6.075 7.98333 7.525 8.4C8.99167 8.81667 10.4833 9.025 12 9.025ZM12 14C12.7 14 13.375 13.9667 14.025 13.9C14.675 13.8333 15.2917 13.7417 15.875 13.625C16.475 13.4917 17.0333 13.3333 17.55 13.15C18.0833 12.9667 18.5667 12.7583 19 12.525V9.525C18.5667 9.75833 18.0833 9.96667 17.55 10.15C17.0333 10.3333 16.475 10.4917 15.875 10.625C15.2917 10.7417 14.675 10.8333 14.025 10.9C13.375 10.9667 12.7 11 12 11C11.3 11 10.6167 10.9667 9.95 10.9C9.28333 10.8333 8.65 10.7417 8.05 10.625C7.46667 10.4917 6.91667 10.3333 6.4 10.15C5.88333 9.96667 5.41667 9.75833 5 9.525V12.525C5.41667 12.7583 5.88333 12.9667 6.4 13.15C6.91667 13.3333 7.46667 13.4917 8.05 13.625C8.65 13.7417 9.28333 13.8333 9.95 13.9C10.6167 13.9667 11.3 14 12 14ZM12 19C12.7667 19 13.5417 18.9417 14.325 18.825C15.125 18.7083 15.8583 18.5583 16.525 18.375C17.1917 18.175 17.75 17.9583 18.2 17.725C18.65 17.475 18.9167 17.225 19 16.975V14.525C18.5667 14.7583 18.0833 14.9667 17.55 15.15C17.0333 15.3333 16.475 15.4917 15.875 15.625C15.2917 15.7417 14.675 15.8333 14.025 15.9C13.375 15.9667 12.7 16 12 16C11.3 16 10.6167 15.9667 9.95 15.9C9.28333 15.8333 8.65 15.7417 8.05 15.625C7.46667 15.4917 6.91667 15.3333 6.4 15.15C5.88333 14.9667 5.41667 14.7583 5 14.525V17C5.08333 17.25 5.34167 17.4917 5.775 17.725C6.225 17.9583 6.78333 18.175 7.45 18.375C8.11667 18.5583 8.85 18.7083 9.65 18.825C10.45 18.9417 11.2333 19 12 19Z" fill="#444746"/>
+</g>
+<defs>
+<clipPath id="clip0_3065_14721">
+<rect width="24" height="24" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/database_widget_icon.svg b/style/icons/database_widget_icon.svg
new file mode 100644
index 00000000..69464f03
--- /dev/null
+++ b/style/icons/database_widget_icon.svg
@@ -0,0 +1,11 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2908_23113)">
+<rect width="18" height="18" fill="white"/>
+<path d="M9 15.3C7.2375 15.3 5.74375 15.0375 4.51875 14.5125C3.30625 13.9875 2.7 13.35 2.7 12.6V5.4C2.7 4.65 3.3125 4.0125 4.5375 3.4875C5.7625 2.9625 7.25 2.7 9 2.7C10.7375 2.7 12.2188 2.9625 13.4438 3.4875C14.6813 4.0125 15.3 4.65 15.3 5.4V12.6C15.3 13.35 14.6875 13.9875 13.4625 14.5125C12.2375 15.0375 10.75 15.3 9 15.3ZM9 6.75C10.075 6.75 11.1125 6.61875 12.1125 6.35625C13.1125 6.08125 13.725 5.7625 13.95 5.4C13.725 5.05 13.1063 4.7375 12.0938 4.4625C11.0938 4.1875 10.0625 4.05 9 4.05C7.925 4.05 6.88125 4.1875 5.86875 4.4625C4.86875 4.7375 4.2625 5.05 4.05 5.4C4.2625 5.7625 4.8625 6.08125 5.85 6.35625C6.85 6.61875 7.9 6.75 9 6.75ZM9 10.35C9.525 10.35 10.025 10.325 10.5 10.275C10.975 10.2125 11.4188 10.1312 11.8313 10.0312C12.2563 9.93125 12.6438 9.80625 12.9938 9.65625C13.3563 9.49375 13.675 9.3125 13.95 9.1125V7.06875C13.65 7.23125 13.3125 7.38125 12.9375 7.51875C12.575 7.64375 12.1813 7.75 11.7563 7.8375C11.3313 7.925 10.8875 7.99375 10.425 8.04375C9.9625 8.08125 9.4875 8.1 9 8.1C8.5 8.1 8.0125 8.08125 7.5375 8.04375C7.075 7.99375 6.63125 7.925 6.20625 7.8375C5.79375 7.75 5.4 7.64375 5.025 7.51875C4.6625 7.38125 4.3375 7.23125 4.05 7.06875V9.1125C4.325 9.3125 4.6375 9.49375 4.9875 9.65625C5.3375 9.80625 5.71875 9.93125 6.13125 10.0312C6.55625 10.1312 7.00625 10.2125 7.48125 10.275C7.96875 10.325 8.475 10.35 9 10.35ZM9 13.95C9.55 13.95 10.1 13.9125 10.65 13.8375C11.2125 13.75 11.725 13.6437 12.1875 13.5187C12.6625 13.3812 13.0563 13.2312 13.3688 13.0687C13.6938 12.9062 13.8875 12.7375 13.95 12.5625V10.6687C13.65 10.8312 13.3125 10.9812 12.9375 11.1187C12.575 11.2437 12.1813 11.35 11.7563 11.4375C11.3313 11.525 10.8875 11.5937 10.425 11.6437C9.9625 11.6812 9.4875 11.7 9 11.7C8.5 11.7 8.0125 11.6812 7.5375 11.6437C7.075 11.5937 6.63125 11.525 6.20625 11.4375C5.79375 11.35 5.4 11.2437 5.025 11.1187C4.6625 10.9812 4.3375 10.8312 4.05 10.6687V12.6C4.1125 12.7625 4.3 12.925 4.6125 13.0875C4.9375 13.25 5.33125 13.3937 5.79375 13.5187C6.26875 13.6437 6.7875 13.75 7.35 13.8375C7.9125 13.9125 8.4625 13.95 9 13.95Z" fill="#3B78E7"/>
+</g>
+<defs>
+<clipPath id="clip0_2908_23113">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/dataproc_icon.svg b/style/icons/dataproc_icon.svg
new file mode 100644
index 00000000..89f0df04
--- /dev/null
+++ b/style/icons/dataproc_icon.svg
@@ -0,0 +1,21 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_3409)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3562 19.5131L9.06505 28.9287L7.5093 26.237L23.8005 16.8213L25.3562 19.5131Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.00774 25.8615L9.00024 7.04547L11.9935 7.04407L12.0002 23.5971L9.00774 25.8615Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.103 12.3096L11.6797 8.52124L27.9792 17.9225L26.4259 20.6157L12.103 12.3096Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.8295 13.2219L11.6797 8.52124L12.103 12.3096L14.5156 13.6874C16.4101 14.3677 18.77 14.0119 19.8295 13.2219Z" fill="#3367D6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.8071 10.0613L9.8069 10.061C9.2795 9.1476 9.0004 8.1049 9 7.0453C8.9994 5.4316 9.627 3.9137 10.768 2.7718C11.909 1.6299 13.4262 1.0007 15.0402 1C17.1939 0.999001 19.2013 2.158 20.2791 4.0245C20.8065 4.9379 21.0854 5.9808 21.086 7.0402C21.0871 10.3725 18.3772 13.0846 15.0453 13.0858C12.8919 13.0863 10.8846 11.9275 9.8071 10.0613ZM17.5866 5.579C17.0627 4.6716 16.0874 4.1083 15.0416 4.1088C14.258 4.1093 13.5214 4.4147 12.9675 4.9691C12.4136 5.5235 12.1088 6.2604 12.109 7.0441C12.1093 7.5589 12.2443 8.0647 12.4993 8.5064C13.0232 9.4138 13.998 9.977 15.0443 9.9766C16.662 9.9762 17.9775 8.6593 17.9769 7.0413C17.9768 6.5268 17.8418 6.021 17.5866 5.579Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.00439 17.5057L9.00779 25.8615L12.0002 23.597L11.9995 20.9899C11.6524 19.282 10.2041 18.0406 9.00439 17.5057Z" fill="#3367D6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M18.122 23.6935L25.3569 19.5124L21.8246 17.9619L19.8294 19.116C18.7904 20.2079 17.9676 22.3548 18.122 23.6935Z" fill="#3367D6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0.809447 26.7189L0.808247 26.7168C-0.857153 23.8325 0.134247 20.1297 3.01805 18.4633C4.41535 17.6555 6.04385 17.4406 7.60325 17.8577C9.16265 18.2748 10.466 19.2741 11.2736 20.6716C12.9402 23.558 11.949 27.2606 9.06545 28.9273C6.18015 30.5947 2.47665 29.604 0.809447 26.7189ZM8.58255 22.2286C8.18975 21.5488 7.55685 21.0637 6.79975 20.861C6.04275 20.6586 5.25215 20.7631 4.57365 21.1552C3.17355 21.9643 2.69215 23.762 3.50085 25.1626L3.50135 25.1634C4.31085 26.5642 6.10905 27.0451 7.50985 26.2356C8.90975 25.4262 9.39115 23.6289 8.58255 22.2286Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.7282 26.1829C19.728 26.1826 19.728 26.1826 19.728 26.1826C18.6514 24.3179 18.6503 22.0015 19.7253 20.1381C20.5318 18.7399 21.8347 17.7395 23.3937 17.321C24.9527 16.9026 26.5811 17.1162 27.9793 17.9226C28.8986 18.4527 29.6636 19.2167 30.1919 20.1316C31.2685 21.9963 31.2696 24.3127 30.1945 26.1765C29.388 27.5747 28.0851 28.5751 26.5264 28.9934C24.9674 29.4118 23.3388 29.198 21.9405 28.3913C21.0213 27.8615 20.2563 27.0976 19.7282 26.1829ZM27.4994 21.6862C27.2438 21.2434 26.8726 20.8733 26.426 20.6158C25.0246 19.8074 23.2267 20.2901 22.4186 21.6913C21.8965 22.5965 21.8973 23.7217 22.4206 24.628C22.6762 25.0708 23.0474 25.4409 23.494 25.6984C24.1732 26.0902 24.9637 26.194 25.7205 25.9909C26.4775 25.7877 27.1097 25.3021 27.5014 24.6233C28.0233 23.7182 28.0227 22.5925 27.4994 21.6862Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9864 22.6326C11.9864 22.6326 8.23897 25.814 7.50977 26.2356L12.0727 23.5987C12.0676 23.2586 12.0497 22.9713 11.9864 22.6326Z" fill="#5C85DE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0359 12.7301C13.0359 12.7301 12.1011 7.55887 12.1008 7.04407L12.103 12.3097C12.4015 12.4746 12.7132 12.6162 13.0359 12.7301Z" fill="#5C85DE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M21.2841 18.3535C21.2841 18.3535 25.696 20.1948 26.426 20.6158L21.861 17.9828C21.567 18.1598 21.5438 18.1301 21.2841 18.3535Z" fill="#5C85DE"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_3409">
+<rect width="32" height="32" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/datasets_icon.svg b/style/icons/datasets_icon.svg
new file mode 100644
index 00000000..56716feb
--- /dev/null
+++ b/style/icons/datasets_icon.svg
@@ -0,0 +1,11 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2908_23634)">
+<rect width="18" height="18" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16 2H2V16H16V2ZM14 4H4V14H14V4ZM6 10H8V12H6V10ZM8 6H6V8H8V6ZM10 10H12V12H10V10ZM12 6H10V8H12V6Z" fill="#3B78E7"/>
+</g>
+<defs>
+<clipPath id="clip0_2908_23634">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/delete_cluster_icon.svg b/style/icons/delete_cluster_icon.svg
new file mode 100644
index 00000000..564d9a84
--- /dev/null
+++ b/style/icons/delete_cluster_icon.svg
@@ -0,0 +1,3 @@
+<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.23244 1H12V3H0V1H3.76756C4.11337 0.402199 4.75972 0 5.5 0H6.5C7.24028 0 7.88663 0.402199 8.23244 1ZM1 4H11V12C11 13.1046 10.1046 14 9 14H3C1.89543 14 1 13.1046 1 12V4Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/delete_icon.svg b/style/icons/delete_icon.svg
new file mode 100644
index 00000000..30ed59de
--- /dev/null
+++ b/style/icons/delete_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2584)">
+<path d="M4.78333 16.975C4.30972 16.975 3.89722 16.8069 3.54583 16.4708C3.20972 16.1194 3.04167 15.7069 3.04167 15.2333V3.775H2.03333V2.03333H6.525V1.025H11.475V2.03333H15.9667V3.775H14.9583V15.2333C14.9583 15.7069 14.7826 16.1194 14.4313 16.4708C14.0951 16.8069 13.6903 16.975 13.2167 16.975H4.78333ZM13.2167 3.775H4.78333V15.2333H13.2167V3.775ZM6.525 13.4917H8.26667V5.51667H6.525V13.4917ZM9.73333 13.4917H11.475V5.51667H9.73333V13.4917ZM4.78333 3.775V15.2333V3.775Z" fill="#444746"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2584">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/down_arrow_icon.svg b/style/icons/down_arrow_icon.svg
new file mode 100644
index 00000000..862a0c80
--- /dev/null
+++ b/style/icons/down_arrow_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3712_17225)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 6.75H4.5L9 11.25L13.5 6.75Z" fill="#575757"/>
+</g>
+<defs>
+<clipPath id="clip0_3712_17225">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/dpms_icon.svg b/style/icons/dpms_icon.svg
new file mode 100644
index 00000000..8ece658b
--- /dev/null
+++ b/style/icons/dpms_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2908_23656)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16 2H2V16H16V2ZM14 4H4V14H14V4ZM6 10H8V12H6V10ZM8 6H6V8H8V6ZM10 10H12V12H10V10ZM12 6H10V8H12V6Z" fill="black"/>
+</g>
+<defs>
+<clipPath id="clip0_2908_23656">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/edit_icon.svg b/style/icons/edit_icon.svg
new file mode 100644
index 00000000..f79d87fa
--- /dev/null
+++ b/style/icons/edit_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3526_17578)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.7756 4.02011C16.0748 4.32014 16.0748 4.80351 15.7756 5.10266L14.3711 6.50715L11.4928 3.62886L12.8973 2.22436C13.1965 1.92521 13.6799 1.92521 13.9799 2.22436L15.7756 4.02011ZM2 16V13.1208L10.4901 4.63157L13.3684 7.50986L4.87917 16H2Z" fill="#3367D6"/>
+</g>
+<defs>
+<clipPath id="clip0_3526_17578">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/edit_icon_disable.svg b/style/icons/edit_icon_disable.svg
new file mode 100644
index 00000000..1271fd65
--- /dev/null
+++ b/style/icons/edit_icon_disable.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3526_17578)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.7756 4.02011C16.0748 4.32014 16.0748 4.80351 15.7756 5.10266L14.3711 6.50715L11.4928 3.62886L12.8973 2.22436C13.1965 1.92521 13.6799 1.92521 13.9799 2.22436L15.7756 4.02011ZM2 16V13.1208L10.4901 4.63157L13.3684 7.50986L4.87917 16H2Z" fill="black"/>
+</g>
+<defs>
+<clipPath id="clip0_3526_17578">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
\ No newline at end of file
diff --git a/style/icons/error_icon.svg b/style/icons/error_icon.svg
new file mode 100644
index 00000000..55b52911
--- /dev/null
+++ b/style/icons/error_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2416_4781)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9 1.5C4.86 1.5 1.5 4.86 1.5 9C1.5 13.14 4.86 16.5 9 16.5C13.14 16.5 16.5 13.14 16.5 9C16.5 4.86 13.14 1.5 9 1.5ZM9.75 12.75H8.25V11.25H9.75V12.75ZM9.75 9.75H8.25V5.25H9.75V9.75Z" fill="#D50000"/>
+</g>
+<defs>
+<clipPath id="clip0_2416_4781">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/filter_icon.svg b/style/icons/filter_icon.svg
new file mode 100644
index 00000000..470d5abc
--- /dev/null
+++ b/style/icons/filter_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H14V2H0V0ZM2 4H12V6H2V4ZM10 8H4V10H10V8Z" fill="black"/>
+</svg>
diff --git a/style/icons/left_arrow_icon.svg b/style/icons/left_arrow_icon.svg
new file mode 100644
index 00000000..9f0c6e45
--- /dev/null
+++ b/style/icons/left_arrow_icon.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M20 11H7.83L13.42 5.41L12 4L4 12L12 20L13.41 18.59L7.83 13H20V11Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/plus_icon.svg b/style/icons/plus_icon.svg
new file mode 100644
index 00000000..e8aaf233
--- /dev/null
+++ b/style/icons/plus_icon.svg
@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7 0H5V5H0V7H5V12H7V7H12V5H7V0Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/plus_icon_disable.svg b/style/icons/plus_icon_disable.svg
new file mode 100644
index 00000000..b6e3ddc6
--- /dev/null
+++ b/style/icons/plus_icon_disable.svg
@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7 0H5V5H0V7H5V12H7V7H12V5H7V0Z" fill="#8A8A8A"/>
+</svg>
\ No newline at end of file
diff --git a/style/icons/refresh_cluster_icon.svg b/style/icons/refresh_cluster_icon.svg
new file mode 100644
index 00000000..464b74c9
--- /dev/null
+++ b/style/icons/refresh_cluster_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7 14C10.5262 14 13.4438 11.3921 13.9291 8H11.9C11.4368 10.2824 9.41913 12 7 12C4.2384 12 2 9.7616 2 7C2 4.2384 4.2384 2 7 2C8.3808 2 9.6308 2.5596 10.5356 3.4644L8 6H14V0L11.9497 2.05034C10.6829 0.783563 8.93287 0 7 0C3.13425 0 0 3.13425 0 7C0 10.8658 3.13425 14 7 14Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/refresh_icon.svg b/style/icons/refresh_icon.svg
new file mode 100644
index 00000000..ef2f2dd3
--- /dev/null
+++ b/style/icons/refresh_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7 14C10.5262 14 13.4438 11.3921 13.9291 8H11.9C11.4368 10.2824 9.41913 12 7 12C4.2384 12 2 9.7616 2 7C2 4.2384 4.2384 2 7 2C8.3808 2 9.6308 2.5596 10.5356 3.4644L8 6H14V0L11.9497 2.05034C10.6829 0.783563 8.93287 0 7 0C3.13425 0 0 3.13425 0 7C0 10.8658 3.13425 14 7 14Z" fill="black"/>
+</svg>
diff --git a/style/icons/restart_icon.svg b/style/icons/restart_icon.svg
new file mode 100644
index 00000000..b842a010
--- /dev/null
+++ b/style/icons/restart_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2377)">
+<path d="M8.12917 16.9521C6.3875 16.7229 4.93611 15.959 3.775 14.6604C2.61389 13.3465 2.03333 11.8035 2.03333 10.0312C2.03333 9.05347 2.21667 8.1368 2.58333 7.28125C2.96528 6.41042 3.49236 5.65417 4.16458 5.0125L5.40208 6.25C4.88264 6.73889 4.47778 7.31181 4.1875 7.96875C3.9125 8.61042 3.775 9.29792 3.775 10.0312C3.775 11.3146 4.1875 12.4451 5.0125 13.4229C5.8375 14.3854 6.87639 14.9736 8.12917 15.1875V16.9521ZM9.87083 16.9521V15.1875C11.1236 14.9736 12.1625 14.3854 12.9875 13.4229C13.8125 12.4451 14.225 11.3146 14.225 10.0312C14.225 8.57986 13.7132 7.35 12.6896 6.34167C11.6813 5.31806 10.4514 4.80625 9 4.80625H8.88542L9.91667 5.8375L8.70208 7.05208L5.58542 3.93542L8.70208 0.818749L9.91667 2.03333L8.90833 3.06458H9C10.9403 3.06458 12.5826 3.74444 13.9271 5.10417C15.2868 6.44861 15.9667 8.09097 15.9667 10.0312C15.9667 11.8035 15.3861 13.3465 14.225 14.6604C13.0639 15.959 11.6125 16.7229 9.87083 16.9521Z" fill="#444746"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2377">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/restart_icon_disable.svg b/style/icons/restart_icon_disable.svg
new file mode 100644
index 00000000..11362805
--- /dev/null
+++ b/style/icons/restart_icon_disable.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2377)">
+<path d="M8.12917 16.9521C6.3875 16.7229 4.93611 15.959 3.775 14.6604C2.61389 13.3465 2.03333 11.8035 2.03333 10.0312C2.03333 9.05347 2.21667 8.1368 2.58333 7.28125C2.96528 6.41042 3.49236 5.65417 4.16458 5.0125L5.40208 6.25C4.88264 6.73889 4.47778 7.31181 4.1875 7.96875C3.9125 8.61042 3.775 9.29792 3.775 10.0312C3.775 11.3146 4.1875 12.4451 5.0125 13.4229C5.8375 14.3854 6.87639 14.9736 8.12917 15.1875V16.9521ZM9.87083 16.9521V15.1875C11.1236 14.9736 12.1625 14.3854 12.9875 13.4229C13.8125 12.4451 14.225 11.3146 14.225 10.0312C14.225 8.57986 13.7132 7.35 12.6896 6.34167C11.6813 5.31806 10.4514 4.80625 9 4.80625H8.88542L9.91667 5.8375L8.70208 7.05208L5.58542 3.93542L8.70208 0.818749L9.91667 2.03333L8.90833 3.06458H9C10.9403 3.06458 12.5826 3.74444 13.9271 5.10417C15.2868 6.44861 15.9667 8.09097 15.9667 10.0312C15.9667 11.8035 15.3861 13.3465 14.225 14.6604C13.0639 15.959 11.6125 16.7229 9.87083 16.9521Z" fill="#8A8A8A"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2377">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/right_arrow_icon.svg b/style/icons/right_arrow_icon.svg
new file mode 100644
index 00000000..62ad16a2
--- /dev/null
+++ b/style/icons/right_arrow_icon.svg
@@ -0,0 +1,10 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3712_17139)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9 6L9 18L15 12L9 6Z" fill="black" fill-opacity="0.66"/>
+</g>
+<defs>
+<clipPath id="clip0_3712_17139">
+<rect width="24" height="24" fill="white" transform="translate(0 24) rotate(-90)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/search_icon.svg b/style/icons/search_icon.svg
new file mode 100644
index 00000000..415fc2b1
--- /dev/null
+++ b/style/icons/search_icon.svg
@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.76831 11.1645C8.97573 11.6924 8.02383 12 7 12C4.2383 12 2 9.7617 2 7C2 4.2383 4.2383 2 7 2C9.7617 2 12 4.2383 12 7C12 8.01451 11.698 8.95839 11.1789 9.74663L15.682 14.2497L14.2678 15.664L9.76831 11.1645ZM7 10C8.65685 10 10 8.65685 10 7C10 5.34315 8.65685 4 7 4C5.34315 4 4 5.34315 4 7C4 8.65685 5.34315 10 7 10Z" fill="black"/>
+</svg>
diff --git a/style/icons/serverless_icon.svg b/style/icons/serverless_icon.svg
new file mode 100644
index 00000000..e78642cd
--- /dev/null
+++ b/style/icons/serverless_icon.svg
@@ -0,0 +1,11 @@
+<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="52" height="52" fill="white"/>
+<g clip-path="url(#clip0_2788_15614)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M41.2396 23.229L36.8855 26.1318L41.2396 29.0346L43.4167 30.486L41.2396 31.9373L36.8855 34.8401L26 42.0971L15.1146 34.8401L10.7605 31.9373L8.58337 30.486L10.7605 29.0346L15.1049 26.1383L15.0894 26.115L10.7605 23.229L8.58337 21.7776L10.7605 20.3262L26 10.1665L41.2396 20.3262L43.4167 21.7776L41.2396 23.229ZM24.5487 14.0417V19.2892L16.8524 24.3875L12.9375 21.7776L24.5487 14.0417ZM27.4514 19.8473L34.7084 24.6804L39.0625 21.7776L27.4514 14.0417V19.8473ZM27.5834 38.1387V32.3332H30.75L26 25.9998L21.25 32.3332H24.4167V38.1387L12.9375 30.486L17.2917 27.5832L26 21.7786L34.7084 27.5832L39.0625 30.486L27.5834 38.1387Z" fill="#3367D6"/>
+</g>
+<defs>
+<clipPath id="clip0_2788_15614">
+<rect width="38" height="38" fill="white" transform="translate(7 7)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/settings_icon.svg b/style/icons/settings_icon.svg
new file mode 100644
index 00000000..e76ae1fe
--- /dev/null
+++ b/style/icons/settings_icon.svg
@@ -0,0 +1,14 @@
+<svg width="31" height="29" viewBox="0 0 31 29" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3562 18.5132L9.06505 27.9289L7.5093 25.2371L23.8005 15.8214L25.3562 18.5132Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.00774 24.8616L9.00024 6.04559L11.9935 6.04419L12.0002 22.5972L9.00774 24.8616Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.103 11.3096L11.6797 7.52124L27.9792 16.9225L26.4259 19.6157L12.103 11.3096Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.8295 12.2219L11.6797 7.52124L12.103 11.3096L14.5156 12.6874C16.4101 13.3677 18.77 13.0119 19.8295 12.2219Z" fill="#3367D6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.8071 9.0613L9.8069 9.061C9.2795 8.1476 9.0004 7.1049 9 6.0453C8.9994 4.4316 9.627 2.9137 10.768 1.7718C11.909 0.629901 13.4262 0.000700647 15.0402 6.46588e-07C17.1939 -0.000999353 19.2013 1.158 20.2791 3.0245C20.8065 3.9379 21.0854 4.9808 21.086 6.0402C21.0871 9.3725 18.3772 12.0846 15.0453 12.0858C12.8919 12.0863 10.8846 10.9275 9.8071 9.0613ZM17.5866 4.579C17.0627 3.6716 16.0874 3.1083 15.0416 3.1088C14.258 3.1093 13.5214 3.4147 12.9675 3.9691C12.4136 4.5235 12.1088 5.2604 12.109 6.0441C12.1093 6.5589 12.2443 7.0647 12.4993 7.5064C13.0232 8.4138 13.998 8.977 15.0443 8.9766C16.662 8.9762 17.9775 7.6593 17.9769 6.0413C17.9768 5.5268 17.8418 5.021 17.5866 4.579Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.00439 16.5056L9.00779 24.8614L12.0002 22.5969L11.9995 19.9898C11.6524 18.2819 10.2041 17.0405 9.00439 16.5056Z" fill="#3367D6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M18.122 22.6935L25.3569 18.5124L21.8246 16.9619L19.8294 18.116C18.7904 19.2079 17.9676 21.3548 18.122 22.6935Z" fill="#3367D6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0.809447 25.719L0.808247 25.7169C-0.857153 22.8326 0.134247 19.1298 3.01805 17.4634C4.41535 16.6556 6.04385 16.4407 7.60325 16.8578C9.16265 17.2749 10.466 18.2742 11.2736 19.6717C12.9402 22.5581 11.949 26.2607 9.06545 27.9274C6.18015 29.5948 2.47665 28.6041 0.809447 25.719ZM8.58255 21.2287C8.18975 20.5489 7.55685 20.0638 6.79975 19.8611C6.04275 19.6587 5.25215 19.7632 4.57365 20.1553C3.17355 20.9644 2.69215 22.7621 3.50085 24.1627L3.50135 24.1635C4.31085 25.5643 6.10905 26.0452 7.50985 25.2357C8.90975 24.4263 9.39115 22.629 8.58255 21.2287Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.7283 25.1829C19.7281 25.1826 19.7281 25.1826 19.7281 25.1826C18.6515 23.3179 18.6504 21.0015 19.7254 19.1381C20.5319 17.7399 21.8348 16.7395 23.3938 16.321C24.9528 15.9026 26.5812 16.1162 27.9794 16.9226C28.8987 17.4527 29.6637 18.2167 30.192 19.1316C31.2686 20.9963 31.2697 23.3127 30.1946 25.1765C29.3881 26.5747 28.0852 27.5751 26.5265 27.9934C24.9675 28.4118 23.3389 28.198 21.9406 27.3913C21.0214 26.8615 20.2564 26.0976 19.7283 25.1829ZM27.4995 20.6862C27.2439 20.2434 26.8727 19.8733 26.4261 19.6158C25.0247 18.8074 23.2268 19.2901 22.4187 20.6913C21.8966 21.5965 21.8974 22.7217 22.4207 23.628C22.6763 24.0708 23.0475 24.4409 23.4941 24.6984C24.1733 25.0902 24.9638 25.194 25.7206 24.9909C26.4776 24.7877 27.1098 24.3021 27.5015 23.6233C28.0234 22.7182 28.0228 21.5925 27.4995 20.6862Z" fill="#85A4E6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9864 21.6326C11.9864 21.6326 8.23897 24.814 7.50977 25.2356L12.0727 22.5987C12.0676 22.2586 12.0497 21.9713 11.9864 21.6326Z" fill="#5C85DE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0359 11.7302C13.0359 11.7302 12.1011 6.55899 12.1008 6.04419L12.103 11.3098C12.4015 11.4747 12.7132 11.6163 13.0359 11.7302Z" fill="#5C85DE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M21.2842 17.3536C21.2842 17.3536 25.6961 19.1949 26.4261 19.6159L21.8611 16.9829C21.5671 17.1599 21.5439 17.1302 21.2842 17.3536Z" fill="#5C85DE"/>
+</svg>
diff --git a/style/icons/signin_google_icon.svg b/style/icons/signin_google_icon.svg
new file mode 100644
index 00000000..21a06ec4
--- /dev/null
+++ b/style/icons/signin_google_icon.svg
@@ -0,0 +1,9 @@
+<svg width="215" height="63" viewBox="0 0 215 63" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<rect width="215" height="63" fill="url(#pattern0)"/>
+<defs>
+<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
+<use xlink:href="#image0_2908_26611" transform="scale(0.00232558 0.00793651)"/>
+</pattern>
+<image id="image0_2908_26611" width="430" height="126" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAa4AAAB+CAYAAAB8vPUBAAAKsWlDQ1BJQ0MgUHJvZmlsZQAASImVlwdUE+kWgP+Z9JDQAhGQEnpHikAAKSG0AArSwUZIAgklxBQE7MriCq4FFREsK7pUBRtFbIgoFhbF3jfIIqCsiwUbKm+AQ9jdd957591z/rnfuXP/W+bMP+cOAGQttkiUDqsCkCGUiiMCfWlx8Qk0XD9AAzIgAFugyuZIRIzw8FCAyJT+u3y4B6Bxfdt2PNa/3/+vosblSTgAQOEIJ3ElnAyETyLrPUcklgKAqkLsxsukonHuRFhDjBSIsHycUyb5/TgnTTAaP+ETFcFEWBcAPInNFqcAQLJA7LQsTgoShxSEsL2QKxAinI2wV0ZGJhfhZoQtEB8RwuPx6Ul/iZPyt5hJiphsdoqCJ3uZELyfQCJKZ+f8n4/jf0tGumwqhxmySHxxUASikbqgB2mZIQoWJs0Lm2IBd8J/gvmyoOgp5kiYCVPMZfuFKPamzwud4mRBAEsRR8qKmmKexD9yisWZEYpcyWImY4rZ4um8srRohZ3PYyni5/KjYqc4SxAzb4olaZEh0z5MhV0si1DUzxMG+k7nDVD0niH5S78ClmKvlB8VpOidPV0/T8iYjimJU9TG5fn5T/tEK/xFUl9FLlF6uMKflx6osEuyIhV7pcgLOb03XPEMU9nB4VMMIoEUyAAXCEAmoAE/REuACKQDNsiR8rKl4w0xM0U5YkEKX0pjICeNR2MJOXY2NEd7RycAxs/t5GvxjjpxHiHqtWnb+moAPE+NjY2dnrYF3wTgWCIAxIZpm8UiAFT7AbhyhiMTZ03a0OMXDCACFaABtIE+MAYWyJfBEbgAD+AD/EEwCANRIB4sBhzABxlADJaBFWAtyAeFYCvYCUrBfnAQVIEj4DhoAmfABXAZXAc3wV3wGMhBH3gFhsEHMApBEA4iQxRIGzKATCFryBGiQ16QPxQKRUDxUCKUAgkhGbQCWg8VQkVQKXQAqoaOQaegC9BVqBt6CPVAg9Bb6AuMgkmwBqwHm8GzYDrMgEPgKHgRnAIvhXPhPHgzXAKXw4fhRvgCfB2+C8vhV/AICqCUUFSUIcoWRUcxUWGoBFQySoxahSpAFaPKUXWoFlQH6jZKjhpCfUZj0RQ0DW2L9kAHoaPRHPRS9Cr0JnQpugrdiG5H30b3oIfR3zFkjC7GGuOOYWHiMCmYZZh8TDGmAtOAuYS5i+nDfMBisVSsOdYVG4SNx6Zil2M3Yfdi67Gt2G5sL3YEh8Np46xxnrgwHBsnxeXjduMO487jbuH6cJ/wSngDvCM+AJ+AF+LX4YvxNfhz+Fv4fvwoQZVgSnAnhBG4hBzCFsIhQgvhBqGPMEpUI5oTPYlRxFTiWmIJsY54ifiE+E5JSclIyU1pvpJAaY1SidJRpStKPUqfSeokKxKTtJAkI20mVZJaSQ9J78hkshnZh5xAlpI3k6vJF8nPyJ+UKcp2yixlrvJq5TLlRuVbyq9VCCqmKgyVxSq5KsUqJ1RuqAypElTNVJmqbNVVqmWqp1Tvq46oUdQc1MLUMtQ2qdWoXVUbUMepm6n7q3PV89QPql9U76WgKMYUJoVDWU85RLlE6dPAaphrsDRSNQo1jmh0aQxrqmvO1ozRzNYs0zyrKaeiqGZUFjWduoV6nHqP+mWG3gzGDN6MjTPqZtya8VFrppaPFk+rQKte667WF22atr92mvY27SbtpzpoHSud+TrLdPbpXNIZmqkx02MmZ2bBzOMzH+nCula6EbrLdQ/qduqO6OnrBeqJ9HbrXdQb0qfq++in6u/QP6c/aEAx8DIQGOwwOG/wkqZJY9DSaSW0dtqwoa5hkKHM8IBhl+GokblRtNE6o3qjp8ZEY7pxsvEO4zbjYRMDk7kmK0xqTR6ZEkzppnzTXaYdph/NzM1izTaYNZkNmGuZs8xzzWvNn1iQLbwtllqUW9yxxFrSLdMs91retIKtnK34VmVWN6xhaxdrgfVe624bjI2bjdCm3Oa+LcmWYZtlW2vbY0e1C7VbZ9dk93qWyayEWdtmdcz6bu9sn25/yP6xg7pDsMM6hxaHt45WjhzHMsc7TmSnAKfVTs1Ob2Zbz+bN3jf7gTPFea7zBuc2528uri5ilzqXQVcT10TXPa736Rr0cPom+hU3jJuv22q3M26f3V3cpe7H3f/0sPVI86jxGJhjPoc359CcXk8jT7bnAU+5F80r0etnL7m3oTfbu9z7uY+xD9enwqefYclIZRxmvPa19xX7Nvh+ZLozVzJb/VB+gX4Ffl3+6v7R/qX+zwKMAlICagOGA50Dlwe2BmGCQoK2Bd1n6bE4rGrWcLBr8Mrg9hBSSGRIacjzUKtQcWjLXHhu8Nztc5/MM50nnNcUBsJYYdvDnoabhy8NPz0fOz98ftn8FxEOESsiOiIpkUsiayI/RPlGbYl6HG0RLYtui1GJWRhTHfMx1i+2KFYeNytuZdz1eJ14QXxzAi4hJqEiYWSB/4KdC/oWOi/MX3hvkfmi7EVXF+ssTl98donKEvaSE4mYxNjEmsSv7DB2OXskiZW0J2mYw+Ts4rzi+nB3cAd5nrwiXn+yZ3JR8kCKZ8r2lEG+N7+YPyRgCkoFb1KDUvenfkwLS6tMG0uPTa/PwGckZpwSqgvThO2Z+pnZmd0ia1G+SL7UfenOpcPiEHGFBJIskjRLNZABqVNmIftB1pPllVWW9WlZzLIT2WrZwuzOHKucjTn9uQG5vyxHL+csb1thuGLtip6VjJUHVkGrkla1rTZenbe6b03gmqq1xLVpa39dZ7+uaN379bHrW/L08tbk9f4Q+ENtvnK+OP/+Bo8N+39E/yj4sWuj08bdG78XcAuuFdoXFhd+3cTZdO0nh59KfhrbnLy5a4vLln1bsVuFW+9t895WVaRWlFvUu33u9sYdtB0FO97vXLLzavHs4v27iLtku+QloSXNu012b939tZRferfMt6x+j+6ejXs+7uXuvbXPZ1/dfr39hfu//Cz4+cGBwAON5WblxQexB7MOvjgUc6jjF/ov1RU6FYUV3yqFlfKqiKr2atfq6hrdmi21cK2sdvDwwsM3j/gdaa6zrTtQT60vPAqOyo6+PJZ47N7xkONtJ+gn6k6antzTQGkoaIQacxqHm/hN8ub45u5TwafaWjxaGk7bna48Y3im7Kzm2S3niOfyzo2dzz0/0ipqHbqQcqG3bUnb44txF++0z2/vuhRy6crlgMsXOxgd5694Xjlz1f3qqWv0a03XXa43djp3Nvzq/GtDl0tX4w3XG8033W62dM/pPnfL+9aF2363L99h3bl+d97d7nvR9x7cX3hf/oD7YOBh+sM3j7IejT5e8wTzpOCp6tPiZ7rPyn+z/K1e7iI/2+PX0/k88vnjXk7vq98lv3/ty3tBflHcb9BfPeA4cGYwYPDmywUv+16JXo0O5f+h9see1xavT/7p82fncNxw3xvxm7G3m95pv6t8P/t920j4yLMPGR9GPxZ80v5U9Zn+ueNL7Jf+0WVfcV9Lvll+a/ke8v3JWMbYmIgtZk+MAihkwcnJALytBIAcDwAFmSGICybn6gmBJv8FJgj8J56cvSfEBYA6RI2PR8xWAI4iy2wNACo+AIyPRlE+AHZyUqypGXhiXh8XLPLnUufO4L+LemZTDf4pk7P8X+r+pwaKqH/T/wJRFA0eTO16dQAAAIplWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAOShgAHAAAAEgAAAHigAgAEAAAAAQAAAa6gAwAEAAAAAQAAAH4AAAAAQVNDSUkAAABTY3JlZW5zaG90KBfV5AAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAdZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTI2PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjQzMDwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlVzZXJDb21tZW50PlNjcmVlbnNob3Q8L2V4aWY6VXNlckNvbW1lbnQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgrXhkB8AAAAHGlET1QAAAACAAAAAAAAAD8AAAAoAAAAPwAAAD8AABEJPIGUpwAAENVJREFUeAHsnAd4FcUahv8ggdBL6B0ELCgdFLEgTa8PvUawXJoVAqFc6R1EkK7yUKSJilQpV4WLgL1RvVKUJr0EBELoJXe+ibt3c87Zwwk5SXaT73+enJ3dnd0z+86c+eaf+TchccqERgIkQAIkQAIuIRBC4XJJTbGYJEACJEACmgCFiw2BBEiABEjAVQQoXK6qLhaWBEiABEiAwsU2QAIkQAIk4CoCFC5XVRcLSwIkQAIkQOFiGyABEiABEnAVAQqXq6qLhSUBEiABEqBwsQ2QAAmQAAm4igCFy1XVxcKSAAmQAAlQuNgGSIAESIAEXEWAwuWq6mJhSYAESIAEKFxsAyRAAiRAAq4iQOFyVXWxsCRAAiRAAhQutgESIAESIAFXEaBwuaq6WFgSIAESIAEKF9sACZAACZCAqwhQuFxVXSwsCZAACZAAhYttgARIgARIwFUEKFyuqi4WlgRIgARIgMLFNkACJEACJOAqAhQuV1UXC0sCJEACJEDhYhsgARIgARJwFQHHCNeVK1cl5kKsXL58Ra5fvyFxrsLIwpIACZCAuwmEqOKHhmaULFnCJGeO7BIWltmxD+QI4ToVfUbOx8Q6FhILRgIkQALpjUCunNmlQP5wRz52qgvXseMn5eKlKxpOntw5JUf2bJIpU6iEhED/aSRAAiRAAilBIC4uTq5duy4XYi/K2XMx+iuzZQ2TIoULpsTXJ+o7UlW4DE8L7mnhgvklc+ZMiSo8M5MACZAACQSfwNWr1+T4yWi9bONEzyvVhAtrWoePntDESxQrTNEKftvjHUmABEjgjglAvA4dOa6vL160kKPWvFJNuAxvC9OD+cLz3DFcXkgCJEACJJA8BE6fOaunDZ3mdaWacB08dFSuqehBelvJ0+B4VxIgARJIKgHD68qklnNKliia1NsF7fpUE669+w7qkPeyZUowECNo1ckbkQAJkEDwCCBgY+/+Q4JQubJ3lwzejZN4p1QTrj1KuGDlHAQjiSx5OQmQAAmkOQJO7KspXGmumfGBSIAESCB4BChcFpZOhGEpHpMkQAIkQAKKgBP7anpcbJokQAIkQAK2BChcFjROhGEpHpMkQAIkQAKKgBP7anpcbJokQAIkQAK2BChcFjROhGEpHpMkQAIkQAKKgBP7anpcbJokQAIkQAK2BChcFjROhGEpHpMkQAIkQAKKgBP7anpcbJokQAIkQAK2BChcFjROhGEpHpMkQAIkQAKKgBP7anpcbJokQAIkQAK2BChcFjROhGEpHpMkQAIkQAKKgBP7anpcbJokQAIkQAK2BChcFjROhIHiXfptmy4ltpd3bI9P74g/lrVCZb2fpUIlvc0X8U+95QcJkAAJpFUCTuyr6XH93dogVGc+mSeX/hapQBshxAxCRhELlBjzkQAJuIkAhctSW06BcaeCZXkUnQxv86LeUsA8yXCfBEjAzQSc0ldbGaZrj+v0wrlyZtE8K48kpyFgFK8kY+QNSIAEHEKAwmWpiNSGkRyiZTwexcsgwS0JkIDbCaR2X+2LX7r0uA4Pikr0WpYveHbHig+fKFkfiA/ksMvD4yRAAiTgBgIULkstpRaM5PS08HipIVq3bt2S/QcOyM6du+SmShctUkTuvae85MyZ00LcO/nrf3/TBys++ID3SRccuXHjhvzw409Spkxp/cwuKHLARQxW3Vy9elXQPmCZM2eWDBkyBFyGpGS8cOGCbpPHT5yUM2fOSI7s2SU8PFweVG0tZ44cSbm1a69NrbpIKrDU6qv9lTtdeVyJES0jWtDqORmh8giT9xV9mNKidV113JMmT5F1X26Qy5cvJ6jnsLAwadOqpTz3XDsJUx2Wp02Z+q4sWbZcH27VorlEdnvdM4vj9zt2fln27tsnISEhMmLYEHn8sUcdX+ZAChjMuonq1Uc2b9mqv3bsmNHy8EM1AynCHefZoQZPn65YKRs2fiXXrl3zug+Es1KlihLZ9TW5u0wZr/Np+UBK10WwWFK4LCRTA8bvLZ60lMB3EoIV3vbF2071eYpgSovWxUuXZOCgIWan5PtpRKpVrSJvvTlKMmXKZGaJi4uTRk1bCEbFsBxqBLx6xTItAGYmhycgWBAuwx57tLaMGjHM2HXtNtC62bFjp5w9d04/Z4X775M8efL4fOaU6izh/U6bPlMWL1nqsxyeB0MzZpROnTpIRNs2kkENPNKDpVRdBJtlavTVt3uGdONxBbKudSdBFRAweGVWz+x20INxfsiwEXpUa9yrXLmygs4bntc3334nR44cNU5Ji+bNpEdkV3MfiaHDR8r6DRv1sbpP1pGhgwfqtFs+bt68KW0i2kv06dO6yL2iukvTJo3dUny/5Qykbnr16Su/bNqk7zNm9Eh5pNbDPu+ZEp0lBlG9er8hO3ftMssAz6pG9Wp6GjdP7txy+MgR2bNnr+zbf0CuX79u5oOXPHL4UHM/LSdSoi6Sgx+Fy0I1JWHcPLddYr6dK6dmx/8HDEsxzOSdiJZ5cQonMEXYqElzLVJ33XWXjB0zSnUS1ROUYv6CD2XW+3P0sfz58smHH8wVTB8aBoFbs3ad3n2qYX3JkiWLcco12+joaPns8zVStmwZqVWrVpoZuQdSN04SrgHK88dgyTCIUXc1UEK787Tz58/Lm2+Nk+9/+FFy5colkye8rcXNM19a3KdwBa9W04XHdWlbT7mhxCsk9G6J/SlOidjeBATdJFoo+KbNW6Rn73/pZ6is1gumTJqQ4HmwgwX5sW9PkMqVK0k95VGFhoZ65eEB9xJwinAtWrxU3nlvmgnyyTpPyOCB/QUDKn+2/NMVUqlixXQjWmBB4fLXIhJ3Ll0IV8zGegmo3PiroUQv+MI8ds+yDWbaDYlvv/te+g8crIuK9Y1p705NdLGPHj0qR48d19cVLVJYihYt6vMeWHP58aef5ZdfNsnJU6ekiIpYfPCBCloQER126NBhOXHypL4W05WYFjJs9+9/SExMjN6FwGKdDd+7ees22b79V71fpnQpqaM6O1+jc+M+dttdu3erdbpYffree+9JEK127Phxc7oUUYf5VETbuXPnZeu2bfovNvailCpVUmrWqKEjMO2+w/N4jFoX3L37d/MwpsMQHOJpWIPCFBqstPqe/Pnze2aRU6ei5c+DB/XxbFmzSoUK9+u0Xd1cvHhREPwAmzHzffljzx6d7tjhRbn/vvt02nofHLB2luPHjdGeOaaRtygOqANEGqJ8Tzz+uBQo4F1GfVObD3j+rVpHmGtt9evVlYH9+yZL5CK8UExt79m7T3E7JdmzZZPCqt3WeeJxVf5SNiVMePgW2rKKQt26bbucVG0W9VaoUEGpUa2aVKtW1Wc9JryDSFJ+D9a68Bcog6nUL9dvkF2qnaGN5MiRXUfN1q/3pO3v1LOcwdxPydmxQMud5oUL04QXlcflaTdjK+qpQ7d5W3gOdMoR7Z43HymqR6Q0b9rE3A8kMWv2HJn/wYc66wvPt5fOHTt4XQbRiVKeHdYmPC08PK8O+vjii7VmdCKCI7DOZpj1h7r4k49k/fqNagF/hnHa3GZXodJ9ekUJRuuJsa6RPcQIG584fpwORDGux7PhGWH9+70hBZVw9B0wyCv6Emsx7Z5tK106dQyo44JwNW3eSrDGBps1Y5qUL1dOp42PKyoEvbEKfkH4M6xJ40bSu2cP47S5fXv8RFm5+t96H+tzWKeD2dUN1pBeea2bzmP3cU/58jJz+nvmaWsdzJj2rho0bJXpM2aZ541ENiUE+H6IT6C27sv1MnzkaJ0dA5YlixcKgi6CbWv/s04mTppiDgQ871/7kUfUGu0ALcKe54z9ffv3S78Bg+XEiRPGoQTbkiVLCNYK8SqJnQXz92AnXBBVrHGePXvWqxgQ2mbqdx7V3X8b8LowiQcoXBaAKQXDmCa0fLWZjLtWUHI1/Mjcd1PCMzijSpXKOvy91sMPBTTitescDQYY4Xbv2TuBd2GcM7ZYMyterKgeBeOYP+Fq2KC+oAOyM3R4H8yfI0UKF7bL4nU8UOHCqBxrKr7Cs42bDhrQTxrUT+iZG+c8t1Yx6Kwi4154rn2CLFjvwbqPYXnz5pXlSz7xEsZWbZ/VI2rkmzDuLamuvDeYXd0kVbgQhGME5Ogv8viAiM95f0bAHsygIcPkq6+/0Xdp3y5CXu7S2eOOSd+F5zFi1Jvmu2h2d8Qa75jRI3xOicOrjezRU3vcdtfjeMECBWTqlIlSqGBBr2zB/j34Ei54k5Hdo0yBhlCFq7bz119/CbxFw9pFtJVXXu5i7Cb7NqX66sQ8SJr3uPwJV8bclSRrZe/1IV8AW4484+twoo4tHRieqPz+MmORe/DQ4Xraw5oPnWQDNWpu2aKZmgYpZD2VIG3XOSITQpv7vNHPDLVH4AY6Z0ynYEpv2/btsnLVaq/v9idcuC/eJ2vf7lntlYVlCdNiMnPWbNMLerphA+0dIW8gFqhwGfdq3OgZqV+3rhQvUVx27dot702brqZLj+nT6KwWLYz3QI38dtvl6j0leAAwTJu+O3Vygqyjx4yVL9asTXBs2jtTzKlAnDhw4E95sWN8R4/XEVYuX2KuC9nVDUb8P6spW9iCjz6W/SpCD4aQ8vJqmhaGez1Us4ZO48MqstjH2lNEm9ZqavAxyZ07l36BG9OOxrQmvJc3Rw1H1tta55deNacr8coFBk3BtK+/+VYwQDO825o1qkt9NbjAtPMJ9WLzps2bBWtsV65c0V+LyEpEKGa0eH2IZuzWvafu/JEJnlVT5QFXq1pVicEtvV786YpVegob5zFwgnhZp66T4/fgKVyYRXn19UjT08JA4Ln27QRTv7GxsbJFTa+PUx46fvew1197Rdq2bqXTyf1B4bIQTikYaVW4DJQYkaIDNsLCjeMYPWOE3V29WIzoLU+z6xyRb+NXX2tRRBrrUuPUi6vw6KwG76VP3/6yVf2gDPMnXBg9DhsySK9JGPmxXbX6M/WDjB88FC9WTEc/Ws/7SydGuHy9EoCReIdOL5kd4/Ili9R/d8jr7yv1udPqP0G0VGs7WO8A51WfLtWCgZMIimnSrKVgShFrgNjCPEfJHy9cZE6bPv1UQ+nfNz7YBnn91Q3Ow+4kOENf5+O1AbShYSNG6fuizKtXLtfp230gstV4vnmzZ0np0qV8XoIX3VesXOXznPVg3z69TXHHdGvzFq1NQdXrZ8or9nznC1Nr/1Lt0JiW9Zw2two31sKmTBrv9XtAfXaLjDIHMZiyRls1LDl+D57ChWArBF3B7F5PwXTnq2qqGGyyKkFbsWyx3+lRo/xJ3aZUX52YcqZ5j8szMMMKJ5vytu5SXlcgFgyPa/jzOaVCydBAvi5ReTCNsXjJMvlcjfKxsG81LLiPHjncax3GX+eIwA8EgMDs1r9wDsEOTVu00h049v0JV+1HaqmR/AhkS2AIOPhHo6b6GEbK69Z8FtBUJy4IVLg8PRprATp1edmc6nxn8kSpWPFB62nb9KtdIwUBGDB0csb6HIQcU6yw7t26yuy58/SL3p6ijDyG6MPDgadjmL+6MfLciXBVrVJFJk0YZ9zC3EJsGzz1jCDYAgbhCuTfMuEldiP4BlOMdv8JA69l4PWM25l1uhYeKzxXWInixWXenFmmR+p5H0QoTpw8VR/GeiPWHWFYz2qr1oIxwMDAafHCj2wDUA78qTzgDvEeMKatl6mpXWPAlxy/B6twIQCjTUQ7PR2IWYkF8+falhOzLBBSGH7Xj9b+f7vRB5Phw4nC9T8AAAD//2tnJ2UAABJJSURBVO2dB3wUxRfHXxRIaKGXgBA6CUERRbEgiihiAYFIE5IYQBARkSK9N0GqAaRKFQOYkFAUewf9C4oKCEiTUAKhh04o/3lzzjKce5tLzF0W7jd8PrezM7O7s98pb96bt8HvmgiUDWHHrr3yqZUrBnv06anf1Hd5f/9ykeRfLsplvp4RPvKYfpqp+PCIQAoLzpmpa925iJvy142/UVz8clq77kfjktKlStH8ubPJ39/fSJszdx4tXLRYnkdGtKEO7aKNvNZto+jAgQPyfPzbY+j++2oZec6RtlHRlJS0TyaPGjGMHqnzsFGke8836ZdfN8rzTh07UJvWrYw8PdLw2cZ07tw5mbRm9QrKmzevnu0y/trrb9AfmzbL/EkTxtG999Q0yvK78TtyqH3/fTRu7FtGnh7pN2CQwWr0yOFU5+GH9GyX8SVLl9G7M2bJ/IZPNaD+fXvLeMyUaRS3PIH8/Pxo+YdLaeas2fTJZ5/LvEXz51JwcFn5rs8934wuX75MuXPnplWJ8ZQrVy7jWVZtowr1fLMvrd+wQZ6OGT2SHnrwAZV1w1Fvg/bRL1FUZNsb8tVJ6zaRdODgQXka+/4CKl26tMpyeXy506u0/a+/ZP5bo0bQww89aFqWWcUvTzTNO3XqFF24eFHmTRg3hu6r5ehrkybHUMKKlTK96fONqfsbr5tez4ncV7nPcmDuX3y2hnLmyEFff/MtDRk2QqaXCw6mhfPfk3FXP+HNW9GRo0dl9sRxY6lWrXtl3BPj4e0xo+mB2vfL+3/z7Xc0eOhwGc+fPz+VL+d6Tkw5cpQOHToky776Skdq1bKFjHvyx1tzdUbewe9WF1znfutBl0/+bsrkVhNc+kt+9/0PNHT4SDk5cvprr3amFs3DjSJWk2ODhs8ak8nqlQkUKAaTqzDqrbH06T8Ts5Xgsppcn2nUhM6cOSMf4QnB1bJFc+rSuZPpKwwYNIS+/2GtzMuI4OJJnid7DoULF6aEuKVy0mzRqg0dOnyYwsKq0fSpMfTD2nXUf+BgWa5jh/bUtk1r+Tx+Lod6jz1Kw4YMknH1Y9U2qkxmBJeVcHkxIor273csVtwVXCwUWDhw4AmUJ9KMhvYdX6EdO3bKy+KXxVKxYsVkXF9QDBk0gOo/Xs/y1k3CW9Dx48dlmdjFC4kXa0s/jKNp786QaY0bPUe9erxheY9hI0bRl199Lcv06d2Lnn26oYx7Yjzogkuvp2UFnTJbt2pBnTtlnLnTbdI9heDSEHkLhpXgylGwBuW5e6JWK9fRrNC44gcWcf2ATOQcOXLEGOhml0+fOYtilyyTWU8+UZ8GDehnFLOaHPVJYN57s6hihQrGdc6Rbt170sbfHAsDK8GlD1Tne3hacL3YqiW90ull58fK88wKLr44un1H2rV7t7zPnFnTyU/844mYg1oNXxTaRKMm4XThwgUKCalKs6ZPo/ETJtHK1R/JckMHD6TH6z0m4+rHqm1UmcwILqs2yIzg0jWawMBAoWEuuUFzVHV1dTx27Dg1a96S2FKQN08eWvORQ8Pi8rpQ7Nm9Gz3fuJGr29CVK1eI+9D58+dlmXhRj2JFi0qNjTU3DsyYWVuFXr370s/rHVosjxUeMxw8PR6WJyTS5Jip8lnVw8Ko1r33yHh6PzVq3HWDlSG98pnN99ZcnZH63fIa18W/F9DFvxeaMtlTuBHVuMt6FaYuHLIoVUUtj5v3prnMzwrBxYOUV4UrVq6iLVv+pBnvTpUTotlDWesaOHiozOIB8e7Ud4xiVpNjx85daNu27bKs1aTBdXlamPmUqcfXBNe8BQtp3nxH3+rQPpoup12m+QsXSW661sJmIDYHsRkrTmgVnbt0pZSUI5QzZ05pJswjJm09WLWNKmcHwcXt37zli3T0mMOMzmbWYUMHSzOdqqfVceLkdyhxxSpZhE2drJWrMG36TFq67EN5+kT9x2nwwP4q619H7qvcZznkECZCNhXeJliv+/En6tvfIayKFGGt2LGI+9cNRMLVq1el8FMm66kxk+muO6vLop4eD2vXraN+AxxaOZsn2UxppwDBpbWGt2C4Elxrbq9Dift3U3j1pvTCnc20mmU+ysLNleCqLva2hok9rv8aeIC1bhtJyckOO3dYtVAhkGLkpOh878UfLKGZs+fI5LqP1KGRw4caRawmR558585bIMtWrVKFYiZPkHsxxsX/RBISV9Ckd6YYyb4muHbv3kMvtXdocndWDxN7V+elBlahQnma/95sg8sXX35Fw0eOludNnm9kTNa8J8TmO+dg1TaqrK4d6GYtla+O+h5XVmtc/AwWPCyAVHjwgdqyn7FQtgpsXmYzMwfej5on9mDLliljXLLhl1+pRy/HvmGBAgWINf+iRcwtFtwHuS9y4OePfWuUjJ8V+6ZNmzU3Flb6vpUsoP3o+0xsGk+IXyYXFlzE0+Mh9fRpaipMnWlpaVJj5b24UkFBWu2uR3lPNyDAn6pUrnw90cMxb83VGXmNW17jYhjODhqTzobRttQTBqfY1o5VspGQyYiVOTGrBBdXzVlgtIuOosiItnKVqarOHbxPvwF09uxZmcSmMjaZqWA1OR5OSaGIqHbSvMXla95dQ05GvHHM4aow7az55FMaN26CjMtE8eNrgovfW9+4VxxeiowgbhMVeAJtLJwx0oQzhh769XmTnm74lJ4k41ZtowqPeXs8fbzmE3n62KN1abjQdDiwFnT77bfLOP94WnDxM1gos3BWgbWG7t26Upk77lBJxvHEiRNC0MXQt999b6Q5OwdxBpsP20ZG0779+2W5smXL0JTJE6lQoULGdRzRnXD4nIUWCy8Vxk2YSKtWfyxP2RGGnY14kaEHFpKsmV26dEkmO+/XeWM8jBw9hj77/Av5/JCqVWQ92fyqh81btghh3keOywdq1xZaaD/Kly+fXsQjcQguDas3YSita1euarT6fBD9mbJVqwllida1RZgIB1uYE7PSo5AHWLcevaSpUL1I+fLl6J6ad9PVK1dp7Y8/SlOUyitYsADFLl4k9xFUWnqTIw/mPn37G5PtbbfdJlZ5laio2Dv4/Y9NdFqsEjlwOmuBHHxRcOn7iBKC+Hlv9kyqXKmiOpXH3oLlT//72Uhj4bIiIc7U8SW9tuGbfBgXT1OmTTfux16AVatUpoPJyXIfTWV4Q3Dx/l0fMfFvFB6temABUUHsjxYWwiZZ1Gv3nr/p7717DQHBZe+pWVMIm5E3eLyqe7ADTNfXuxumSNa87hNC8W6xt5MsPOt+EX1023aHVyNf83KHdhTR5kV1uTzyHiOzV/uw7L3JCzHeR7oi+i3389/FHq1aVEiNUXjHshaoB0+PB17ccFspEz0LrfCmTaiyGHPHhCl2/YZf5P4bs+YQHRVJ0S9F6lX0WNybc7W7L+ETGhcLrsR9u6Vp0BWY/2IyTE9o8TOzYn9LrztrUt2696K/duzQk/8Vr1SxIo0YPkR6WemZ7kyOvCrmTXIlmPTrOc5mL3ZIUG7Lvii4tvy5Ve5ZKTZBQSVp6Qfvq1PjuPqjj+nt8dcdgdh1n134zYI7bcMTWFS7DobJWN2nePFiFLc0Vp16RePih3EfmTt/AS16/wOpLRkVcBFhAdLp5fb0QngzUzO3uixp3z7q2q0HsaZmFdjNn939zQKzYtMqWyGsAmuKY8U+myszp6fHAy8GeUzv3LXLqprU/IVw6tqls2WZrMyE4NJoehvGiC9H/0vT0qojo5kRXu4IrZZ181CLurmdH/efz3k1mSi+dVkcu4ROnjx1w/3YLPJE/Xr0+mtdTFez7kyOfENeqcYuWSpXt7wq5fuyKYO9tNjTSzfFOO+huLvav1m9CpkPm7T4+x/loMCfHPCnB86B26dJsxcM02qPN7pJwe9cjs/dbRvWSCaL/R32hON6cKgWGiocdq7vO7rbBpnxKpQPdPrhfb/ElSuF2etL49s8vQg7SYRUDaFOHdsTf1vlTti7N4mmTZ9BP/+83uCnrmNB3Up87sAC0Cqw0wXf43NRL+VMpMpzn27Y4EnqLD6ZCNC+dVT5+tHT44H7yTtTpkpTKn/np4dCBQtSeHhTqVWyo4+3grfnanfeyyc0LgbB5kEWXu4EdwQY3y9+UwJdPV+J/vitvuVts1rbcn4YCzA2wfCHwLzyDalalcqIPQH2rMqqwPtaJ8Wqt6AYPGweVIFNlspENHfOTGIND8G7BFijOHgwmXgS98aehztvx8IhKSlJfCx7WGpLbMrkvsFm68wG9sRkbYSP+fLlpaCSJSk0NOSG/pjevdlSsXXbdjosvrUTqh6VLFGCQsVnCs6enendx9Pj4cTJk7RdmEFTxH5zQECAo57CEcvZhJlePbMiH4JLo5gdMOI2Laf4zQlaLVxHqxUPpdDiIbJAtRKhRsE/D2+lrSnbbtDeggNDaMdGx/c7RsF/Ip7Stpyfk9XnvI/Gbvc8gHSnDv05vDps3SaC2D7PA2rViuUZngD0+yEOAnYl4MvjITvm6vT6gc9oXAqEOyZDVTajxyJXn6HkPQ2My7LSk9C4qRci/JcMugtX5NRUx7dr+seY6vFsxmB3ZvWXBh6t+wiNGDZEZeMIArcMAV8fDxBcWlfOThieFF4h+RtJ0+HNKrS4idjcGN2hI+0RXmAcWJu6X/y9vwfFR6Ls4swmoBUrVxubyGz+iRGuyu7uWcib4gcEbhICvj4esnOudtVFfE7jUiAyYjZU17h7jAjrTcEFQjz6B3XdrUtmy/HGf0/xzQi7V1sF3jCePGk8lS9XzqoY8kDgpibgy+MBgkvrunaA4QnhxXtjg+q7/vM0GgLbR9kFea74k0affPoZsQOIc+Cv9wcO6AtNyxkMzm9JAr46HuwwVzt3KJ/VuBQIFl4c3HXaUNeZHd3xRjS7zu5p/F9P8Iee/N8p8J81Klq0iPwwEqZBu7cc6ucJAr42HiC4tF5kRxiZ0cCU92FW/b1DDRGiIAACIJDtBOw4V/u8xmXWK5QWxnm66zsLKRXC72wqo3qaysMRBEAABG4VAhBcWkvaEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDYEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDYEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDYEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDYEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDYEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDYEYZWPURBAARAAAQEATvO1dC40DVBAARAAARcEoDg0tDs3LWXronzShXKkp+fn5aDKAiAAAiAgB0IXLt2jXbuTiKeoStVDLZDlWQdsk3j2pt0gC6lXaaydwSRv38u2wBBRUAABEAABBwELl68REn7kylXzhwUXLa0bbBkm+BKOXKMTqWeoUIFA6lokUK2AYKKgAAIgAAIOAgcPXaCTpxMpQKB+ah4sSK2wZJtguvChYu078AhCQJal236AyoCAiAAApKA0rb4pEzpkhQQ4G8bMtkmuJiA0rpyCjU0qEQxmAxt0y1QERAAAV8mwEIr+fARShPbOXbTtrhdslVwcQUOJh+ms+cucFSaDfPny0u5cuWEw4Ykgh8QAAEQ8A4BdsS4dCmNTp85K82D/NS8eQKoVFAJ71QgA0/JdsHFdVWaVwbqjaIgAAIgAAIeJGBHTUu9ri0EF1eG97xST5+h8+cvSPWUXeURQAAEQAAEvEOAXd552yZ37gAKzJ/PVntazgRsI7icK4ZzEAABEAABEDAjAMFlRgVpIAACIAACtiUAwWXbpkHFQAAEQAAEzAhAcJlRQRoIgAAIgIBtCUBw2bZpUDEQAAEQAAEzAhBcZlSQBgIgAAIgYFsCEFy2bRpUDARAAARAwIwABJcZFaSBAAiAAAjYlgAEl22bBhUDARAAARAwIwDBZUYFaSAAAiAAArYlAMFl26ZBxUAABEAABMwIQHCZUUEaCIAACICAbQlAcNm2aVAxEAABEAABMwIQXGZUkAYCIAACIGBbAhBctm0aVAwEQAAEQMCMAASXGRWkgQAIgAAI2JYABJdtmwYVAwEQAAEQMCMAwWVGBWkgAAIgAAK2JQDBZdumQcVAAARAAATMCEBwmVFBGgiAAAiAgG0JQHDZtmlQMRAAARAAATMCEFxmVJAGAiAAAiBgWwIQXLZtGlQMBEAABEDAjMD/AQlGmZU+qyz9AAAAAElFTkSuQmCC"/>
+</defs>
+</svg>
diff --git a/style/icons/start_cluster_icon.svg b/style/icons/start_cluster_icon.svg
new file mode 100644
index 00000000..2714727e
--- /dev/null
+++ b/style/icons/start_cluster_icon.svg
@@ -0,0 +1,3 @@
+<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0L11 6L0 12V0Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/start_cluster_icon_disable.svg b/style/icons/start_cluster_icon_disable.svg
new file mode 100644
index 00000000..31d184f7
--- /dev/null
+++ b/style/icons/start_cluster_icon_disable.svg
@@ -0,0 +1,4 @@
+<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0L11 6L0 12V0Z" fill="#8A8A8A"/>
+</svg>
+
diff --git a/style/icons/start_icon.svg b/style/icons/start_icon.svg
new file mode 100644
index 00000000..7ba44086
--- /dev/null
+++ b/style/icons/start_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2375)">
+<path d="M7 12.5L12.5 9L7 5.5V12.5ZM9 17C7.90278 17 6.86806 16.7917 5.89583 16.375C4.92361 15.9583 4.06944 15.3889 3.33333 14.6667C2.61111 13.9306 2.04167 13.0764 1.625 12.1042C1.20833 11.1319 1 10.0972 1 9C1 7.88889 1.20833 6.85417 1.625 5.89583C2.04167 4.92361 2.61111 4.07639 3.33333 3.35417C4.06944 2.61805 4.92361 2.04167 5.89583 1.625C6.86806 1.20833 7.90278 0.999999 9 0.999999C10.1111 0.999999 11.1458 1.20833 12.1042 1.625C13.0764 2.04167 13.9236 2.61805 14.6458 3.35417C15.3819 4.07639 15.9583 4.92361 16.375 5.89583C16.7917 6.85417 17 7.88889 17 9C17 10.0972 16.7917 11.1319 16.375 12.1042C15.9583 13.0764 15.3819 13.9306 14.6458 14.6667C13.9236 15.3889 13.0764 15.9583 12.1042 16.375C11.1458 16.7917 10.1111 17 9 17ZM9 15.5C10.8056 15.5 12.3403 14.8681 13.6042 13.6042C14.8681 12.3403 15.5 10.8056 15.5 9C15.5 7.19444 14.8681 5.65972 13.6042 4.39583C12.3403 3.13194 10.8056 2.5 9 2.5C7.19444 2.5 5.65972 3.13194 4.39583 4.39583C3.13194 5.65972 2.5 7.19444 2.5 9C2.5 10.8056 3.13194 12.3403 4.39583 13.6042C5.65972 14.8681 7.19444 15.5 9 15.5Z" fill="#444746"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2375">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/start_icon_disable.svg b/style/icons/start_icon_disable.svg
new file mode 100644
index 00000000..2b649a6f
--- /dev/null
+++ b/style/icons/start_icon_disable.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2375)">
+<path d="M7 12.5L12.5 9L7 5.5V12.5ZM9 17C7.90278 17 6.86806 16.7917 5.89583 16.375C4.92361 15.9583 4.06944 15.3889 3.33333 14.6667C2.61111 13.9306 2.04167 13.0764 1.625 12.1042C1.20833 11.1319 1 10.0972 1 9C1 7.88889 1.20833 6.85417 1.625 5.89583C2.04167 4.92361 2.61111 4.07639 3.33333 3.35417C4.06944 2.61805 4.92361 2.04167 5.89583 1.625C6.86806 1.20833 7.90278 0.999999 9 0.999999C10.1111 0.999999 11.1458 1.20833 12.1042 1.625C13.0764 2.04167 13.9236 2.61805 14.6458 3.35417C15.3819 4.07639 15.9583 4.92361 16.375 5.89583C16.7917 6.85417 17 7.88889 17 9C17 10.0972 16.7917 11.1319 16.375 12.1042C15.9583 13.0764 15.3819 13.9306 14.6458 14.6667C13.9236 15.3889 13.0764 15.9583 12.1042 16.375C11.1458 16.7917 10.1111 17 9 17ZM9 15.5C10.8056 15.5 12.3403 14.8681 13.6042 13.6042C14.8681 12.3403 15.5 10.8056 15.5 9C15.5 7.19444 14.8681 5.65972 13.6042 4.39583C12.3403 3.13194 10.8056 2.5 9 2.5C7.19444 2.5 5.65972 3.13194 4.39583 4.39583C3.13194 5.65972 2.5 7.19444 2.5 9C2.5 10.8056 3.13194 12.3403 4.39583 13.6042C5.65972 14.8681 7.19444 15.5 9 15.5Z" fill="#8A8A8A"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2375">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/stop_cluster_disable_icon.svg b/style/icons/stop_cluster_disable_icon.svg
new file mode 100644
index 00000000..321dba6e
--- /dev/null
+++ b/style/icons/stop_cluster_disable_icon.svg
@@ -0,0 +1,3 @@
+<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="10" height="10" fill="#8A8A8A"/>
+</svg>
diff --git a/style/icons/stop_cluster_icon.svg b/style/icons/stop_cluster_icon.svg
new file mode 100644
index 00000000..e609a9d1
--- /dev/null
+++ b/style/icons/stop_cluster_icon.svg
@@ -0,0 +1,3 @@
+<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="10" height="10" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/stop_disable_icon.svg b/style/icons/stop_disable_icon.svg
new file mode 100644
index 00000000..7ca5f8cb
--- /dev/null
+++ b/style/icons/stop_disable_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2376)">
+<path d="M6 12H12V6H6V12ZM9 17C7.90278 17 6.86806 16.7917 5.89583 16.375C4.92361 15.9583 4.06944 15.3889 3.33333 14.6667C2.61111 13.9306 2.04167 13.0764 1.625 12.1042C1.20833 11.1319 1 10.0972 1 9C1 7.88889 1.20833 6.85417 1.625 5.89583C2.04167 4.92361 2.61111 4.07639 3.33333 3.35417C4.06944 2.61805 4.92361 2.04167 5.89583 1.625C6.86806 1.20833 7.90278 0.999999 9 0.999999C10.1111 0.999999 11.1458 1.20833 12.1042 1.625C13.0764 2.04167 13.9236 2.61805 14.6458 3.35417C15.3819 4.07639 15.9583 4.92361 16.375 5.89583C16.7917 6.85417 17 7.88889 17 9C17 10.0972 16.7917 11.1319 16.375 12.1042C15.9583 13.0764 15.3819 13.9306 14.6458 14.6667C13.9236 15.3889 13.0764 15.9583 12.1042 16.375C11.1458 16.7917 10.1111 17 9 17ZM9 15.5C10.8056 15.5 12.3403 14.8681 13.6042 13.6042C14.8681 12.3403 15.5 10.8056 15.5 9C15.5 7.19444 14.8681 5.65972 13.6042 4.39583C12.3403 3.13194 10.8056 2.5 9 2.5C7.19444 2.5 5.65972 3.13194 4.39583 4.39583C3.13194 5.65972 2.5 7.19444 2.5 9C2.5 10.8056 3.13194 12.3403 4.39583 13.6042C5.65972 14.8681 7.19444 15.5 9 15.5Z" fill="#8A8A8A"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2376">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/stop_icon.svg b/style/icons/stop_icon.svg
new file mode 100644
index 00000000..03acd479
--- /dev/null
+++ b/style/icons/stop_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2376)">
+<path d="M6 12H12V6H6V12ZM9 17C7.90278 17 6.86806 16.7917 5.89583 16.375C4.92361 15.9583 4.06944 15.3889 3.33333 14.6667C2.61111 13.9306 2.04167 13.0764 1.625 12.1042C1.20833 11.1319 1 10.0972 1 9C1 7.88889 1.20833 6.85417 1.625 5.89583C2.04167 4.92361 2.61111 4.07639 3.33333 3.35417C4.06944 2.61805 4.92361 2.04167 5.89583 1.625C6.86806 1.20833 7.90278 0.999999 9 0.999999C10.1111 0.999999 11.1458 1.20833 12.1042 1.625C13.0764 2.04167 13.9236 2.61805 14.6458 3.35417C15.3819 4.07639 15.9583 4.92361 16.375 5.89583C16.7917 6.85417 17 7.88889 17 9C17 10.0972 16.7917 11.1319 16.375 12.1042C15.9583 13.0764 15.3819 13.9306 14.6458 14.6667C13.9236 15.3889 13.0764 15.9583 12.1042 16.375C11.1458 16.7917 10.1111 17 9 17ZM9 15.5C10.8056 15.5 12.3403 14.8681 13.6042 13.6042C14.8681 12.3403 15.5 10.8056 15.5 9C15.5 7.19444 14.8681 5.65972 13.6042 4.39583C12.3403 3.13194 10.8056 2.5 9 2.5C7.19444 2.5 5.65972 3.13194 4.39583 4.39583C3.13194 5.65972 2.5 7.19444 2.5 9C2.5 10.8056 3.13194 12.3403 4.39583 13.6042C5.65972 14.8681 7.19444 15.5 9 15.5Z" fill="#444746"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2376">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/stop_icon_disable.svg b/style/icons/stop_icon_disable.svg
new file mode 100644
index 00000000..7ca5f8cb
--- /dev/null
+++ b/style/icons/stop_icon_disable.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1227_2376)">
+<path d="M6 12H12V6H6V12ZM9 17C7.90278 17 6.86806 16.7917 5.89583 16.375C4.92361 15.9583 4.06944 15.3889 3.33333 14.6667C2.61111 13.9306 2.04167 13.0764 1.625 12.1042C1.20833 11.1319 1 10.0972 1 9C1 7.88889 1.20833 6.85417 1.625 5.89583C2.04167 4.92361 2.61111 4.07639 3.33333 3.35417C4.06944 2.61805 4.92361 2.04167 5.89583 1.625C6.86806 1.20833 7.90278 0.999999 9 0.999999C10.1111 0.999999 11.1458 1.20833 12.1042 1.625C13.0764 2.04167 13.9236 2.61805 14.6458 3.35417C15.3819 4.07639 15.9583 4.92361 16.375 5.89583C16.7917 6.85417 17 7.88889 17 9C17 10.0972 16.7917 11.1319 16.375 12.1042C15.9583 13.0764 15.3819 13.9306 14.6458 14.6667C13.9236 15.3889 13.0764 15.9583 12.1042 16.375C11.1458 16.7917 10.1111 17 9 17ZM9 15.5C10.8056 15.5 12.3403 14.8681 13.6042 13.6042C14.8681 12.3403 15.5 10.8056 15.5 9C15.5 7.19444 14.8681 5.65972 13.6042 4.39583C12.3403 3.13194 10.8056 2.5 9 2.5C7.19444 2.5 5.65972 3.13194 4.39583 4.39583C3.13194 5.65972 2.5 7.19444 2.5 9C2.5 10.8056 3.13194 12.3403 4.39583 13.6042C5.65972 14.8681 7.19444 15.5 9 15.5Z" fill="#8A8A8A"/>
+</g>
+<defs>
+<clipPath id="clip0_1227_2376">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/submit_job_icon.svg b/style/icons/submit_job_icon.svg
new file mode 100644
index 00000000..edcf6988
--- /dev/null
+++ b/style/icons/submit_job_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 0.25H1.75C0.9175 0.25 0.25 0.925 0.25 1.75V12.25C0.25 13.075 0.9175 13.75 1.75 13.75H12.25C13.075 13.75 13.75 13.075 13.75 12.25V1.75C13.75 0.925 13.075 0.25 12.25 0.25ZM10.75 7.75H7.75V10.75H6.25V7.75H3.25V6.25H6.25V3.25H7.75V6.25H10.75V7.75Z" fill="#3367D6"/>
+</svg>
diff --git a/style/icons/succeeded_icon.svg b/style/icons/succeeded_icon.svg
new file mode 100644
index 00000000..737b7f6f
--- /dev/null
+++ b/style/icons/succeeded_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7 0.5C3.136 0.5 0 3.636 0 7.5C0 11.364 3.136 14.5 7 14.5C10.864 14.5 14 11.364 14 7.5C14 3.636 10.864 0.5 7 0.5ZM5.6 11L2.1 7.5L3.087 6.513L5.6 9.019L10.913 3.706L11.9 4.7L5.6 11Z" fill="#188038"/>
+</svg>
diff --git a/style/icons/table_icon.svg b/style/icons/table_icon.svg
new file mode 100644
index 00000000..92564226
--- /dev/null
+++ b/style/icons/table_icon.svg
@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3237_14396)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16 2H2V16H16V2ZM6 8H4V10H6V8ZM4 4H14V6H4V4ZM6 12H4V14H6V12ZM8 8H10V10H8V8ZM14 8H12V10H14V8ZM8 12H10V14H8V12ZM14 12H12V14H14V12Z" fill="black" fill-opacity="0.66"/>
+</g>
+<defs>
+<clipPath id="clip0_3237_14396">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/style/icons/view_logs_icon.svg b/style/icons/view_logs_icon.svg
new file mode 100644
index 00000000..c46cf96e
--- /dev/null
+++ b/style/icons/view_logs_icon.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12.25H1.75V1.75H7V0.25H1.75C0.9175 0.25 0.25 0.925 0.25 1.75V12.25C0.25 13.075 0.9175 13.75 1.75 13.75H12.25C13.075 13.75 13.75 13.075 13.75 12.25V7H12.25V12.25ZM8.5 0.25V1.75H11.1925L3.82 9.1225L4.8775 10.18L12.25 2.8075V5.5H13.75V0.25H8.5Z" fill="#3367D6"/>
+</svg>
diff --git a/style/index.css b/style/index.css
new file mode 100644
index 00000000..791eec77
--- /dev/null
+++ b/style/index.css
@@ -0,0 +1,14 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+@import url('base.css');
+@import url('cluster.css');
+@import url('job.css');
+@import url('clusterDetails.css');
+@import url('submitJob.css');
+@import url('batches.css');
+@import url('authLogin.css');
+@import url('sessionDetails.css');
+@import url('popup.css');
+@import url('createBatch.css');
+@import url('databaseInfo.css');
+@import url('dpms.css');
+@import url('paginationView.css');
diff --git a/style/index.js b/style/index.js
new file mode 100644
index 00000000..0ecf4694
--- /dev/null
+++ b/style/index.js
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 './base.css';
+import './cluster.css';
+import './job.css';
+import './clusterDetails.css';
+import './submitJob.css';
+import './batches.css';
+import './authLogin.css';
+import './sessionDetails.css';
+import './popup.css';
+import './createBatch.css'
+import './popup.css'
+import './dpms.css'
+import './databaseInfo.css'
+import './paginationView.css';
diff --git a/style/job.css b/style/job.css
new file mode 100644
index 00000000..f19cb734
--- /dev/null
+++ b/style/job.css
@@ -0,0 +1,252 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+.cluster-details-value-job {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  color: #3367d6;
+  text-decoration: underline;
+  cursor: pointer;
+  height: 29px;
+}
+
+.job-label-style {
+  background-color: rgba(0, 0, 0, 0.08);
+  padding: 0px 10px;
+  border-radius: 10px;
+  color: rgb(0, 0, 0);
+  font-weight: 400;
+  line-height: 20px;
+  margin: 3px 0px;
+  margin-right: 5px;
+}
+
+.job-argument-style {
+  padding: 2px;
+}
+
+.edit-input-style {
+  margin-right: 20px;
+  width: 90%;
+}
+
+.key-message-wrapper {
+  width: 90%;
+}
+
+.job-label-edit-parent {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  width: 50%;
+  color: rgba(0, 0, 0, 0.66);
+  padding: 10px;
+}
+
+.job-label-edit-row {
+  display: flex;
+  margin: 5px 0px;
+  padding-bottom: 18px;
+}
+
+.job-label-style-parent {
+  display: flex;
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  width: 65%;
+  flex-wrap: wrap;
+  color: rgba(0, 0, 0, 0.66);
+}
+
+.job-edit-header {
+  padding: 0px 15px;
+  height: 48px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.job-save-button-style {
+  margin-right: 10px;
+  background: #3367d6;
+  color: #ffffff;
+  height: 30px;
+  font-size: 13px;
+  width: 55px;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 4px;
+}
+
+.job-button-style-parent {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  min-height: 55px;
+}
+
+.job-cancel-button-style {
+  margin-right: 10px;
+  background: white;
+  color: #000000;
+  height: 30px;
+  width: 75px;
+  font-size: 13px;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 4px;
+  font-weight: 600;
+}
+.job-cancel-button-style:hover {
+  background-color: #d3d3d3;
+}
+
+.job-add-label-button-disabled {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px 12px;
+  gap: 4px;
+
+  width: 110px;
+  height: 32px;
+
+  background: #ffffff;
+  color: rgba(0, 0, 0, 0.54);
+}
+
+.job-add-label-button {
+  cursor: pointer;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px 12px;
+  gap: 4px;
+
+  width: 110px;
+  height: 32px;
+
+  background: #ffffff;
+  box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.12), 0px 2px 2px rgba(0, 0, 0, 0.2),
+    0px 1px 1px rgba(0, 0, 0, 0.14);
+  border-radius: 4px;
+}
+
+.job-edit-button {
+  cursor: pointer;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px 12px;
+  gap: 4px;
+
+  width: 75px;
+  height: 32px;
+
+  background: #ffffff;
+  box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.12), 0px 2px 2px rgba(0, 0, 0, 0.2),
+    0px 1px 1px rgba(0, 0, 0, 0.14);
+  border-radius: 4px;
+}
+
+.job-edit-button-disabled {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px 12px;
+  gap: 4px;
+  color: rgba(0, 0, 0, 0.54);
+
+  width: 75px;
+  height: 32px;
+
+  background: #ffffff;
+  opacity: 0.5;
+}
+
+.job-edit-text-disabled {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 32px;
+  /* identical to box height, or 246% */
+
+  display: flex;
+  align-items: center;
+  text-align: center;
+  text-transform: uppercase;
+
+  /* Quick Selection/Blue 700 */
+
+  color: rgba(0, 0, 0, 0.54);
+}
+
+.color-icon {
+  color: #3367d6;
+  display: flex;
+}
+
+.color-icon-disabled {
+  color: rgba(0, 0, 0, 0.54);
+  display: flex;
+}
+
+.job-edit-text {
+  font-style: normal;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 32px;
+  /* identical to box height, or 246% */
+
+  display: flex;
+  align-items: center;
+  text-align: center;
+  text-transform: uppercase;
+
+  /* Quick Selection/Blue 700 */
+
+  color: #3367d6;
+}
+
+.job-delete-icon {
+  margin-top: 6px;
+  cursor: pointer;
+}
+
+.job-label-style-list {
+  background-color: rgba(0, 0, 0, 0.08);
+  padding: 0px 10px;
+  width: fit-content;
+  border-radius: 10px;
+  color: rgb(0, 0, 0);
+  font-weight: 400;
+  line-height: 20px;
+  margin: 3px 0px;
+}
+
+.jobs-list-table-parent {
+  height: 63vh;
+  overflow: auto;
+}
+
+.jobs-list-table-parent-small {
+  height: 49vh;
+  overflow: auto;
+}
+.job-details-label-row {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  display: flex;
+  justify-content: flex-start;
+  align-items: flex-start;
+  min-height: 18px;
+  margin-bottom: 6px;
+}
diff --git a/style/paginationView.css b/style/paginationView.css
new file mode 100644
index 00000000..5ccf5463
--- /dev/null
+++ b/style/paginationView.css
@@ -0,0 +1,24 @@
+.page-move-button.disabled {
+  opacity: 0.5;
+}
+
+.page-move-button {
+  padding: 0px 15px;
+  font-size: 25px;
+  color: #000000;
+}
+
+.page-size-selection {
+  border: none;
+  margin-left: 5px;
+}
+
+.pagination-parent-view {
+  display: flex;
+  justify-content: flex-end;
+  margin: 10px;
+}
+
+.page-display-part {
+  padding-left: 20px;
+}
\ No newline at end of file
diff --git a/style/popup.css b/style/popup.css
new file mode 100644
index 00000000..30724074
--- /dev/null
+++ b/style/popup.css
@@ -0,0 +1,34 @@
+.popup-main{
+    padding: 25px 25px;
+}
+.popup-header{
+    font-weight: bold;
+    font-size: 18px;
+    line-height: 30px;
+    padding-bottom: 15px;
+}
+.popup-text{
+    font-weight: 500;
+    font-size: 14px;
+    line-height: 30px;
+    padding-bottom: 15px;
+}
+.popup-button-style {
+    margin-right: 10px;
+    color: #3367d6;
+    height: 30px;
+    width: 75px;
+    font-size: 13px;
+    cursor: pointer;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: 4px;
+    font-weight: 600;
+  }
+  .popup-buttons{
+    display: flex;
+    margin-left:auto;
+    margin-right: 0;
+    justify-content: right;
+  }
\ No newline at end of file
diff --git a/style/sessionDetails.css b/style/sessionDetails.css
new file mode 100644
index 00000000..59aab0ed
--- /dev/null
+++ b/style/sessionDetails.css
@@ -0,0 +1,68 @@
+.session-details-label {
+    font-style: normal;
+    font-weight: 500;
+    font-size: 13px;
+    line-height: 18px;
+    width: 35%;
+    color: #000000;
+    overflow-wrap: anywhere;
+    padding-left : 15px;
+}
+.session-env-details-label{
+    font-style: normal;
+    font-weight: 500;
+    font-size: 13px;
+    line-height: 18px;
+    width: 35%;
+    color: #000000;
+    overflow-wrap: anywhere;
+    padding-left: 35px;
+}
+
+.session-details-value {
+    font-style: normal;
+    font-weight: 400;
+    font-size: 13px;
+    line-height: 18px;
+    width: 65%;
+    color: rgba(0, 0, 0, 0.66);
+    padding-left: 20px;
+}
+
+.session-env-details-value {
+    font-style: normal;
+    font-weight: 400;
+    font-size: 13px;
+    line-height: 18px;
+    width: 65%;
+    color: rgba(0, 0, 0, 0.66);
+    padding-left: 20px;
+}
+.session-detail-status-parent{
+    display: flex;
+    align-items: center;
+    height: 29px;
+    color: rgba(0, 0, 0, 0.66);
+    padding-left: 20px;
+}
+.session-label-style-parent {
+    display: flex;
+    font-style: normal;
+    font-weight: 400;
+    font-size: 13px;
+    line-height: 18px;
+    width: 65%;
+    color: rgba(0, 0, 0, 0.66);
+    padding-left: 20px;
+    padding-top: 5px;
+    padding-bottom: 5px;
+}
+.session-loader{
+    display: flex;
+    align-items: end;
+}
+
+  .session-list-table-parent {
+    height: 70vh;
+    overflow: auto;
+  }
\ No newline at end of file
diff --git a/style/submitJob.css b/style/submitJob.css
new file mode 100644
index 00000000..345a3886
--- /dev/null
+++ b/style/submitJob.css
@@ -0,0 +1,231 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+.submit-job-container {
+  margin-left: 15px;
+  margin-bottom: 15px;
+  display: flex;
+  flex-direction: column;
+}
+
+.submit-job-label-header {
+  margin-top: 15px;
+  font-style: normal;
+  font-weight: 500;
+  font-size: 20px;
+  line-height: 28px;
+  display: flex;
+  align-items: center;
+  color: #000000;
+}
+
+.job-add-property-button-disabled {
+  margin-top: 15px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px 12px;
+  gap: 4px;
+
+  width: 150px;
+  height: 32px;
+
+  background: #ffffff;
+  color: rgba(0, 0, 0, 0.54);
+}
+
+.job-add-property-button {
+  margin-top: 15px;
+  cursor: pointer;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 0px 12px;
+  gap: 4px;
+
+  width: 150px;
+  height: 32px;
+
+  background: #ffffff;
+  box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.12), 0px 2px 2px rgba(0, 0, 0, 0.2),
+    0px 1px 1px rgba(0, 0, 0, 0.14);
+  border-radius: 4px;
+}
+
+.select-job-style {
+  background: #ffffff;
+  border: 1px solid black !important;
+  border-radius: 4px;
+  width: 50%;
+  margin-top: 10px;
+  min-height: 40px;
+  outline: none;
+}
+.input-style{
+    background: #ffffff;
+    width: 50%;
+    margin-top: 10px;
+    min-height: 40px;
+    outline: none;
+}
+.react-tagsinput-input {
+  font-family: 'Roboto' !important;
+  color: black !important;
+  font-style: normal;
+  font-weight: 400;
+  background: transparent;
+  border: 0;
+  font-size: 13px;
+  margin-bottom: 10px !important;
+  margin-top: 10px !important;
+  outline: none !important;
+  padding: 0px !important;
+  width: 95% !important;
+  margin-left: 15px;
+}
+
+.react-tagsinput-tag {
+  border: 1px solid #d3d3d3 !important;
+  display: inline-block;
+  font-family: 'Roboto' !important;
+  font-size: 13px;
+  font-weight: 400;
+  margin-bottom: 5px;
+  margin-right: 5px;
+  padding: 5px;
+  background-color: lightgrey !important;
+  border-radius: 20px !important;
+  color: black !important;
+  margin-top: 5px !important;
+}
+.react-tagsinput-remove {
+  border-radius: 50% !important;
+  padding: 4px !important;
+  margin-left: 4px !important;
+  background-color: grey !important;
+  padding-right: 6px !important;
+}
+a {
+  color: black !important;
+}
+a:hover {
+  color: black !important;
+}
+.submit-job-cluster-message {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 10px;
+  padding-top: 10px;
+  padding-left: 5px;
+  color: #000000;
+}
+.submit-job-learn-more {
+  color: #3367d6;
+  text-decoration: underline;
+  cursor: pointer;
+  padding-left: 3px;
+}
+
+.submit-job-message {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 10px;
+  line-height: 15px;
+  padding-left: 5px;
+  color: #575757;
+  width: 50%;
+  overflow: hidden;
+  word-wrap: break-word;
+}
+
+.submit-job-message-with-link {
+  font-style: normal;
+  font-weight: 400;
+  font-size: 10px;
+  line-height: 15px;
+  padding-left: 5px;
+  color: #575757;
+  width: 50%;
+  overflow: hidden;
+  display: flex;
+  word-wrap: break-word;
+}
+.jobType-select {
+  box-sizing: border-box;
+  background: #ffffff !important;
+  border: 1px solid rgba(0, 0, 0, 0.38) !important;
+  display: block !important;
+  height: 40px;
+  width: 100%;
+  border-radius: 4px !important;
+  appearance: none !important;
+  -webkit-appearance: none !important;
+  -moz-appearance: none;
+  transform: none !important;
+  position: unset !important;
+}
+.jobType-overlay {
+  position: relative;
+  margin-top: 20px;
+  width: 50%;
+}
+.submit-button-disable-style {
+  margin-right: 10px;
+  background: white;
+  color: #575757;
+  height: 30px;
+  font-size: 13px;
+  width: 70px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 4px;
+  font-weight: 600;
+}
+.submit-button-style {
+  margin-right: 10px;
+  background: #3367d6;
+  color: #ffffff;
+  height: 30px;
+  width: 70px;
+  font-size: 13px;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 4px;
+}
+
+.error-key-parent {
+  padding-top: 5px;
+  display: flex;
+  align-items: center;
+  width: 90%;
+}
+
+.error-key-missing {
+  color: #d50000;
+  padding-left: 5px;
+  font-size: 10px;
+}
+
+/* Highlight input field when focus */
+.ui.input>input {
+  outline: none;
+  border-radius: 4px;
+  border: 1px solid black !important;
+}
+.ui.input>input:focus{
+  border: 3px solid #3367D6 !important;
+  border-radius: 4px;
+  outline: none;
+}
+
+/* Highlight tags input field when focus */
+.react-tagsinput--focused{
+  border: 3px solid #3367D6 !important;
+}
+
+.labels-delete-icon {
+  padding-top: 7px;
+}
\ No newline at end of file
diff --git a/third-party-licenses.txt b/third-party-licenses.txt
new file mode 100644
index 00000000..474493ea
--- /dev/null
+++ b/third-party-licenses.txt
@@ -0,0 +1,4225 @@
+This file was generated with the generate-license-file npm package!
+https://www.npmjs.com/package/generate-license-file
+
+The following npm package may be included in this product:
+
+ - @babel/runtime@7.22.10
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @codemirror/state@6.2.1
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - @fluentui/react-component-event-listener@0.63.1
+ - @fluentui/react-component-ref@0.63.1
+
+These packages each contain the following license and notice below:
+
+(MIT)
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @fortawesome/fontawesome-free@5.15.4
+
+This package contains the following license and notice below:
+
+Font Awesome Free License
+-------------------------
+
+Font Awesome Free is free, open source, and GPL friendly. You can use it for
+commercial projects, open source projects, or really almost whatever you want.
+Full Font Awesome Free license: https://fontawesome.com/license/free.
+
+# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
+In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
+packaged as SVG and JS file types.
+
+# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
+In the Font Awesome Free download, the SIL OFL license applies to all icons
+packaged as web and desktop font files.
+
+# Code: MIT License (https://opensource.org/licenses/MIT)
+In the Font Awesome Free download, the MIT license applies to all non-font and
+non-icon files.
+
+# Attribution
+Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
+Awesome Free files already contain embedded comments with sufficient
+attribution, so you shouldn't need to do anything additional when using these
+files normally.
+
+We've kept attribution comments terse, so we ask that you do not actively work
+to remove them from files, especially code. They're a great way for folks to
+learn about Font Awesome.
+
+# Brand Icons
+All brand icons are trademarks of their respective owners. The use of these
+trademarks does not indicate endorsement of the trademark holder by Font
+Awesome, nor vice versa. **Please do not use brand logos for any purpose except
+to represent the company, product, or service to which they refer.**
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyter/ydoc@1.0.2
+
+This package contains the following license and notice below:
+
+# @jupyter/ydoc
+
+`@jupyter/ydoc` provides [Yjs](https://github.com/yjs/yjs)-based data structures for various
+documents used in the Jupyter ecosystem. Built-in documents include:
+- `YFile`: a generic text document.
+- `YNotebook`: a Jupyter notebook document.
+
+The API documentation is available [there](https://jupyter-ydoc.readthedocs.io/en/latest/api/index.html).
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/application@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/application
+
+A JupyterLab package that provides the top-level application object,
+with which JupyterLab plugins may be registered.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/apputils@4.1.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/apputils
+
+A JupyterLab package which provides a collection of utilities and UI elements for building an application and manipulating the DOM.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/codeeditor@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/codeeditor
+
+A JupyterLab package which defines an abstract interface to a code editor,
+which is used in many places in the application, including [cells](../cells)
+and the [file editor](../fileeditor).
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/coreutils@6.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/coreutils
+
+A JupyterLab package which provides utility functions that are widely used
+across many of the `@jupyterlab` packages. This includes (among other things)
+functions for manipulating paths, urls, and the notebook format.
+
+This package is intended for use within both Node.js and browser environments.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/docregistry@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/docregistry
+
+A JupyterLab package which tracks the different types of documents that the application is able to interact with.
+This includes notebooks, text files, and base64 encoded documents.
+
+Extensions may register new document types with the document registry to allow them to be opened with JupyterLab.
+An example of this may be found in the [@jupyterlab/notebook](../notebook) package.
+
+The document registry is a singleton on the [application](../application).
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/launcher@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/launcher
+
+A JupyterLab package that provides a launcher for various activities,
+including notebooks, consoles, text editors, and terminals.
+
+JupyterLab extensions may register themselves with the launcher in order to show up as an activity when the application starts.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/mainmenu@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/mainmenu
+
+A JupyterLab extension which provides the application menubar.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - @jupyterlab/nbformat@4.0.4
+ - @jupyterlab/settingregistry@4.0.4
+ - @jupyterlab/statedb@4.0.4
+ - @jupyterlab/statusbar@4.0.4
+ - @lumino/algorithm@2.0.1
+ - @lumino/application@2.2.1
+ - @lumino/collections@2.0.1
+ - @lumino/commands@2.1.3
+ - @lumino/coreutils@2.1.2
+ - @lumino/disposable@2.1.2
+ - @lumino/domutils@2.0.1
+ - @lumino/dragdrop@2.1.3
+ - @lumino/keyboard@2.0.1
+ - @lumino/messaging@2.0.1
+ - @lumino/properties@2.0.1
+ - @lumino/signaling@2.1.2
+ - @lumino/virtualdom@2.0.1
+ - @lumino/widgets@2.3.0
+
+These packages each contain the following license and notice below:
+
+(BSD-3-Clause)
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/observables@5.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/observables
+
+A JupyterLab package which provides data structures (such as strings, lists, and maps), which can be listened to for changes.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/rendermime-interfaces@3.8.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/rendermime-interfaces
+
+A package for JupyterLab which provides interfaces for implementing mime renderer extensions.
+
+A general JupyterLab plugin involves a certain amount of boilerplate code
+that can be annoying for authors of relatively extensions.
+The interfaces in this package are meant to give an easier way for extension authors
+to provide a plugin that renders mime bundles and documents of a specific mime type.
+
+When using these interfaces, extensions only need to provide some metadata about
+what kind of mime bundle they are able to render, and a `Widget` with
+a `renderModel` method that renders the mime bundle.
+
+Examples can be found in [@jupyterlab/vega5-extension](../vega5-extension) and [@jupyterlab/pdf-extension](../pdf-extension).
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/rendermime@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/rendermime
+
+A JupyterLab package which manages mime bundle renderers for the application,
+and provides default renderers for a number of formats, such as markdown,
+HTML, images, and LaTeX.
+
+A simplified interface for adding new mime renderers to the application
+can be found in [@jupyterlab/rendermime-interfaces](../rendermime-interfaces).
+
+The rendermime is a singleton on the [application](../application).
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/services@7.0.4
+
+This package contains the following license and notice below:
+
+# JupyterLab Services
+
+Javascript client for the Jupyter services REST APIs
+
+[API Docs](https://jupyterlab.readthedocs.io/en/stable/api/)
+
+[REST API Docs](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter-server/jupyter_server/main/jupyter_server/services/api/api.yaml)
+
+Note: All functions and classes using the REST API allow a `serverSettings`
+parameter to configure requests.
+Requests are made using the `fetch` API, which is available in modern browsers
+or via `npm install fetch` for node users. The `whatwg-fetch` npm package
+can be used to polyfill browsers that do not support the `fetch` API.
+
+## Package Install
+
+**Prerequisites**
+
+- [node](http://nodejs.org/)
+- Python, e.g. via [conda](https://www.anaconda.com/download)
+
+```bash
+npm install --save @jupyterlab/services
+conda install jupyter-server # jupyter-server 2.0+ is recommended
+# or `pip install jupyter-server`
+```
+
+## Source Build
+
+**Prerequisites**
+
+See the [building instructions for JupyterLab](../../CONTRIBUTING.md), which
+will build this module as part of the build process.
+
+**Rebuild**
+
+```bash
+yarn run clean
+yarn run build
+```
+
+## Run Tests
+
+Follow the source build instructions first.
+
+```bash
+yarn run test
+```
+
+## Build Docs
+
+Follow the source build instructions first.
+
+```bash
+yarn run docs
+```
+
+Navigate to `docs/index.html`.
+
+## Supported Runtimes
+
+The runtime versions which should work are listed below. Earlier versions may
+also work, but come with no guarantees.
+
+- Node 10
+- Firefox 52+
+- Chrome 55+
+
+Note: "requirejs" may need be included in a global context for `Comm` targets
+using the a `target_module` (in the classic Notebook).
+This can be as a `<script>` tag in the browser or by using the `requirejs`
+package in node (`npm install requirejs` and setting
+`global.requirejs = require('requirejs');`).
+
+## Starting the Jupyter Server
+
+Follow the package install instructions first.
+
+The library requires a running Jupyter Server, launched as:
+
+```bash
+jupyter server
+```
+
+or
+
+```bash
+jupyter lab
+```
+
+## Bundling for the Browser
+
+Follow the package install instructions first.
+
+See `examples/browser` for an example of using Webpack to bundle the library.
+
+## Usage from Node.js
+
+Follow the package install instructions first.
+
+See `examples/node` for an example of using an ES5 node script.
+
+## Usage Examples
+
+**Note:** This package is compiled to ES2018 JavaScript syntax from
+TypeScript. Here are some examples of using parts of this package. See the
+other `examples` subdirectories for more examples.
+
+- [Comms](./examples/browser/src/comm.ts)
+- [Config](./examples/browser/src/config.ts)
+- [Contents](./examples/browser/src/contents.ts)
+- [Kernel](./examples/browser/src/kernel.ts)
+- [Session](./examples/browser/src/session.ts)
+- [Terminal](./examples/browser/src/terminal.ts)
+
+## Overview
+
+This package introduces a number of concepts, such as session context, etc.
+Here we give a brief overview of some of the top-level concepts in this
+package.
+
+### Clients
+
+A _client_ is a single entity connected to a kernel. Since kernel messages
+include the client id, it is easy for a client to filter kernel messages for
+just messages between it and the kernel. In JupyterLab, different activities
+(such as a console and a notebook) are usually considered separate clients when
+connected to the same kernel.
+
+### Kernel specs
+
+A _kernel spec_ is the data about an available kernel on the system. We can
+retrieve a current list of kernel specs from the server.
+
+### Kernels
+
+A _kernel_ represents a running process on the server that implements the
+Jupyter kernel messaging protocol.
+
+#### Kernel model
+
+A _kernel model_ mirrors the server kernel models, and represents a single
+running kernel on the server. A kernel can be created, restarted, shut down,
+etc., through calls to the server. A kernel model's lifecycle mirrors the
+server kernel model's lifecycle, and it will be disposed when the server
+kernel is shut down.
+
+#### Kernel connection
+
+A _kernel connection_ represents a single client connecting to a kernel over a
+websocket. Typically only one kernel connection handles comms for any given
+kernel. The kernel connection is disposed when the client no longer has a need
+for the connection. Disposing a kernel connection does not cause the kernel to
+shut down. However, if a kernel is shut down, (eventually) all of its kernel
+connections should be disposed if they were initiated from a kernel manager.
+If the kernel connections were instantiated outside of a manager, you are
+responsible for cleaning them up.
+
+A kernel connection has a number of signals, such as kernel status, kernel
+connection status, etc.
+
+#### Kernel manager
+
+A _kernel manager_ is an object that maintains a list of kernel models by
+regular polling. The kernel manager can instantiate a kernel connection and
+will manage its lifecycle (e.g., when the kernel is shut down, the connections
+will be disposed). The manager provides some minimal bookkeeping around
+kernels and their connections. Generally, it is easiest to interact with
+kernels on a server through a manager.
+
+### Sessions
+
+A _session_ is a mapping on the server from an identifying string (the
+session's `path`) to a kernel. A session has a few other pieces of information
+to allow for easy categorization and searching of sessions.
+
+The primary usecase of a session is to enable persisting a connection to a
+kernel. For example, a notebook viewer may start a session with session path
+of the notebook's file path. When a browser is refreshed, the notebook viewer
+can connect to the same kernel by asking the server for the session
+corresponding with the notebook file path.
+
+#### Session model
+
+A _session model_ mirrors a server session. The session models can be
+refreshed from the server, created, changed (including creating a new session
+kernel), and shut down (which implies that the kernel will be shut down). A
+session model's lifecycle mirrors the server session's lifecycle, and it will
+be disposed when the server session is shut down.
+
+#### Session connection
+
+A _session connection_ represents a single client connected to a session's
+kernel. A session's kernel connection can change and may be null to signify no
+current kernel connection. A session connection owns the kernel connection,
+meaning the kernel connection is created and disposed by the session
+connection as needed. The session connection proxies signals from the kernel
+connection for convenience (e.g., you can listen to the session's status
+signal to get status changes for whatever the current kernel is, without
+having to disconnect and reconnect your signal handlers every time the session
+kernel changes). The session connection can be disposed when the client no
+longer is connected to that session's kernel, and disposal will not cause the
+session model to be deleted.
+
+#### Session manager
+
+A _session manager_ is an object that maintains a list of session models by
+regular polling. The session manager can instantiate a session connection and
+will manage its lifecycle (e.g., when the session is shut down, the connections
+will be disposed). The manager provides some minimal bookkeeping around
+sessions and their connections. Generally, it is easiest to interact with
+sessions on a server through a manager.
+
+### Session Context
+
+A _session context_ is an object which has the same lifecycle as the client.
+The session context owns a session connection (which may be null if the client
+is not currently associated with a session). The session context proxies the
+current session connection's signals for convenience. The session context
+primarily serves as a stable object for a client to keep track of the current
+session connection. The session context also contains some convenience
+functionality, such as preferences for whether a kernel should be started and
+a user-friendly kernel name and status.
+
+Here is a diagram that provides an overview of the different components:
+
+![architecture-diagram](./architecture.png)
+
+_The diagram can be edited on [diagrams.net](https://diagrams.net) by importing the [source](./architecture.xml)_.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/translation@4.0.4
+
+This package contains the following license and notice below:
+
+# @jupyterlab/translation
+
+Translation utilities.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @jupyterlab/ui-components@4.0.4
+
+This package contains the following license and notice below:
+
+<!--
+THIS FILE IS AUTOGENERATED, DO NOT EDIT
+
+Instead, make changes to docs sources in `packages/ui-components/docs`,
+then run "jlpm docs:init" to refresh the built docs
+-->
+
+# @jupyterlab/ui-components
+
+The
+[@jupyterlab/ui-components](https://jupyterlab.github.io/jupyterlab/modules/_ui_components_src_index_.html)
+package provides UI elements that are widely used in JupyterLab core,
+and that can be reused in your own extensions.
+
+For example, all of the icons in JupyterLab core can be reused via
+`LabIcon`. You can also use `LabIcon` to create your own custom icons
+that will be able to automatically change color to match the current
+JupyterLab theme.
+
+# `LabIcon` - set up and render icons
+
+`LabIcon` is the icon class used by JupyterLab, and is part of the new
+icon system introduced in JupyterLab v2.0.
+
+## How JupyterLab handles icons
+
+The @jupyterlab/ui-components package provides icons to the rest of
+JupyterLab, in the form of a set of `LabIcon` instances (currently about
+80). All of the icons in the core JupyterLab packages are rendered using
+one of these `LabIcon` instances.
+
+## Using the icons in your own code
+
+You can use any of JupyterLab icons in your own code via an `import`
+statement. For example, to use `jupyterIcon` you would first do:
+
+```typescript
+import { jupyterIcon } from '@jupyterlab/ui-components';
+```
+
+## How to render an icon into a DOM node
+
+Icons can be added as children to any `div` or `span` nodes using the
+`icon.element(...)` method (where `icon` is any instance of `LabIcon`).
+For example, to render the Jupyter icon you could do:
+
+```typescript
+jupyterIcon.element({
+  container: elem,
+  height: '16px',
+  width: '16px',
+  marginLeft: '2px'
+});
+```
+
+where `elem` is any `HTMLElement` with a `div` or `span` tag. As shown
+in the above example, the icon can be styled by passing CSS parameters
+into `.element(...)`. Any valid CSS parameter can be used (one catch:
+snake case params do have to be converted to camel case: instead of
+`foo-bar: '8px'`, you’d need to use `fooBar: '8px'`.
+
+## How to render an icon as a React component
+
+Icons can also be rendered using React. The `icon.react` parameter holds
+a standard React component that will display the icon on render. Like
+any React component, `icon.react` can be used in various ways.
+
+For example, here is how you would add the Jupyter icon to the render
+tree of another React component:
+
+```jsx
+public render() {
+  return (
+    <div className="outer">
+      <div className="inner">
+        <jupyterIcon.react tag="span" right="7px" top="5px" />
+        "and here's a text node"
+      </div>
+    </div>
+  );
+}
+```
+
+Alternatively, you can just render the icon directly into any existing
+DOM node `elem` by using the `ReactDOM` module:
+
+```typescript
+const root = createRoot(elem);
+root.render(jupyterIcon.react);
+```
+
+If do you use `ReactDOM` to render, and if the `elem` node is ever
+removed from the DOM, you’ll first need to clean it up:
+
+```typescript
+root.unmount();
+```
+
+This cleanup step is not a special property of `LabIcon`, but is instead
+needed for any React component that is rendered directly at the top
+level by `ReactDOM`.
+
+## How to create your own custom `LabIcon`
+
+You can create your own custom icon by constructing a new instance of
+`LabIcon`:
+
+```typescript
+export const fooIcon = new LabIcon({
+  name: 'barpkg:foo',
+  svgstr: '<svg>...</svg>'
+});
+```
+
+where `name` should be of the form “your-pkg:icon-name”, and `svgstr` is
+the raw contents of your icon’s svg file.
+
+## How to create a new `LabIcon` from an external svg file
+
+Although you can copy-and-paste an svg directly into the `LabIcon`
+constructor, the best practice is to keep the svg for each of your icons
+in its own separate svg file. You will need to have an `svg.d.ts` file
+at the root of your project’s `src` directory:
+
+```typescript
+// svg.d.ts
+
+declare module '*.svg' {
+  const value: string;
+  export default value;
+}
+```
+
+You can then `import` the contents of an svg file:
+
+```typescript
+import fooSvgstr from 'path-to-your/foo.svg';
+
+export const fooIcon = new LabIcon({
+  name: 'barpkg:foo',
+  svgstr: fooSvgstr
+});
+```
+
+## Sync icon color to JupyterLab theme
+
+<em>Example svgs with class annotation can be found in <a href="https://github.com/jupyterlab/jupyterlab/tree/f0153e0258b32674c9aec106383ddf7b618cebab/packages/ui-components/style/icons">ui-components/style/icons</a></em>
+
+You can ensure that the colors of your custom `LabIcon` sync up to the
+colors of the current JupyterLab theme by adding appropriate `class`
+annotations to each colored element of your icon's svg.
+
+In other words, each element of your svg that a `fill="..."` or a
+`stroke="..."` property should also have a `class="jp-icon<whatever>"`
+property.
+
+### Available icon classes
+
+<em>Icon-related CSS classes are defined in <a href="https://github.com/jupyterlab/jupyterlab/blob/f0153e0258b32674c9aec106383ddf7b618cebab/packages/ui-components/style/icons.css">ui-components/style/icons.css</a></em>
+
+All colors shown are for the standard light/dark theme, mouse over for
+hex values.
+
+#### `jp-iconX`: contrast to theme background
+
+<ul>
+<li>jp-icon0: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#111"/><title>#111</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#fff"/><title>#fff</title></svg></li>
+<li>jp-icon1: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#212121"/><title>#212121</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#fff"/><title>#fff</title></svg></li>
+<li>jp-icon2: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#424242"/><title>#424242</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#eee"/><title>#eee</title></svg></li>
+<li>jp-icon3: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#616161"/><title>#616161</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#bdbdbd"/><title>#bdbdbd</title></svg></li>
+<li>jp-icon4: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#757575"/><title>#757575</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#757575"/><title>#757575</title></svg></li>
+</ul>
+
+Most one-color icons in JupyterLab (including the sidebar and toolbar
+icons) are colored using the `jp-icon3` class.
+
+For light/dark themes, `jp-icon0` corresponds to the darkest/lightest
+background color, while `jp-icon1` is somewhat lighter/darker, and so
+forth.
+
+#### `jp-icon-accentX`: match to theme background
+
+<ul>
+<li>jp-icon-accent0: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#fff"/><title>#fff</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#111"/><title>#111</title></svg></li>
+<li>jp-icon-accent1: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#fff"/><title>#fff</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#212121"/><title>#212121</title></svg></li>
+<li>jp-icon-accent2: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#eee"/><title>#eee</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#424242"/><title>#424242</title></svg></li>
+<li>jp-icon-accent3: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#bdbdbd"/><title>#bdbdbd</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#616161"/><title>#616161</title></svg></li>
+<li>jp-icon-accent4: <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#757575"/><title>#757575</title></svg> / <svg width="16" viewBox="0 0 1 1"><rect width="1" height="1" fill="#757575"/><title>#757575</title></svg></li>
+</ul>
+
+For light/dark themes, `jp-icon-accent0` corresponds to the
+lightest/darkest background color, while `jp-icon-accent1` is somewhat
+darker/lighter, and so forth.
+
+### Adding classes to a one-color icon
+
+For most simple, one-color icons, it is desirable for the icon's color
+to strongly contrast with that of the application's background. You can
+achieve this using one of the `jp-iconX` classes.
+
+**Example: check icon**
+
+_svg source:_
+
+```html
+<svg xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24">
+  <path
+    class="jp-icon3"
+    fill="#616161"
+    d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
+  />
+</svg>
+```
+
+_rendered icon:_
+
+<svg xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24">
+  <path class="jp-icon3" fill="#616161" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+</svg>
+
+### Adding classes to a multi-colored icon
+
+For more complex icons, each element that needs to match the background
+should be annotated with a `jp-icon-accentX` class, while each element
+that needs to contrast with the background should be annotated with a
+`jp-iconX` class.
+
+**Example: close-circle icon**
+
+_svg source:_
+
+```html
+<svg xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24">
+  <circle class="jp-icon3" fill="#616161" cx="12" cy="12" r="11" />
+  <rect
+    class="jp-icon-accent0"
+    fill="#fff"
+    height="18"
+    width="2"
+    x="11"
+    y="3"
+    transform="rotate(315, 12, 12)"
+  />
+  <rect
+    class="jp-icon-accent0"
+    fill="#fff"
+    height="18"
+    width="2"
+    x="11"
+    y="3"
+    transform="rotate(45, 12, 12)"
+  />
+</svg>
+```
+
+_rendered icon:_
+
+<svg xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24">
+  <circle class="jp-icon3" fill="#616161" cx="12" cy="12" r="11"/>
+  <rect class="jp-icon-accent0" fill="#fff" height="18" width="2" x="11" y="3" transform="rotate(315, 12, 12)"/>
+  <rect class="jp-icon-accent0" fill="#fff" height="18" width="2" x="11" y="3" transform="rotate(45, 12, 12)"/>
+</svg>
+
+## Background
+
+### Icon handling in Jupyterlab
+
+Pre JupyterLab 2.0, most icons were created using the
+icons-as-css-background pattern:
+
+- Set up the icon’s svg as a `background-image` in CSS:
+
+  ```css
+  /* CSS */
+
+  .jp-FooIcon {
+    background-image: url('path-to-your/foo.svg');
+  }
+  ```
+
+- Add the icon to the DOM by constructing an otherwise empty DOM node
+  with the appropriate class:
+
+  ```typescript
+  // typescript
+
+  const e = document.createElement('div');
+  e.className = 'jp-FooIcon';
+  document.body.append(e);
+  ```
+
+What you end up with is a single DOM node that has the “foo” icon as a
+background image.
+
+Post JupyterLab 2.0, nearly all icons in core are now created using
+[LabIcon](https://github.com/jupyterlab/jupyterlab/blob/f0153e0258b32674c9aec106383ddf7b618cebab/packages/ui-components/src/icon/labicon.tsx)
+and the icons-as-inline-svg pattern:
+
+- Construct a new instance of LabIcon from the icon’s name and svg:
+
+  ```typescript
+  // typescript
+
+  // svgstr is the raw contents of an icon's svg file
+  export const fooIcon = new LabIcon({
+    name: 'barpkg:foo',
+    svgstr: '<svg>...</svg>'
+  });
+  ```
+
+- Add the icon to the DOM using the appropriate property of your
+  LabIcon instance (either LabIcon.element() to directly create a DOM
+  node, or LabIcon.react to get the icon as a react component):
+
+  ```typescript
+  // typescript
+
+  const e = fooIcon.element();
+  document.body.append(e);
+  ```
+
+What you end up with is a DOM node (by default a ‘div’) that has an
+inline svg node as a child.
+
+### `background-image` vs inline svg
+
+The big limitation of the old icon-as-css-background pattern is that svg
+images rendered as `background-image` are invisible to CSS. On the other
+hand, an icon rendered as an inline svg node is fully exposed to the
+CSS. This allows us to dynamically change icon styling as needed simply by
+modifying our CSS. Most importantly, this allows us to recolor icons
+according to Jupyterlab’s current theme.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @lumino/polling@2.1.2
+
+This package contains the following license and notice below:
+
+# @lumino/polling
+
+This package provides a class for generic polling functionality (`Poll`). It
+also provides rate limiters (`Debouncer` and `Throttler`).
+
+The `Poll` class provides three different ways to "subscribe" to poll ticks:
+
+- [`@lumino/signaling`](../signaling/): `Poll#ticked` is a Lumino signal that
+  emits each time there is a poll tick.
+- `Promise`-based: `Poll#tick` is a promise that resolves after every tick and
+  only rejects when the poll is disposed.
+- `AsyncIterable`: `Poll#[`Symbol.asyncIterator`]` implements the async iterable
+  protocol that allows iteration using [`for-await...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loops.
+
+## Example usage
+
+These are examples from the unit tests for this package. They
+demonstrate the three different ways polling is supported.
+
+### Using `Poll#tick` promise
+
+Here, we set up the testing state variables and create a new `Poll` instance.
+
+```typescript
+const expected = 'started resolved resolved';
+const ticker: IPoll.Phase<any>[] = [];
+const tock = (poll: Poll) => {
+  ticker.push(poll.state.phase);
+  poll.tick.then(tock).catch(() => undefined);
+};
+const poll = new Poll({
+  auto: false,
+  factory: () => Promise.resolve(),
+  frequency: { interval: 100, backoff: false }
+});
+```
+
+Next we assign the `tock` function to run after the poll ticks and
+we start the poll.
+
+```typescript
+void poll.tick.then(tock);
+void poll.start();
+```
+
+And we verify that the `ticker` did indeed get populated when `tock`
+was called and the next promise was captured as well.
+
+```typescript
+await sleep(1000); // Sleep for longer than the interval.
+expect(ticker.join(' ').startsWith(expected)).to.equal(true);
+poll.dispose();
+```
+
+### Using `Poll#ticked` signal
+
+Here, we set up the testing state variables and create a new `Poll` instance.
+
+```typescript
+const poll = new Poll<void, void>({
+  factory: () => Promise.resolve(),
+  frequency: { interval: 100, backoff: false }
+});
+```
+
+Here we connect to the `ticked` signal and simply check that each
+tick matches the poll `state` accessor's contents.
+
+```typescript
+poll.ticked.connect((_, tick) => {
+  expect(tick).to.equal(poll.state);
+});
+await sleep(1000); // Sleep for longer than the interval.
+poll.dispose();
+```
+
+### Using `Poll` as an `AsyncIterable`
+
+Here, we set up the testing state variables and create a new `Poll` instance.
+
+```typescript
+let poll: Poll;
+let total = 2;
+let i = 0;
+
+poll = new Poll({
+  auto: false,
+  factory: () => Promise.resolve(++i > total ? poll.dispose() : void 0),
+  frequency: { interval: Poll.IMMEDIATE }
+});
+
+const expected = `started${' resolved'.repeat(total)}`;
+const ticker: IPoll.Phase<any>[] = [];
+```
+
+Then the poll is started:
+
+```typescript
+void poll.start();
+```
+
+Instead of connecting to the `ticked` signal or awaiting the `tick` promise, we can now use a `for-await...of` loop:
+
+```typescript
+for await (const state of poll) {
+  ticker.push(state.phase);
+  if (poll.isDisposed) {
+    break;
+  }
+}
+```
+
+And we check to make sure the results are as expected:
+
+```typescript
+// ticker and expected both equal:
+// 'started resolved resolved disposed'
+expect(ticker.join(' ')).to.equal(expected);
+```
+
+### Note for consumers of async iterators
+
+In order to use `for-await...of` loops in TypeScript, you will need to use `ES2018` or above in your `lib` array in `tsconfig.json`.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @popperjs/core@2.11.8
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Federico Zivolo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @react-dnd/asap@4.0.1
+
+This package contains the following license and notice below:
+
+Copyright 2009–2014 Contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - @react-dnd/invariant@2.0.0
+ - prop-types@15.8.1
+ - warning@4.0.3
+
+These packages each contain the following license and notice below:
+
+MIT License
+
+Copyright (c) 2013-present, Facebook, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - @react-dnd/shallowequal@2.0.0
+ - shallowequal@1.1.0
+
+These packages each contain the following license and notice below:
+
+MIT License
+
+Copyright (c) 2017 Alberto Leal <mailforalberto@gmail.com> (github.com/dashed)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - @rjsf/core@5.12.0
+ - @rjsf/utils@5.12.0
+
+These packages each contain the following license and notice below:
+
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1.  Definitions.
+
+    "License" shall mean the terms and conditions for use, reproduction,
+    and distribution as defined by Sections 1 through 9 of this document.
+
+    "Licensor" shall mean the copyright owner or entity authorized by
+    the copyright owner that is granting the License.
+
+    "Legal Entity" shall mean the union of the acting entity and all
+    other entities that control, are controlled by, or are under common
+    control with that entity. For the purposes of this definition,
+    "control" means (i) the power, direct or indirect, to cause the
+    direction or management of such entity, whether by contract or
+    otherwise, or (ii) ownership of fifty percent (50%) or more of the
+    outstanding shares, or (iii) beneficial ownership of such entity.
+
+    "You" (or "Your") shall mean an individual or Legal Entity
+    exercising permissions granted by this License.
+
+    "Source" form shall mean the preferred form for making modifications,
+    including but not limited to software source code, documentation
+    source, and configuration files.
+
+    "Object" form shall mean any form resulting from mechanical
+    transformation or translation of a Source form, including but
+    not limited to compiled object code, generated documentation,
+    and conversions to other media types.
+
+    "Work" shall mean the work of authorship, whether in Source or
+    Object form, made available under the License, as indicated by a
+    copyright notice that is included in or attached to the work
+    (an example is provided in the Appendix below).
+
+    "Derivative Works" shall mean any work, whether in Source or Object
+    form, that is based on (or derived from) the Work and for which the
+    editorial revisions, annotations, elaborations, or other modifications
+    represent, as a whole, an original work of authorship. For the purposes
+    of this License, Derivative Works shall not include works that remain
+    separable from, or merely link (or bind by name) to the interfaces of,
+    the Work and Derivative Works thereof.
+
+    "Contribution" shall mean any work of authorship, including
+    the original version of the Work and any modifications or additions
+    to that Work or Derivative Works thereof, that is intentionally
+    submitted to Licensor for inclusion in the Work by the copyright owner
+    or by an individual or Legal Entity authorized to submit on behalf of
+    the copyright owner. For the purposes of this definition, "submitted"
+    means any form of electronic, verbal, or written communication sent
+    to the Licensor or its representatives, including but not limited to
+    communication on electronic mailing lists, source code control systems,
+    and issue tracking systems that are managed by, or on behalf of, the
+    Licensor for the purpose of discussing and improving the Work, but
+    excluding communication that is conspicuously marked or otherwise
+    designated in writing by the copyright owner as "Not a Contribution."
+
+    "Contributor" shall mean Licensor and any individual or Legal Entity
+    on behalf of whom a Contribution has been received by Licensor and
+    subsequently incorporated within the Work.
+
+2.  Grant of Copyright License. Subject to the terms and conditions of
+    this License, each Contributor hereby grants to You a perpetual,
+    worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+    copyright license to reproduce, prepare Derivative Works of,
+    publicly display, publicly perform, sublicense, and distribute the
+    Work and such Derivative Works in Source or Object form.
+
+3.  Grant of Patent License. Subject to the terms and conditions of
+    this License, each Contributor hereby grants to You a perpetual,
+    worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+    (except as stated in this section) patent license to make, have made,
+    use, offer to sell, sell, import, and otherwise transfer the Work,
+    where such license applies only to those patent claims licensable
+    by such Contributor that are necessarily infringed by their
+    Contribution(s) alone or by combination of their Contribution(s)
+    with the Work to which such Contribution(s) was submitted. If You
+    institute patent litigation against any entity (including a
+    cross-claim or counterclaim in a lawsuit) alleging that the Work
+    or a Contribution incorporated within the Work constitutes direct
+    or contributory patent infringement, then any patent licenses
+    granted to You under this License for that Work shall terminate
+    as of the date such litigation is filed.
+
+4.  Redistribution. You may reproduce and distribute copies of the
+    Work or Derivative Works thereof in any medium, with or without
+    modifications, and in Source or Object form, provided that You
+    meet the following conditions:
+
+    (a) You must give any other recipients of the Work or
+    Derivative Works a copy of this License; and
+
+    (b) You must cause any modified files to carry prominent notices
+    stating that You changed the files; and
+
+    (c) You must retain, in the Source form of any Derivative Works
+    that You distribute, all copyright, patent, trademark, and
+    attribution notices from the Source form of the Work,
+    excluding those notices that do not pertain to any part of
+    the Derivative Works; and
+
+    (d) If the Work includes a "NOTICE" text file as part of its
+    distribution, then any Derivative Works that You distribute must
+    include a readable copy of the attribution notices contained
+    within such NOTICE file, excluding those notices that do not
+    pertain to any part of the Derivative Works, in at least one
+    of the following places: within a NOTICE text file distributed
+    as part of the Derivative Works; within the Source form or
+    documentation, if provided along with the Derivative Works; or,
+    within a display generated by the Derivative Works, if and
+    wherever such third-party notices normally appear. The contents
+    of the NOTICE file are for informational purposes only and
+    do not modify the License. You may add Your own attribution
+    notices within Derivative Works that You distribute, alongside
+    or as an addendum to the NOTICE text from the Work, provided
+    that such additional attribution notices cannot be construed
+    as modifying the License.
+
+    You may add Your own copyright statement to Your modifications and
+    may provide additional or different license terms and conditions
+    for use, reproduction, or distribution of Your modifications, or
+    for any such Derivative Works as a whole, provided Your use,
+    reproduction, and distribution of the Work otherwise complies with
+    the conditions stated in this License.
+
+5.  Submission of Contributions. Unless You explicitly state otherwise,
+    any Contribution intentionally submitted for inclusion in the Work
+    by You to the Licensor shall be under the terms and conditions of
+    this License, without any additional terms or conditions.
+    Notwithstanding the above, nothing herein shall supersede or modify
+    the terms of any separate license agreement you may have executed
+    with Licensor regarding such Contributions.
+
+6.  Trademarks. This License does not grant permission to use the trade
+    names, trademarks, service marks, or product names of the Licensor,
+    except as required for reasonable and customary use in describing the
+    origin of the Work and reproducing the content of the NOTICE file.
+
+7.  Disclaimer of Warranty. Unless required by applicable law or
+    agreed to in writing, Licensor provides the Work (and each
+    Contributor provides its Contributions) on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+    implied, including, without limitation, any warranties or conditions
+    of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+    PARTICULAR PURPOSE. You are solely responsible for determining the
+    appropriateness of using or redistributing the Work and assume any
+    risks associated with Your exercise of permissions under this License.
+
+8.  Limitation of Liability. In no event and under no legal theory,
+    whether in tort (including negligence), contract, or otherwise,
+    unless required by applicable law (such as deliberate and grossly
+    negligent acts) or agreed to in writing, shall any Contributor be
+    liable to You for damages, including any direct, indirect, special,
+    incidental, or consequential damages of any character arising as a
+    result of this License or out of the use or inability to use the
+    Work (including but not limited to damages for loss of goodwill,
+    work stoppage, computer failure or malfunction, or any and all
+    other commercial damages or losses), even if such Contributor
+    has been advised of the possibility of such damages.
+
+9.  Accepting Warranty or Additional Liability. While redistributing
+    the Work or Derivative Works thereof, You may choose to offer,
+    and charge a fee for, acceptance of support, warranty, indemnity,
+    or other liability obligations and/or rights consistent with this
+    License. However, in accepting such obligations, You may act only
+    on Your own behalf and on Your sole responsibility, not on behalf
+    of any other Contributor, and only if You agree to indemnify,
+    defend, and hold each Contributor harmless for any liability
+    incurred by, or claims asserted against, such Contributor by reason
+    of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed 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.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @semantic-ui-react/event-stack@3.1.3
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2018 Alexander Fedyashov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - @types/hoist-non-react-statics@3.3.1
+
+This package contains the following license and notice below:
+
+MIT License
+
+    Copyright (c) Microsoft Corporation. All rights reserved.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - @types/node@20.4.10
+ - @types/prop-types@15.7.5
+ - @types/react@18.2.20
+ - @types/scheduler@0.16.3
+ - @types/uuid@9.0.2
+
+These packages each contain the following license and notice below:
+
+MIT License
+
+    Copyright (c) Microsoft Corporation.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE
+
+-----------
+
+The following npm package may be included in this product:
+
+ - ajv@8.12.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015-2021 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - clsx@1.2.1
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - compute-gcd@1.2.1
+ - compute-lcm@1.1.2
+ - validate.io-array@1.0.6
+
+These packages each contain the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014-2015 Athan Reines.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - csstype@3.0.10
+
+This package contains the following license and notice below:
+
+Copyright (c) 2017-2018 Fredrik Nicol
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - csstype@3.1.2
+
+This package contains the following license and notice below:
+
+Copyright (c) 2017-2018 Fredrik Nicol
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - deepmerge@4.3.1
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2012 James Halliday, Josh Duff, and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - dnd-core@14.0.1
+ - react-dnd-html5-backend@14.1.0
+
+These packages each contain the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Dan Abramov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - dom-serializer@1.4.1
+
+This package contains the following license and notice below:
+
+License
+
+(The MIT License)
+
+Copyright (c) 2014 The cheeriojs contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - domelementtype@2.3.0
+ - domhandler@4.3.1
+ - domutils@2.8.0
+ - entities@2.2.0
+
+These packages each contain the following license and notice below:
+
+Copyright (c) Felix Böhm
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - escape-string-regexp@4.0.0
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - exenv@1.2.2
+
+This package contains the following license and notice below:
+
+BSD License
+
+For React software
+
+Copyright (c) 2013-2015, Facebook, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name Facebook nor the names of its contributors may be used to
+   endorse or promote products derived from this software without specific
+   prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - fast-deep-equal@3.1.3
+ - json-schema-traverse@1.0.0
+
+These packages each contain the following license and notice below:
+
+MIT License
+
+Copyright (c) 2017 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - free-style@3.1.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - hoist-non-react-statics@3.3.2
+
+This package contains the following license and notice below:
+
+Software License Agreement (BSD License)
+========================================
+
+Copyright (c) 2015, Yahoo! Inc. All rights reserved.
+----------------------------------------------------
+
+Redistribution and use of this software in source and binary forms, with or
+without modification, are permitted provided that the following conditions are
+met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation
+    and/or other materials provided with the distribution.
+  * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be
+    used to endorse or promote products derived from this software without
+    specific prior written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - htmlparser2@6.1.0
+
+This package contains the following license and notice below:
+
+Copyright 2010, 2011, Chris Winberry <chris@winberry.net>. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+ 
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+ 
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - is-plain-object@5.0.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014-2017, Jon Schlinkert.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - isomorphic.js@0.2.5
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2020 Kevin Jahns <kevin.jahns@protonmail.com>.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - jquery@3.7.0
+
+This package contains the following license and notice below:
+
+Copyright OpenJS Foundation and other contributors, https://openjsf.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - js-tokens@4.0.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - json-schema-compare@0.2.2
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2017 Martin Hansen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - json-schema-merge-allof@0.8.1
+
+This package contains the following license and notice below:
+
+# json-schema-merge-allof [![Build Status](https://travis-ci.org/mokkabonna/json-schema-merge-allof.svg?branch=master)](https://travis-ci.org/mokkabonna/json-schema-merge-allof) [![Coverage Status](https://coveralls.io/repos/github/mokkabonna/json-schema-merge-allof/badge.svg?branch=master)](https://coveralls.io/github/mokkabonna/json-schema-merge-allof?branch=master)
+
+> Merge schemas combined using allOf into a more readable composed schema free from allOf.
+
+```bash
+npm install json-schema-merge-allof --save
+```
+
+## Features
+
+- **Real** and **safe** merging of schemas combined with **allOf**
+- Takes away all allOf found in the whole schema
+- Lossless in terms of validation rules, merged schema does not validate more or less than the original schema
+- Results in a more readable root schema
+- Removes almost all logical impossibilities
+- Throws if no logical intersection is found (your schema would not validate anything from the start)
+- Validates in a way not possible by regular simple meta validators
+- Correctly considers additionalProperties, patternProperties and properties as a part of an whole when merging schemas containing those
+- Correctly considers items and additionalItems as a whole when merging schemas containing those
+- Supports merging schemas with items as array and direct schema
+- Supports merging dependencies when mixed array and schema
+- Supports all JSON schema core/validation keywords (v6, use custom resolvers to support other keywords)
+- Option to override common impossibility like adding properties when using **additionalProperties: false**
+- Pluggable keyword resolvers
+
+## How
+
+Since allOf require ALL schemas provided (including the parent schema) to apply, we can iterate over all the schemas, extracting all the values for say, **type**, and find the **intersection** of valid values. Here is an example:
+
+```js
+{
+  type: ['object', 'null'],
+  additionalProperties: {
+    type: 'string',
+    minLength: 5
+  },
+  allOf: [{
+    type: ['array', 'object'],
+    additionalProperties: {
+      type: 'string',
+      minLength: 10,
+      maxLength: 20
+    }
+  }]
+}
+```
+
+This result in the schema :
+```js
+{
+  type: 'object',
+  additionalProperties: {
+    type: 'string',
+    minLength: 10,
+    maxLength: 20
+  }
+}
+```
+
+Notice that type now excludes null and array since those are not logically possible. Also minLength is raised to 10. The other properties have no conflict and are merged into the root schema with no resolving needed.
+
+For other keywords other methods are used, here are some simple examples:
+
+- minLength, minimum, minItems etc chooses the **highest** value of the conflicting values.
+- maxLength, maximum, maxItems etc chooses the **lowest** value of the conflicting values.
+- uniqueItems is true if **any** of the conflicting values are true
+
+As you can see above the strategy is to choose the **most** restrictive of the set of values that conflict. For some keywords that is done by intersection, for others like **required** it is done by a union of all the values, since that is the most restrictive.
+
+What you are left with is a schema completely free of allOf. Except for in a couple of values that are impossible to properly intersect/combine:
+
+### not
+
+When multiple conflicting **not** values are found, we also use the approach that pattern use, but instead of allOf we use anyOf. When extraction of common rules from anyOf is in place this can be further simplified.
+
+## Options
+**ignoreAdditionalProperties** default **false**
+
+Allows you to combine schema properties even though some schemas have `additionalProperties: false` This is the most common issue people face when trying to expand schemas using allOf and a limitation of the json schema spec. Be aware though that the schema produced will allow more than the original schema. But this is useful if just want to combine schemas using allOf as if additionalProperties wasn't false during the merge process. The resulting schema will still get additionalProperties set to false.
+
+**deep** boolean, default *true*
+If false, resolves only the top-level `allOf` keyword in the schema.
+
+If true, resolves all `allOf` keywords in the schema.
+
+
+**resolvers** Object
+Override any default resolver like this:
+
+```js
+mergeAllOf(schema, {
+  resolvers: {
+    title: function(values, path, mergeSchemas, options) {
+      // choose what title you want to be used based on the conflicting values
+      // resolvers MUST return a value other than undefined
+    }
+  }
+})
+```
+
+The function is passed:
+
+- **values** an array of the conflicting values that need to be resolved
+- **path** an array of strings containing the path to the position in the schema that caused the resolver to be called (useful if you use the same resolver for multiple keywords, or want to implement specific logic for custom paths)
+- **mergeSchemas** a function you can call that merges an array of schemas
+- **options** the options mergeAllOf was called with
+
+
+### Combined resolvers
+Some keyword are dependant on other keywords, like properties, patternProperties, additionalProperties. To create a resolver for these the resolver requires this structure:
+
+```js
+mergeAllOf(schema, {
+  resolvers: {
+    properties:
+      keywords: ['properties', 'patternProperties', 'additionalProperties'],
+      resolver(values, parents, mergers, options) {
+
+      }
+    }
+  }
+})
+```
+
+This type of resolvers are expected to return an object containing the resolved values of all the associated keywords. The keys must be the name of the keywords. So the properties resolver need to return an object like this containing the resolved values for each keyword:
+
+```js
+{
+    properties: ...,
+    patternProperties: ...,
+    additionalProperties: ...,
+}
+```
+
+Also the resolve function is not passed **mergeSchemas**, but an object **mergers** that contains mergers for each of the related keywords. So properties get passed an object like this:
+
+```js
+const mergers = {
+    properties: function mergeSchemas(schemas, childSchemaName){...},
+    patternProperties: function mergeSchemas(schemas, childSchemaName){...},
+    additionalProperties: function mergeSchemas(schemas){...},
+}
+```
+
+Some of the mergers requires you to supply a string of the name or index of the subschema you are currently merging. This is to make sure the path passed to child resolvers are correct.
+
+### Default resolver
+You can set a default resolver that catches any unknown keyword. Let's say you want to use the same strategy as the ones for the meta keywords, to use the first value found. You can accomplish that like this:
+
+```js
+mergeJsonSchema({
+  ...
+}, {
+  resolvers: {
+    defaultResolver: mergeJsonSchema.options.resolvers.title
+  }
+})
+```
+
+
+## Resolvers
+
+Resolvers are called whenever multiple conflicting values are found on the same position in the schemas.
+
+You can override a resolver by supplying it in the options.
+
+### Lossy vs lossless
+
+All built in reducers for validation keywords are lossless, meaning that they don't remove or add anything in terms of validation.
+
+For meta keywords like title, description, $id, $schema, default the strategy is to use the first possible value if there are conflicting ones. So the root schema is prioritized. This process possibly removes some meta information from your schema. So it's lossy. Override this by providing custom resolvers.
+
+
+## $ref
+
+If one of your schemas contain a $ref property you should resolve them using a ref resolver like [json-schema-ref-parser](https://github.com/BigstickCarpet/json-schema-ref-parser) to dereference your schema for you first. Resolving $refs is not the task of this library.
+
+
+## Other libraries
+
+There exists some libraries that claim to merge schemas combined with allOf, but they just merge schemas using a **very** basic logic. Basically just the same as lodash merge. So you risk ending up with a schema that allows more or less than the original schema would allow.
+
+
+## Restrictions
+
+We cannot merge schemas that are a logical impossibility, like:
+
+```js
+{
+  type: 'object',
+  allOf: [{
+    type: 'array'
+  }]
+}
+```
+
+The library will then throw an error reporting the values that had no valid intersection. But then again, your original schema wouldn't validate anything either.
+
+
+## Roadmap
+
+- [x] Treat the interdependent validations like properties and additionalProperties as one resolver (and items additionalItems)
+- [ ] Extract repeating validators from anyOf/oneOf and merge them with parent schema
+- [ ] After extraction of validators from anyOf/oneOf, compare them and remove duplicates.
+- [ ] If left with only one in anyOf/oneOf then merge it to the parent schema.
+- [ ] Expose seperate tools for validation, extraction
+- [ ] Consider adding even more logical validation (like minLength <= maxLength)
+
+## Contributing
+
+Create tests for new functionality and follow the eslint rules.
+
+## License
+
+MIT © [Martin Hansen](http://martinhansen.com)
+
+-----------
+
+The following npm package may be included in this product:
+
+ - json5@2.2.3
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2012-2018 Aseem Kishore, and [others].
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+[others]: https://github.com/json5/json5/contributors
+
+-----------
+
+The following npm package may be included in this product:
+
+ - jsonpointer@5.0.1
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2011-2015 Jan Lehnardt <jan@apache.org> & Marc Bachmann <https://github.com/marcbachmann> 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - keyboard-key@1.1.0
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2018 Levi Thomason
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - lib0@0.2.80
+ - y-protocols@1.0.5
+
+These packages each contain the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Kevin Jahns <kevin.jahns@protonmail.com>.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - lodash-es@4.17.21
+ - lodash@4.17.21
+
+These packages each contain the following license and notice below:
+
+Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
+
+Based on Underscore.js, copyright Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/lodash/lodash
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code displayed within the prose of the
+documentation.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+Files located in the node_modules and vendor directories are externally
+maintained libraries used by this software which have their own
+licenses; we recommend you read them, as their terms may differ from the
+terms above.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - lodash.escape@4.0.1
+
+This package contains the following license and notice below:
+
+Copyright jQuery Foundation and other contributors <https://jquery.org/>
+
+Based on Underscore.js, copyright Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/lodash/lodash
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code displayed within the prose of the
+documentation.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+Files located in the node_modules and vendor directories are externally
+maintained libraries used by this software which have their own
+licenses; we recommend you read them, as their terms may differ from the
+terms above.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - loose-envify@1.4.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Andres Suarez <zertosh@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - markdown-to-jsx@7.3.2
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015-present Evan Jacobs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - memoize-one@5.2.1
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2019 Alexander Reardon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - minimist@1.2.8
+
+This package contains the following license and notice below:
+
+This software is released under the MIT license:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - nanoid@3.3.6
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright 2017 Andrey Sitnik <andrey@sitnik.ru>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - object-assign@4.1.1
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - parse-srcset@1.0.2
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Alex Bell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - path-browserify@1.0.1
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2013 James Halliday
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - picocolors@1.0.0
+
+This package contains the following license and notice below:
+
+ISC License
+
+Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - postcss@8.4.27
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright 2013 Andrey Sitnik <andrey@sitnik.ru>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - punycode@2.3.0
+
+This package contains the following license and notice below:
+
+Copyright Mathias Bynens <https://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - querystringify@2.2.0
+ - requires-port@1.0.0
+ - url-parse@1.5.10
+
+These packages each contain the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Unshift.io, Arnout Kazemier,  the Contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-arborist@3.2.0
+
+This package contains the following license and notice below:
+
+![Logo](https://user-images.githubusercontent.com/3460638/161630636-3512fe81-41c2-4ee5-8f7e-adaad07033b6.svg)
+
+<h1>React Arborist</h1>
+
+[See the Demos](https://react-arborist.netlify.app/)
+
+The tree view is ubiquitous in software applications. This library provides the React ecosystem with a complete solution to build the equivalent of a VSCode sidebar, Mac Finder, Windows Explorer, or Sketch/Figma layers panel.
+
+Here is a Gmail sidebar clone built with react-arborist.
+
+<img src="https://user-images.githubusercontent.com/3460638/197306119-59fe59e6-50ae-4bc2-8cb9-3faa2bc52cd2.gif" width="270px" alt="Gmail sidebar clone built with react-arborist" />
+
+## Features
+
+- Drag and drop sorting
+- Open/close folders
+- Inline renaming
+- Virtualized rendering
+- Custom styling
+- Keyboard navigation
+- Aria attributes
+- Tree filtering
+- Selection synchronization
+- Callbacks (onScroll, onActivate, onSelect)
+- Controlled or uncontrolled trees
+
+## Installation
+
+```
+yarn add react-arborist
+```
+
+```
+npm install react-arborist
+```
+
+## Examples
+
+Assume our data is this:
+
+```js
+const data = [
+  { id: "1", name: "Unread" },
+  { id: "2", name: "Threads" },
+  {
+    id: "3",
+    name: "Chat Rooms",
+    children: [
+      { id: "c1", name: "General" },
+      { id: "c2", name: "Random" },
+      { id: "c3", name: "Open Source Projects" },
+    ],
+  },
+  {
+    id: "4",
+    name: "Direct Messages",
+    children: [
+      { id: "d1", name: "Alice" },
+      { id: "d2", name: "Bob" },
+      { id: "d3", name: "Charlie" },
+    ],
+  },
+];
+```
+
+### The Simplest Tree
+
+Use all the defaults. The _initialData_ prop makes the tree an uncontrolled component. Create, move, rename, and delete will be handled internally.
+
+```jsx
+function App() {
+  return <Tree initialData={data} />;
+}
+```
+
+<img width="214" alt="image" src="https://user-images.githubusercontent.com/3460638/198098015-d7dc6400-6391-4094-9f66-0f56a99433e9.png">
+
+[Demo](https://codesandbox.io/s/the-simplest-tree-7tbedw)
+
+### Customize the Appearance
+
+We provide our own dimensions and our own `Node` component.
+
+```jsx
+function App() {
+  return (
+    <Tree
+      initialData={data}
+      openByDefault={false}
+      width={600}
+      height={1000}
+      indent={24}
+      rowHeight={36}
+      overscanCount={1}
+      paddingTop={30}
+      paddingBottom={10}
+      padding={25 /* sets both */}
+    >
+      {Node}
+    </Tree>
+  );
+}
+
+function Node({ node, style, dragHandle }) {
+  /* This node instance can do many things. See the API reference. */
+  return (
+    <div style={style} ref={dragHandle}>
+      {node.isLeaf ? "🍁" : "🗀"}
+      {node.data.name}
+    </div>
+  );
+}
+```
+
+<img width="166" alt="image" src="https://user-images.githubusercontent.com/3460638/198100281-594a492d-2ea0-4ff0-883d-1dd79dbb5acd.png">
+
+[Demo](https://codesandbox.io/s/customize-appearance-f4g15v?file=/src/App.tsx)
+
+### Control the Tree data
+
+Here we use the _data_ prop to make the tree a controlled component. We must handle all the data modifications ourselves using the props below.
+
+```jsx
+function App() {
+  /* Handle the data modifications outside the tree component */
+  const onCreate = ({ parentId, index, type }) => {};
+  const onRename = ({ id, name }) => {};
+  const onMove = ({ dragIds, parentId, index }) => {};
+  const onDelete = ({ ids }) => {};
+
+  return (
+    <Tree
+      data={data}
+      onCreate={onCreate}
+      onRename={onRename}
+      onMove={onMove}
+      onDelete={onDelete}
+    />
+  );
+}
+```
+
+### Tree Filtering
+
+Providing a non-empty _searchTerm_ will only show nodes that match. If a child matches, all its parents also match. Internal nodes are opened when filtering. You can provide your own _searchMatch_ function, or use the default.
+
+```jsx
+function App() {
+  const term = useSearchTermString()
+  <Tree
+    data={data}
+    searchTerm={term}
+    searchMatch={
+      (node, term) => node.data.name.toLowerCase().includes(term.toLowerCase())
+    }
+  />
+}
+```
+
+### Sync the Selection
+
+It's common to open something elsewhere in the app, but have the tree reflect the new selection.
+
+Passing an id to the _selection_ prop will select and scroll to that node whenever that id changes.
+
+```jsx
+function App() {
+  const chatId = useCurrentChatId();
+
+  /* 
+    Whenever the currentChatRoomId changes, 
+    the tree will automatically select it and scroll to it. 
+  */
+
+  return <Tree initialData={data} selection={chatId} />;
+}
+```
+
+### Use the Tree Api Instance
+
+You can access the Tree Api in the parent component by giving a ref to the tree.
+
+```jsx
+function App() {
+  const treeRef = useRef();
+
+  useEffect(() => {
+    const tree = treeRef.current;
+    tree.selectAll();
+    /* See the Tree API reference for all you can do with it. */
+  }, []);
+
+  return <Tree initialData={data} ref={treeRef} />;
+}
+```
+
+### Data with Different Property Names
+
+The _idAccessor_ and _childrenAccessor_ props allow you to specify the children and id fields in your data.
+
+```jsx
+function App() {
+  const data = [
+    {
+      category: "Food",
+      subCategories: [{ category: "Restaurants" }, { category: "Groceries" }],
+    },
+  ];
+  return (
+    <Tree
+      data={data}
+      /* An accessor can provide a string property name */
+      idAccessor="category"
+      /* or a function with the data as the argument */
+      childrenAccessor={(d) => d.subCategories}
+    />
+  );
+}
+```
+
+### Custom Rendering
+
+Render every single piece of the tree yourself. See the API reference for the props passed to each renderer.
+
+```jsx
+function App() {
+  return (
+    <Tree
+      data={data}
+      /* The outer most element in the list */
+      renderRow={MyRow}
+      /* The "ghost" element that follows the mouse as you drag */
+      renderDragPreview={MyDragPreview}
+      /* The line that shows where an element will be dropped */
+      renderCursor={MyCursor}
+    >
+      {/* The inner element that shows the indentation and data */}
+      {MyNode}
+    </Tree>
+  );
+}
+```
+
+## API Reference
+
+- Components
+  - [Tree Component Props](#tree-component-props)
+  - [Row Component Props](#row-component-props)
+  - [Node Component Props](#node-component-props)
+  - [DragPreview Component Props](#dragpreview-component-props)
+  - [Cursor Component Props](#cursor-component-props)
+- Interfaces
+  - [Node API](#node-api-reference)
+  - [Tree API](#tree-api-reference)
+
+## Tree Component Props
+
+These are all the props you can pass to the Tree component.
+
+```ts
+interface TreeProps<T> {
+  /* Data Options */
+  data?: readonly T[];
+  initialData?: readonly T[];
+
+  /* Data Handlers */
+  onCreate?: handlers.CreateHandler<T>;
+  onMove?: handlers.MoveHandler<T>;
+  onRename?: handlers.RenameHandler<T>;
+  onDelete?: handlers.DeleteHandler<T>;
+
+  /* Renderers*/
+  children?: ElementType<renderers.NodeRendererProps<T>>;
+  renderRow?: ElementType<renderers.RowRendererProps<T>>;
+  renderDragPreview?: ElementType<renderers.DragPreviewProps>;
+  renderCursor?: ElementType<renderers.CursorProps>;
+  renderContainer?: ElementType<{}>;
+
+  /* Sizes */
+  rowHeight?: number;
+  overscanCount?: number;
+  width?: number | string;
+  height?: number;
+  indent?: number;
+  paddingTop?: number;
+  paddingBottom?: number;
+  padding?: number;
+
+  /* Config */
+  childrenAccessor?: string | ((d: T) => T[] | null);
+  idAccessor?: string | ((d: T) => string);
+  openByDefault?: boolean;
+  selectionFollowsFocus?: boolean;
+  disableMultiSelection?: boolean;
+  disableEdit?: string | boolean | BoolFunc<T>;
+  disableDrag?: string | boolean | BoolFunc<T>;
+  disableDrop?:
+    | string
+    | boolean
+    | ((args: {
+        parentNode: NodeApi<T>;
+        dragNodes: NodeApi<T>[];
+        index: number;
+      }) => boolean);
+
+  /* Event Handlers */
+  onActivate?: (node: NodeApi<T>) => void;
+  onSelect?: (nodes: NodeApi<T>[]) => void;
+  onScroll?: (props: ListOnScrollProps) => void;
+  onToggle?: (id: string) => void;
+  onFocus?: (node: NodeApi<T>) => void;
+
+  /* Selection */
+  selection?: string;
+
+  /* Open State */
+  initialOpenState?: OpenMap;
+
+  /* Search */
+  searchTerm?: string;
+  searchMatch?: (node: NodeApi<T>, searchTerm: string) => boolean;
+
+  /* Extra */
+  className?: string | undefined;
+  rowClassName?: string | undefined;
+
+  dndRootElement?: globalThis.Node | null;
+  onClick?: MouseEventHandler;
+  onContextMenu?: MouseEventHandler;
+  dndManager?: DragDropManager;
+}
+```
+
+## Row Component Props
+
+The _\<RowRenderer\>_ is responsible for attaching the drop ref, the row style (top, height) and the aria-attributes. The default should work fine for most use cases, but it can be replaced by your own component if you need. See the _renderRow_ prop in the _\<Tree\>_ component.
+
+```ts
+type RowRendererProps<T> = {
+  node: NodeApi<T>;
+  innerRef: (el: HTMLDivElement | null) => void;
+  attrs: HTMLAttributes<any>;
+  children: ReactElement;
+};
+```
+
+## Node Component Props
+
+The _\<NodeRenderer\>_ is responsible for attaching the drag ref, the node style (padding for indentation), the visual look of the node, the edit input of the node, and anything else you can dream up.
+
+There is a default renderer, but it's only there as a placeholder to get started. You'll want to create your own component for this. It is passed as the _\<Tree\>_ components only child.
+
+```ts
+export type NodeRendererProps<T> = {
+  style: CSSProperties;
+  node: NodeApi<T>;
+  tree: TreeApi<T>;
+  dragHandle?: (el: HTMLDivElement | null) => void;
+  preview?: boolean;
+};
+```
+
+## DragPreview Component Props
+
+The _\<DragPreview\>_ is responsible for showing a "ghost" version of the node being dragged. The default is a semi-transparent version of the NodeRenderer and should work fine for most people. To customize it, pass your new component to the _renderDragPreview_ prop.
+
+```ts
+type DragPreviewProps = {
+  offset: XYCoord | null;
+  mouse: XYCoord | null;
+  id: string | null;
+  dragIds: string[];
+  isDragging: boolean;
+};
+```
+
+## Cursor Component Props
+
+The _\<Cursor\>_ is responsible for showing a line that indicates where the node will move to when it's dropped. The default is a blue line with circle on the left side. You may want to customize this. Pass your own component to the _renderCursor_ prop.
+
+```ts
+export type CursorProps = {
+  top: number;
+  left: number;
+  indent: number;
+};
+```
+
+## Node API Reference
+
+### State Properties
+
+All these properties on the node instance return booleans related to the state of the node.
+
+_node_.**isRoot**
+
+Returns true if this is the root node. The root node is added internally by react-arborist and not shown in the UI.
+
+_node_.**isLeaf**
+
+Returns true if the children property is not an array.
+
+_node_.**isInternal**
+
+Returns true if the children property is an array.
+
+_node_.**isOpen**
+
+Returns true if node is internal and in an open state.
+
+_node_.**isEditing**
+
+Returns true if this node is currently being edited. Use this property in the NodeRenderer to render the rename form.
+
+_node_.**isSelected**
+
+Returns true if node is selected.
+
+_node_.**isSelectedStart**
+
+Returns true if node is the first of a contiguous group of selected nodes. Useful for styling.
+
+_node_.**isSelectedEnd**
+
+Returns true if node is the last of a contiguous group of selected nodes. Useful for styling.
+
+_node_.**isOnlySelection**
+
+Returns true if node is the only node selected in the tree.
+
+_node_.**isFocused**
+
+Returns true if node is focused.
+
+_node_.**isDragging**
+
+Returns true if node is being dragged.
+
+_node_.**willReceiveDrop**
+
+Returns true if node is internal and the user is hovering a dragged node over it.
+
+_node_.**state**
+
+Returns an object with all the above properties as keys and boolean values. Useful for adding class names to an element with a library like [clsx](https://github.com/lukeed/clsx) or [classnames](https://github.com/JedWatson/classnames).
+
+```ts
+type NodeState = {
+  isEditing: boolean;
+  isDragging: boolean;
+  isSelected: boolean;
+  isSelectedStart: boolean;
+  isSelectedEnd: boolean;
+  isFocused: boolean;
+  isOpen: boolean;
+  isClosed: boolean;
+  isLeaf: boolean;
+  isInternal: boolean;
+  willReceiveDrop: boolean;
+};
+```
+
+### Accessors
+
+_node_.**childIndex**
+
+Returns the node's index in relation to its siblings.
+
+_node_.**next**
+
+Returns the next visible node. The node directly under this node in the tree component. Returns null if none exist.
+
+_node_.**prev**
+
+Returns the previous visible node. The node directly above this node in the tree component. Returns null if none exist.
+
+_node_.**nextSibling**
+
+Returns the next sibling in the data of this node. Returns null if none exist.
+
+### Selection Methods
+
+_node_.**select**()
+
+Select only this node.
+
+_node_.**deselect**()
+
+Deselect this node. Other nodes may still be selected.
+
+_node_.**selectMulti**()
+
+Select this node while maintaining all other selections.
+
+_node_.**selectContiguous**()
+
+Deselect all nodes from the anchor node to the last selected node, the select all nodes from the anchor node to this node. The anchor changes to the focused node after calling _select()_ or _selectMulti()_.
+
+### Activation Methods
+
+_node_.**activate**()
+
+Runs the Tree props' onActivate callback passing in this node.
+
+_node_.**focus**()
+
+Focus this node.
+
+### Open/Close Methods
+
+_node_.**open**()
+
+Opens the node if it is an internal node.
+
+_node_.**close**()
+
+Closes the node if it is an internal node.
+
+_node_.**toggle**()
+
+Toggles the open/closed state of the node if it is an internal node.
+
+_node_.**openParents**()
+
+Opens all the parents of this node.
+
+_node_.**edit**()
+
+Moves this node into the editing state. Calling node._isEditing_ will return true.
+
+_node_.**submit**(_newName_)
+
+Submits _newName_ string to the _onRename_ handler. Moves this node out of the editing state.
+
+_node_.**reset**()
+
+Moves this node out of the editing state without submitting a new name.
+
+### Event Handlers
+
+_node_.**handleClick**(_event_)
+
+Useful for using the standard selection methods when a node is clicked. If the meta key is down, call _multiSelect()_. If the shift key is down, call _selectContiguous()_. Otherwise, call _select()_ and _activate()_.
+
+## Tree API Reference
+
+The tree api reference is stable across re-renders. It always has the most recent state and props.
+
+### Node Accessors
+
+_tree_.**get**(_id_) : _NodeApi | null_
+
+Get node by id from the _visibleNodes_ array.
+
+_tree_.**at**(_index_) : _NodeApi | null_
+
+Get node by index from the _visibleNodes_ array.
+
+_tree_.**visibleNodes** : _NodeApi[]_
+
+Returns an array of the visible nodes.
+
+_tree_.**firstNode** : _NodeApi | null_
+
+The first node in the _visibleNodes_ array.
+
+_tree_.**lastNode** : _NodeApi | null_
+
+The last node in the _visibleNodes_ array.
+
+_tree_.**focusedNode** : _NodeApi | null_
+
+The currently focused node.
+
+_tree_.**mostRecentNode** : _NodeApi | null_
+
+The most recently selected node.
+
+_tree_.**nextNode** : _NodeApi | null_
+
+The node directly after the _focusedNode_ in the _visibleNodes_ array.
+
+_tree_.**prevNode** : _NodeApi | null_
+
+The node directly before the _focusedNode_ in the _visibleNodes_ array.
+
+### Focus Methods
+
+_tree_.**hasFocus** : _boolean_
+
+Returns true if the the tree has focus somewhere within it.
+
+_tree_.**focus**(_id_)
+
+Focus on the node with _id_.
+
+_tree_.**isFocused**(_id_) : _boolean_
+
+Check if the node with _id_ is focused.
+
+_tree_.**pageUp**()
+
+Move focus up one page.
+
+_tree_.**pageDown**()
+
+Move focus down one page.
+
+### Selection Methods
+
+_tree_.**selectedIds** : _Set\<string\>_
+
+Returns a set of ids that are selected.
+
+_tree_.**selectedNodes** : _NodeApi[]_
+
+Returns an array of nodes that are selected.
+
+_tree_.**hasNoSelection** : boolean
+
+Returns true if nothing is selected in the tree.
+
+_tree_.**hasSingleSelection** : boolean
+
+Returns true if there is only one selection.
+
+_tree_.**hasMultipleSelections** : boolean
+
+Returns true if there is more than one selection.
+
+_tree_.**isSelected**(_id_) : _boolean_
+
+Returns true if the node with _id_ is selected.
+
+_tree_.**select**(_id_)
+
+Select only the node with _id_.
+
+_tree_.**deselect**(_id_)
+
+Deselect the node with _id_.
+
+_tree_.**selectMulti**(_id_)
+
+Add to the selection the node with _id_.
+
+_tree_.**selectContiguous**(_id_)
+
+Deselected nodes between the anchor and the last selected node, then select the nodes between the anchor and the node with _id_.
+
+_tree_.**deselectAll**()
+
+Deselect all nodes.
+
+_tree_.**selectAll**()
+
+Select all nodes.
+
+### Visibility
+
+_tree_.**open**(_id_)
+
+Open the node with _id_.
+
+_tree_.**close**(_id_)
+
+Close the node with _id_.
+
+_tree_.**toggle**(_id_)
+
+Toggle the open state of the node with _id_.
+
+_tree_.**openParents**(_id_)
+
+Open all parents of the node with _id_.
+
+_tree_.**openSiblings**(_id_)
+
+Open all siblings of the node with _id_.
+
+_tree_.**openAll**()
+
+Open all internal nodes.
+
+_tree_.**closeAll**()
+
+Close all internal nodes.
+
+_tree_.**isOpen**(_id_) : _boolean_
+
+Returns true if the node with _id_ is open.
+
+### Drag and Drop
+
+_tree_.**isDragging**(_id_) : _boolean_
+
+Returns true if the node with _id_ is being dragged.
+
+_tree_.**willReceiveDrop**(_id_) : _boolean_
+
+Returns true if the node with _id_ is internal and is under the dragged node.
+
+### Scrolling
+
+_tree_.**scrollTo**(_id_, _[align]_)
+
+Scroll to the node with _id_. If this node is not visible, this method will open all its parents. The align argument can be _"auto" | "smart" | "center" | "end" | "start"_.
+
+### Properties
+
+_tree_.**isEditing** : _boolean_
+
+Returns true if the tree is editing a node.
+
+_tree_.**isFiltered** : _boolean_
+
+Returns true if the _searchTerm_ prop is not an empty string when trimmed.
+
+_tree_.**props** : _TreeProps_
+
+Returns all the props that were passed to the _\<Tree\>_ component.
+
+_tree_.**root** : _NodeApi_
+
+Returns the root _NodeApi_ instance. Its children are the Node representations of the _data_ prop array.
+
+## Author
+
+[James Kerr](https://twitter.com/specialCaseDev) at [Brim Data](https://brimdata.io) for the [Zui desktop app](https://www.youtube.com/watch?v=I2y663n8d2A).
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-dnd@14.0.5
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Dan Abramov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - react-dom@18.2.0
+ - react-is@16.13.1
+ - react-is@18.2.0
+ - react@18.2.0
+ - scheduler@0.23.0
+ - use-sync-external-store@1.2.0
+
+These packages each contain the following license and notice below:
+
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-fast-compare@3.2.2
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2018 Formidable Labs
+Copyright (c) 2017 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-popper@2.3.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2018 React Popper authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-spinners@0.13.8
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2017 David Hu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-table@7.8.0
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2016 Tanner Linsley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-tagsinput@3.20.3
+
+This package contains the following license and notice below:
+
+The MIT License
+
+Copyright (c) 2015 Ola Holmström <olaholmstrom+github@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-toastify@9.1.3
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2023 Fadi Khadra
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - react-window@1.8.9
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Vaughn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - redux@4.2.1
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015-present Dan Abramov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - regenerator-runtime@0.14.0
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2014-present, Facebook, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - require-from-string@2.0.2
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) Vsevolod Strukchinsky <floatdrop@gmail.com> (github.com/floatdrop)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - sanitize-html@2.7.3
+
+This package contains the following license and notice below:
+
+Copyright (c) 2013, 2014, 2015 P'unk Avenue LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - semantic-ui-css@2.5.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Semantic Org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - semantic-ui-react@2.1.4
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2016 TechnologyAdvice
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - source-map-js@1.0.2
+
+This package contains the following license and notice below:
+
+Copyright (c) 2009-2011, Mozilla Foundation and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the names of the Mozilla Foundation nor the names of project
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - typestyle@2.4.0
+
+This package contains the following license and notice below:
+
+MIT License
+
+Copyright (c) 2016 typestyle
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - uri-js@4.4.1
+
+This package contains the following license and notice below:
+
+Copyright 2011 Gary Court. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1.	Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2.	Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court.
+
+-----------
+
+The following npm packages may be included in this product:
+
+ - validate.io-function@1.0.2
+ - validate.io-integer@1.0.5
+ - validate.io-number@1.0.3
+
+These packages each contain the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Athan Reines.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - validate.io-integer-array@1.0.0
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Athan Reines.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - ws@8.13.0
+
+This package contains the following license and notice below:
+
+Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
+Copyright (c) 2013 Arnout Kazemier and contributors
+Copyright (c) 2016 Luigi Pinca and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----------
+
+The following npm package may be included in this product:
+
+ - yjs@13.6.7
+
+This package contains the following license and notice below:
+
+The MIT License (MIT)
+
+Copyright (c) 2014
+  - Kevin Jahns <kevin.jahns@rwth-aachen.de>.
+  - Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------
+
+This file was generated with the generate-license-file npm package!
+https://www.npmjs.com/package/generate-license-file
diff --git a/third_party/icons/pyspark_logo.svg b/third_party/icons/pyspark_logo.svg
new file mode 100644
index 00000000..cf7c9aff
--- /dev/null
+++ b/third_party/icons/pyspark_logo.svg
@@ -0,0 +1,25 @@
+<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="52" height="52" fill="white"/>
+<path d="M1.95545 27.1482H2.59885C3.20021 27.1482 3.65017 27.0302 3.94874 26.7943C4.24732 26.5541 4.3966 26.2065 4.3966 25.7514C4.3966 25.2921 4.27045 24.9529 4.01813 24.7338C3.77002 24.5147 3.37893 24.4052 2.84486 24.4052H1.95545V27.1482ZM6.37098 25.6819C6.37098 26.6763 6.05979 27.4368 5.43741 27.9635C4.81923 28.4902 3.93823 28.7536 2.7944 28.7536H1.95545V32.0402H0V22.7998H2.94579C4.06439 22.7998 4.91385 23.0421 5.49418 23.5266C6.07871 24.007 6.37098 24.7254 6.37098 25.6819Z" fill="#E25A1C"/>
+<path d="M6.9513 24.974H9.05814L10.3891 28.9495C10.5026 29.295 10.5804 29.7038 10.6225 30.1757H10.6603C10.7066 29.7417 10.797 29.333 10.9316 28.9495L12.2373 24.974H14.3L11.3164 32.944C11.043 33.6814 10.6519 34.2334 10.1431 34.5999C9.63846 34.9665 9.04763 35.1498 8.37058 35.1498C8.03836 35.1498 7.71245 35.114 7.39285 35.0424V33.5128C7.62414 33.5676 7.87646 33.595 8.1498 33.595C8.49043 33.595 8.7869 33.4897 9.03922 33.279C9.29574 33.0725 9.49549 32.7586 9.63846 32.3372L9.75201 31.9896L6.9513 24.974Z" fill="#E25A1C"/>
+<g clip-path="url(#clip0_3526_17591)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5824 24.9978C48.5487 24.9273 48.5336 24.8918 48.5154 24.858C48.028 23.9502 47.5419 23.0424 47.0495 22.1346C47.0002 22.0436 47.0064 21.9899 47.074 21.9114C47.8495 21.0245 48.615 20.1326 49.3918 19.2494C49.4193 19.2182 49.4437 19.1852 49.454 19.1297C49.2284 19.1872 49.0029 19.2441 48.7761 19.3026C47.8402 19.5455 46.8968 19.7872 45.9696 20.0338C45.8826 20.0567 45.8431 20.0318 45.7993 19.9609C45.268 19.0911 44.733 18.2189 44.1955 17.3602C44.1681 17.3152 44.1382 17.2715 44.0795 17.2339C44.0364 17.4657 43.9928 17.6964 43.9505 17.9282C43.8014 18.7464 43.6523 19.5598 43.5044 20.3817C43.4883 20.4699 43.4662 20.5583 43.4585 20.6466C43.4511 20.731 43.4066 20.7622 43.3282 20.7865C42.2244 21.1263 41.1231 21.4685 40.0205 21.8108C39.9721 21.8256 39.9247 21.8453 39.8727 21.8913C40.7748 22.2421 41.6768 22.593 42.5915 22.9499C42.5581 22.9758 42.5361 22.9959 42.5116 23.0113C41.9478 23.3682 41.3828 23.7252 40.8202 24.0834C40.7527 24.1266 40.6993 24.1327 40.6235 24.0992C39.9495 23.8024 39.2704 23.5104 38.5938 23.216C38.2906 23.0835 38.0188 22.9081 37.807 22.6529C37.3272 22.0788 37.4224 21.4262 38.0614 21.0214C38.2706 20.8901 38.5086 20.7932 38.7454 20.7172C39.8267 20.3725 40.9129 20.04 41.9904 19.7063C42.0814 19.678 42.1232 19.6383 42.1408 19.542C42.2861 18.7225 42.4352 17.8982 42.5868 17.0885C42.6675 16.6506 42.7101 16.2028 42.9276 15.8005C43.0109 15.6459 43.1105 15.495 43.2308 15.3662C43.6618 14.8976 44.2619 14.8804 44.7218 15.326C44.8759 15.4768 45.0074 15.6535 45.1214 15.8363C45.6226 16.6373 46.1162 17.4433 46.6124 18.2529C46.6706 18.3482 46.723 18.3674 46.8304 18.3397C48.0394 18.0219 49.2485 17.7091 50.4638 17.3963C50.7144 17.3317 50.9662 17.3082 51.2231 17.3552C51.7806 17.4573 52.0249 17.8729 51.8357 18.4016C51.7501 18.642 51.6014 18.8444 51.4348 19.037C50.5903 20.0098 49.7434 20.9875 48.9039 21.9566C48.8349 22.0361 48.8335 22.0928 48.8814 22.1824C49.3876 23.1159 49.8887 24.047 50.3974 24.9916C50.5178 25.2148 50.6104 25.4479 50.6129 25.7043C50.6188 26.2882 50.1819 26.7666 49.5893 26.8525C49.2573 26.9 48.949 26.8305 48.6371 26.7366C47.8791 26.5072 47.1211 26.2815 46.3568 26.057C46.2863 26.0364 46.2593 26.009 46.2467 25.9348C46.1595 25.411 46.0637 24.8884 45.971 24.3646C45.9686 24.3502 45.973 24.3349 45.9755 24.3028C46.8413 24.5371 47.7045 24.7677 48.5941 25.0094" fill="#E25A1C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M47.0789 32.6646C46.3948 32.6636 45.7133 32.6603 45.0242 32.6632C44.9336 32.6632 44.8826 32.6378 44.8325 32.5625C44.0231 31.3554 43.2037 30.1459 42.4019 28.9437C42.3759 28.9054 42.3485 28.8685 42.3025 28.8038C42.1283 30.1042 41.9567 31.38 41.7851 32.6558H39.9934C40.0145 32.4852 40.0331 32.3196 40.0552 32.1552C40.2293 30.8427 40.406 29.5423 40.5814 28.2297C40.7493 26.9785 40.9159 25.7272 41.0863 24.476C41.0912 24.439 41.1166 24.3916 41.1475 24.3719C41.7639 23.9781 42.3828 23.588 43.0017 23.198C43.0106 23.1922 43.0233 23.1917 43.0551 23.1826C42.8684 24.5811 42.683 25.9673 42.4963 27.3535C42.5037 27.3583 42.511 27.3635 42.5184 27.3683C43.4894 26.3158 44.4604 25.2584 45.4502 24.1911C45.4786 24.353 45.5035 24.4917 45.528 24.6303C45.5975 25.0265 45.6645 25.4252 45.7384 25.8214C45.7536 25.9014 45.7316 25.9502 45.6773 26.0067C45.0496 26.6495 44.4244 27.2947 43.798 27.9449C43.7705 27.9732 43.7446 28.0024 43.7133 28.036C43.7333 28.0671 43.7505 28.0974 43.7705 28.1251C44.8493 29.6094 45.9255 31.0938 47.003 32.5781C47.0225 32.605 47.0509 32.6261 47.0749 32.65V32.6734" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M25.905 28.7394C25.8776 28.6032 25.858 28.402 25.7959 28.2143C25.4952 27.3078 24.5468 26.8159 23.5657 27.0355C22.487 27.2808 21.7114 28.1113 21.6112 29.1945C21.5285 29.9919 21.967 30.7647 22.7814 31.0469C23.4379 31.2787 24.0719 31.1818 24.6608 30.8371C25.4388 30.3807 25.8598 29.7024 25.9137 28.7394H25.905ZM21.1691 32.1129C21.1162 32.503 21.0653 32.8698 21.0162 33.2378C20.9507 33.726 20.8847 34.2155 20.822 34.7099C20.8148 34.7669 20.7971 34.7923 20.7344 34.7918C20.2421 34.79 19.7497 34.7905 19.256 34.7895C19.2447 34.7895 19.2335 34.7837 19.207 34.7761C19.2369 34.5455 19.2658 34.3136 19.2966 34.083C19.4052 33.2734 19.5134 32.4637 19.6236 31.6541C19.7502 30.7267 19.8491 29.8017 20.0108 28.8817C20.2964 27.2501 21.7147 25.8517 23.3685 25.4959C24.3295 25.2923 25.2479 25.3866 26.0873 25.9167C26.9243 26.4442 27.4029 27.217 27.5156 28.1739C27.671 29.5355 27.1586 30.6641 26.175 31.6087C25.526 32.2233 24.7467 32.6134 23.8572 32.7459C22.9338 32.882 22.0655 32.7315 21.2887 32.1987C21.2603 32.1791 21.2295 32.1619 21.1806 32.1318" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M20.3421 24.3846C19.7908 24.787 19.2583 25.1746 18.7259 25.5635C18.6398 25.431 18.563 25.2985 18.4728 25.1747C18.241 24.8569 17.9528 24.6202 17.5344 24.592C17.1861 24.5679 16.8879 24.68 16.6511 24.9318C16.4393 25.1562 16.4118 25.4764 16.6095 25.7304C16.8275 26.01 17.0655 26.275 17.3098 26.5339C17.7145 26.9632 18.1368 27.3754 18.5377 27.8097C18.9023 28.2022 19.1942 28.6414 19.2844 29.1836C19.3917 29.8215 19.2615 30.4226 18.9536 30.9869C18.3836 32.0235 17.4878 32.6307 16.2975 32.8147C15.7738 32.8975 15.2526 32.8813 14.7439 32.7341C14.0661 32.539 13.5937 32.1036 13.2905 31.4951C13.1834 31.278 13.1014 31.0486 13.0049 30.8155C13.5975 30.5051 14.1751 30.2021 14.7589 29.8967C14.779 29.9442 14.7936 29.9839 14.8123 30.0218C14.9122 30.2169 14.994 30.4242 15.1168 30.6045C15.4839 31.1381 16.0752 31.3001 16.6704 31.0363C16.8245 30.9682 16.9736 30.8732 17.1001 30.7627C17.486 30.4266 17.5587 29.958 17.273 29.536C17.1089 29.2931 16.9034 29.0748 16.7029 28.8576C16.2243 28.3375 15.7257 27.8333 15.2621 27.2997C14.9401 26.9341 14.7209 26.5097 14.6507 26.0239C14.5749 25.4891 14.684 24.9861 14.9677 24.5396C15.6731 23.422 16.6967 22.8467 18.0498 22.8835C18.8228 22.9109 19.4405 23.265 19.9166 23.8538C20.0582 24.028 20.191 24.2084 20.3351 24.3973" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M33.4224 30.3339C33.3323 31.0074 33.247 31.6465 33.1593 32.2844C33.1544 32.317 33.1285 32.3611 33.1005 32.3736C31.7599 32.9796 30.0059 32.8949 28.9159 31.6756C28.327 31.0205 28.0802 30.2403 28.1165 29.3816C28.2021 27.382 29.8956 25.6278 31.9253 25.3825C33.1093 25.2402 34.143 25.553 34.9448 26.4546C35.4911 27.068 35.7442 27.804 35.7066 28.6137C35.6821 29.1485 35.5964 29.6797 35.5287 30.2084C35.4322 30.9616 35.327 31.7173 35.2255 32.4656C35.2216 32.4918 35.2162 32.5187 35.2098 32.5528H33.6186C33.6397 32.3798 33.6592 32.2106 33.6818 32.0413C33.7973 31.1715 33.9248 30.2993 34.0263 29.4283C34.0895 28.8861 34.0497 28.3476 33.7958 27.8459C33.5264 27.3122 33.0754 27.0289 32.4802 26.9675C31.2499 26.84 30.0747 27.6766 29.8241 28.8567C29.6512 29.6393 29.924 30.3901 30.5608 30.8072C31.181 31.2108 31.8388 31.212 32.5153 30.9507C32.8586 30.8182 33.1493 30.6072 33.4274 30.3239" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M40.1881 25.5866C40.1152 26.1288 40.0428 26.6625 39.9701 27.2059C39.6318 27.2059 39.301 27.2041 38.9703 27.2064C38.7022 27.2083 38.4578 27.3781 38.3764 27.6235C38.3451 27.7193 38.3334 27.8222 38.3196 27.9228C38.1518 29.1741 37.9851 30.413 37.8185 31.652C37.7764 31.9661 37.7358 32.2801 37.6937 32.5991H36.0273C36.0582 32.3599 36.0875 32.128 36.1183 31.8962C36.2264 31.089 36.3351 30.2769 36.4441 29.4795C36.5385 28.7815 36.6232 28.081 36.7347 27.3818C36.8838 26.4495 37.831 25.6153 38.7895 25.5785C39.248 25.5589 39.7079 25.5752 40.1802 25.5752" fill="#3C3A3E"/>
+<path d="M49.0837 32.6649V31.978H49.0798L48.8041 32.6649H48.7166L48.4409 31.978H48.4365V32.6649H48.2987V31.8418H48.513L48.7635 32.4674L49.0104 31.8418H49.2234V32.6649H49.0837ZM47.8633 31.9522V32.6649H47.7255V31.9522H47.4624V31.8419H48.1277V31.9522H47.8646" fill="#3C3A3E"/>
+<path d="M22.5598 23.2315H22.972L22.8745 22.6206L22.5598 23.2315ZM23.0372 23.6265H22.3518L22.1338 24.0448H21.6477L22.7189 22.082H23.1875L23.5521 24.0448H23.1023L23.0377 23.6265" fill="#3C3A3E"/>
+<path d="M25.4916 22.4832H25.2473L25.1651 22.9358H25.4094C25.5573 22.9358 25.6738 22.841 25.6738 22.6623C25.6738 22.5439 25.6004 22.4832 25.4909 22.4832H25.4916ZM24.9003 22.0882H25.5417C25.8775 22.0882 26.1118 22.2844 26.1118 22.6206C26.1118 23.045 25.8061 23.3296 25.3701 23.3296H25.0932L24.9629 24.0472H24.5444L24.8978 22.0845" fill="#3C3A3E"/>
+<path d="M27.5337 23.2315H27.9459L27.8486 22.6206L27.5337 23.2315ZM28.0098 23.6265H27.3245L27.1065 24.0448H26.6204L27.6916 22.082H28.1602L28.5248 24.0448H28.075L28.0103 23.6265" fill="#3C3A3E"/>
+<path d="M30.6659 23.9794C30.5243 24.0427 30.3689 24.0805 30.2123 24.0805C29.6824 24.0805 29.3503 23.6916 29.3503 23.2046C29.3503 22.5814 29.8853 22.0576 30.5218 22.0576C30.6809 22.0576 30.825 22.095 30.9428 22.1582L30.884 22.628C30.7959 22.5327 30.6535 22.4661 30.4781 22.4661C30.1135 22.4661 29.789 22.7887 29.789 23.1629C29.789 23.4475 29.9719 23.6708 30.2601 23.6708C30.4367 23.6708 30.6071 23.6041 30.7224 23.5125L30.6666 23.9787" fill="#3C3A3E"/>
+<path d="M33.297 23.256H32.4262L32.2846 24.0448H31.8674L32.2208 22.082H32.6392L32.4989 22.8573H33.3697L33.5112 22.082H33.9297L33.5764 24.0448H33.1579L33.2982 23.256" fill="#3C3A3E"/>
+<path d="M34.8005 24.0409L35.1539 22.0781H36.2639L36.1934 22.4731H35.5018L35.4312 22.8534H36.0665L35.9959 23.2484H35.3607L35.2902 23.6434H35.9818L35.9112 24.0384H34.8012" fill="#3C3A3E"/>
+</g>
+<defs>
+<clipPath id="clip0_3526_17591">
+<rect width="39" height="19.8305" fill="white" transform="translate(13 15)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/third_party/icons/python_logo.svg b/third_party/icons/python_logo.svg
new file mode 100644
index 00000000..764d7cc8
--- /dev/null
+++ b/third_party/icons/python_logo.svg
@@ -0,0 +1,14 @@
+<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M21.0869 0C10.3025 0 10.9755 4.66616 10.9755 4.66616L10.9888 9.50052H21.2794V10.9508H6.89887C6.89887 10.9508 0 10.1694 0 21.0275C0 31.883 6.02274 31.4989 6.02274 31.4989H9.61753V26.4605C9.61753 26.4605 9.42372 20.4514 15.5447 20.4514H25.7503C25.7503 20.4514 31.485 20.5441 31.485 14.9216V5.62508C31.485 5.62508 32.3558 0 21.0869 0ZM15.4119 3.25028C16.4354 3.25028 17.2638 4.07676 17.2638 5.09794C17.2638 6.11912 16.4354 6.94559 15.4119 6.94559C15.1687 6.94594 14.9277 6.89839 14.7029 6.80566C14.478 6.71293 14.2738 6.57685 14.1017 6.40521C13.9297 6.23356 13.7933 6.02974 13.7004 5.80542C13.6074 5.58109 13.5598 5.34068 13.5601 5.09794C13.5601 4.07676 14.3885 3.25028 15.4119 3.25028Z" fill="url(#paint0_linear_3526_17687)"/>
+<path d="M20.9132 42.0006C31.6976 42.0006 31.0245 37.3342 31.0245 37.3342L31.0113 32.4996H20.7207V31.0493H35.0999C35.0999 31.0493 42.0001 31.8307 42.0001 20.9735C42.0001 10.1163 35.9773 10.5017 35.9773 10.5017H32.3825V15.5389C32.3825 15.5389 32.5764 21.5483 26.4554 21.5483H16.2498C16.2498 21.5483 10.5151 21.4556 10.5151 27.0783V36.3752C10.5151 36.3752 9.6443 42.0006 20.9132 42.0006ZM26.5881 38.7501C26.3449 38.7505 26.1039 38.7029 25.8791 38.6102C25.6542 38.5175 25.4499 38.3814 25.2779 38.2097C25.1059 38.0381 24.9695 37.8342 24.8766 37.6099C24.7836 37.3856 24.736 37.1452 24.7363 36.9024C24.7363 35.8825 25.5647 35.056 26.5881 35.056C27.6116 35.056 28.44 35.8812 28.44 36.9024C28.44 37.925 27.6116 38.7501 26.5881 38.7501Z" fill="url(#paint1_linear_3526_17687)"/>
+<defs>
+<linearGradient id="paint0_linear_3526_17687" x1="4.08197" y1="3.68471" x2="25.0391" y2="24.7594" gradientUnits="userSpaceOnUse">
+<stop stop-color="#387EB8"/>
+<stop offset="1" stop-color="#366994"/>
+</linearGradient>
+<linearGradient id="paint1_linear_3526_17687" x1="16.5246" y1="16.8992" x2="39.0359" y2="38.5138" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFE052"/>
+<stop offset="1" stop-color="#FFC331"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/third_party/icons/scala_logo.svg b/third_party/icons/scala_logo.svg
new file mode 100644
index 00000000..9b266a81
--- /dev/null
+++ b/third_party/icons/scala_logo.svg
@@ -0,0 +1,36 @@
+<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3526_17609)">
+<rect width="52" height="52" fill="white"/>
+<g clip-path="url(#clip1_3526_17609)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M43.3194 39.9948C43.2831 39.9173 43.2667 39.8783 43.2472 39.8411C42.7223 38.8428 42.1988 37.8445 41.6685 36.8462C41.6154 36.7461 41.6221 36.687 41.6948 36.6007C42.53 35.6253 43.3544 34.6446 44.191 33.6733C44.2205 33.639 44.2469 33.6027 44.2579 33.5416C44.0151 33.6049 43.7722 33.6675 43.528 33.7318C42.5201 33.9989 41.5041 34.2647 40.5056 34.5358C40.4118 34.5611 40.3693 34.5337 40.3221 34.4557C39.75 33.4992 39.1739 32.5401 38.595 31.5958C38.5655 31.5462 38.5332 31.4982 38.4701 31.4568C38.4237 31.7118 38.3767 31.9654 38.3311 32.2204C38.1705 33.1202 38.01 34.0146 37.8508 34.9184C37.8333 35.0154 37.8096 35.1127 37.8012 35.2098C37.7933 35.3026 37.7454 35.3369 37.6609 35.3636C36.4722 35.7373 35.2862 36.1137 34.0988 36.49C34.0467 36.5064 33.9956 36.528 33.9396 36.5785C34.9111 36.9644 35.8826 37.3502 36.8675 37.7428C36.8316 37.7712 36.8079 37.7934 36.7816 37.8102C36.1744 38.2028 35.5659 38.5954 34.9601 38.9893C34.8873 39.0368 34.8299 39.0435 34.7482 39.0067C34.0223 38.6802 33.291 38.3591 32.5624 38.0354C32.2359 37.8897 31.9431 37.6968 31.7151 37.4162C31.1983 36.7848 31.3008 36.0671 31.989 35.6219C32.2143 35.4776 32.4707 35.371 32.7257 35.2874C33.8901 34.9083 35.0599 34.5427 36.2203 34.1758C36.3182 34.1446 36.3633 34.1009 36.3822 33.995C36.5387 33.0939 36.6993 32.1873 36.8625 31.2969C36.9494 30.8153 36.9953 30.3229 37.2295 29.8805C37.3193 29.7105 37.4265 29.5445 37.5561 29.4029C38.0202 28.8876 38.6665 28.8687 39.1617 29.3587C39.3277 29.5246 39.4693 29.7188 39.5921 29.9199C40.1318 30.8008 40.6635 31.6871 41.1978 32.5774C41.2605 32.6823 41.3169 32.7033 41.4325 32.6728C42.7346 32.3234 44.0366 31.9794 45.3454 31.6354C45.6153 31.5643 45.8865 31.5386 46.1631 31.5902C46.7635 31.7025 47.0266 32.1595 46.8229 32.741C46.7307 33.0054 46.5706 33.228 46.3911 33.4397C45.4817 34.5095 44.5696 35.5847 43.6656 36.6504C43.5912 36.7379 43.5898 36.8002 43.6413 36.8987C44.1864 37.9253 44.7261 38.9492 45.2739 39.988C45.4036 40.2335 45.5033 40.4898 45.506 40.7717C45.5123 41.4139 45.0419 41.94 44.4036 42.0344C44.0461 42.0867 43.7142 42.0103 43.3782 41.907C42.5619 41.6547 41.7456 41.4065 40.9225 41.1596C40.8465 41.1369 40.8175 41.1068 40.8039 41.0252C40.71 40.4492 40.6069 39.8745 40.5071 39.2985C40.5045 39.2827 40.5092 39.2658 40.5119 39.2305C41.4443 39.4881 42.3739 39.7418 43.3319 40.0075" fill="#E25A1C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M41.7004 48.4264C40.9637 48.4253 40.2297 48.4217 39.4876 48.4249C39.39 48.4249 39.3351 48.397 39.2811 48.3141C38.4095 46.9867 37.5271 45.6565 36.6635 44.3345C36.6356 44.2924 36.6061 44.2518 36.5565 44.1807C36.369 45.6107 36.1841 47.0137 35.9993 48.4167H34.0698C34.0925 48.2292 34.1126 48.047 34.1363 47.8663C34.3239 46.4228 34.5141 44.9928 34.703 43.5494C34.8838 42.1733 35.0633 40.7973 35.2468 39.4213C35.2521 39.3807 35.2794 39.3285 35.3126 39.3069C35.9765 38.8739 36.643 38.4449 37.3096 38.0159C37.3191 38.0095 37.3328 38.009 37.367 37.999C37.166 39.5369 36.9663 41.0613 36.7653 42.5857C36.7732 42.591 36.7811 42.5968 36.789 42.6021C37.8347 41.4446 38.8804 40.2817 39.9463 39.1081C39.977 39.2861 40.0038 39.4386 40.0301 39.591C40.105 40.0268 40.1772 40.4652 40.2568 40.9009C40.2731 40.9889 40.2494 41.0426 40.1909 41.1046C39.515 41.8115 38.8417 42.5211 38.167 43.2361C38.1375 43.2673 38.1096 43.2994 38.0758 43.3363C38.0974 43.3705 38.1159 43.4038 38.1375 43.4343C39.2992 45.0666 40.4582 46.6989 41.6186 48.3313C41.6397 48.3608 41.6702 48.384 41.6961 48.4103V48.4361" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M18.8976 44.1093C18.868 43.9596 18.847 43.7383 18.78 43.5319C18.4562 42.535 17.4348 41.994 16.3783 42.2355C15.2166 42.5053 14.3814 43.4186 14.2735 44.6098C14.1844 45.4867 14.6566 46.3366 15.5337 46.6468C16.2407 46.9018 16.9234 46.7952 17.5576 46.4162C18.3955 45.9143 18.8488 45.1683 18.9069 44.1093H18.8976ZM13.7973 47.8192C13.7404 48.2481 13.6856 48.6515 13.6327 49.0562C13.5621 49.5931 13.491 50.1314 13.4235 50.6751C13.4157 50.7378 13.3967 50.7657 13.3292 50.7652C12.799 50.7632 12.2687 50.7637 11.7371 50.7626C11.7249 50.7626 11.7128 50.7563 11.6843 50.7479C11.7164 50.4943 11.7476 50.2393 11.7808 49.9857C11.8978 49.0953 12.0142 48.205 12.133 47.3146C12.2692 46.2947 12.3758 45.2776 12.5499 44.2658C12.8575 42.4716 14.3849 40.9337 16.1659 40.5425C17.2008 40.3185 18.1899 40.4223 19.0939 41.0052C19.9952 41.5853 20.5106 42.4352 20.632 43.4874C20.7994 44.9848 20.2475 46.2259 19.1883 47.2647C18.4894 47.9406 17.6501 48.3696 16.6922 48.5153C15.6977 48.665 14.7627 48.4995 13.9261 47.9136C13.8955 47.892 13.8623 47.8731 13.8097 47.8399" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9068 39.3201C12.3131 39.7626 11.7396 40.1889 11.1662 40.6165C11.0735 40.4708 10.9908 40.3252 10.8936 40.1889C10.644 39.8395 10.3337 39.5791 9.88303 39.5481C9.50793 39.5217 9.1868 39.645 8.93179 39.9218C8.70376 40.1687 8.67408 40.5208 8.88699 40.8C9.12177 41.1076 9.37813 41.399 9.64124 41.6836C10.0771 42.1558 10.5318 42.6091 10.9635 43.0866C11.3562 43.5183 11.6705 44.0013 11.7677 44.5975C11.8832 45.299 11.743 45.9601 11.4115 46.5806C10.7976 47.7206 9.83283 48.3883 8.55102 48.5907C7.98702 48.6817 7.42573 48.6639 6.87792 48.502C6.14796 48.2876 5.63929 47.8086 5.31276 47.1395C5.1974 46.9007 5.10902 46.6485 5.00513 46.3922C5.64333 46.0509 6.26535 45.7176 6.89411 45.3817C6.9157 45.4339 6.93149 45.4777 6.95159 45.5193C7.05913 45.7338 7.14724 45.9618 7.27946 46.1601C7.6748 46.747 8.31166 46.925 8.95257 46.635C9.11853 46.5601 9.27909 46.4556 9.41537 46.3342C9.83094 45.9645 9.9092 45.4492 9.60157 44.9851C9.42481 44.718 9.20353 44.4779 8.98765 44.2391C8.47222 43.6671 7.93521 43.1127 7.43598 42.5258C7.08922 42.1238 6.85309 41.6571 6.77753 41.1228C6.6959 40.5347 6.81343 39.9816 7.1189 39.4905C7.87854 38.2615 8.9809 37.6288 10.4381 37.6693C11.2706 37.6994 11.9358 38.0889 12.4485 38.7364C12.601 38.928 12.744 39.1263 12.8992 39.334" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M26.9932 45.8631C26.8962 46.6037 26.8043 47.3066 26.7098 48.0081C26.7046 48.0439 26.6766 48.0924 26.6466 48.1061C25.2028 48.7726 23.3138 48.6795 22.14 47.3385C21.5058 46.6181 21.24 45.7602 21.2791 44.8158C21.3713 42.6169 23.1951 40.6878 25.3809 40.418C26.656 40.2615 27.7692 40.6055 28.6327 41.597C29.221 42.2716 29.4935 43.081 29.4531 43.9713C29.4267 44.5595 29.3345 45.1437 29.2615 45.7251C29.1576 46.5534 29.0442 47.3844 28.9349 48.2073C28.9307 48.2362 28.9249 48.2657 28.9181 48.3032H27.2045C27.2272 48.113 27.2482 47.9268 27.2725 47.7407C27.3969 46.7842 27.5342 45.8251 27.6435 44.8672C27.7115 44.271 27.6688 43.6787 27.3953 43.127C27.1052 42.5402 26.6194 42.2285 25.9785 42.1611C24.6535 42.0208 23.3879 42.9408 23.1181 44.2386C22.9319 45.0993 23.2256 45.9249 23.9114 46.3836C24.5793 46.8274 25.2877 46.8287 26.0163 46.5414C26.386 46.3957 26.699 46.1637 26.9986 45.852" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M34.2794 40.6426C34.2009 41.2389 34.1229 41.8257 34.0446 42.4233C33.6803 42.4233 33.3241 42.4213 32.9679 42.4239C32.6792 42.426 32.416 42.6127 32.3283 42.8825C32.2946 42.9879 32.2821 43.1011 32.2672 43.2117C32.0864 44.5877 31.907 45.9502 31.7275 47.3128C31.6822 47.6581 31.6385 48.0035 31.5931 48.3542H29.7986C29.8318 48.0911 29.8633 47.8362 29.8965 47.5812C30.013 46.6935 30.13 45.8005 30.2474 44.9236C30.3491 44.156 30.4403 43.3857 30.5604 42.6168C30.7209 41.5915 31.741 40.6742 32.7732 40.6337C33.267 40.6121 33.7622 40.63 34.2709 40.63" fill="#3C3A3E"/>
+<path d="M43.8592 48.4262V47.6707H43.8551L43.5582 48.4262H43.4639L43.1671 47.6707H43.1623V48.4262H43.0139V47.521H43.2447L43.5145 48.209L43.7803 47.521H44.0097V48.4262H43.8592ZM42.5451 47.6424V48.4262H42.3966V47.6424H42.1133V47.5211H42.8297V47.6424H42.5464" fill="#3C3A3E"/>
+<path d="M15.295 38.0521H15.7389L15.634 37.3803L15.295 38.0521ZM15.8091 38.4865H15.071L14.8363 38.9465H14.3127L15.4664 36.7881H15.971L16.3636 38.9465H15.8793L15.8096 38.4865" fill="#3C3A3E"/>
+<path d="M18.4522 37.2295H18.1891L18.1006 37.7272H18.3637C18.5229 37.7272 18.6484 37.623 18.6484 37.4264C18.6484 37.2962 18.5693 37.2295 18.4514 37.2295H18.4522ZM17.8153 36.7951H18.5062C18.8678 36.7951 19.1201 37.0109 19.1201 37.3805C19.1201 37.8473 18.7908 38.1603 18.3213 38.1603H18.0231L17.8828 38.9495H17.4321L17.8126 36.791" fill="#3C3A3E"/>
+<path d="M20.6516 38.0521H21.0955L20.9907 37.3803L20.6516 38.0521ZM21.1643 38.4865H20.4263L20.1915 38.9465H19.668L20.8216 36.7881H21.3262L21.7189 38.9465H21.2345L21.1649 38.4865" fill="#3C3A3E"/>
+<path d="M24.0249 38.8751C23.8724 38.9447 23.7051 38.9863 23.5365 38.9863C22.9657 38.9863 22.6082 38.5586 22.6082 38.0231C22.6082 37.3378 23.1843 36.7617 23.8697 36.7617C24.0411 36.7617 24.1963 36.8029 24.3231 36.8723L24.2598 37.389C24.1649 37.2842 24.0115 37.2109 23.8226 37.2109C23.43 37.2109 23.0805 37.5657 23.0805 37.9772C23.0805 38.2902 23.2775 38.5357 23.5879 38.5357C23.7781 38.5357 23.9616 38.4624 24.0857 38.3617L24.0257 38.8743" fill="#3C3A3E"/>
+<path d="M26.8584 38.0791H25.9206L25.7682 38.9465H25.3188L25.6993 36.7881H26.15L25.9989 37.6407H26.9366L27.0891 36.7881H27.5398L27.1593 38.9465H26.7086L26.8597 38.0791" fill="#3C3A3E"/>
+<path d="M28.4773 38.9426L28.8578 36.7842H30.0532L29.9773 37.2186H29.2325L29.1565 37.6368H29.8406L29.7646 38.0712H29.0806L29.0046 38.5055H29.7494L29.6734 38.9399H28.478" fill="#3C3A3E"/>
+</g>
+<rect x="-26" y="-3" width="107" height="21" fill="#F4F6FB"/>
+<path d="M19.5541 10.6707C19.5541 11.428 19.2806 12.0247 18.7336 12.4607C18.1905 12.8967 17.4332 13.1147 16.4617 13.1147C15.5667 13.1147 14.7749 12.9465 14.0864 12.6099V10.9575C14.6525 11.21 15.1306 11.3878 15.5208 11.4911C15.9147 11.5944 16.2743 11.646 16.5994 11.646C16.9895 11.646 17.2878 11.5714 17.4944 11.4222C17.7048 11.2731 17.8099 11.0512 17.8099 10.7567C17.8099 10.5922 17.764 10.4469 17.6722 10.3207C17.5804 10.1906 17.4447 10.0663 17.2649 9.94775C17.0889 9.82918 16.7275 9.63985 16.1805 9.37976C15.668 9.13879 15.2836 8.90739 15.0273 8.68555C14.7711 8.4637 14.5664 8.20553 14.4135 7.91101C14.2605 7.6165 14.184 7.27226 14.184 6.8783C14.184 6.13627 14.4345 5.55298 14.9355 5.12842C15.4404 4.70386 16.1366 4.49158 17.0239 4.49158C17.46 4.49158 17.875 4.54321 18.2689 4.64648C18.6667 4.74976 19.0817 4.8951 19.5139 5.08252L18.9402 6.46521C18.4927 6.28162 18.1217 6.15348 17.8271 6.08081C17.5365 6.00814 17.2496 5.9718 16.9666 5.9718C16.63 5.9718 16.3718 6.05021 16.192 6.20703C16.0122 6.36385 15.9224 6.56848 15.9224 6.82092C15.9224 6.97774 15.9587 7.11544 16.0314 7.23401C16.104 7.34875 16.2188 7.46159 16.3756 7.57251C16.5363 7.67961 16.913 7.87467 17.5059 8.15771C18.29 8.53255 18.8274 8.9093 19.118 9.28796C19.4087 9.6628 19.5541 10.1237 19.5541 10.6707Z" fill="black"/>
+<path d="M23.189 13.1147C21.1924 13.1147 20.1941 12.0189 20.1941 9.82727C20.1941 8.73718 20.4657 7.90527 21.0088 7.33154C21.552 6.75399 22.3303 6.46521 23.3439 6.46521C24.0859 6.46521 24.7515 6.61055 25.3405 6.90125L24.8241 8.25525C24.5487 8.14433 24.2925 8.05444 24.0553 7.9856C23.8182 7.91292 23.5811 7.87659 23.3439 7.87659C22.4336 7.87659 21.9784 8.52299 21.9784 9.8158C21.9784 11.0704 22.4336 11.6976 23.3439 11.6976C23.6805 11.6976 23.9922 11.6536 24.2791 11.5657C24.566 11.4739 24.8528 11.3324 25.1397 11.1411V12.6385C24.8566 12.8183 24.5698 12.9426 24.2791 13.0115C23.9922 13.0803 23.6289 13.1147 23.189 13.1147Z" fill="black"/>
+<path d="M30.3524 13L30.0139 12.1279H29.968C29.6735 12.4989 29.3694 12.7571 29.0557 12.9025C28.7459 13.044 28.3405 13.1147 27.8394 13.1147C27.2236 13.1147 26.7379 12.9388 26.3822 12.5869C26.0303 12.235 25.8543 11.734 25.8543 11.0837C25.8543 10.4029 26.0915 9.90186 26.5658 9.58057C27.0439 9.25545 27.7629 9.07568 28.723 9.04126L29.836 9.00684V8.72571C29.836 8.07548 29.5033 7.75037 28.8377 7.75037C28.3252 7.75037 27.7228 7.90527 27.0305 8.21509L26.451 7.0332C27.1892 6.64689 28.0077 6.45374 28.9066 6.45374C29.7672 6.45374 30.427 6.64115 30.8859 7.01599C31.3449 7.39083 31.5744 7.96073 31.5744 8.72571V13H30.3524ZM29.836 10.0281L29.159 10.051C28.6503 10.0663 28.2716 10.1581 28.023 10.3264C27.7744 10.4947 27.6501 10.751 27.6501 11.0952C27.6501 11.5886 27.9331 11.8353 28.4992 11.8353C28.9047 11.8353 29.2279 11.7187 29.4688 11.4854C29.7136 11.252 29.836 10.9422 29.836 10.5559V10.0281Z" fill="black"/>
+<path d="M34.779 13H33.0292V4.07275H34.779V13Z" fill="black"/>
+<path d="M40.3417 13L40.0032 12.1279H39.9573C39.6628 12.4989 39.3587 12.7571 39.0451 12.9025C38.7353 13.044 38.3298 13.1147 37.8288 13.1147C37.213 13.1147 36.7272 12.9388 36.3715 12.5869C36.0196 12.235 35.8437 11.734 35.8437 11.0837C35.8437 10.4029 36.0808 9.90186 36.5551 9.58057C37.0332 9.25545 37.7523 9.07568 38.7123 9.04126L39.8254 9.00684V8.72571C39.8254 8.07548 39.4926 7.75037 38.8271 7.75037C38.3145 7.75037 37.7121 7.90527 37.0198 8.21509L36.4403 7.0332C37.1785 6.64689 37.9971 6.45374 38.8959 6.45374C39.7565 6.45374 40.4163 6.64115 40.8753 7.01599C41.3343 7.39083 41.5638 7.96073 41.5638 8.72571V13H40.3417ZM39.8254 10.0281L39.1483 10.051C38.6396 10.0663 38.261 10.1581 38.0124 10.3264C37.7637 10.4947 37.6394 10.751 37.6394 11.0952C37.6394 11.5886 37.9225 11.8353 38.4886 11.8353C38.894 11.8353 39.2172 11.7187 39.4582 11.4854C39.703 11.252 39.8254 10.9422 39.8254 10.5559V10.0281Z" fill="black"/>
+<path d="M25.065 25.4977C25.065 26.0682 25.0104 26.5721 24.901 27.0093C24.7948 27.4434 24.6338 27.8093 24.4182 28.107C24.2057 28.4016 23.9415 28.6248 23.6257 28.7767C23.3129 28.9256 22.95 29 22.5371 29C22.1271 29 21.7643 28.924 21.4484 28.7721C21.1326 28.6202 20.8669 28.3969 20.6513 28.1023C20.4357 27.8047 20.2733 27.4372 20.164 27C20.0547 26.5628 20 26.0589 20 25.4884C20 24.7318 20.0957 24.0946 20.287 23.5767C20.4813 23.0589 20.7667 22.6667 21.1433 22.4C21.5228 22.1333 21.9874 22 22.5371 22C23.0897 22 23.5528 22.1318 23.9263 22.3953C24.3028 22.6589 24.5867 23.0512 24.7781 23.5721C24.9694 24.0899 25.065 24.7318 25.065 25.4977ZM21.6489 25.4977C21.6489 25.9597 21.6823 26.3457 21.7491 26.6558C21.8159 26.9659 21.9146 27.1984 22.0451 27.3535C22.1787 27.5054 22.3427 27.5814 22.5371 27.5814C22.7375 27.5814 22.9014 27.507 23.029 27.3581C23.1596 27.2093 23.2552 26.9814 23.3159 26.6744C23.3797 26.3643 23.4116 25.9721 23.4116 25.4977C23.4116 24.7783 23.3387 24.2481 23.193 23.907C23.0502 23.5628 22.8316 23.3907 22.5371 23.3907C22.3427 23.3907 22.1787 23.4698 22.0451 23.6279C21.9146 23.7829 21.8159 24.0171 21.7491 24.3302C21.6823 24.6403 21.6489 25.0295 21.6489 25.4977Z" fill="#3C3A3E"/>
+<path d="M31 28.907H29.1507L27.4153 24.6791H27.3789C27.3971 24.9271 27.4108 25.1581 27.4199 25.3721C27.432 25.5829 27.4396 25.7752 27.4427 25.9488C27.4487 26.1194 27.4518 26.2698 27.4518 26.4V28.907H25.9805V22.107H27.8161L29.5424 26.2186H29.5789C29.5667 25.9891 29.5561 25.7752 29.547 25.5767C29.5379 25.3783 29.5303 25.1953 29.5242 25.0279C29.5182 24.8605 29.5151 24.7085 29.5151 24.5721V22.107H31V28.907Z" fill="#3C3A3E"/>
+</g>
+<defs>
+<clipPath id="clip0_3526_17609">
+<rect width="52" height="52" fill="white"/>
+</clipPath>
+<clipPath id="clip1_3526_17609">
+<rect width="42" height="21.8077" fill="white" transform="translate(5 29)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/third_party/icons/sparkr_logo.svg b/third_party/icons/sparkr_logo.svg
new file mode 100644
index 00000000..ade6f041
--- /dev/null
+++ b/third_party/icons/sparkr_logo.svg
@@ -0,0 +1,32 @@
+<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3526_17649)">
+<rect width="52" height="52" fill="white"/>
+<g clip-path="url(#clip1_3526_17649)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M43.3194 39.9948C43.2831 39.9173 43.2667 39.8783 43.2472 39.8411C42.7223 38.8428 42.1988 37.8445 41.6685 36.8462C41.6154 36.7461 41.6221 36.687 41.6948 36.6007C42.53 35.6253 43.3544 34.6446 44.191 33.6733C44.2205 33.639 44.2469 33.6027 44.2579 33.5416C44.0151 33.6049 43.7722 33.6675 43.528 33.7318C42.5201 33.9989 41.5041 34.2647 40.5056 34.5358C40.4118 34.5611 40.3693 34.5337 40.3221 34.4557C39.75 33.4992 39.1739 32.5401 38.595 31.5958C38.5655 31.5462 38.5332 31.4982 38.4701 31.4568C38.4237 31.7118 38.3767 31.9654 38.3311 32.2204C38.1705 33.1202 38.01 34.0146 37.8508 34.9184C37.8333 35.0154 37.8096 35.1127 37.8012 35.2098C37.7933 35.3026 37.7454 35.3369 37.6609 35.3636C36.4722 35.7373 35.2862 36.1137 34.0988 36.4901C34.0467 36.5064 33.9956 36.528 33.9396 36.5785C34.9111 36.9644 35.8826 37.3502 36.8675 37.7428C36.8316 37.7712 36.8079 37.7934 36.7816 37.8102C36.1744 38.2028 35.5659 38.5954 34.9601 38.9893C34.8873 39.0368 34.8299 39.0435 34.7482 39.0067C34.0223 38.6802 33.291 38.3591 32.5624 38.0354C32.2359 37.8897 31.9431 37.6968 31.7151 37.4162C31.1983 36.7848 31.3008 36.0671 31.989 35.6219C32.2143 35.4776 32.4707 35.371 32.7257 35.2874C33.8901 34.9083 35.0599 34.5427 36.2203 34.1758C36.3182 34.1446 36.3633 34.1009 36.3822 33.995C36.5387 33.0939 36.6993 32.1873 36.8625 31.2969C36.9494 30.8153 36.9953 30.3229 37.2295 29.8805C37.3193 29.7105 37.4265 29.5445 37.5561 29.4029C38.0202 28.8876 38.6665 28.8687 39.1617 29.3587C39.3277 29.5246 39.4693 29.7188 39.5921 29.9199C40.1318 30.8008 40.6635 31.6871 41.1978 32.5774C41.2605 32.6823 41.3169 32.7033 41.4325 32.6728C42.7346 32.3234 44.0366 31.9794 45.3454 31.6354C45.6153 31.5643 45.8865 31.5386 46.1631 31.5902C46.7635 31.7025 47.0266 32.1595 46.8229 32.741C46.7307 33.0054 46.5706 33.228 46.3911 33.4398C45.4817 34.5095 44.5696 35.5847 43.6656 36.6505C43.5912 36.7379 43.5898 36.8002 43.6413 36.8987C44.1864 37.9253 44.7261 38.9492 45.2739 39.988C45.4036 40.2335 45.5033 40.4898 45.506 40.7717C45.5123 41.4139 45.0419 41.94 44.4036 42.0344C44.0461 42.0867 43.7142 42.0103 43.3782 41.907C42.5619 41.6547 41.7456 41.4065 40.9225 41.1596C40.8465 41.1369 40.8175 41.1068 40.8039 41.0252C40.71 40.4492 40.6069 39.8745 40.5071 39.2985C40.5045 39.2827 40.5092 39.2658 40.5119 39.2305C41.4443 39.4881 42.3739 39.7418 43.3319 40.0075" fill="#E25A1C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M41.7003 48.4264C40.9636 48.4253 40.2295 48.4217 39.4874 48.4249C39.3899 48.4249 39.335 48.397 39.281 48.3141C38.4094 46.9867 37.5269 45.6565 36.6634 44.3345C36.6355 44.2924 36.6059 44.2518 36.5564 44.1807C36.3689 45.6107 36.184 47.0137 35.9992 48.4167H34.0697C34.0924 48.2292 34.1125 48.047 34.1362 47.8663C34.3238 46.4228 34.514 44.9928 34.7029 43.5494C34.8837 42.1733 35.0632 40.7973 35.2467 39.4213C35.2519 39.3807 35.2793 39.3285 35.3125 39.3069C35.9764 38.8739 36.6429 38.4449 37.3094 38.0159C37.319 38.0095 37.3327 38.009 37.3669 37.999C37.1659 39.5369 36.9662 41.0613 36.7651 42.5857C36.7731 42.591 36.7809 42.5968 36.7889 42.6021C37.8346 41.4446 38.8803 40.2817 39.9462 39.1081C39.9768 39.2861 40.0037 39.4386 40.03 39.591C40.1049 40.0268 40.1771 40.4652 40.2567 40.9009C40.273 40.9889 40.2492 41.0426 40.1908 41.1046C39.5148 41.8115 38.8415 42.5211 38.1669 43.2361C38.1374 43.2673 38.1094 43.2994 38.0757 43.3363C38.0973 43.3705 38.1158 43.4038 38.1374 43.4343C39.2991 45.0666 40.4581 46.6989 41.6185 48.3313C41.6395 48.3608 41.67 48.384 41.6959 48.4103V48.4361" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M18.8976 44.1093C18.868 43.9596 18.847 43.7383 18.78 43.5319C18.4562 42.535 17.4348 41.994 16.3783 42.2355C15.2166 42.5053 14.3814 43.4186 14.2735 44.6098C14.1844 45.4867 14.6566 46.3366 15.5337 46.6468C16.2407 46.9018 16.9234 46.7952 17.5576 46.4162C18.3955 45.9143 18.8488 45.1683 18.9069 44.1093H18.8976ZM13.7973 47.8192C13.7404 48.2481 13.6856 48.6515 13.6327 49.0562C13.5621 49.5931 13.491 50.1314 13.4235 50.6751C13.4157 50.7378 13.3967 50.7657 13.3292 50.7652C12.799 50.7631 12.2687 50.7637 11.7371 50.7626C11.7249 50.7626 11.7128 50.7563 11.6843 50.7479C11.7164 50.4943 11.7476 50.2393 11.7808 49.9857C11.8978 49.0953 12.0142 48.205 12.133 47.3146C12.2692 46.2947 12.3758 45.2776 12.5499 44.2658C12.8575 42.4716 14.3849 40.9337 16.1659 40.5425C17.2008 40.3185 18.1899 40.4223 19.0939 41.0052C19.9952 41.5853 20.5106 42.4352 20.632 43.4874C20.7994 44.9848 20.2475 46.2259 19.1883 47.2647C18.4894 47.9406 17.6501 48.3696 16.6922 48.5153C15.6977 48.665 14.7627 48.4995 13.9261 47.9136C13.8955 47.892 13.8623 47.8731 13.8097 47.8399" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9065 39.3201C12.3128 39.7626 11.7394 40.1889 11.1659 40.6165C11.0732 40.4708 10.9905 40.3252 10.8934 40.1889C10.6438 39.8395 10.3334 39.5791 9.88278 39.5481C9.50768 39.5217 9.18656 39.645 8.93154 39.9218C8.70352 40.1687 8.67383 40.5208 8.88675 40.8C9.12152 41.1076 9.37788 41.399 9.64099 41.6836C10.0768 42.1558 10.5315 42.6091 10.9633 43.0866C11.3559 43.5183 11.6703 44.0013 11.7675 44.5975C11.8829 45.299 11.7428 45.9601 11.4112 46.5806C10.7973 47.7206 9.83259 48.3883 8.55078 48.5907C7.98678 48.6817 7.42548 48.6639 6.87768 48.502C6.14772 48.2876 5.63904 47.8086 5.31252 47.1395C5.19715 46.9007 5.10878 46.6485 5.00488 46.3922C5.64309 46.0509 6.26511 45.7176 6.89387 45.3817C6.91546 45.4339 6.93124 45.4777 6.95135 45.5193C7.05888 45.7338 7.14699 45.9618 7.27922 46.1601C7.67456 46.747 8.31142 46.925 8.95232 46.635C9.11828 46.5601 9.27885 46.4556 9.41512 46.3342C9.8307 45.9645 9.90896 45.4492 9.60132 44.9851C9.42457 44.718 9.20329 44.4779 8.9874 44.2391C8.47198 43.6671 7.93497 43.1127 7.43574 42.5258C7.08897 42.1238 6.85285 41.6571 6.77729 41.1228C6.69566 40.5347 6.81318 39.9816 7.11866 39.4905C7.8783 38.2615 8.98066 37.6288 10.4379 37.6693C11.2704 37.6994 11.9356 38.0889 12.4483 38.7364C12.6008 38.928 12.7438 39.1263 12.899 39.334" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M26.9932 45.8631C26.8962 46.6037 26.8043 47.3066 26.7098 48.0081C26.7046 48.0439 26.6766 48.0924 26.6466 48.1061C25.2028 48.7726 23.3138 48.6795 22.14 47.3385C21.5058 46.6181 21.24 45.7602 21.2791 44.8158C21.3713 42.6169 23.1951 40.6878 25.3809 40.418C26.656 40.2615 27.7692 40.6055 28.6327 41.597C29.221 42.2716 29.4935 43.081 29.4531 43.9713C29.4267 44.5595 29.3344 45.1437 29.2615 45.7251C29.1576 46.5534 29.0442 47.3844 28.9349 48.2073C28.9307 48.2362 28.9249 48.2657 28.9181 48.3032H27.2045C27.2271 48.113 27.2482 47.9268 27.2725 47.7407C27.3969 46.7842 27.5342 45.8251 27.6435 44.8672C27.7115 44.271 27.6688 43.6787 27.3953 43.127C27.1052 42.5402 26.6194 42.2285 25.9785 42.1611C24.6535 42.0208 23.3879 42.9408 23.1181 44.2386C22.9319 45.0993 23.2256 45.9249 23.9114 46.3836C24.5793 46.8274 25.2877 46.8287 26.0163 46.5414C26.386 46.3957 26.699 46.1637 26.9986 45.852" fill="#3C3A3E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M34.2793 40.6421C34.2007 41.2384 34.1228 41.8252 34.0445 42.4228C33.6802 42.4228 33.324 42.4208 32.9678 42.4234C32.679 42.4255 32.4159 42.6122 32.3282 42.882C32.2945 42.9874 32.2819 43.1006 32.2671 43.2112C32.0863 44.5872 31.9068 45.9497 31.7274 47.3123C31.6821 47.6576 31.6383 48.003 31.593 48.3537H29.7985C29.8317 48.0907 29.8632 47.8357 29.8964 47.5807C30.0129 46.6931 30.1298 45.8 30.2472 44.9231C30.349 44.1555 30.4402 43.3852 30.5603 42.6163C30.7208 41.591 31.7409 40.6737 32.7731 40.6332C33.2669 40.6116 33.7621 40.6296 34.2708 40.6296" fill="#3C3A3E"/>
+<path d="M43.8591 48.4262V47.6707H43.8549L43.5581 48.4262H43.4638L43.1669 47.6707H43.1622V48.4262H43.0138V47.521H43.2445L43.5144 48.209L43.7802 47.521H44.0096V48.4262H43.8591ZM42.5449 47.6424V48.4262H42.3965V47.6424H42.1132V47.5211H42.8296V47.6424H42.5463" fill="#3C3A3E"/>
+<path d="M15.295 38.0521H15.7389L15.634 37.3803L15.295 38.0521ZM15.8091 38.4865H15.071L14.8363 38.9465H14.3127L15.4664 36.7881H15.971L16.3636 38.9465H15.8793L15.8096 38.4865" fill="#3C3A3E"/>
+<path d="M18.4523 37.2295H18.1892L18.1007 37.7272H18.3638C18.523 37.7272 18.6485 37.623 18.6485 37.4264C18.6485 37.2962 18.5694 37.2295 18.4515 37.2295H18.4523ZM17.8154 36.7951H18.5063C18.8679 36.7951 19.1202 37.0109 19.1202 37.3805C19.1202 37.8473 18.791 38.1603 18.3214 38.1603H18.0232L17.8829 38.9495H17.4323L17.8127 36.791" fill="#3C3A3E"/>
+<path d="M20.6516 38.0521H21.0955L20.9907 37.3803L20.6516 38.0521ZM21.1643 38.4865H20.4263L20.1915 38.9465H19.668L20.8216 36.7881H21.3262L21.7189 38.9465H21.2345L21.1649 38.4865" fill="#3C3A3E"/>
+<path d="M24.0248 38.8751C23.8723 38.9447 23.705 38.9863 23.5363 38.9863C22.9656 38.9863 22.608 38.5586 22.608 38.0231C22.608 37.3378 23.1842 36.7617 23.8696 36.7617C24.041 36.7617 24.1961 36.8029 24.323 36.8723L24.2597 37.389C24.1648 37.2842 24.0114 37.2109 23.8225 37.2109C23.4299 37.2109 23.0804 37.5657 23.0804 37.9772C23.0804 38.2902 23.2774 38.5357 23.5877 38.5357C23.778 38.5357 23.9615 38.4624 24.0856 38.3617L24.0256 38.8743" fill="#3C3A3E"/>
+<path d="M26.8582 38.0791H25.9205L25.768 38.9465H25.3187L25.6992 36.7881H26.1499L25.9988 37.6407H26.9365L27.089 36.7881H27.5396L27.1591 38.9465H26.7085L26.8596 38.0791" fill="#3C3A3E"/>
+<path d="M28.4774 38.9426L28.8579 36.7842H30.0534L29.9774 37.2186H29.2326L29.1566 37.6368H29.8407L29.7648 38.0712H29.0807L29.0047 38.5055H29.7495L29.6736 38.9399H28.4781" fill="#3C3A3E"/>
+</g>
+<rect x="-28" y="-3" width="107" height="21" fill="#F4F6FB"/>
+<path d="M24.9558 8.33582H25.5295C26.0918 8.33582 26.5068 8.24211 26.7745 8.05469C27.0423 7.86727 27.1761 7.57275 27.1761 7.17114C27.1761 6.77336 27.0385 6.49032 26.7631 6.32202C26.4915 6.15373 26.0688 6.06958 25.4951 6.06958H24.9558V8.33582ZM24.9558 9.78162V13.0002H23.1772V4.6123H25.6213C26.7611 4.6123 27.6045 4.82076 28.1515 5.23767C28.6984 5.65076 28.9719 6.27995 28.9719 7.12524C28.9719 7.61865 28.8361 8.05851 28.5646 8.44482C28.293 8.82731 27.9086 9.12756 27.4114 9.34558C28.6736 11.2312 29.4959 12.4495 29.8784 13.0002H27.9048L25.9025 9.78162H24.9558Z" fill="black"/>
+<path d="M25.065 25.4977C25.065 26.0682 25.0104 26.5721 24.901 27.0093C24.7948 27.4434 24.6338 27.8093 24.4182 28.107C24.2057 28.4016 23.9415 28.6248 23.6257 28.7767C23.3129 28.9256 22.95 29 22.5371 29C22.1271 29 21.7643 28.924 21.4484 28.7721C21.1326 28.6202 20.8669 28.3969 20.6513 28.1023C20.4357 27.8047 20.2733 27.4372 20.164 27C20.0547 26.5628 20 26.0589 20 25.4884C20 24.7318 20.0957 24.0946 20.287 23.5767C20.4813 23.0589 20.7667 22.6667 21.1433 22.4C21.5228 22.1333 21.9874 22 22.5371 22C23.0897 22 23.5528 22.1318 23.9263 22.3953C24.3028 22.6589 24.5867 23.0512 24.7781 23.5721C24.9694 24.0899 25.065 24.7318 25.065 25.4977ZM21.6489 25.4977C21.6489 25.9597 21.6823 26.3457 21.7491 26.6558C21.8159 26.9659 21.9146 27.1984 22.0451 27.3535C22.1787 27.5054 22.3427 27.5814 22.5371 27.5814C22.7375 27.5814 22.9014 27.507 23.029 27.3581C23.1596 27.2093 23.2552 26.9814 23.3159 26.6744C23.3797 26.3643 23.4116 25.9721 23.4116 25.4977C23.4116 24.7783 23.3387 24.2481 23.193 23.907C23.0502 23.5628 22.8316 23.3907 22.5371 23.3907C22.3427 23.3907 22.1787 23.4698 22.0451 23.6279C21.9146 23.7829 21.8159 24.0171 21.7491 24.3302C21.6823 24.6403 21.6489 25.0295 21.6489 25.4977Z" fill="#3C3A3E"/>
+<path d="M31 28.907H29.1507L27.4153 24.6791H27.3789C27.3971 24.9271 27.4108 25.1581 27.4199 25.3721C27.432 25.5829 27.4396 25.7752 27.4427 25.9488C27.4487 26.1194 27.4518 26.2698 27.4518 26.4V28.907H25.9805V22.107H27.8161L29.5424 26.2186H29.5789C29.5667 25.9891 29.5561 25.7752 29.547 25.5767C29.5379 25.3783 29.5303 25.1953 29.5242 25.0279C29.5182 24.8605 29.5151 24.7085 29.5151 24.5721V22.107H31V28.907Z" fill="#3C3A3E"/>
+</g>
+<defs>
+<clipPath id="clip0_3526_17649">
+<rect width="52" height="52" fill="white"/>
+</clipPath>
+<clipPath id="clip1_3526_17649">
+<rect width="42" height="21.8077" fill="white" transform="translate(5 29)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..b45c2152
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+  "compilerOptions": {
+    "allowSyntheticDefaultImports": true,
+    "composite": true,
+    "declaration": true,
+    "esModuleInterop": true,
+    "incremental": true,
+    "jsx": "react",
+    "module": "esnext",
+    "moduleResolution": "node",
+    "noEmitOnError": true,
+    "noImplicitAny": true,
+    "noUnusedLocals": true,
+    "preserveWatchOutput": true,
+    "resolveJsonModule": true,
+    "outDir": "lib",
+    "rootDir": "src",
+    "strict": true,
+    "strictNullChecks": true,
+    "target": "ES2018",
+    "types": ["jest"],
+    "skipLibCheck": true
+  },
+  "include": ["src/*", "src/**/*"]
+}


Test patch:
diff --git a/conftest.py b/conftest.py
new file mode 100644
index 00000000..c25f8e97
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,22 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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 pytest
+
+pytest_plugins = ("pytest_jupyter.jupyter_server", )
+
+
+@pytest.fixture
+def jp_server_config(jp_server_config):
+    return {"ServerApp": {"jpserver_extensions": {"dataproc_plugin": True}}}
diff --git a/dataproc_plugin/tests/__init__.py b/dataproc_plugin/tests/__init__.py
new file mode 100644
index 00000000..0c04cc36
--- /dev/null
+++ b/dataproc_plugin/tests/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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.
+
+"""Python unit tests for dataproc_plugin."""
diff --git a/dataproc_plugin/tests/test_handlers.py b/dataproc_plugin/tests/test_handlers.py
new file mode 100644
index 00000000..a64912cd
--- /dev/null
+++ b/dataproc_plugin/tests/test_handlers.py
@@ -0,0 +1,27 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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 json
+
+
+async def test_get_example(jp_fetch):
+    # When
+    response = await jp_fetch("dataproc-plugin", "get-example")
+
+    # Then
+    assert response.code == 200
+    payload = json.loads(response.body)
+    assert payload == {
+        "data": "This is /dataproc-plugin/get-example endpoint!"
+    }
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 00000000..1c66acf6
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,3 @@
+{
+  "extends": "./tsconfig"
+}
diff --git a/ui-tests/README.md b/ui-tests/README.md
new file mode 100644
index 00000000..dbe6e8aa
--- /dev/null
+++ b/ui-tests/README.md
@@ -0,0 +1,167 @@
+# Integration Testing
+
+This folder contains the integration tests of the extension.
+
+They are defined using [Playwright](https://playwright.dev/docs/intro) test runner
+and [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) helper.
+
+The Playwright configuration is defined in [playwright.config.js](./playwright.config.js).
+
+The JupyterLab server configuration to use for the integration test is defined
+in [jupyter_server_test_config.py](./jupyter_server_test_config.py).
+
+The default configuration will produce video for failing tests and an HTML report.
+
+> There is a new experimental UI mode that you may fall in love with; see [that video](https://www.youtube.com/watch?v=jF0yA-JLQW0).
+
+## Run the tests
+
+> All commands are assumed to be executed from the root directory
+
+To run the tests, you need to:
+
+1. Compile the extension:
+
+```sh
+jlpm install
+jlpm build:prod
+```
+
+> Check the extension is installed in JupyterLab.
+
+2. Install test dependencies (needed only once):
+
+```sh
+cd ./ui-tests
+jlpm install
+jlpm playwright install
+cd ..
+```
+
+3. Execute the [Playwright](https://playwright.dev/docs/intro) tests:
+
+```sh
+cd ./ui-tests
+jlpm playwright test
+```
+
+Test results will be shown in the terminal. In case of any test failures, the test report
+will be opened in your browser at the end of the tests execution; see
+[Playwright documentation](https://playwright.dev/docs/test-reporters#html-reporter)
+for configuring that behavior.
+
+## Update the tests snapshots
+
+> All commands are assumed to be executed from the root directory
+
+If you are comparing snapshots to validate your tests, you may need to update
+the reference snapshots stored in the repository. To do that, you need to:
+
+1. Compile the extension:
+
+```sh
+jlpm install
+jlpm build:prod
+```
+
+> Check the extension is installed in JupyterLab.
+
+2. Install test dependencies (needed only once):
+
+```sh
+cd ./ui-tests
+jlpm install
+jlpm playwright install
+cd ..
+```
+
+3. Execute the [Playwright](https://playwright.dev/docs/intro) command:
+
+```sh
+cd ./ui-tests
+jlpm playwright test -u
+```
+
+> Some discrepancy may occurs between the snapshots generated on your computer and
+> the one generated on the CI. To ease updating the snapshots on a PR, you can
+> type `please update playwright snapshots` to trigger the update by a bot on the CI.
+> Once the bot has computed new snapshots, it will commit them to the PR branch.
+
+## Create tests
+
+> All commands are assumed to be executed from the root directory
+
+To create tests, the easiest way is to use the code generator tool of playwright:
+
+1. Compile the extension:
+
+```sh
+jlpm install
+jlpm build:prod
+```
+
+> Check the extension is installed in JupyterLab.
+
+2. Install test dependencies (needed only once):
+
+```sh
+cd ./ui-tests
+jlpm install
+jlpm playwright install
+cd ..
+```
+
+3. Start the server:
+
+```sh
+cd ./ui-tests
+jlpm start
+```
+
+4. Execute the [Playwright code generator](https://playwright.dev/docs/codegen) in **another terminal**:
+
+```sh
+cd ./ui-tests
+jlpm playwright codegen localhost:8888
+```
+
+## Debug tests
+
+> All commands are assumed to be executed from the root directory
+
+To debug tests, a good way is to use the inspector tool of playwright:
+
+1. Compile the extension:
+
+```sh
+jlpm install
+jlpm build:prod
+```
+
+> Check the extension is installed in JupyterLab.
+
+2. Install test dependencies (needed only once):
+
+```sh
+cd ./ui-tests
+jlpm install
+jlpm playwright install
+cd ..
+```
+
+3. Execute the Playwright tests in [debug mode](https://playwright.dev/docs/debug):
+
+```sh
+cd ./ui-tests
+jlpm playwright test --debug
+```
+
+## Upgrade Playwright and the browsers
+
+To update the web browser versions, you must update the package `@playwright/test`:
+
+```sh
+cd ./ui-tests
+jlpm up "@playwright/test"
+jlpm playwright install
+```
diff --git a/ui-tests/jupyter_server_test_config.py b/ui-tests/jupyter_server_test_config.py
new file mode 100644
index 00000000..9dca5f84
--- /dev/null
+++ b/ui-tests/jupyter_server_test_config.py
@@ -0,0 +1,26 @@
+# Copyright 2023 Google LLC
+#
+# Licensed 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
+#
+# https://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.
+
+"""Server configuration for integration tests.
+
+!! Never use this configuration in production because it
+opens the server to the world and provide access to JupyterLab
+JavaScript objects through the global window variable.
+"""
+from jupyterlab.galata import configure_jupyter_server
+
+configure_jupyter_server(c)
+
+# Uncomment to set server log level to debug level
+# c.ServerApp.log_level = "DEBUG"
diff --git a/ui-tests/package.json b/ui-tests/package.json
new file mode 100644
index 00000000..2e32aa75
--- /dev/null
+++ b/ui-tests/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "dataproc_plugin-ui-tests",
+  "version": "1.0.0",
+  "description": "JupyterLab dataproc_plugin Integration Tests",
+  "private": true,
+  "scripts": {
+    "start": "jupyter lab --config jupyter_server_test_config.py",
+    "test": "jlpm playwright test",
+    "test:update": "jlpm playwright test --update-snapshots"
+  },
+  "devDependencies": {
+    "@jupyterlab/galata": "^5.0.0",
+    "@playwright/test": "^1.32.0"
+  }
+}
diff --git a/ui-tests/playwright.config.js b/ui-tests/playwright.config.js
new file mode 100644
index 00000000..c3a3109d
--- /dev/null
+++ b/ui-tests/playwright.config.js
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Configuration for Playwright using default from @jupyterlab/galata
+ */
+const baseConfig = require('@jupyterlab/galata/lib/playwright-config');
+
+module.exports = {
+  ...baseConfig,
+  webServer: {
+    command: 'jlpm start',
+    url: 'http://localhost:8888/lab',
+    timeout: 120 * 1000,
+    reuseExistingServer: !process.env.CI
+  }
+};
diff --git a/ui-tests/tests/dataproc_plugin.spec.ts b/ui-tests/tests/dataproc_plugin.spec.ts
new file mode 100644
index 00000000..6c350c26
--- /dev/null
+++ b/ui-tests/tests/dataproc_plugin.spec.ts
@@ -0,0 +1,38 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ *
+ * Licensed 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 { expect, test } from '@jupyterlab/galata';
+
+/**
+ * Don't load JupyterLab webpage before running the tests.
+ * This is required to ensure we capture all log messages.
+ */
+test.use({ autoGoto: false });
+
+test('should emit an activation console message', async ({ page }) => {
+  const logs: string[] = [];
+
+  page.on('console', message => {
+    logs.push(message.text());
+  });
+
+  await page.goto();
+
+  expect(
+    logs.filter(s => s === 'JupyterLab extension dataproc_plugin is activated!')
+  ).toHaveLength(1);
+});
diff --git a/ui-tests/yarn.lock b/ui-tests/yarn.lock
new file mode 100644
index 00000000..e69de29b