diff --git a/.gitignore b/.gitignore
index d45de5ca..8719d1d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 .DS_Store
 .idea/
+.env
 
 engine/bin/
 /db-lab-run/
@@ -13,3 +14,5 @@ engine/bin/
 /engine/configs/ci_checker.yml
 
 engine/meta
+
+ui/packages/shared/dist/
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..a4267581
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,23 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Build/Test/Lint Commands
+- Build all components: `cd engine && make build`
+- Lint code: `cd engine && make lint`
+- Run unit tests: `cd engine && make test`
+- Run integration tests: `cd engine && make test-ci-integration`
+- Run a specific test: `cd engine && GO111MODULE=on go test -v ./path/to/package -run TestName`
+- Run UI: `cd ui && pnpm start:ce` (Community Edition) or `pnpm start:platform`
+
+## Code Style Guidelines
+- Go code follows "Effective Go" and "Go Code Review Comments" guidelines
+- Use present tense and imperative mood in commit messages
+- Limit first commit line to 72 characters
+- All Git commits must be signed
+- Format Go code with `cd engine && make fmt`
+- Use error handling with pkg/errors
+- Follow standard Go import ordering
+- Group similar functions together
+- Error messages should be descriptive and actionable
+- UI uses pnpm for package management
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f32b4abf..4d399f35 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,11 +23,11 @@ These are mostly guidelines, not rules. Use your best judgment, and feel free to
     - [Git commit messages](#git-commit-messages)
     - [Go styleguide](#go-styleguide)
     - [Documentation styleguide](#documentation-styleguide)
+    - [API design and testing](#api-design-and-testing)
+    - [UI development](#ui-development)
     - [Development setup](#development-setup)
     - [Repo overview](#repo-overview)
-<!--
     - [Building from source](#building-from-source)
--->
 
 ---
 
@@ -121,6 +121,45 @@ We encourage you to follow the principles described in the following documents:
 - [Effective Go](https://go.dev/doc/effective_go)
 - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
 
+### Message style guide
+Consistent messaging is important throughout the codebase. Follow these guidelines for errors, logs, and user-facing messages:
+
+#### Error messages
+- Lowercase for internal errors and logs: `failed to start session` (no ending period)
+- Uppercase for user-facing errors: `Requested object does not exist. Specify your request.` (with ending period)
+- Omit articles ("a", "an", "the") for brevity: use `failed to update clone` not `failed to update the clone`
+- Be specific and actionable whenever possible
+- For variable interpolation, use consistent formatting: `failed to find clone: %s`
+
+#### CLI output
+- Use concise, action-oriented language
+- Present tense with ellipsis for in-progress actions: `Creating clone...` 
+  - Ellipsis (`...`) indicates an ongoing process where the user should wait
+  - Always follow up with a completion message when the operation finishes
+- Past tense with period for results: `Clone created successfully.`
+- Include relevant identifiers (IDs, names) in output
+
+#### Progress indication
+- Use ellipsis (`...`) to indicate that an operation is in progress and the user should wait
+- For longer operations, consider providing percentage or step indicators: `Cloning database... (25%)`
+- When an operation with ellipsis completes, always provide a completion message without ellipsis
+- Example sequence:
+  ```
+  Creating clone...
+  Clone "test-clone" created successfully.
+  ```
+
+#### UI messages
+- Be consistent with terminology across UI and documentation
+- For confirmations, use format: `{Resource} {action} successfully.`
+- For errors, provide clear next steps when possible
+- Use sentence case for all messages (capitalize first word only)
+
+#### Commit messages
+- Start with lowercase type prefix: `fix:`, `feat:`, `docs:`, etc.
+- Use imperative mood: `add feature` not `added feature`
+- Provide context in the body if needed
+
 ### Documentation styleguide
 Documentation for Database Lab Engine and additional components is hosted at https://postgres.ai/docs and is maintained in this GitLab repo: https://gitlab.com/postgres-ai/docs.
 
@@ -132,6 +171,94 @@ We're building documentation following the principles described at https://docum
 
 Learn more: https://documentation.divio.com/.
 
+### API design and testing
+The DBLab API follows RESTful principles with these key guidelines:
+- Clear resource-based URL structure
+- Consistent usage of HTTP methods (GET, POST, DELETE, etc.)
+- Standardized error responses
+- Authentication via API tokens
+- JSON for request and response bodies
+- Comprehensive documentation with examples
+
+#### API Documentation
+We use readme.io to host the API docs: https://dblab.readme.io/ and https://api.dblab.dev.
+
+When updating the API specification:
+1. Make changes to the OpenAPI spec file in `engine/api/swagger-spec/`
+2. Upload it to readme.io as a new documentation version
+3. Review and publish the new version
+
+#### Testing with Postman and Newman
+Postman collection is generated based on the OpenAPI spec file, using [Portman](https://github.com/apideck-libraries/portman).
+
+##### Setup and Generation
+1. Install Portman: `npm install -g @apideck/portman`
+2. Generate Postman collection file:
+   ```
+   portman --cliOptionsFile engine/api/postman/portman-cli.json
+   ```
+
+##### Test Structure Best Practices
+- Arrange tests in logical flows (create, read, update, delete)
+- Use environment variables to store and pass data between requests
+- For object creation tests, capture the ID in the response to use in subsequent requests
+- Add validation tests for response status, body structure, and expected values
+- Clean up created resources at the end of test flows
+
+##### CI/CD Integration
+The Postman collection is automatically run in CI/CD pipelines using Newman. For local testing:
+```
+newman run engine/api/postman/dblab_api.postman_collection.json -e engine/api/postman/branching.aws.postgres.ai.postman_environment.json
+```
+
+### UI development
+The Database Lab Engine UI contains two main packages:
+- `@postgres.ai/platform` - Platform version of UI
+- `@postgres.ai/ce` - Community Edition version of UI
+- `@postgres.ai/shared` - Common modules shared between packages
+
+#### Working with UI packages
+At the repository root:
+- `pnpm install` - Install all dependencies
+- `npm run build -ws` - Build all packages
+- `npm run start -w @postgres.ai/platform` - Run Platform UI in dev mode
+- `npm run start -w @postgres.ai/ce` - Run Community Edition UI in dev mode
+
+_Note: Don't use commands for `@postgres.ai/shared` - it's a dependent package that can't be run or built directly_
+
+#### Platform UI Development
+1. Set up environment variables:
+   ```bash
+   cd ui/packages/platform
+   cp .env_example_dev .env
+   ```
+2. Edit `.env` to set:
+   - `REACT_APP_API_URL_PREFIX` to point to dev API server
+   - `REACT_APP_TOKEN_DEBUG` to set your JWT token
+3. Start development server: `pnpm run start`
+
+#### CI pipelines for UI code
+To deploy UI changes, tag the commit with `ui/` prefix and push it:
+```shell
+git tag ui/1.0.12
+git push origin ui/1.0.12
+```
+
+#### Handling Vulnerabilities
+When addressing vulnerabilities in UI packages:
+1. Update the affected package to a newer version if available
+2. For sub-package vulnerabilities, try using [npm-force-resolutions](https://www.npmjs.com/package/npm-force-resolutions)
+3. As a last resort, consider forking the package locally
+
+For code-related issues:
+1. Consider rewriting JavaScript code in TypeScript
+2. Follow recommendations from security analysis tools
+3. Only ignore false positives when absolutely necessary
+
+#### TypeScript Migration
+- `@postgres.ai/shared` and `@postgres.ai/ce` are written in TypeScript
+- `@postgres.ai/platform` is partially written in TypeScript with ongoing migration efforts
+
 ### Repo overview
 The [postgres-ai/database-lab](https://gitlab.com/postgres-ai/database-lab) repo contains 2 components:
 - [Database Lab Engine](https://gitlab.com/postgres-ai/database-lab/-/tree/master/engine)
@@ -140,7 +267,6 @@ The [postgres-ai/database-lab](https://gitlab.com/postgres-ai/database-lab) repo
   - [Database Lab CLI](https://gitlab.com/postgres-ai/database-lab/-/tree/master/engine/cmd/cli)
 - [Database Lab UI](https://gitlab.com/postgres-ai/database-lab/-/tree/master/ui)
   - [Community Edition](https://gitlab.com/postgres-ai/database-lab/-/tree/master/ui/packages/ce)
-  - [Platform](https://gitlab.com/postgres-ai/database-lab/-/tree/master/ui/packages/platform)
   - [Shared components](https://gitlab.com/postgres-ai/database-lab/-/tree/master/ui/packages/shared)
 
 Components have a separate version, denoted by either:
@@ -191,10 +317,27 @@ Components have a separate version, denoted by either:
 
 
 ### Building from source
-Use `Makefile` to build Database Lab components from source.
+The Database Lab Engine provides multiple build targets in its `Makefile`:
+
+```bash
+cd engine
+make help      # View all available build targets
+make build     # Build all components (Server, CLI, CI Checker)
+make build-dle # Build Database Lab Engine binary and Docker image
+make test      # Run unit tests
+```
+
+You can also build specific components:
+
+```bash
+# Build the CLI for all supported platforms
+make build-client
+
+# Build the Server in debug mode
+make build-debug
 
-Run `make help` to see all available targets.
+# Build and run DLE locally
+make run-dle
+```
 
-<!--
-mention dev images: See our [GitLab Container Registry](https://gitlab.com/postgres-ai/database-lab/container_registry) to find the images built for development branches.
--->
+See our [GitLab Container Registry](https://gitlab.com/postgres-ai/database-lab/container_registry) to find pre-built images for development branches.
diff --git a/LICENSE b/LICENSE
index cb43d4eb..b0cc8d52 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2023 Postgres.ai https://postgres.ai/
+   Copyright 2023-2025 Postgres AI https://postgres.ai/
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 03f77d9e..9eada025 100644
--- a/README.md
+++ b/README.md
@@ -8,18 +8,18 @@
 <div align="center"><h1 align="center">DBLab Engine</h1></div>
 
 <div align="center">
-  <a href="https://twitter.com/intent/tweet?via=Database_Lab&url=https://github.com/postgres-ai/database-lab-engine/&text=20@PostgreSQL%branching%20–%20DLE%20provides%20blazing-fast%20database%20cloning%20to%20build%20powerful%20development,%20test,%20QA,%20staging%20environments.">
+  <a href="https://twitter.com/intent/tweet?via=Database_Lab&url=https://github.com/postgres-ai/database-lab-engine/&text=PostgreSQL%20branching%20%E2%80%93%20DLE%20provides%20blazing-fast%20database%20cloning%20to%20build%20powerful%20development,%20test,%20QA,%20and%20staging%20environments.">
     <img src="https://img.shields.io/twitter/url/https/github.com/postgres-ai/database-lab-engine.svg?style=for-the-badge" alt="twitter">
   </a>
 </div>
 
 <div align="center">
-  <strong>⚡ Blazing-fast Postgres cloning and branching 🐘</strong><br /><br />
+  <strong>⚡ Blazing-fast PostgreSQL cloning and branching 🐘</strong><br /><br />
   🛠️ Build powerful dev/test environments.<br />
   🔃 Cover 100% of DB migrations with CI tests.<br>
   💡 Quickly verify ChatGPT ideas to get rid of hallucinations.<br /><br />
-  Available for any PostgreSQL, including self-managed and managed<sup>*</sup> like AWS RDS, GCP CloudSQL, Supabase, Timescale.<br /><br />
-  Can be installed and used anywhere: all clouds and on-premises.
+  Available for any PostgreSQL, including self-managed and managed services<sup>*</sup> like AWS RDS, GCP Cloud SQL, Supabase, and Timescale.<br /><br />
+  It can be installed and used anywhere: across all cloud environments and on-premises.
 </div>
 
 <br />
@@ -60,11 +60,11 @@ For example, cloning a 1 TiB PostgreSQL database takes just about 10 seconds. On
 <p><img src="./assets/dle-demo-animated.gif" border="0" /></p>
 
 Try it yourself right now:
-- Visit [Postgres.ai Console](https://console.postgres.ai/), set up your first organization and provision a DBLab Standard Edition (DBLab SE) to any cloud or on-prem
+- Visit [Postgres.ai Console](https://console.postgres.ai/), set up your first organization, and provision a DBLab Standard Edition (DBLab SE) to any cloud or on-premises environment.
     - [Pricing](https://postgres.ai/pricing) (starting at $62/month)
-    - [Doc: How to install DBLab SE](https://postgres.ai/docs/how-to-guides/administration/install-dle-from-postgres-ai)
-- Demo: https://demo.aws.postgres.ai (use the token `demo-token` to access)
-- Looking for a free version? Install DBLab Community Edition by [following this tutorial](https://postgres.ai/docs/tutorials/database-lab-tutorial)
+    - [Documentation: How to install DBLab SE](https://postgres.ai/docs/how-to-guides/administration/install-dle-from-postgres-ai)
+- Demo: https://demo.dblab.dev (use the token `demo-token` to access)
+- Looking for a free version? Install the DBLab Community Edition by [following this tutorial](https://postgres.ai/docs/tutorials/database-lab-tutorial).
 
 ## How it works
 Thin cloning is fast because it is based on [Copy-on-Write (CoW)](https://en.wikipedia.org/wiki/Copy-on-write#In_computer_storage). DBLab employs two technologies for enabling thin cloning: [ZFS](https://en.wikipedia.org/wiki/ZFS) (default) and [LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)).
@@ -88,7 +88,7 @@ Read more:
 
 ## Features
 - Speed & scale
-    - Blazing-fast cloning of Postgres databases – clone in seconds, irrespective of database size
+    - Blazing-fast cloning of PostgreSQL databases – clone in seconds, irrespective of database size
     - Theoretical max of snapshots/clones: 2<sup>64</sup> ([ZFS](https://en.wikipedia.org/wiki/ZFS), default)
     - Maximum size of PostgreSQL data directory: 256 quadrillion zebibytes, or 2<sup>128</sup> bytes ([ZFS](https://en.wikipedia.org/wiki/ZFS), default)
 - Support & technologies
@@ -96,12 +96,12 @@ Read more:
     - Thin cloning ([CoW](https://en.wikipedia.org/wiki/Copy-on-write)) technologies: [ZFS](https://en.wikipedia.org/wiki/ZFS) and [LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux))
     - UI for manual tasks and API & CLI for automation
     - Packaged in Docker containers for all components
-- Postgres containers
+- PostgreSQL containers
     - Popular extensions including contrib modules, pgvector, HypoPG and many others ([docs](https://postgres.ai/docs/database-lab/supported-databases#extensions-included-by-default))
     - Customization capabilities for containers ([docs](https://postgres.ai/docs/database-lab/supported-databases#how-to-add-more-extensions))
-    - Docker container and Postgres config parameters in DBLab config
+    - Docker container and PostgreSQL configuration parameters in the DBLab config
 - Source database requirements
-    - Location flexibility: self-managed Postgres, AWS RDS, GCP CloudSQL, Azure, etc. No source adjustments needed
+    - Location flexibility: self-managed PostgreSQL, AWS RDS, GCP Cloud SQL, Azure, etc.—no source adjustments needed.
     - No ZFS or Docker requirements for source databases
 - Data provisioning & retrieval
     - Physical (pg_basebackup, WAL-G, pgBackRest) and logical (dump/restore) provisioning
@@ -128,8 +128,8 @@ The simplest way to show your support is by giving us a star on GitHub or GitLab
 ![Add a star](./assets/star.gif)
 
 ### Spread the word
-- Shoot out a tweet and mention [@Database_Lab](https://twitter.com/Database_Lab) 
-- Share this repo's link on your favorite social media platform
+- Tweet about DBLab and mention [@Database_Lab](https://twitter.com/Database_Lab).
+- Share a link to this repository on your favorite social media platform.
 
 ### Share your experience
 If DBLab has been a vital tool for you, tell the world about your journey. Use the logo from the `./assets` folder for a visual touch. Whether it's in documents, presentations, applications, or on your website, let everyone know you trust and use DBLab.
@@ -157,10 +157,7 @@ For darker backgrounds:
 ```
 
 ### Propose an idea or report a bug
-Check out our [contributing guide](./CONTRIBUTING.md) for more details.
-
-### Participate in development
-Check out our [contributing guide](./CONTRIBUTING.md) for more details.
+For proposals, bug reports, and participation in development, see our [Contributing Guide](./CONTRIBUTING.md).
 
 
 ### Reference guides
@@ -173,8 +170,11 @@ Check out our [contributing guide](./CONTRIBUTING.md) for more details.
 - [How to install and initialize Database Lab CLI](https://postgres.ai/docs/how-to-guides/cli/cli-install-init)
 - [How to manage DBLab](https://postgres.ai/docs/how-to-guides/administration)
 - [How to work with clones](https://postgres.ai/docs/how-to-guides/cloning)
+- [How to work with branches](XXXXXXX) – TBD
+- [How to integrate DBLab with GitHub Actions](XXXXXXX) – TBD
+- [How to integrate DBLab with GitLab CI/CD](XXXXXXX) – TBD
 
-More you can find in [the "How-to guides" section](https://postgres.ai/docs/how-to-guides) of the docs. 
+You can find more in the ["How-to guides" section](https://postgres.ai/docs/how-to-guides) of the documentation.
 
 ### Miscellaneous
 - [DBLab Docker images](https://hub.docker.com/r/postgresai/dblab-server)
@@ -183,15 +183,15 @@ More you can find in [the "How-to guides" section](https://postgres.ai/docs/how-
 - [DB Migration Checker](https://postgres.ai/docs/db-migration-checker)
 
 ## License
-DBLab source code is licensed under the OSI-approved open source license [Apache 2.0](https://opensource.org/license/apache-2-0/).
+The DBLab source code is licensed under the OSI-approved open source license [Apache 2.0](https://opensource.org/license/apache-2-0/).
 
 Reach out to the Postgres.ai team if you want a trial or commercial license that does not contain the GPL clauses: [Contact page](https://postgres.ai/contact).
 
 ## Community & Support
-- ["Database Lab Engine Community Covenant Code of Conduct"](./CODE_OF_CONDUCT.md)
-- Where to get help: [Contact page](https://postgres.ai/contact)
+- [Database Lab Engine Community Covenant Code of Conduct](./CODE_OF_CONDUCT.md)
+- Where to get help: [Contact page](https://postgres.ai/contact).
 - [Community Slack](https://slack.postgres.ai)
-- If you need to report a security issue, follow instructions in ["Database Lab Engine security guidelines"](./SECURITY.md)
+- If you need to report a security issue, follow the instructions in [Database Lab Engine Security Guidelines](./SECURITY.md).
 
 [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?color=blue)](./CODE_OF_CONDUCT.md)
 
diff --git a/engine/.gitlab-ci.yml b/engine/.gitlab-ci.yml
index 7b8f074f..a048e132 100644
--- a/engine/.gitlab-ci.yml
+++ b/engine/.gitlab-ci.yml
@@ -1,5 +1,7 @@
 default:
-  image: golang:1.23
+  image:
+    name: golang:1.23
+    pull_policy: if-not-present
 
 stages:
   - test
@@ -56,7 +58,9 @@ lint:
 ### Build binary.
 build-binary-alpine:
   <<: *only_engine
-  image: golang:1.23-alpine
+  image:
+    name: golang:1.23-alpine
+    pull_policy: if-not-present
   stage: build-binary
   artifacts:
     paths:
@@ -85,7 +89,7 @@ build-binary-client-master:
 
     # Install google-cloud-sdk.
     - echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
-    - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor | tee /usr/share/keyrings/cloud.google.gpg > /dev/null
+    - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
     - apt-get update && apt-get install -y google-cloud-sdk
 
     # Authenticate.
@@ -105,7 +109,7 @@ build-binary-client:
 
     # Install google-cloud-sdk.
     - echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
-    - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor | tee /usr/share/keyrings/cloud.google.gpg > /dev/null
+    - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
     - apt-get update && apt-get install -y google-cloud-sdk
 
     # Authenticate.
@@ -126,7 +130,7 @@ build-binary-client-rc:
 
     # Install google-cloud-sdk.
     - echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
-    - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor | tee /usr/share/keyrings/cloud.google.gpg > /dev/null
+    - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
     - apt-get update && apt-get install -y google-cloud-sdk
 
     # Authenticate.
@@ -136,13 +140,18 @@ build-binary-client-rc:
     - gsutil -m cp -r bin/cli/* gs://database-lab-cli/${CLEAN_TAG}/
 
 .job_template: &build_image_definition
-  image: docker:20
+  image:
+    name: docker:24
+    pull_policy: if-not-present
   stage: build
   artifacts:
     paths:
       - engine/bin
   services:
-    - name: docker:dind
+    - name: docker:24-dind
+      alias: docker
+      command: [ "--tls=false" ]
+      pull_policy: if-not-present    
   script:
     - cd engine
     - apk update && apk upgrade && apk add --no-cache bash # TODO(anatoly): Remove dependency.
@@ -203,7 +212,7 @@ build-image-master-server:
 build-image-master-server-zfs08:
   <<: *build_image_definition
   <<: *only_master
-  variables:
+  variables:    
     DOCKER_FILE: "Dockerfile.dblab-server-zfs08"
     DOCKER_NAME: "registry.gitlab.com/postgres-ai/database-lab/dblab-server"
     TAGS: "${DOCKER_NAME}:master-zfs0.8,${DOCKER_NAME}:master-${CI_COMMIT_SHORT_SHA}-zfs0.8"
@@ -219,7 +228,7 @@ build-image-master-ci-checker:
 build-image-master-client:
   <<: *build_image_definition
   <<: *only_master
-  variables:
+  variables:    
     DOCKER_FILE: "Dockerfile.dblab-cli"
     DOCKER_NAME: "registry.gitlab.com/postgres-ai/database-lab/dblab-cli"
     TAGS: "${DOCKER_NAME}:master,${DOCKER_NAME}:master-${CI_COMMIT_SHORT_SHA}"
@@ -237,7 +246,6 @@ build-image-latest-server:
     - export CLEAN_TAG=$(echo ${CI_COMMIT_TAG#"v"})
     - export LATEST_TAG=$(echo ${CLEAN_TAG%.*}-latest)
     - export TAGS="${DOCKER_NAME}:${LATEST_TAG},${DOCKER_NAME}:${CLEAN_TAG}"
-
 build-image-latest-server-zfs08:
   <<: *build_image_definition
   <<: *only_tag_release
@@ -331,7 +339,6 @@ build-image-rc-server-zfs08:
     REGISTRY: "${DH_CI_REGISTRY}"
     DOCKER_FILE: "Dockerfile.dblab-server-zfs08"
     DOCKER_NAME: "postgresai/dblab-server"
-
 build-image-rc-server-dev:
   <<: *build_image_definition
   <<: *only_tag_rc
@@ -344,7 +351,6 @@ build-image-rc-server-dev:
     REGISTRY: "${CI_REGISTRY}"
     DOCKER_FILE: "Dockerfile.dblab-server"
     DOCKER_NAME: "registry.gitlab.com/postgres-ai/database-lab/dblab-server"
-
 build-image-rc-server-dev-zfs08:
   <<: *build_image_definition
   <<: *only_tag_rc
@@ -357,7 +363,6 @@ build-image-rc-server-dev-zfs08:
     REGISTRY: "${CI_REGISTRY}"
     DOCKER_FILE: "Dockerfile.dblab-server-zfs08"
     DOCKER_NAME: "registry.gitlab.com/postgres-ai/database-lab/dblab-server"
-
 build-image-rc-ci-checker:
   <<: *build_image_definition
   <<: *only_tag_rc
@@ -370,7 +375,6 @@ build-image-rc-ci-checker:
     REGISTRY: "${DH_CI_REGISTRY}"
     DOCKER_FILE: "Dockerfile.ci-checker"
     DOCKER_NAME: "postgresai/dblab-ci-checker"
-
 build-image-rc-ci-checker-dev:
   <<: *build_image_definition
   <<: *only_tag_rc
@@ -383,7 +387,6 @@ build-image-rc-ci-checker-dev:
     REGISTRY: "${CI_REGISTRY}"
     DOCKER_FILE: "Dockerfile.ci-checker"
     DOCKER_NAME: "registry.gitlab.com/postgres-ai/database-lab/dblab-ci-checker"
-
 build-image-rc-client:
   <<: *build_image_definition
   <<: *only_tag_rc
@@ -396,7 +399,6 @@ build-image-rc-client:
     REGISTRY: "${DH_CI_REGISTRY}"
     DOCKER_FILE: "Dockerfile.dblab-cli"
     DOCKER_NAME: "postgresai/dblab"
-
 build-image-swagger-release:
   <<: *build_image_definition
   <<: *only_tag_release
@@ -420,6 +422,8 @@ build-image-swagger-release:
   artifacts:
     paths:
       - engine/bin
+  before_script:
+    - bash engine/test/_cleanup.sh
   script:
     - bash engine/test/1.synthetic.sh
     - bash engine/test/2.logical_generic.sh
@@ -476,13 +480,15 @@ bash-test-17:
 
 integration-test:
   services:
-    - name: docker:dind
+    - name: docker:24-dind
+      alias: docker
       command: [ "--tls=false" ]
+      pull_policy: if-not-present
   <<: *only_feature
   stage: integration-test
   variables:
     # Instruct Testcontainers to use the daemon of DinD.
-    DOCKER_HOST: "tcp://docker:2375"
+    # DOCKER_HOST: "tcp://docker:2375"
     # Instruct Docker not to start over TLS.
     DOCKER_TLS_CERTDIR: ""
     # Improve performance with overlayfs.
@@ -496,7 +502,9 @@ integration-test:
 ## Deploy
 .deploy-definition: &deploy_definition
   stage: deploy
-  image: dtzar/helm-kubectl:2.14.1
+  image:
+    name: dtzar/helm-kubectl:2.14.1
+    pull_policy: if-not-present
   script:
     - bash ./engine/scripts/do.sh subs_envs ./engine/deploy/swagger-ui.yaml /tmp/swagger-ui.yaml
     - kubectl apply --filename /tmp/swagger-ui.yaml -n $NAMESPACE
diff --git a/engine/.golangci.yml b/engine/.golangci.yml
index c8a38ec1..bad31644 100644
--- a/engine/.golangci.yml
+++ b/engine/.golangci.yml
@@ -91,7 +91,6 @@ linters:
     - depguard
     - gosec
     - gocyclo # currently unmaintained
-  #presets:
   fast: false
 
 issues:
diff --git a/engine/api/README.md b/engine/api/README.md
new file mode 100644
index 00000000..37e228aa
--- /dev/null
+++ b/engine/api/README.md
@@ -0,0 +1,24 @@
+# Database Lab Engine API
+
+## Directory Contents
+- `swagger-spec` – OpenAPI 3.0 specification of DBLab API
+- `swagger-ui` – Swagger UI to see the API specification (embedded in DBLab, available at :2345 or :2346/api)
+- `postman` – [Postman](https://www.postman.com/) collection and environment files used to test the API in CI/CD pipelines via [`newman`](https://github.com/postmanlabs/newman)
+
+## Design principles
+Work in progress: https://gitlab.com/postgres-ai/database-lab/-/merge_requests/744
+
+## API docs
+We use ReadMe.io to host the API documentation: https://dblab.readme.io/. Once a new API spec is ready, upload it as a new documentation version and publish.
+
+## Postman, newman, and CI/CD tests
+The Postman collection is generated from the OpenAPI spec file using [Portman](https://github.com/apideck-libraries/portman).
+1. Install and initialize `portman`.
+1. Generate a new version of the Postman collection:
+    ```
+    portman --cliOptionsFile engine/api/postman/portman-cli.json
+    ```
+1. Review and adjust the collection:
+    - Ensure object creation occurs before its deletion and pass the new object's ID between requests (TODO: provide example).
+    - Review and update tests as needed (TODO: details).
+1. Commit, push, and ensure Newman's CI/CD testing passes.
\ No newline at end of file
diff --git a/engine/api/postman/branching.aws.postgres.ai.postman_environment.json b/engine/api/postman/branching.aws.postgres.ai.postman_environment.json
new file mode 100644
index 00000000..407d3d88
--- /dev/null
+++ b/engine/api/postman/branching.aws.postgres.ai.postman_environment.json
@@ -0,0 +1,21 @@
+{
+	"id": "30035c51-5e48-4d31-8676-2aac8af456ee",
+	"name": "branching.aws.postgres.ai",
+	"values": [
+		{
+			"key": "baseUrl",
+			"value": "https://branching.aws.postgres.ai:446/api",
+			"type": "default",
+			"enabled": true
+		},
+		{
+			"key": "verificationToken",
+			"value": "demo-token",
+			"type": "default",
+			"enabled": true
+		}
+	],
+	"_postman_variable_scope": "environment",
+	"_postman_exported_at": "2023-05-18T04:01:37.154Z",
+	"_postman_exported_using": "Postman/10.14.2-230517-0637"
+}
\ No newline at end of file
diff --git a/engine/api/postman/dblab.postman_collection.json b/engine/api/postman/dblab.postman_collection.json
deleted file mode 100644
index 2c57013d..00000000
--- a/engine/api/postman/dblab.postman_collection.json
+++ /dev/null
@@ -1,431 +0,0 @@
-{
-	"variables": [],
-	"info": {
-		"name": "Database Lab",
-		"_postman_id": "d0182a6c-79d0-877f-df91-18dbca63b734",
-		"description": "",
-		"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
-	},
-	"item": [
-		{
-			"name": "status",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check instance status\"] = responseCode.code === 200 && jsonData && jsonData.status && jsonData.status.code && jsonData.status.code === \"OK\";"
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/status",
-				"method": "GET",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": "",
-						"disabled": true
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"dblab_id\": 1\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "snapshots",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check snapshots list\"] = responseCode.code === 200 && jsonData && Array.isArray(jsonData) && jsonData.length === 1;",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/snapshots",
-				"method": "GET",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"dblab_id\": 1\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "clone not found",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check for clone status\"] =  responseCode.code === 404 && jsonData && jsonData.detail && jsonData.detail === \"Requested object does not exist.\";",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/bopta26mq8oddsim86v0",
-				"method": "GET",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"dblab_id\": 1\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "create clone",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check for clone create\"] =  responseCode.code === 201 && jsonData && jsonData.id && jsonData.status && ",
-							"(jsonData.status.code == 'OK' ||  jsonData.status.code == 'CREATING');",
-							"postman.setGlobalVariable(\"DBLAB_CLONE_ID\", jsonData.id);"
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone",
-				"method": "POST",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\r\n\t\"name\": \"test-demo-clone\",\r\n\t\"protected\": false,\r\n\t\"db\": {\r\n\t\t\"username\": \"username\",\r\n\t\t\"password\": \"password\"\r\n\t}\r\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "clone status",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check for clone status\"] =  responseCode.code === 200 && jsonData && jsonData.id && jsonData.status && ",
-							"(jsonData.status.code == 'OK' ||  jsonData.status.code == 'CREATING');",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}",
-				"method": "GET",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"dblab_id\": 1\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "clone update (name, protected)",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"tests[\"Check for clone update\"] = responseCode.code === 200;",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}",
-				"method": "PATCH",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": "",
-						"disabled": true
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"protected\": true,\n\t\"name\": \"UPDATE_CLONE_TEST\"\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "clone/reset",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"tests[\"Check for clone reset\"] = responseCode.code === 200;",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}/reset",
-				"method": "POST",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": "",
-						"disabled": true
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"id\": \"xxx\"\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "delete protected clone",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check for delete protected clone\"] = responseCode.code === 500 && jsonData && jsonData.detail && jsonData.detail === \"clone is protected\";",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}",
-				"method": "DELETE",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": ""
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "clone update (disable protection)",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"tests[\"Check for clone update\"] = responseCode.code === 200;",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}",
-				"method": "PATCH",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": "",
-						"disabled": true
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"protected\": false\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "delete clone",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"tests[\"Check for delete protected clone\"] = responseCode.code === 200;",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}",
-				"method": "DELETE",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": ""
-				},
-				"description": "Select users"
-			},
-			"response": []
-		},
-		{
-			"name": "removed clone status",
-			"event": [
-				{
-					"listen": "test",
-					"script": {
-						"type": "text/javascript",
-						"exec": [
-							"var jsonData = JSON.parse(responseBody);",
-							"tests[\"Check for clone status\"] =  (responseCode.code === 200 && jsonData && jsonData.id && jsonData.status && ",
-							"jsonData.status.code == 'DELETING') || responseCode.code == 404;",
-							""
-						]
-					}
-				}
-			],
-			"request": {
-				"url": "{{DBLAB_URL}}/clone/{{DBLAB_CLONE_ID}}",
-				"method": "GET",
-				"header": [
-					{
-						"key": "Verification-Token",
-						"value": "{{DBLAB_VERIFY_TOKEN}}",
-						"description": ""
-					},
-					{
-						"key": "Content-Type",
-						"value": "application/json",
-						"description": ""
-					}
-				],
-				"body": {
-					"mode": "raw",
-					"raw": "{\n\t\"dblab_id\": 1\n}"
-				},
-				"description": "Select users"
-			},
-			"response": []
-		}
-	]
-}
diff --git a/engine/api/postman/dblab.postman_environment.json b/engine/api/postman/dblab.postman_environment.json
deleted file mode 100644
index 5f7244c9..00000000
--- a/engine/api/postman/dblab.postman_environment.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "id": "ff4200f0-7acd-eb4f-1dee-59da8c98c313",
-  "name": "Database Lab",
-  "values": [
-    {
-      "enabled": true,
-      "key": "DBLAB_URL",
-      "value": "https://url",
-      "type": "text"
-    },
-    {
-      "enabled": true,
-      "key": "DBLAB_VERIFY_TOKEN",
-      "value": "secret_token",
-      "type": "text"
-    }
-  ],
-  "timestamp": 1580454458304,
-  "_postman_variable_scope": "environment",
-  "_postman_exported_at": "2020-01-31T09:42:37.377Z",
-  "_postman_exported_using": "Postman/5.5.4"
-}
diff --git a/engine/api/postman/dblab_api.postman_collection.json b/engine/api/postman/dblab_api.postman_collection.json
new file mode 100644
index 00000000..7995382f
--- /dev/null
+++ b/engine/api/postman/dblab_api.postman_collection.json
@@ -0,0 +1,4057 @@
+{
+	"info": {
+		"_postman_id": "ed8af9f0-1cde-4633-8a57-a47e10d12bfa",
+		"name": "DBLab API 4.0.0-beta.2",
+		"description": "This page provides the OpenAPI specification for the Database Lab (DBLab) API, previously recognized as the DLE API (Database Lab Engine API).\n\nContact Support:\n Name: DBLab API Support\n Email: api@postgres.ai",
+		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+		"_exporter_id": "34026417"
+	},
+	"item": [
+		{
+			"name": "Instance",
+			"item": [
+				{
+					"name": "DBLab instance status and detailed information",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/status - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/status - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[GET]::/status - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\",\"properties\":{\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"engine\":{\"type\":\"object\",\"properties\":{\"version\":{\"type\":\"string\"},\"edition\":{\"type\":\"string\"},\"billingActive\":{\"type\":\"string\"},\"instanceID\":{\"type\":\"string\"},\"startedAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"telemetry\":{\"type\":\"boolean\"},\"disableConfigModification\":{\"type\":\"boolean\"}}},\"pools\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"mode\":{\"type\":\"string\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"status\":{\"type\":\"string\"},\"cloneList\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"fileSystem\":{\"type\":\"object\",\"properties\":{\"mode\":{\"type\":\"string\"},\"free\":{\"type\":\"integer\",\"format\":\"int64\"},\"size\":{\"type\":\"integer\",\"format\":\"int64\"},\"used\":{\"type\":\"integer\",\"format\":\"int64\"},\"dataSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"usedBySnapshots\":{\"type\":\"integer\",\"format\":\"int64\"},\"usedByClones\":{\"type\":\"integer\",\"format\":\"int64\"},\"compressRatio\":{\"type\":\"integer\",\"format\":\"float64\"}}}}}},\"cloning\":{\"type\":\"object\",\"properties\":{\"expectedCloningTime\":{\"type\":\"integer\",\"format\":\"float64\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int64\"},\"clones\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"snapshot\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"physicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"pool\":{\"type\":\"string\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int\"}}},\"protected\":{\"type\":\"boolean\",\"default\":false},\"deleteAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"db\":{\"type\":\"object\",\"properties\":{\"connStr\":{\"type\":\"string\"},\"host\":{\"type\":\"string\"},\"port\":{\"type\":\"string\"},\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"}}},\"metadata\":{\"type\":\"object\",\"properties\":{\"cloneDiffSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"cloningTime\":{\"type\":\"integer\",\"format\":\"float64\"},\"maxIdleMinutes\":{\"type\":\"integer\",\"format\":\"int64\"}}}}}}}},\"retrieving\":{\"type\":\"object\",\"properties\":{\"mode\":{\"type\":\"string\"},\"status\":{\"type\":\"string\"},\"lastRefresh\":{\"type\":\"string\",\"format\":\"date-time\"},\"nextRefresh\":{\"type\":\"string\",\"format\":\"date-time\"},\"alerts\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"activity\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"user\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"waitEventType\":{\"type\":\"string\"},\"waitEvent\":{\"type\":\"string\"}}}},\"target\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"user\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"waitEventType\":{\"type\":\"string\"},\"waitEvent\":{\"type\":\"string\"}}}}}}}},\"provisioner\":{\"type\":\"object\",\"properties\":{\"dockerImage\":{\"type\":\"string\"},\"containerConfig\":{\"type\":\"object\",\"properties\":{}}}},\"synchronization\":{\"type\":\"object\",\"properties\":{\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"startedAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"lastReplayedLsn\":{\"type\":\"string\"},\"lastReplayedLsnAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"replicationLag\":{\"type\":\"string\"},\"replicationUptime\":{\"type\":\"integer\"}}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[GET]::/status - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/status",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"status"
+							]
+						},
+						"description": "Retrieves detailed information about the DBLab instance: status, version, clones, snapshots, etc."
+					},
+					"response": [
+						{
+							"name": "Returned detailed information about the DBLab instance",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/status",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"status"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"status\": {\n    \"code\": \"OK\",\n    \"message\": \"Instance is ready\"\n  },\n  \"engine\": {\n    \"version\": \"v4.0.0-alpha.5-20230516-0224\",\n    \"edition\": \"standard\",\n    \"billingActive\": true,\n    \"instanceID\": \"chhfqfcnvrvc73d0lij0\",\n    \"startedAt\": \"2023-05-16T03:50:19Z\",\n    \"telemetry\": true,\n    \"disableConfigModification\": false\n  },\n  \"pools\": [\n    {\n      \"name\": \"dblab_pool/dataset_1\",\n      \"mode\": \"zfs\",\n      \"dataStateAt\": \"\",\n      \"status\": \"empty\",\n      \"cloneList\": [],\n      \"fileSystem\": {\n        \"mode\": \"zfs\",\n        \"size\": 30685528064,\n        \"free\": 30685282816,\n        \"used\": 245248,\n        \"dataSize\": 12288,\n        \"usedBySnapshots\": 0,\n        \"usedByClones\": 219648,\n        \"compressRatio\": 1\n      }\n    },\n    {\n      \"name\": \"dblab_pool/dataset_2\",\n      \"mode\": \"zfs\",\n      \"dataStateAt\": \"\",\n      \"status\": \"empty\",\n      \"cloneList\": [],\n      \"fileSystem\": {\n        \"mode\": \"zfs\",\n        \"size\": 30685528064,\n        \"free\": 30685282816,\n        \"used\": 245248,\n        \"dataSize\": 12288,\n        \"usedBySnapshots\": 0,\n        \"usedByClones\": 219648,\n        \"compressRatio\": 1\n      }\n    },\n    {\n      \"name\": \"dblab_pool/dataset_3\",\n      \"mode\": \"zfs\",\n      \"dataStateAt\": \"\",\n      \"status\": \"empty\",\n      \"cloneList\": [],\n      \"fileSystem\": {\n        \"mode\": \"zfs\",\n        \"size\": 30685528064,\n        \"free\": 30685282816,\n        \"used\": 245248,\n        \"dataSize\": 12288,\n        \"usedBySnapshots\": 0,\n        \"usedByClones\": 219648,\n        \"compressRatio\": 1\n      }\n    }\n  ],\n  \"cloning\": {\n    \"expectedCloningTime\": 0,\n    \"numClones\": 0,\n    \"clones\": []\n  },\n  \"retrieving\": {\n    \"mode\": \"logical\",\n    \"status\": \"pending\",\n    \"lastRefresh\": null,\n    \"nextRefresh\": null,\n    \"alerts\": {},\n    \"activity\": null\n  },\n  \"provisioner\": {\n    \"dockerImage\": \"postgresai/extended-postgres:15\",\n    \"containerConfig\": {\n      \"shm-size\": \"1gb\"\n    }\n  },\n  \"synchronization\": {\n    \"status\": {\n      \"code\": \"Not available\",\n      \"message\": \"\"\n    },\n    \"lastReplayedLsn\": \"\",\n    \"lastReplayedLsnAt\": \"\",\n    \"replicationLag\": 0,\n    \"replicationUptime\": 0\n  }\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/status",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"status"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Data refresh status",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/instance/retrieval - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/instance/retrieval - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[GET]::/instance/retrieval - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\",\"properties\":{\"mode\":{\"type\":\"string\"},\"status\":{\"type\":\"string\"},\"lastRefresh\":{\"type\":\"string\",\"format\":\"date-time\"},\"nextRefresh\":{\"type\":\"string\",\"format\":\"date-time\"},\"alerts\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"activity\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"user\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"waitEventType\":{\"type\":\"string\"},\"waitEvent\":{\"type\":\"string\"}}}},\"target\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"user\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"waitEventType\":{\"type\":\"string\"},\"waitEvent\":{\"type\":\"string\"}}}}}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[GET]::/instance/retrieval - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/instance/retrieval",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"instance",
+								"retrieval"
+							]
+						},
+						"description": "Report a status of the data refresh subsystem (also known as \"data retrieval\"): timestamps of the previous and next refresh runs, status, messages."
+					},
+					"response": [
+						{
+							"name": "Reported a status of the data retrieval subsystem",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/instance/retrieval",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"instance",
+										"retrieval"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"mode\": \"logical\",\n  \"status\": \"pending\",\n  \"lastRefresh\": null,\n  \"nextRefresh\": null,\n  \"alerts\": {},\n  \"activity\": null\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/instance/retrieval",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"instance",
+										"retrieval"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Service health check",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"type": "text/javascript",
+								"exec": [
+									"// Validate status 2xx \npm.test(\"[GET]::/healthz - Status code is 2xx\", function () {\n   pm.response.to.be.success;\n});\n",
+									"// Validate if response header has matching content-type\npm.test(\"[GET]::/healthz - Content-Type is application/json\", function () {\n   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");\n});\n",
+									"// Validate if response has JSON Body \npm.test(\"[GET]::/healthz - Response has JSON Body\", function () {\n    pm.response.to.have.jsonBody();\n});\n",
+									"// Response Validation\nconst schema = {\"type\":\"object\",\"properties\":{\"version\":{\"type\":\"string\"},\"edition\":{\"type\":\"string\"},\"billingActive\":{\"type\":\"string\"},\"instanceID\":{\"type\":\"string\"},\"startedAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"telemetry\":{\"type\":\"boolean\"},\"disableConfigModification\":{\"type\":\"boolean\"}}}\n\n// Validate if response matches JSON schema \npm.test(\"[GET]::/healthz - Schema is valid\", function() {\n    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});\n});\n"
+								]
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/healthz",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"healthz"
+							]
+						},
+						"description": "Check the overall health and availability of the API system. This endpoint does not require the 'Verification-Token' header."
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/healthz",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"healthz"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"version\": \"v4.0.0-alpha.5-20230516-0224\",\n  \"edition\": \"standard\",\n  \"instanceID\": \"chhfqfcnvrvc73d0lij0\"\n}"
+						}
+					]
+				}
+			]
+		},
+		{
+			"name": "Snapshots",
+			"item": [
+				{
+					"name": "List all snapshots",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/snapshots - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/snapshots - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[GET]::/snapshots - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"physicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"pool\":{\"type\":\"string\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int\"}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[GET]::/snapshots - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/snapshots",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"snapshots"
+							]
+						},
+						"description": "Return a list of all available snapshots."
+					},
+					"response": [
+						{
+							"name": "Returned a list of snapshots",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/snapshots",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"snapshots"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "[\n  {\n    \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\",\n    \"createdAt\": \"2023-05-09T21:27:11Z\",\n    \"dataStateAt\": \"2023-05-09T21:27:11Z\",\n    \"physicalSize\": 0,\n    \"logicalSize\": 11518021632,\n    \"pool\": \"dblab_pool/dataset_2\",\n    \"numClones\": 1\n  },\n  {\n    \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230307171959@20230307171959\",\n    \"createdAt\": \"2023-03-07T17:19:59Z\",\n    \"dataStateAt\": \"2023-03-07T17:19:59Z\",\n    \"physicalSize\": 151552,\n    \"logicalSize\": 11518015488,\n    \"pool\": \"dblab_pool/dataset_2\",\n    \"numClones\": 1\n  }\n]"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/snapshots",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"snapshots"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Create a snapshot",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/branch/snapshot - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/branch/snapshot - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript",
+								"packages": {}
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "*/*"
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"cloneID\": \"test3\",\n  \"message\": \"do\"\n}"
+						},
+						"url": {
+							"raw": "{{baseUrl}}/branch/snapshot",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"branch",
+								"snapshot"
+							]
+						},
+						"description": "Create a new snapshot using the specified clone. After a snapshot has been created, the original clone can be deleted in order to free up compute resources, if necessary.  The snapshot created by this endpoint can be used later to create one or more new clones."
+					},
+					"response": [
+						{
+							"name": "OK",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"cloneID\": \"aliquip sit nisi\",\n  \"message\": \"do\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/snapshot",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"snapshot"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"snapshotID\": \"voluptate\"\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"cloneID\": \"aliquip sit nisi\",\n  \"message\": \"do\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/snapshot",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"snapshot"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Retrieve a snapshot",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/branch/snapshot/:id - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/branch/snapshot/:id - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript",
+								"packages": {}
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/branch/snapshot/:id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"branch",
+								"snapshot",
+								":id"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": ""
+								}
+							]
+						},
+						"description": "Retrieves the information for the specified snapshot."
+					},
+					"response": [
+						{
+							"name": "OK",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/branch/snapshot/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"snapshot",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) ID of the branch snapshot"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"id\": \"nostrud exercitation id velit\",\n  \"parent\": \"exercitation sunt do anim\",\n  \"child\": \"cillum incididunt voluptate veniam\",\n  \"branch\": [\n    \"cillum\",\n    \"Excepteur ut ut occaecat eu\"\n  ],\n  \"root\": \"mollit culpa enim nostrud\",\n  \"dataStateAt\": \"2008-01-19T00:42:22.510Z\",\n  \"message\": \"irure qui \"\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/branch/snapshot/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"snapshot",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) ID of the branch snapshot"
+										}
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Delete a snapshot",
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) ",
+								"type": "text"
+							},
+							{
+								"key": "Accept",
+								"value": "*/*",
+								"type": "text"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/snapshot/:id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"snapshot",
+								":id"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": "lorem"
+								}
+							]
+						}
+					},
+					"response": [
+						{
+							"name": "OK",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "{{verificationToken}}",
+										"type": "text"
+									},
+									{
+										"key": "Accept",
+										"value": "*/*",
+										"type": "text"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/snapshot/dblab_pool/dataset_3@snapshot_20250324084404",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"snapshot",
+										"dblab_pool",
+										"dataset_3@snapshot_20250324084404"
+									]
+								}
+							},
+							"_postman_previewlanguage": null,
+							"header": null,
+							"cookie": [],
+							"body": null
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "{{verificationToken}}",
+										"type": "text"
+									},
+									{
+										"key": "Accept",
+										"value": "*/*",
+										"type": "text"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/snapshot/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"snapshot",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": ""
+										}
+									]
+								}
+							},
+							"_postman_previewlanguage": null,
+							"header": null,
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\"\n}"
+						}
+					]
+				}
+			]
+		},
+		{
+			"name": "Clones",
+			"item": [
+				{
+					"name": "List all clones",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/clones - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/clones - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[GET]::/clones - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"snapshot\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"physicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"pool\":{\"type\":\"string\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int\"}}},\"protected\":{\"type\":\"boolean\",\"default\":false},\"deleteAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"db\":{\"type\":\"object\",\"properties\":{\"connStr\":{\"type\":\"string\"},\"host\":{\"type\":\"string\"},\"port\":{\"type\":\"string\"},\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"}}},\"metadata\":{\"type\":\"object\",\"properties\":{\"cloneDiffSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"cloningTime\":{\"type\":\"integer\",\"format\":\"float64\"},\"maxIdleMinutes\":{\"type\":\"integer\",\"format\":\"int64\"}}}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[GET]::/clones - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/clones",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"clones"
+							]
+						},
+						"description": "Return a list of all available clones (database endpoints)."
+					},
+					"response": [
+						{
+							"name": "Returned a list of all available clones",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clones",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clones"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "[\n  {\n    \"id\": \"test-clone-2\",\n    \"snapshot\": {\n      \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\",\n      \"createdAt\": \"2023-05-09T21:27:11Z\",\n      \"dataStateAt\": \"2023-05-09T21:27:11Z\",\n      \"physicalSize\": 120832,\n      \"logicalSize\": 11518021632,\n      \"pool\": \"dblab_pool/dataset_2\",\n      \"numClones\": 3\n    },\n    \"branch\": \"\",\n    \"protected\": false,\n    \"deleteAt\": null,\n    \"createdAt\": \"2023-05-16T06:12:52Z\",\n    \"status\": {\n      \"code\": \"OK\",\n      \"message\": \"Clone is ready to accept Postgres connections.\"\n    },\n    \"db\": {\n      \"connStr\": \"host=branching.aws.postgres.ai port=6005 user=tester dbname=postgres\",\n      \"host\": \"branching.aws.postgres.ai\",\n      \"port\": \"6005\",\n      \"username\": \"tester\",\n      \"password\": \"\",\n      \"dbName\": \"postgres\"\n    },\n    \"metadata\": {\n      \"cloneDiffSize\": 484352,\n      \"logicalSize\": 11518029312,\n      \"cloningTime\": 1.5250661829999999,\n      \"maxIdleMinutes\": 120\n    }\n  },\n  {\n    \"id\": \"test-clone\",\n    \"snapshot\": {\n      \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\",\n      \"createdAt\": \"2023-05-09T21:27:11Z\",\n      \"dataStateAt\": \"2023-05-09T21:27:11Z\",\n      \"physicalSize\": 120832,\n      \"logicalSize\": 11518021632,\n      \"pool\": \"dblab_pool/dataset_2\",\n      \"numClones\": 3\n    },\n    \"branch\": \"\",\n    \"protected\": false,\n    \"deleteAt\": null,\n    \"createdAt\": \"2023-05-16T06:12:30Z\",\n    \"status\": {\n      \"code\": \"OK\",\n      \"message\": \"Clone is ready to accept Postgres connections.\"\n    },\n    \"db\": {\n      \"connStr\": \"host=branching.aws.postgres.ai port=6004 user=tester dbname=postgres\",\n      \"host\": \"branching.aws.postgres.ai\",\n      \"port\": \"6004\",\n      \"username\": \"tester\",\n      \"password\": \"\",\n      \"dbName\": \"postgres\"\n    },\n    \"metadata\": {\n      \"cloneDiffSize\": 486400,\n      \"logicalSize\": 11518030336,\n      \"cloningTime\": 1.57552338,\n      \"maxIdleMinutes\": 120\n    }\n  }\n]"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clones",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clones"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Create a clone",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/clone - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/clone - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[POST]::/clone - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"snapshot\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"physicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"pool\":{\"type\":\"string\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int\"}}},\"protected\":{\"type\":\"boolean\",\"default\":false},\"deleteAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"db\":{\"type\":\"object\",\"properties\":{\"connStr\":{\"type\":\"string\"},\"host\":{\"type\":\"string\"},\"port\":{\"type\":\"string\"},\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"}}},\"metadata\":{\"type\":\"object\",\"properties\":{\"cloneDiffSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"cloningTime\":{\"type\":\"integer\",\"format\":\"float64\"},\"maxIdleMinutes\":{\"type\":\"integer\",\"format\":\"int64\"}}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[POST]::/clone - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"id\": \"magna cupidatat\",\n  \"snapshot\": {\n    \"id\": \"veniam\"\n  },\n  \"branch\": \"incididunt aliquip\",\n  \"protected\": null,\n  \"db\": {\n    \"username\": \"Duis Lorem\",\n    \"password\": \"culpa non velit ut\",\n    \"restricted\": null,\n    \"db_name\": \"dolore qui ut\"\n  }\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/clone",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"clone"
+							]
+						}
+					},
+					"response": [
+						{
+							"name": "Created a new clone",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"id\": \"magna cupidatat\",\n  \"snapshot\": {\n    \"id\": \"veniam\"\n  },\n  \"branch\": \"incididunt aliquip\",\n  \"protected\": null,\n  \"db\": {\n    \"username\": \"Duis Lorem\",\n    \"password\": \"culpa non velit ut\",\n    \"restricted\": null,\n    \"db_name\": \"dolore qui ut\"\n  }\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone"
+									]
+								}
+							},
+							"status": "Created",
+							"code": 201,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"id\": \"test-clone-2\",\n  \"snapshot\": {\n    \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\",\n    \"createdAt\": \"2023-05-09T21:27:11Z\",\n    \"dataStateAt\": \"2023-05-09T21:27:11Z\",\n    \"physicalSize\": 120832,\n    \"logicalSize\": 11518021632,\n    \"pool\": \"dblab_pool/dataset_2\",\n    \"numClones\": 3\n  },\n  \"branch\": \"\",\n  \"protected\": false,\n  \"deleteAt\": null,\n  \"createdAt\": \"2023-05-16T06:12:52Z\",\n  \"status\": {\n    \"code\": \"CREATING\",\n    \"message\": \"Clone is being created.\"\n  },\n  \"db\": {\n    \"connStr\": \"\",\n    \"host\": \"\",\n    \"port\": \"\",\n    \"username\": \"tester\",\n    \"password\": \"\",\n    \"dbName\": \"postgres\"\n  },\n  \"metadata\": {\n    \"cloneDiffSize\": 0,\n    \"logicalSize\": 0,\n    \"cloningTime\": 0,\n    \"maxIdleMinutes\": 0\n  }\n}"
+						},
+						{
+							"name": "Returned an error caused by invalid request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"id\": \"magna cupidatat\",\n  \"snapshot\": {\n    \"id\": \"veniam\"\n  },\n  \"branch\": \"incididunt aliquip\",\n  \"protected\": null,\n  \"db\": {\n    \"username\": \"Duis Lorem\",\n    \"password\": \"culpa non velit ut\",\n    \"restricted\": null,\n    \"db_name\": \"dolore qui ut\"\n  }\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"BAD_REQUEST\",\n  \"message\": \"clone with such ID already exists\"\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"id\": \"magna cupidatat\",\n  \"snapshot\": {\n    \"id\": \"veniam\"\n  },\n  \"branch\": \"incididunt aliquip\",\n  \"protected\": null,\n  \"db\": {\n    \"username\": \"Duis Lorem\",\n    \"password\": \"culpa non velit ut\",\n    \"restricted\": null,\n    \"db_name\": \"dolore qui ut\"\n  }\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Retrieve a clone",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/clone/:id - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/clone/:id - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[GET]::/clone/:id - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"snapshot\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"physicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"pool\":{\"type\":\"string\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int\"}}},\"protected\":{\"type\":\"boolean\",\"default\":false},\"deleteAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"db\":{\"type\":\"object\",\"properties\":{\"connStr\":{\"type\":\"string\"},\"host\":{\"type\":\"string\"},\"port\":{\"type\":\"string\"},\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"}}},\"metadata\":{\"type\":\"object\",\"properties\":{\"cloneDiffSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"cloningTime\":{\"type\":\"integer\",\"format\":\"float64\"},\"maxIdleMinutes\":{\"type\":\"integer\",\"format\":\"int64\"}}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[GET]::/clone/:id - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/clone/:id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"clone",
+								":id"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Clone ID"
+								}
+							]
+						},
+						"description": "Retrieves the information for the specified clone."
+					},
+					"response": [
+						{
+							"name": "Returned detailed information for the specified clone",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"id\": \"test-clone\",\n  \"snapshot\": {\n    \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\",\n    \"createdAt\": \"2023-05-09T21:27:11Z\",\n    \"dataStateAt\": \"2023-05-09T21:27:11Z\",\n    \"physicalSize\": 120832,\n    \"logicalSize\": 11518021632,\n    \"pool\": \"dblab_pool/dataset_2\",\n    \"numClones\": 3\n  },\n  \"branch\": \"\",\n  \"protected\": false,\n  \"deleteAt\": null,\n  \"createdAt\": \"2023-05-16T06:12:30Z\",\n  \"status\": {\n    \"code\": \"OK\",\n    \"message\": \"Clone is ready to accept Postgres connections.\"\n  },\n  \"db\": {\n    \"connStr\": \"host=branching.aws.postgres.ai port=6004 user=tester dbname=postgres\",\n    \"host\": \"branching.aws.postgres.ai\",\n    \"port\": \"6004\",\n    \"username\": \"tester\",\n    \"password\": \"\",\n    \"dbName\": \"postgres\"\n  },\n  \"metadata\": {\n    \"cloneDiffSize\": 486400,\n    \"logicalSize\": 11518030336,\n    \"cloningTime\": 1.57552338,\n    \"maxIdleMinutes\": 120\n  }\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						},
+						{
+							"name": "Not found",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "Not Found",
+							"code": 404,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"NOT_FOUND\",\n  \"message\": \"Requested object does not exist. Specify your request.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Delete a clone",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[DELETE]::/clone/:id - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[DELETE]::/clone/:id - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[DELETE]::/clone/:id - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/clone/:id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"clone",
+								":id"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Clone ID"
+								}
+							]
+						},
+						"description": "Permanently delete the specified clone. It cannot be undone."
+					},
+					"response": [
+						{
+							"name": "Successfully deleted the specified clone",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "\"OK\""
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						},
+						{
+							"name": "Not found",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "Not Found",
+							"code": 404,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"NOT_FOUND\",\n  \"message\": \"Requested object does not exist. Specify your request.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Update a clone",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[PATCH]::/clone/:id - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[PATCH]::/clone/:id - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[PATCH]::/clone/:id - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"snapshot\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"dataStateAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"physicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"pool\":{\"type\":\"string\"},\"numClones\":{\"type\":\"integer\",\"format\":\"int\"}}},\"protected\":{\"type\":\"boolean\",\"default\":false},\"deleteAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"createdAt\":{\"type\":\"string\",\"format\":\"date-time\"},\"status\":{\"required\":[\"code\",\"message\"],\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"Status code\"},\"message\":{\"type\":\"string\",\"description\":\"Status description\"}}},\"db\":{\"type\":\"object\",\"properties\":{\"connStr\":{\"type\":\"string\"},\"host\":{\"type\":\"string\"},\"port\":{\"type\":\"string\"},\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"}}},\"metadata\":{\"type\":\"object\",\"properties\":{\"cloneDiffSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"logicalSize\":{\"type\":\"integer\",\"format\":\"int64\"},\"cloningTime\":{\"type\":\"integer\",\"format\":\"float64\"},\"maxIdleMinutes\":{\"type\":\"integer\",\"format\":\"int64\"}}}}}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[PATCH]::/clone/:id - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"protected\": false\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/clone/:id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"clone",
+								":id"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Clone ID"
+								}
+							]
+						},
+						"description": "Updates the specified clone by setting the values of the parameters passed. Currently, only one paramater is supported: 'protected'."
+					},
+					"response": [
+						{
+							"name": "Successfully updated the specified clone",
+							"originalRequest": {
+								"method": "PATCH",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"protected\": false\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"id\": \"test-clone-2\",\n  \"snapshot\": {\n    \"id\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\",\n    \"createdAt\": \"2023-05-09T21:27:11Z\",\n    \"dataStateAt\": \"2023-05-09T21:27:11Z\",\n    \"physicalSize\": 120832,\n    \"logicalSize\": 11518021632,\n    \"pool\": \"dblab_pool/dataset_2\",\n    \"numClones\": 2\n  },\n  \"branch\": \"\",\n  \"protected\": true,\n  \"deleteAt\": null,\n  \"createdAt\": \"2023-05-16T06:12:52Z\",\n  \"status\": {\n    \"code\": \"OK\",\n    \"message\": \"Clone is ready to accept Postgres connections.\"\n  },\n  \"db\": {\n    \"connStr\": \"host=branching.aws.postgres.ai port=6005 user=tester dbname=postgres\",\n    \"host\": \"branching.aws.postgres.ai\",\n    \"port\": \"6005\",\n    \"username\": \"tester\",\n    \"password\": \"\",\n    \"dbName\": \"postgres\"\n  },\n  \"metadata\": {\n    \"cloneDiffSize\": 561664,\n    \"logicalSize\": 11518030336,\n    \"cloningTime\": 1.5250661829999999,\n    \"maxIdleMinutes\": 120\n  }\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "PATCH",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"protected\": false\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Reset a clone",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/clone/:id/reset - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/clone/:id/reset - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[POST]::/clone/:id/reset - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"snapshotID\": \"ut nulla Duis in in\",\n  \"latest\": false\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/clone/:id/reset",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"clone",
+								":id",
+								"reset"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Clone ID"
+								}
+							]
+						},
+						"description": "Reset the specified clone to a previously stored state. This can be done by specifying a particular snapshot ID or using the 'latest' flag. All changes made after the snapshot are discarded during the reset, unless those changes were preserved in a snapshot. All database connections will be reset, requiring users and applications to reconnect. The duration of the reset operation is comparable to the creation of a new clone. However, unlike creating a new clone, the reset operation retains the database credentials and does not change the port. Consequently, users and applications can continue to use the same database credentials post-reset, though reconnection will be necessary. Please note that any unsaved changes will be irretrievably lost during this operation, so ensure necessary data is backed up in a snapshot prior to resetting the clone."
+					},
+					"response": [
+						{
+							"name": "Successfully reset the state of the specified clone",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"snapshotID\": \"ut nulla Duis in in\",\n  \"latest\": false\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id/reset",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id",
+										"reset"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "\"OK\""
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"snapshotID\": \"ut nulla Duis in in\",\n  \"latest\": false\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/clone/:id/reset",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"clone",
+										":id",
+										"reset"
+									],
+									"variable": [
+										{
+											"key": "id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										}
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				}
+			]
+		},
+		{
+			"name": "Branches",
+			"item": [
+				{
+					"name": "List all branches",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/branches - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/branches - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/branches",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"branches"
+							]
+						},
+						"description": "Return a list of all available branches (named pointers to snapshots)."
+					},
+					"response": [
+						{
+							"name": "Returned a list of all available branches",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/branches",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branches"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "[\n  {\n    \"name\": \"my-1\",\n    \"parent\": \"main\",\n    \"dataStateAt\": \"20230224202652\",\n    \"snapshotID\": \"dblab_pool/dataset_2/main/20230224202652@20230224202652\"\n  },\n  {\n    \"name\": \"nik-test-branch\",\n    \"parent\": \"-\",\n    \"dataStateAt\": \"20230509212711\",\n    \"snapshotID\": \"dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711\"\n  },\n  {\n    \"name\": \"main\",\n    \"parent\": \"-\",\n    \"dataStateAt\": \"20230224202652\",\n    \"snapshotID\": \"dblab_pool/dataset_2/main/20230224202652@20230224202652\"\n  }\n]"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/branches",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branches"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Create a branch",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/branch/create - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/branch/create - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript",
+								"packages": {}
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "*/*"
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"branchName\": \"aute do laborum\",\n  \"baseBranch\": \"tempor aliqua consectetur\",\n  \"snapshotID\": \"mollit velit\"\n}"
+						},
+						"url": {
+							"raw": "{{baseUrl}}/branch",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"branch"
+							]
+						}
+					},
+					"response": [
+						{
+							"name": "OK",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"branchName\": \"aute do laborum\",\n  \"baseBranch\": \"tempor aliqua consectetur\",\n  \"snapshotID\": \"mollit velit\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/create",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"create"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"name\": \"cillum in laborum\"\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"branchName\": \"aute do laborum\",\n  \"baseBranch\": \"tempor aliqua consectetur\",\n  \"snapshotID\": \"mollit velit\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/create",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"create"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Delete a branch",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/branch/delete - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/branch/delete - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript",
+								"packages": {}
+							}
+						}
+					],
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "*/*"
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{baseUrl}}/branch/:branchName",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"branch",
+								":branchName"
+							],
+							"variable": [
+								{
+									"key": "branchName",
+									"value": ""
+								}
+							]
+						},
+						"description": "Permanently delete the specified branch. It cannot be undone."
+					},
+					"response": [
+						{
+							"name": "OK",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"branchName\": \"dolore aliqua laboris offi\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/delete",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"delete"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"status\": \"irure pariatur Excepteur occaecat ullamco\",\n  \"message\": \"in enim tempor\"\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"branchName\": \"dolore aliqua laboris offi\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/delete",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"delete"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Retrieve a branch log",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/branch/log - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/branch/log - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript",
+								"packages": {}
+							}
+						}
+					],
+					"protocolProfileBehavior": {
+						"disableBodyPruning": true
+					},
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "*/*"
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"branchName\": \"in exercitation eiusmod voluptate eu\"\n}"
+						},
+						"url": {
+							"raw": "{{baseUrl}}/branch/:branchName/log",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"branch",
+								":branchName",
+								"log"
+							],
+							"variable": [
+								{
+									"key": "branchName",
+									"value": ""
+								}
+							]
+						},
+						"description": "Retrieve a log of the specified branch (history of snapshots)."
+					},
+					"response": [
+						{
+							"name": "OK",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"branchName\": \"in exercitation eiusmod voluptate eu\"\n}"
+								},
+								"url": {
+									"raw": "{{baseUrl}}/branch/log",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"branch",
+										"log"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "[\n  {\n    \"id\": \"commodo enim\",\n    \"parent\": \"laboris anim labore adipisi\",\n    \"child\": \"consequat\",\n    \"branch\": [\n      \"ullamco ad cillum proident\",\n      \"ea elit tempor nostrud\"\n    ],\n    \"root\": \"sunt\",\n    \"dataStateAt\": \"2013-09-01T22:20:46.803Z\",\n    \"message\": \"et sit\"\n  },\n  {\n    \"id\": \"nisi cillum est deserunt\",\n    \"parent\": \"pariatur Lorem\",\n    \"child\": \"eu labore do deserunt\",\n    \"branch\": [\n      \"officia dolor\",\n      \"dolor cillum eu culpa ut\"\n    ],\n    \"root\": \"exercitation aute\",\n    \"dataStateAt\": \"1963-05-08T18:09:20.040Z\",\n    \"message\": \"est Excepteur mollit nostrud\"\n  }\n]"
+						}
+					]
+				}
+			]
+		},
+		{
+			"name": "Admin",
+			"item": [
+				{
+					"name": "Get config",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/admin/config - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/admin/config - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[GET]::/admin/config - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\"}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[GET]::/admin/config - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/admin/config",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"admin",
+								"config"
+							]
+						},
+						"description": "Retrieve the DBLab configuration. All sensitive values are masked. Only limited set of configuration parameters is returned – only those that can be changed via API (unless reconfiguration via API is disabled by admin). The result is provided in JSON format."
+					},
+					"response": [
+						{
+							"name": "Returned configuration",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/config",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"databaseConfigs\": {\n    \"configs\": {\n      \"shared_buffers\": \"1GB\",\n      \"shared_preload_libraries\": \"pg_stat_statements, pg_stat_kcache, auto_explain, logerrors\"\n    }\n  },\n  \"databaseContainer\": {\n    \"dockerImage\": \"registry.gitlab.com/postgres-ai/se-images/supabase:15\"\n  },\n  \"global\": {\n    \"debug\": true\n  },\n  \"retrieval\": {\n    \"refresh\": {\n      \"timetable\": \"0 1 * * 0\"\n    },\n    \"spec\": {\n      \"logicalDump\": {\n        \"options\": {\n          \"customOptions\": [],\n          \"databases\": {\n            \"test_small\": {}\n          },\n          \"parallelJobs\": 4,\n          \"source\": {\n            \"connection\": {\n              \"dbname\": \"test_small\",\n              \"host\": \"dev1.postgres.ai\",\n              \"port\": 6666,\n              \"username\": \"john\"\n            }\n          }\n        }\n      },\n      \"logicalRestore\": {\n        \"options\": {\n          \"customOptions\": [\n            \"--no-tablespaces\",\n            \"--no-privileges\",\n            \"--no-owner\",\n            \"--exit-on-error\"\n          ],\n          \"parallelJobs\": 4\n        }\n      }\n    }\n  }\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/config",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Set config",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/admin/config - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/admin/config - Content-Type is application/json\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
+									"});",
+									"",
+									"// Validate if response has JSON Body ",
+									"pm.test(\"[POST]::/admin/config - Response has JSON Body\", function () {",
+									"    pm.response.to.have.jsonBody();",
+									"});",
+									"",
+									"// Response Validation",
+									"const schema = {\"type\":\"object\"}",
+									"",
+									"// Validate if response matches JSON schema ",
+									"pm.test(\"[POST]::/admin/config - Schema is valid\", function() {",
+									"    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/admin/config",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"admin",
+								"config"
+							]
+						},
+						"description": "Set specific configurations for the DBLab instance using this endpoint. The returned configuration parameters are limited to those that can be modified via the API (unless the API-based reconfiguration has been disabled by an administrator). The result will be provided in JSON format."
+					},
+					"response": [
+						{
+							"name": "Successfully saved configuration parameters",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/admin/config",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/admin/config",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"BAD_REQUEST\",\n  \"message\": \"configuration management via UI/API disabled by admin\"\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/admin/config",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Get full config (YAML)",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[GET]::/admin/config.yaml - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[GET]::/admin/config.yaml - Content-Type is application/yaml\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/yaml\");",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/yaml"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/admin/config.yaml",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"admin",
+								"config.yaml"
+							]
+						},
+						"description": "Retrieve the DBLab configuration in YAML format. All sensitive values are masked. This method allows seeing the entire configuration file and can be helpful for reviewing configuration and setting up workflows to automate DBLab provisioning and configuration."
+					},
+					"response": [
+						{
+							"name": "Returned configuration (YAML)",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/yaml"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/config.yaml",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config.yaml"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "text",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/yaml"
+								}
+							],
+							"cookie": [],
+							"body": ""
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/config.yaml",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"config.yaml"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Test source database",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/admin/test-db-source - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"host\": \"veniam\",\n  \"port\": \"tempor\",\n  \"dbname\": \"et tempor in\",\n  \"username\": \"minim ir\",\n  \"password\": \"nisi ut incididunt in mollit\",\n  \"db_list\": [\n    \"veniam exercitation dolore\",\n    \"do nisi in occaecat\"\n  ]\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/admin/test-db-source",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"admin",
+								"test-db-source"
+							]
+						}
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"host\": \"adipisicing dolor\",\n  \"port\": \"elit\",\n  \"dbname\": \"cupidatat in veniam laborum dolore\",\n  \"username\": \"sint\",\n  \"password\": \"cillum nisi consectetur\",\n  \"db_list\": [\n    \"ad quis\",\n    \"aliqua nisi\"\n  ]\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/admin/test-db-source",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"test-db-source"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "text",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "text/plain"
+								}
+							],
+							"cookie": [],
+							"body": ""
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"host\": \"adipisicing dolor\",\n  \"port\": \"elit\",\n  \"dbname\": \"cupidatat in veniam laborum dolore\",\n  \"username\": \"sint\",\n  \"password\": \"cillum nisi consectetur\",\n  \"db_list\": [\n    \"ad quis\",\n    \"aliqua nisi\"\n  ]\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/admin/test-db-source",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"test-db-source"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"BAD_REQUEST\",\n  \"message\": \"configuration management via UI/API disabled by admin\"\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"host\": \"adipisicing dolor\",\n  \"port\": \"elit\",\n  \"dbname\": \"cupidatat in veniam laborum dolore\",\n  \"username\": \"sint\",\n  \"password\": \"cillum nisi consectetur\",\n  \"db_list\": [\n    \"ad quis\",\n    \"aliqua nisi\"\n  ]\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/admin/test-db-source",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"test-db-source"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Test source database",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"// Validate status 2xx ",
+									"pm.test(\"[POST]::/admin/ws-auth - Status code is 2xx\", function () {",
+									"   pm.response.to.be.success;",
+									"});",
+									"",
+									"// Validate if response header has matching content-type",
+									"pm.test(\"[POST]::/admin/ws-auth - Content-Type is */*\", function () {",
+									"   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"*/*\");",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "{{verificationToken}}",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "*/*"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/admin/ws-auth",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"admin",
+								"ws-auth"
+							]
+						}
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "*/*"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/ws-auth",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"ws-auth"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "*/*"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"token\": \"velit ut minim\"\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/ws-auth",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"ws-auth"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"BAD_REQUEST\",\n  \"message\": \"configuration management via UI/API disabled by admin\"\n}"
+						},
+						{
+							"name": "Unauthorized access",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/admin/ws-auth",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"admin",
+										"ws-auth"
+									]
+								}
+							},
+							"status": "Unauthorized",
+							"code": 401,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"UNAUTHORIZED\",\n  \"message\": \"Check your verification token.\"\n}"
+						}
+					]
+				}
+			]
+		},
+		{
+			"name": "Observation",
+			"item": [
+				{
+					"name": "Start observing",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"type": "text/javascript",
+								"exec": [
+									"// Validate status 2xx \npm.test(\"[POST]::/observation/start - Status code is 2xx\", function () {\n   pm.response.to.be.success;\n});\n",
+									"// Validate if response header has matching content-type\npm.test(\"[POST]::/observation/start - Content-Type is application/json\", function () {\n   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");\n});\n",
+									"// Validate if response has JSON Body \npm.test(\"[POST]::/observation/start - Response has JSON Body\", function () {\n    pm.response.to.have.jsonBody();\n});\n",
+									"// Response Validation\nconst schema = {\"type\":\"object\",\"properties\":{\"session_id\":{\"type\":\"integer\",\"format\":\"int64\"},\"started_at\":{\"type\":\"string\",\"format\":\"date-time\"},\"finished_at\":{\"type\":\"string\",\"format\":\"date-time\"},\"config\":{\"type\":\"object\",\"properties\":{\"observation_interval\":{\"type\":\"integer\",\"format\":\"int64\"},\"max_lock_duration\":{\"type\":\"integer\",\"format\":\"int64\"},\"max_duration\":{\"type\":\"integer\",\"format\":\"int64\"}}},\"tags\":{\"type\":\"object\",\"properties\":{}},\"artifacts\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"result\":{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"},\"intervals\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"started_at\":{\"type\":\"string\",\"format\":\"date-time\"},\"duration\":{\"type\":\"integer\",\"format\":\"int64\"},\"warning\":{\"type\":\"string\"}}}},\"summary\":{\"type\":\"object\",\"properties\":{\"total_duration\":{\"type\":\"integer\",\"format\":\"float64\"},\"total_intervals\":{\"type\":\"integer\",\"format\":\"int\"},\"warning_intervals\":{\"type\":\"integer\",\"format\":\"int\"},\"checklist\":{\"type\":\"object\",\"properties\":{\"overall_success\":{\"type\":\"boolean\"},\"session_duration_acceptable\":{\"type\":\"boolean\"},\"no_long_dangerous_locks\":{\"type\":\"boolean\"}}}}}}}}}\n\n// Validate if response matches JSON schema \npm.test(\"[POST]::/observation/start - Schema is valid\", function() {\n    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});\n});\n"
+								]
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "Ut magna qui deserunt",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"clone_id\": \"ut sit irure\",\n  \"config\": {\n    \"observation_interval\": 33950905,\n    \"max_lock_duration\": 82462220,\n    \"max_duration\": 54143470\n  },\n  \"tags\": {},\n  \"db_name\": \"magna esse dolore\"\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/observation/start",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"observation",
+								"start"
+							]
+						},
+						"description": "[EXPERIMENTAL] Start an observation session for the specified clone. Observation sessions help detect dangerous (long-lasting, exclusive) locks in CI/CD pipelines. One of common scenarios is using observation sessions to test schema changes (DB migrations)."
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"clone_id\": \"ut sit irure\",\n  \"config\": {\n    \"observation_interval\": 33950905,\n    \"max_lock_duration\": 82462220,\n    \"max_duration\": 54143470\n  },\n  \"tags\": {},\n  \"db_name\": \"magna esse dolore\"\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/observation/start",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"start"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"session_id\": -41566390,\n  \"started_at\": \"1991-02-14T03:01:06.417Z\",\n  \"finished_at\": \"2018-05-30T06:18:09.119Z\",\n  \"config\": {\n    \"observation_interval\": 76803835,\n    \"max_lock_duration\": -6633155,\n    \"max_duration\": -968293\n  },\n  \"tags\": {},\n  \"artifacts\": [\n    \"aliqua do\",\n    \"consectetur amet tempor eiusmod\"\n  ],\n  \"result\": {\n    \"status\": \"qui adipisicing velit aute\",\n    \"intervals\": [\n      {\n        \"started_at\": \"2008-06-20T07:35:49.463Z\",\n        \"duration\": 34650553,\n        \"warning\": \"velit nulla ex\"\n      },\n      {\n        \"started_at\": \"1994-03-12T02:59:52.189Z\",\n        \"duration\": 10020998,\n        \"warning\": \"ipsum laborum\"\n      }\n    ],\n    \"summary\": {\n      \"total_duration\": -51894451,\n      \"total_intervals\": -93757197,\n      \"warning_intervals\": 95087393,\n      \"checklist\": {\n        \"overall_success\": false,\n        \"session_duration_acceptable\": true,\n        \"no_long_dangerous_locks\": false\n      }\n    }\n  }\n}"
+						},
+						{
+							"name": "Not found",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"clone_id\": \"ut sit irure\",\n  \"config\": {\n    \"observation_interval\": 33950905,\n    \"max_lock_duration\": 82462220,\n    \"max_duration\": 54143470\n  },\n  \"tags\": {},\n  \"db_name\": \"magna esse dolore\"\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/observation/start",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"start"
+									]
+								}
+							},
+							"status": "Not Found",
+							"code": 404,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"NOT_FOUND\",\n  \"message\": \"Requested object does not exist. Specify your request.\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Stop observing",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"type": "text/javascript",
+								"exec": [
+									"// Validate status 2xx \npm.test(\"[POST]::/observation/stop - Status code is 2xx\", function () {\n   pm.response.to.be.success;\n});\n",
+									"// Validate if response header has matching content-type\npm.test(\"[POST]::/observation/stop - Content-Type is application/json\", function () {\n   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");\n});\n",
+									"// Validate if response has JSON Body \npm.test(\"[POST]::/observation/stop - Response has JSON Body\", function () {\n    pm.response.to.have.jsonBody();\n});\n",
+									"// Response Validation\nconst schema = {\"type\":\"object\",\"properties\":{\"session_id\":{\"type\":\"integer\",\"format\":\"int64\"},\"started_at\":{\"type\":\"string\",\"format\":\"date-time\"},\"finished_at\":{\"type\":\"string\",\"format\":\"date-time\"},\"config\":{\"type\":\"object\",\"properties\":{\"observation_interval\":{\"type\":\"integer\",\"format\":\"int64\"},\"max_lock_duration\":{\"type\":\"integer\",\"format\":\"int64\"},\"max_duration\":{\"type\":\"integer\",\"format\":\"int64\"}}},\"tags\":{\"type\":\"object\",\"properties\":{}},\"artifacts\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"result\":{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"},\"intervals\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"started_at\":{\"type\":\"string\",\"format\":\"date-time\"},\"duration\":{\"type\":\"integer\",\"format\":\"int64\"},\"warning\":{\"type\":\"string\"}}}},\"summary\":{\"type\":\"object\",\"properties\":{\"total_duration\":{\"type\":\"integer\",\"format\":\"float64\"},\"total_intervals\":{\"type\":\"integer\",\"format\":\"int\"},\"warning_intervals\":{\"type\":\"integer\",\"format\":\"int\"},\"checklist\":{\"type\":\"object\",\"properties\":{\"overall_success\":{\"type\":\"boolean\"},\"session_duration_acceptable\":{\"type\":\"boolean\"},\"no_long_dangerous_locks\":{\"type\":\"boolean\"}}}}}}}}}\n\n// Validate if response matches JSON schema \npm.test(\"[POST]::/observation/stop - Schema is valid\", function() {\n    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});\n});\n"
+								]
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "Ut magna qui deserunt",
+								"description": "(Required) "
+							},
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"clone_id\": \"proident cillum nostrud officia\",\n  \"overall_error\": false\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{baseUrl}}/observation/stop",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"observation",
+								"stop"
+							]
+						},
+						"description": "[EXPERIMENTAL] Stop the previously started observation session."
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"clone_id\": \"proident cillum nostrud officia\",\n  \"overall_error\": false\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/observation/stop",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"stop"
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"session_id\": 9614128,\n  \"started_at\": \"1993-11-12T01:24:57.933Z\",\n  \"finished_at\": \"1953-01-01T04:06:59.652Z\",\n  \"config\": {\n    \"observation_interval\": -46635741,\n    \"max_lock_duration\": -53938384,\n    \"max_duration\": 85779944\n  },\n  \"tags\": {},\n  \"artifacts\": [\n    \"deseru\",\n    \"in ullamco veniam\"\n  ],\n  \"result\": {\n    \"status\": \"ut ea l\",\n    \"intervals\": [\n      {\n        \"started_at\": \"1943-07-24T05:03:49.697Z\",\n        \"duration\": -45788381,\n        \"warning\": \"Ut qui occaecat\"\n      },\n      {\n        \"started_at\": \"1973-02-08T19:49:36.906Z\",\n        \"duration\": 78310177,\n        \"warning\": \"dolore amet mollit velit\"\n      }\n    ],\n    \"summary\": {\n      \"total_duration\": 89098265,\n      \"total_intervals\": -25796081,\n      \"warning_intervals\": -74609996,\n      \"checklist\": {\n        \"overall_success\": false,\n        \"session_duration_acceptable\": true,\n        \"no_long_dangerous_locks\": false\n      }\n    }\n  }\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"clone_id\": \"proident cillum nostrud officia\",\n  \"overall_error\": false\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "{{baseUrl}}/observation/stop",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"stop"
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Get observation summary",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"type": "text/javascript",
+								"exec": [
+									"// Validate status 2xx \npm.test(\"[GET]::/observation/summary/:clone_id/:session_id - Status code is 2xx\", function () {\n   pm.response.to.be.success;\n});\n",
+									"// Validate if response header has matching content-type\npm.test(\"[GET]::/observation/summary/:clone_id/:session_id - Content-Type is application/json\", function () {\n   pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");\n});\n",
+									"// Validate if response has JSON Body \npm.test(\"[GET]::/observation/summary/:clone_id/:session_id - Response has JSON Body\", function () {\n    pm.response.to.have.jsonBody();\n});\n",
+									"// Response Validation\nconst schema = {\"type\":\"object\",\"properties\":{\"session_id\":{\"type\":\"integer\",\"format\":\"int64\"},\"clone_id\":{\"type\":\"string\"},\"duration\":{\"type\":\"object\",\"properties\":{}},\"db_size\":{\"type\":\"object\",\"properties\":{}},\"locks\":{\"type\":\"object\",\"properties\":{}},\"log_errors\":{\"type\":\"object\",\"properties\":{}},\"artifact_types\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}\n\n// Validate if response matches JSON schema \npm.test(\"[GET]::/observation/summary/:clone_id/:session_id - Schema is valid\", function() {\n    pm.response.to.have.jsonSchema(schema,{unknownFormats: [\"int32\", \"int64\", \"float\", \"double\"]});\n});\n"
+								]
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "Ut magna qui deserunt",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/observation/summary/:clone_id/:session_id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"observation",
+								"summary",
+								":clone_id",
+								":session_id"
+							],
+							"variable": [
+								{
+									"key": "clone_id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Clone ID"
+								},
+								{
+									"key": "session_id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Session ID"
+								}
+							]
+						},
+						"description": "[EXPERIMENTAL] Collect the observation summary info."
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/observation/summary/:clone_id/:session_id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"summary",
+										":clone_id",
+										":session_id"
+									],
+									"variable": [
+										{
+											"key": "clone_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										},
+										{
+											"key": "session_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Session ID"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"session_id\": 55155718,\n  \"clone_id\": \"cupidatat laborum consequat Lorem officia\",\n  \"duration\": {},\n  \"db_size\": {},\n  \"locks\": {},\n  \"log_errors\": {},\n  \"artifact_types\": [\n    \"laboris anim Ut enim\",\n    \"ullamco in esse nostrud Exc\"\n  ]\n}"
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/observation/summary/:clone_id/:session_id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"summary",
+										":clone_id",
+										":session_id"
+									],
+									"variable": [
+										{
+											"key": "clone_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										},
+										{
+											"key": "session_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Session ID"
+										}
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				},
+				{
+					"name": "Download an observation artifact",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"type": "text/javascript",
+								"exec": [
+									"// Validate status 2xx \npm.test(\"[GET]::/observation/download/:artifact_type/:clone_id/:session_id - Status code is 2xx\", function () {\n   pm.response.to.be.success;\n});\n"
+								]
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Verification-Token",
+								"value": "Ut magna qui deserunt",
+								"description": "(Required) "
+							},
+							{
+								"key": "Accept",
+								"value": "application/json"
+							}
+						],
+						"url": {
+							"raw": "{{baseUrl}}/observation/download/:artifact_type/:clone_id/:session_id",
+							"host": [
+								"{{baseUrl}}"
+							],
+							"path": [
+								"observation",
+								"download",
+								":artifact_type",
+								":clone_id",
+								":session_id"
+							],
+							"variable": [
+								{
+									"key": "artifact_type",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Type of the requested artifact"
+								},
+								{
+									"key": "clone_id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Clone ID"
+								},
+								{
+									"key": "session_id",
+									"value": "Ut magna qui deserunt",
+									"description": "(Required) Session ID"
+								}
+							]
+						},
+						"description": "[EXPERIMENTAL] Download an artifact for the specified clone and observation session."
+					},
+					"response": [
+						{
+							"name": "Successful operation",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/observation/download/:artifact_type/:clone_id/:session_id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"download",
+										":artifact_type",
+										":clone_id",
+										":session_id"
+									],
+									"variable": [
+										{
+											"key": "artifact_type",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Type of the requested artifact"
+										},
+										{
+											"key": "clone_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										},
+										{
+											"key": "session_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Session ID"
+										}
+									]
+								}
+							},
+							"status": "OK",
+							"code": 200,
+							"_postman_previewlanguage": "text",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "text/plain"
+								}
+							],
+							"cookie": [],
+							"body": ""
+						},
+						{
+							"name": "Bad request",
+							"originalRequest": {
+								"method": "GET",
+								"header": [
+									{
+										"key": "Verification-Token",
+										"value": "Ut magna qui deserunt",
+										"description": "(Required) "
+									},
+									{
+										"key": "Accept",
+										"value": "application/json"
+									}
+								],
+								"url": {
+									"raw": "{{baseUrl}}/observation/download/:artifact_type/:clone_id/:session_id",
+									"host": [
+										"{{baseUrl}}"
+									],
+									"path": [
+										"observation",
+										"download",
+										":artifact_type",
+										":clone_id",
+										":session_id"
+									],
+									"variable": [
+										{
+											"key": "artifact_type",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Type of the requested artifact"
+										},
+										{
+											"key": "clone_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Clone ID"
+										},
+										{
+											"key": "session_id",
+											"value": "Ut magna qui deserunt",
+											"description": "(Required) Session ID"
+										}
+									]
+								}
+							},
+							"status": "Bad Request",
+							"code": 400,
+							"_postman_previewlanguage": "json",
+							"header": [
+								{
+									"key": "Content-Type",
+									"value": "application/json"
+								}
+							],
+							"cookie": [],
+							"body": "{\n  \"code\": \"incididunt minim nulla\",\n  \"message\": \"qui fugiat\",\n  \"detail\": \"occaecat\",\n  \"hint\": \"anim\"\n}"
+						}
+					]
+				}
+			]
+		}
+	],
+	"variable": [
+		{
+			"key": "baseUrl",
+			"value": "https://branching.aws.postgres.ai:446/api",
+			"type": "string"
+		}
+	]
+}
\ No newline at end of file
diff --git a/engine/api/postman/portman-cli.json b/engine/api/postman/portman-cli.json
new file mode 100644
index 00000000..89b27ed2
--- /dev/null
+++ b/engine/api/postman/portman-cli.json
@@ -0,0 +1,10 @@
+{
+  "baseUrL": "http://branching.aws.postgres.ai:446/api",
+  "verificationToken": "demo-token",
+  "local": "engine/api/swagger-spec/dblab_openapi.yaml",
+  "output": "engine/api/postman/output.json",
+  "envFile": "engine/api/postman/portman.env",
+  "includeTests": true,
+  "syncPostman": true,
+  "runNewman": false
+}
diff --git a/engine/api/swagger-spec/dblab_openapi.yaml b/engine/api/swagger-spec/dblab_openapi.yaml
new file mode 100644
index 00000000..a1d7b208
--- /dev/null
+++ b/engine/api/swagger-spec/dblab_openapi.yaml
@@ -0,0 +1,1919 @@
+# OpenAPI spec for DBLab API
+# Useful links:
+# - validate and test: https://editor.swagger.io/
+# - official reference location for this API: https://dblab.readme.io/
+# - GitHub (give us a ⭐️): https://github.com/postgres-ai/database-lab-engine
+
+openapi: 3.0.1
+info:
+  title: DBLab API
+  description: This page provides the OpenAPI specification for the Database Lab (DBLab)
+    API, previously recognized as the DLE API (Database Lab Engine API).
+  contact:
+    name: DBLab API Support
+    url: https://postgres.ai/contact
+    email: api@postgres.ai
+  license:
+    name: Apache 2.0
+    url: https://github.com/postgres-ai/database-lab-engine/blob/dle-4-0/LICENSE
+  version: 4.0.0
+externalDocs:
+  description: DBLab Docs
+  url: https://gitlab.com/postgres-ai/docs/tree/master/docs/database-lab
+
+servers:
+  - url: "https://demo.dblab.dev/api"
+    description: "DBLab 4.0 demo server (with DB branching support); token: 'demo-token'"
+    x-examples:
+      Verification-Token: "demo-token"
+  - url: "https://demo.aws.postgres.ai:446/api"
+    description: "DBLab 3.x demo server; token: 'demo-token'"
+    x-examples:
+      Verification-Token: "demo-token"
+  - url: "{scheme}://{host}:{port}/{basePath}"
+    description: "Any DBLab accessed locally / through SSH port forwarding"
+    variables:
+      scheme:
+        enum:
+          - "https"
+          - "http"
+        default: "http"
+        description: "'http' for local connections and SSH port forwarding;
+          'https' for everything else."
+      host:
+        default: "localhost"
+        description: "where DBLab server is installed. Use 'localhost' to work locally
+          or when SSH port forwarding is used."
+      port:
+        default: "2346"
+        description: "Port to access DBLab UI or API. Originally, '2345' is used for
+          direct work with API and '2346' – with UI. However, with UI, API is also available,
+          at ':2346/api'."
+      basePath:
+        default: "api"
+        description: "basePath value to access API. Use empty when working with API port
+          (2345 by default), or '/api' when working with UI port ('2346' by default)."
+    x-examples:
+      Verification-Token: "custom_example_token"
+
+tags:
+- name: DBLab
+  description: "DBLab API Reference – database branching, instant cloning, and more.
+    DBLab CLI and UI rely on DBLab API."
+  externalDocs:
+    description: "DBLab Docs - tutorials, howtos, references."
+    url: https://postgres.ai/docs/reference-guides/database-lab-engine-api-reference
+
+paths:
+  /status:
+    get:
+      tags:
+      - Instance
+      summary: DBLab instance status and detailed information
+      description: "Retrieves detailed information about the DBLab instance: status, version,
+        clones, snapshots, etc."
+      operationId: status
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Returned detailed information about the DBLab instance
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                status:
+                  code: OK
+                  message: Instance is ready
+                engine:
+                  version: v4.0.0-alpha.5-20230516-0224
+                  edition: standard
+                  billingActive: true
+                  instanceID: chhfqfcnvrvc73d0lij0
+                  startedAt: '2023-05-16T03:50:19Z'
+                  telemetry: true
+                  disableConfigModification: false
+                pools:
+                - name: dblab_pool/dataset_1
+                  mode: zfs
+                  dataStateAt: ''
+                  status: empty
+                  cloneList: []
+                  fileSystem:
+                    mode: zfs
+                    size: 30685528064
+                    free: 30685282816
+                    used: 245248
+                    dataSize: 12288
+                    usedBySnapshots: 0
+                    usedByClones: 219648
+                    compressRatio: 1
+                - name: dblab_pool/dataset_2
+                  mode: zfs
+                  dataStateAt: ''
+                  status: empty
+                  cloneList: []
+                  fileSystem:
+                    mode: zfs
+                    size: 30685528064
+                    free: 30685282816
+                    used: 245248
+                    dataSize: 12288
+                    usedBySnapshots: 0
+                    usedByClones: 219648
+                    compressRatio: 1
+                - name: dblab_pool/dataset_3
+                  mode: zfs
+                  dataStateAt: ''
+                  status: empty
+                  cloneList: []
+                  fileSystem:
+                    mode: zfs
+                    size: 30685528064
+                    free: 30685282816
+                    used: 245248
+                    dataSize: 12288
+                    usedBySnapshots: 0
+                    usedByClones: 219648
+                    compressRatio: 1
+                cloning:
+                  expectedCloningTime: 0
+                  numClones: 0
+                  clones: []
+                retrieving:
+                  mode: logical
+                  status: pending
+                  lastRefresh:
+                  nextRefresh:
+                  alerts: {}
+                  activity:
+                provisioner:
+                  dockerImage: postgresai/extended-postgres:15
+                  containerConfig:
+                    shm-size: 1gb
+                synchronization:
+                  status:
+                    code: Not available
+                    message: ''
+                  lastReplayedLsn: ''
+                  lastReplayedLsnAt: ''
+                  replicationLag: 0
+                  replicationUptime: 0
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /snapshots:
+    get:
+      tags:
+      - Snapshots
+      summary: List all snapshots
+      description: Return a list of all available snapshots.
+      operationId: snapshots
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: branch
+        in: query
+        required: false
+        schema:
+          type: string
+      responses:
+        200:
+          description: Returned a list of snapshots
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Snapshot'
+              example:
+                - id: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                  createdAt: '2023-05-09T21:27:11Z'
+                  dataStateAt: '2023-05-09T21:27:11Z'
+                  physicalSize: 0
+                  logicalSize: 11518021632
+                  pool: dblab_pool/dataset_2
+                  numClones: 1
+                - id: dblab_pool/dataset_2/nik-test-branch/20230307171959@20230307171959
+                  createdAt: '2023-03-07T17:19:59Z'
+                  dataStateAt: '2023-03-07T17:19:59Z'
+                  physicalSize: 151552
+                  logicalSize: 11518015488
+                  pool: dblab_pool/dataset_2
+                  numClones: 1
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /full-refresh:
+    post:
+      tags:
+        - Instance
+      summary: Trigger full data refresh
+      description: "Initiates a full data refresh."
+      operationId: refresh
+      parameters:
+        - name: Verification-Token
+          in: header
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: Full refresh has been initiated
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/FullRefresh'
+              example:
+                status: OK
+                message: Full refresh started
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /snapshot:
+    post:
+      tags:
+        - Snapshots
+      summary: Create a snapshot
+      description: "Create a new snapshot from the current state of the selected pool.
+        This snapshot can later be used to create clones or new branches."
+      parameters:
+        - name: Verification-Token
+          in: header
+          required: true
+          schema:
+            type: string
+      requestBody:
+        description: "Optional parameters for snapshot creation.
+          If no pool name is provided, the first available pool is used."
+        content:
+          '*/*':
+            schema:
+              type: object
+              properties:
+                poolName:
+                  type: string
+                  description: Name of the pool to create snapshot in.
+        required: false
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Snapshot'
+        400:
+          description: Bad request
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Error'
+      x-codegen-request-body-name: body
+  /snapshot/{id}:
+    delete:
+      tags:
+        - Snapshots
+      summary: Delete a snapshot
+      description: "Permanently delete the specified snapshot.
+        If the snapshot has dependent clones or datasets, `force=true` can be provided as a query parameter."
+      parameters:
+        - name: id
+          in: path
+          required: true
+          description: The ID of the snapshot to delete.
+          schema:
+            type: string
+            pattern: '.*'
+        - name: force
+          in: query
+          required: false
+          description: Force deletion even if dependent clones or datasets exist.
+          schema:
+            type: boolean
+        - name: Verification-Token
+          in: header
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/ResponseStatus'
+        400:
+          description: Bad request
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Error'
+  /clones:
+    get:
+      tags:
+      - Clones
+      summary: List all clones
+      description: Return a list of all available clones (database endpoints).
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Returned a list of all available clones
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Clone'
+              example:
+                - id: test-clone-2
+                  snapshot:
+                    id: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                    createdAt: '2023-05-09T21:27:11Z'
+                    dataStateAt: '2023-05-09T21:27:11Z'
+                    physicalSize: 120832
+                    logicalSize: 11518021632
+                    pool: dblab_pool/dataset_2
+                    numClones: 3
+                  branch: ''
+                  protected: false
+                  deleteAt:
+                  createdAt: '2023-05-16T06:12:52Z'
+                  status:
+                    code: OK
+                    message: Clone is ready to accept Postgres connections.
+                  db:
+                    connStr: host=branching.aws.postgres.ai port=6005 user=tester dbname=postgres
+                    host: branching.aws.postgres.ai
+                    port: '6005'
+                    username: tester
+                    password: ''
+                    dbName: postgres
+                  metadata:
+                    cloneDiffSize: 484352
+                    logicalSize: 11518029312
+                    cloningTime: 1.5250661829999999
+                    maxIdleMinutes: 120
+                - id: test-clone
+                  snapshot:
+                    id: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                    createdAt: '2023-05-09T21:27:11Z'
+                    dataStateAt: '2023-05-09T21:27:11Z'
+                    physicalSize: 120832
+                    logicalSize: 11518021632
+                    pool: dblab_pool/dataset_2
+                    numClones: 3
+                  branch: ''
+                  protected: false
+                  deleteAt:
+                  createdAt: '2023-05-16T06:12:30Z'
+                  status:
+                    code: OK
+                    message: Clone is ready to accept Postgres connections.
+                  db:
+                    connStr: host=branching.aws.postgres.ai port=6004 user=tester dbname=postgres
+                    host: branching.aws.postgres.ai
+                    port: '6004'
+                    username: tester
+                    password: ''
+                    dbName: postgres
+                  metadata:
+                    cloneDiffSize: 486400
+                    logicalSize: 11518030336
+                    cloningTime: 1.57552338
+                    maxIdleMinutes: 120
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /clone:
+    post:
+      tags:
+      - Clones
+      summary: Create a clone
+      operationId: createClone
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Clone object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/CreateClone'
+        required: true
+      responses:
+        201:
+          description: Created a new clone
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Clone'
+              example:
+                id: test-clone-2
+                snapshot:
+                  id: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                  createdAt: '2023-05-09T21:27:11Z'
+                  dataStateAt: '2023-05-09T21:27:11Z'
+                  physicalSize: 120832
+                  logicalSize: 11518021632
+                  pool: dblab_pool/dataset_2
+                  numClones: 3
+                branch: ''
+                protected: false
+                deleteAt:
+                createdAt: '2023-05-16T06:12:52Z'
+                status:
+                  code: CREATING
+                  message: Clone is being created.
+                db:
+                  connStr: ''
+                  host: ''
+                  port: ''
+                  username: tester
+                  password: ''
+                  dbName: postgres
+                metadata:
+                  cloneDiffSize: 0
+                  logicalSize: 0
+                  cloningTime: 0
+                  maxIdleMinutes: 0
+        400:
+          description: Returned an error caused by invalid request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "BAD_REQUEST"
+                message: "clone with such ID already exists"
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+      x-codegen-request-body-name: body
+  /clone/{id}:
+    get:
+      tags:
+      - Clones
+      summary: Retrieve a clone
+      description: Retrieves the information for the specified clone.
+      operationId: getClone
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: id
+        in: path
+        description: Clone ID
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Returned detailed information for the specified clone
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Clone'
+              example:
+                id: test-clone
+                snapshot:
+                  id: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                  createdAt: '2023-05-09T21:27:11Z'
+                  dataStateAt: '2023-05-09T21:27:11Z'
+                  physicalSize: 120832
+                  logicalSize: 11518021632
+                  pool: dblab_pool/dataset_2
+                  numClones: 3
+                branch: ''
+                protected: false
+                deleteAt:
+                createdAt: '2023-05-16T06:12:30Z'
+                status:
+                  code: OK
+                  message: Clone is ready to accept Postgres connections.
+                db:
+                  connStr: host=branching.aws.postgres.ai port=6004 user=tester dbname=postgres
+                  host: branching.aws.postgres.ai
+                  port: '6004'
+                  username: tester
+                  password: ''
+                  dbName: postgres
+                metadata:
+                  cloneDiffSize: 486400
+                  logicalSize: 11518030336
+                  cloningTime: 1.57552338
+                  maxIdleMinutes: 120
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+        404:
+          description: Not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: NOT_FOUND
+                message: Requested object does not exist. Specify your request.
+    delete:
+      tags:
+      - Clones
+      summary: Delete a clone
+      description: Permanently delete the specified clone. It cannot be undone.
+      operationId: deleteClone
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: id
+        in: path
+        description: Clone ID
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Successfully deleted the specified clone
+          content:
+            application/json:
+              example:
+                "OK"
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+        404:
+          description: Not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: NOT_FOUND
+                message: Requested object does not exist. Specify your request.
+    patch:
+      tags:
+      - Clones
+      summary: Update a clone
+      description: "Updates the specified clone by setting the values of the parameters passed.
+        Currently, only one paramater is supported: 'protected'."
+      operationId: updateClone
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: id
+        in: path
+        description: Clone ID
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Clone object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/UpdateClone'
+        required: true
+      responses:
+        200:
+          description: Successfully updated the specified clone
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Clone'
+              example:
+                id: test-clone-2
+                snapshot:
+                  id: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                  createdAt: '2023-05-09T21:27:11Z'
+                  dataStateAt: '2023-05-09T21:27:11Z'
+                  physicalSize: 120832
+                  logicalSize: 11518021632
+                  pool: dblab_pool/dataset_2
+                  numClones: 2
+                branch: ''
+                protected: true
+                deleteAt:
+                createdAt: '2023-05-16T06:12:52Z'
+                status:
+                  code: OK
+                  message: Clone is ready to accept Postgres connections.
+                db:
+                  connStr: host=branching.aws.postgres.ai port=6005 user=tester dbname=postgres
+                  host: branching.aws.postgres.ai
+                  port: '6005'
+                  username: tester
+                  password: ''
+                  dbName: postgres
+                metadata:
+                  cloneDiffSize: 561664
+                  logicalSize: 11518030336
+                  cloningTime: 1.5250661829999999
+                  maxIdleMinutes: 120
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+        #404:  # TODO: fix it in engine (currently returns 500)
+        #  description: Not found
+        #  content:
+        #    application/json:
+        #      schema:
+        #        $ref: '#/components/schemas/Error'
+        #      example:
+        #        code: NOT_FOUND
+        #        message: Requested object does not exist. Specify your request.
+      x-codegen-request-body-name: body
+  /clone/{id}/reset:
+    post:
+      tags:
+      - Clones
+      summary: Reset a clone
+      description: "Reset the specified clone to a previously stored state.
+        This can be done by specifying a particular snapshot ID or using the 'latest' flag.
+        All changes made after the snapshot are discarded during the reset, unless those
+        changes were preserved in a snapshot. All database connections will be reset,
+        requiring users and applications to reconnect. The duration of the reset operation
+        is comparable to the creation of a new clone. However, unlike creating a new clone,
+        the reset operation retains the database credentials and does not change the port.
+        Consequently, users and applications can continue to use the same database credentials
+        post-reset, though reconnection will be necessary. Please note that any unsaved changes
+        will be irretrievably lost during this operation, so ensure necessary data is backed up
+        in a snapshot prior to resetting the clone."
+      operationId: resetClone
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: id
+        in: path
+        description: Clone ID
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Reset object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/ResetClone'
+        required: false
+      responses:
+        200:
+          description: Successfully reset the state of the specified clone
+          content:
+            application/json:
+              example:
+                "OK"
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+        #404:    # TODO: fix it in engine (currently returns 500)
+        #  description: Not found
+        #  content:
+        #    application/json:
+        #      schema:
+        #        $ref: '#/components/schemas/Error'
+      x-codegen-request-body-name: body
+  /branches:
+    get:
+      tags:
+      - Branches
+      summary: List all branches
+      description: Return a list of all available branches (named pointers to snapshots).
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Returned a list of all available branches
+          content:
+            '*/*':
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Branch'
+              example:
+                - name: my-1
+                  parent: main
+                  dataStateAt: '20230224202652'
+                  snapshotID: dblab_pool/dataset_2/main/20230224202652@20230224202652
+                - name: nik-test-branch
+                  parent: "-"
+                  dataStateAt: '20230509212711'
+                  snapshotID: dblab_pool/dataset_2/nik-test-branch/20230509212711@20230509212711
+                - name: main
+                  parent: "-"
+                  dataStateAt: '20230224202652'
+                  snapshotID: dblab_pool/dataset_2/main/20230224202652@20230224202652
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /branch/snapshot/{id}:
+    get:
+      tags:
+        - Snapshots
+      summary: Retrieve a snapshot
+      description: Retrieves the information for the specified snapshot.
+      parameters:
+      - name: id
+        in: path
+        description: ID of the branch snapshot
+        required: true
+        schema:
+          type: string
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/SnapshotDetails'
+        400:
+          description: Bad request
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Error'
+  /branch:
+    post:
+      tags:
+      - Branches
+      summary: Create a branch
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        content:
+          '*/*':
+            schema:
+              type: object
+              properties:
+                branchName:
+                  type: string
+                  description: The name of the new branch.
+                baseBranch:
+                  type: string
+                  description: "The name of parent branch user to create a new branch.
+                    Must not be specified if 'snapshotID' is specified."
+                snapshotID:
+                  type: string
+                  description: "The ID of the snapshot used to create a new branch.
+                    Must not be specified if 'baseBranch' is specified."
+        required: true
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
+        400:
+          description: Bad request
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Error'
+      x-codegen-request-body-name: body
+  /branch/snapshot:
+    post:
+      tags:
+      - Snapshots
+      summary: Create a snapshot
+      description: "Create a new snapshot using the specified clone. After a snapshot
+        has been created, the original clone can be deleted in order to free up compute resources, if necessary. 
+        The snapshot created by this endpoint can be used later to create one or more new clones."
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: "Parameters necessary for snapshot creation: 'cloneID' – the
+          ID of the clone, 'message' – description of the snapshot"
+        content:
+          '*/*':
+            schema:
+              type: object
+              properties:
+                cloneID:
+                  type: string
+                message:
+                  type: string
+        required: true
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                type: object
+                properties:
+                  snapshotID:
+                    type: string
+        400:
+          description: Bad request
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Error'
+      x-codegen-request-body-name: body
+  /branch/{branchName}:
+    delete:
+      tags:
+      - Branches
+      summary: Delete a branch
+      description: "Permanently delete the specified branch. It cannot be undone."
+      parameters:
+        - name: branchName
+          in: path
+          required: true
+          schema:
+            type: string
+            description: "The name of the branch to be deleted."
+        - name: Verification-Token
+          in: header
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/ResponseStatus'
+        400:
+          description: Bad request
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/Error'
+      x-codegen-request-body-name: body
+  /branch/{branchName}/log:
+    get:
+      tags:
+      - Branches
+      summary: Retrieve a branch log
+      description: Retrieve a log of the specified branch (history of snapshots).
+      parameters:
+        - name: branchName
+          in: path
+          required: true
+          schema:
+            type: string
+            description: The name of the branch.
+        - name: Verification-Token
+          in: header
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: OK
+          content:
+            '*/*':
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/SnapshotDetails'
+      x-codegen-request-body-name: body
+  /instance/retrieval:
+    get:
+      tags:
+      - Instance
+      summary: Data refresh status
+      description: 'Report a status of the data refresh subsystem (also known as
+        "data retrieval"): timestamps of the previous and next refresh runs, status, messages.'
+      operationId: instanceRetrieval
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Reported a status of the data retrieval subsystem
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Retrieving'
+              example:
+                mode: logical
+                status: pending
+                lastRefresh:
+                nextRefresh:
+                alerts: {}
+                activity:
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /healthz:
+    get:
+      tags:
+      - Instance
+      summary: Service health check
+      description: "Check the overall health and availability of the API system.
+        This endpoint does not require the 'Verification-Token' header."
+      operationId: healthz
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Engine'
+              example:
+                version: "v4.0.0-alpha.5-20230516-0224"
+                edition: "standard"
+                instanceID: "chhfqfcnvrvc73d0lij0"
+  /admin/config:
+    get:
+      tags:
+      - Admin
+      summary: Get config
+      description: "Retrieve the DBLab configuration. All sensitive values are masked.
+        Only limited set of configuration parameters is returned – only those that can be
+        changed via API (unless reconfiguration via API is disabled by admin). The result
+        is provided in JSON format."
+      operationId: getConfig
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Returned configuration
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Config'
+              example:
+                databaseConfigs:
+                  configs:
+                    shared_buffers: 1GB
+                    shared_preload_libraries: pg_stat_statements, pg_stat_kcache, auto_explain, logerrors
+                databaseContainer:
+                  dockerImage: registry.gitlab.com/postgres-ai/se-images/supabase:15
+                global:
+                  debug: true
+                retrieval:
+                  refresh:
+                    timetable: 0 1 * * 0
+                  spec:
+                    logicalDump:
+                      options:
+                        customOptions: []
+                        databases:
+                          test_small: {}
+                        parallelJobs: 4
+                        source:
+                          connection:
+                            dbname: test_small
+                            host: dev1.postgres.ai
+                            port: 6666
+                            username: john
+                    logicalRestore:
+                      options:
+                        customOptions:
+                        - "--no-tablespaces"
+                        - "--no-privileges"
+                        - "--no-owner"
+                        - "--exit-on-error"
+                        parallelJobs: 4
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+    post:
+      tags:
+      - Admin
+      summary: Set config
+      description: "Set specific configurations for the DBLab instance using this endpoint.
+        The returned configuration parameters are limited to those that can be modified
+        via the API (unless the API-based reconfiguration has been disabled by an administrator).
+        The result will be provided in JSON format."
+      operationId: setConfig
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Set configuration object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Config'
+        required: true
+      responses:
+        200:
+          description: Successfully saved configuration parameters
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Config'
+        400:
+          description: Bad request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: BAD_REQUEST
+                message: configuration management via UI/API disabled by admin
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+      x-codegen-request-body-name: body
+  /admin/config.yaml:
+    get:
+      tags:
+      - Admin
+      summary: Get full config (YAML)
+      description: "Retrieve the DBLab configuration in YAML format. All sensitive values are masked.
+        This method allows seeing the entire configuration file and can be helpful for
+        reviewing configuration and setting up workflows to automate DBLab provisioning
+        and configuration."
+      operationId: getConfigYaml
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: "Returned configuration (YAML)"
+          content:
+            application/yaml:
+              schema:
+                $ref: '#/components/schemas/Config'
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /admin/test-db-source:
+    post:
+      tags:
+      - Admin
+      summary: Test source database
+      operationId: testDBConnection1
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Connection DB object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Connection'
+        required: true
+      responses:
+        200:
+          description: Successful operation
+          content: {}
+        400:
+          description: Bad request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: BAD_REQUEST
+                message: configuration management via UI/API disabled by admin
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+      x-codegen-request-body-name: body
+  /admin/ws-auth:
+    post:
+      tags:
+      - Admin
+      summary: Test source database
+      operationId: testDBConnection2
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/WSToken'
+        400:
+          description: Bad request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: BAD_REQUEST
+                message: configuration management via UI/API disabled by admin
+        401:
+          description: Unauthorized access
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Instance'
+              example:
+                code: "UNAUTHORIZED"
+                message: "Check your verification token."
+  /observation/start:
+    post:
+      tags:
+      - Observation
+      summary: Start observing
+      description: "[EXPERIMENTAL] Start an observation session for the specified clone.
+        Observation sessions help detect dangerous (long-lasting, exclusive) locks in CI/CD pipelines.
+        One of common scenarios is using observation sessions to test schema changes (DB migrations)."
+      operationId: startObservation
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Start observation object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/StartObservationRequest'
+        required: true
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ObservationSession'
+        404:
+          description: Not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+              example:
+                code: NOT_FOUND
+                message: Requested object does not exist. Specify your request.
+      x-codegen-request-body-name: body
+  /observation/stop:
+    post:
+      tags:
+      - Observation
+      summary: Stop observing
+      description: "[EXPERIMENTAL] Stop the previously started observation session."
+      operationId: stopObservation
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      requestBody:
+        description: Stop observation object
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/StopObservationRequest'
+        required: true
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ObservationSession'
+        400:
+          description: Bad request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+      x-codegen-request-body-name: body
+  /observation/summary/{clone_id}/{session_id}:
+    get:
+      tags:
+      - Observation
+      summary: Get observation summary
+      description: "[EXPERIMENTAL] Collect the observation summary info."
+      operationId: summaryObservation
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: clone_id
+        in: path
+        description: Clone ID
+        required: true
+        schema:
+          type: string
+      - name: session_id
+        in: path
+        description: Session ID
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ObservationSummaryArtifact'
+        400:
+          description: Bad request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /observation/download:
+    get:
+      tags:
+      - Observation
+      summary: Download an observation artifact
+      description: "[EXPERIMENTAL] Download an artifact for the specified clone and observation session."
+      operationId: downloadObservationArtifact
+      parameters:
+      - name: Verification-Token
+        in: header
+        required: true
+        schema:
+          type: string
+      - name: artifact_type
+        in: query
+        description: Type of the requested artifact
+        required: true
+        schema:
+          type: string
+      - name: clone_id
+        in: query
+        description: Clone ID
+        required: true
+        schema:
+          type: string
+      - name: session_id
+        in: query
+        description: Session ID
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Successful operation
+          content: {}
+        400:
+          description: Bad request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+
+components:
+  schemas:
+    Instance:
+      type: object
+      properties:
+        status:
+          $ref: '#/components/schemas/Status'
+        engine:
+          $ref: '#/components/schemas/Engine'
+        pools:
+          type: array
+          items:
+            $ref: '#/components/schemas/PoolEntry'
+        cloning:
+          $ref: '#/components/schemas/Cloning'
+        retrieving:
+          $ref: '#/components/schemas/Retrieving'
+        provisioner:
+          $ref: '#/components/schemas/Provisioner'
+        synchronization:
+          $ref: '#/components/schemas/Synchronization'
+    Status:
+      required:
+      - code
+      - message
+      type: object
+      properties:
+        code:
+          type: string
+          description: Status code
+        message:
+          type: string
+          description: Status description
+    Engine:
+      type: object
+      properties:
+        version:
+          type: string
+        edition:
+          type: string
+        billingActive:
+          type: string
+        instanceID:
+          type: string
+        startedAt:
+          type: string
+          format: date-time
+        telemetry:
+          type: boolean
+        disableConfigModification:
+          type: boolean
+    PoolEntry:
+      type: object
+      properties:
+        name:
+          type: string
+        mode:
+          type: string
+        dataStateAt:
+          type: string
+          format: date-time
+        status:
+          type: string
+        cloneList:
+          type: array
+          items:
+            type: string
+        fileSystem:
+          $ref: '#/components/schemas/FileSystem'
+    FileSystem:
+      type: object
+      properties:
+        mode:
+          type: string
+        free:
+          type: integer
+          format: int64
+        size:
+          type: integer
+          format: int64
+        used:
+          type: integer
+          format: int64
+        dataSize:
+          type: integer
+          format: int64
+        usedBySnapshots:
+          type: integer
+          format: int64
+        usedByClones:
+          type: integer
+          format: int64
+        compressRatio:
+          type: integer
+          format: float64
+    Cloning:
+      type: object
+      properties:
+        expectedCloningTime:
+          type: integer
+          format: float64
+        numClones:
+          type: integer
+          format: int64
+        clones:
+          type: array
+          items:
+            $ref: '#/components/schemas/Clone'
+    Retrieving:
+      type: object
+      properties:
+        mode:
+          type: string
+        status:
+          type: string
+        lastRefresh:
+          type: string
+          format: date-time
+        nextRefresh:
+          type: string
+          format: date-time
+        alerts:
+          type: array
+          items:
+            type: string
+        activity:
+          $ref: '#/components/schemas/Activity'
+    Activity:
+      type: object
+      properties:
+        source:
+          type: array
+          items:
+            $ref: '#/components/schemas/PGActivityEvent'
+        target:
+          type: array
+          items:
+            $ref: '#/components/schemas/PGActivityEvent'
+    PGActivityEvent:
+      type: object
+      properties:
+        user:
+          type: string
+        query:
+          type: string
+        duration:
+          type: number
+        waitEventType:
+          type: string
+        waitEvent:
+          type: string
+    Provisioner:
+      type: object
+      properties:
+        dockerImage:
+          type: string
+        containerConfig:
+          type: object
+          properties: {}
+    Synchronization:
+      type: object
+      properties:
+        status:
+          $ref: '#/components/schemas/Status'
+        startedAt:
+          type: string
+          format: date-time
+        lastReplayedLsn:
+          type: string
+        lastReplayedLsnAt:
+          type: string
+          format: date-time
+        replicationLag:
+          type: string
+        replicationUptime:
+          type: integer
+    Snapshot:
+      type: object
+      properties:
+        id:
+          type: string
+        createdAt:
+          type: string
+          format: date-time
+        dataStateAt:
+          type: string
+          format: date-time
+        physicalSize:
+          type: integer
+          format: int64
+        logicalSize:
+          type: integer
+          format: int64
+        pool:
+          type: string
+        numClones:
+          type: integer
+          format: int
+    Database:
+      type: object
+      properties:
+        connStr:
+          type: string
+        host:
+          type: string
+        port:
+          type: string
+        username:
+          type: string
+        password:
+          type: string
+    Clone:
+      type: object
+      properties:
+        id:
+          type: string
+        name:
+          type: string
+        snapshot:
+          $ref: '#/components/schemas/Snapshot'
+        protected:
+          type: boolean
+          default: false
+        deleteAt:
+          type: string
+          format: date-time
+        createdAt:
+          type: string
+          format: date-time
+        status:
+          $ref: '#/components/schemas/Status'
+        db:
+          $ref: '#/components/schemas/Database'
+        metadata:
+          $ref: '#/components/schemas/CloneMetadata'
+    CloneMetadata:
+      type: object
+      properties:
+        cloneDiffSize:
+          type: integer
+          format: int64
+        logicalSize:
+          type: integer
+          format: int64
+        cloningTime:
+          type: integer
+          format: float64
+        maxIdleMinutes:
+          type: integer
+          format: int64
+    CreateClone:
+      type: object
+      properties:
+        id:
+          type: string
+        snapshot:
+          type: object
+          properties:
+            id:
+              type: string
+        branch:
+          type: string
+        protected:
+          type: boolean
+          default:
+        db:
+          type: object
+          properties:
+            username:
+              type: string
+            password:
+              type: string
+            restricted:
+              type: boolean
+              default:
+            db_name:
+              type: string
+    ResetClone:
+      type: object
+      properties:
+        snapshotID:
+          type: string
+        latest:
+          type: boolean
+          default: false
+      description: "Define what snapshot needs to be used when resetting the clone.
+       'snapshotID' allows specifying the exact snapshot, while 'latest' allows using 
+       the latest snapshot among all available snapshots. The latter method can be 
+       helpful when the exact snapshot ID is not known."
+    UpdateClone:
+      type: object
+      properties:
+        protected:
+          type: boolean
+          default: false
+    StartObservationRequest:
+      type: object
+      properties:
+        clone_id:
+          type: string
+        config:
+          $ref: '#/components/schemas/ObservationConfig'
+        tags:
+          type: object
+          properties: {}
+        db_name:
+          type: string
+    ObservationConfig:
+      type: object
+      properties:
+        observation_interval:
+          type: integer
+          format: int64
+        max_lock_duration:
+          type: integer
+          format: int64
+        max_duration:
+          type: integer
+          format: int64
+    ObservationSession:
+      type: object
+      properties:
+        session_id:
+          type: integer
+          format: int64
+        started_at:
+          type: string
+          format: date-time
+        finished_at:
+          type: string
+          format: date-time
+        config:
+          $ref: '#/components/schemas/ObservationConfig'
+        tags:
+          type: object
+          properties: {}
+        artifacts:
+          type: array
+          items:
+            type: string
+        result:
+          $ref: '#/components/schemas/ObservationResult'
+    ObservationResult:
+      type: object
+      properties:
+        status:
+          type: string
+        intervals:
+          type: array
+          items:
+            $ref: '#/components/schemas/ObservationInterval'
+        summary:
+          $ref: '#/components/schemas/ObservationSummary'
+    ObservationInterval:
+      type: object
+      properties:
+        started_at:
+          type: string
+          format: date-time
+        duration:
+          type: integer
+          format: int64
+        warning:
+          type: string
+    ObservationSummary:
+      type: object
+      properties:
+        total_duration:
+          type: integer
+          format: float64
+        total_intervals:
+          type: integer
+          format: int
+        warning_intervals:
+          type: integer
+          format: int
+        checklist:
+          $ref: '#/components/schemas/ObservationChecklist'
+    ObservationChecklist:
+      type: object
+      properties:
+        overall_success:
+          type: boolean
+        session_duration_acceptable:
+          type: boolean
+        no_long_dangerous_locks:
+          type: boolean
+    StopObservationRequest:
+      type: object
+      properties:
+        clone_id:
+          type: string
+        overall_error:
+          type: boolean
+    SummaryObservationRequest:
+      type: object
+      properties:
+        clone_id:
+          type: string
+        session_id:
+          type: string
+    ObservationSummaryArtifact:
+      type: object
+      properties:
+        session_id:
+          type: integer
+          format: int64
+        clone_id:
+          type: string
+        duration:
+          type: object
+          properties: {}
+        db_size:
+          type: object
+          properties: {}
+        locks:
+          type: object
+          properties: {}
+        log_errors:
+          type: object
+          properties: {}
+        artifact_types:
+          type: array
+          items:
+            type: string
+    Error:
+      type: object
+      properties:
+        code:
+          type: string
+        message:
+          type: string
+        detail:
+          type: string
+        hint:
+          type: string
+    ResponseStatus:
+      type: object
+      properties:
+        status:
+          type: string
+        message:
+          type: string
+    Config:
+      type: object
+    Connection:
+      type: object
+      properties:
+        host:
+          type: string
+        port:
+          type: string
+        dbname:
+          type: string
+        username:
+          type: string
+        password:
+          type: string
+        db_list:
+          type: array
+          items:
+            type: string
+    WSToken:
+      type: object
+      properties:
+        token:
+          type: string
+          description: WebSocket token
+    Branch:
+      type: object
+      properties:
+        name:
+          type: string
+        parent:
+          type: string
+        dataStateAt:
+          type: string
+          format: date-time
+        snapshotID:
+          type: string
+    SnapshotDetails:
+      type: object
+      properties:
+        id:
+          type: string
+        parent:
+          type: string
+        child:
+          type: string
+        branch:
+          type: array
+          items:
+            type: string
+        root:
+          type: string
+        dataStateAt:
+          type: string
+          format: date-time
+        message:
+          type: string
+    FullRefresh:
+      type: object
+      properties:
+        status:
+          type: string
+          example: OK
+        message:
+          type: string
+          example: Full refresh started
diff --git a/engine/api/swagger-spec/dblab_server_swagger.yaml b/engine/api/swagger-spec/dblab_server_swagger.yaml
index cf93cead..8d44307a 100644
--- a/engine/api/swagger-spec/dblab_server_swagger.yaml
+++ b/engine/api/swagger-spec/dblab_server_swagger.yaml
@@ -21,8 +21,8 @@ externalDocs:
   url: https://gitlab.com/postgres-ai/docs/tree/master/docs/database-lab
 
 servers:
-  - url: "https://demo.aws.postgres.ai/api"
-    description: "DBLab 3.x demo server; token: 'demo-token'"
+  - url: "https://demo.dblab.dev/api"
+    description: "DBLab demo server; token: 'demo-token'"
     x-examples:
       Verification-Token: "demo-token"
   - url: "{scheme}://{host}:{port}/{basePath}"
diff --git a/engine/api/swagger-ui/swagger-initializer.js b/engine/api/swagger-ui/swagger-initializer.js
index 03966101..c5e40fbe 100644
--- a/engine/api/swagger-ui/swagger-initializer.js
+++ b/engine/api/swagger-ui/swagger-initializer.js
@@ -3,7 +3,7 @@ window.onload = function() {
 
   // the following lines will be replaced by docker/configurator, when it runs in a docker-container
   window.ui = SwaggerUIBundle({
-    url: "api/swagger-spec/dblab_server_swagger.yaml",
+    url: "api/swagger-spec/dblab_openapi.yaml",
     dom_id: '#swagger-ui',
     deepLinking: true,
     presets: [
diff --git a/engine/cmd/cli/commands/branch/actions.go b/engine/cmd/cli/commands/branch/actions.go
new file mode 100644
index 00000000..6aa71232
--- /dev/null
+++ b/engine/cmd/cli/commands/branch/actions.go
@@ -0,0 +1,342 @@
+/*
+2022 © Postgres.ai
+*/
+
+// Package branch provides commands to manage DLE branches.
+package branch
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+	"text/template"
+	"time"
+
+	"github.com/urfave/cli/v2"
+
+	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
+	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands/config"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+)
+
+const (
+	defaultBranch = "main"
+
+	snapshotTemplate = `{{range .}}snapshot {{.ID}} {{.Branch | formatBranch}}
+DataStateAt: {{.DataStateAt | formatDSA }}{{if and (ne .Message "-") (ne .Message "")}}
+    {{.Message}}{{end}}
+
+{{end}}`
+)
+
+// Create a new template and parse the letter into it.
+var logTemplate = template.Must(template.New("branchLog").Funcs(
+	template.FuncMap{
+		"formatDSA": func(dsa string) string {
+			p, err := time.Parse(util.DataStateAtFormat, dsa)
+			if err != nil {
+				return ""
+			}
+			return p.Format(time.RFC1123Z)
+		},
+		"formatBranch": func(dsa []string) string {
+			if len(dsa) == 0 {
+				return ""
+			}
+
+			return "(HEAD -> " + strings.Join(dsa, ", ") + ")"
+		},
+	}).Parse(snapshotTemplate))
+
+func switchLocalContext(branchName string) error {
+	dirname, err := config.GetDirname()
+	if err != nil {
+		return err
+	}
+
+	filename := config.BuildFileName(dirname)
+
+	cfg, err := config.Load(filename)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	if len(cfg.Environments) == 0 {
+		return errors.New("no environments found. Use `dblab init` to create a new environment before branching")
+	}
+
+	currentEnv := cfg.Environments[cfg.CurrentEnvironment]
+	currentEnv.Branching.CurrentBranch = branchName
+
+	cfg.Environments[cfg.CurrentEnvironment] = currentEnv
+
+	if err := config.SaveConfig(filename, cfg); err != nil {
+		return commands.ToActionError(err)
+	}
+
+	return err
+}
+
+func list(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	// Create a new branch.
+	if branchName := cliCtx.Args().First(); branchName != "" {
+		return create(cliCtx)
+	}
+
+	// Delete branch.
+	if branchName := cliCtx.String("delete"); branchName != "" {
+		return deleteBranch(cliCtx)
+	}
+
+	// List branches.
+	branches, err := dblabClient.ListBranches(cliCtx.Context)
+	if err != nil {
+		return err
+	}
+
+	if len(branches) == 0 {
+		_, err = fmt.Fprintln(cliCtx.App.Writer, "No branches found")
+		return err
+	}
+
+	formatted := formatBranchList(cliCtx, branches)
+
+	_, err = fmt.Fprint(cliCtx.App.Writer, formatted)
+
+	return err
+}
+
+func formatBranchList(cliCtx *cli.Context, branches []string) string {
+	baseBranch := getBaseBranch(cliCtx)
+
+	s := strings.Builder{}
+
+	for _, branch := range branches {
+		var prefixStar = "  "
+
+		if baseBranch == branch {
+			prefixStar = "* "
+			branch = "\033[1;32m" + branch + "\033[0m"
+		}
+
+		s.WriteString(prefixStar + branch + "\n")
+	}
+
+	return s.String()
+}
+
+func switchBranch(cliCtx *cli.Context) error {
+	branchName := cliCtx.Args().First()
+
+	if branchName == "" {
+		return errors.New("branch name must not be empty")
+	}
+
+	if err := isBranchExist(cliCtx, branchName); err != nil {
+		return fmt.Errorf("cannot confirm if branch exists: %w", err)
+	}
+
+	if err := switchLocalContext(branchName); err != nil {
+		return commands.ToActionError(err)
+	}
+
+	_, err := fmt.Fprintf(cliCtx.App.Writer, "Switched to branch '%s'\n", branchName)
+
+	return err
+}
+
+func isBranchExist(cliCtx *cli.Context, branchName string) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	branches, err := dblabClient.ListBranches(cliCtx.Context)
+	if err != nil {
+		return err
+	}
+
+	for _, branch := range branches {
+		if branch == branchName {
+			return nil
+		}
+	}
+
+	return fmt.Errorf("invalid reference: %s", branchName)
+}
+
+func create(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	branchName := cliCtx.Args().First()
+
+	baseBranch := cliCtx.String("parent-branch")
+	snapshotID := cliCtx.String("snapshot-id")
+
+	if baseBranch != "" && snapshotID != "" {
+		return commands.NewActionError("either --parent-branch or --snapshot-id must be specified")
+	}
+
+	if baseBranch == "" {
+		baseBranch = getBaseBranch(cliCtx)
+	}
+
+	branchRequest := types.BranchCreateRequest{
+		BranchName: branchName,
+		BaseBranch: baseBranch,
+		SnapshotID: snapshotID,
+	}
+
+	branch, err := dblabClient.CreateBranch(cliCtx.Context, branchRequest)
+	if err != nil {
+		return err
+	}
+
+	if err := switchLocalContext(branchName); err != nil {
+		return commands.ToActionError(err)
+	}
+
+	_, err = fmt.Fprintf(cliCtx.App.Writer, "Switched to new branch '%s'\n", branch.Name)
+
+	return err
+}
+
+func getBaseBranch(cliCtx *cli.Context) string {
+	baseBranch := cliCtx.String(commands.CurrentBranch)
+
+	if baseBranch == "" {
+		baseBranch = defaultBranch
+	}
+
+	return baseBranch
+}
+
+func deleteBranch(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	branchName := cliCtx.String("delete")
+
+	branching, err := getBranchingFromEnv()
+	if err != nil {
+		return err
+	}
+
+	if branching.CurrentBranch == branchName {
+		return fmt.Errorf("cannot delete branch %q because it is the current one", branchName)
+	}
+
+	if err = dblabClient.DeleteBranch(cliCtx.Context, types.BranchDeleteRequest{
+		BranchName: branchName,
+	}); err != nil {
+		return err
+	}
+
+	if err := switchLocalContext(defaultBranch); err != nil {
+		return commands.ToActionError(err)
+	}
+
+	_, err = fmt.Fprintf(cliCtx.App.Writer, "Deleted branch '%s'\n", branchName)
+
+	return err
+}
+
+func commit(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	cloneID := cliCtx.String("clone-id")
+	message := cliCtx.String("message")
+
+	snapshotRequest := types.SnapshotCloneCreateRequest{
+		CloneID: cloneID,
+		Message: message,
+	}
+
+	snapshot, err := dblabClient.CreateSnapshotForBranch(cliCtx.Context, snapshotRequest)
+	if err != nil {
+		return err
+	}
+
+	_, err = fmt.Fprintf(cliCtx.App.Writer, "Created new snapshot '%s'\n", snapshot.SnapshotID)
+
+	return err
+}
+
+func history(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	branchName := cliCtx.Args().First()
+
+	if branchName == "" {
+		branchName = getBaseBranch(cliCtx)
+	}
+
+	logRequest := types.LogRequest{BranchName: branchName}
+
+	snapshots, err := dblabClient.BranchLog(cliCtx.Context, logRequest)
+	if err != nil {
+		return err
+	}
+
+	formattedLog, err := formatSnapshotLog(snapshots)
+	if err != nil {
+		return err
+	}
+
+	_, err = fmt.Fprint(cliCtx.App.Writer, formattedLog)
+
+	return err
+}
+
+func getBranchingFromEnv() (config.Branching, error) {
+	branching := config.Branching{}
+
+	dirname, err := config.GetDirname()
+	if err != nil {
+		return branching, err
+	}
+
+	filename := config.BuildFileName(dirname)
+
+	cfg, err := config.Load(filename)
+	if err != nil && !os.IsNotExist(err) {
+		return branching, err
+	}
+
+	if len(cfg.Environments) == 0 {
+		return branching, errors.New("no environments found. Use `dblab init` to create a new environment before branching")
+	}
+
+	branching = cfg.Environments[cfg.CurrentEnvironment].Branching
+
+	return branching, nil
+}
+
+func formatSnapshotLog(snapshots []models.SnapshotDetails) (string, error) {
+	sb := &strings.Builder{}
+
+	if err := logTemplate.Execute(sb, snapshots); err != nil {
+		return "", fmt.Errorf("executing template: %w", err)
+	}
+
+	return sb.String(), nil
+}
diff --git a/engine/cmd/cli/commands/branch/command_list.go b/engine/cmd/cli/commands/branch/command_list.go
new file mode 100644
index 00000000..90087824
--- /dev/null
+++ b/engine/cmd/cli/commands/branch/command_list.go
@@ -0,0 +1,62 @@
+/*
+2020 © Postgres.ai
+*/
+
+package branch
+
+import (
+	"github.com/urfave/cli/v2"
+)
+
+// List provides commands for getting started.
+func List() []*cli.Command {
+	return []*cli.Command{
+		{
+			Name:   "branch",
+			Usage:  "list, create, or delete branches",
+			Action: list,
+			Flags: []cli.Flag{
+				&cli.StringFlag{
+					Name:    "delete",
+					Aliases: []string{"d"},
+				},
+				&cli.StringFlag{
+					Name:  "parent-branch",
+					Usage: "specify branch name as starting point for new branch; cannot be used together with --snapshot-id",
+				},
+				&cli.StringFlag{
+					Name:  "snapshot-id",
+					Usage: "specify snapshot ID is starting point for new branch; cannot be used together with --parent-branch",
+				},
+			},
+			ArgsUsage: "BRANCH_NAME",
+		},
+		{
+			Name:   "switch",
+			Usage:  "switch to a specified branch",
+			Action: switchBranch,
+		},
+		{
+			Name:   "commit",
+			Usage:  "create a new snapshot containing the current state of data and the given log message describing the changes",
+			Action: commit,
+			Flags: []cli.Flag{
+				&cli.StringFlag{
+					Name:  "clone-id",
+					Usage: "clone ID",
+				},
+				&cli.StringFlag{
+					Name:    "message",
+					Usage:   "use the given message as the commit message",
+					Aliases: []string{"m"},
+				},
+			},
+		},
+		{
+			Name:      "log",
+			Usage:     "shows the snapshot logs",
+			Action:    history,
+			ArgsUsage: "BRANCH_NAME",
+		},
+	}
+}
diff --git a/engine/cmd/cli/commands/client.go b/engine/cmd/cli/commands/client.go
index cde42073..d4e45f2d 100644
--- a/engine/cmd/cli/commands/client.go
+++ b/engine/cmd/cli/commands/client.go
@@ -24,6 +24,7 @@ const (
 	FwLocalPortKey    = "forwarding-local-port"
 	IdentityFileKey   = "identity-file"
 	TZKey             = "tz"
+	CurrentBranch     = "current-branch"
 )
 
 // ClientByCLIContext creates a new Database Lab API client.
diff --git a/engine/cmd/cli/commands/clone/actions.go b/engine/cmd/cli/commands/clone/actions.go
index 6946470d..3eca7e3f 100644
--- a/engine/cmd/cli/commands/clone/actions.go
+++ b/engine/cmd/cli/commands/clone/actions.go
@@ -105,6 +105,7 @@ func create(cliCtx *cli.Context) error {
 			Restricted: cliCtx.Bool("restricted"),
 			DBName:     cliCtx.String("db-name"),
 		},
+		Branch: cliCtx.String("branch"),
 	}
 
 	if cliCtx.IsSet("snapshot-id") {
@@ -125,6 +126,11 @@ func create(cliCtx *cli.Context) error {
 		return err
 	}
 
+	if clone.Branch != "" {
+		_, err = fmt.Fprintln(cliCtx.App.Writer, buildCloneOutput(clone))
+		return err
+	}
+
 	viewClone, err := convertCloneView(clone)
 	if err != nil {
 		return err
@@ -140,6 +146,37 @@ func create(cliCtx *cli.Context) error {
 	return err
 }
 
+func buildCloneOutput(clone *models.Clone) string {
+	const (
+		outputAlign      = 2
+		id               = "ID"
+		branch           = "Branch"
+		snapshot         = "Snapshot"
+		connectionString = "Connection string"
+		maxNameLen       = len(connectionString)
+	)
+
+	s := strings.Builder{}
+
+	s.WriteString(id + ":" + strings.Repeat(" ", maxNameLen-len(id)+outputAlign))
+	s.WriteString(clone.ID)
+	s.WriteString("\n")
+
+	s.WriteString(branch + ":" + strings.Repeat(" ", maxNameLen-len(branch)+outputAlign))
+	s.WriteString(clone.Branch)
+	s.WriteString("\n")
+
+	s.WriteString(snapshot + ":" + strings.Repeat(" ", maxNameLen-len(snapshot)+outputAlign))
+	s.WriteString(clone.Snapshot.ID)
+	s.WriteString("\n")
+
+	s.WriteString(connectionString + ":" + strings.Repeat(" ", maxNameLen-len(connectionString)+outputAlign))
+	s.WriteString(clone.DB.ConnStr)
+	s.WriteString("\n")
+
+	return s.String()
+}
+
 // update runs a request to update an existing clone.
 func update(cliCtx *cli.Context) error {
 	dblabClient, err := commands.ClientByCLIContext(cliCtx)
diff --git a/engine/cmd/cli/commands/clone/command_list.go b/engine/cmd/cli/commands/clone/command_list.go
index 44dc35fd..15cd8953 100644
--- a/engine/cmd/cli/commands/clone/command_list.go
+++ b/engine/cmd/cli/commands/clone/command_list.go
@@ -19,7 +19,7 @@ const (
 func CommandList() []*cli.Command {
 	return []*cli.Command{{
 		Name:  "clone",
-		Usage: "manages clones",
+		Usage: "create, update, delete, reset, or retrieve clone",
 		Subcommands: []*cli.Command{
 			{
 				Name:   "list",
@@ -64,6 +64,10 @@ func CommandList() []*cli.Command {
 						Name:  "snapshot-id",
 						Usage: "snapshot ID (optional)",
 					},
+					&cli.StringFlag{
+						Name:  "branch",
+						Usage: "branch name (optional)",
+					},
 					&cli.BoolFlag{
 						Name:    "protected",
 						Usage:   "mark instance as protected from deletion",
diff --git a/engine/cmd/cli/commands/config/environment.go b/engine/cmd/cli/commands/config/environment.go
index 4e6146e6..0130a604 100644
--- a/engine/cmd/cli/commands/config/environment.go
+++ b/engine/cmd/cli/commands/config/environment.go
@@ -11,6 +11,9 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
 )
 
+// DefaultBranch defines the name of data branch.
+const DefaultBranch = "main"
+
 // CLIConfig defines a format of CLI configuration.
 type CLIConfig struct {
 	CurrentEnvironment string                 `yaml:"current_environment" json:"current_environment"`
@@ -26,6 +29,7 @@ type Environment struct {
 	Insecure       bool       `yaml:"insecure" json:"insecure"`
 	RequestTimeout Duration   `yaml:"request_timeout,omitempty" json:"request_timeout,omitempty"`
 	Forwarding     Forwarding `yaml:"forwarding" json:"forwarding"`
+	Branching      Branching  `yaml:"branching" json:"branching"`
 }
 
 // Forwarding defines configuration for port forwarding.
@@ -40,6 +44,11 @@ type Settings struct {
 	TZ string `yaml:"tz" json:"tz"`
 }
 
+// Branching defines branching context.
+type Branching struct {
+	CurrentBranch string `yaml:"current_branch" json:"current_branch"`
+}
+
 // AddEnvironmentToConfig adds a new environment to CLIConfig.
 func AddEnvironmentToConfig(c *cli.Context, cfg *CLIConfig, environmentID string) error {
 	if environmentID == "" {
@@ -60,6 +69,13 @@ func AddEnvironmentToConfig(c *cli.Context, cfg *CLIConfig, environmentID string
 			LocalPort:    c.String(commands.FwLocalPortKey),
 			IdentityFile: c.String(commands.IdentityFileKey),
 		},
+		Branching: Branching{
+			CurrentBranch: c.String(commands.CurrentBranch),
+		},
+	}
+
+	if env.Branching.CurrentBranch == "" {
+		env.Branching.CurrentBranch = DefaultBranch
 	}
 
 	if cfg.Environments == nil {
@@ -117,6 +133,10 @@ func updateEnvironmentInConfig(c *cli.Context, cfg *CLIConfig, environmentID str
 		newEnvironment.Forwarding.IdentityFile = c.String(commands.IdentityFileKey)
 	}
 
+	if c.IsSet(commands.CurrentBranch) {
+		newEnvironment.Branching.CurrentBranch = c.String(commands.CurrentBranch)
+	}
+
 	if newEnvironment == environment {
 		return errors.New("config unchanged. Set different option values to update.") // nolint
 	}
diff --git a/engine/cmd/cli/commands/config/file.go b/engine/cmd/cli/commands/config/file.go
index 0b04e0cc..67ffbc53 100644
--- a/engine/cmd/cli/commands/config/file.go
+++ b/engine/cmd/cli/commands/config/file.go
@@ -8,6 +8,7 @@ import (
 	"os"
 	"os/user"
 	"path"
+	"path/filepath"
 
 	"gopkg.in/yaml.v2"
 )
@@ -16,6 +17,12 @@ const (
 	dblabDir       = ".dblab"
 	configPath     = "cli"
 	configFilename = "cli.yml"
+	envs           = "envs"
+)
+
+const (
+	branches  = "branches"
+	snapshots = "snapshots"
 )
 
 // GetDirname returns the CLI config path located in the current user's home directory.
@@ -40,19 +47,35 @@ func GetFilename() (string, error) {
 	return BuildFileName(dirname), nil
 }
 
+// BuildBranchPath builds a path to the branch dir.
+func BuildBranchPath(dirname string) string {
+	return filepath.Join(dirname, envs, branches)
+}
+
+// BuildSnapshotPath builds a path to the snapshot dir.
+func BuildSnapshotPath(dirname string) string {
+	return filepath.Join(dirname, envs, snapshots)
+}
+
 // BuildFileName builds a config filename.
 func BuildFileName(dirname string) string {
 	return path.Join(dirname, configFilename)
 }
 
+// BuildEnvsDirName builds envs directory name.
+func BuildEnvsDirName(dirname string) string {
+	return path.Join(dirname, envs)
+}
+
 // Load loads a CLI config by a provided filename.
 func Load(filename string) (*CLIConfig, error) {
+	cfg := &CLIConfig{}
+
 	configData, err := os.ReadFile(filename)
 	if err != nil {
-		return nil, err
+		return cfg, err
 	}
 
-	cfg := &CLIConfig{}
 	if err := yaml.Unmarshal(configData, cfg); err != nil {
 		return nil, err
 	}
diff --git a/engine/cmd/cli/commands/global/actions.go b/engine/cmd/cli/commands/global/actions.go
index 35fe83a5..1de794fa 100644
--- a/engine/cmd/cli/commands/global/actions.go
+++ b/engine/cmd/cli/commands/global/actions.go
@@ -10,7 +10,6 @@ import (
 	"net/url"
 	"os"
 
-	"github.com/pkg/errors"
 	"github.com/urfave/cli/v2"
 
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
@@ -25,7 +24,7 @@ func initCLI(c *cli.Context) error {
 	}
 
 	if err := os.MkdirAll(dirname, 0755); err != nil {
-		return errors.Wrapf(err, "Cannot create config directory %s", dirname)
+		return fmt.Errorf("cannot create config directory %s: %w", dirname, err)
 	}
 
 	filename := config.BuildFileName(dirname)
diff --git a/engine/cmd/cli/commands/global/command_list.go b/engine/cmd/cli/commands/global/command_list.go
index f36fafa7..c665684e 100644
--- a/engine/cmd/cli/commands/global/command_list.go
+++ b/engine/cmd/cli/commands/global/command_list.go
@@ -58,7 +58,7 @@ func List() []*cli.Command {
 		},
 		{
 			Name:   "port-forward",
-			Usage:  "start port forwarding to the Database Lab instance",
+			Usage:  "start port forwarding to the DBLab instance",
 			Before: commands.CheckForwardingServerURL,
 			Action: forward,
 		},
diff --git a/engine/cmd/cli/commands/instance/actions.go b/engine/cmd/cli/commands/instance/actions.go
index ab0689d0..c4bafb65 100644
--- a/engine/cmd/cli/commands/instance/actions.go
+++ b/engine/cmd/cli/commands/instance/actions.go
@@ -66,3 +66,20 @@ func health(cliCtx *cli.Context) error {
 
 	return err
 }
+
+// refresh runs a request to initiate a full refresh.
+func refresh(cliCtx *cli.Context) error {
+	client, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	response, err := client.FullRefresh(cliCtx.Context)
+	if err != nil {
+		return err
+	}
+
+	_, err = fmt.Fprintln(cliCtx.App.Writer, response.Message)
+
+	return err
+}
diff --git a/engine/cmd/cli/commands/instance/command_list.go b/engine/cmd/cli/commands/instance/command_list.go
index 164a46c4..07d9ec8e 100644
--- a/engine/cmd/cli/commands/instance/command_list.go
+++ b/engine/cmd/cli/commands/instance/command_list.go
@@ -13,7 +13,7 @@ func CommandList() []*cli.Command {
 	return []*cli.Command{
 		{
 			Name:  "instance",
-			Usage: "displays instance info",
+			Usage: "display instance info",
 			Subcommands: []*cli.Command{
 				{
 					Name:   "status",
@@ -25,6 +25,11 @@ func CommandList() []*cli.Command {
 					Usage:  "display instance's version",
 					Action: health,
 				},
+				{
+					Name:   "full-refresh",
+					Usage:  "initiate full refresh",
+					Action: refresh,
+				},
 			},
 		},
 	}
diff --git a/engine/cmd/cli/commands/snapshot/actions.go b/engine/cmd/cli/commands/snapshot/actions.go
index 0ac175a5..1f4c7dd0 100644
--- a/engine/cmd/cli/commands/snapshot/actions.go
+++ b/engine/cmd/cli/commands/snapshot/actions.go
@@ -7,11 +7,14 @@ package snapshot
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 
 	"github.com/urfave/cli/v2"
 
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 )
 
@@ -44,3 +47,83 @@ func list(cliCtx *cli.Context) error {
 
 	return err
 }
+
+// create runs a request to create a new snapshot.
+func create(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	cloneID := cliCtx.String("clone-id")
+
+	var commandResponse []byte
+
+	if cloneID != "" {
+		commandResponse, err = createFromClone(cliCtx, dblabClient)
+	} else {
+		commandResponse, err = createOnPool(cliCtx, dblabClient)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	_, err = fmt.Fprintln(cliCtx.App.Writer, string(commandResponse))
+
+	return err
+}
+
+// createOnPool runs a request to create a new snapshot.
+func createOnPool(cliCtx *cli.Context, client *dblabapi.Client) ([]byte, error) {
+	snapshotRequest := types.SnapshotCreateRequest{
+		PoolName: cliCtx.String("pool"),
+	}
+
+	snapshot, err := client.CreateSnapshot(cliCtx.Context, snapshotRequest)
+	if err != nil {
+		return nil, err
+	}
+
+	return json.MarshalIndent(snapshot, "", "    ")
+}
+
+// createFromClone runs a request to create a new snapshot from clone.
+func createFromClone(cliCtx *cli.Context, client *dblabapi.Client) ([]byte, error) {
+	cloneID := cliCtx.String("clone-id")
+	message := cliCtx.String("message")
+
+	snapshotRequest := types.SnapshotCloneCreateRequest{
+		CloneID: cloneID,
+		Message: message,
+	}
+
+	snapshot, err := client.CreateSnapshotFromClone(cliCtx.Context, snapshotRequest)
+	if err != nil {
+		return nil, err
+	}
+
+	return json.MarshalIndent(snapshot, "", "    ")
+}
+
+// deleteSnapshot runs a request to delete existing snapshot.
+func deleteSnapshot(cliCtx *cli.Context) error {
+	dblabClient, err := commands.ClientByCLIContext(cliCtx)
+	if err != nil {
+		return err
+	}
+
+	snapshotID := cliCtx.Args().First()
+
+	snapshotRequest := types.SnapshotDestroyRequest{
+		SnapshotID: snapshotID,
+	}
+
+	if err := dblabClient.DeleteSnapshot(cliCtx.Context, snapshotRequest); err != nil {
+		return errors.Unwrap(err)
+	}
+
+	_, err = fmt.Fprintf(cliCtx.App.Writer, "Deleted snapshot '%s'\n", snapshotID)
+
+	return err
+}
diff --git a/engine/cmd/cli/commands/snapshot/command_list.go b/engine/cmd/cli/commands/snapshot/command_list.go
index 3fd6e3cb..bda2b865 100644
--- a/engine/cmd/cli/commands/snapshot/command_list.go
+++ b/engine/cmd/cli/commands/snapshot/command_list.go
@@ -6,6 +6,8 @@ package snapshot
 
 import (
 	"github.com/urfave/cli/v2"
+
+	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
 )
 
 // CommandList returns available commands for a snapshot management.
@@ -13,14 +15,48 @@ func CommandList() []*cli.Command {
 	return []*cli.Command{
 		{
 			Name:  "snapshot",
-			Usage: "manage snapshots",
+			Usage: "create, retrieve, or delete snapshot",
 			Subcommands: []*cli.Command{
 				{
 					Name:   "list",
 					Usage:  "list all existing snapshots",
 					Action: list,
 				},
+				{
+					Name:   "create",
+					Usage:  "create a snapshot",
+					Action: create,
+					Flags: []cli.Flag{
+						&cli.StringFlag{
+							Name:  "pool",
+							Usage: "pool name",
+						},
+						&cli.StringFlag{
+							Name:  "clone-id",
+							Usage: "create a snapshot from existing clone",
+						},
+						&cli.StringFlag{
+							Name:  "message",
+							Usage: "optional message for new snapshot created from existing clone",
+						},
+					},
+				},
+				{
+					Name:      "delete",
+					Usage:     "delete existing snapshot",
+					Action:    deleteSnapshot,
+					ArgsUsage: "SNAPSHOT_ID",
+					Before:    checkSnapshotIDBefore,
+				},
 			},
 		},
 	}
 }
+
+func checkSnapshotIDBefore(c *cli.Context) error {
+	if c.NArg() == 0 {
+		return commands.NewActionError("SNAPSHOT_ID argument is required")
+	}
+
+	return nil
+}
diff --git a/engine/cmd/cli/main.go b/engine/cmd/cli/main.go
index 5bd6c4ed..41ca8789 100644
--- a/engine/cmd/cli/main.go
+++ b/engine/cmd/cli/main.go
@@ -10,6 +10,7 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
+	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands/branch"
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands/clone"
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands/config"
 	"gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands/global"
@@ -31,6 +32,9 @@ func main() {
 			// Config commands.
 			global.List(),
 
+			// Branching.
+			branch.List(),
+
 			// Database Lab API.
 			clone.CommandList(),
 			instance.CommandList(),
@@ -81,6 +85,11 @@ func main() {
 				Usage:   "run in debug mode",
 				EnvVars: []string{"DBLAB_CLI_DEBUG"},
 			},
+			&cli.StringFlag{
+				Name:    "current-branch",
+				Usage:   "current branch",
+				EnvVars: []string{"DBLAB_CLI_CURRENT_BRANCH"},
+			},
 		},
 		EnableBashCompletion: true,
 	}
@@ -158,6 +167,16 @@ func loadEnvironmentParams(c *cli.Context) error {
 				return err
 			}
 		}
+
+		currentBranch := config.DefaultBranch
+
+		if env.Branching.CurrentBranch != "" {
+			currentBranch = env.Branching.CurrentBranch
+		}
+
+		if err := c.Set(commands.CurrentBranch, currentBranch); err != nil {
+			return err
+		}
 	}
 
 	return nil
diff --git a/engine/cmd/cli/templates/help.go b/engine/cmd/cli/templates/help.go
index ce0d5ecc..fe515397 100644
--- a/engine/cmd/cli/templates/help.go
+++ b/engine/cmd/cli/templates/help.go
@@ -65,7 +65,7 @@ OPTIONS:
 `
 
 // SupportProjectTemplate contains the text for support the Database Lab project.
-const SupportProjectTemplate = `Please support the project giving a GitLab star: https://gitlab.com/postgres-ai/database-lab/
-To discuss Database Lab, join our Slack:  https://database-lab-team-slack-invite.herokuapp.com/
+const SupportProjectTemplate = `Please support the project giving a GitHub star: https://github.com/postgres-ai/database-lab-engine
+To discuss DBLab, join our Slack:  https://slack.postgres.ai/
 
 `
diff --git a/engine/cmd/database-lab/main.go b/engine/cmd/database-lab/main.go
index bd90ef52..edce91b7 100644
--- a/engine/cmd/database-lab/main.go
+++ b/engine/cmd/database-lab/main.go
@@ -37,6 +37,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/srv"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/srv/ws"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/webhooks"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
@@ -57,7 +58,7 @@ func main() {
 	}
 
 	logFilter := log.GetFilter()
-	logFilter.ReloadLogRegExp([]string{cfg.Server.VerificationToken, cfg.Platform.AccessToken, cfg.Platform.OrgKey})
+	logFilter.ReloadLogRegExp(maskedSecrets(cfg))
 
 	config.ApplyGlobals(cfg)
 
@@ -112,6 +113,11 @@ func main() {
 
 	tm := telemetry.New(platformSvc, engProps.InstanceID)
 
+	webhookChan := make(chan webhooks.EventTyper, 1)
+	whs := webhooks.NewService(&cfg.Webhooks, webhookChan)
+
+	go whs.Run(ctx)
+
 	pm := pool.NewPoolManager(&cfg.PoolManager, runner)
 	if err = pm.ReloadPools(); err != nil {
 		log.Err(err.Error())
@@ -147,7 +153,7 @@ func main() {
 		shutdownDatabaseLabEngine(context.Background(), docker, &cfg.Global.Database, engProps.InstanceID, pm.First())
 	}
 
-	cloningSvc := cloning.NewBase(&cfg.Cloning, provisioner, tm, observingChan)
+	cloningSvc := cloning.NewBase(&cfg.Cloning, &cfg.Global, provisioner, tm, observingChan, webhookChan)
 	if err = cloningSvc.Run(ctx); err != nil {
 		log.Err(err)
 		emergencyShutdown()
@@ -178,11 +184,12 @@ func main() {
 			server,
 			logCleaner,
 			logFilter,
+			whs,
 		)
 	}
 
 	server := srv.NewServer(&cfg.Server, &cfg.Global, &engProps, docker, cloningSvc, provisioner, retrievalSvc, platformSvc,
-		billingSvc, obs, pm, tm, tokenHolder, logFilter, embeddedUI, reloadConfigFn)
+		billingSvc, obs, pm, tm, tokenHolder, logFilter, embeddedUI, reloadConfigFn, webhookChan)
 
 	server.InitHandlers()
 
@@ -195,7 +202,7 @@ func main() {
 	if cfg.EmbeddedUI.Enabled {
 		go func() {
 			if err := embeddedUI.Run(ctx); err != nil {
-				log.Err("Failed to start embedded UI container:", err.Error())
+				log.Err("failed to start embedded UI container:", err.Error())
 				return
 			}
 		}()
@@ -230,19 +237,19 @@ func main() {
 	go setReloadListener(ctx, engProps, provisioner, billingSvc,
 		retrievalSvc, pm, cloningSvc, platformSvc,
 		embeddedUI, server,
-		logCleaner, logFilter)
+		logCleaner, logFilter, whs)
 
 	go billingSvc.CollectUsage(ctx, systemMetrics)
 
 	if err := retrievalSvc.Run(ctx); err != nil {
-		log.Err("Failed to run the data retrieval service:", err)
+		log.Err("failed to run data retrieval service:", err)
 		log.Msg(contactSupport)
 	}
 
 	defer retrievalSvc.Stop()
 
 	if err := logCleaner.ScheduleLogCleanupJob(cfg.Diagnostic); err != nil {
-		log.Err("Failed to schedule a cleanup job of the diagnostic logs collector", err)
+		log.Err("failed to schedule cleanup job of diagnostic logs collector", err)
 	}
 
 	<-shutdownCh
@@ -312,13 +319,14 @@ func getEngineProperties(ctx context.Context, docker *client.Client, cfg *config
 
 func reloadConfig(ctx context.Context, engProp global.EngineProps, provisionSvc *provision.Provisioner, billingSvc *billing.Billing,
 	retrievalSvc *retrieval.Retrieval, pm *pool.Manager, cloningSvc *cloning.Base, platformSvc *platform.Service,
-	embeddedUI *embeddedui.UIManager, server *srv.Server, cleaner *diagnostic.Cleaner, filtering *log.Filtering) error {
+	embeddedUI *embeddedui.UIManager, server *srv.Server, cleaner *diagnostic.Cleaner, filtering *log.Filtering,
+	whs *webhooks.Service) error {
 	cfg, err := config.LoadConfiguration()
 	if err != nil {
 		return err
 	}
 
-	filtering.ReloadLogRegExp([]string{cfg.Server.VerificationToken, cfg.Platform.AccessToken, cfg.Platform.OrgKey})
+	filtering.ReloadLogRegExp(maskedSecrets(cfg))
 	config.ApplyGlobals(cfg)
 
 	if err := provision.IsValidConfig(cfg.Provision); err != nil {
@@ -354,17 +362,19 @@ func reloadConfig(ctx context.Context, engProp global.EngineProps, provisionSvc
 
 	provisionSvc.Reload(cfg.Provision, dbCfg)
 	retrievalSvc.Reload(ctx, newRetrievalConfig)
-	cloningSvc.Reload(cfg.Cloning)
+	cloningSvc.Reload(cfg.Cloning, cfg.Global)
 	platformSvc.Reload(newPlatformSvc)
 	billingSvc.Reload(newPlatformSvc.Client)
 	server.Reload(cfg.Server)
+	whs.Reload(&cfg.Webhooks)
 
 	return nil
 }
 
 func setReloadListener(ctx context.Context, engProp global.EngineProps, provisionSvc *provision.Provisioner, billingSvc *billing.Billing,
 	retrievalSvc *retrieval.Retrieval, pm *pool.Manager, cloningSvc *cloning.Base, platformSvc *platform.Service,
-	embeddedUI *embeddedui.UIManager, server *srv.Server, cleaner *diagnostic.Cleaner, logFilter *log.Filtering) {
+	embeddedUI *embeddedui.UIManager, server *srv.Server, cleaner *diagnostic.Cleaner, logFilter *log.Filtering,
+	whs *webhooks.Service) {
 	reloadCh := make(chan os.Signal, 1)
 	signal.Notify(reloadCh, syscall.SIGHUP)
 
@@ -376,8 +386,8 @@ func setReloadListener(ctx context.Context, engProp global.EngineProps, provisio
 			pm, cloningSvc,
 			platformSvc,
 			embeddedUI, server,
-			cleaner, logFilter); err != nil {
-			log.Err("Failed to reload configuration:", err)
+			cleaner, logFilter, whs); err != nil {
+			log.Err("failed to reload configuration:", err)
 
 			continue
 		}
@@ -397,11 +407,11 @@ func shutdownDatabaseLabEngine(ctx context.Context, docker *client.Client, dbCfg
 	log.Msg("Stopping auxiliary containers")
 
 	if err := cont.StopControlContainers(ctx, docker, dbCfg, instanceID, fsm); err != nil {
-		log.Err("Failed to stop control containers", err)
+		log.Err("failed to stop control containers", err)
 	}
 
 	if err := cont.CleanUpSatelliteContainers(ctx, docker, instanceID); err != nil {
-		log.Err("Failed to stop satellite containers", err)
+		log.Err("failed to stop satellite containers", err)
 	}
 
 	log.Msg("Auxiliary containers have been stopped")
@@ -412,3 +422,19 @@ func removeObservingClones(obsCh chan string, obs *observer.Observer) {
 		obs.RemoveObservingClone(cloneID)
 	}
 }
+
+func maskedSecrets(cfg *config.Config) []string {
+	maskedSecrets := []string{
+		cfg.Server.VerificationToken,
+		cfg.Platform.AccessToken,
+		cfg.Platform.OrgKey,
+	}
+
+	for _, webhookCfg := range cfg.Webhooks.Hooks {
+		if webhookCfg.Secret != "" {
+			maskedSecrets = append(maskedSecrets, webhookCfg.Secret)
+		}
+	}
+
+	return maskedSecrets
+}
diff --git a/engine/cmd/runci/main.go b/engine/cmd/runci/main.go
index 60af0beb..47905644 100644
--- a/engine/cmd/runci/main.go
+++ b/engine/cmd/runci/main.go
@@ -32,7 +32,7 @@ func main() {
 
 	cfg, err := runci.LoadConfiguration()
 	if err != nil {
-		log.Errf("Failed to load config: %v", err)
+		log.Errf("failed to load config: %v", err)
 		return
 	}
 
@@ -40,7 +40,7 @@ func main() {
 	log.Dbg("Config loaded: ", cfg)
 
 	if cfg.App.VerificationToken == "" {
-		log.Err("DB Migration Checker is insecure since the Verification Token is empty")
+		log.Err("migration checker is insecure since verification token is empty")
 		return
 	}
 
diff --git a/engine/configs/config.example.logical_generic.yml b/engine/configs/config.example.logical_generic.yml
index e08d1c28..2ba2c6ac 100644
--- a/engine/configs/config.example.logical_generic.yml
+++ b/engine/configs/config.example.logical_generic.yml
@@ -1,412 +1,191 @@
-# Copy the following to: ~/.dblab/engine/configs/server.yml
-
-# Database Lab API server. This API is used to work with clones
-# (list them, create, delete, see how to connect to a clone).
-# Normally, it is supposed to listen 127.0.0.1:2345 (default),
-# and to be running inside a Docker container,
-# with port mapping, to allow users to connect from outside
-# to 2345 port using private or public IP address of the machine
-# where the container is running. See https://postgres.ai/docs/database-lab/how-to-manage-database-lab
+# Copy this configuration to: ~/.dblab/engine/configs/server.yml
+# Configuration reference guide: https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference
 server:
-  # The main token that is used to work with Database Lab API.
-  # Note, that only one token is supported.
-  # However, if the integration with Postgres.ai Platform is configured
-  # (see below, "platform: ..." configuration), then users may use
-  # their personal tokens generated on the Platform. In this case,
-  # it is recommended to keep "verificationToken" secret, known
-  # only to the administrator of the Database Lab instance.
-  #
-  # Database Lab Engine can be running with an empty verification token, which is not recommended.
-  # In this case, the DLE API and the UI application will not require any credentials.
-  verificationToken: "secret_token"
-
-
-  # HTTP server port. Default: 2345.
-  port: 2345
-
-  # Disable modifying configuration via UI/API. Default: false.
-  disableConfigModification: false
+  verificationToken: "secret_token" # Primary auth token; can be empty (not recommended); for multi-user mode, use DBLab EE
+  port: 2345 # API server port; default: "2345"
+  disableConfigModification: false # When true, configuration changes via API/CLI/UI are disabled; default: "false"
 
-# Embedded UI. Controls the application to provide a user interface to DLE API.
 embeddedUI:
-  enabled: true
-
-  # Docker image of the UI application.
-  dockerImage: "postgresai/ce-ui:latest"
-
-  # Host or IP address, from which the embedded UI container accepts HTTP connections.
-  # By default, use a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  host: "127.0.0.1"
-
-  # HTTP port of the UI application. Default: 2346.
-  port: 2346
+  enabled: true # If enabled, a separate UI container will be started
+  dockerImage: "postgresai/ce-ui:latest" # Default: "postgresai/ce-ui:latest"
+  host: "127.0.0.1" # Default: "127.0.0.1" (accepts only local connections)
+  port: 2346 # UI port; default: "2346"
 
 global:
-  # Database engine. Currently, the only supported option: "postgres".
-  engine: postgres
-
-  # Debugging, when enabled, allows seeing more in the Database Lab logs
-  # (not PostgreSQL logs). Enable in the case of troubleshooting.
-  debug: true
-
-  # Contains default configuration options of the restored database.
-  database:
-    # Default database username that will be used for Postgres management connections.
-    # This user must exist.
-    username: postgres
-
-    # Default database name.
-    dbname: postgres
-
-# Manages filesystem pools (in the case of ZFS) or volume groups.
-poolManager:
-  # The full path which contains the pool mount directories. mountDir can contain multiple pool directories.
-  mountDir: /var/lib/dblab
-
-  # Subdir where PGDATA located relative to the pool mount directory.
-  # This directory must already exist before launching Database Lab instance. It may be empty if
-  # data initialization is configured (see below).
-  # Note, it is a relative path. Default: "data".
-  # For example, for the PostgreSQL data directory "/var/lib/dblab/dblab_pool/data" (`dblab_pool` is a pool mount directory) set:
-  #      mountDir:  /var/lib/dblab
-  #      dataSubDir:  data
-  # In this case, we assume that the mount point is: /var/lib/dblab/dblab_pool
-  dataSubDir: data
-
-  # Directory that will be used to mount clones. Subdirectories in this directory
-  # will be used as mount points for clones. Subdirectory names will
-  # correspond to ports. E.g., subdirectory "dblab_clone_6000" for the clone running on port 6000.
-  clonesMountSubDir: clones
-
-  # Unix domain socket directory used to establish local connections to cloned databases.
-  socketSubDir: sockets
-
-  # Directory that will be used to store observability artifacts. The directory will be created inside PGDATA.
-  observerSubDir: observer
-
-  # Snapshots with this suffix are considered preliminary. They are not supposed to be accessible to end-users.
-  preSnapshotSuffix: "_pre"
-
-  # Force selection of a working pool inside the `mountDir`.
-  # It is an empty string by default which means that the standard selection and rotation mechanism will be applied.
-  selectedPool: ""
-
-# Configure database containers
-databaseContainer: &db_container
-  # Database Lab provisions thin clones using Docker containers and uses auxiliary containers.
-  # We need to specify which Postgres Docker image is to be used for that.
-  # The default is the extended Postgres image built on top of the official Postgres image
-  # (See https://postgres.ai/docs/database-lab/supported_databases).
-  #
-  # For paid customers, Postgres.AI maintains a set of images compatible with
-  # RDS and RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS.
-  #
-  # IMPORTANT: Postgres version of this image should match the source's Postgres version.
-  # For logical mode, it is a recommendation. For physical mode, it is a *requirement*.
-  dockerImage: "postgresai/extended-postgres:16-0.5.0"
-
-  # Container parameters, see
-  # https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
-  containerConfig:
-    "shm-size": 1gb # default is 64mb, which is often not enough
-
-# Adjust database configuration
-databaseConfigs: &db_configs
+  engine: postgres # Default: "postgres" (only Postgres is currently supported)
+  debug: true # When true, more detailed logs are written to the server log
+  database: # DB credentials used for management connections
+    username: postgres # DB user, default: "postgres" (user must exist)
+    dbname: postgres # DB name, default: "postgres" (DB must exist)
+
+poolManager: # Manages filesystem pools (ZFS) or volume groups (LVM)
+  mountDir: /var/lib/dblab # Pool mount directory; can contain multiple pools; default: "/var/lib/dblab"
+  dataSubDir: data  # The "golden copy" data directory location, relative to mountDir; must exist; default: "data"
+                    # Example: for "/var/lib/dblab/dblab_pool/data" set mountDir: "/var/lib/dblab" and dataSubDir: "data" (assuming mount point is "/var/lib/dblab/dblab_pool")
+  clonesMountSubDir: clones # Where clones are mounted, relative to mountDir; default: "clones"
+                            # Example: for "/var/lib/dblab/dblab_pool/clones" set mountDir: "/var/lib/dblab" and clonesMountSubDir: "clones" (assuming mount point is "/var/lib/dblab/dblab_pool"), resulting path for a clone running on port 6000: "/var/lib/dblab/dblab_pool/clones/6000"
+  socketSubDir: sockets # Where sockets are located, relative to mountDir; default: "sockets"
+  observerSubDir: observer # Where observability artifacts are located, relative to clone's data directory; default: "observer"
+  preSnapshotSuffix: "_pre" # Suffix for preliminary snapshots; default: "_pre"
+  selectedPool: "" # Force selection of working pool inside mountDir; default: "" (standard selection and rotation mechanism will be applied)
+
+databaseContainer: &db_container  # Docker config for all DB containers
+                                  # See https://postgres.ai/docs/database-lab/supported_databases
+                                  # DBLab SE and EE customers get images compatible with RDS, RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS
+  dockerImage: "postgresai/extended-postgres:17-0.5.3" # Postgres image; major version (17) must match source if physical mode
+  containerConfig: # Custom container config; see https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
+    "shm-size": 1gb # Shared memory size; increase if "could not resize shared memory segment" errors occur
+
+databaseConfigs: &db_configs # Postgres config for all DB containers
   configs:
-    # In order to match production plans with Database Lab plans set parameters related to Query Planning as on production.
-    shared_buffers: 1GB
-    # shared_preload_libraries – copy the value from the source
-    # Adding shared preload libraries, make sure that there are "pg_stat_statements, auto_explain, logerrors" in the list.
-    # They are needed for query analysis and DB migration testing.
-    # Note, if you are using PostgreSQL 9.6 and older, remove the logerrors extension from the list since it is not supported.
-    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors"
-    # work_mem and all the Query Planning parameters – copy the values from the source.
-    # Detailed guide: https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
-    work_mem: "100MB"
-    # The maximum amount of memory to be used by maintenance operations, such as VACUUM, CREATE INDEX, and ALTER TABLE ADD FOREIGN KEY. 
-    maintenance_work_mem: "500MB"
+    shared_buffers: 1GB # Postgres buffer pool size; large values can lead to OOM
+    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors" # Shared libraries; copy from source
+    maintenance_work_mem: "500MB" # Maximum memory for maintenance operations (VACUUM, CREATE INDEX, etc.)
+    work_mem: "100MB" # This and Query Planning parameters should be copied from source; see https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
     # ... put Query Planning parameters here
 
-# Details of provisioning – where data is located,
-# thin cloning method, etc.
-provision:
+provision: # Defines how data is provisioned
   <<: *db_container
-  # Pool of ports for Postgres clones. Ports will be allocated sequentially,
-  # starting from the lowest value. The "from" value must be less than or equal to "to".
-  portPool:
-    from: 6000
-    to: 6099
-
-  # Use sudo for ZFS/LVM and Docker commands if Database Lab server running
-  # outside a container. Keep it "false" (default) when running in a container.
-  useSudo: false
-
-  # Avoid default password resetting in clones and have the ability for
-  # existing users to log in with old passwords.
-  keepUserPasswords: false
-
-  # IP addresses that can be used to access clones.
-  # By default, using a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  # The option supports multiple IPs (using comma-separated format) and IPv6 addresses (for example, [::1])
-  cloneAccessAddresses: "127.0.0.1"
-
-# Data retrieval flow. This section defines both initial retrieval, and rules
-# to keep the data directory in a synchronized state with the source. Both are optional:
-# you may already have the data directory, so neither initial retrieval nor
-# synchronization are needed.
-# 
-# Data retrieval can be also considered as "thick" cloning. Once it's done, users
-# can use "thin" cloning to get independent full-size clones of the database in
-# seconds, for testing and development. Normally, retrieval (thick cloning) is
-# a slow operation (1 TiB/h is a good speed). Optionally, the process of keeping
-# the Database Lab data directory in sync with the source (being continuously
-# updated) can be configured.
-#
-# There are two basic ways to organize data retrieval:
-#  - "logical":  use dump/restore processes, obtaining a logical copy of the initial
-#                database (a sequence  of SQL commands), and then loading it to
-#                the target Database Lab data directory. This is the only option
-#                for managed cloud PostgreSQL services such as Amazon RDS. Physically,
-#                the copy of the database created using this method differs from
-#                the original one (data blocks are stored differently). However,
-#                row counts are the same, as well as internal database statistics,
-#                allowing to do various kinds of development and testing, including
-#                running EXPLAIN command to optimize SQL queries.
-#  - "physical": physically copy the data directory from the source (or from the
-#                archive if a physical backup tool such as WAL-G, pgBackRest, or Barman
-#                is used). This approach allows having a copy of the original database
-#                which is physically identical, including the existing bloat, data
-#                blocks location. Not supported for managed cloud Postgres services
-#                such as Amazon RDS.
-retrieval:
-  # Make full data refresh on the schedule defined here. The process requires at least one additional filesystem mount point.
+  portPool: # Range of ports for Postgres clones; ports will be allocated sequentially, starting from the lowest value
+    from: 6000 # First port in the range
+    to: 6099 # Last port in the range
+  useSudo: false # Use sudo for ZFS/LVM and Docker commands if DBLab server running outside a container (not recommended)
+  keepUserPasswords: false # Keep user passwords in clones; default: "false"
+  cloneAccessAddresses: "127.0.0.1" # IP addresses that can be used to access clones; supports multiple IPs and IPv6; default: "127.0.0.1" (loop-back)
+
+retrieval:  # Data retrieval: initial sync and ongoing updates. Two methods:
+            #   - logical: dump/restore (works with RDS, different physical layout)
+            #   - physical: direct copy (identical layout, not for RDS) e.g. using pg_basebackup, WAL-G, or pgBackRest
   refresh:
-    # Timetable is to be defined in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-    timetable: "0 0 * * 1"
-
-    # Skip data refresh while the retrieval starts.
-    skipStartRefresh: false
-
-  # The jobs section must not contain physical and logical restore jobs simultaneously.
-  jobs:
+    timetable: "0 0 * * 1" # Full data refresh schedule in crontab format; see https://en.wikipedia.org/wiki/Cron#Overview
+    skipStartRefresh: false # Skip data refresh while the retrieval starts
+  jobs: # Jobs to run; must not contain physical and logical restore jobs simultaneously
     - logicalDump
     - logicalRestore
     - logicalSnapshot
-
   spec:
-    # Dumps PostgreSQL database from provided source.
-    logicalDump:
+    logicalDump: # Dumps PostgreSQL database from provided source
       options:
         <<: *db_container
-        # The dump file will be automatically created on this location and then used to restore.
-        # Ensure that there is enough disk space.
-        # If you specify dumpLocation outside the mountDir, add this location as a volume to the DLE container.
-        dumpLocation: "/var/lib/dblab/dblab_pool/dump"
-
-        # Source of data.
+        dumpLocation: "/var/lib/dblab/dblab_pool/dump" # Dump file location; ensure enough disk space
+        
         source:
-          # Source types: "local", "remote", "rdsIam"
-          type: remote
-
-          # Connection parameters of the database to be dumped.
-          connection:
-            # Database connection parameters.
-            # Currently, only password can be specified via environment variable (PGPASSWORD),
-            # everything else needs to be specified here.
+          type: remote # Source types: "local", "remote", "rdsIam"
+          connection: # Database connection parameters; use PGPASSWORD env var for password
             dbname: postgres
             host: 34.56.78.90
             port: 5432
             username: postgres
+            password: postgres # Use PGPASSWORD env var instead (higher priority)
 
-            # Connection password. The environment variable PGPASSWORD can be used instead of this option.
-            # The environment variable has a higher priority.
-            password: postgres
-
-        # Option for specifying the database list that must be copied.
-        # By default, DLE dumps and restores all available databases.
-        # Do not specify the databases section to take all databases.
-        databases:
+        databases: # List of databases to dump; leave empty to dump all databases
         #   database1:
-        # Options for a partial dump.
-        # Do not specify the tables section to dump all available tables.
-        # Corresponds to the --table option of pg_dump.
-        #     tables:
+        #     tables: # Partial dump tables; corresponds to --table option of pg_dump
         #       - table1
-        # Do not dump data for any of the tables matching pattern.
-        # Corresponds to the --exclude-table option of pg_dump.
-        #     excludeTables:
+        #     excludeTables: # Exclude tables; corresponds to --exclude-table option of pg_dump
         #       - table2
         #   database2:
         #   databaseN:
 
-        # Use parallel jobs to dump faster.
-        # It’s ignored if “immediateRestore.enabled: true” is present because “pg_dump | pg_restore” is always single-threaded.
-        # If your source database has 4 vCPUs or less, and you don't want to saturate them, use 2 or 1.
-        parallelJobs: 4
-
-        # Ignore errors that occurred during logical data dump. Do not ignore by default.
-        ignoreErrors: false
-
-        # Options for direct restore to Database Lab Engine instance.
-        # Uncomment this if you prefer restoring from the dump on the fly. In this case,
-        # you do not need to use "logicalRestore" job. Keep in mind that unlike "logicalRestore",
-        # this option does not support parallelization, it is always a single-threaded (both for
-        # dumping on the source, and restoring on the destination end).
-        # immediateRestore:
-        #   # Enable immediate restore.
+        parallelJobs: 4 # Parallel jobs for faster dump; ignored if immediateRestore.enabled is true
+        
+        # immediateRestore: # Direct restore to DBLab Engine instance; single-threaded unlike logicalRestore
         #   enabled: true
-        #   # Option to adjust PostgreSQL configuration for a logical dump job.
-        #   # It's useful if a dumped database contains non-standard extensions.
-        #   <<: *db_configs
-        #   # Custom options for pg_restore command.
-        #   customOptions:
+        #   <<: *db_configs # Adjust PostgreSQL configuration for logical dump job
+        #   customOptions: # Custom options for pg_restore command
         #     - "--no-privileges"
         #     - "--no-owner"
         #     - "--exit-on-error"
 
-        # Custom options for pg_dump command.
-        customOptions:
+        customOptions: # Custom options for pg_dump command
         #  - --no-publications
         #  - --no-subscriptions
 
-    # Restores PostgreSQL database from the provided dump. If you use this block, do not use
-    # "restore" option in the "logicalDump" job.
-    logicalRestore:
+    logicalRestore: # Restores PostgreSQL database from dump; don't use with immediateRestore
       options:
         <<: *db_container
-        # The location of the archive files (or directories, for directory-format archives) to be restored.
-        # If you specify dumpLocation outside the mountDir, add this location as a volume to the DLE container.
-        dumpLocation: "/var/lib/dblab/dblab_pool/dump"
-
-        # Use parallel jobs to restore faster.
-        # If your machine with DLE has 4 vCPUs or less, and you don't want to saturate them, use 2 or 1.
-        parallelJobs: 4
-
-        # Ignore errors that occurred during logical data restore. Do not ignore by default.
-        ignoreErrors: false
+        dumpLocation: "/var/lib/dblab/dblab_pool/dump" # Location of archive files to restore
+        parallelJobs: 4 # Parallel jobs for faster restore
+        <<: *db_configs # Adjust PostgreSQL configuration for logical restore job
 
-        # Option to adjust PostgreSQL configuration for a logical restore job
-        # It's useful if a restored database contains non-standard extensions.
-        <<: *db_configs
-
-        # Option for specifying the database list that must be restored.
-        # By default, DLE restores all available databases.
-        # Do not specify the databases section to restore all available databases.
-        # databases:
+        databases: # Database list to restore; comment out to restore all databases
         #   database1:
-        #     # Dump format. Available formats: directory, custom, plain. Default format: directory.
-        #     format: directory
-        #     # Compression (only for plain-text dumps): "gzip", "bzip2", or "no". Default: "no".
-        #     compression: no
-        #     # Option for a partial restore. Do not specify the tables section to restore all available tables.
-        #     tables:
+        #     format: directory # Dump format: directory, custom, plain; default: directory
+        #     compression: no # Compression for plain-text dumps: gzip, bzip2, no; default: no
+        #     tables: # Partial restore tables
         #       - table1
         #       - table2
         #   database2:
         #   databaseN:
 
-        # It is possible to define pre-processing SQL queries. For example, "/tmp/scripts/sql".
-        # Default: empty string (no pre-processing defined).
-        queryPreprocessing:
-          # Path to SQL pre-processing queries. Default: empty string (no pre-processing defined).
-          queryPath: ""
-
-          # Worker limit for parallel queries. Parallelization doesn't work for inline SQL queries.
-          maxParallelWorkers: 2
+        queryPreprocessing: # Pre-processing SQL queries
+          queryPath: "" # Path to SQL pre-processing queries; default: empty (no pre-processing)
+          maxParallelWorkers: 2 # Worker limit for parallel queries; doesn't work for inline SQL
+          inline: "" # Inline SQL; runs after scripts in queryPath
 
-          # Inline SQL. Queries run after scripts placed in 'queryPath'.
-          inline: ""
-
-        # Custom options for pg_restore command.
-        customOptions:
+        customOptions: # Custom options for pg_restore command
           - "--no-tablespaces"
           - "--no-privileges"
           - "--no-owner"
           - "--exit-on-error"
+        
+        skipPolicies: true # Skip policies during restore
 
-        # Option to skip policies during restore.
-        skipPolicies: true
-
-    logicalSnapshot:
+    logicalSnapshot: # Final snapshot configuration
       options:
-        # Adjust PostgreSQL configuration
-        <<: *db_configs
-
-        # It is possible to define a pre-processing script. For example, "/tmp/scripts/custom.sh".
-        # Default: empty string (no pre-processing defined).
-        # This can be used for scrubbing eliminating PII data, to define data masking, etc.
-        preprocessingScript: ""
-
-        # Define pre-processing SQL queries for data patching. For example, "/tmp/scripts/sql".
-        dataPatching:
+        <<: *db_configs # Adjust PostgreSQL configuration
+        preprocessingScript: "" # Pre-processing script for data scrubbing/masking; e.g., "/tmp/scripts/custom.sh"
+        
+        dataPatching: # Pre-processing SQL queries for data patching
           <<: *db_container
           queryPreprocessing:
-            # Path to SQL pre-processing queries. Default: empty string (no pre-processing defined).
-            queryPath: ""
-
-            # Worker limit for parallel queries.
-            maxParallelWorkers: 2
-
-            # Inline SQL. Queries run after scripts placed in 'queryPath'.
-            inline: ""
+            queryPath: "" # Path to SQL pre-processing queries; default: empty
+            maxParallelWorkers: 2 # Worker limit for parallel queries
+            inline: "" # Inline SQL; runs after scripts in queryPath
 
 cloning:
-  # Host that will be specified in database connection info for all clones
-  # Use public IP address if database connections are allowed from outside
-  # This value is only used to inform users about how to connect to database clones
-  accessHost: "localhost"
-
-  # Automatically delete clones after the specified minutes of inactivity.
-  # 0 - disable automatic deletion.
-  # Inactivity means:
-  #   - no active sessions (queries being processed right now)
-  #   - no recently logged queries in the query log
-  maxIdleMinutes: 120
+  accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
+  maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
 
 diagnostic:
-  logsRetentionDays: 7
+  logsRetentionDays: 7 # How many days to keep logs
 
-# ### INTEGRATION ###
+observer: # CI Observer configuration
+#  replacementRules:  # Regexp rules for masking personal data in Postgres logs; applied before sending the logs to the Platform
+#                     # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
+#    "regexp": "replace"
+#    "select \\d+": "***"
+#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+
+webhooks: # Webhooks can be used to trigger actions in external systems upon events such as clone creation
+#  hooks:
+#    - url: ""
+#      secret: "" # (optional) Sent with the request in the `DBLab-Webhook-Token` HTTP header.
+#      trigger:
+#        - clone_create
+#        - clone_reset
 
-# Postgres.ai Platform integration (provides GUI) – extends the open source offering.
-# Uncomment the following lines if you need GUI, personal tokens, audit logs, more.
-#
 platform:
-  # Platform API URL. To work with Postgres.ai SaaS, keep it default
-  # ("https://postgres.ai/api/general").
-  url: "https://postgres.ai/api/general"
-  # Telemetry: anonymous statistics sent to Postgres.ai.
-  # Used to analyze DLE usage, it helps the DLE maintainers make decisions on product development.
-  # Please leave it enabled if possible – this will contribute to DLE development.
-  # The full list of data points being collected: https://postgres.ai/docs/database-lab/telemetry
+  url: "https://postgres.ai/api/general" # Default: "https://postgres.ai/api/general"
   enableTelemetry: true
+
+#  ╔══════════════════════════════════════════════════════════════════════════╗
+#  ║                     POSTGRES AI PLATFORM INTEGRATION                     ║
+#  ╠══════════════════════════════════════════════════════════════════════════╣
+#  ║                                                                          ║
+#  ║  - Production-ready UI, AI assistance and support from human experts     ║
+#  ║  - Enterprise-grade user management & role-based access control          ║
+#  ║  - Advanced security: audit trails, SIEM integration, compliance         ║
+#  ║  - Real-time performance monitoring & intelligent recommendations        ║
+#  ║                                                                          ║
+#  ║  Learn more at https://postgres.ai/                                      ║
+#  ║                                                                          ║
+#  ╚══════════════════════════════════════════════════════════════════════════╝
 #
-#  # Project name
-#  projectName: "project_name"
-#
-#  # Organization key
-#  orgKey: "org_key"
-#
-#  # Token for authorization in Platform API. This token can be obtained on
-#  # the Postgres.ai Console: https://postgres.ai/console/YOUR_ORG_NAME/tokens
-#  # This token needs to be kept in secret, known only to the administrator.
-#  accessToken: "platform_access_token"
-#
-#  # Enable authorization with personal tokens of the organization's members.
-#  # If false: all users must use "verificationToken" value for any API request
-#  # If true: "verificationToken" is known only to admin, users use their own tokens,
-#  #          and any token can be revoked not affecting others
-#  enablePersonalTokens: true
+# Uncomment the following lines if you need the Platform integration
 #
-# CI Observer configuration.
-#observer:
-#  # Set up regexp rules for Postgres logs.
-#  # These rules are applied before sending the logs to the Platform, to ensure that personal data is masked properly.
-#  # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
-#  replacementRules:
-#    "regexp": "replace"
-#    "select \\d+": "***"
-#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+#  projectName: "project_name" # Project name
+#  orgKey: "org_key" # Organization key
+#  accessToken: "platform_access_token" # Token for authorization in Platform API; get it at https://postgres.ai/console/YOUR_ORG_NAME/tokens
+#  enablePersonalTokens: true # Enable authorization with personal tokens of the organization's members.
 #
diff --git a/engine/configs/config.example.logical_rds_iam.yml b/engine/configs/config.example.logical_rds_iam.yml
index 507779e4..2742076e 100644
--- a/engine/configs/config.example.logical_rds_iam.yml
+++ b/engine/configs/config.example.logical_rds_iam.yml
@@ -1,412 +1,191 @@
-# Copy the following to: ~/.dblab/engine/configs/server.yml
-
-# Database Lab API server. This API is used to work with clones
-# (list them, create, delete, see how to connect to a clone).
-# Normally, it is supposed to listen 127.0.0.1:2345 (default),
-# and to be running inside a Docker container,
-# with port mapping, to allow users to connect from outside
-# to 2345 port using private or public IP address of the machine
-# where the container is running. See https://postgres.ai/docs/database-lab/how-to-manage-database-lab
+# Copy this configuration to: ~/.dblab/engine/configs/server.yml
+# Configuration reference guide: https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference
 server:
-  # The main token that is used to work with Database Lab API.
-  # Note, that only one token is supported.
-  # However, if the integration with Postgres.ai Platform is configured
-  # (see below, "platform: ..." configuration), then users may use
-  # their personal tokens generated on the Platform. In this case,
-  # it is recommended to keep "verificationToken" secret, known
-  # only to the administrator of the Database Lab instance.
-  #
-  # Database Lab Engine can be running with an empty verification token, which is not recommended.
-  # In this case, the DLE API and the UI application will not require any credentials.
-  verificationToken: "secret_token"
-
-  # HTTP server port. Default: 2345.
-  port: 2345
+  verificationToken: "secret_token" # Primary auth token; can be empty (not recommended); for multi-user mode, use DBLab EE
+  port: 2345 # API server port; default: "2345"
+  disableConfigModification: false # When true, configuration changes via API/CLI/UI are disabled; default: "false"
 
-  # Disable modifying configuration via UI/API. Default: false.
-  disableConfigModification: false
-
-# Embedded UI. Controls the application to provide a user interface to DLE API.
 embeddedUI:
-  enabled: true
-
-  # Docker image of the UI application.
-  dockerImage: "postgresai/ce-ui:latest"
-
-  # Host or IP address, from which the embedded UI container accepts HTTP connections.
-  # By default, use a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  host: "127.0.0.1"
-
-  # HTTP port of the UI application. Default: 2346.
-  port: 2346
+  enabled: true # If enabled, a separate UI container will be started
+  dockerImage: "postgresai/ce-ui:latest" # Default: "postgresai/ce-ui:latest"
+  host: "127.0.0.1" # Default: "127.0.0.1" (accepts only local connections)
+  port: 2346 # UI port; default: "2346"
 
 global:
-  # Database engine. Currently, the only supported option: "postgres".
-  engine: postgres
-
-  # Debugging, when enabled, allows seeing more in the Database Lab logs
-  # (not PostgreSQL logs). Enable in the case of troubleshooting.
-  debug: true
-
-  # Contains default configuration options of the restored database.
-  database:
-    # Default database username that will be used for Postgres management connections.
-    # This user must exist.
-    username: postgres
-
-    # Default database name.
-    dbname: postgres
-
-# Manages filesystem pools (in the case of ZFS) or volume groups.
-poolManager:
-  # The full path which contains the pool mount directories. mountDir can contain multiple pool directories.
-  mountDir: /var/lib/dblab
-
-  # Subdir where PGDATA located relative to the pool mount directory.
-  # This directory must already exist before launching Database Lab instance. It may be empty if
-  # data initialization is configured (see below).
-  # Note, it is a relative path. Default: "data".
-  # For example, for the PostgreSQL data directory "/var/lib/dblab/dblab_pool/data" (`dblab_pool` is a pool mount directory) set:
-  #      mountDir:  /var/lib/dblab
-  #      dataSubDir:  data
-  # In this case, we assume that the mount point is: /var/lib/dblab/dblab_pool
-  dataSubDir: data
-
-  # Directory that will be used to mount clones. Subdirectories in this directory
-  # will be used as mount points for clones. Subdirectory names will
-  # correspond to ports. E.g., subdirectory "dblab_clone_6000" for the clone running on port 6000.
-  clonesMountSubDir: clones
-
-  # Unix domain socket directory used to establish local connections to cloned databases.
-  socketSubDir: sockets
-
-  # Directory that will be used to store observability artifacts. The directory will be created inside PGDATA.
-  observerSubDir: observer
-
-  # Snapshots with this suffix are considered preliminary. They are not supposed to be accessible to end-users.
-  preSnapshotSuffix: "_pre"
-
-  # Force selection of a working pool inside the `mountDir`.
-  # It is an empty string by default which means that the standard selection and rotation mechanism will be applied.
-  selectedPool: ""
-
-# Configure database containers
-databaseContainer: &db_container
-  # Database Lab provisions thin clones using Docker containers and uses auxiliary containers.
-  # We need to specify which Postgres Docker image is to be used for that.
-  # The default is the extended Postgres image built on top of the official Postgres image
-  # (See https://postgres.ai/docs/database-lab/supported_databases).
-  #
-  # For paid customers, Postgres.AI maintains a set of images compatible with
-  # RDS and RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS.
-  #
-  # IMPORTANT: Postgres version of this image should match the source's Postgres version.
-  # For logical mode, it is a recommendation. For physical mode, it is a *requirement*.
-  dockerImage: "postgresai/extended-postgres:16-0.5.0"
-
-  # Custom parameters for containers with PostgreSQL, see
-  # https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
-  containerConfig:
-    "shm-size": 1gb
-
-# Adjust database configuration
-databaseConfigs: &db_configs
+  engine: postgres # Default: "postgres" (only Postgres is currently supported)
+  debug: true # When true, more detailed logs are written to the server log
+  database: # DB credentials used for management connections
+    username: postgres # DB user, default: "postgres" (user must exist)
+    dbname: postgres # DB name, default: "postgres" (DB must exist)
+
+poolManager: # Manages filesystem pools (ZFS) or volume groups (LVM)
+  mountDir: /var/lib/dblab # Pool mount directory; can contain multiple pools; default: "/var/lib/dblab"
+  dataSubDir: data  # The "golden copy" data directory location, relative to mountDir; must exist; default: "data"
+                    # Example: for "/var/lib/dblab/dblab_pool/data" set mountDir: "/var/lib/dblab" and dataSubDir: "data" (assuming mount point is "/var/lib/dblab/dblab_pool")
+  clonesMountSubDir: clones # Where clones are mounted, relative to mountDir; default: "clones"
+                            # Example: for "/var/lib/dblab/dblab_pool/clones" set mountDir: "/var/lib/dblab" and clonesMountSubDir: "clones" (assuming mount point is "/var/lib/dblab/dblab_pool"), resulting path for a clone running on port 6000: "/var/lib/dblab/dblab_pool/clones/6000"
+  socketSubDir: sockets # Where sockets are located, relative to mountDir; default: "sockets"
+  observerSubDir: observer # Where observability artifacts are located, relative to clone's data directory; default: "observer"
+  preSnapshotSuffix: "_pre" # Suffix for preliminary snapshots; default: "_pre"
+  selectedPool: "" # Force selection of working pool inside mountDir; default: "" (standard selection and rotation mechanism will be applied)
+
+databaseContainer: &db_container  # Docker config for all DB containers
+                                  # See https://postgres.ai/docs/database-lab/supported_databases
+                                  # DBLab SE and EE customers get images compatible with RDS, RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS
+  dockerImage: "postgresai/extended-postgres:17-0.5.3" # Postgres image; major version (17) must match source if physical mode
+  containerConfig: # Custom container config; see https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
+    "shm-size": 1gb # Shared memory size; increase if "could not resize shared memory segment" errors occur
+
+databaseConfigs: &db_configs # Postgres config for all DB containers
   configs:
-    # In order to match production plans with Database Lab plans set parameters related to Query Planning as on production.
-    shared_buffers: 1GB
-    # shared_preload_libraries – copy the value from the source
-    # Adding shared preload libraries, make sure that there are "pg_stat_statements, auto_explain, logerrors" in the list.
-    # It is necessary to perform query and db migration analysis.
-    # Note, if you are using PostgreSQL 9.6 and older, remove the logerrors extension from the list since it is not supported.
-    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors"
-    # work_mem and all the Query Planning parameters – copy the values from the source.
-    # Detailed guide: https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
-    work_mem: "100MB"
-    # The maximum amount of memory to be used by maintenance operations, such as VACUUM, CREATE INDEX, and ALTER TABLE ADD FOREIGN KEY. 
-    maintenance_work_mem: "500MB"
+    shared_buffers: 1GB # Postgres buffer pool size; large values can lead to OOM
+    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors" # Shared libraries; copy from source
+    maintenance_work_mem: "500MB" # Maximum memory for maintenance operations (VACUUM, CREATE INDEX, etc.)
+    work_mem: "100MB" # This and Query Planning parameters should be copied from source; see https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
     # ... put Query Planning parameters here
 
-# Details of provisioning – where data is located,
-# thin cloning method, etc.
-provision:
+provision: # Defines how data is provisioned
   <<: *db_container
-  # Pool of ports for Postgres clones. Ports will be allocated sequentially,
-  # starting from the lowest value. The "from" value must be less than or equal to "to".
-  portPool:
-    from: 6000
-    to: 6099
-
-  # Use sudo for ZFS/LVM and Docker commands if Database Lab server running
-  # outside a container. Keep it "false" (default) when running in a container.
-  useSudo: false
-
-  # Avoid default password resetting in clones and have the ability for
-  # existing users to log in with old passwords.
-  keepUserPasswords: false
-
-  # IP addresses that can be used to access clones.
-  # By default, using a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  # The option supports multiple IPs (using comma-separated format) and IPv6 addresses (for example, [::1])
-  cloneAccessAddresses: "127.0.0.1"
-
-# Data retrieval flow. This section defines both initial retrieval, and rules
-# to keep the data directory in a synchronized state with the source. Both are optional:
-# you may already have the data directory, so neither initial retrieval nor
-# synchronization are needed.
-# 
-# Data retrieval can be also considered as "thick" cloning. Once it's done, users
-# can use "thin" cloning to get independent full-size clones of the database in
-# seconds, for testing and development. Normally, retrieval (thick cloning) is
-# a slow operation (1 TiB/h is a good speed). Optionally, the process of keeping
-# the Database Lab data directory in sync with the source (being continuously
-# updated) can be configured.
-#
-# There are two basic ways to organize data retrieval:
-#  - "logical":  use dump/restore processes, obtaining a logical copy of the initial
-#                database (a sequence  of SQL commands), and then loading it to
-#                the target Database Lab data directory. This is the only option
-#                for managed cloud PostgreSQL services such as Amazon RDS. Physically,
-#                the copy of the database created using this method differs from
-#                the original one (data blocks are stored differently). However,
-#                row counts are the same, as well as internal database statistics,
-#                allowing to do various kinds of development and testing, including
-#                running EXPLAIN command to optimize SQL queries.
-#  - "physical": physically copy the data directory from the source (or from the
-#                archive if a physical backup tool such as WAL-G, pgBackRest, or Barman
-#                is used). This approach allows to have a copy of the original database
-#                which is physically identical, including the existing bloat, data
-#                blocks location. Not supported for managed cloud Postgres services
-#                such as Amazon RDS.
-retrieval:
-  # Make full data refresh on the schedule defined here. The process requires at least one additional filesystem mount point.
+  portPool: # Range of ports for Postgres clones; ports will be allocated sequentially, starting from the lowest value
+    from: 6000 # First port in the range
+    to: 6099 # Last port in the range
+  useSudo: false # Use sudo for ZFS/LVM and Docker commands if DBLab server running outside a container (not recommended)
+  keepUserPasswords: false # Keep user passwords in clones; default: "false"
+  cloneAccessAddresses: "127.0.0.1" # IP addresses that can be used to access clones; supports multiple IPs and IPv6; default: "127.0.0.1" (loop-back)
+
+retrieval:  # Data retrieval: initial sync and ongoing updates. Two methods:
+            #   - logical: dump/restore (works with RDS, different physical layout)
+            #   - physical: direct copy (identical layout, not for RDS) e.g. using pg_basebackup, WAL-G, or pgBackRest
   refresh:
-    # Timetable is to be defined in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-    timetable: "0 0 * * 1"
-
-    # Skip data refresh while the retrieval starts.
-    skipStartRefresh: false
-
-  # The jobs section must not contain physical and logical restore jobs simultaneously.
-  jobs:
+    timetable: "0 0 * * 1" # Full data refresh schedule in crontab format; see https://en.wikipedia.org/wiki/Cron#Overview
+    skipStartRefresh: false # Skip data refresh while the retrieval starts
+  jobs: # Jobs to run; must not contain physical and logical restore jobs simultaneously
     - logicalDump
     - logicalRestore
     - logicalSnapshot
-
   spec:
-    # Dumps PostgreSQL database from provided source.
-    logicalDump:
+    logicalDump: # Dumps PostgreSQL database from provided source
       options:
         <<: *db_container
-        # The dump file will be automatically created on this location and then used to restore.
-        # Ensure that there is enough disk space.
-        # If you specify dumpLocation outside the mountDir, add this location as a volume to the DLE container.
-        dumpLocation: "/var/lib/dblab/dblab_pool/dump"
-
-        # Source of data.
+        dumpLocation: "/var/lib/dblab/dblab_pool/dump" # Dump file location; ensure enough disk space
+        
         source:
-          # Source types: "local", "remote", "rdsIam"
-          type: rdsIam
-
-          # RDS database details for pg_dump
-          connection:
+          type: rdsIam # Source types: "local", "remote", "rdsIam"
+          connection: # RDS database connection details for pg_dump
             dbname: test
             username: test_user
+          rdsIam: # RDS IAM authentication configuration
+            awsRegion: us-east-2 # AWS Region where RDS instance is located
+            dbInstanceIdentifier: database-1 # RDS instance identifier
+            sslRootCert: "/cert/rds-combined-ca-bundle.pem" # Path to SSL root certificate; download from https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem
 
-          # Optional definition of RDS data source.
-          rdsIam:
-            # AWS Region.
-            awsRegion: us-east-2
-
-            # RDS instance Identifier.
-            dbInstanceIdentifier: database-1
-
-            # Path to the SSL root certificate: https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem
-            sslRootCert: "/cert/rds-combined-ca-bundle.pem"
-
-        # Option for specifying the database list that must be copied.
-        # By default, DLE dumps and restores all available databases.
-        # Do not specify the databases section to take all databases.
-        databases:
+        databases: # List of databases to dump; leave empty to dump all databases
         #   database1:
-        #     # Option for a partial dump. Do not specify the tables section to dump all available tables.
-        #     tables:
+        #     tables: # Partial dump tables; corresponds to --table option of pg_dump
         #       - table1
         #       - table2
         #   database2:
         #   databaseN:
 
-        # Use parallel jobs to dump faster.
-        # It’s ignored if “immediateRestore.enabled: true” is present because “pg_dump | pg_restore” is always single-threaded.
-        # If your source database has 4 vCPUs or less, and you don't want to saturate them, use 2 or 1.
-        parallelJobs: 4
-
-        # Ignore errors that occurred during logical data dump. Do not ignore by default.
-        ignoreErrors: false
-
-        # Options for direct restore to Database Lab Engine instance.
-        # Uncomment this if you prefer restoring from the dump on the fly. In this case,
-        # you do not need to use "logicalRestore" job. Keep in mind that unlike "logicalRestore",
-        # this option does not support parallelization, it is always a single-threaded (both for
-        # dumping on the source, and restoring on the destination end).
-        # immediateRestore:
-        #   # Enable immediate restore.
+        parallelJobs: 4 # Parallel jobs for faster dump; ignored if immediateRestore.enabled is true
+        
+        # immediateRestore: # Direct restore to DBLab Engine instance; single-threaded unlike logicalRestore
         #   enabled: true
-        #   # Option to adjust PostgreSQL configuration for a logical dump job.
-        #   # It's useful if a dumped database contains non-standard extensions.
-        #   <<: *db_configs
-        #   # Custom options for pg_restore command.
-        #   customOptions:
+        #   <<: *db_configs # Adjust PostgreSQL configuration for logical dump job
+        #   customOptions: # Custom options for pg_restore command
         #     - "--no-privileges"
         #     - "--no-owner"
         #     - "--exit-on-error"
 
-        # Custom options for pg_dump command.
-        customOptions:
-          - "--exclude-schema=rdsdms"
+        customOptions: # Custom options for pg_dump command
+          - "--exclude-schema=rdsdms" # Exclude RDS DMS schema
 
-    # Restores PostgreSQL database from the provided dump. If you use this block, do not use
-    # "restore" option in the "logicalDump" job.
-    logicalRestore:
+    logicalRestore: # Restores PostgreSQL database from dump; don't use with immediateRestore
       options:
         <<: *db_container
-        # The location of the archive file (or directory, for a directory-format archive) to be restored.
-        # If you specify dumpLocation outside the mountDir, add this location as a volume to the DLE container.
-        dumpLocation: "/var/lib/dblab/dblab_pool/dump"
-
-        # Use parallel jobs to restore faster.
-        # If your machine with DLE has 4 vCPUs or less, and you don't want to saturate them, use 2 or 1.
-        parallelJobs: 4
-
-        # Ignore errors that occurred during logical data restore. Do not ignore by default.
-        ignoreErrors: false
+        dumpLocation: "/var/lib/dblab/dblab_pool/dump" # Location of archive files to restore
+        parallelJobs: 4 # Parallel jobs for faster restore
+        <<: *db_configs # Adjust PostgreSQL configuration for logical restore job
 
-        # Option to adjust PostgreSQL configuration for a logical restore job
-        # It's useful if a restored database contains non-standard extensions.
-        <<: *db_configs
-
-        # Option for specifying the database list that must be restored.
-        # By default, DLE restores all available databases.
-        # Do not specify the databases section to restore all available databases.
-        # databases:
+        databases: # List of databases to restore; leave empty to restore all databases
         #   database1:
-        #     # Dump format. Available formats: directory, custom, plain. Default format: directory.
-        #     format: directory
-        #     # Compression (only for plain-text dumps): "gzip", "bzip2", or "no". Default: "no".
-        #     compression: no
-        # Options for a partial dump.
-        # Do not specify the tables section to dump all available tables.
-        # Corresponds to the --table option of pg_dump.
-        #     tables:
+        #     format: directory # Dump format: directory, custom, plain; default: directory
+        #     compression: no # Compression for plain-text dumps: gzip, bzip2, no; default: no
+        #     tables: # Partial restore tables
         #       - table1
-        # Do not dump data for any of the tables matching pattern.
-        # Corresponds to the --exclude-table option of pg_dump.
-        #     excludeTables:
+        #     excludeTables: # Exclude tables; corresponds to --exclude-table option of pg_dump
         #       - table2
         #   database2:
         #   databaseN:
 
-        # It is possible to define pre-processing SQL queries. For example, "/tmp/scripts/sql".
-        # Default: empty string (no pre-processing defined).
-        queryPreprocessing:
-          # Path to SQL pre-processing queries. Default: empty string (no pre-processing defined).
-          queryPath: ""
-
-          # Worker limit for parallel queries. Parallelization doesn't work for inline SQL queries.
-          maxParallelWorkers: 2
+        queryPreprocessing: # Pre-processing SQL queries
+          queryPath: "" # Path to SQL pre-processing queries; default: empty (no pre-processing)
+          maxParallelWorkers: 2 # Worker limit for parallel queries; doesn't work for inline SQL
+          inline: "" # Inline SQL; runs after scripts in queryPath
 
-          # Inline SQL. Queries run after scripts placed in 'queryPath'.
-          inline: ""
-
-        # Custom options for pg_restore command.
-        customOptions:
+        customOptions: # Custom options for pg_restore command
           - "--no-tablespaces"
           - "--no-privileges"
           - "--no-owner"
           - "--exit-on-error"
+        
+        skipPolicies: true # Skip policies during restore
 
-        # Option to skip policies during restore.
-        skipPolicies: true
-
-    logicalSnapshot:
+    logicalSnapshot: # Final snapshot configuration
       options:
-        # Adjust PostgreSQL configuration
-        <<: *db_configs
-
-        # It is possible to define a pre-processing script. For example, "/tmp/scripts/custom.sh".
-        # Default: empty string (no pre-processing defined).
-        # This can be used for scrubbing eliminating PII data, to define data masking, etc.
-        preprocessingScript: ""
-
-        # Define pre-processing SQL queries for data patching. For example, "/tmp/scripts/sql".
-        dataPatching:
+        <<: *db_configs # Adjust PostgreSQL configuration
+        preprocessingScript: "" # Pre-processing script for data scrubbing/masking; e.g., "/tmp/scripts/custom.sh"
+        
+        dataPatching: # Pre-processing SQL queries for data patching
           <<: *db_container
           queryPreprocessing:
-            # Path to SQL pre-processing queries. Default: empty string (no pre-processing defined).
-            queryPath: ""
-
-            # Worker limit for parallel queries.
-            maxParallelWorkers: 2
-
-            # Inline SQL. Queries run after scripts placed in 'queryPath'.
-            inline: ""
+            queryPath: "" # Path to SQL pre-processing queries; default: empty
+            maxParallelWorkers: 2 # Worker limit for parallel queries
+            inline: "" # Inline SQL; runs after scripts in queryPath
 
 cloning:
-  # Host that will be specified in database connection info for all clones
-  # Use public IP address if database connections are allowed from outside
-  # This value is only used to inform users about how to connect to database clones
-  accessHost: "localhost"
-
-  # Automatically delete clones after the specified minutes of inactivity.
-  # 0 - disable automatic deletion.
-  # Inactivity means:
-  #   - no active sessions (queries being processed right now)
-  #   - no recently logged queries in the query log
-  maxIdleMinutes: 120
+  accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
+  maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
 
 diagnostic:
-  logsRetentionDays: 7
+  logsRetentionDays: 7 # How many days to keep logs
 
-# ### INTEGRATION ###
+observer: # CI Observer configuration
+#  replacementRules:  # Regexp rules for masking personal data in Postgres logs; applied before sending the logs to the Platform
+#                     # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
+#    "regexp": "replace"
+#    "select \\d+": "***"
+#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+
+webhooks: # Webhooks can be used to trigger actions in external systems upon events such as clone creation
+#  hooks:
+#    - url: ""
+#      secret: "" # (optional) Sent with the request in the `DBLab-Webhook-Token` HTTP header.
+#      trigger:
+#        - clone_create
+#        - clone_reset
 
-# Postgres.ai Platform integration (provides GUI) – extends the open source offering.
-# Uncomment the following lines if you need GUI, personal tokens, audit logs, more.
-#
 platform:
-  # Platform API URL. To work with Postgres.ai SaaS, keep it default
-  # ("https://postgres.ai/api/general").
-  url: "https://postgres.ai/api/general"
-  # Telemetry: anonymous statistics sent to Postgres.ai.
-  # Used to analyze DLE usage, it helps the DLE maintainers make decisions on product development.
-  # Please leave it enabled if possible – this will contribute to DLE development.
-  # The full list of data points being collected: https://postgres.ai/docs/database-lab/telemetry
+  url: "https://postgres.ai/api/general" # Default: "https://postgres.ai/api/general"
   enableTelemetry: true
+
+#  ╔══════════════════════════════════════════════════════════════════════════╗
+#  ║                     POSTGRES AI PLATFORM INTEGRATION                     ║
+#  ╠══════════════════════════════════════════════════════════════════════════╣
+#  ║                                                                          ║
+#  ║  - Production-ready UI, AI assistance and support from human experts     ║
+#  ║  - Enterprise-grade user management & role-based access control          ║
+#  ║  - Advanced security: audit trails, SIEM integration, compliance         ║
+#  ║  - Real-time performance monitoring & intelligent recommendations        ║
+#  ║                                                                          ║
+#  ║  Learn more at https://postgres.ai/                                      ║
+#  ║                                                                          ║
+#  ╚══════════════════════════════════════════════════════════════════════════╝
 #
-#  # Project name
-#  projectName: "project_name"
-#
-#  # Organization key
-#  orgKey: "org_key"
-#
-#  # Token for authorization in Platform API. This token can be obtained on
-#  # the Postgres.ai Console: https://postgres.ai/console/YOUR_ORG_NAME/tokens
-#  # This token needs to be kept in secret, known only to the administrator.
-#  accessToken: "platform_access_token"
-#
-#  # Enable authorization with personal tokens of the organization's members.
-#  # If false: all users must use "verificationToken" value for any API request
-#  # If true: "verificationToken" is known only to admin, users use their own tokens,
-#  #          and any token can be revoked not affecting others
-#  enablePersonalTokens: true
+# Uncomment the following lines if you need the Platform integration
 #
-# CI Observer configuration.
-#observer:
-#  # Set up regexp rules for Postgres logs.
-#  # These rules are applied before sending the logs to the Platform, to ensure that personal data is masked properly.
-#  # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
-#  replacementRules:
-#    "regexp": "replace"
-#    "select \\d+": "***"
-#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+#  projectName: "project_name" # Project name
+#  orgKey: "org_key" # Organization key
+#  accessToken: "platform_access_token" # Token for authorization in Platform API; get it at https://postgres.ai/console/YOUR_ORG_NAME/tokens
+#  enablePersonalTokens: true # Enable authorization with personal tokens of the organization's members.
 #
diff --git a/engine/configs/config.example.physical_generic.yml b/engine/configs/config.example.physical_generic.yml
index ebf29392..d7a34084 100644
--- a/engine/configs/config.example.physical_generic.yml
+++ b/engine/configs/config.example.physical_generic.yml
@@ -1,359 +1,159 @@
-# Copy the following to: ~/.dblab/engine/configs/server.yml
-
-# Database Lab API server. This API is used to work with clones
-# (list them, create, delete, see how to connect to a clone).
-# Normally, it is supposed to listen 127.0.0.1:2345 (default),
-# and to be running inside a Docker container,
-# with port mapping, to allow users to connect from outside
-# to 2345 port using private or public IP address of the machine
-# where the container is running. See https://postgres.ai/docs/database-lab/how-to-manage-database-lab
+# Copy this configuration to: ~/.dblab/engine/configs/server.yml
+# Configuration reference guide: https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference
 server:
-  # The main token that is used to work with Database Lab API.
-  # Note, that only one token is supported.
-  # However, if the integration with Postgres.ai Platform is configured
-  # (see below, "platform: ..." configuration), then users may use
-  # their personal tokens generated on the Platform. In this case,
-  # it is recommended to keep "verificationToken" secret, known
-  # only to the administrator of the Database Lab instance.
-  #
-  # Database Lab Engine can be running with an empty verification token, which is not recommended.
-  # In this case, the DLE API and the UI application will not require any credentials.
-  verificationToken: "secret_token"
-
-  # HTTP server port. Default: 2345.
-  port: 2345
+  verificationToken: "secret_token" # Primary auth token; can be empty (not recommended); for multi-user mode, use DBLab EE
+  port: 2345 # API server port; default: "2345"
+  disableConfigModification: false # When true, configuration changes via API/CLI/UI are disabled; default: "false"
 
-  # Disable modifying configuration via UI/API. Default: false.
-  disableConfigModification: false
-
-# Embedded UI. Controls the application to provide a user interface to DLE API.
 embeddedUI:
-  enabled: true
-
-  # Docker image of the UI application.
-  dockerImage: "postgresai/ce-ui:latest"
-
-  # Host or IP address, from which the embedded UI container accepts HTTP connections.
-  # By default, use a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  host: "127.0.0.1"
-
-  # HTTP port of the UI application. Default: 2346.
-  port: 2346
+  enabled: true # If enabled, a separate UI container will be started
+  dockerImage: "postgresai/ce-ui:latest" # Default: "postgresai/ce-ui:latest"
+  host: "127.0.0.1" # Default: "127.0.0.1" (accepts only local connections)
+  port: 2346 # UI port; default: "2346"
 
 global:
-  # Database engine. Currently, the only supported option: "postgres".
-  engine: postgres
-
-  # Debugging, when enabled, allows seeing more in the Database Lab logs
-  # (not PostgreSQL logs). Enable in the case of troubleshooting.
-  debug: true
-
-  # Contains default configuration options of the restored database.
-  database:
-    # Default database username that will be used for Postgres management connections.
-    # This user must exist.
-    username: postgres
-
-    # Default database name.
-    dbname: postgres
-
-# Manages filesystem pools (in the case of ZFS) or volume groups.
-poolManager:
-  # The full path which contains the pool mount directories. mountDir can contain multiple pool directories.
-  mountDir: /var/lib/dblab
-
-  # Subdir where PGDATA located relative to the pool mount directory.
-  # This directory must already exist before launching Database Lab instance. It may be empty if
-  # data initialization is configured (see below).
-  # Note, it is a relative path. Default: "data".
-  # For example, for the PostgreSQL data directory "/var/lib/dblab/dblab_pool/data" (`dblab_pool` is a pool mount directory) set:
-  #      mountDir:  /var/lib/dblab
-  #      dataSubDir:  data
-  # In this case, we assume that the mount point is: /var/lib/dblab/dblab_pool
-  dataSubDir: data
-
-  # Directory that will be used to mount clones. Subdirectories in this directory
-  # will be used as mount points for clones. Subdirectory names will
-  # correspond to ports. E.g., subdirectory "dblab_clone_6000" for the clone running on port 6000.
-  clonesMountSubDir: clones
-
-  # Unix domain socket directory used to establish local connections to cloned databases.
-  socketSubDir: sockets
-
-  # Directory that will be used to store observability artifacts. The directory will be created inside PGDATA.
-  observerSubDir: observer
-
-  # Snapshots with this suffix are considered preliminary. They are not supposed to be accessible to end-users.
-  preSnapshotSuffix: "_pre"
-
-  # Force selection of a working pool inside the `mountDir`.
-  # It is an empty string by default which means that the standard selection and rotation mechanism will be applied.
-  selectedPool: ""
-
-# Configure PostgreSQL containers
-databaseContainer: &db_container
-  # Database Lab provisions thin clones using Docker containers and uses auxiliary containers.
-  # We need to specify which Postgres Docker image is to be used for that.
-  # The default is the extended Postgres image built on top of the official Postgres image
-  # (See https://postgres.ai/docs/database-lab/supported_databases).
-  #
-  # For paid customers, Postgres.AI maintains a set of images compatible with
-  # RDS and RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS.
-  #
-  # IMPORTANT: Postgres version of this image should match the source's Postgres version.
-  # For logical mode, it is a recommendation. For physical mode, it is a *requirement*.
-  dockerImage: "postgresai/extended-postgres:16-0.5.0"
-
-  # Custom parameters for containers with PostgreSQL, see
-  # https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
-  containerConfig:
-    "shm-size": 1gb
-
-# Adjust PostgreSQL configuration
-databaseConfigs: &db_configs
+  engine: postgres # Default: "postgres" (only Postgres is currently supported)
+  debug: true # When true, more detailed logs are written to the server log
+  database: # DB credentials used for management connections
+    username: postgres # DB user, default: "postgres" (user must exist)
+    dbname: postgres # DB name, default: "postgres" (DB must exist)
+
+poolManager: # Manages filesystem pools (ZFS) or volume groups (LVM)
+  mountDir: /var/lib/dblab # Pool mount directory; can contain multiple pools; default: "/var/lib/dblab"
+  dataSubDir: data  # The "golden copy" data directory location, relative to mountDir; must exist; default: "data"
+                    # Example: for "/var/lib/dblab/dblab_pool/data" set mountDir: "/var/lib/dblab" and dataSubDir: "data" (assuming mount point is "/var/lib/dblab/dblab_pool")
+  clonesMountSubDir: clones # Where clones are mounted, relative to mountDir; default: "clones"
+                            # Example: for "/var/lib/dblab/dblab_pool/clones" set mountDir: "/var/lib/dblab" and clonesMountSubDir: "clones" (assuming mount point is "/var/lib/dblab/dblab_pool"), resulting path for a clone running on port 6000: "/var/lib/dblab/dblab_pool/clones/6000"
+  socketSubDir: sockets # Where sockets are located, relative to mountDir; default: "sockets"
+  observerSubDir: observer # Where observability artifacts are located, relative to clone's data directory; default: "observer"
+  preSnapshotSuffix: "_pre" # Suffix for preliminary snapshots; default: "_pre"
+  selectedPool: "" # Force selection of working pool inside mountDir; default: "" (standard selection and rotation mechanism will be applied)
+
+databaseContainer: &db_container  # Docker config for all DB containers
+                                  # See https://postgres.ai/docs/database-lab/supported_databases
+                                  # DBLab SE and EE customers get images compatible with RDS, RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS
+  dockerImage: "postgresai/extended-postgres:17-0.5.3" # Postgres image; major version (17) must match source if physical mode
+  containerConfig: # Custom container config; see https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
+    "shm-size": 1gb # Shared memory size; increase if "could not resize shared memory segment" errors occur
+
+databaseConfigs: &db_configs # Postgres config for all DB containers
   configs:
-    # In order to match production plans with Database Lab plans set parameters related to Query Planning as on production.
-    shared_buffers: 1GB
-    # shared_preload_libraries – copy the value from the source
-    # Adding shared preload libraries, make sure that there are "pg_stat_statements, auto_explain, logerrors" in the list.
-    # It is necessary to perform query and db migration analysis.
-    # Note, if you are using PostgreSQL 9.6 and older, remove the logerrors extension from the list since it is not supported.
-    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors"
-    # work_mem and all the Query Planning parameters – copy the values from the source.
-    # Detailed guide: https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
-    work_mem: "100MB"
+    shared_buffers: 1GB # Postgres buffer pool size; large values can lead to OOM
+    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors" # Shared libraries; copy from source
+    maintenance_work_mem: "500MB" # Maximum memory for maintenance operations (VACUUM, CREATE INDEX, etc.)
+    work_mem: "100MB" # This and Query Planning parameters should be copied from source; see https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
     # ... put Query Planning parameters here
 
-# Details of provisioning – where data is located,
-# thin cloning method, etc.
-provision:
+provision: # Defines how data is provisioned
   <<: *db_container
-  # Pool of ports for Postgres clones. Ports will be allocated sequentially,
-  # starting from the lowest value. The "from" value must be less than or equal to "to".
-  portPool:
-    from: 6000
-    to: 6099
-
-  # Use sudo for ZFS/LVM and Docker commands if Database Lab server running
-  # outside a container. Keep it "false" (default) when running in a container.
-  useSudo: false
-
-  # Avoid default password resetting in clones and have the ability for
-  # existing users to log in with old passwords.
-  keepUserPasswords: false
-
-  # IP addresses that can be used to access clones.
-  # By default, using a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  # The option supports multiple IPs (using comma-separated format) and IPv6 addresses (for example, [::1])
-  cloneAccessAddresses: "127.0.0.1"
-
-# Data retrieval flow. This section defines both initial retrieval, and rules
-# to keep the data directory in a synchronized state with the source. Both are optional:
-# you may already have the data directory, so neither initial retrieval nor
-# synchronization are needed.
-# 
-# Data retrieval can be also considered as "thick" cloning. Once it's done, users
-# can use "thin" cloning to get independent full-size clones of the database in
-# seconds, for testing and development. Normally, retrieval (thick cloning) is
-# a slow operation (1 TiB/h is a good speed). Optionally, the process of keeping
-# the Database Lab data directory in sync with the source (being continuously
-# updated) can be configured.
-#
-# There are two basic ways to organize data retrieval:
-#  - "logical":  use dump/restore processes, obtaining a logical copy of the initial
-#                database (a sequence  of SQL commands), and then loading it to
-#                the target Database Lab data directory. This is the only option
-#                for managed cloud PostgreSQL services such as Amazon RDS. Physically,
-#                the copy of the database created using this method differs from
-#                the original one (data blocks are stored differently). However,
-#                row counts are the same, as well as internal database statistics,
-#                allowing to do various kinds of development and testing, including
-#                running EXPLAIN command to optimize SQL queries.
-#  - "physical": physically copy the data directory from the source (or from the
-#                archive if a physical backup tool such as WAL-G, pgBackRest, or Barman
-#                is used). This approach allows to have a copy of the original database
-#                which is physically identical, including the existing bloat, data
-#                blocks location. Not supported for managed cloud Postgres services
-#                such as Amazon RDS.
-retrieval:
-  # The jobs section must not contain physical and logical restore jobs simultaneously.
-  jobs:
+  portPool: # Range of ports for Postgres clones; ports will be allocated sequentially, starting from the lowest value
+    from: 6000 # First port in the range
+    to: 6099 # Last port in the range
+  useSudo: false # Use sudo for ZFS/LVM and Docker commands if DBLab server running outside a container (not recommended)
+  keepUserPasswords: false # Keep user passwords in clones; default: "false"
+  cloneAccessAddresses: "127.0.0.1" # IP addresses that can be used to access clones; supports multiple IPs and IPv6; default: "127.0.0.1" (loop-back)
+
+retrieval:  # Data retrieval: initial sync and ongoing updates. Two methods:
+            #   - logical: dump/restore (works with RDS, different physical layout)
+            #   - physical: direct copy (identical layout, not for RDS) e.g. using pg_basebackup, WAL-G, or pgBackRest
+  jobs: # Jobs to run; must not contain physical and logical restore jobs simultaneously
     - physicalRestore
     - physicalSnapshot
-
   spec:
-    # Restores database data from a physical backup.
-    physicalRestore:
+    physicalRestore: # Restores data directory from a physical copy
       options:
         <<: *db_container
-        # Defines the tool to restore data.
-        tool: customTool
-
-        # Sync instance options.
-        sync:
-          # Enable running of a sync instance.
-          enabled: true
-
-          # Custom health check options for a sync instance container.
+        tool: customTool # Defines the tool to restore data
+        sync: # Additional "sync" container is used to keep the data directory in a synchronized state with the source (in fact, "sync" container is an asyncrhonous replica)
+          enabled: true # Enable running of sync container
           healthCheck:
-            # Health check interval for a sync instance container (in seconds).
-            interval: 5
-
-            # Maximum number of health check retries.
-            maxRetries: 200
-
-          # Add PostgreSQL configuration parameters to the sync container.
-          configs:
-            shared_buffers: 2GB
-
-          # Add PostgreSQL recovery configuration parameters to the sync container.
-          recovery:
-          # Uncomment this only if you are on Postgres version 11 or older.
-          #  standby_mode: on
-          #  recovery_target_timeline: 'latest'
-
-        # Set environment variables here. See https://www.postgresql.org/docs/current/libpq-envars.html
-        envs:
+            interval: 5 # Health check frequency (seconds)
+            maxRetries: 200 # Max retries before giving up
+          configs: # Additional Postgres configuration for sync container
+            shared_buffers: 2GB # Bigger buffer pool helps avoid lagging behind the source
+          recovery: # Legacy recovery.conf options; only for Postgres 11 or older
+            # standby_mode: on
+            # recovery_target_timeline: 'latest'
+        envs: # Environment vars; can be used, for example, to defined connection to source DB. See https://www.postgresql.org/docs/current/libpq-envars.html
           PGUSER: "postgres"
           PGPASSWORD: "postgres"
           PGHOST: "source.hostname"
           PGPORT: 5432
-
         customTool:
-          # To use pg_basebackup, specify environment variables in "envs".
-          # Do not edit PostgreSQL data directory (-D).
-          # Note that command chains are not supported here; if you need to use a more
-          # complicated snippet, create a shell script, use --mount (-v) option
-          # when starting a container with Database Lab and use path to it here.
-          # Write your data to dataDir defined in "global.config"
-          command: "pg_basebackup -X stream -D /var/lib/dblab/dblab_pool/data"
-
-          # PostgreSQL "restore_command" configuration option.
-          restore_command: ""
-
+          command: "pg_basebackup -X stream -D /var/lib/dblab/dblab_pool/data" # For pg_basebackup, DB connection is specified in envs. Do not change option -D. Command chains are not supported here; if need more complex logic, use a script and mount it (-v) to dblab_server container. 
+          restore_command: "" # Standard Postgres option defining how WALs are restored (e.g. from backups)
     physicalSnapshot:
       options:
-        # Skip taking a snapshot while the retrieval starts.
-        skipStartSnapshot: false
-
-        # Adjust PostgreSQL configuration of the snapshot.
-        <<: *db_configs
-
-        # Promote PGDATA after data fetching.
+        skipStartSnapshot: false # Skip taking a snapshot when retrieval starts; default: "false"
+        <<: *db_configs # Additional Postgres configuration for containers participating in physicalSnapshot (promotion, snapshot)
         promotion:
           <<: *db_container
-          # Enable PGDATA promotion.
-          enabled: true
-
-          # Custom health check options for a data promotion container.
+          enabled: true # Enable Postgres promotion to read-write mode before finalizing snapshot
           healthCheck:
-            # Health check interval for a data promotion container (in seconds).
-            interval: 5
-
-            # Maximum number of health check retries.
-            maxRetries: 200
-
-          # It is possible to define pre-processing SQL queries. For example, "/tmp/scripts/sql".
-          # Default: empty string (no pre-processing defined).
-          queryPreprocessing:
-            # Path to SQL pre-processing queries.
-            queryPath: ""
-
-            # Worker limit for parallel queries.
-            maxParallelWorkers: 2
-
-            # Inline SQL. Queries run after scripts placed in 'queryPath'.
-            inline: ""
-
-          # Add PostgreSQL configuration parameters to the promotion container.
-          configs:
+            interval: 5 # Health check interval in seconds
+            maxRetries: 200 # Maximum retry attempts before failing
+          queryPreprocessing: # Data transformation using SQL before promoting to read-write mode
+            queryPath: "" # Directory path containing SQL query files; example: "/tmp/scripts/sql"; default: "" (disabled)
+            maxParallelWorkers: 2 # Maximum number of concurrent workers for query preprocessing
+            inline: "" # Direct SQL queries to execute after scripts from 'queryPath'. Supports multiple statements separated by semicolons
+          configs: # Postgres configuration overrides for promotion container
             shared_buffers: 2GB
-
-          # Add PostgreSQL recovery configuration parameters to the promotion container.
-          recovery:
-          # Uncomment this only if you are on Postgres version 11 or older.
-          #  recovery_target: 'immediate'
-          #  recovery_target_action: 'promote'
-          #  recovery_target_timeline: 'latest'
-
-        # It is possible to define a pre-processing script. For example, "/tmp/scripts/custom.sh".
-        # Default: empty string (no pre-processing defined).
-        # This can be used for scrubbing eliminating PII data, to define data masking, etc.
-        preprocessingScript: ""
-
-        # Scheduler contains tasks that run on a schedule.
-        scheduler:
-          # Snapshot scheduler creates a new snapshot on a schedule.
-          snapshot:
-            # Timetable defines in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-            timetable: "0 */6 * * *"
-          # Retention scheduler cleans up old snapshots on a schedule.
-          retention:
-            # Timetable defines in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-            timetable: "0 * * * *"
-            # Limit defines how many snapshots should be hold.
-            limit: 4
-
-        # Passes custom environment variables to the promotion Docker container.
-        envs:
+          recovery: # Legacy recovery.conf configuration options; only applicable for Postgres 11 or earlier versions
+            # recovery_target: 'immediate'
+            # recovery_target_action: 'promote'
+            # recovery_target_timeline: 'latest'
+        preprocessingScript: "" # Shell script path to execute before finalizing snapshot; example: "/tmp/scripts/custom.sh"; default: "" (disabled)
+        scheduler: # Snapshot scheduling and retention policy configuration
+          snapshot: # Snapshot creation scheduling
+            timetable: "0 */6 * * *" # Cron expression defining snapshot schedule: https://en.wikipedia.org/wiki/Cron#Overview
+          retention: # Snapshot retention policy
+            timetable: "0 * * * *" # Cron expression defining retention check schedule: https://en.wikipedia.org/wiki/Cron#Overview
+            limit: 4 # Maximum number of snapshots to retain
+        envs: # Environment variables to pass to promotion container
 
 cloning:
-  # Host that will be specified in database connection info for all clones
-  # Use public IP address if database connections are allowed from outside
-  # This value is only used to inform users about how to connect to database clones
-  accessHost: "localhost"
-
-  # Automatically delete clones after the specified minutes of inactivity.
-  # 0 - disable automatic deletion.
-  # Inactivity means:
-  #   - no active sessions (queries being processed right now)
-  #   - no recently logged queries in the query log
-  maxIdleMinutes: 120
+  accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
+  maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
 
 diagnostic:
-  logsRetentionDays: 7
+  logsRetentionDays: 7 # How many days to keep logs
+
+observer: # CI Observer configuration
+#  replacementRules:  # Regexp rules for masking personal data in Postgres logs; applied before sending the logs to the Platform
+#                     # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
+#    "regexp": "replace"
+#    "select \\d+": "***"
+#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
 
-# ### INTEGRATION ###
+webhooks: # Webhooks can be used to trigger actions in external systems upon events such as clone creation
+#  hooks:
+#    - url: ""
+#      secret: "" # (optional) Sent with the request in the `DBLab-Webhook-Token` HTTP header.
+#      trigger:
+#        - clone_create
+#        - clone_reset
 
-# Postgres.ai Platform integration (provides GUI) – extends the open source offering.
-# Uncomment the following lines if you need GUI, personal tokens, audit logs, more.
-#
 platform:
-  # Platform API URL. To work with Postgres.ai SaaS, keep it default
-  # ("https://postgres.ai/api/general").
-  url: "https://postgres.ai/api/general"
-  # Telemetry: anonymous statistics sent to Postgres.ai.
-  # Used to analyze DLE usage, it helps the DLE maintainers make decisions on product development.
-  # Please leave it enabled if possible – this will contribute to DLE development.
-  # The full list of data points being collected: https://postgres.ai/docs/database-lab/telemetry
+  url: "https://postgres.ai/api/general" # Default: "https://postgres.ai/api/general"
   enableTelemetry: true
+
+#  ╔══════════════════════════════════════════════════════════════════════════╗
+#  ║                     POSTGRES AI PLATFORM INTEGRATION                     ║
+#  ╠══════════════════════════════════════════════════════════════════════════╣
+#  ║                                                                          ║
+#  ║  - Production-ready UI, AI assistance and support from human experts     ║
+#  ║  - Enterprise-grade user management & role-based access control          ║
+#  ║  - Advanced security: audit trails, SIEM integration, compliance         ║
+#  ║  - Real-time performance monitoring & intelligent recommendations        ║
+#  ║                                                                          ║
+#  ║  Learn more at https://postgres.ai/                                      ║
+#  ║                                                                          ║
+#  ╚══════════════════════════════════════════════════════════════════════════╝
 #
-#  # Project name
-#  projectName: "project_name"
-#
-#  # Organization key
-#  orgKey: "org_key"
-#
-#  # Token for authorization in Platform API. This token can be obtained on
-#  # the Postgres.ai Console: https://postgres.ai/console/YOUR_ORG_NAME/tokens
-#  # This token needs to be kept in secret, known only to the administrator.
-#  accessToken: "platform_access_token"
-#
-#  # Enable authorization with personal tokens of the organization's members.
-#  # If false: all users must use "verificationToken" value for any API request
-#  # If true: "verificationToken" is known only to admin, users use their own tokens,
-#  #          and any token can be revoked not affecting others
-#  enablePersonalTokens: true
-#
-# CI Observer configuration.
-#observer:
-#  # Set up regexp rules for Postgres logs.
-#  # These rules are applied before sending the logs to the Platform, to ensure that personal data is masked properly.
-#  # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
-#  replacementRules:
-#    "regexp": "replace"
-#    "select \\d+": "***"
-#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+# Uncomment the following lines if you need the Platform integration
 #
+#  projectName: "project_name" # Project name
+#  orgKey: "org_key" # Organization key
+#  accessToken: "platform_access_token" # Token for authorization in Platform API; get it at https://postgres.ai/console/YOUR_ORG_NAME/tokens
+#  enablePersonalTokens: true # Enable authorization with personal tokens of the organization's members.
+#
\ No newline at end of file
diff --git a/engine/configs/config.example.physical_pgbackrest.yml b/engine/configs/config.example.physical_pgbackrest.yml
index 447272e9..4840747b 100644
--- a/engine/configs/config.example.physical_pgbackrest.yml
+++ b/engine/configs/config.example.physical_pgbackrest.yml
@@ -1,315 +1,138 @@
-# Copy the following to: ~/.dblab/engine/configs/server.yml
-
-# Database Lab API server. This API is used to work with clones
-# (list them, create, delete, see how to connect to a clone).
-# Normally, it is supposed to listen 127.0.0.1:2345 (default),
-# and to be running inside a Docker container,
-# with port mapping, to allow users to connect from outside
-# to 2345 port using private or public IP address of the machine
-# where the container is running. See https://postgres.ai/docs/database-lab/how-to-manage-database-lab
+# Copy this configuration to: ~/.dblab/engine/configs/server.yml
+# Configuration reference guide: https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference
 server:
-  # The main token that is used to work with Database Lab API.
-  # Note, that only one token is supported.
-  # However, if the integration with Postgres.ai Platform is configured
-  # (see below, "platform: ..." configuration), then users may use
-  # their personal tokens generated on the Platform. In this case,
-  # it is recommended to keep "verificationToken" secret, known
-  # only to the administrator of the Database Lab instance.
-  #
-  # Database Lab Engine can be running with an empty verification token, which is not recommended.
-  # In this case, the DLE API and the UI application will not require any credentials.
-  verificationToken: "secret_token"
-
-  # HTTP server port. Default: 2345.
-  port: 2345
-
-  # Disable modifying configuration via UI/API. Default: false.
-  disableConfigModification: false
+  verificationToken: "secret_token" # Primary auth token; can be empty (not recommended); for multi-user mode, use DBLab EE
+  port: 2345 # API server port; default: "2345"
+  disableConfigModification: false # When true, configuration changes via API/CLI/UI are disabled; default: "false"
 
-# Embedded UI. Controls the application to provide a user interface to DLE API.
 embeddedUI:
-  enabled: true
-
-  # Docker image of the UI application.
-  dockerImage: "postgresai/ce-ui:latest"
-
-  # Host or IP address, from which the embedded UI container accepts HTTP connections.
-  # By default, use a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  host: "127.0.0.1"
-
-  # HTTP port of the UI application. Default: 2346.
-  port: 2346
+  enabled: true # If enabled, a separate UI container will be started
+  dockerImage: "postgresai/ce-ui:latest" # Default: "postgresai/ce-ui:latest"
+  host: "127.0.0.1" # Default: "127.0.0.1" (accepts only local connections)
+  port: 2346 # UI port; default: "2346"
 
 global:
-  # Database engine. Currently, the only supported option: "postgres".
-  engine: postgres
-
-  # Debugging, when enabled, allows seeing more in the Database Lab logs
-  # (not PostgreSQL logs). Enable in the case of troubleshooting.
-  debug: true
-
-  # Contains default configuration options of the restored database.
-  database:
-    # Default database username that will be used for Postgres management connections.
-    # This user must exist.
-    username: postgres
-
-    # Default database name.
-    dbname: postgres
-
-# Manages filesystem pools (in the case of ZFS) or volume groups.
-poolManager:
-  # The full path which contains the pool mount directories. mountDir can contain multiple pool directories.
-  mountDir: /var/lib/dblab
-
-  # Subdir where PGDATA located relative to the pool mount directory.
-  # This directory must already exist before launching Database Lab instance. It may be empty if
-  # data initialization is configured (see below).
-  # Note, it is a relative path. Default: "data".
-  # For example, for the PostgreSQL data directory "/var/lib/dblab/dblab_pool/data" (`dblab_pool` is a pool mount directory) set:
-  #      mountDir:  /var/lib/dblab
-  #      dataSubDir:  data
-  # In this case, we assume that the mount point is: /var/lib/dblab/dblab_pool
-  dataSubDir: data
-
-  # Directory that will be used to mount clones. Subdirectories in this directory
-  # will be used as mount points for clones. Subdirectory names will
-  # correspond to ports. E.g., subdirectory "dblab_clone_6000" for the clone running on port 6000.
-  clonesMountSubDir: clones
-
-  # Unix domain socket directory used to establish local connections to cloned databases.
-  socketSubDir: sockets
-
-  # Directory that will be used to store observability artifacts. The directory will be created inside PGDATA.
-  observerSubDir: observer
-
-  # Snapshots with this suffix are considered preliminary. They are not supposed to be accessible to end-users.
-  preSnapshotSuffix: "_pre"
-
-  # Force selection of a working pool inside the `mountDir`.
-  # It is an empty string by default which means that the standard selection and rotation mechanism will be applied.
-  selectedPool: ""
-
-# Configure PostgreSQL containers
-databaseContainer: &db_container
-  # Database Lab provisions thin clones using Docker containers and uses auxiliary containers.
-  # We need to specify which Postgres Docker image is to be used for that.
-  # The default is the extended Postgres image built on top of the official Postgres image
-  # (See https://postgres.ai/docs/database-lab/supported_databases).
-  #
-  # For paid customers, Postgres.AI additionally maintains a set of images compatible with
-  # RDS and RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS.
-  #
-  # IMPORTANT: Postgres version of this image should match the source's Postgres version.
-  # For logical mode, it is a recommendation. For physical mode, it is a *requirement*.
-  dockerImage: "postgresai/extended-postgres:16-0.5.0"
-
-  # Custom parameters for containers with PostgreSQL, see
-  # https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
-  containerConfig:
-    "shm-size": 1gb
-
-# Adjust PostgreSQL configuration
-databaseConfigs: &db_configs
+  engine: postgres # Default: "postgres" (only Postgres is currently supported)
+  debug: true # When true, more detailed logs are written to the server log
+  database: # DB credentials used for management connections
+    username: postgres # DB user, default: "postgres" (user must exist)
+    dbname: postgres # DB name, default: "postgres" (DB must exist)
+
+poolManager: # Manages filesystem pools (ZFS) or volume groups (LVM)
+  mountDir: /var/lib/dblab # Pool mount directory; can contain multiple pools; default: "/var/lib/dblab"
+  dataSubDir: data  # The "golden copy" data directory location, relative to mountDir; must exist; default: "data"
+                    # Example: for "/var/lib/dblab/dblab_pool/data" set mountDir: "/var/lib/dblab" and dataSubDir: "data" (assuming mount point is "/var/lib/dblab/dblab_pool")
+  clonesMountSubDir: clones # Where clones are mounted, relative to mountDir; default: "clones"
+                            # Example: for "/var/lib/dblab/dblab_pool/clones" set mountDir: "/var/lib/dblab" and clonesMountSubDir: "clones" (assuming mount point is "/var/lib/dblab/dblab_pool"), resulting path for a clone running on port 6000: "/var/lib/dblab/dblab_pool/clones/6000"
+  socketSubDir: sockets # Where sockets are located, relative to mountDir; default: "sockets"
+  observerSubDir: observer # Where observability artifacts are located, relative to clone's data directory; default: "observer"
+  preSnapshotSuffix: "_pre" # Suffix for preliminary snapshots; default: "_pre"
+  selectedPool: "" # Force selection of working pool inside mountDir; default: "" (standard selection and rotation mechanism will be applied)
+
+databaseContainer: &db_container  # Docker config for all DB containers
+                                  # See https://postgres.ai/docs/database-lab/supported_databases
+                                  # DBLab SE and EE customers get images compatible with RDS, RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS
+  dockerImage: "postgresai/extended-postgres:17-0.5.3" # Postgres image; major version (17) must match source if physical mode
+  containerConfig: # Custom container config; see https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
+    "shm-size": 1gb # Shared memory size; increase if "could not resize shared memory segment" errors occur
+
+databaseConfigs: &db_configs # Postgres config for all DB containers
   configs:
-    # In order to match production plans with Database Lab plans set parameters related to Query Planning as on production.
-    shared_buffers: 1GB
-    # shared_preload_libraries – copy the value from the source
-    # Adding shared preload libraries, make sure that there are "pg_stat_statements, auto_explain, logerrors" in the list.
-    # It is necessary to perform query and db migration analysis.
-    # Note, if you are using PostgreSQL 9.6 and older, remove the logerrors extension from the list since it is not supported.
-    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors"
-    # work_mem and all the Query Planning parameters – copy the values from the source.
-    # Detailed guide: https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
-    work_mem: "100MB"
+    shared_buffers: 1GB # Postgres buffer pool size; large values can lead to OOM
+    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors" # Shared libraries; copy from source
+    maintenance_work_mem: "500MB" # Maximum memory for maintenance operations (VACUUM, CREATE INDEX, etc.)
+    work_mem: "100MB" # This and Query Planning parameters should be copied from source; see https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
     # ... put Query Planning parameters here
 
-# Details of provisioning – where data is located,
-# thin cloning method, etc.
-provision:
+provision: # Defines how data is provisioned
   <<: *db_container
-  # Pool of ports for Postgres clones. Ports will be allocated sequentially,
-  # starting from the lowest value. The "from" value must be less than or equal to "to".
-  portPool:
-    from: 6000
-    to: 6099
-
-  # Use sudo for ZFS/LVM and Docker commands if Database Lab server running
-  # outside a container. Keep it "false" (default) when running in a container.
-  useSudo: false
-
-  # Avoid default password resetting in clones and have the ability for
-  # existing users to log in with old passwords.
-  keepUserPasswords: false
-
-  # IP addresses that can be used to access clones.
-  # By default, using a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  # The option supports multiple IPs (using comma-separated format) and IPv6 addresses (for example, [::1])
-  cloneAccessAddresses: "127.0.0.1"
-
-# Data retrieval flow. This section defines both initial retrieval, and rules
-# to keep the data directory in a synchronized state with the source. Both are optional:
-# you may already have the data directory, so neither initial retrieval nor
-# synchronization are needed.
-# 
-# Data retrieval can be also considered as "thick" cloning. Once it's done, users
-# can use "thin" cloning to get independent full-size clones of the database in
-# seconds, for testing and development. Normally, retrieval (thick cloning) is
-# a slow operation (1 TiB/h is a good speed). Optionally, the process of keeping
-# the Database Lab data directory in sync with the source (being continuously
-# updated) can be configured.
-#
-# There are two basic ways to organize data retrieval:
-#  - "logical":  use dump/restore processes, obtaining a logical copy of the initial
-#                database (a sequence  of SQL commands), and then loading it to
-#                the target Database Lab data directory. This is the only option
-#                for managed cloud PostgreSQL services such as Amazon RDS. Physically,
-#                the copy of the database created using this method differs from
-#                the original one (data blocks are stored differently). However,
-#                row counts are the same, as well as internal database statistics,
-#                allowing to do various kinds of development and testing, including
-#                running EXPLAIN command to optimize SQL queries.
-#  - "physical": physically copy the data directory from the source (or from the
-#                archive if a physical backup tool such as WAL-G, pgBackRest, or Barman
-#                is used). This approach allows to have a copy of the original database
-#                which is physically identical, including the existing bloat, data
-#                blocks location. Not supported for managed cloud Postgres services
-#                such as Amazon RDS.
-retrieval:
-  # The jobs section must not contain physical and logical restore jobs simultaneously.
-  jobs:
+  portPool: # Range of ports for Postgres clones; ports will be allocated sequentially, starting from the lowest value
+    from: 6000 # First port in the range
+    to: 6099 # Last port in the range
+  useSudo: false # Use sudo for ZFS/LVM and Docker commands if DBLab server running outside a container (not recommended)
+  keepUserPasswords: false # Keep user passwords in clones; default: "false"
+  cloneAccessAddresses: "127.0.0.1" # IP addresses that can be used to access clones; supports multiple IPs and IPv6; default: "127.0.0.1" (loop-back)
+
+retrieval:  # Data retrieval: initial sync and ongoing updates. Two methods:
+            #   - logical: dump/restore (works with RDS, different physical layout)
+            #   - physical: direct copy (identical layout, not for RDS) e.g. using pg_basebackup, WAL-G, or pgBackRest
+  jobs: # Jobs to run; must not contain physical and logical restore jobs simultaneously
     - physicalRestore
     - physicalSnapshot
-
   spec:
-    # Restores database data from a physical backup.
-    physicalRestore:
+    physicalRestore: # Restores data directory from a physical backup using pgBackRest
       options:
         <<: *db_container
-        # Defines the tool to restore data.
-        tool: pgbackrest
-
-        # Sync instance options.
-        sync:
-          # Enable running of a sync instance.
-          enabled: true
-
-          # Custom health check options for a sync instance container.
+        tool: pgbackrest # Use pgBackRest backup tool for data restoration
+        sync: # Additional "sync" container is used to keep the data directory in a synchronized state with the source
+          enabled: true # Enable running of sync container
           healthCheck:
-            # Health check interval for a sync instance container (in seconds).
-            interval: 5
-
-            # Maximum number of health check retries.
-            maxRetries: 200
-
-          # Add PostgreSQL configuration parameters to the sync container.
-          configs:
-            shared_buffers: 2GB
-
-          # Add PostgreSQL recovery configuration parameters to the sync container.
-          recovery:
-          # Uncomment this only if you are on Postgres version 11 or older.
-          #  standby_mode: on
-          #  recovery_target_timeline: 'latest'
-
-        # Passes custom environment variables to the Docker container with the restoring tool.
-        envs:
-          PGBACKREST_LOG_LEVEL_CONSOLE: detail
-          PGBACKREST_PROCESS_MAX: 2
-          PGBACKREST_REPO: 1
-          # SSH example
-          PGBACKREST_REPO1_TYPE: posix
-          PGBACKREST_REPO1_HOST: repo.hostname
-          PGBACKREST_REPO1_HOST_USER: postgres
-          # S3 example
-          #PGBACKREST_REPO1_TYPE: s3
-          #PGBACKREST_REPO1_PATH: "/pgbackrest"
-          #PGBACKREST_REPO1_S3_BUCKET: my_bucket
-          #PGBACKREST_REPO1_S3_ENDPOINT: s3.amazonaws.com
-          #PGBACKREST_REPO1_S3_KEY: "XXXXXXXXXXXXXXXXXX"
-          #PGBACKREST_REPO1_S3_KEY_SECRET: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
-          #PGBACKREST_REPO1_S3_REGION: us_east_1
-
-        # Defines pgBackRest configuration options.
-        pgbackrest:
-          stanza: stanzaName
-          delta: false
+            interval: 5 # Health check frequency (seconds)
+            maxRetries: 200 # Max retries before giving up
+          configs: # Additional Postgres configuration for sync container
+            shared_buffers: 2GB # Bigger buffer pool helps avoid lagging behind the source
+          recovery: # Legacy recovery.conf options; only for Postgres 11 or older
+            # standby_mode: on
+            # recovery_target_timeline: 'latest'
+
+        envs: # Environment variables for pgBackRest; see https://pgbackrest.org/user-guide.html
+          PGBACKREST_LOG_LEVEL_CONSOLE: detail # Log level; options: off, error, warn, info, detail, debug, trace
+          PGBACKREST_PROCESS_MAX: 2 # Maximum number of processes to use for compression/decompression
+          PGBACKREST_REPO: 1 # Repository to use for backups; default: 1
+          # SSH repository example
+          PGBACKREST_REPO1_TYPE: posix # Repository type; options: posix, s3, azure, gcs
+          PGBACKREST_REPO1_HOST: repo.hostname # Repository host for SSH connections
+          PGBACKREST_REPO1_HOST_USER: postgres # SSH user for repository connections
+          # S3 repository example (uncomment to use)
+          #PGBACKREST_REPO1_TYPE: s3 # Repository type: s3
+          #PGBACKREST_REPO1_PATH: "/pgbackrest" # S3 path prefix
+          #PGBACKREST_REPO1_S3_BUCKET: my_bucket # S3 bucket name
+          #PGBACKREST_REPO1_S3_ENDPOINT: s3.amazonaws.com # S3 endpoint
+          #PGBACKREST_REPO1_S3_KEY: "XXXXXXXXXXXXXXXXXX" # S3 access key
+          #PGBACKREST_REPO1_S3_KEY_SECRET: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # S3 secret key
+          #PGBACKREST_REPO1_S3_REGION: us_east_1 # S3 region
+
+        pgbackrest: # pgBackRest specific configuration
+          stanza: stanzaName # Stanza name (must match the stanza configured in your pgBackRest setup)
+          delta: false # Use delta restore; set to true for incremental restores from last backup
 
     physicalSnapshot:
       options:
-        # Skip taking a snapshot while the retrieval starts.
-        skipStartSnapshot: false
-
-        # Adjust PostgreSQL configuration of the snapshot.
-        <<: *db_configs
-
-        # Promote PGDATA after data fetching.
+        skipStartSnapshot: false # Skip taking a snapshot when retrieval starts; default: "false"
+        <<: *db_configs # Additional Postgres configuration for containers participating in physicalSnapshot (promotion, snapshot)
         promotion:
           <<: *db_container
-          # Enable PGDATA promotion.
-          enabled: true
-
-          # Custom health check options for a data promotion container.
+          enabled: true # Enable Postgres promotion to read-write mode before finalizing snapshot
           healthCheck:
-            # Health check interval for a data promotion container (in seconds).
-            interval: 5
-
-            # Maximum number of health check retries.
-            maxRetries: 200
-
-          # It is possible to define pre-processing SQL queries. For example, "/tmp/scripts/sql".
-          # Default: empty string (no pre-processing defined).
-          queryPreprocessing:
-            # Path to SQL pre-processing queries.
-            queryPath: ""
-
-            # Worker limit for parallel queries.
-            maxParallelWorkers: 2
-
-            # Inline SQL. Queries run after scripts placed in 'queryPath'.
-            inline: ""
-
-          # Add PostgreSQL configuration parameters to the promotion container.
-          configs:
+            interval: 5 # Health check interval in seconds
+            maxRetries: 200 # Maximum retry attempts before failing
+          queryPreprocessing: # Data transformation using SQL before promoting to read-write mode
+            queryPath: "" # Directory path containing SQL query files; example: "/tmp/scripts/sql"; default: "" (disabled)
+            maxParallelWorkers: 2 # Maximum number of concurrent workers for query preprocessing
+            inline: "" # Direct SQL queries to execute after scripts from 'queryPath'. Supports multiple statements separated by semicolons
+          configs: # Postgres configuration overrides for promotion container
             shared_buffers: 2GB
-
-          # Add PostgreSQL recovery configuration parameters to the promotion container.
-          recovery:
-          # Uncomment this only if you are on Postgres version 11 or older.
-          #  recovery_target: 'immediate'
-          #  recovery_target_action: 'promote'
-          #  recovery_target_timeline: 'latest'
-
-        # It is possible to define a pre-processing script. For example, "/tmp/scripts/custom.sh".
-        # Default: empty string (no pre-processing defined).
-        # This can be used for scrubbing eliminating PII data, to define data masking, etc.
-        preprocessingScript: ""
-
-        # Scheduler contains tasks that run on a schedule.
-        scheduler:
-          # Snapshot scheduler creates a new snapshot on a schedule.
-          snapshot:
-            # Timetable defines in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-            timetable: "0 */6 * * *"
-          # Retention scheduler cleans up old snapshots on a schedule.
-          retention:
-            # Timetable defines in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-            timetable: "0 * * * *"
-            # Limit defines how many snapshots should be hold.
-            limit: 4
-
-        # Passes custom environment variables to the promotion Docker container.
-        envs:
-          PGBACKREST_LOG_LEVEL_CONSOLE: detail
-          PGBACKREST_PROCESS_MAX: 2
-          PGBACKREST_REPO: 1
-          # SSH example
-          PGBACKREST_REPO1_TYPE: posix
-          PGBACKREST_REPO1_HOST: repo.hostname
-          PGBACKREST_REPO1_HOST_USER: postgres
-          # S3 example
+          recovery: # Legacy recovery.conf configuration options; only applicable for Postgres 11 or earlier versions
+            # recovery_target: 'immediate'
+            # recovery_target_action: 'promote'
+            # recovery_target_timeline: 'latest'
+
+        preprocessingScript: "" # Shell script path to execute before finalizing snapshot; example: "/tmp/scripts/custom.sh"; default: "" (disabled)
+        scheduler: # Snapshot scheduling and retention policy configuration
+          snapshot: # Snapshot creation scheduling
+            timetable: "0 */6 * * *" # Cron expression defining snapshot schedule: https://en.wikipedia.org/wiki/Cron#Overview
+          retention: # Snapshot retention policy
+            timetable: "0 * * * *" # Cron expression defining retention check schedule: https://en.wikipedia.org/wiki/Cron#Overview
+            limit: 4 # Maximum number of snapshots to retain
+        envs: # Environment variables for pgBackRest operations during snapshot
+          PGBACKREST_LOG_LEVEL_CONSOLE: detail # Log level for snapshot operations
+          PGBACKREST_PROCESS_MAX: 2 # Maximum number of processes for snapshot operations
+          PGBACKREST_REPO: 1 # Repository to use for snapshot operations
+          # SSH repository example
+          PGBACKREST_REPO1_TYPE: posix # Repository type
+          PGBACKREST_REPO1_HOST: repo.hostname # Repository host
+          PGBACKREST_REPO1_HOST_USER: postgres # SSH user
+          # S3 repository example (uncomment to use)
           #PGBACKREST_REPO1_TYPE: s3
           #PGBACKREST_REPO1_PATH: "/pgbackrest"
           #PGBACKREST_REPO1_S3_BUCKET: my_bucket
@@ -319,59 +142,48 @@ retrieval:
           #PGBACKREST_REPO1_S3_REGION: us_east_1
 
 cloning:
-  # Host that will be specified in database connection info for all clones
-  # Use public IP address if database connections are allowed from outside
-  # This value is only used to inform users about how to connect to database clones
-  accessHost: "localhost"
-
-  # Automatically delete clones after the specified minutes of inactivity.
-  # 0 - disable automatic deletion.
-  # Inactivity means:
-  #   - no active sessions (queries being processed right now)
-  #   - no recently logged queries in the query log
-  maxIdleMinutes: 120
+  accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
+  maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
 
 diagnostic:
-  logsRetentionDays: 7
+  logsRetentionDays: 7 # How many days to keep logs
 
-# ### INTEGRATION ###
+observer: # CI Observer configuration
+#  replacementRules:  # Regexp rules for masking personal data in Postgres logs; applied before sending the logs to the Platform
+#                     # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
+#    "regexp": "replace"
+#    "select \\d+": "***"
+#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+
+webhooks: # Webhooks can be used to trigger actions in external systems upon events such as clone creation
+#  hooks:
+#    - url: ""
+#      secret: "" # (optional) Sent with the request in the `DBLab-Webhook-Token` HTTP header.
+#      trigger:
+#        - clone_create
+#        - clone_reset
 
-# Postgres.ai Platform integration (provides GUI) – extends the open source offering.
-# Uncomment the following lines if you need GUI, personal tokens, audit logs, more.
-#
 platform:
-  # Platform API URL. To work with Postgres.ai SaaS, keep it default
-  # ("https://postgres.ai/api/general").
-  url: "https://postgres.ai/api/general"
-  # Telemetry: anonymous statistics sent to Postgres.ai.
-  # Used to analyze DLE usage, it helps the DLE maintainers make decisions on product development.
-  # Please leave it enabled if possible – this will contribute to DLE development.
-  # The full list of data points being collected: https://postgres.ai/docs/database-lab/telemetry
+  url: "https://postgres.ai/api/general" # Default: "https://postgres.ai/api/general"
   enableTelemetry: true
+
+#  ╔══════════════════════════════════════════════════════════════════════════╗
+#  ║                     POSTGRES AI PLATFORM INTEGRATION                     ║
+#  ╠══════════════════════════════════════════════════════════════════════════╣
+#  ║                                                                          ║
+#  ║  - Production-ready UI, AI assistance and support from human experts     ║
+#  ║  - Enterprise-grade user management & role-based access control          ║
+#  ║  - Advanced security: audit trails, SIEM integration, compliance         ║
+#  ║  - Real-time performance monitoring & intelligent recommendations        ║
+#  ║                                                                          ║
+#  ║  Learn more at https://postgres.ai/                                      ║
+#  ║                                                                          ║
+#  ╚══════════════════════════════════════════════════════════════════════════╝
 #
-#  # Project name
-#  projectName: "project_name"
-#
-#  # Organization key
-#  orgKey: "org_key"
-#
-#  # Token for authorization in Platform API. This token can be obtained on
-#  # the Postgres.ai Console: https://postgres.ai/console/YOUR_ORG_NAME/tokens
-#  # This token needs to be kept in secret, known only to the administrator.
-#  accessToken: "platform_access_token"
-#
-#  # Enable authorization with personal tokens of the organization's members.
-#  # If false: all users must use "verificationToken" value for any API request
-#  # If true: "verificationToken" is known only to admin, users use their own tokens,
-#  #          and any token can be revoked not affecting others
-#  enablePersonalTokens: true
+# Uncomment the following lines if you need the Platform integration
 #
-# CI Observer configuration.
-#observer:
-#  # Set up regexp rules for Postgres logs.
-#  # These rules are applied before sending the logs to the Platform, to ensure that personal data is masked properly.
-#  # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
-#  replacementRules:
-#    "regexp": "replace"
-#    "select \\d+": "***"
-#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+#  projectName: "project_name" # Project name
+#  orgKey: "org_key" # Organization key
+#  accessToken: "platform_access_token" # Token for authorization in Platform API; get it at https://postgres.ai/console/YOUR_ORG_NAME/tokens
+#  enablePersonalTokens: true # Enable authorization with personal tokens of the organization's members.
+#
\ No newline at end of file
diff --git a/engine/configs/config.example.physical_walg.yml b/engine/configs/config.example.physical_walg.yml
index 17a4167c..82a15c40 100644
--- a/engine/configs/config.example.physical_walg.yml
+++ b/engine/configs/config.example.physical_walg.yml
@@ -1,350 +1,162 @@
-# Copy the following to: ~/.dblab/engine/configs/server.yml
-
-# Database Lab API server. This API is used to work with clones
-# (list them, create, delete, see how to connect to a clone).
-# Normally, it is supposed to listen 127.0.0.1:2345 (default),
-# and to be running inside a Docker container,
-# with port mapping, to allow users to connect from outside
-# to 2345 port using private or public IP address of the machine
-# where the container is running. See https://postgres.ai/docs/database-lab/how-to-manage-database-lab
+# Copy this configuration to: ~/.dblab/engine/configs/server.yml
+# Configuration reference guide: https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference
 server:
-  # The main token that is used to work with Database Lab API.
-  # Note, that only one token is supported.
-  # However, if the integration with Postgres.ai Platform is configured
-  # (see below, "platform: ..." configuration), then users may use
-  # their personal tokens generated on the Platform. In this case,
-  # it is recommended to keep "verificationToken" secret, known
-  # only to the administrator of the Database Lab instance.
-  #
-  # Database Lab Engine can be running with an empty verification token, which is not recommended.
-  # In this case, the DLE API and the UI application will not require any credentials.
-  verificationToken: "secret_token"
-
-  # HTTP server port. Default: 2345.
-  port: 2345
+  verificationToken: "secret_token" # Primary auth token; can be empty (not recommended); for multi-user mode, use DBLab EE
+  port: 2345 # API server port; default: "2345"
+  disableConfigModification: false # When true, configuration changes via API/CLI/UI are disabled; default: "false"
 
-  # Disable modifying configuration via UI/API. Default: false.
-  disableConfigModification: false
-
-# Embedded UI. Controls the application to provide a user interface to DLE API.
 embeddedUI:
-  enabled: true
-
-  # Docker image of the UI application.
-  dockerImage: "postgresai/ce-ui:latest"
-
-  # Host or IP address, from which the embedded UI container accepts HTTP connections.
-  # By default, use a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  host: "127.0.0.1"
-
-  # HTTP port of the UI application. Default: 2346.
-  port: 2346
+  enabled: true # If enabled, a separate UI container will be started
+  dockerImage: "postgresai/ce-ui:latest" # Default: "postgresai/ce-ui:latest"
+  host: "127.0.0.1" # Default: "127.0.0.1" (accepts only local connections)
+  port: 2346 # UI port; default: "2346"
 
 global:
-  # Database engine. Currently, the only supported option: "postgres".
-  engine: postgres
-
-  # Debugging, when enabled, allows seeing more in the Database Lab logs
-  # (not PostgreSQL logs). Enable in the case of troubleshooting.
-  debug: true
-
-  # Contains default configuration options of the restored database.
-  database:
-    # Default database username that will be used for Postgres management connections.
-    # This user must exist.
-    username: postgres
-
-    # Default database name.
-    dbname: postgres
-
-# Manages filesystem pools (in the case of ZFS) or volume groups.
-poolManager:
-  # The full path which contains the pool mount directories. mountDir can contain multiple pool directories.
-  mountDir: /var/lib/dblab
-
-  # Subdir where PGDATA located relative to the pool mount directory.
-  # This directory must already exist before launching Database Lab instance. It may be empty if
-  # data initialization is configured (see below).
-  # Note, it is a relative path. Default: "data".
-  # For example, for the PostgreSQL data directory "/var/lib/dblab/dblab_pool/data" (`dblab_pool` is a pool mount directory) set:
-  #      mountDir:  /var/lib/dblab
-  #      dataSubDir:  data
-  # In this case, we assume that the mount point is: /var/lib/dblab/dblab_pool
-  dataSubDir: data
-
-  # Directory that will be used to mount clones. Subdirectories in this directory
-  # will be used as mount points for clones. Subdirectory names will
-  # correspond to ports. E.g., subdirectory "dblab_clone_6000" for the clone running on port 6000.
-  clonesMountSubDir: clones
-
-  # Unix domain socket directory used to establish local connections to cloned databases.
-  socketSubDir: sockets
-
-  # Directory that will be used to store observability artifacts. The directory will be created inside PGDATA.
-  observerSubDir: observer
-
-  # Snapshots with this suffix are considered preliminary. They are not supposed to be accessible to end-users.
-  preSnapshotSuffix: "_pre"
-
-  # Force selection of a working pool inside the `mountDir`.
-  # It is an empty string by default which means that the standard selection and rotation mechanism will be applied.
-  selectedPool: ""
-
-# Configure PostgreSQL containers
-databaseContainer: &db_container
-  # Database Lab provisions thin clones using Docker containers and uses auxiliary containers.
-  # We need to specify which Postgres Docker image is to be used for that.
-  # The default is the extended Postgres image built on top of the official Postgres image
-  # (See https://postgres.ai/docs/database-lab/supported_databases).
-  #
-  # For paid customers, Postgres.AI additionally maintains a set of images compatible with
-  # RDS and RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS.
-  #
-  # IMPORTANT: Postgres version of this image should match the source's Postgres version.
-  # For logical mode, it is a recommendation. For physical mode, it is a *requirement*.
-  dockerImage: "postgresai/extended-postgres:16-0.5.0"
-
-  # Custom parameters for containers with PostgreSQL, see
-  # https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
-  containerConfig:
-    "shm-size": 1gb
-
-# Adjust PostgreSQL configuration
-databaseConfigs: &db_configs
+  engine: postgres # Default: "postgres" (only Postgres is currently supported)
+  debug: true # When true, more detailed logs are written to the server log
+  database: # DB credentials used for management connections
+    username: postgres # DB user, default: "postgres" (user must exist)
+    dbname: postgres # DB name, default: "postgres" (DB must exist)
+
+poolManager: # Manages filesystem pools (ZFS) or volume groups (LVM)
+  mountDir: /var/lib/dblab # Pool mount directory; can contain multiple pools; default: "/var/lib/dblab"
+  dataSubDir: data  # The "golden copy" data directory location, relative to mountDir; must exist; default: "data"
+                    # Example: for "/var/lib/dblab/dblab_pool/data" set mountDir: "/var/lib/dblab" and dataSubDir: "data" (assuming mount point is "/var/lib/dblab/dblab_pool")
+  clonesMountSubDir: clones # Where clones are mounted, relative to mountDir; default: "clones"
+                            # Example: for "/var/lib/dblab/dblab_pool/clones" set mountDir: "/var/lib/dblab" and clonesMountSubDir: "clones" (assuming mount point is "/var/lib/dblab/dblab_pool"), resulting path for a clone running on port 6000: "/var/lib/dblab/dblab_pool/clones/6000"
+  socketSubDir: sockets # Where sockets are located, relative to mountDir; default: "sockets"
+  observerSubDir: observer # Where observability artifacts are located, relative to clone's data directory; default: "observer"
+  preSnapshotSuffix: "_pre" # Suffix for preliminary snapshots; default: "_pre"
+  selectedPool: "" # Force selection of working pool inside mountDir; default: "" (standard selection and rotation mechanism will be applied)
+
+databaseContainer: &db_container  # Docker config for all DB containers
+                                  # See https://postgres.ai/docs/database-lab/supported_databases
+                                  # DBLab SE and EE customers get images compatible with RDS, RDS Aurora, GCP CloudSQL, Heroku, Timescale Cloud, Supabase, PostGIS
+  dockerImage: "postgresai/extended-postgres:17-0.5.3" # Postgres image; major version (17) must match source if physical mode
+  containerConfig: # Custom container config; see https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
+    "shm-size": 1gb # Shared memory size; increase if "could not resize shared memory segment" errors occur
+
+databaseConfigs: &db_configs # Postgres config for all DB containers
   configs:
-    # In order to match production plans with Database Lab plans set parameters related to Query Planning as on production.
-    shared_buffers: 1GB
-    # shared_preload_libraries – copy the value from the source
-    # Adding shared preload libraries, make sure that there are "pg_stat_statements, auto_explain, logerrors" in the list.
-    # It is necessary to perform query and db migration analysis.
-    # Note, if you are using PostgreSQL 9.6 and older, remove the logerrors extension from the list since it is not supported.
-    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors"
-    # work_mem and all the Query Planning parameters – copy the values from the source.
-    # Detailed guide: https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
-    work_mem: "100MB"
+    shared_buffers: 1GB # Postgres buffer pool size; large values can lead to OOM
+    shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors" # Shared libraries; copy from source
+    maintenance_work_mem: "500MB" # Maximum memory for maintenance operations (VACUUM, CREATE INDEX, etc.)
+    work_mem: "100MB" # This and Query Planning parameters should be copied from source; see https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones
     # ... put Query Planning parameters here
 
-# Details of provisioning – where data is located,
-# thin cloning method, etc.
-provision:
+provision: # Defines how data is provisioned
   <<: *db_container
-  # Pool of ports for Postgres clones. Ports will be allocated sequentially,
-  # starting from the lowest value. The "from" value must be less than or equal to "to".
-  portPool:
-    from: 6000
-    to: 6099
-
-  # Use sudo for ZFS/LVM and Docker commands if Database Lab server running
-  # outside a container. Keep it "false" (default) when running in a container.
-  useSudo: false
-
-  # Avoid default password resetting in clones and have the ability for
-  # existing users to log in with old passwords.
-  keepUserPasswords: false
-
-  # IP addresses that can be used to access clones.
-  # By default, using a loop-back to accept only local connections.
-  # The empty string means "all available addresses".
-  # The option supports multiple IPs (using comma-separated format) and IPv6 addresses (for example, [::1])
-  cloneAccessAddresses: "127.0.0.1"
-
-# Data retrieval flow. This section defines both initial retrieval, and rules
-# to keep the data directory in a synchronized state with the source. Both are optional:
-# you may already have the data directory, so neither initial retrieval nor
-# synchronization are needed.
-# 
-# Data retrieval can be also considered as "thick" cloning. Once it's done, users
-# can use "thin" cloning to get independent full-size clones of the database in
-# seconds, for testing and development. Normally, retrieval (thick cloning) is
-# a slow operation (1 TiB/h is a good speed). Optionally, the process of keeping
-# the Database Lab data directory in sync with the source (being continuously
-# updated) can be configured.
-#
-# There are two basic ways to organize data retrieval:
-#  - "logical":  use dump/restore processes, obtaining a logical copy of the initial
-#                database (a sequence  of SQL commands), and then loading it to
-#                the target Database Lab data directory. This is the only option
-#                for managed cloud PostgreSQL services such as Amazon RDS. Physically,
-#                the copy of the database created using this method differs from
-#                the original one (data blocks are stored differently). However,
-#                row counts are the same, as well as internal database statistics,
-#                allowing to do various kinds of development and testing, including
-#                running EXPLAIN command to optimize SQL queries.
-#  - "physical": physically copy the data directory from the source (or from the
-#                archive if a physical backup tool such as WAL-G, pgBackRest, or Barman
-#                is used). This approach allows to have a copy of the original database
-#                which is physically identical, including the existing bloat, data
-#                blocks location. Not supported for managed cloud Postgres services
-#                such as Amazon RDS.
-retrieval:
-  # The jobs section must not contain physical and logical restore jobs simultaneously.
-  jobs:
+  portPool: # Range of ports for Postgres clones; ports will be allocated sequentially, starting from the lowest value
+    from: 6000 # First port in the range
+    to: 6099 # Last port in the range
+  useSudo: false # Use sudo for ZFS/LVM and Docker commands if DBLab server running outside a container (not recommended)
+  keepUserPasswords: false # Keep user passwords in clones; default: "false"
+  cloneAccessAddresses: "127.0.0.1" # IP addresses that can be used to access clones; supports multiple IPs and IPv6; default: "127.0.0.1" (loop-back)
+
+retrieval:  # Data retrieval: initial sync and ongoing updates. Two methods:
+            #   - logical: dump/restore (works with RDS, different physical layout)
+            #   - physical: direct copy (identical layout, not for RDS) e.g. using pg_basebackup, WAL-G, or pgBackRest
+  jobs: # Jobs to run; must not contain physical and logical restore jobs simultaneously
     - physicalRestore
     - physicalSnapshot
-
   spec:
-    # Restores database data from a physical backup.
-    physicalRestore:
+    physicalRestore: # Restores data directory from a physical backup using WAL-G
       options:
         <<: *db_container
-        # Defines the tool to restore data.
-        tool: walg
-
-        # Sync instance options.
-        sync:
-          # Enable running of a sync instance.
-          enabled: true
-
-          # Custom health check options for a sync instance container.
+        tool: walg # Use WAL-G backup tool for data restoration
+        sync: # Additional "sync" container is used to keep the data directory in a synchronized state with the source
+          enabled: true # Enable running of sync container
           healthCheck:
-            # Health check interval for a sync instance container (in seconds).
-            interval: 5
-
-            # Maximum number of health check retries.
-            maxRetries: 200
+            interval: 5 # Health check frequency (seconds)
+            maxRetries: 200 # Max retries before giving up
+          configs: # Additional Postgres configuration for sync container
+            shared_buffers: 2GB # Bigger buffer pool helps avoid lagging behind the source
+          recovery: # Legacy recovery.conf options; only for Postgres 11 or older
+            # standby_mode: on
+            # recovery_target_timeline: 'latest'
 
-          # Add PostgreSQL configuration parameters to the sync container.
-          configs:
-            shared_buffers: 2GB
-
-          # Add PostgreSQL recovery configuration parameters to the sync container.
-          recovery:
-          # Uncomment this only if you are on Postgres version 11 or older.
-          #  standby_mode: on
-          #  recovery_target_timeline: 'latest'
+        envs: # Environment variables for WAL-G; see https://github.com/wal-g/wal-g/blob/master/docs/README.md
+          WALG_GS_PREFIX: "gs://{BUCKET}/{SCOPE}" # Google Storage prefix for WAL-G backups
+          GOOGLE_APPLICATION_CREDENTIALS: "/tmp/sa.json" # Path to Google service account credentials
 
-        # Passes custom environment variables to the Docker container with the restoring tool.
-        envs:
-          WALG_GS_PREFIX: "gs://{BUCKET}/{SCOPE}"
-          GOOGLE_APPLICATION_CREDENTIALS: "/tmp/sa.json"
-
-        # Defines WAL-G configuration options.
-        walg:
-          backupName: LATEST
+        walg: # WAL-G specific configuration
+          backupName: LATEST # Which backup to restore; use "LATEST" for most recent backup
 
     physicalSnapshot:
       options:
-        # Skip taking a snapshot while the retrieval starts.
-        skipStartSnapshot: false
-
-        # Adjust PostgreSQL configuration of the snapshot.
-        <<: *db_configs
-
-        # Promote PGDATA after data fetching.
+        skipStartSnapshot: false # Skip taking a snapshot when retrieval starts; default: "false"
+        <<: *db_configs # Additional Postgres configuration for containers participating in physicalSnapshot (promotion, snapshot)
         promotion:
           <<: *db_container
-          # Enable PGDATA promotion.
-          enabled: true
-
-          # Custom health check options for a data promotion container.
+          enabled: true # Enable Postgres promotion to read-write mode before finalizing snapshot
           healthCheck:
-            # Health check interval for a data promotion container (in seconds).
-            interval: 5
-
-            # Maximum number of health check retries.
-            maxRetries: 200
-
-          # It is possible to define pre-processing SQL queries. For example, "/tmp/scripts/sql".
-          # Default: empty string (no pre-processing defined).
-          queryPreprocessing:
-            # Path to SQL pre-processing queries.
-            queryPath: ""
-
-            # Worker limit for parallel queries.
-            maxParallelWorkers: 2
-
-            # Inline SQL. Queries run after scripts placed in 'queryPath'.
-            inline: ""
-
-          # Add PostgreSQL configuration parameters to the promotion container.
-          configs:
+            interval: 5 # Health check interval in seconds
+            maxRetries: 200 # Maximum retry attempts before failing
+          queryPreprocessing: # Data transformation using SQL before promoting to read-write mode
+            queryPath: "" # Directory path containing SQL query files; example: "/tmp/scripts/sql"; default: "" (disabled)
+            maxParallelWorkers: 2 # Maximum number of concurrent workers for query preprocessing
+            inline: "" # Direct SQL queries to execute after scripts from 'queryPath'. Supports multiple statements separated by semicolons
+          configs: # Postgres configuration overrides for promotion container
             shared_buffers: 2GB
-
-          # Add PostgreSQL recovery configuration parameters to the promotion container.
-          recovery:
-          # Uncomment this only if you are on Postgres version 11 or older.
-          #  recovery_target: 'immediate'
-          #  recovery_target_action: 'promote'
-          #  recovery_target_timeline: 'latest'
-
-        # It is possible to define a pre-processing script. For example, "/tmp/scripts/custom.sh".
-        # Default: empty string (no pre-processing defined).
-        # This can be used for scrubbing eliminating PII data, to define data masking, etc.
-        preprocessingScript: ""
-
-        # Scheduler contains tasks that run on a schedule.
-        scheduler:
-          # Snapshot scheduler creates a new snapshot on a schedule.
-          snapshot:
-            # Timetable defines in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-            timetable: "0 */6 * * *"
-          # Retention scheduler cleans up old snapshots on a schedule.
-          retention:
-            # Timetable defines in crontab format: https://en.wikipedia.org/wiki/Cron#Overview
-            timetable: "0 * * * *"
-            # Limit defines how many snapshots should be hold.
-            limit: 4
-
-        # Passes custom environment variables to the promotion Docker container.
-        envs:
-          WALG_GS_PREFIX: "gs://{BUCKET}/{SCOPE}"
-          GOOGLE_APPLICATION_CREDENTIALS: "/tmp/sa.json"
+          recovery: # Legacy recovery.conf configuration options; only applicable for Postgres 11 or earlier versions
+            # recovery_target: 'immediate'
+            # recovery_target_action: 'promote'
+            # recovery_target_timeline: 'latest'
+
+        preprocessingScript: "" # Shell script path to execute before finalizing snapshot; example: "/tmp/scripts/custom.sh"; default: "" (disabled)
+        scheduler: # Snapshot scheduling and retention policy configuration
+          snapshot: # Snapshot creation scheduling
+            timetable: "0 */6 * * *" # Cron expression defining snapshot schedule: https://en.wikipedia.org/wiki/Cron#Overview
+          retention: # Snapshot retention policy
+            timetable: "0 * * * *" # Cron expression defining retention check schedule: https://en.wikipedia.org/wiki/Cron#Overview
+            limit: 4 # Maximum number of snapshots to retain
+        envs: # Environment variables for WAL-G operations during snapshot
+          WALG_GS_PREFIX: "gs://{BUCKET}/{SCOPE}" # Google Storage prefix for WAL-G backups
+          GOOGLE_APPLICATION_CREDENTIALS: "/tmp/sa.json" # Path to Google service account credentials
 
 cloning:
-  # Host that will be specified in database connection info for all clones
-  # Use public IP address if database connections are allowed from outside
-  # This value is only used to inform users about how to connect to database clones
-  accessHost: "localhost"
-
-  # Automatically delete clones after the specified minutes of inactivity.
-  # 0 - disable automatic deletion.
-  # Inactivity means:
-  #   - no active sessions (queries being processed right now)
-  #   - no recently logged queries in the query log
-  maxIdleMinutes: 120
+  accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
+  maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
 
 diagnostic:
-  logsRetentionDays: 7
+  logsRetentionDays: 7 # How many days to keep logs
 
-# ### INTEGRATION ###
+observer: # CI Observer configuration
+#  replacementRules:  # Regexp rules for masking personal data in Postgres logs; applied before sending the logs to the Platform
+#                     # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
+#    "regexp": "replace"
+#    "select \\d+": "***"
+#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+
+webhooks: # Webhooks can be used to trigger actions in external systems upon events such as clone creation
+#  hooks:
+#    - url: ""
+#      secret: "" # (optional) Sent with the request in the `DBLab-Webhook-Token` HTTP header.
+#      trigger:
+#        - clone_create
+#        - clone_reset
 
-# Postgres.ai Platform integration (provides GUI) – extends the open source offering.
-# Uncomment the following lines if you need GUI, personal tokens, audit logs, more.
-#
 platform:
-  # Platform API URL. To work with Postgres.ai SaaS, keep it default
-  # ("https://postgres.ai/api/general").
-  url: "https://postgres.ai/api/general"
-  # Telemetry: anonymous statistics sent to Postgres.ai.
-  # Used to analyze DLE usage, it helps the DLE maintainers make decisions on product development.
-  # Please leave it enabled if possible – this will contribute to DLE development.
-  # The full list of data points being collected: https://postgres.ai/docs/database-lab/telemetry
+  url: "https://postgres.ai/api/general" # Default: "https://postgres.ai/api/general"
   enableTelemetry: true
+
+#  ╔══════════════════════════════════════════════════════════════════════════╗
+#  ║                     POSTGRES AI PLATFORM INTEGRATION                     ║
+#  ╠══════════════════════════════════════════════════════════════════════════╣
+#  ║                                                                          ║
+#  ║  - Production-ready UI, AI assistance and support from human experts     ║
+#  ║  - Enterprise-grade user management & role-based access control          ║
+#  ║  - Advanced security: audit trails, SIEM integration, compliance         ║
+#  ║  - Real-time performance monitoring & intelligent recommendations        ║
+#  ║                                                                          ║
+#  ║  Learn more at https://postgres.ai/                                      ║
+#  ║                                                                          ║
+#  ╚══════════════════════════════════════════════════════════════════════════╝
 #
-#  # Project name
-#  projectName: "project_name"
-#
-#  # Organization key
-#  orgKey: "org_key"
-#
-#  # Token for authorization in Platform API. This token can be obtained on
-#  # the Postgres.ai Console: https://postgres.ai/console/YOUR_ORG_NAME/tokens
-#  # This token needs to be kept in secret, known only to the administrator.
-#  accessToken: "platform_access_token"
-#
-#  # Enable authorization with personal tokens of the organization's members.
-#  # If false: all users must use "verificationToken" value for any API request
-#  # If true: "verificationToken" is known only to admin, users use their own tokens,
-#  #          and any token can be revoked not affecting others
-#  enablePersonalTokens: true
+# Uncomment the following lines if you need the Platform integration
 #
-# CI Observer configuration.
-#observer:
-#  # Set up regexp rules for Postgres logs.
-#  # These rules are applied before sending the logs to the Platform, to ensure that personal data is masked properly.
-#  # Check the syntax of regular expressions: https://github.com/google/re2/wiki/Syntax
-#  replacementRules:
-#    "regexp": "replace"
-#    "select \\d+": "***"
-#    "[a-z0-9._%+\\-]+(@[a-z0-9.\\-]+\\.[a-z]{2,4})": "***$1"
+#  projectName: "project_name" # Project name
+#  orgKey: "org_key" # Organization key
+#  accessToken: "platform_access_token" # Token for authorization in Platform API; get it at https://postgres.ai/console/YOUR_ORG_NAME/tokens
+#  enablePersonalTokens: true # Enable authorization with personal tokens of the organization's members.
+#
\ No newline at end of file
diff --git a/engine/internal/cloning/base.go b/engine/internal/cloning/base.go
index 0a69a9aa..e5edb759 100644
--- a/engine/internal/cloning/base.go
+++ b/engine/internal/cloning/base.go
@@ -7,6 +7,7 @@ package cloning
 import (
 	"context"
 	"database/sql"
+	stderrors "errors"
 	"fmt"
 	"sort"
 	"strconv"
@@ -23,7 +24,9 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/webhooks"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
@@ -32,8 +35,6 @@ import (
 
 const (
 	idleCheckDuration = 5 * time.Minute
-
-	defaultDatabaseName = "postgres"
 )
 
 // Config contains a cloning configuration.
@@ -45,22 +46,27 @@ type Config struct {
 // Base provides cloning service.
 type Base struct {
 	config      *Config
+	global      *global.Config
 	cloneMutex  sync.RWMutex
 	clones      map[string]*CloneWrapper
 	snapshotBox SnapshotBox
 	provision   *provision.Provisioner
 	tm          *telemetry.Agent
 	observingCh chan string
+	webhookCh   chan webhooks.EventTyper
 }
 
 // NewBase instances a new Base service.
-func NewBase(cfg *Config, provision *provision.Provisioner, tm *telemetry.Agent, observingCh chan string) *Base {
+func NewBase(cfg *Config, global *global.Config, provision *provision.Provisioner, tm *telemetry.Agent,
+	observingCh chan string, whCh chan webhooks.EventTyper) *Base {
 	return &Base{
 		config:      cfg,
+		global:      global,
 		clones:      make(map[string]*CloneWrapper),
 		provision:   provision,
 		tm:          tm,
 		observingCh: observingCh,
+		webhookCh:   whCh,
 		snapshotBox: SnapshotBox{
 			items: make(map[string]*models.Snapshot),
 		},
@@ -68,8 +74,9 @@ func NewBase(cfg *Config, provision *provision.Provisioner, tm *telemetry.Agent,
 }
 
 // Reload reloads base cloning configuration.
-func (c *Base) Reload(cfg Config) {
+func (c *Base) Reload(cfg Config, global global.Config) {
 	*c.config = cfg
+	*c.global = global
 }
 
 // Run initializes and runs cloning component.
@@ -79,11 +86,11 @@ func (c *Base) Run(ctx context.Context) error {
 	}
 
 	if _, err := c.GetSnapshots(); err != nil {
-		log.Err("No available snapshots: ", err)
+		log.Err("no available snapshots:", err)
 	}
 
 	if err := c.RestoreClonesState(); err != nil {
-		log.Err("Failed to load stored sessions:", err)
+		log.Err("failed to load stored sessions:", err)
 	}
 
 	c.restartCloneContainers(ctx)
@@ -109,7 +116,7 @@ func (c *Base) cleanupInvalidClones() error {
 	c.cloneMutex.Lock()
 
 	for _, clone := range c.clones {
-		keepClones[util.GetCloneName(clone.Session.Port)] = struct{}{}
+		keepClones[clone.Clone.ID] = struct{}{}
 	}
 
 	c.cloneMutex.Unlock()
@@ -123,6 +130,16 @@ func (c *Base) cleanupInvalidClones() error {
 	return nil
 }
 
+// GetLatestSnapshot returns the latest snapshot.
+func (c *Base) GetLatestSnapshot() (*models.Snapshot, error) {
+	snapshot, err := c.getLatestSnapshot()
+	if err != nil {
+		return nil, fmt.Errorf("failed to find the latest snapshot: %w", err)
+	}
+
+	return snapshot, err
+}
+
 // CreateClone creates a new clone.
 func (c *Base) CreateClone(cloneRequest *types.CloneCreateRequest) (*models.Clone, error) {
 	cloneRequest.ID = strings.TrimSpace(cloneRequest.ID)
@@ -154,9 +171,14 @@ func (c *Base) CreateClone(cloneRequest *types.CloneCreateRequest) (*models.Clon
 		}
 	}
 
+	if cloneRequest.Branch == "" {
+		cloneRequest.Branch = snapshot.Branch
+	}
+
 	clone := &models.Clone{
 		ID:        cloneRequest.ID,
 		Snapshot:  snapshot,
+		Branch:    cloneRequest.Branch,
 		Protected: cloneRequest.Protected,
 		CreatedAt: models.NewLocalTime(createdAt),
 		Status: models.Status{
@@ -167,6 +189,7 @@ func (c *Base) CreateClone(cloneRequest *types.CloneCreateRequest) (*models.Clon
 			Username: cloneRequest.DB.Username,
 			DBName:   cloneRequest.DB.DBName,
 		},
+		Revision: cloneRequest.Revision,
 	}
 
 	w := NewCloneWrapper(clone, createdAt)
@@ -181,19 +204,19 @@ func (c *Base) CreateClone(cloneRequest *types.CloneCreateRequest) (*models.Clon
 		AvailableDB: cloneRequest.DB.DBName,
 	}
 
-	c.incrementCloneNumber(clone.Snapshot.ID)
+	c.IncrementCloneNumber(clone.Snapshot.ID)
 
 	go func() {
-		session, err := c.provision.StartSession(clone.Snapshot.ID, ephemeralUser, cloneRequest.ExtraConf)
+		session, err := c.provision.StartSession(clone, ephemeralUser, cloneRequest.ExtraConf)
 		if err != nil {
 			// TODO(anatoly): Empty room case.
-			log.Errf("Failed to start session: %v.", err)
+			log.Errf("failed to start session: %v", err)
 
 			if updateErr := c.UpdateCloneStatus(cloneID, models.Status{
 				Code:    models.StatusFatal,
 				Message: errors.Cause(err).Error(),
 			}); updateErr != nil {
-				log.Errf("Failed to update clone status: %v", updateErr)
+				log.Errf("failed to update clone status: %v", updateErr)
 			}
 
 			return
@@ -201,6 +224,18 @@ func (c *Base) CreateClone(cloneRequest *types.CloneCreateRequest) (*models.Clon
 
 		c.fillCloneSession(cloneID, session)
 		c.SaveClonesState()
+
+		c.webhookCh <- webhooks.CloneEvent{
+			BasicEvent: webhooks.BasicEvent{
+				EventType: webhooks.CloneCreatedEvent,
+				EntityID:  cloneID,
+			},
+			Host:          c.config.AccessHost,
+			Port:          session.Port,
+			Username:      clone.DB.Username,
+			DBName:        clone.DB.DBName,
+			ContainerName: cloneID,
+		}
 	}()
 
 	return clone, nil
@@ -212,7 +247,7 @@ func (c *Base) fillCloneSession(cloneID string, session *resources.Session) {
 
 	w, ok := c.clones[cloneID]
 	if !ok {
-		log.Errf("Clone %q not found", cloneID)
+		log.Errf("clone %q not found", cloneID)
 		return
 	}
 
@@ -225,15 +260,14 @@ func (c *Base) fillCloneSession(cloneID string, session *resources.Session) {
 		Message: models.CloneMessageOK,
 	}
 
-	dbName := clone.DB.DBName
-	if dbName == "" {
-		dbName = defaultDatabaseName
+	if dbName := clone.DB.DBName; dbName == "" {
+		clone.DB.DBName = c.global.Database.Name()
 	}
 
 	clone.DB.Port = strconv.FormatUint(uint64(session.Port), 10)
 	clone.DB.Host = c.config.AccessHost
 	clone.DB.ConnStr = fmt.Sprintf("host=%s port=%s user=%s dbname=%s",
-		clone.DB.Host, clone.DB.Port, clone.DB.Username, dbName)
+		clone.DB.Host, clone.DB.Port, clone.DB.Username, clone.DB.DBName)
 
 	clone.Metadata = models.CloneMetadata{
 		CloningTime:    w.TimeStartedAt.Sub(w.TimeCreatedAt).Seconds(),
@@ -271,10 +305,30 @@ func (c *Base) DestroyClone(cloneID string) error {
 		return models.New(models.ErrCodeNotFound, "clone not found")
 	}
 
+	if err := c.destroyPreChecks(cloneID, w); err != nil {
+		if stderrors.Is(err, errNoSession) {
+			return nil
+		}
+
+		return err
+	}
+
+	go c.destroyClone(cloneID, w)
+
+	return nil
+}
+
+var errNoSession = errors.New("no clone session")
+
+func (c *Base) destroyPreChecks(cloneID string, w *CloneWrapper) error {
 	if w.Clone.Protected && w.Clone.Status.Code != models.StatusFatal {
 		return models.New(models.ErrCodeBadRequest, "clone is protected")
 	}
 
+	if c.hasDependentSnapshots(w) {
+		log.Warn("clone has dependent snapshots", cloneID)
+	}
+
 	if err := c.UpdateCloneStatus(cloneID, models.Status{
 		Code:    models.StatusDeleting,
 		Message: models.CloneMessageDeleting,
@@ -289,34 +343,65 @@ func (c *Base) DestroyClone(cloneID string) error {
 			c.decrementCloneNumber(w.Clone.Snapshot.ID)
 		}
 
-		return nil
+		return errNoSession
 	}
 
-	go func() {
-		if err := c.provision.StopSession(w.Session); err != nil {
-			log.Errf("Failed to delete a clone: %v.", err)
+	return nil
+}
 
-			if updateErr := c.UpdateCloneStatus(cloneID, models.Status{
-				Code:    models.StatusFatal,
-				Message: errors.Cause(err).Error(),
-			}); updateErr != nil {
-				log.Errf("Failed to update clone status: %v", updateErr)
-			}
+func (c *Base) DestroyCloneSync(cloneID string) error {
+	w, ok := c.findWrapper(cloneID)
+	if !ok {
+		return models.New(models.ErrCodeNotFound, "clone not found")
+	}
 
-			return
+	if err := c.destroyPreChecks(cloneID, w); err != nil {
+		if stderrors.Is(err, errNoSession) {
+			return nil
 		}
 
-		c.deleteClone(cloneID)
+		return err
+	}
 
-		if w.Clone.Snapshot != nil {
-			c.decrementCloneNumber(w.Clone.Snapshot.ID)
+	c.destroyClone(cloneID, w)
+
+	return nil
+}
+
+func (c *Base) destroyClone(cloneID string, w *CloneWrapper) {
+	if err := c.provision.StopSession(w.Session, w.Clone); err != nil {
+		log.Errf("failed to delete clone: %v", err)
+
+		if updateErr := c.UpdateCloneStatus(cloneID, models.Status{
+			Code:    models.StatusFatal,
+			Message: errors.Cause(err).Error(),
+		}); updateErr != nil {
+			log.Errf("failed to update clone status: %v", updateErr)
 		}
-		c.observingCh <- cloneID
 
-		c.SaveClonesState()
-	}()
+		return
+	}
 
-	return nil
+	c.deleteClone(cloneID)
+
+	if w.Clone.Snapshot != nil {
+		c.decrementCloneNumber(w.Clone.Snapshot.ID)
+	}
+	c.observingCh <- cloneID
+
+	c.SaveClonesState()
+
+	c.webhookCh <- webhooks.CloneEvent{
+		BasicEvent: webhooks.BasicEvent{
+			EventType: webhooks.CloneDeleteEvent,
+			EntityID:  cloneID,
+		},
+		Host:          c.config.AccessHost,
+		Port:          w.Session.Port,
+		Username:      w.Clone.DB.Username,
+		DBName:        w.Clone.DB.DBName,
+		ContainerName: cloneID,
+	}
 }
 
 // GetClone returns clone by ID.
@@ -337,10 +422,10 @@ func (c *Base) refreshCloneMetadata(w *CloneWrapper) {
 		return
 	}
 
-	sessionState, err := c.provision.GetSessionState(w.Session)
+	sessionState, err := c.provision.GetSessionState(w.Session, w.Clone.Branch, w.Clone.ID)
 	if err != nil {
 		// Session not ready yet.
-		log.Err(fmt.Errorf("failed to get a session state: %w", err))
+		log.Err(fmt.Errorf("failed to get session state: %w", err))
 
 		return
 	}
@@ -384,6 +469,21 @@ func (c *Base) UpdateCloneStatus(cloneID string, status models.Status) error {
 	return nil
 }
 
+// UpdateCloneSnapshot updates clone snapshot.
+func (c *Base) UpdateCloneSnapshot(cloneID string, snapshot *models.Snapshot) error {
+	c.cloneMutex.Lock()
+	defer c.cloneMutex.Unlock()
+
+	w, ok := c.clones[cloneID]
+	if !ok {
+		return errors.Errorf("clone %q not found", cloneID)
+	}
+
+	w.Clone.Snapshot = snapshot
+
+	return nil
+}
+
 // ResetClone resets clone to chosen snapshot.
 func (c *Base) ResetClone(cloneID string, resetOptions types.ResetCloneRequest) error {
 	w, ok := c.findWrapper(cloneID)
@@ -418,6 +518,18 @@ func (c *Base) ResetClone(cloneID string, resetOptions types.ResetCloneRequest)
 		return errors.Wrap(err, "failed to update clone status")
 	}
 
+	if c.hasDependentSnapshots(w) {
+		log.Warn("clone has dependent snapshots", cloneID)
+		c.cloneMutex.Lock()
+		w.Clone.Revision++
+		w.Clone.HasDependent = true
+		c.cloneMutex.Unlock()
+	} else {
+		c.cloneMutex.Lock()
+		w.Clone.HasDependent = false
+		c.cloneMutex.Unlock()
+	}
+
 	go func() {
 		var originalSnapshotID string
 
@@ -425,9 +537,9 @@ func (c *Base) ResetClone(cloneID string, resetOptions types.ResetCloneRequest)
 			originalSnapshotID = w.Clone.Snapshot.ID
 		}
 
-		snapshot, err := c.provision.ResetSession(w.Session, snapshotID)
+		snapshot, err := c.provision.ResetSession(w.Session, w.Clone, snapshotID)
 		if err != nil {
-			log.Errf("Failed to reset clone: %v", err)
+			log.Errf("failed to reset clone: %v", err)
 
 			if updateErr := c.UpdateCloneStatus(cloneID, models.Status{
 				Code:    models.StatusFatal,
@@ -443,7 +555,7 @@ func (c *Base) ResetClone(cloneID string, resetOptions types.ResetCloneRequest)
 		w.Clone.Snapshot = snapshot
 		c.cloneMutex.Unlock()
 		c.decrementCloneNumber(originalSnapshotID)
-		c.incrementCloneNumber(snapshot.ID)
+		c.IncrementCloneNumber(snapshot.ID)
 
 		if err := c.UpdateCloneStatus(cloneID, models.Status{
 			Code:    models.StatusOK,
@@ -454,6 +566,18 @@ func (c *Base) ResetClone(cloneID string, resetOptions types.ResetCloneRequest)
 
 		c.SaveClonesState()
 
+		c.webhookCh <- webhooks.CloneEvent{
+			BasicEvent: webhooks.BasicEvent{
+				EventType: webhooks.CloneResetEvent,
+				EntityID:  cloneID,
+			},
+			Host:          c.config.AccessHost,
+			Port:          w.Session.Port,
+			Username:      w.Clone.DB.Username,
+			DBName:        w.Clone.DB.DBName,
+			ContainerName: cloneID,
+		}
+
 		c.tm.SendEvent(context.Background(), telemetry.CloneResetEvent, telemetry.CloneCreated{
 			ID:          util.HashID(w.Clone.ID),
 			CloningTime: w.Clone.Metadata.CloningTime,
@@ -486,6 +610,16 @@ func (c *Base) GetSnapshots() ([]models.Snapshot, error) {
 	return c.getSnapshotList(), nil
 }
 
+// GetSnapshotByID returns snapshot by ID.
+func (c *Base) GetSnapshotByID(snapshotID string) (*models.Snapshot, error) {
+	return c.getSnapshotByID(snapshotID)
+}
+
+// ReloadSnapshots reloads snapshot list.
+func (c *Base) ReloadSnapshots() error {
+	return c.fetchSnapshots()
+}
+
 // GetClones returns the list of clones descend ordered by creation time.
 func (c *Base) GetClones() []*models.Clone {
 	clones := make([]*models.Clone, 0, c.lenClones())
@@ -495,7 +629,7 @@ func (c *Base) GetClones() []*models.Clone {
 		if cloneWrapper.Clone.Snapshot != nil {
 			snapshot, err := c.getSnapshotByID(cloneWrapper.Clone.Snapshot.ID)
 			if err != nil {
-				log.Err("Snapshot not found: ", cloneWrapper.Clone.Snapshot.ID)
+				log.Err("snapshot not found: ", cloneWrapper.Clone.Snapshot.ID)
 			}
 
 			if snapshot != nil {
@@ -595,7 +729,7 @@ func (c *Base) destroyIdleClones(ctx context.Context) {
 		default:
 			isIdleClone, err := c.isIdleClone(cloneWrapper)
 			if err != nil {
-				log.Errf("Failed to check the idleness of clone %s: %v.", cloneWrapper.Clone.ID, err)
+				log.Errf("failed to check idleness of clone %s: %v", cloneWrapper.Clone.ID, err)
 				continue
 			}
 
@@ -603,7 +737,7 @@ func (c *Base) destroyIdleClones(ctx context.Context) {
 				log.Msg(fmt.Sprintf("Idle clone %q is going to be removed.", cloneWrapper.Clone.ID))
 
 				if err = c.DestroyClone(cloneWrapper.Clone.ID); err != nil {
-					log.Errf("Failed to destroy clone: %v.", err)
+					log.Errf("failed to destroy clone: %v", err)
 					continue
 				}
 			}
@@ -618,7 +752,8 @@ func (c *Base) isIdleClone(wrapper *CloneWrapper) (bool, error) {
 	idleDuration := time.Duration(c.config.MaxIdleMinutes) * time.Minute
 	minimumTime := currentTime.Add(-idleDuration)
 
-	if wrapper.Clone.Protected || wrapper.Clone.Status.Code == models.StatusExporting || wrapper.TimeStartedAt.After(minimumTime) {
+	if wrapper.Clone.Protected || wrapper.Clone.Status.Code == models.StatusExporting || wrapper.TimeStartedAt.After(minimumTime) ||
+		c.hasDependentSnapshots(wrapper) {
 		return false, nil
 	}
 
@@ -632,10 +767,11 @@ func (c *Base) isIdleClone(wrapper *CloneWrapper) (bool, error) {
 		return false, errors.New("failed to get clone session")
 	}
 
-	if _, err := c.provision.LastSessionActivity(session, minimumTime); err != nil {
+	if _, err := c.provision.LastSessionActivity(session, wrapper.Clone.Branch, wrapper.Clone.ID, wrapper.Clone.Revision,
+		minimumTime); err != nil {
 		if err == pglog.ErrNotFound {
-			log.Dbg(fmt.Sprintf("Not found recent activity for the session: %q. Clone name: %q",
-				session.ID, util.GetCloneName(session.Port)))
+			log.Dbg(fmt.Sprintf("Not found recent activity for session: %q. Clone name: %q",
+				session.ID, wrapper.Clone.ID))
 
 			return hasNotQueryActivity(session)
 		}
@@ -660,7 +796,7 @@ func hasNotQueryActivity(session *resources.Session) (bool, error) {
 
 	defer func() {
 		if err := db.Close(); err != nil {
-			log.Err("Cannot close database connection.")
+			log.Err("cannot close database connection")
 		}
 	}()
 
diff --git a/engine/internal/cloning/snapshots.go b/engine/internal/cloning/snapshots.go
index 6e353182..43044308 100644
--- a/engine/internal/cloning/snapshots.go
+++ b/engine/internal/cloning/snapshots.go
@@ -6,12 +6,14 @@ package cloning
 
 import (
 	"sort"
+	"strings"
 	"sync"
 
 	"github.com/pkg/errors"
 
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 )
 
 // SnapshotBox contains instance snapshots.
@@ -30,13 +32,13 @@ func (c *Base) fetchSnapshots() error {
 	var latestSnapshot *models.Snapshot
 
 	snapshots := make(map[string]*models.Snapshot, len(entries))
-	cloneCounter := c.cloneCounter()
+	cloneCounters := c.counterClones()
 
 	for _, entry := range entries {
-		numClones := 0
+		cloneList := []string{}
 
-		if num, ok := cloneCounter[entry.ID]; ok {
-			numClones = num
+		if foundList, ok := cloneCounters[entry.ID]; ok {
+			cloneList = foundList
 		}
 
 		currentSnapshot := &models.Snapshot{
@@ -46,7 +48,10 @@ func (c *Base) fetchSnapshots() error {
 			PhysicalSize: entry.Used,
 			LogicalSize:  entry.LogicalReferenced,
 			Pool:         entry.Pool,
-			NumClones:    numClones,
+			Branch:       entry.Branch,
+			NumClones:    len(cloneList),
+			Clones:       cloneList,
+			Message:      entry.Message,
 		}
 
 		snapshots[entry.ID] = currentSnapshot
@@ -60,20 +65,21 @@ func (c *Base) fetchSnapshots() error {
 	return nil
 }
 
-func (c *Base) cloneCounter() map[string]int {
-	cloneCounter := make(map[string]int)
+func (c *Base) counterClones() map[string][]string {
+	clones := make(map[string][]string, 0)
 
 	c.cloneMutex.RLock()
 
 	for cloneName := range c.clones {
 		if c.clones[cloneName] != nil && c.clones[cloneName].Clone.Snapshot != nil {
-			cloneCounter[c.clones[cloneName].Clone.Snapshot.ID]++
+			snapshotID := c.clones[cloneName].Clone.Snapshot.ID
+			clones[snapshotID] = append(clones[snapshotID], cloneName)
 		}
 	}
 
 	c.cloneMutex.RUnlock()
 
-	return cloneCounter
+	return clones
 }
 
 func (c *Base) resetSnapshots(snapshotMap map[string]*models.Snapshot, latestSnapshot *models.Snapshot) {
@@ -128,13 +134,14 @@ func (c *Base) getSnapshotByID(snapshotID string) (*models.Snapshot, error) {
 	return snapshot, nil
 }
 
-func (c *Base) incrementCloneNumber(snapshotID string) {
+// IncrementCloneNumber increases clone counter by 1.
+func (c *Base) IncrementCloneNumber(snapshotID string) {
 	c.snapshotBox.snapshotMutex.Lock()
 	defer c.snapshotBox.snapshotMutex.Unlock()
 
 	snapshot, ok := c.snapshotBox.items[snapshotID]
 	if !ok {
-		log.Err("Snapshot not found:", snapshotID)
+		log.Err("snapshot not found:", snapshotID)
 		return
 	}
 
@@ -147,18 +154,32 @@ func (c *Base) decrementCloneNumber(snapshotID string) {
 
 	snapshot, ok := c.snapshotBox.items[snapshotID]
 	if !ok {
-		log.Err("Snapshot not found:", snapshotID)
+		log.Err("snapshot not found:", snapshotID)
 		return
 	}
 
 	if snapshot.NumClones == 0 {
-		log.Err("The number of clones for the snapshot is negative. Snapshot ID:", snapshotID)
+		log.Err("number of clones for snapshot is negative. Snapshot ID:", snapshotID)
 		return
 	}
 
 	snapshot.NumClones--
 }
 
+// GetCloneNumber counts snapshot clones.
+func (c *Base) GetCloneNumber(snapshotID string) int {
+	c.snapshotBox.snapshotMutex.Lock()
+	defer c.snapshotBox.snapshotMutex.Unlock()
+
+	snapshot, ok := c.snapshotBox.items[snapshotID]
+	if !ok {
+		log.Err("snapshot not found:", snapshotID)
+		return 0
+	}
+
+	return snapshot.NumClones
+}
+
 func (c *Base) getSnapshotList() []models.Snapshot {
 	c.snapshotBox.snapshotMutex.RLock()
 	defer c.snapshotBox.snapshotMutex.RUnlock()
@@ -181,3 +202,18 @@ func (c *Base) getSnapshotList() []models.Snapshot {
 
 	return snapshots
 }
+
+func (c *Base) hasDependentSnapshots(w *CloneWrapper) bool {
+	c.snapshotBox.snapshotMutex.RLock()
+	defer c.snapshotBox.snapshotMutex.RUnlock()
+
+	poolName := branching.CloneName(w.Clone.Snapshot.Pool, w.Clone.Branch, w.Clone.ID, w.Clone.Revision)
+
+	for name := range c.snapshotBox.items {
+		if strings.HasPrefix(name, poolName) {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/engine/internal/cloning/snapshots_test.go b/engine/internal/cloning/snapshots_test.go
index 7e4ac8c0..2034d023 100644
--- a/engine/internal/cloning/snapshots_test.go
+++ b/engine/internal/cloning/snapshots_test.go
@@ -110,7 +110,7 @@ func TestCloneCounter(t *testing.T) {
 	require.Nil(t, err)
 	require.Equal(t, 0, snapshot.NumClones)
 
-	c.incrementCloneNumber("testSnapshotID")
+	c.IncrementCloneNumber("testSnapshotID")
 	snapshot, err = c.getSnapshotByID("testSnapshotID")
 	require.Nil(t, err)
 	require.Equal(t, 1, snapshot.NumClones)
@@ -158,11 +158,13 @@ func TestInitialCloneCounter(t *testing.T) {
 	c.clones["test_clone002"] = cloneWrapper02
 	c.clones["test_clone003"] = cloneWrapper03
 
-	counters := c.cloneCounter()
+	counters := c.counterClones()
 
-	require.Equal(t, 2, len(counters))
-	require.Equal(t, 2, counters["testSnapshotID"])
-	require.Equal(t, 1, counters["testSnapshotID2"])
+	require.Len(t, counters, 2)
+	require.Len(t, counters["testSnapshotID"], 2)
+	require.Len(t, counters["testSnapshotID2"], 1)
+	require.Len(t, counters["testSnapshotID3"], 0)
+	require.ElementsMatch(t, []string{"test_clone001", "test_clone002"}, counters["testSnapshotID"])
 }
 
 func TestLatestSnapshots(t *testing.T) {
diff --git a/engine/internal/cloning/storage.go b/engine/internal/cloning/storage.go
index 558b111d..6244a628 100644
--- a/engine/internal/cloning/storage.go
+++ b/engine/internal/cloning/storage.go
@@ -55,18 +55,18 @@ func (c *Base) restartCloneContainers(ctx context.Context) {
 			continue
 		}
 
-		cloneName := util.GetCloneName(wrapper.Session.Port)
+		cloneName := wrapper.Clone.ID
 		if c.provision.IsCloneRunning(ctx, cloneName) {
 			continue
 		}
 
 		if err := c.provision.ReconnectClone(ctx, cloneName); err != nil {
-			log.Err(fmt.Sprintf("Clone container %s cannot be reconnected to the internal network: %s", cloneName, err))
+			log.Err(fmt.Sprintf("clone container %s cannot be reconnected to internal network: %s", cloneName, err))
 			continue
 		}
 
 		if err := c.provision.StartCloneContainer(ctx, cloneName); err != nil {
-			log.Err(fmt.Sprintf("Clone container %s cannot start: %s", cloneName, err))
+			log.Err(fmt.Sprintf("clone container %s cannot start: %s", cloneName, err))
 			continue
 		}
 
@@ -102,11 +102,11 @@ func (c *Base) filterRunningClones(ctx context.Context) {
 			snapshotCache[snapshot.ID] = struct{}{}
 		}
 
-		if !c.provision.IsCloneRunning(ctx, util.GetCloneName(wrapper.Session.Port)) {
+		if !c.provision.IsCloneRunning(ctx, wrapper.Clone.ID) {
 			delete(c.clones, cloneID)
 		}
 
-		c.incrementCloneNumber(wrapper.Clone.Snapshot.ID)
+		c.IncrementCloneNumber(wrapper.Clone.Snapshot.ID)
 	}
 }
 
@@ -114,11 +114,11 @@ func (c *Base) filterRunningClones(ctx context.Context) {
 func (c *Base) SaveClonesState() {
 	sessionsPath, err := util.GetMetaPath(sessionsFilename)
 	if err != nil {
-		log.Err("failed to get path of a sessions file", err)
+		log.Err("failed to get path of sessions file", err)
 	}
 
 	if err := c.saveClonesState(sessionsPath); err != nil {
-		log.Err("Failed to save the state of running clones", err)
+		log.Err("failed to save state of running clones", err)
 	}
 }
 
diff --git a/engine/internal/cloning/storage_test.go b/engine/internal/cloning/storage_test.go
index 4df70a22..70036449 100644
--- a/engine/internal/cloning/storage_test.go
+++ b/engine/internal/cloning/storage_test.go
@@ -122,7 +122,7 @@ func TestSavingSessionState(t *testing.T) {
 		prov, err := newProvisioner()
 		assert.NoError(t, err)
 
-		s := NewBase(nil, prov, &telemetry.Agent{}, nil)
+		s := NewBase(nil, nil, prov, &telemetry.Agent{}, nil, nil)
 		err = s.saveClonesState(f.Name())
 		assert.NoError(t, err)
 
@@ -166,7 +166,7 @@ func TestFilter(t *testing.T) {
 				assert.NoError(t, err)
 				defer func() { _ = os.Remove(filepath) }()
 
-				s := NewBase(nil, prov, &telemetry.Agent{}, nil)
+				s := NewBase(nil, nil, prov, &telemetry.Agent{}, nil, nil)
 
 				s.filterRunningClones(context.Background())
 				assert.Equal(t, 0, len(s.clones))
diff --git a/engine/internal/diagnostic/logs.go b/engine/internal/diagnostic/logs.go
index 25fc552b..5649d14d 100644
--- a/engine/internal/diagnostic/logs.go
+++ b/engine/internal/diagnostic/logs.go
@@ -87,13 +87,13 @@ func CollectContainerDiagnostics(ctx context.Context, client *client.Client, con
 
 	err = collectContainerLogs(ctx, client, diagnosticsDir, containerName)
 	if err != nil {
-		log.Warn("Failed to collect container logs ", containerName, err)
+		log.Warn("failed to collect container logs ", containerName, err)
 	}
 
 	err = collectPostgresLogs(ctx, client, diagnosticsDir, containerName, dbDataDir)
 
 	if err != nil {
-		log.Warn("Failed to collect Postgres logs ", containerName, err)
+		log.Warn("failed to collect Postgres logs ", containerName, err)
 	}
 }
 
@@ -107,7 +107,7 @@ func collectContainersOutput(ctx context.Context, client *client.Client, diagnos
 	for _, containerName := range containerList {
 		err = collectContainerLogs(ctx, client, diagnosticDir, containerName)
 		if err != nil {
-			log.Warn("Failed to collect container logs ", containerName, err)
+			log.Warn("failed to collect container logs ", containerName, err)
 		}
 	}
 
@@ -236,7 +236,7 @@ func extractTar(dir string, reader *tar.Reader, header *tar.Header) error {
 
 		defer func() {
 			if err := f.Close(); err != nil {
-				log.Err("Failed to close TAR stream", err)
+				log.Err("failed to close TAR stream", err)
 			}
 		}()
 
@@ -255,14 +255,14 @@ func cleanLogsFunc(logRetentionDays int) func() {
 		log.Dbg("Cleaning old logs", logsDir)
 
 		if err != nil {
-			log.Err("Failed to fetch logs dir", err)
+			log.Err("failed to fetch logs dir", err)
 			return
 		}
 
 		err = cleanupLogsDir(logsDir, logRetentionDays)
 
 		if err != nil {
-			log.Err("Failed to fetch logs dir", err)
+			log.Err("failed to fetch logs dir", err)
 			return
 		}
 	}
@@ -273,7 +273,7 @@ func cleanupLogsDir(logsDir string, logRetentionDays int) error {
 	dirList, err := os.ReadDir(logsDir)
 
 	if err != nil {
-		log.Err("Failed list logs directories", err)
+		log.Err("failed to list logs directories", err)
 		return err
 	}
 
@@ -285,7 +285,7 @@ func cleanupLogsDir(logsDir string, logRetentionDays int) error {
 		dirTime, err := time.Parse(timeFormat, name)
 
 		if err != nil {
-			log.Warn("Failed to parse time", name, err)
+			log.Warn("failed to parse time", name, err)
 			continue
 		}
 
@@ -296,7 +296,7 @@ func cleanupLogsDir(logsDir string, logRetentionDays int) error {
 		log.Dbg("Removing old logs directory", name)
 
 		if err = os.RemoveAll(path.Join(logsDir, name)); err != nil {
-			log.Err("Directory removal failed", err)
+			log.Err("directory removal failed", err)
 		}
 	}
 
diff --git a/engine/internal/observer/observer.go b/engine/internal/observer/observer.go
index 25bdf0ef..563b5d03 100644
--- a/engine/internal/observer/observer.go
+++ b/engine/internal/observer/observer.go
@@ -12,7 +12,6 @@ import (
 	"io"
 	"os"
 	"regexp"
-	"strconv"
 	"sync"
 	"time"
 
@@ -80,13 +79,8 @@ func NewObserver(dockerClient *client.Client, cfg *Config, pm *pool.Manager) *Ob
 
 // GetCloneLog gets clone logs.
 // TODO (akartasov): Split log to chunks.
-func (o *Observer) GetCloneLog(ctx context.Context, port string, obsClone *ObservingClone) ([]byte, error) {
-	clonePort, err := strconv.Atoi(port)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to parse clone port")
-	}
-
-	fileSelector := pglog.NewSelector(obsClone.pool.ClonePath(uint(clonePort)))
+func (o *Observer) GetCloneLog(ctx context.Context, obsClone *ObservingClone) ([]byte, error) {
+	fileSelector := pglog.NewSelector(obsClone.pool.ClonePath(obsClone.branch, obsClone.cloneID, obsClone.revision))
 	fileSelector.SetMinimumTime(obsClone.session.StartedAt)
 
 	if err := fileSelector.DiscoverLogDir(); err != nil {
@@ -127,7 +121,7 @@ func (o *Observer) processCSVLogFile(ctx context.Context, buf io.Writer, filenam
 
 	defer func() {
 		if err := logFile.Close(); err != nil {
-			log.Errf("Failed to close a CSV log file: %s", err.Error())
+			log.Errf("failed to close CSV log file: %s", err.Error())
 		}
 	}()
 
@@ -193,11 +187,13 @@ func (o *Observer) maskLogs(entry []string, maskedFieldIndexes []int) {
 }
 
 // AddObservingClone adds a new observing session to storage.
-func (o *Observer) AddObservingClone(cloneID string, port uint, session *ObservingClone) {
+func (o *Observer) AddObservingClone(cloneID, branch string, revision int, port uint, session *ObservingClone) {
 	o.sessionMu.Lock()
 	defer o.sessionMu.Unlock()
 	session.pool = o.pm.First().Pool()
 	session.cloneID = cloneID
+	session.branch = branch
+	session.revision = revision
 	session.port = port
 
 	o.storage[cloneID] = session
diff --git a/engine/internal/observer/observing_clone.go b/engine/internal/observer/observing_clone.go
index dc85387e..a46cfd6c 100644
--- a/engine/internal/observer/observing_clone.go
+++ b/engine/internal/observer/observing_clone.go
@@ -43,6 +43,8 @@ var maskedFields = map[string]struct{}{
 type ObservingClone struct {
 	pool        *resources.Pool
 	cloneID     string
+	branch      string
+	revision    int
 	port        uint
 	superUserDB *pgx.Conn
 
@@ -217,7 +219,7 @@ func (c *ObservingClone) RunSession() error {
 
 	defer func() {
 		if err := c.db.Close(ctx); err != nil {
-			log.Err("Failed to close a database connection after observation for SessionID: ", c.session.SessionID)
+			log.Err("failed to close database connection after observation for SessionID: ", c.session.SessionID)
 		}
 	}()
 
@@ -254,7 +256,7 @@ func (c *ObservingClone) RunSession() error {
 			log.Dbg("Stop observation for SessionID: ", c.session.SessionID)
 
 			if err := c.storeArtifacts(); err != nil {
-				log.Err("Failed to store artifacts: ", err)
+				log.Err("failed to store artifacts: ", err)
 			}
 
 			c.done <- struct{}{}
@@ -479,7 +481,7 @@ func (c *ObservingClone) currentArtifactsSessionPath() string {
 }
 
 func (c *ObservingClone) artifactsSessionPath(sessionID uint64) string {
-	return path.Join(c.pool.ObserverDir(c.port), c.cloneID, strconv.FormatUint(sessionID, 10))
+	return path.Join(c.pool.ObserverDir(c.branch, c.cloneID, c.revision), c.cloneID, strconv.FormatUint(sessionID, 10))
 }
 
 // CheckPerformanceRequirements checks monitoring data and returns an error if any of performance requires was not satisfied.
diff --git a/engine/internal/observer/sql.go b/engine/internal/observer/sql.go
index 8db4d99c..88fc4623 100644
--- a/engine/internal/observer/sql.go
+++ b/engine/internal/observer/sql.go
@@ -8,7 +8,6 @@ import (
 	"context"
 	"fmt"
 	"path"
-	"strconv"
 	"strings"
 
 	"github.com/jackc/pgx/v4"
@@ -17,16 +16,11 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/defaults"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
-	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
 )
 
 // InitConnection creates a new connection to the clone database.
 func InitConnection(clone *models.Clone, socketDir string) (*pgx.Conn, error) {
-	host, err := unixSocketDir(socketDir, clone.DB.Port)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to parse clone port")
-	}
-
+	host := unixSocketDir(socketDir, clone.ID)
 	connectionStr := buildConnectionString(clone, host)
 
 	conn, err := pgx.Connect(context.Background(), connectionStr)
@@ -73,13 +67,8 @@ func runQuery(ctx context.Context, db *pgx.Conn, query string, args ...interface
 	return result.String(), nil
 }
 
-func unixSocketDir(socketDir, portStr string) (string, error) {
-	port, err := strconv.ParseUint(portStr, 10, 64)
-	if err != nil {
-		return "", err
-	}
-
-	return path.Join(socketDir, util.GetCloneName(uint(port))), nil
+func unixSocketDir(socketDir, cloneID string) string {
+	return path.Join(socketDir, cloneID)
 }
 
 func buildConnectionString(clone *models.Clone, socketDir string) string {
diff --git a/engine/internal/provision/databases/postgres/postgres.go b/engine/internal/provision/databases/postgres/postgres.go
index 74df3d87..5ca5bb94 100644
--- a/engine/internal/provision/databases/postgres/postgres.go
+++ b/engine/internal/provision/databases/postgres/postgres.go
@@ -99,7 +99,7 @@ func Start(r runners.Runner, c *resources.AppConfig) error {
 
 				_, err = pgctlPromote(r, c)
 				if err != nil {
-					if runnerError := Stop(r, c.Pool, c.CloneName); runnerError != nil {
+					if runnerError := Stop(r, c.Pool, c.CloneName, strconv.FormatUint(uint64(c.Port), 10)); runnerError != nil {
 						log.Err(runnerError)
 					}
 
@@ -107,7 +107,7 @@ func Start(r runners.Runner, c *resources.AppConfig) error {
 				}
 			}
 		} else {
-			log.Err("Currently cannot connect to Postgres: ", out, err)
+			log.Err("currently cannot connect to Postgres: ", out, err)
 		}
 
 		cnt++
@@ -115,7 +115,7 @@ func Start(r runners.Runner, c *resources.AppConfig) error {
 		if cnt > waitPostgresTimeout {
 			collectDiagnostics(c)
 
-			if runnerErr := Stop(r, c.Pool, c.CloneName); runnerErr != nil {
+			if runnerErr := Stop(r, c.Pool, c.CloneName, strconv.FormatUint(uint64(c.Port), 10)); runnerErr != nil {
 				log.Err(runnerErr)
 			}
 
@@ -138,7 +138,7 @@ func collectDiagnostics(c *resources.AppConfig) {
 }
 
 // Stop stops Postgres instance.
-func Stop(r runners.Runner, p *resources.Pool, name string) error {
+func Stop(r runners.Runner, p *resources.Pool, name, port string) error {
 	log.Dbg("Stopping Postgres container...")
 
 	if _, err := docker.RemoveContainer(r, name); err != nil {
@@ -151,8 +151,8 @@ func Stop(r runners.Runner, p *resources.Pool, name string) error {
 		log.Msg("docker container was not found, ignore", err)
 	}
 
-	if _, err := r.Run("rm -rf " + p.SocketCloneDir(name) + "/*"); err != nil {
-		return errors.Wrap(err, "failed to clean unix socket directory")
+	if _, err := r.Run("rm -rf " + p.SocketCloneDir(name) + "/.*" + port); err != nil {
+		return errors.Wrap(err, "failed to clean Unix socket directory")
 	}
 
 	return nil
@@ -186,6 +186,33 @@ func getPgConnStr(host, dbname, username string, port uint) string {
 	return sb.String()
 }
 
+// runExistsSQL executes simple SQL commands which returns one bool value.
+func runExistsSQL(command, connStr string) (bool, error) {
+	db, err := sql.Open("postgres", connStr)
+
+	if err != nil {
+		return false, fmt.Errorf("cannot connect to database: %w", err)
+	}
+
+	var result bool
+
+	row := db.QueryRow(command)
+	err = row.Scan(&result)
+
+	defer func() {
+		err := db.Close()
+		if err != nil {
+			log.Err("cannot close database connection")
+		}
+	}()
+
+	if err != nil && err == sql.ErrNoRows {
+		return false, nil
+	}
+
+	return result, err
+}
+
 // runSimpleSQL executes simple SQL commands which returns one string value.
 func runSimpleSQL(command, connStr string) (string, error) {
 	db, err := sql.Open("postgres", connStr)
@@ -201,7 +228,7 @@ func runSimpleSQL(command, connStr string) (string, error) {
 	defer func() {
 		err := db.Close()
 		if err != nil {
-			log.Err("Cannot close database connection.")
+			log.Err("cannot close database connection")
 		}
 	}()
 
diff --git a/engine/internal/provision/databases/postgres/postgres_mgmt.go b/engine/internal/provision/databases/postgres/postgres_mgmt.go
index a9562a95..718354cb 100644
--- a/engine/internal/provision/databases/postgres/postgres_mgmt.go
+++ b/engine/internal/provision/databases/postgres/postgres_mgmt.go
@@ -82,10 +82,18 @@ func CreateUser(c *resources.AppConfig, user resources.EphemeralUser) error {
 		dbName = user.AvailableDB
 	}
 
+	// check user
+	pgConnStr := getPgConnStr(c.Host, dbName, c.DB.Username, c.Port)
+
+	userExists, err := runExistsSQL(userExistsQuery(user.Name), pgConnStr)
+	if err != nil {
+		return fmt.Errorf("failed to check if user exists: %w", err)
+	}
+
 	if user.Restricted {
-		// create restricted user
-		query = restrictedUserQuery(user.Name, user.Password)
-		out, err := runSimpleSQL(query, getPgConnStr(c.Host, dbName, c.DB.Username, c.Port))
+		// Create or alter restricted user.
+		query = restrictedUserQuery(user.Name, user.Password, userExists)
+		out, err := runSimpleSQL(query, pgConnStr)
 
 		if err != nil {
 			return fmt.Errorf("failed to create restricted user: %w", err)
@@ -93,8 +101,18 @@ func CreateUser(c *resources.AppConfig, user resources.EphemeralUser) error {
 
 		log.Dbg("Restricted user has been created: ", out)
 
-		// set restricted user as owner for database objects
-		databaseList, err := runSQLSelectQuery(selectAllDatabases, getPgConnStr(c.Host, dbName, c.DB.Username, c.Port))
+		// Change user ownership.
+		query = restrictedUserOwnershipQuery(user.Name, user.Password)
+		out, err = runSimpleSQL(query, pgConnStr)
+
+		if err != nil {
+			return fmt.Errorf("failed to create restricted user: %w", err)
+		}
+
+		log.Dbg("Database ownership has been changed: ", out)
+
+		// Set restricted user as owner for database objects.
+		databaseList, err := runSQLSelectQuery(selectAllDatabases, pgConnStr)
 
 		if err != nil {
 			return fmt.Errorf("failed list all databases: %w", err)
@@ -111,26 +129,47 @@ func CreateUser(c *resources.AppConfig, user resources.EphemeralUser) error {
 			log.Dbg("Objects restriction applied", database, out)
 		}
 	} else {
-		query = superuserQuery(user.Name, user.Password)
+		query = superuserQuery(user.Name, user.Password, userExists)
 
-		out, err := runSimpleSQL(query, getPgConnStr(c.Host, dbName, c.DB.Username, c.Port))
+		out, err := runSimpleSQL(query, pgConnStr)
 		if err != nil {
 			return fmt.Errorf("failed to create superuser: %w", err)
 		}
 
-		log.Dbg("Super user has been created: ", out)
+		log.Dbg("Superuser has been created: ", out)
+
+		return nil
 	}
 
 	return nil
 }
 
-func superuserQuery(username, password string) string {
-	return fmt.Sprintf(`create user %s with password %s login superuser;`, pq.QuoteIdentifier(username), pq.QuoteLiteral(password))
+func superuserQuery(username, password string, exists bool) string {
+	if exists {
+		return fmt.Sprintf(`alter role %s with password %s login superuser;`,
+			pq.QuoteIdentifier(username), pq.QuoteLiteral(password))
+	}
+
+	return fmt.Sprintf(`create user %s with password %s login superuser;`,
+		pq.QuoteIdentifier(username), pq.QuoteLiteral(password))
+}
+
+func restrictedUserQuery(username, password string, exists bool) string {
+	if exists {
+		return fmt.Sprintf(`alter role %s with password %s login;`,
+			pq.QuoteIdentifier(username), pq.QuoteLiteral(password))
+	}
+
+	return fmt.Sprintf(`create user %s with password %s login;`,
+		pq.QuoteIdentifier(username), pq.QuoteLiteral(password))
+}
+
+func userExistsQuery(username string) string {
+	return fmt.Sprintf(`select exists (select from pg_roles where rolname = %s)`, pq.QuoteLiteral(username))
 }
 
 const restrictionUserCreationTemplate = `
--- create a new user 
-create user @username with password @password login;
+-- change owner
 do $$
 declare
   new_owner text;
@@ -307,7 +346,7 @@ end
 $$;
 `
 
-func restrictedUserQuery(username, password string) string {
+func restrictedUserOwnershipQuery(username, password string) string {
 	repl := strings.NewReplacer(
 		"@usernameStr", pq.QuoteLiteral(username),
 		"@username", pq.QuoteIdentifier(username),
diff --git a/engine/internal/provision/databases/postgres/postgres_mgmt_test.go b/engine/internal/provision/databases/postgres/postgres_mgmt_test.go
index e510484f..332e582d 100644
--- a/engine/internal/provision/databases/postgres/postgres_mgmt_test.go
+++ b/engine/internal/provision/databases/postgres/postgres_mgmt_test.go
@@ -11,45 +11,89 @@ import (
 )
 
 func TestSuperuserQuery(t *testing.T) {
+	const (
+		user     = "user1"
+		userTest = "user.test\""
+		pwd      = "pwd"
+		pwdQuote = "pwd\\'--"
+	)
+
+	t.Run("username and password must be quoted", func(t *testing.T) {
+		assert.Equal(t, `create user "user1" with password 'pwd' login superuser;`, superuserQuery(user, pwd, false))
+	})
+
+	t.Run("username and password must be quoted", func(t *testing.T) {
+		assert.Equal(t, `alter role "user1" with password 'pwd' login superuser;`, superuserQuery(user, pwd, true))
+	})
+
+	t.Run("special chars must be quoted", func(t *testing.T) {
+
+		assert.Equal(t, `create user "user.test""" with password  E'pwd\\''--' login superuser;`,
+			superuserQuery(userTest, pwdQuote, false))
+	})
+
+	t.Run("special chars must be quoted", func(t *testing.T) {
+		assert.Equal(t, `alter role "user.test""" with password  E'pwd\\''--' login superuser;`,
+			superuserQuery(userTest, pwdQuote, true))
+	})
+}
+
+func TestRestrictedUserQuery(t *testing.T) {
 	t.Run("username and password must be quoted", func(t *testing.T) {
 		user := "user1"
 		pwd := "pwd"
-		assert.Equal(t, `create user "user1" with password 'pwd' login superuser;`, superuserQuery(user, pwd))
+		query := restrictedUserQuery(user, pwd, false)
+
+		assert.Contains(t, query, `create user "user1" with password 'pwd' login;`)
+	})
+
+	t.Run("username and password must be quoted", func(t *testing.T) {
+		user := "user1"
+		pwd := "pwd"
+		query := restrictedUserQuery(user, pwd, true)
+
+		assert.Contains(t, query, `alter role "user1" with password 'pwd' login;`)
+	})
+
+	t.Run("special chars must be quoted", func(t *testing.T) {
+		user := "user.test\""
+		pwd := "pwd\\'--"
+		query := restrictedUserQuery(user, pwd, false)
+
+		assert.Contains(t, query, `create user "user.test""" with password  E'pwd\\''--' login;`)
 	})
 
 	t.Run("special chars must be quoted", func(t *testing.T) {
 		user := "user.test\""
 		pwd := "pwd\\'--"
-		assert.Equal(t, `create user "user.test""" with password  E'pwd\\''--' login superuser;`, superuserQuery(user, pwd))
+		query := restrictedUserQuery(user, pwd, true)
+
+		assert.Contains(t, query, `alter role "user.test""" with password  E'pwd\\''--' login;`)
 	})
 }
 
-func TestRestrictedUserQuery(t *testing.T) {
+func TestRestrictedUserOwnershipQuery(t *testing.T) {
 	t.Run("username and password must be quoted", func(t *testing.T) {
 		user := "user1"
 		pwd := "pwd"
-		query := restrictedUserQuery(user, pwd)
+		query := restrictedUserOwnershipQuery(user, pwd)
 
-		assert.Contains(t, query, `create user "user1" with password 'pwd' login;`)
 		assert.Contains(t, query, `new_owner := 'user1'`)
-
 	})
 
 	t.Run("special chars must be quoted", func(t *testing.T) {
 		user := "user.test\""
 		pwd := "pwd\\'--"
-		query := restrictedUserQuery(user, pwd)
+		query := restrictedUserOwnershipQuery(user, pwd)
 
-		assert.Contains(t, query, `create user "user.test""" with password  E'pwd\\''--' login;`)
 		assert.Contains(t, query, `new_owner := 'user.test"'`)
 	})
 
 	t.Run("change owner of all databases", func(t *testing.T) {
 		user := "user.test"
 		pwd := "pwd"
-		query := restrictedUserQuery(user, pwd)
+		query := restrictedUserOwnershipQuery(user, pwd)
 
 		assert.Contains(t, query, `select datname from pg_catalog.pg_database where not datistemplat`)
 	})
-
 }
diff --git a/engine/internal/provision/databases/postgres/postgres_test.go b/engine/internal/provision/databases/postgres/postgres_test.go
index 5484ae0d..b82c8cbd 100644
--- a/engine/internal/provision/databases/postgres/postgres_test.go
+++ b/engine/internal/provision/databases/postgres/postgres_test.go
@@ -67,7 +67,7 @@ func TestRemoveContainers(t *testing.T) {
 					})).
 			Return("", nil)
 
-		err := Stop(runner, p, "test_clone")
+		err := Stop(runner, p, "test_clone", "6200")
 
 		assert.Equal(t, tc.err, errors.Cause(err))
 	}
diff --git a/engine/internal/provision/docker/docker_test.go b/engine/internal/provision/docker/docker_test.go
index ef7287e5..edf43e39 100644
--- a/engine/internal/provision/docker/docker_test.go
+++ b/engine/internal/provision/docker/docker_test.go
@@ -40,11 +40,12 @@ func TestVolumesBuilding(t *testing.T) {
 		{
 			appConfig: &resources.AppConfig{
 				CloneName: "dblab_clone_6000",
+				Branch:    "main",
+				Revision:  0,
 				Pool: &resources.Pool{
 					Name:         "dblab_pool",
 					PoolDirName:  "dblab_pool",
 					MountDir:     "/var/lib/dblab/",
-					CloneSubDir:  "clones",
 					DataSubDir:   "data",
 					SocketSubDir: "sockets",
 				},
@@ -61,7 +62,7 @@ func TestVolumesBuilding(t *testing.T) {
 			},
 			expectedVolumes: []string{
 				"--volume /var/lib/dblab/dblab_pool/sockets/dblab_clone_6000:/var/lib/dblab/dblab_pool/sockets/dblab_clone_6000:rshared",
-				"--volume /var/lib/dblab/dblab_pool/clones/dblab_clone_6000:/var/lib/dblab/dblab_pool/clones/dblab_clone_6000:rshared",
+				"--volume /var/lib/dblab/dblab_pool/branch/main/dblab_clone_6000/r0:/var/lib/dblab/dblab_pool/branch/main/dblab_clone_6000/r0:rshared",
 			},
 		},
 	}
@@ -80,7 +81,9 @@ func TestDefaultVolumes(t *testing.T) {
 	pool.SocketSubDir = "socket"
 
 	appConfig := &resources.AppConfig{
-		Pool: pool,
+		Pool:     pool,
+		Branch:   "main",
+		Revision: 0,
 	}
 
 	unixSocketCloneDir, volumes := createDefaultVolumes(appConfig)
@@ -91,7 +94,7 @@ func TestDefaultVolumes(t *testing.T) {
 	assert.Equal(t, 2, len(volumes))
 
 	assert.ElementsMatch(t, []string{
-		"--volume /tmp/test/default:/tmp/test/default",
+		"--volume /tmp/test/default/branch/main/r0:/tmp/test/default/branch/main/r0",
 		"--volume /tmp/test/default/socket:/tmp/test/default/socket"}, volumes)
 }
 
diff --git a/engine/internal/provision/mode_local.go b/engine/internal/provision/mode_local.go
index 9bbb9243..7bc89cab 100644
--- a/engine/internal/provision/mode_local.go
+++ b/engine/internal/provision/mode_local.go
@@ -34,7 +34,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/fs"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
-	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/networks"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/pglog"
 )
@@ -151,9 +151,9 @@ func (p *Provisioner) ContainerOptions() models.ContainerOptions {
 }
 
 // StartSession starts a new session.
-func (p *Provisioner) StartSession(snapshotID string, user resources.EphemeralUser,
+func (p *Provisioner) StartSession(clone *models.Clone, user resources.EphemeralUser,
 	extraConfig map[string]string) (*resources.Session, error) {
-	snapshot, err := p.getSnapshot(snapshotID)
+	snapshot, err := p.getSnapshot(clone.Snapshot.ID)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get snapshots")
 	}
@@ -163,7 +163,7 @@ func (p *Provisioner) StartSession(snapshotID string, user resources.EphemeralUs
 		return nil, errors.New("failed to get a free port")
 	}
 
-	name := util.GetCloneName(port)
+	name := clone.ID
 
 	fsm, err := p.pm.GetFSManager(snapshot.Pool)
 	if err != nil {
@@ -174,7 +174,7 @@ func (p *Provisioner) StartSession(snapshotID string, user resources.EphemeralUs
 
 	defer func() {
 		if err != nil {
-			p.revertSession(fsm, name)
+			p.revertSession(fsm, clone.Branch, name, strconv.FormatUint(uint64(port), 10), clone.Revision)
 
 			if portErr := p.FreePort(port); portErr != nil {
 				log.Err(portErr)
@@ -182,11 +182,11 @@ func (p *Provisioner) StartSession(snapshotID string, user resources.EphemeralUs
 		}
 	}()
 
-	if err = fsm.CreateClone(name, snapshot.ID); err != nil {
+	if err = fsm.CreateClone(clone.Branch, name, snapshot.ID, clone.Revision); err != nil {
 		return nil, errors.Wrap(err, "failed to create clone")
 	}
 
-	appConfig := p.getAppConfig(fsm.Pool(), name, port)
+	appConfig := p.getAppConfig(fsm.Pool(), clone.Branch, name, clone.Revision, port)
 	appConfig.SetExtraConf(extraConfig)
 
 	if err := fs.CleanupLogsDir(appConfig.DataDir()); err != nil {
@@ -217,20 +217,16 @@ func (p *Provisioner) StartSession(snapshotID string, user resources.EphemeralUs
 }
 
 // StopSession stops an existing session.
-func (p *Provisioner) StopSession(session *resources.Session) error {
+func (p *Provisioner) StopSession(session *resources.Session, clone *models.Clone) error {
 	fsm, err := p.pm.GetFSManager(session.Pool)
 	if err != nil {
 		return errors.Wrap(err, "failed to find a filesystem manager of this session")
 	}
 
-	name := util.GetCloneName(session.Port)
-
-	if err := postgres.Stop(p.runner, fsm.Pool(), name); err != nil {
-		return errors.Wrap(err, "failed to stop a container")
-	}
+	name := clone.ID
 
-	if err := fsm.DestroyClone(name); err != nil {
-		return errors.Wrap(err, "failed to destroy a clone")
+	if err := postgres.Stop(p.runner, fsm.Pool(), name, clone.DB.Port); err != nil {
+		return errors.Wrap(err, "failed to stop container")
 	}
 
 	if err := p.FreePort(session.Port); err != nil {
@@ -241,13 +237,13 @@ func (p *Provisioner) StopSession(session *resources.Session) error {
 }
 
 // ResetSession resets an existing session.
-func (p *Provisioner) ResetSession(session *resources.Session, snapshotID string) (*models.Snapshot, error) {
+func (p *Provisioner) ResetSession(session *resources.Session, clone *models.Clone, snapshotID string) (*models.Snapshot, error) {
 	fsm, err := p.pm.GetFSManager(session.Pool)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to find filesystem manager of this session")
 	}
 
-	name := util.GetCloneName(session.Port)
+	name := clone.ID
 
 	snapshot, err := p.getSnapshot(snapshotID)
 	if err != nil {
@@ -270,23 +266,25 @@ func (p *Provisioner) ResetSession(session *resources.Session, snapshotID string
 
 	defer func() {
 		if err != nil {
-			p.revertSession(newFSManager, name)
+			p.revertSession(newFSManager, clone.Branch, name, clone.DB.Port, clone.Revision)
 		}
 	}()
 
-	if err = postgres.Stop(p.runner, fsm.Pool(), name); err != nil {
+	if err = postgres.Stop(p.runner, fsm.Pool(), name, clone.DB.Port); err != nil {
 		return nil, errors.Wrap(err, "failed to stop container")
 	}
 
-	if err = fsm.DestroyClone(name); err != nil {
-		return nil, errors.Wrap(err, "failed to destroy clone")
+	if clone.Revision == branching.DefaultRevision || !clone.HasDependent {
+		if err = fsm.DestroyClone(clone.Branch, name, clone.Revision); err != nil {
+			return nil, errors.Wrap(err, "failed to destroy clone")
+		}
 	}
 
-	if err = newFSManager.CreateClone(name, snapshot.ID); err != nil {
+	if err = newFSManager.CreateClone(clone.Branch, name, snapshot.ID, clone.Revision); err != nil {
 		return nil, errors.Wrap(err, "failed to create clone")
 	}
 
-	appConfig := p.getAppConfig(newFSManager.Pool(), name, session.Port)
+	appConfig := p.getAppConfig(newFSManager.Pool(), clone.Branch, name, clone.Revision, session.Port)
 	appConfig.SetExtraConf(session.ExtraConfig)
 
 	if err := fs.CleanupLogsDir(appConfig.DataDir()); err != nil {
@@ -328,13 +326,13 @@ func (p *Provisioner) GetSnapshots() ([]resources.Snapshot, error) {
 }
 
 // GetSessionState describes the state of the session.
-func (p *Provisioner) GetSessionState(s *resources.Session) (*resources.SessionState, error) {
+func (p *Provisioner) GetSessionState(s *resources.Session, branch, cloneID string) (*resources.SessionState, error) {
 	fsm, err := p.pm.GetFSManager(s.Pool)
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to find a filesystem manager of this session")
+		return nil, errors.Wrap(err, "failed to find filesystem manager of this session")
 	}
 
-	return fsm.GetSessionState(util.GetCloneName(s.Port))
+	return fsm.GetSessionState(branch, cloneID)
 }
 
 // GetPoolEntryList provides an ordered list of available pools.
@@ -389,15 +387,15 @@ func buildPoolEntry(fsm pool.FSManager) (models.PoolEntry, error) {
 }
 
 // Other methods.
-func (p *Provisioner) revertSession(fsm pool.FSManager, name string) {
-	log.Dbg(`Reverting start of a session...`)
+func (p *Provisioner) revertSession(fsm pool.FSManager, branch, name, port string, revision int) {
+	log.Dbg(`Reverting start of session...`)
 
-	if runnerErr := postgres.Stop(p.runner, fsm.Pool(), name); runnerErr != nil {
-		log.Err("Stop Postgres:", runnerErr)
+	if runnerErr := postgres.Stop(p.runner, fsm.Pool(), name, port); runnerErr != nil {
+		log.Err("stop Postgres:", runnerErr)
 	}
 
-	if runnerErr := fsm.DestroyClone(name); runnerErr != nil {
-		log.Err("Destroy clone:", runnerErr)
+	if runnerErr := fsm.DestroyClone(branch, name, revision); runnerErr != nil {
+		log.Err("destroy clone:", runnerErr)
 	}
 }
 
@@ -590,7 +588,9 @@ func (p *Provisioner) stopPoolSessions(fsm pool.FSManager, exceptClones map[stri
 
 		log.Dbg("Stopping container:", instance)
 
-		if err = postgres.Stop(p.runner, fsPool, instance); err != nil {
+		port := "" // TODO: check this case to prevent removing active sockets.
+
+		if err = postgres.Stop(p.runner, fsPool, instance, port); err != nil {
 			return errors.Wrap(err, "failed to container")
 		}
 	}
@@ -607,7 +607,10 @@ func (p *Provisioner) stopPoolSessions(fsm pool.FSManager, exceptClones map[stri
 			continue
 		}
 
-		if err := fsm.DestroyClone(clone); err != nil {
+		branchName := branching.DefaultBranch // TODO: extract branch from name OR pass as an argument.
+		revision := branching.DefaultRevision // TODO: the same for the revision.
+
+		if err := fsm.DestroyClone(branchName, clone, revision); err != nil {
 			return err
 		}
 	}
@@ -615,11 +618,13 @@ func (p *Provisioner) stopPoolSessions(fsm pool.FSManager, exceptClones map[stri
 	return nil
 }
 
-func (p *Provisioner) getAppConfig(pool *resources.Pool, name string, port uint) *resources.AppConfig {
+func (p *Provisioner) getAppConfig(pool *resources.Pool, branch, name string, rev int, port uint) *resources.AppConfig {
 	provisionHosts := p.getProvisionHosts()
 
 	appConfig := &resources.AppConfig{
 		CloneName:      name,
+		Branch:         branch,
+		Revision:       rev,
 		DockerImage:    p.config.DockerImage,
 		Host:           pool.SocketCloneDir(name),
 		Port:           port,
@@ -655,16 +660,17 @@ func (p *Provisioner) getProvisionHosts() string {
 }
 
 // LastSessionActivity returns the time of the last session activity.
-func (p *Provisioner) LastSessionActivity(session *resources.Session, minimumTime time.Time) (*time.Time, error) {
+func (p *Provisioner) LastSessionActivity(session *resources.Session, branch, cloneID string, revision int,
+	minimumTime time.Time) (*time.Time, error) {
 	fsm, err := p.pm.GetFSManager(session.Pool)
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to find a filesystem manager")
+		return nil, errors.Wrap(err, "failed to find filesystem manager")
 	}
 
 	ctx, cancel := context.WithCancel(p.ctx)
 	defer cancel()
 
-	clonePath := fsm.Pool().ClonePath(session.Port)
+	clonePath := fsm.Pool().ClonePath(branch, cloneID, revision)
 	fileSelector := pglog.NewSelector(clonePath)
 
 	if err := fileSelector.DiscoverLogDir(); err != nil {
@@ -734,7 +740,7 @@ func (p *Provisioner) scanCSVLogFile(ctx context.Context, filename string, avail
 
 	defer func() {
 		if err := csvFile.Close(); err != nil {
-			log.Errf("Failed to close a CSV log file: %s", err.Error())
+			log.Errf("failed to close CSV log file: %s", err.Error())
 		}
 	}()
 
diff --git a/engine/internal/provision/mode_local_test.go b/engine/internal/provision/mode_local_test.go
index 02fe78a3..72c70e13 100644
--- a/engine/internal/provision/mode_local_test.go
+++ b/engine/internal/provision/mode_local_test.go
@@ -14,6 +14,7 @@ import (
 
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
 
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 )
@@ -66,11 +67,11 @@ type mockFSManager struct {
 	cloneList []string
 }
 
-func (m mockFSManager) CreateClone(_, _ string) error {
+func (m mockFSManager) CreateClone(_, _, _ string, _ int) error {
 	return nil
 }
 
-func (m mockFSManager) DestroyClone(_ string) error {
+func (m mockFSManager) DestroyClone(_, _ string, _ int) error {
 	return nil
 }
 
@@ -82,7 +83,7 @@ func (m mockFSManager) CreateSnapshot(_, _ string) (snapshotName string, err err
 	return "", nil
 }
 
-func (m mockFSManager) DestroySnapshot(_ string) (err error) {
+func (m mockFSManager) DestroySnapshot(_ string, _ thinclones.DestroyOptions) (err error) {
 	return nil
 }
 
@@ -97,7 +98,7 @@ func (m mockFSManager) SnapshotList() []resources.Snapshot {
 func (m mockFSManager) RefreshSnapshotList() {
 }
 
-func (m mockFSManager) GetSessionState(_ string) (*resources.SessionState, error) {
+func (m mockFSManager) GetSessionState(_, _ string) (*resources.SessionState, error) {
 	return nil, nil
 }
 
@@ -109,6 +110,110 @@ func (m mockFSManager) Pool() *resources.Pool {
 	return m.pool
 }
 
+func (m mockFSManager) InitBranching() error {
+	return nil
+}
+
+func (m mockFSManager) VerifyBranchMetadata() error {
+	return nil
+}
+
+func (m mockFSManager) CreateDataset(_ string) error {
+	return nil
+}
+
+func (m mockFSManager) CreateBranch(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) DestroyDataset(_ string) error {
+	return nil
+}
+
+func (m mockFSManager) Snapshot(_ string) error {
+	return nil
+}
+
+func (m mockFSManager) Reset(_ string, _ thinclones.ResetOptions) error {
+	return nil
+}
+
+func (m mockFSManager) ListBranches() (map[string]string, error) {
+	return nil, nil
+}
+
+func (m mockFSManager) ListAllBranches(_ []string) ([]models.BranchEntity, error) {
+	return nil, nil
+}
+
+func (m mockFSManager) GetSnapshotProperties(_ string) (thinclones.SnapshotProperties, error) {
+	return thinclones.SnapshotProperties{}, nil
+}
+
+func (m mockFSManager) AddBranchProp(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) DeleteBranchProp(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) SetRelation(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) SetRoot(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) GetRepo() (*models.Repo, error) {
+	return nil, nil
+}
+
+func (m mockFSManager) GetAllRepo() (*models.Repo, error) {
+	return nil, nil
+}
+
+func (m mockFSManager) SetDSA(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) SetMessage(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) SetMountpoint(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) Move(_, _, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) Rename(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) DeleteBranch(_ string) error {
+	return nil
+}
+
+func (m mockFSManager) DeleteChildProp(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) DeleteRootProp(_, _ string) error {
+	return nil
+}
+
+func (m mockFSManager) HasDependentEntity(_ string) ([]string, error) {
+	return nil, nil
+}
+
+func (m mockFSManager) KeepRelation(_ string) error {
+	return nil
+}
+
 func TestBuildPoolEntry(t *testing.T) {
 	testCases := []struct {
 		pool          *resources.Pool
diff --git a/engine/internal/provision/pool/manager.go b/engine/internal/provision/pool/manager.go
index 74c41171..1c63a6a2 100644
--- a/engine/internal/provision/pool/manager.go
+++ b/engine/internal/provision/pool/manager.go
@@ -13,6 +13,7 @@ import (
 
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones/lvm"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones/zfs"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
@@ -25,30 +26,60 @@ type FSManager interface {
 	Snapshotter
 	StateReporter
 	Pooler
+	Branching
 }
 
 // Cloner describes methods of clone management.
 type Cloner interface {
-	CreateClone(name, snapshotID string) error
-	DestroyClone(name string) error
+	CreateClone(branch, name, snapshotID string, revision int) error
+	DestroyClone(branch, name string, revision int) error
 	ListClonesNames() ([]string, error)
 }
 
 // StateReporter describes methods of state reporting.
 type StateReporter interface {
-	GetSessionState(name string) (*resources.SessionState, error)
+	GetSessionState(branch, name string) (*resources.SessionState, error)
 	GetFilesystemState() (models.FileSystem, error)
 }
 
 // Snapshotter describes methods of snapshot management.
 type Snapshotter interface {
 	CreateSnapshot(poolSuffix, dataStateAt string) (snapshotName string, err error)
-	DestroySnapshot(snapshotName string) (err error)
+	DestroySnapshot(snapshotName string, options thinclones.DestroyOptions) (err error)
 	CleanupSnapshots(retentionLimit int) ([]string, error)
 	SnapshotList() []resources.Snapshot
 	RefreshSnapshotList()
 }
 
+// Branching describes methods for data branching.
+type Branching interface {
+	InitBranching() error
+	VerifyBranchMetadata() error
+	CreateDataset(datasetName string) error
+	CreateBranch(branchName, snapshotID string) error
+	DestroyDataset(branchName string) (err error)
+	ListBranches() (map[string]string, error)
+	ListAllBranches(filterPools []string) ([]models.BranchEntity, error)
+	GetRepo() (*models.Repo, error)
+	GetAllRepo() (*models.Repo, error)
+	SetRelation(parent, snapshotName string) error
+	Snapshot(snapshotName string) error
+	Move(baseSnap, currentSnap, target string) error
+	SetMountpoint(path, branch string) error
+	Rename(oldName, branch string) error
+	GetSnapshotProperties(snapshotName string) (thinclones.SnapshotProperties, error)
+	AddBranchProp(branch, snapshotName string) error
+	DeleteBranchProp(branch, snapshotName string) error
+	DeleteChildProp(childSnapshot, snapshotName string) error
+	DeleteRootProp(branch, snapshotName string) error
+	SetRoot(branch, snapshotName string) error
+	SetDSA(dsa, snapshotName string) error
+	SetMessage(message, snapshotName string) error
+	Reset(snapshotID string, options thinclones.ResetOptions) error
+	HasDependentEntity(snapshotName string) ([]string, error)
+	KeepRelation(snapshotName string) error
+}
+
 // Pooler describes methods for Pool providing.
 type Pooler interface {
 	Pool() *resources.Pool
diff --git a/engine/internal/provision/pool/pool_manager.go b/engine/internal/provision/pool/pool_manager.go
index fb56f80e..fc35da3a 100644
--- a/engine/internal/provision/pool/pool_manager.go
+++ b/engine/internal/provision/pool/pool_manager.go
@@ -30,6 +30,9 @@ const (
 	ext4 = "ext4"
 )
 
+// ErrNoPools means that there no available pools.
+var ErrNoPools = errors.New("no available pools")
+
 // Manager describes a pool manager.
 type Manager struct {
 	cfg              *Config
@@ -144,7 +147,7 @@ func (pm *Manager) GetFSManager(name string) (FSManager, error) {
 	pm.mu.Unlock()
 
 	if !ok {
-		return nil, errors.New("pool manager not found")
+		return nil, fmt.Errorf("pool manager not found: %s", name)
 	}
 
 	return fsm, nil
@@ -240,7 +243,7 @@ func (pm *Manager) ReloadPools() error {
 	fsPools, fsManagerList := pm.examineEntries(dirEntries)
 
 	if len(fsPools) == 0 {
-		return errors.New("no available pools")
+		return ErrNoPools
 	}
 
 	pm.mu.Lock()
@@ -294,7 +297,6 @@ func (pm *Manager) examineEntries(entries []os.DirEntry) (map[string]FSManager,
 			Name:           entry.Name(),
 			PoolDirName:    entry.Name(),
 			MountDir:       pm.cfg.MountDir,
-			CloneSubDir:    pm.cfg.CloneSubDir,
 			DataSubDir:     pm.cfg.DataSubDir,
 			SocketSubDir:   pm.cfg.SocketSubDir,
 			ObserverSubDir: pm.cfg.ObserverSubDir,
diff --git a/engine/internal/provision/resources/appconfig.go b/engine/internal/provision/resources/appconfig.go
index 94a37c40..f05f5266 100644
--- a/engine/internal/provision/resources/appconfig.go
+++ b/engine/internal/provision/resources/appconfig.go
@@ -6,11 +6,15 @@ package resources
 
 import (
 	"path"
+
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 )
 
 // AppConfig currently stores Postgres configuration (other application in the future too).
 type AppConfig struct {
 	CloneName      string
+	Branch         string
+	Revision       int
 	DockerImage    string
 	Pool           *Pool
 	Host           string
@@ -32,13 +36,13 @@ type DB struct {
 // CloneDir returns the path of the clone directory.
 func (c *AppConfig) CloneDir() string {
 	// TODO(akartasov): Move to pool.
-	return path.Join(c.Pool.ClonesDir(), c.CloneName)
+	return path.Join(c.Pool.ClonesDir(c.Branch), c.CloneName, branching.RevisionSegment(c.Revision))
 }
 
 // DataDir returns the path of clone data.
 func (c *AppConfig) DataDir() string {
 	// TODO(akartasov): Move to pool.
-	return path.Join(c.Pool.ClonesDir(), c.CloneName, c.Pool.DataSubDir)
+	return path.Join(c.Pool.ClonesDir(c.Branch), c.CloneName, branching.RevisionSegment(c.Revision), c.Pool.DataSubDir)
 }
 
 // ExtraConf returns a map with an extra configuration.
diff --git a/engine/internal/provision/resources/pool.go b/engine/internal/provision/resources/pool.go
index 1fd5b28e..0f4e695e 100644
--- a/engine/internal/provision/resources/pool.go
+++ b/engine/internal/provision/resources/pool.go
@@ -9,7 +9,7 @@ import (
 	"sync"
 	"time"
 
-	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 )
 
 // PoolStatus represents a pool status.
@@ -65,18 +65,28 @@ func (p *Pool) SocketDir() string {
 }
 
 // ObserverDir returns a path to the observer directory of the storage pool.
-func (p *Pool) ObserverDir(port uint) string {
-	return path.Join(p.ClonePath(port), p.ObserverSubDir)
+func (p *Pool) ObserverDir(branch, name string, revision int) string {
+	return path.Join(p.ClonePath(branch, name, revision), p.ObserverSubDir)
 }
 
 // ClonesDir returns a path to the clones directory of the storage pool.
-func (p *Pool) ClonesDir() string {
-	return path.Join(p.MountDir, p.PoolDirName, p.CloneSubDir)
+func (p *Pool) ClonesDir(branch string) string {
+	return path.Join(p.MountDir, p.PoolDirName, branching.BranchDir, branch)
 }
 
-// ClonePath returns a path to the initialized clone directory.
-func (p *Pool) ClonePath(port uint) string {
-	return path.Join(p.MountDir, p.PoolDirName, p.CloneSubDir, util.GetCloneName(port), p.DataSubDir)
+// ClonePath returns a path to the data clone directory.
+func (p *Pool) ClonePath(branchName, name string, revision int) string {
+	return path.Join(p.MountDir, p.PoolDirName, branching.BranchDir, branchName, name, branching.RevisionSegment(revision), p.DataSubDir)
+}
+
+// CloneLocation returns a path to the initialized clone directory.
+func (p *Pool) CloneLocation(branchName, name string, revision int) string {
+	return path.Join(p.MountDir, p.PoolDirName, branching.BranchDir, branchName, name, branching.RevisionSegment(revision))
+}
+
+// CloneRevisionLocation returns a path to the clone revisions.
+func (p *Pool) CloneRevisionLocation(branchName, name string) string {
+	return path.Join(p.MountDir, p.PoolDirName, branching.BranchDir, branchName, name)
 }
 
 // SocketCloneDir returns a path to the socket clone directory.
@@ -84,6 +94,21 @@ func (p *Pool) SocketCloneDir(name string) string {
 	return path.Join(p.SocketDir(), name)
 }
 
+// BranchName returns a full branch name in the data pool.
+func (p *Pool) BranchName(poolName, branchName string) string {
+	return branching.BranchName(poolName, branchName)
+}
+
+// CloneDataset returns a full clone dataset in the data pool.
+func (p *Pool) CloneDataset(branchName, cloneName string) string {
+	return branching.CloneDataset(p.Name, branchName, cloneName)
+}
+
+// CloneName returns a full clone name in the data pool.
+func (p *Pool) CloneName(branchName, cloneName string, revision int) string {
+	return branching.CloneName(p.Name, branchName, cloneName, revision)
+}
+
 // Status gets the pool status.
 func (p *Pool) Status() PoolStatus {
 	p.mu.RLock()
diff --git a/engine/internal/provision/resources/resources.go b/engine/internal/provision/resources/resources.go
index 201f9e11..1a5538ee 100644
--- a/engine/internal/provision/resources/resources.go
+++ b/engine/internal/provision/resources/resources.go
@@ -33,12 +33,14 @@ type EphemeralUser struct {
 
 // Snapshot defines snapshot of the data with related meta-information.
 type Snapshot struct {
-	ID                string
-	CreatedAt         time.Time
-	DataStateAt       time.Time
-	Used              uint64
-	LogicalReferenced uint64
-	Pool              string
+	ID                string    `json:"id"`
+	CreatedAt         time.Time `json:"createdAt"`
+	DataStateAt       time.Time `json:"dataStateAt"`
+	Used              uint64    `json:"used"`
+	LogicalReferenced uint64    `json:"logicalReferenced"`
+	Pool              string    `json:"pool"`
+	Branch            string    `json:"branch"`
+	Message           string    `json:"message"`
 }
 
 // SessionState defines current state of a Session.
diff --git a/engine/internal/provision/thinclones/lvm/lvmanager.go b/engine/internal/provision/thinclones/lvm/lvmanager.go
index 35da7082..8afc4c74 100644
--- a/engine/internal/provision/thinclones/lvm/lvmanager.go
+++ b/engine/internal/provision/thinclones/lvm/lvmanager.go
@@ -12,6 +12,7 @@ import (
 
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 )
@@ -53,13 +54,13 @@ func (m *LVManager) UpdateConfig(pool *resources.Pool) {
 }
 
 // CreateClone creates a new volume.
-func (m *LVManager) CreateClone(name, _ string) error {
-	return CreateVolume(m.runner, m.volumeGroup, m.logicalVolume, name, m.pool.ClonesDir())
+func (m *LVManager) CreateClone(branch, name, _ string, _ int) error {
+	return CreateVolume(m.runner, m.volumeGroup, m.logicalVolume, name, m.pool.ClonesDir(branch))
 }
 
 // DestroyClone destroys volumes.
-func (m *LVManager) DestroyClone(name string) error {
-	return RemoveVolume(m.runner, m.volumeGroup, m.logicalVolume, name, m.pool.ClonesDir())
+func (m *LVManager) DestroyClone(branch, name string, _ int) error {
+	return RemoveVolume(m.runner, m.volumeGroup, m.logicalVolume, name, m.pool.ClonesDir(branch))
 }
 
 // ListClonesNames returns a list of clone names.
@@ -98,7 +99,7 @@ func (m *LVManager) CreateSnapshot(_, _ string) (string, error) {
 }
 
 // DestroySnapshot is not supported in LVM mode.
-func (m *LVManager) DestroySnapshot(_ string) error {
+func (m *LVManager) DestroySnapshot(_ string, _ thinclones.DestroyOptions) error {
 	log.Msg("Destroying a snapshot is not supported in LVM mode. Skip the operation.")
 
 	return nil
@@ -130,7 +131,7 @@ func (m *LVManager) RefreshSnapshotList() {
 }
 
 // GetSessionState is not implemented.
-func (m *LVManager) GetSessionState(_ string) (*resources.SessionState, error) {
+func (m *LVManager) GetSessionState(_, _ string) (*resources.SessionState, error) {
 	// TODO(anatoly): Implement.
 	return &resources.SessionState{}, nil
 }
@@ -140,3 +141,178 @@ func (m *LVManager) GetFilesystemState() (models.FileSystem, error) {
 	// TODO(anatoly): Implement.
 	return models.FileSystem{Mode: PoolMode}, nil
 }
+
+// InitBranching inits data branching.
+func (m *LVManager) InitBranching() error {
+	log.Msg("InitBranching is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// VerifyBranchMetadata checks snapshot metadata.
+func (m *LVManager) VerifyBranchMetadata() error {
+	log.Msg("VerifyBranchMetadata is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// CreateDataset creates a new dataset.
+func (m *LVManager) CreateDataset(_ string) error {
+	log.Msg("CreateDataset is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// CreateBranch clones data as a new branch.
+func (m *LVManager) CreateBranch(_, _ string) error {
+	log.Msg("CreateBranch is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// DestroyDataset destroys dataset.
+func (m *LVManager) DestroyDataset(_ string) error {
+	log.Msg("DestroyDataset is not supported for LVM; skipping operation")
+
+	return nil
+}
+
+// Snapshot takes a snapshot of the current data state.
+func (m *LVManager) Snapshot(_ string) error {
+	log.Msg("Snapshot is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// Reset rollbacks data to ZFS snapshot.
+func (m *LVManager) Reset(_ string, _ thinclones.ResetOptions) error {
+	log.Msg("Reset is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// ListBranches lists data pool branches.
+func (m *LVManager) ListBranches() (map[string]string, error) {
+	log.Msg("ListBranches is not supported for LVM. Skip the operation")
+
+	return nil, nil
+}
+
+// ListAllBranches lists all branches.
+func (m *LVManager) ListAllBranches(_ []string) ([]models.BranchEntity, error) {
+	log.Msg("ListAllBranches is not supported for LVM. Skip the operation")
+
+	return nil, nil
+}
+
+// GetSnapshotProperties get custom snapshot properties.
+func (m *LVManager) GetSnapshotProperties(_ string) (thinclones.SnapshotProperties, error) {
+	log.Msg("GetSnapshotProperties is not supported for LVM. Skip the operation")
+
+	return thinclones.SnapshotProperties{}, nil
+}
+
+// AddBranchProp adds branch to snapshot property.
+func (m *LVManager) AddBranchProp(_, _ string) error {
+	log.Msg("AddBranchProp is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// DeleteBranchProp deletes branch from snapshot property.
+func (m *LVManager) DeleteBranchProp(_, _ string) error {
+	log.Msg("DeleteBranchProp is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// DeleteChildProp deletes child from snapshot property.
+func (m *LVManager) DeleteChildProp(_, _ string) error {
+	log.Msg("DeleteChildProp is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// DeleteRootProp deletes root from snapshot property.
+func (m *LVManager) DeleteRootProp(_, _ string) error {
+	log.Msg("DeleteRootProp is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// SetRelation sets relation between snapshots.
+func (m *LVManager) SetRelation(_, _ string) error {
+	log.Msg("SetRelation is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// SetRoot marks snapshot as a root of branch.
+func (m *LVManager) SetRoot(_, _ string) error {
+	log.Msg("SetRoot is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// GetRepo provides data repository details.
+func (m *LVManager) GetRepo() (*models.Repo, error) {
+	log.Msg("GetRepo is not supported for LVM. Skip the operation")
+
+	return nil, nil
+}
+
+// GetAllRepo provides data repository details.
+func (m *LVManager) GetAllRepo() (*models.Repo, error) {
+	log.Msg("GetAllRepo is not supported for LVM. Skip the operation")
+
+	return nil, nil
+}
+
+// SetDSA sets value of DataStateAt to snapshot.
+func (m *LVManager) SetDSA(_, _ string) error {
+	log.Msg("SetDSA is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// SetMessage sets commit message to snapshot.
+func (m *LVManager) SetMessage(_, _ string) error {
+	log.Msg("SetMessage is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// SetMountpoint sets clone mount point.
+func (m *LVManager) SetMountpoint(_, _ string) error {
+	log.Msg("SetMountpoint is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// Rename renames clone.
+func (m *LVManager) Rename(_, _ string) error {
+	log.Msg("Rename is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// Move moves snapshot diff.
+func (m *LVManager) Move(_, _, _ string) error {
+	log.Msg("Move is not supported for LVM. Skip the operation")
+
+	return nil
+}
+
+// HasDependentEntity checks if snapshot has dependent entities.
+func (m *LVManager) HasDependentEntity(_ string) ([]string, error) {
+	log.Msg("HasDependentEntity is not supported for LVM. Skip the operation")
+
+	return nil, nil
+}
+
+// KeepRelation keeps relation between adjacent snapshots.
+func (m *LVManager) KeepRelation(_ string) error {
+	log.Msg("KeepRelation is not supported for LVM. Skip the operation")
+
+	return nil
+}
diff --git a/engine/internal/provision/thinclones/manager.go b/engine/internal/provision/thinclones/manager.go
index b830fad9..648d8c87 100644
--- a/engine/internal/provision/thinclones/manager.go
+++ b/engine/internal/provision/thinclones/manager.go
@@ -9,6 +9,12 @@ import (
 	"fmt"
 )
 
+// ResetOptions defines reset options.
+type ResetOptions struct {
+	// -f
+	// -r
+}
+
 // SnapshotExistsError defines an error when snapshot already exists.
 type SnapshotExistsError struct {
 	name string
@@ -23,3 +29,20 @@ func NewSnapshotExistsError(name string) *SnapshotExistsError {
 func (e *SnapshotExistsError) Error() string {
 	return fmt.Sprintf(`snapshot %s already exists`, e.name)
 }
+
+// DestroyOptions provides options for destroy commands.
+type DestroyOptions struct {
+	Force bool
+}
+
+// SnapshotProperties describe custom properties of the dataset.
+type SnapshotProperties struct {
+	Name        string
+	Parent      string
+	Child       string
+	Branch      string
+	Root        string
+	DataStateAt string
+	Message     string
+	Clones      string
+}
diff --git a/engine/internal/provision/thinclones/zfs/branching.go b/engine/internal/provision/thinclones/zfs/branching.go
new file mode 100644
index 00000000..f446edc9
--- /dev/null
+++ b/engine/internal/provision/thinclones/zfs/branching.go
@@ -0,0 +1,685 @@
+/*
+2022 © Postgres.ai
+*/
+
+package zfs
+
+import (
+	"bytes"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"strings"
+
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
+)
+
+const (
+	branchProp  = "dle:branch"
+	parentProp  = "dle:parent"
+	childProp   = "dle:child"
+	rootProp    = "dle:root"
+	messageProp = "dle:message"
+	branchSep   = ","
+	empty       = "-"
+)
+
+type cmdCfg struct {
+	pool string
+}
+
+// InitBranching inits data branching.
+func (m *Manager) InitBranching() error {
+	snapshots := m.SnapshotList()
+
+	numberSnapshots := len(snapshots)
+
+	if numberSnapshots == 0 {
+		log.Dbg("no snapshots to init data branching")
+		return nil
+	}
+
+	latest := snapshots[0]
+
+	if getPoolPrefix(latest.ID) != m.config.Pool.Name {
+		for _, s := range snapshots {
+			if s.Pool == m.config.Pool.Name {
+				latest = s
+				break
+			}
+		}
+	}
+
+	latestBranchProperty, err := m.getProperty(branchProp, latest.ID)
+	if err != nil {
+		return fmt.Errorf("failed to read snapshot property: %w", err)
+	}
+
+	if latestBranchProperty != "" && latestBranchProperty != "-" {
+		log.Dbg("data branching is already initialized")
+
+		return nil
+	}
+
+	if err := m.AddBranchProp(branching.DefaultBranch, latest.ID); err != nil {
+		return fmt.Errorf("failed to add branch property: %w", err)
+	}
+
+	leader := latest
+
+	for i := 1; i < numberSnapshots; i++ {
+		follower := snapshots[i]
+
+		if getPoolPrefix(leader.ID) != getPoolPrefix(follower.ID) {
+			continue
+		}
+
+		if err := m.SetRelation(leader.ID, follower.ID); err != nil {
+			return fmt.Errorf("failed to set snapshot relations: %w", err)
+		}
+
+		brProperty, err := m.getProperty(branchProp, follower.ID)
+		if err != nil {
+			return fmt.Errorf("failed to read branch property: %w", err)
+		}
+
+		if brProperty == branching.DefaultBranch {
+			if err := m.DeleteBranchProp(branching.DefaultBranch, follower.ID); err != nil {
+				return fmt.Errorf("failed to delete default branch property: %w", err)
+			}
+
+			break
+		}
+
+		leader = follower
+	}
+
+	// If not exists pool/branch/main, init main branch dataset.
+	brName := m.Pool().BranchName(m.Pool().Name, branching.DefaultBranch)
+
+	if err := m.CreateDataset(brName); err != nil {
+		return fmt.Errorf("failed to init main branch dataset: %w", err)
+	}
+
+	m.RefreshSnapshotList()
+
+	log.Msg("data branching has been successfully initialized")
+
+	return nil
+}
+
+func getPoolPrefix(pool string) string {
+	return strings.Split(pool, "@")[0]
+}
+
+// VerifyBranchMetadata verifies data branching metadata.
+func (m *Manager) VerifyBranchMetadata() error {
+	snapshots := m.SnapshotList()
+
+	numberSnapshots := len(snapshots)
+
+	if numberSnapshots == 0 {
+		log.Dbg("no snapshots to verify data branching")
+		return nil
+	}
+
+	latest := snapshots[0]
+
+	brName, err := m.getProperty(branchProp, latest.ID)
+	if err != nil {
+		log.Dbg("cannot find branch for snapshot", latest.ID, err.Error())
+	}
+
+	for i := numberSnapshots; i > 1; i-- {
+		if err := m.SetRelation(snapshots[i-1].ID, snapshots[i-2].ID); err != nil {
+			return fmt.Errorf("failed to set snapshot relations: %w", err)
+		}
+
+		if brName == "" {
+			brName, err = m.getProperty(branchProp, snapshots[i-1].ID)
+			if err != nil {
+				log.Dbg("cannot find branch for snapshot", snapshots[i-1].ID, err.Error())
+			}
+		}
+	}
+
+	if brName == "" {
+		brName = branching.DefaultBranch
+	}
+
+	if err := m.AddBranchProp(brName, latest.ID); err != nil {
+		return fmt.Errorf("failed to add branch property: %w", err)
+	}
+
+	log.Msg("data branching has been verified")
+
+	return nil
+}
+
+// CreateBranch clones data as a new branch.
+func (m *Manager) CreateBranch(branchName, snapshotID string) error {
+	// zfs clone -p pool@snapshot_20221019094237 pool/branch/001-branch
+	cmd := []string{
+		"zfs clone -p", snapshotID, branchName,
+	}
+
+	out, err := m.runner.Run(strings.Join(cmd, " "))
+	if err != nil {
+		return fmt.Errorf("zfs clone error: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
+
+// Snapshot takes a snapshot of the current data state.
+func (m *Manager) Snapshot(snapshotName string) error {
+	cmd := []string{
+		"zfs snapshot ", snapshotName,
+	}
+
+	out, err := m.runner.Run(strings.Join(cmd, " "))
+	if err != nil {
+		return fmt.Errorf("zfs snapshot error: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
+
+// Move sends and receives snapshot diff.
+func (m *Manager) Move(baseSnap, currentSnap, target string) error {
+	cmd := fmt.Sprintf(
+		"zfs send -I %s %s | zfs receive -F %s", baseSnap, currentSnap, target,
+	)
+
+	out, err := m.runner.Run(cmd)
+	if err != nil {
+		return fmt.Errorf("zfs moving snapshot error: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
+
+// Rename renames clone.
+func (m *Manager) Rename(oldName, newName string) error {
+	cmd := []string{
+		"zfs rename -p", oldName, newName,
+	}
+
+	out, err := m.runner.Run(strings.Join(cmd, " "))
+	if err != nil {
+		return fmt.Errorf("zfs renaming error: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
+
+// SetMountpoint sets clone mount point.
+func (m *Manager) SetMountpoint(path, name string) error {
+	cmd := []string{
+		"zfs set", "mountpoint=" + path, name,
+	}
+
+	out, err := m.runner.Run(strings.Join(cmd, " "))
+	if err != nil {
+		return fmt.Errorf("zfs mountpoint error: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
+
+// ListBranches lists data pool branches.
+func (m *Manager) ListBranches() (map[string]string, error) {
+	return m.listBranches()
+}
+
+// ListAllBranches lists all branches.
+func (m *Manager) ListAllBranches(poolList []string) ([]models.BranchEntity, error) {
+	poolFilter := ""
+
+	if len(poolList) > 0 {
+		poolFilter += "-r " + strings.Join(poolList, " ")
+	}
+
+	cmd := fmt.Sprintf(
+		// Get all ZFS snapshots (-t) with options (-o) without output headers (-H).
+		// Excluding snapshots without "dle:branch" property ("grep -v").
+		`zfs list -H -t snapshot -o %s,name %s | grep -v "^-" | cat`, branchProp, poolFilter,
+	)
+
+	out, err := m.runner.Run(cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to list branches: %w. Out: %v", err, out)
+	}
+
+	branches := make([]models.BranchEntity, 0)
+	lines := strings.Split(strings.TrimSpace(out), "\n")
+
+	const expectedColumns = 2
+
+	for _, line := range lines {
+		fields := strings.Fields(line)
+
+		if len(fields) != expectedColumns {
+			continue
+		}
+
+		if !strings.Contains(fields[0], branchSep) {
+			branches = append(branches, models.BranchEntity{Name: fields[0], SnapshotID: fields[1]})
+			continue
+		}
+
+		for _, branchName := range strings.Split(fields[0], branchSep) {
+			branches = append(branches, models.BranchEntity{Name: branchName, SnapshotID: fields[1]})
+		}
+	}
+
+	return branches, nil
+}
+
+func (m *Manager) listBranches() (map[string]string, error) {
+	cmd := fmt.Sprintf(
+		// Get ZFS snapshots (-t) with options (-o) without output headers (-H) filtered by pool (-r).
+		// Excluding snapshots without "dle:branch" property ("grep -v").
+		`zfs list -H -t snapshot -o %s,name -r %s | grep -v "^-" | cat`, branchProp, m.config.Pool.Name,
+	)
+
+	out, err := m.runner.Run(cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to list branches: %w. Out: %v", err, out)
+	}
+
+	branches := make(map[string]string)
+	lines := strings.Split(strings.TrimSpace(out), "\n")
+
+	const expectedColumns = 2
+
+	for _, line := range lines {
+		fields := strings.Fields(line)
+
+		if len(fields) != expectedColumns {
+			continue
+		}
+
+		if !strings.Contains(fields[0], branchSep) {
+			branches[fields[0]] = fields[1]
+			continue
+		}
+
+		for _, branchName := range strings.Split(fields[0], branchSep) {
+			branches[branchName] = fields[1]
+		}
+	}
+
+	return branches, nil
+}
+
+var repoFields = []any{"name", parentProp, childProp, branchProp, rootProp, dataStateAtLabel, messageProp, "clones"}
+
+// GetRepo provides repository details about snapshots and branches filtered by data pool.
+func (m *Manager) GetRepo() (*models.Repo, error) {
+	return m.getRepo(cmdCfg{pool: m.config.Pool.Name})
+}
+
+// GetAllRepo provides all repository details about snapshots and branches.
+func (m *Manager) GetAllRepo() (*models.Repo, error) {
+	return m.getRepo(cmdCfg{})
+}
+
+func (m *Manager) getRepo(cmdCfg cmdCfg) (*models.Repo, error) {
+	strFields := bytes.TrimRight(bytes.Repeat([]byte(`%s,`), len(repoFields)), ",")
+
+	// Get ZFS snapshots (-t) with options (-o) without output headers (-H) filtered by pool (-r).
+	format := `zfs list -H -t snapshot -o ` + string(strFields)
+	args := repoFields
+
+	if cmdCfg.pool != "" {
+		format += " -r %s"
+
+		args = append(args, cmdCfg.pool)
+	}
+
+	out, err := m.runner.Run(fmt.Sprintf(format, args...))
+	if err != nil {
+		return nil, fmt.Errorf("failed to list branches: %w. Out: %v", err, out)
+	}
+
+	lines := strings.Split(strings.TrimSpace(out), "\n")
+
+	repo := models.NewRepo()
+
+	for _, line := range lines {
+		fields := strings.Fields(line)
+
+		if len(fields) != len(repoFields) {
+			log.Dbg(fmt.Sprintf("Skip invalid line: %#v\n", line))
+
+			continue
+		}
+
+		dataset, _, _ := strings.Cut(fields[0], "@")
+
+		snDetail := models.SnapshotDetails{
+			ID:          fields[0],
+			Parent:      fields[1],
+			Child:       unwindField(fields[2]),
+			Branch:      unwindField(fields[3]),
+			Root:        unwindField(fields[4]),
+			DataStateAt: strings.Trim(fields[5], empty),
+			Message:     decodeCommitMessage(fields[6]),
+			Dataset:     dataset,
+			Clones:      unwindField(fields[7]),
+		}
+
+		repo.Snapshots[fields[0]] = snDetail
+
+		for _, sn := range snDetail.Branch {
+			if sn == "" {
+				continue
+			}
+
+			repo.Branches[sn] = fields[0]
+		}
+	}
+
+	return repo, nil
+}
+
+func decodeCommitMessage(field string) string {
+	if field == "" || field == empty {
+		return field
+	}
+
+	decodedString, err := base64.StdEncoding.DecodeString(field)
+	if err != nil {
+		log.Dbg(fmt.Sprintf("Unable to decode commit message: %#v\n", field))
+		return field
+	}
+
+	return string(decodedString)
+}
+
+func unwindField(field string) []string {
+	trimValue := strings.Trim(field, empty)
+
+	if len(trimValue) == 0 {
+		return nil
+	}
+
+	if !strings.Contains(field, branchSep) {
+		return []string{trimValue}
+	}
+
+	items := make([]string, 0)
+	for _, item := range strings.Split(field, branchSep) {
+		items = append(items, strings.Trim(item, empty))
+	}
+
+	return items
+}
+
+// GetSnapshotProperties get custom snapshot properties.
+func (m *Manager) GetSnapshotProperties(snapshotName string) (thinclones.SnapshotProperties, error) {
+	strFields := bytes.TrimRight(bytes.Repeat([]byte(`%s,`), len(repoFields)), ",")
+
+	// Get ZFS snapshot (-t) with options (-o) without output headers (-H) filtered by snapshot.
+	format := `zfs list -H -t snapshot -o ` + string(strFields) + ` %s`
+
+	args := append(repoFields, snapshotName)
+
+	out, err := m.runner.Run(fmt.Sprintf(format, args...))
+	if err != nil {
+		log.Dbg(out)
+
+		return thinclones.SnapshotProperties{}, err
+	}
+
+	fields := strings.Fields(strings.TrimSpace(out))
+
+	if len(fields) != len(repoFields) {
+		log.Dbg("Retrieved fields values:", fields)
+
+		return thinclones.SnapshotProperties{}, errors.New("some snapshot properties could not be retrieved")
+	}
+
+	properties := thinclones.SnapshotProperties{
+		Name:        strings.Trim(fields[0], empty),
+		Parent:      strings.Trim(fields[1], empty),
+		Child:       strings.Trim(fields[2], empty),
+		Branch:      strings.Trim(fields[3], empty),
+		Root:        strings.Trim(fields[4], empty),
+		DataStateAt: strings.Trim(fields[5], empty),
+		Message:     decodeCommitMessage(fields[6]),
+		Clones:      strings.Trim(fields[7], empty),
+	}
+
+	return properties, nil
+}
+
+// AddBranchProp adds branch to snapshot property.
+func (m *Manager) AddBranchProp(branch, snapshotName string) error {
+	return m.addToSet(branchProp, snapshotName, branch)
+}
+
+// DeleteBranchProp deletes branch from snapshot property.
+func (m *Manager) DeleteBranchProp(branch, snapshotName string) error {
+	return m.deleteFromSet(branchProp, branch, snapshotName)
+}
+
+// SetRelation sets up relation between two snapshots.
+func (m *Manager) SetRelation(parent, snapshotName string) error {
+	if err := m.setParent(parent, snapshotName); err != nil {
+		return err
+	}
+
+	return m.addChild(parent, snapshotName)
+}
+
+// DeleteChildProp deletes child from snapshot property.
+func (m *Manager) DeleteChildProp(childSnapshot, snapshotName string) error {
+	return m.deleteFromSet(childProp, childSnapshot, snapshotName)
+}
+
+// DeleteRootProp deletes root from snapshot property.
+func (m *Manager) DeleteRootProp(branch, snapshotName string) error {
+	return m.deleteFromSet(rootProp, branch, snapshotName)
+}
+
+func (m *Manager) setParent(parent, snapshotName string) error {
+	return m.setProperty(parentProp, parent, snapshotName)
+}
+
+func (m *Manager) addChild(parent, snapshotName string) error {
+	return m.addToSet(childProp, parent, snapshotName)
+}
+
+// SetRoot marks snapshot as a root of branch.
+func (m *Manager) SetRoot(branch, snapshotName string) error {
+	return m.addToSet(rootProp, snapshotName, branch)
+}
+
+// SetDSA sets value of DataStateAt to snapshot.
+func (m *Manager) SetDSA(dsa, snapshotName string) error {
+	return m.setProperty(dataStateAtLabel, dsa, snapshotName)
+}
+
+// SetMessage uses the given message as the commit message.
+func (m *Manager) SetMessage(message, snapshotName string) error {
+	encodedMessage := base64.StdEncoding.EncodeToString([]byte(message))
+	return m.setProperty(messageProp, encodedMessage, snapshotName)
+}
+
+// HasDependentEntity gets the root property of the snapshot.
+func (m *Manager) HasDependentEntity(snapshotName string) ([]string, error) {
+	root, err := m.getProperty(rootProp, snapshotName)
+	if err != nil {
+		return nil, fmt.Errorf("failed to check root property: %w", err)
+	}
+
+	if root != "" {
+		log.Warn(fmt.Errorf("snapshot has dependent branches: %s", root))
+	}
+
+	child, err := m.getProperty(childProp, snapshotName)
+	if err != nil {
+		return nil, fmt.Errorf("failed to check snapshot child property: %w", err)
+	}
+
+	if child != "" {
+		log.Warn(fmt.Sprintf("snapshot %s has dependent snapshots: %s", snapshotName, child))
+	}
+
+	clones, err := m.checkDependentClones(snapshotName)
+	if err != nil {
+		return nil, fmt.Errorf("failed to check dependent clones: %w", err)
+	}
+
+	dependentClones := strings.Split(clones, ",")
+
+	// Check clones of dependent snapshots.
+	if child != "" {
+		// check all child snapshots
+		childList := strings.Split(child, ",")
+
+		for _, childSnapshot := range childList {
+			// TODO: limit the max level of recursion.
+			childClones, err := m.HasDependentEntity(childSnapshot)
+			if err != nil {
+				return nil, fmt.Errorf("failed to check dependent clones of dependent snapshots: %w", err)
+			}
+
+			dependentClones = append(dependentClones, childClones...)
+		}
+	}
+
+	return dependentClones, nil
+}
+
+// KeepRelation keeps relation between adjacent snapshots.
+func (m *Manager) KeepRelation(snapshotName string) error {
+	child, err := m.getProperty(childProp, snapshotName)
+	if err != nil {
+		return fmt.Errorf("failed to check snapshot child property: %w", err)
+	}
+
+	parent, err := m.getProperty(parentProp, snapshotName)
+	if err != nil {
+		return fmt.Errorf("failed to check snapshot parent property: %w", err)
+	}
+
+	if parent != "" {
+		if err := m.DeleteChildProp(snapshotName, parent); err != nil {
+			return fmt.Errorf("failed to delete child: %w", err)
+		}
+
+		if err := m.addChild(parent, child); err != nil {
+			return fmt.Errorf("failed to add child: %w", err)
+		}
+	}
+
+	if child != "" {
+		if err := m.setParent(parent, child); err != nil {
+			return fmt.Errorf("failed to set parent: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func (m *Manager) addToSet(property, snapshot, value string) error {
+	original, err := m.getProperty(property, snapshot)
+	if err != nil {
+		return err
+	}
+
+	dirtyList := append(strings.Split(original, branchSep), value)
+	uniqueList := unique(dirtyList)
+
+	return m.setProperty(property, strings.Join(uniqueList, branchSep), snapshot)
+}
+
+// deleteFromSet deletes specific value from snapshot property.
+func (m *Manager) deleteFromSet(prop, branch, snapshotName string) error {
+	propertyValue, err := m.getProperty(prop, snapshotName)
+	if err != nil {
+		return err
+	}
+
+	originalList := strings.Split(propertyValue, branchSep)
+	resultList := make([]string, 0, len(originalList)-1)
+
+	for _, item := range originalList {
+		if item != branch {
+			resultList = append(resultList, item)
+		}
+	}
+
+	value := strings.Join(resultList, branchSep)
+
+	if value == "" {
+		value = empty
+	}
+
+	return m.setProperty(prop, value, snapshotName)
+}
+
+func (m *Manager) getProperty(property, snapshotName string) (string, error) {
+	cmd := fmt.Sprintf("zfs get -H -o value %s %s", property, snapshotName)
+
+	out, err := m.runner.Run(cmd)
+	if err != nil {
+		return "", fmt.Errorf("error when trying to get property: %w. Out: %v", err, out)
+	}
+
+	value := strings.Trim(strings.TrimSpace(out), "-")
+
+	return value, nil
+}
+
+func (m *Manager) setProperty(property, value, snapshotName string) error {
+	if value == "" {
+		value = empty
+	}
+
+	cmd := fmt.Sprintf("zfs set %s=%q %s", property, value, snapshotName)
+
+	out, err := m.runner.Run(cmd)
+	if err != nil {
+		return fmt.Errorf("error when trying to set property: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
+
+func unique(originalList []string) []string {
+	keys := make(map[string]struct{}, 0)
+	branchList := make([]string, 0, len(originalList))
+
+	for _, item := range originalList {
+		if _, ok := keys[item]; !ok {
+			if item == "" || item == "-" {
+				continue
+			}
+
+			keys[item] = struct{}{}
+
+			branchList = append(branchList, item)
+		}
+	}
+
+	return branchList
+}
+
+// Reset rollbacks data to ZFS snapshot.
+func (m *Manager) Reset(snapshotID string, _ thinclones.ResetOptions) error {
+	// zfs rollback pool@snapshot_20221019094237
+	cmd := fmt.Sprintf("zfs rollback %s", snapshotID)
+
+	if out, err := m.runner.Run(cmd, true); err != nil {
+		return fmt.Errorf("failed to rollback a snapshot: %w. Out: %v", err, out)
+	}
+
+	return nil
+}
diff --git a/engine/internal/provision/thinclones/zfs/snapshots_filter.go b/engine/internal/provision/thinclones/zfs/snapshots_filter.go
index 05d2e0ca..d1dcaccb 100644
--- a/engine/internal/provision/thinclones/zfs/snapshots_filter.go
+++ b/engine/internal/provision/thinclones/zfs/snapshots_filter.go
@@ -41,6 +41,8 @@ var defaultFields = snapshotFields{
 	"usedbysnapshots",
 	"usedbychildren",
 	dataStateAtLabel,
+	branchProp,
+	messageProp,
 }
 
 var defaultSorting = snapshotSorting{
diff --git a/engine/internal/provision/thinclones/zfs/zfs.go b/engine/internal/provision/thinclones/zfs/zfs.go
index 14c17dde..c753b1cf 100644
--- a/engine/internal/provision/thinclones/zfs/zfs.go
+++ b/engine/internal/provision/thinclones/zfs/zfs.go
@@ -6,6 +6,7 @@
 package zfs
 
 import (
+	"encoding/base64"
 	"fmt"
 	"path"
 	"strconv"
@@ -22,6 +23,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 )
 
 const (
@@ -31,6 +33,9 @@ const (
 
 	// PoolMode defines the zfs filesystem name.
 	PoolMode = "zfs"
+
+	// Clone must have 3 segments: branch, name, revision.
+	numCloneSegments = 3
 )
 
 // ListEntry defines entry of ZFS list command.
@@ -116,6 +121,12 @@ type ListEntry struct {
 
 	// Data state timestamp.
 	DataStateAt time.Time
+
+	// Branch to which the snapshot belongs.
+	Branch string
+
+	// Message associated with the snapshot.
+	Message string
 }
 
 type setFunc func(s string) error
@@ -179,23 +190,26 @@ func (m *Manager) UpdateConfig(cfg Config) {
 }
 
 // CreateClone creates a new ZFS clone.
-func (m *Manager) CreateClone(cloneName, snapshotID string) error {
-	exists, err := m.cloneExists(cloneName)
+func (m *Manager) CreateClone(branchName, cloneName, snapshotID string, revision int) error {
+	cloneMountName := m.config.Pool.CloneName(branchName, cloneName, revision)
+
+	log.Dbg(cloneMountName)
+
+	exists, err := m.cloneExists(cloneMountName)
 	if err != nil {
-		return fmt.Errorf("cannot check the clone existence: %w", err)
+		return fmt.Errorf("cannot check existence of clone: %w", err)
 	}
 
-	if exists {
-		return fmt.Errorf("clone %q is already exists. Skip creation", cloneName)
+	if exists && revision == branching.DefaultRevision {
+		return fmt.Errorf("clone %q is already exists; skipping", cloneName)
 	}
 
-	clonesMountDir := m.config.Pool.ClonesDir()
+	cloneMountLocation := m.config.Pool.CloneLocation(branchName, cloneName, revision)
+
+	cmd := fmt.Sprintf("zfs clone -p -o mountpoint=%s %s %s && chown -R %s %s",
+		cloneMountLocation, snapshotID, cloneMountName, m.config.OSUsername, cloneMountLocation)
 
-	cmd := "zfs clone " +
-		"-o mountpoint=" + clonesMountDir + "/" + cloneName + " " +
-		snapshotID + " " +
-		m.config.Pool.Name + "/" + cloneName + " && " +
-		"chown -R " + m.config.OSUsername + " " + clonesMountDir + "/" + cloneName
+	log.Dbg(cmd)
 
 	out, err := m.runner.Run(cmd)
 	if err != nil {
@@ -206,14 +220,18 @@ func (m *Manager) CreateClone(cloneName, snapshotID string) error {
 }
 
 // DestroyClone destroys a ZFS clone.
-func (m *Manager) DestroyClone(cloneName string) error {
-	exists, err := m.cloneExists(cloneName)
+func (m *Manager) DestroyClone(branchName, cloneName string, revision int) error {
+	cloneMountName := m.config.Pool.CloneName(branchName, cloneName, revision)
+
+	log.Dbg(cloneMountName)
+
+	exists, err := m.cloneExists(cloneMountName)
 	if err != nil {
 		return errors.Wrap(err, "clone does not exist")
 	}
 
 	if !exists {
-		log.Msg(fmt.Sprintf("clone %q is not exists. Skip deletion", cloneName))
+		log.Msg(fmt.Sprintf("clone %q is not exists; skipping", cloneMountName))
 		return nil
 	}
 
@@ -223,10 +241,14 @@ func (m *Manager) DestroyClone(cloneName string) error {
 	// this function to delete clones used during the preparation
 	// of baseline snapshots, we need to omit `-R`, to avoid
 	// unexpected deletion of users' clones.
-	cmd := fmt.Sprintf("zfs destroy -R %s/%s", m.config.Pool.Name, cloneName)
+	cmd := fmt.Sprintf("zfs destroy %s", cloneMountName)
 
 	if _, err = m.runner.Run(cmd); err != nil {
-		return errors.Wrap(err, "failed to run command")
+		if strings.Contains(cloneName, "clone_pre") {
+			return errors.Wrap(err, "failed to run command")
+		}
+
+		log.Dbg(err)
 	}
 
 	return nil
@@ -254,25 +276,54 @@ func (m *Manager) ListClonesNames() ([]string, error) {
 	}
 
 	cloneNames := []string{}
-	poolPrefix := m.config.Pool.Name + "/"
-	clonePoolPrefix := m.config.Pool.Name + "/" + util.ClonePrefix
+	branchPrefix := m.config.Pool.Name + "/branch/"
 	lines := strings.Split(strings.TrimSpace(cmdOutput), "\n")
 
 	for _, line := range lines {
-		if strings.HasPrefix(line, clonePoolPrefix) {
-			cloneNames = append(cloneNames, strings.TrimPrefix(line, poolPrefix))
+		bc, found := strings.CutPrefix(line, branchPrefix)
+		if !found {
+			// It's a pool dataset, not a clone. Skip it.
+			continue
+		}
+
+		segments := strings.Split(bc, "/")
+
+		if len(segments) != numCloneSegments {
+			// It's a branch dataset, not a clone. Skip it.
+			continue
+		}
+
+		cloneName := segments[1]
+
+		// TODO: check revision suffix.
+
+		if cloneName != "" && !strings.Contains(line, "_pre") {
+			cloneNames = append(cloneNames, cloneName)
 		}
 	}
 
 	return util.Unique(cloneNames), nil
 }
 
+// CreateDataset creates a new dataset.
+func (m *Manager) CreateDataset(datasetName string) error {
+	datasetCmd := fmt.Sprintf("zfs create -p %s", datasetName)
+
+	cmdOutput, err := m.runner.Run(datasetCmd)
+	if err != nil {
+		log.Dbg(cmdOutput)
+		return fmt.Errorf("failed to create dataset: %w", err)
+	}
+
+	return nil
+}
+
 // CreateSnapshot creates a new snapshot.
 func (m *Manager) CreateSnapshot(poolSuffix, dataStateAt string) (string, error) {
 	poolName := m.config.Pool.Name
 
 	if poolSuffix != "" {
-		poolName += "/" + poolSuffix
+		poolName = util.GetPoolName(m.config.Pool.Name, poolSuffix)
 	}
 
 	originalDSA := dataStateAt
@@ -297,7 +348,7 @@ func (m *Manager) CreateSnapshot(poolSuffix, dataStateAt string) (string, error)
 		}
 	}
 
-	cmd := fmt.Sprintf("zfs snapshot -r %s", snapshotName)
+	cmd := fmt.Sprintf("zfs snapshot %s", snapshotName)
 
 	if _, err := m.runner.Run(cmd, true); err != nil {
 		return "", errors.Wrap(err, "failed to create snapshot")
@@ -345,30 +396,113 @@ func getSnapshotName(pool, dataStateAt string) string {
 	return fmt.Sprintf("%s@snapshot_%s", pool, dataStateAt)
 }
 
-// RollbackSnapshot rollbacks ZFS snapshot.
-func RollbackSnapshot(r runners.Runner, _ string, snapshot string) error {
-	cmd := fmt.Sprintf("zfs rollback -f -r %s", snapshot)
+// DestroySnapshot destroys the snapshot.
+func (m *Manager) DestroySnapshot(snapshotName string, opts thinclones.DestroyOptions) error {
+	rel, err := m.detectBranching(snapshotName)
+	if err != nil {
+		return fmt.Errorf("failed to inspect snapshot properties: %w", err)
+	}
+
+	flags := ""
+
+	if opts.Force {
+		flags = "-R"
+	}
+
+	cmd := fmt.Sprintf("zfs destroy %s %s", flags, snapshotName)
+
+	if _, err := m.runner.Run(cmd); err != nil {
+		return fmt.Errorf("failed to run command: %w", err)
+	}
 
-	if _, err := r.Run(cmd, true); err != nil {
-		return errors.Wrap(err, "failed to rollback a snapshot")
+	if rel != nil {
+		if err := m.moveBranchPointer(rel, snapshotName); err != nil {
+			return err
+		}
 	}
 
+	m.removeSnapshotFromList(snapshotName)
+
 	return nil
 }
 
-// DestroySnapshot destroys the snapshot.
-func (m *Manager) DestroySnapshot(snapshotName string) error {
-	cmd := fmt.Sprintf("zfs destroy -R %s", snapshotName)
+// DestroyDataset destroys dataset with all dependent objects.
+func (m *Manager) DestroyDataset(dataset string) error {
+	cmd := fmt.Sprintf("zfs destroy -R %s", dataset)
 
 	if _, err := m.runner.Run(cmd); err != nil {
-		return errors.Wrap(err, "failed to run command")
+		return fmt.Errorf("failed to run command: %w", err)
 	}
 
-	m.removeSnapshotFromList(snapshotName)
+	return nil
+}
+
+type snapshotRelation struct {
+	parent string
+	branch string
+}
+
+func (m *Manager) detectBranching(snapshotName string) (*snapshotRelation, error) {
+	cmd := fmt.Sprintf("zfs list -H -o dle:parent,dle:branch %s", snapshotName)
+
+	out, err := m.runner.Run(cmd)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to run command")
+	}
+
+	response := strings.Fields(out)
+
+	const fieldsCounter = 2
+
+	if len(response) != fieldsCounter || response[0] == "-" || response[1] == "-" {
+		return nil, nil
+	}
+
+	return &snapshotRelation{
+		parent: response[0],
+		branch: response[1],
+	}, nil
+}
+
+func (m *Manager) moveBranchPointer(rel *snapshotRelation, snapshotName string) error {
+	if rel == nil {
+		return nil
+	}
+
+	if err := m.DeleteChildProp(snapshotName, rel.parent); err != nil {
+		return fmt.Errorf("failed to delete a child property from snapshot %s: %w", rel.parent, err)
+	}
+
+	parentProperties, err := m.GetSnapshotProperties(rel.parent)
+	if err != nil {
+		return fmt.Errorf("failed to get parent snapshot properties: %w", err)
+	}
+
+	if parentProperties.Root == rel.branch {
+		if err := m.DeleteRootProp(rel.branch, rel.parent); err != nil {
+			return fmt.Errorf("failed to delete root property: %w", err)
+		}
+	} else {
+		if err := m.AddBranchProp(rel.branch, rel.parent); err != nil {
+			return fmt.Errorf("failed to set branch property to snapshot %s: %w", rel.parent, err)
+		}
+	}
 
 	return nil
 }
 
+func (m *Manager) checkDependentClones(snapshotName string) (string, error) {
+	clonesCmd := fmt.Sprintf("zfs list -t snapshot -H -o clones %s", snapshotName)
+
+	clonesOutput, err := m.runner.Run(clonesCmd)
+	if err != nil {
+		log.Dbg(clonesOutput)
+		return "", fmt.Errorf("failed to list dependent clones: %w", err)
+	}
+
+	return strings.Trim(strings.TrimSpace(clonesOutput), "-"), nil
+}
+
 // CleanupSnapshots destroys old snapshots considering retention limit and related clones.
 func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
 	clonesCmd := fmt.Sprintf("zfs list -S clones -o name,origin -H -r %s", m.config.Pool.Name)
@@ -381,12 +515,14 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
 	busySnapshots := m.getBusySnapshotList(clonesOutput)
 
 	cleanupCmd := fmt.Sprintf(
-		"zfs list -t snapshot -H -o name -s %s -s creation -r %s | grep -v clone | head -n -%d %s"+
+		"zfs list -t snapshot -H -o name -s %s -s creation -r %s | grep -v clone | grep _pre$ | head -n -%d %s"+
 			"| xargs -n1 --no-run-if-empty zfs destroy -R ",
 		dataStateAtLabel, m.config.Pool.Name, retentionLimit, excludeBusySnapshots(busySnapshots))
 
 	out, err := m.runner.Run(cleanupCmd)
 	if err != nil {
+		log.Dbg(out)
+
 		return nil, errors.Wrap(err, "failed to clean up snapshots")
 	}
 
@@ -398,9 +534,10 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
 }
 
 func (m *Manager) getBusySnapshotList(clonesOutput string) []string {
-	systemClones, userClones := make(map[string]string), make(map[string]struct{})
+	systemClones := make(map[string]string)
+	branchingSnapshotDatasets := []string{}
 
-	userClonePrefix := m.config.Pool.Name + "/" + util.ClonePrefix
+	systemDatasetPrefix := fmt.Sprintf("%s/%s/%s/clone_pre_", m.config.Pool.Name, branching.BranchDir, branching.DefaultBranch)
 
 	for _, line := range strings.Split(clonesOutput, "\n") {
 		cloneLine := strings.FieldsFunc(line, unicode.IsSpace)
@@ -409,25 +546,30 @@ func (m *Manager) getBusySnapshotList(clonesOutput string) []string {
 			continue
 		}
 
-		if strings.HasPrefix(cloneLine[0], userClonePrefix) {
-			origin := cloneLine[1]
+		// Make dataset-snapshot map for system snapshots.
+		if strings.HasPrefix(cloneLine[0], systemDatasetPrefix) {
+			systemClones[cloneLine[0]] = cloneLine[1]
+			continue
+		}
 
-			if idx := strings.Index(origin, "@"); idx != -1 {
-				origin = origin[:idx]
+		// Keep snapshots related to the user-defined datasets.
+		if strings.HasPrefix(cloneLine[1], systemDatasetPrefix) {
+			systemDataset, _, found := strings.Cut(cloneLine[1], "@")
+			if found {
+				branchingSnapshotDatasets = append(branchingSnapshotDatasets, systemDataset)
 			}
 
-			userClones[origin] = struct{}{}
-
 			continue
 		}
-
-		systemClones[cloneLine[0]] = cloneLine[1]
 	}
 
-	busySnapshots := make([]string, 0, len(userClones))
+	busySnapshots := make([]string, 0, len(branchingSnapshotDatasets))
 
-	for userClone := range userClones {
-		busySnapshots = append(busySnapshots, systemClones[userClone])
+	for _, busyDataset := range branchingSnapshotDatasets {
+		busySnapshot, ok := systemClones[busyDataset]
+		if ok {
+			busySnapshots = append(busySnapshots, busySnapshot)
+		}
 	}
 
 	return busySnapshots
@@ -444,7 +586,7 @@ func excludeBusySnapshots(busySnapshots []string) string {
 }
 
 // GetSessionState returns a state of a session.
-func (m *Manager) GetSessionState(name string) (*resources.SessionState, error) {
+func (m *Manager) GetSessionState(branch, name string) (*resources.SessionState, error) {
 	entries, err := m.listFilesystems(m.config.Pool.Name)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to list filesystems")
@@ -452,7 +594,7 @@ func (m *Manager) GetSessionState(name string) (*resources.SessionState, error)
 
 	var sEntry *ListEntry
 
-	entryName := m.config.Pool.Name + "/" + name
+	entryName := path.Join(m.config.Pool.Name, "branch", branch, name)
 
 	for _, entry := range entries {
 		if entry.Name == entryName {
@@ -510,12 +652,12 @@ func (m *Manager) GetFilesystemState() (models.FileSystem, error) {
 	fileSystem := models.FileSystem{
 		Mode:            PoolMode,
 		Size:            parentPoolEntry.Available + parentPoolEntry.Used,
-		Free:            parentPoolEntry.Available,
-		Used:            parentPoolEntry.Used,
-		UsedBySnapshots: parentPoolEntry.UsedBySnapshots,
-		UsedByClones:    parentPoolEntry.UsedByChildren,
+		Free:            poolEntry.Available,
+		Used:            poolEntry.Used,
+		UsedBySnapshots: poolEntry.UsedBySnapshots,
+		UsedByClones:    poolEntry.UsedByChildren,
 		DataSize:        poolEntry.LogicalReferenced,
-		CompressRatio:   parentPoolEntry.CompressRatio,
+		CompressRatio:   poolEntry.CompressRatio,
 	}
 
 	return fileSystem, nil
@@ -534,7 +676,7 @@ func (m *Manager) SnapshotList() []resources.Snapshot {
 func (m *Manager) RefreshSnapshotList() {
 	snapshots, err := m.getSnapshots()
 	if err != nil {
-		log.Err("Failed to refresh snapshot list: ", err)
+		log.Err("failed to refresh snapshot list: ", err)
 		return
 	}
 
@@ -557,6 +699,16 @@ func (m *Manager) getSnapshots() ([]resources.Snapshot, error) {
 			continue
 		}
 
+		branch := entry.Branch
+
+		if branch == empty {
+			if parsedBranch := branching.ParseBranchNameFromSnapshot(entry.Name, m.config.Pool.Name); parsedBranch != "" {
+				branch = parsedBranch
+			} else {
+				branch = branching.DefaultBranch
+			}
+		}
+
 		snapshot := resources.Snapshot{
 			ID:                entry.Name,
 			CreatedAt:         entry.Creation,
@@ -564,6 +716,8 @@ func (m *Manager) getSnapshots() ([]resources.Snapshot, error) {
 			Used:              entry.Used,
 			LogicalReferenced: entry.LogicalReferenced,
 			Pool:              m.config.Pool.Name,
+			Branch:            branch,
+			Message:           entry.Message,
 		}
 
 		snapshots = append(snapshots, snapshot)
@@ -689,7 +843,7 @@ func (m *Manager) listDetails(filter snapshotFilter) ([]*ListEntry, error) {
 		return nil, NewEmptyPoolError(filter.dsType, filter.pool)
 	}
 
-	numberFields := len([]string(filter.fields)) // 14
+	numberFields := len([]string(filter.fields)) // 16
 	entries := make([]*ListEntry, len(lines)-headerOffset)
 
 	for i := headerOffset; i < len(lines); i++ {
@@ -715,6 +869,7 @@ func (m *Manager) listDetails(filter snapshotFilter) ([]*ListEntry, error) {
 			MountPoint: fields[2],
 			Type:       fields[5],
 			Origin:     fields[6],
+			Branch:     fields[14],
 		}
 
 		setRules := []setTuple{
@@ -728,6 +883,7 @@ func (m *Manager) listDetails(filter snapshotFilter) ([]*ListEntry, error) {
 			{field: fields[11], setFunc: zfsListEntry.setUsedBySnapshots},
 			{field: fields[12], setFunc: zfsListEntry.setUsedByChildren},
 			{field: fields[13], setFunc: zfsListEntry.setDataStateAt},
+			{field: fields[15], setFunc: zfsListEntry.setMessage},
 		}
 
 		for _, rule := range setRules {
@@ -859,6 +1015,22 @@ func (z *ListEntry) setDataStateAt(field string) error {
 	return nil
 }
 
+func (z *ListEntry) setMessage(field string) error {
+	if field == empty || field == "" {
+		z.Message = field
+		return nil
+	}
+
+	decoded, err := base64.StdEncoding.DecodeString(field)
+	if err != nil {
+		return err
+	}
+
+	z.Message = string(decoded)
+
+	return nil
+}
+
 // PoolMappings provides a mapping of pool name and mount point directory.
 func PoolMappings(runner runners.Runner, mountDir, preSnapshotSuffix string) (map[string]string, error) {
 	listCmd := "zfs list -Ho name,mountpoint -t filesystem | grep -v " + preSnapshotSuffix
diff --git a/engine/internal/provision/thinclones/zfs/zfs_test.go b/engine/internal/provision/thinclones/zfs/zfs_test.go
index db2acecd..0001c8a6 100644
--- a/engine/internal/provision/thinclones/zfs/zfs_test.go
+++ b/engine/internal/provision/thinclones/zfs/zfs_test.go
@@ -21,8 +21,8 @@ func (r runnerMock) Run(string, ...bool) (string, error) {
 
 func TestListClones(t *testing.T) {
 	const (
-		poolName    = "datastore"
-		clonePrefix = "dblab_clone_"
+		poolName          = "datastore"
+		preSnapshotSuffix = "_pre"
 	)
 
 	testCases := []struct {
@@ -36,48 +36,48 @@ func TestListClones(t *testing.T) {
 		},
 		{
 			caseName: "single clone",
-			cmdOutput: `datastore/clone_pre_20200831030000
-datastore/dblab_clone_6000
+			cmdOutput: `datastore/branch/main/clone_pre_20200831030000
+datastore/branch/main/cls19p20l4rc73bc2v9g/r0
 `,
 			cloneNames: []string{
-				"dblab_clone_6000",
+				"cls19p20l4rc73bc2v9g",
 			},
 		},
 		{
 			caseName: "multiple clones",
-			cmdOutput: `datastore/clone_pre_20200831030000
-datastore/dblab_clone_6000
-datastore/dblab_clone_6001
+			cmdOutput: `datastore/branch/main/clone_pre_20200831030000
+datastore/branch/main/cls19p20l4rc73bc2v9g/r0
+datastore/branch/main/cls184a0l4rc73bc2v90/r0
 `,
 			cloneNames: []string{
-				"dblab_clone_6000",
-				"dblab_clone_6001",
+				"cls19p20l4rc73bc2v9g",
+				"cls184a0l4rc73bc2v90",
 			},
 		},
 		{
 			caseName: "clone duplicate",
-			cmdOutput: `datastore/clone_pre_20200831030000
-datastore/dblab_clone_6000
-datastore/dblab_clone_6000
+			cmdOutput: `datastore/branch/main/clone_pre_20200831030000
+datastore/branch/main/cls19p20l4rc73bc2v9g/r0
+datastore/branch/main/cls19p20l4rc73bc2v9g/r1
 `,
 			cloneNames: []string{
-				"dblab_clone_6000",
+				"cls19p20l4rc73bc2v9g",
 			},
 		},
 		{
 			caseName: "different pool",
-			cmdOutput: `datastore/clone_pre_20200831030000
-dblab_pool/dblab_clone_6001
-datastore/dblab_clone_6000
+			cmdOutput: `datastore/branch/main/clone_pre_20200831030000
+dblab_pool/branch/main/cls19p20l4rc73bc2v9g/r0
+datastore/branch/main/cls184a0l4rc73bc2v90/r0
 `,
 			cloneNames: []string{
-				"dblab_clone_6000",
+				"cls184a0l4rc73bc2v90",
 			},
 		},
 		{
 			caseName: "no matched clone",
-			cmdOutput: `datastore/clone_pre_20200831030000
-dblab_pool/dblab_clone_6001
+			cmdOutput: `datastore/branch/main/clone_pre_20200831030000
+dblab_pool/branch/main/cls19p20l4rc73bc2v9g/r0
 `,
 			cloneNames: []string{},
 		},
@@ -90,7 +90,7 @@ dblab_pool/dblab_clone_6001
 			},
 			config: Config{
 				Pool:              resources.NewPool(poolName),
-				PreSnapshotSuffix: clonePrefix,
+				PreSnapshotSuffix: preSnapshotSuffix,
 			},
 		}
 
@@ -115,25 +115,35 @@ func TestFailedListClones(t *testing.T) {
 }
 
 func TestBusySnapshotList(t *testing.T) {
-	m := Manager{config: Config{Pool: &resources.Pool{Name: "dblab_pool"}}}
-
-	out := `dblab_pool	-
-dblab_pool/clone_pre_20210127105215	dblab_pool@snapshot_20210127105215_pre
-dblab_pool/clone_pre_20210127113000	dblab_pool@snapshot_20210127113000_pre
-dblab_pool/clone_pre_20210127120000	dblab_pool@snapshot_20210127120000_pre
-dblab_pool/clone_pre_20210127123000	dblab_pool@snapshot_20210127123000_pre
-dblab_pool/clone_pre_20210127130000	dblab_pool@snapshot_20210127130000_pre
-dblab_pool/clone_pre_20210127133000	dblab_pool@snapshot_20210127133000_pre
-dblab_pool/clone_pre_20210127140000	dblab_pool@snapshot_20210127140000_pre
-dblab_pool/dblab_clone_6000	dblab_pool/clone_pre_20210127133000@snapshot_20210127133008
-dblab_pool/dblab_clone_6001	dblab_pool/clone_pre_20210127123000@snapshot_20210127133008
+	const preSnapshotSuffix = "_pre"
+	m := Manager{config: Config{Pool: &resources.Pool{Name: "test_dblab_pool"}, PreSnapshotSuffix: preSnapshotSuffix}}
+
+	out := `test_dblab_pool	-
+test_dblab_pool/branch	-
+test_dblab_pool/branch/main	-
+test_dblab_pool/branch/main/clone_pre_20250403061908	-
+test_dblab_pool/branch/main/clone_pre_20250403061908/r0	test_dblab_pool@snapshot_20250403061908_pre
+test_dblab_pool/branch/main/clone_pre_20250403085500	-
+test_dblab_pool/branch/main/clone_pre_20250403085500/r0	test_dblab_pool@snapshot_20250403085500_pre
+test_dblab_pool/branch/main/clone_pre_20250403090000	-
+test_dblab_pool/branch/main/clone_pre_20250403090000/r0	test_dblab_pool@snapshot_20250403090000_pre
+test_dblab_pool/branch/main/clone_pre_20250403090500	-
+test_dblab_pool/branch/main/clone_pre_20250403090500/r0	test_dblab_pool@snapshot_20250403090500_pre
+test_dblab_pool/branch/main/cvn2j50n9i6s73as3k9g	-
+test_dblab_pool/branch/main/cvn2j50n9i6s73as3k9g/r0	test_dblab_pool/branch/main/clone_pre_20250403061908/r0@snapshot_20250403061908
+test_dblab_pool/branch/main/cvn2kdon9i6s73as3ka0	-
+test_dblab_pool/branch/main/cvn2kdon9i6s73as3ka0/r0	test_dblab_pool/branch/new001@20250403062641
+test_dblab_pool/branch/new001	test_dblab_pool/branch/main/cvn2j50n9i6s73as3k9g/r0@20250403062503
+test_dblab_pool/branch/new001/cvn4n38n9i6s73as3kag	-
+test_dblab_pool/branch/new001/cvn4n38n9i6s73as3kag/r0	test_dblab_pool/branch/new001@20250403062641
 `
-	expected := []string{"dblab_pool@snapshot_20210127133000_pre", "dblab_pool@snapshot_20210127123000_pre"}
+	expected := []string{
+		"test_dblab_pool@snapshot_20250403061908_pre",
+	}
 
 	list := m.getBusySnapshotList(out)
-	require.Equal(t, 2, len(list))
-	assert.Contains(t, list, expected[0])
-	assert.Contains(t, list, expected[1])
+	require.Len(t, list, len(expected))
+	assert.ElementsMatch(t, list, expected)
 }
 
 func TestExcludingBusySnapshots(t *testing.T) {
diff --git a/engine/internal/retrieval/dbmarker/dbmarker.go b/engine/internal/retrieval/dbmarker/dbmarker.go
index 8acb5892..4d6e3b97 100644
--- a/engine/internal/retrieval/dbmarker/dbmarker.go
+++ b/engine/internal/retrieval/dbmarker/dbmarker.go
@@ -6,13 +6,34 @@
 package dbmarker
 
 import (
+	"bytes"
+	"fmt"
 	"os"
 	"path"
+	"strings"
 
 	"github.com/pkg/errors"
 	"gopkg.in/yaml.v2"
 )
 
+const (
+	configDir      = ".dblab"
+	configFilename = "dbmarker"
+
+	refsDir      = "refs"
+	branchesDir  = "branch"
+	snapshotsDir = "snapshot"
+	headFile     = "HEAD"
+	logsFile     = "logs"
+	mainBranch   = "main"
+
+	// LogicalDataType defines a logical data type.
+	LogicalDataType = "logical"
+
+	// PhysicalDataType defines a physical data type.
+	PhysicalDataType = "physical"
+)
+
 // Marker marks database data depends on a retrieval process.
 type Marker struct {
 	dataPath string
@@ -31,21 +52,22 @@ type Config struct {
 	DataType    string `yaml:"dataType"`
 }
 
-const (
-	// ConfigDir defines the name of the dbMarker configuration directory.
-	ConfigDir      = ".dblab"
-	configFilename = "dbmarker"
-
-	// LogicalDataType defines a logical data type.
-	LogicalDataType = "logical"
+// Head describes content of HEAD file.
+type Head struct {
+	Ref string `yaml:"ref"`
+}
 
-	// PhysicalDataType defines a physical data type.
-	PhysicalDataType = "physical"
-)
+// SnapshotInfo describes snapshot info.
+type SnapshotInfo struct {
+	ID        string
+	Parent    string
+	CreatedAt string
+	StateAt   string
+}
 
 // Init inits DB marker for the data directory.
 func (m *Marker) initDBLabDirectory() error {
-	dirname := path.Join(m.dataPath, ConfigDir)
+	dirname := path.Join(m.dataPath, configDir)
 	if err := os.MkdirAll(dirname, 0755); err != nil {
 		return errors.Wrapf(err, "cannot create a DBMarker directory %s", dirname)
 	}
@@ -59,7 +81,7 @@ func (m *Marker) CreateConfig() error {
 		return errors.Wrap(err, "failed to init DBMarker")
 	}
 
-	dbMarkerFile, err := os.OpenFile(m.buildFileName(), os.O_RDWR|os.O_CREATE, 0600)
+	dbMarkerFile, err := os.OpenFile(m.buildFileName(configFilename), os.O_RDWR|os.O_CREATE, 0600)
 	if err != nil {
 		return err
 	}
@@ -71,7 +93,7 @@ func (m *Marker) CreateConfig() error {
 
 // GetConfig provides a loaded DBMarker config.
 func (m *Marker) GetConfig() (*Config, error) {
-	configData, err := os.ReadFile(m.buildFileName())
+	configData, err := os.ReadFile(m.buildFileName(configFilename))
 	if err != nil {
 		return nil, err
 	}
@@ -96,10 +118,243 @@ func (m *Marker) SaveConfig(cfg *Config) error {
 		return err
 	}
 
-	return os.WriteFile(m.buildFileName(), configData, 0600)
+	return os.WriteFile(m.buildFileName(configFilename), configData, 0600)
+}
+
+// buildFileName builds a DBMarker filename.
+func (m *Marker) buildFileName(filename string) string {
+	return path.Join(m.dataPath, configDir, filename)
+}
+
+// InitBranching creates structures for data branching.
+func (m *Marker) InitBranching() error {
+	branchesDir := m.buildBranchesPath()
+	if err := os.MkdirAll(branchesDir, 0755); err != nil {
+		return fmt.Errorf("cannot create branches directory %s: %w", branchesDir, err)
+	}
+
+	snapshotsDir := m.buildSnapshotsPath()
+	if err := os.MkdirAll(snapshotsDir, 0755); err != nil {
+		return fmt.Errorf("cannot create snapshots directory %s: %w", snapshotsDir, err)
+	}
+
+	f, err := os.Create(m.buildFileName(headFile))
+	if err != nil {
+		return fmt.Errorf("cannot create HEAD file: %w", err)
+	}
+
+	_ = f.Close()
+
+	return nil
+}
+
+// InitMainBranch creates a new main branch.
+func (m *Marker) InitMainBranch(infos []SnapshotInfo) error {
+	var head Head
+
+	mainDir := m.buildBranchName(mainBranch)
+	if err := os.MkdirAll(mainDir, 0755); err != nil {
+		return fmt.Errorf("cannot create branches directory %s: %w", mainDir, err)
+	}
+
+	var bb bytes.Buffer
+
+	for _, info := range infos {
+		if err := m.storeSnapshotInfo(info); err != nil {
+			return err
+		}
+
+		head.Ref = buildSnapshotRef(info.ID)
+		log := strings.Join([]string{info.Parent, info.ID, info.CreatedAt, info.StateAt}, " ") + "\n"
+		bb.WriteString(log)
+	}
+
+	if err := os.WriteFile(m.buildBranchArtifactPath(mainBranch, logsFile), bb.Bytes(), 0755); err != nil {
+		return fmt.Errorf("cannot store file with HEAD metadata: %w", err)
+	}
+
+	headData, err := yaml.Marshal(head)
+	if err != nil {
+		return fmt.Errorf("cannot prepare HEAD metadata: %w", err)
+	}
+
+	if err := os.WriteFile(m.buildFileName(headFile), headData, 0755); err != nil {
+		return fmt.Errorf("cannot store file with HEAD metadata: %w", err)
+	}
+
+	if err := os.WriteFile(m.buildBranchArtifactPath(mainBranch, headFile), headData, 0755); err != nil {
+		return fmt.Errorf("cannot store file with HEAD metadata: %w", err)
+	}
+
+	return nil
+}
+
+func (m *Marker) storeSnapshotInfo(info SnapshotInfo) error {
+	snapshotName := m.buildSnapshotName(info.ID)
+
+	data, err := yaml.Marshal(info)
+	if err != nil {
+		return fmt.Errorf("cannot prepare snapshot metadata %s: %w", snapshotName, err)
+	}
+
+	if err := os.WriteFile(snapshotName, data, 0755); err != nil {
+		return fmt.Errorf("cannot store file with snapshot metadata %s: %w", snapshotName, err)
+	}
+
+	return nil
+}
+
+// CreateBranch creates a new DLE data branch.
+func (m *Marker) CreateBranch(branch, base string) error {
+	dirname := m.buildBranchName(branch)
+	if err := os.MkdirAll(dirname, 0755); err != nil {
+		return fmt.Errorf("cannot create branches directory %s: %w", dirname, err)
+	}
+
+	headPath := m.buildBranchArtifactPath(base, headFile)
+
+	readData, err := os.ReadFile(headPath)
+	if err != nil {
+		return fmt.Errorf("cannot read file %s: %w", headPath, err)
+	}
+
+	branchPath := m.buildBranchArtifactPath(branch, headFile)
+
+	if err := os.WriteFile(branchPath, readData, 0755); err != nil {
+		return fmt.Errorf("cannot write file %s: %w", branchPath, err)
+	}
+
+	return nil
+}
+
+// ListBranches returns branch list.
+func (m *Marker) ListBranches() ([]string, error) {
+	branches := []string{}
+
+	dirs, err := os.ReadDir(m.buildBranchesPath())
+	if err != nil {
+		return nil, fmt.Errorf("failed to read repository: %w", err)
+	}
+
+	for _, dir := range dirs {
+		if !dir.IsDir() {
+			continue
+		}
+
+		branches = append(branches, dir.Name())
+	}
+
+	return branches, nil
+}
+
+// GetSnapshotID returns snapshot pointer for branch.
+func (m *Marker) GetSnapshotID(branch string) (string, error) {
+	headPath := m.buildBranchArtifactPath(branch, headFile)
+
+	readData, err := os.ReadFile(headPath)
+	if err != nil {
+		return "", fmt.Errorf("cannot read file %s: %w", headPath, err)
+	}
+
+	h := &Head{}
+	if err := yaml.Unmarshal(readData, &h); err != nil {
+		return "", fmt.Errorf("cannot read reference: %w", err)
+	}
+
+	snapshotsPath := m.buildPathFromRef(h.Ref)
+
+	snapshotData, err := os.ReadFile(snapshotsPath)
+	if err != nil {
+		return "", fmt.Errorf("cannot read file %s: %w", snapshotsPath, err)
+	}
+
+	snInfo := &SnapshotInfo{}
+
+	if err := yaml.Unmarshal(snapshotData, &snInfo); err != nil {
+		return "", fmt.Errorf("cannot read reference: %w", err)
+	}
+
+	return snInfo.ID, nil
+}
+
+// SaveSnapshotRef stores snapshot reference for branch.
+func (m *Marker) SaveSnapshotRef(branch, snapshotID string) error {
+	h, err := m.getBranchHead(branch)
+	if err != nil {
+		return err
+	}
+
+	h.Ref = buildSnapshotRef(snapshotID)
+
+	if err := m.writeBranchHead(h, branch); err != nil {
+		return fmt.Errorf("cannot write branch head: %w", err)
+	}
+
+	return nil
+}
+
+func (m *Marker) getBranchHead(branch string) (*Head, error) {
+	headPath := m.buildBranchArtifactPath(branch, headFile)
+
+	readData, err := os.ReadFile(headPath)
+	if err != nil {
+		return nil, fmt.Errorf("cannot read file %s: %w", headPath, err)
+	}
+
+	h := &Head{}
+	if err := yaml.Unmarshal(readData, &h); err != nil {
+		return nil, fmt.Errorf("cannot read reference: %w", err)
+	}
+
+	return h, nil
+}
+
+func (m *Marker) writeBranchHead(h *Head, branch string) error {
+	headPath := m.buildBranchArtifactPath(branch, headFile)
+
+	writeData, err := yaml.Marshal(h)
+	if err != nil {
+		return fmt.Errorf("cannot marshal structure: %w", err)
+	}
+
+	if err := os.WriteFile(headPath, writeData, 0755); err != nil {
+		return fmt.Errorf("cannot write file %s: %w", headPath, err)
+	}
+
+	return nil
+}
+
+// buildBranchesPath builds path of branches dir.
+func (m *Marker) buildBranchesPath() string {
+	return path.Join(m.dataPath, configDir, refsDir, branchesDir)
+}
+
+// buildBranchName builds a branch name.
+func (m *Marker) buildBranchName(branch string) string {
+	return path.Join(m.buildBranchesPath(), branch)
+}
+
+// buildBranchArtifactPath builds a branch artifact name.
+func (m *Marker) buildBranchArtifactPath(branch, artifact string) string {
+	return path.Join(m.buildBranchName(branch), artifact)
+}
+
+// buildSnapshotsPath builds path of snapshots dir.
+func (m *Marker) buildSnapshotsPath() string {
+	return path.Join(m.dataPath, configDir, refsDir, snapshotsDir)
+}
+
+// buildSnapshotName builds a snapshot file name.
+func (m *Marker) buildSnapshotName(snapshotID string) string {
+	return path.Join(m.buildSnapshotsPath(), snapshotID)
+}
+
+// buildSnapshotRef builds snapshot ref.
+func buildSnapshotRef(snapshotID string) string {
+	return path.Join(refsDir, snapshotsDir, snapshotID)
 }
 
-// buildFileName builds a DBMarker config filename.
-func (m *Marker) buildFileName() string {
-	return path.Join(m.dataPath, ConfigDir, configFilename)
+// buildPathFromRef builds path from ref.
+func (m *Marker) buildPathFromRef(ref string) string {
+	return path.Join(m.dataPath, configDir, ref)
 }
diff --git a/engine/internal/retrieval/engine/postgres/logical/dump.go b/engine/internal/retrieval/engine/postgres/logical/dump.go
index 5f36b9f5..d2a8ba57 100644
--- a/engine/internal/retrieval/engine/postgres/logical/dump.go
+++ b/engine/internal/retrieval/engine/postgres/logical/dump.go
@@ -88,7 +88,6 @@ type DumpOptions struct {
 	Source          Source                    `yaml:"source"`
 	Databases       map[string]DumpDefinition `yaml:"databases"`
 	ParallelJobs    int                       `yaml:"parallelJobs"`
-	IgnoreErrors    bool                      `yaml:"ignoreErrors"`
 	Restore         ImmediateRestore          `yaml:"immediateRestore"`
 	CustomOptions   []string                  `yaml:"customOptions"`
 }
@@ -416,7 +415,7 @@ func collectDiagnostics(ctx context.Context, client *client.Client, postgresName
 			Value: fmt.Sprintf("%s=%s", cont.DBLabControlLabel, cont.DBLabDumpLabel)})
 
 	if err := diagnostic.CollectDiagnostics(ctx, client, filterArgs, postgresName, dataDir); err != nil {
-		log.Err("Failed to collect container diagnostics", err)
+		log.Err("failed to collect container diagnostics", err)
 	}
 }
 
@@ -508,11 +507,9 @@ func (d *DumpJob) dumpDatabase(ctx context.Context, dumpContID, dbName string, d
 		Cmd: dumpCommand,
 		Env: d.getExecEnvironmentVariables(),
 	}); err != nil {
-		log.Err("Dump command failed: ", output)
+		log.Err("dump command failed: ", output)
 
-		if !d.DumpOptions.IgnoreErrors {
-			return fmt.Errorf("failed to dump a database: %w. Output: %s", err, output)
-		}
+		return fmt.Errorf("failed to dump a database: %w. Output: %s", err, output)
 	}
 
 	log.Msg(fmt.Sprintf("Dumping job for the database %q has been finished", dbName))
diff --git a/engine/internal/retrieval/engine/postgres/logical/restore.go b/engine/internal/retrieval/engine/postgres/logical/restore.go
index 43b24440..9e4aa52c 100644
--- a/engine/internal/retrieval/engine/postgres/logical/restore.go
+++ b/engine/internal/retrieval/engine/postgres/logical/restore.go
@@ -210,10 +210,6 @@ func (r *RestoreJob) Run(ctx context.Context) (err error) {
 		return fmt.Errorf("failed to explore the data directory %q: %w", dataDir, err)
 	}
 
-	if !isEmpty {
-		log.Warn(fmt.Sprintf("The data directory %q is not empty. Existing data will be overwritten.", dataDir))
-	}
-
 	if err := tools.PullImage(ctx, r.dockerClient, r.RestoreOptions.DockerImage); err != nil {
 		return errors.Wrap(err, "failed to scan image pulling response")
 	}
@@ -243,6 +239,16 @@ func (r *RestoreJob) Run(ctx context.Context) (err error) {
 		}
 	}()
 
+	if !isEmpty {
+		log.Warn(fmt.Sprintf("The data directory %q is not empty. Existing data will be overwritten.", dataDir))
+
+		log.Msg("Clean up data directory:", dataDir)
+
+		if err := tools.CleanupDir(dataDir); err != nil {
+			return fmt.Errorf("failed to clean up data directory before restore: %w", err)
+		}
+	}
+
 	log.Msg(fmt.Sprintf("Running container: %s. ID: %v", r.restoreContainerName(), containerID))
 
 	if err := r.dockerClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
@@ -521,7 +527,7 @@ func (r *RestoreJob) getDirectoryDumpDefinition(ctx context.Context, contID, dum
 
 	dbName, err := r.extractDBNameFromDump(ctx, contID, dumpDir)
 	if err != nil {
-		log.Err("Invalid dump: ", err)
+		log.Err("invalid dump: ", err)
 		return DumpDefinition{}, errors.Wrap(err, "invalid database name")
 	}
 
@@ -590,7 +596,7 @@ func (r *RestoreJob) restoreDB(ctx context.Context, contID, dbName string, dbDef
 	})
 
 	if err != nil && !r.RestoreOptions.IgnoreErrors {
-		log.Err("Restore command failed: ", output)
+		log.Err("restore command failed: ", output)
 
 		return fmt.Errorf("failed to exec restore command: %w. Output: %s", err, output)
 	}
@@ -600,7 +606,7 @@ func (r *RestoreJob) restoreDB(ctx context.Context, contID, dbName string, dbDef
 	}
 
 	if err := r.defineDSA(ctx, dbDefinition, contID, dbName); err != nil {
-		log.Err("Failed to define DataStateAt: ", err)
+		log.Err("failed to define DataStateAt: ", err)
 	}
 
 	if err := r.markDatabase(); err != nil {
@@ -771,7 +777,7 @@ func (r *RestoreJob) markDatabase() error {
 func (r *RestoreJob) updateDataStateAt() {
 	dsaTime, err := time.Parse(util.DataStateAtFormat, r.dbMark.DataStateAt)
 	if err != nil {
-		log.Err("Invalid value for DataStateAt: ", r.dbMark.DataStateAt)
+		log.Err("invalid value for DataStateAt: ", r.dbMark.DataStateAt)
 		return
 	}
 
diff --git a/engine/internal/retrieval/engine/postgres/physical/physical.go b/engine/internal/retrieval/engine/postgres/physical/physical.go
index 2bfe97e4..62f719e3 100644
--- a/engine/internal/retrieval/engine/postgres/physical/physical.go
+++ b/engine/internal/retrieval/engine/postgres/physical/physical.go
@@ -176,7 +176,7 @@ func (r *RestoreJob) Run(ctx context.Context) (err error) {
 		if err == nil && r.CopyOptions.Sync.Enabled {
 			go func() {
 				if syncErr := r.runSyncInstance(ctx); syncErr != nil {
-					log.Err("Failed to run sync instance: ", syncErr)
+					log.Err("failed to run sync instance: ", syncErr)
 
 					if ctx.Err() != nil {
 						// if context was canceled
@@ -249,7 +249,7 @@ func (r *RestoreJob) Run(ctx context.Context) (err error) {
 	log.Msg("Restoring job has been finished")
 
 	if err := r.markDatabaseData(); err != nil {
-		log.Err("Failed to mark database data: ", err)
+		log.Err("failed to mark database data: ", err)
 	}
 
 	cfgManager, err := pgconfig.NewCorrector(dataDir)
@@ -350,7 +350,7 @@ func (r *RestoreJob) runSyncInstance(ctx context.Context) (err error) {
 					Value: fmt.Sprintf("%s=%s", cont.DBLabControlLabel, cont.DBLabSyncLabel)})
 
 			if err := diagnostic.CollectDiagnostics(ctx, r.dockerClient, filterArgs, r.syncInstanceName(), r.fsPool.DataDir()); err != nil {
-				log.Err("Failed to collect container diagnostics", err)
+				log.Err("failed to collect container diagnostics", err)
 			}
 		}
 	}()
diff --git a/engine/internal/retrieval/engine/postgres/physical/wal_g.go b/engine/internal/retrieval/engine/postgres/physical/wal_g.go
index cdb934b8..0abb2b36 100644
--- a/engine/internal/retrieval/engine/postgres/physical/wal_g.go
+++ b/engine/internal/retrieval/engine/postgres/physical/wal_g.go
@@ -106,7 +106,7 @@ func getLastBackupName(ctx context.Context, dockerClient *client.Client, contain
 		}
 
 		// fallback to fetching last backup from list
-		log.Err("Failed to parse last backup from wal-g details", err)
+		log.Err("failed to parse last backup from wal-g details", err)
 	}
 
 	return parseLastBackupFromList(ctx, dockerClient, containerID)
diff --git a/engine/internal/retrieval/engine/postgres/snapshot/logical.go b/engine/internal/retrieval/engine/postgres/snapshot/logical.go
index c596d6d5..744be021 100644
--- a/engine/internal/retrieval/engine/postgres/snapshot/logical.go
+++ b/engine/internal/retrieval/engine/postgres/snapshot/logical.go
@@ -149,13 +149,19 @@ func (s *LogicalInitial) Run(ctx context.Context) error {
 		}
 	}
 
+	log.Dbg("Cleaning up old snapshots from a dataset")
+
+	if _, err := s.cloneManager.CleanupSnapshots(0); err != nil {
+		return errors.Wrap(err, "failed to destroy old snapshots")
+	}
+
 	dataStateAt := extractDataStateAt(s.dbMarker)
 
 	if _, err := s.cloneManager.CreateSnapshot("", dataStateAt); err != nil {
 		var existsError *thinclones.SnapshotExistsError
 		if errors.As(err, &existsError) {
 			log.Msg("Skip snapshotting: ", existsError.Error())
-			return nil
+			return err
 		}
 
 		return errors.Wrap(err, "failed to create a snapshot")
@@ -240,7 +246,7 @@ func (s *LogicalInitial) runPreprocessingQueries(ctx context.Context, dataDir st
 					Value: fmt.Sprintf("%s=%s", cont.DBLabControlLabel, cont.DBLabPatchLabel)})
 
 			if err := diagnostic.CollectDiagnostics(ctx, s.dockerClient, filterArgs, s.patchContainerName(), dataDir); err != nil {
-				log.Err("Failed to collect container diagnostics", err)
+				log.Err("failed to collect container diagnostics", err)
 			}
 		}
 	}()
diff --git a/engine/internal/retrieval/engine/postgres/snapshot/physical.go b/engine/internal/retrieval/engine/postgres/snapshot/physical.go
index 8bbd047c..f49b9d8d 100644
--- a/engine/internal/retrieval/engine/postgres/snapshot/physical.go
+++ b/engine/internal/retrieval/engine/postgres/snapshot/physical.go
@@ -32,6 +32,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/databases/postgres/pgconfig"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/config"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/dbmarker"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools"
@@ -47,6 +48,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 )
 
 const (
@@ -302,6 +304,8 @@ func (p *PhysicalInitial) Run(ctx context.Context) (err error) {
 }
 
 func (p *PhysicalInitial) run(ctx context.Context) (err error) {
+	log.Msg("Run job: ", p.Name())
+
 	select {
 	case <-ctx.Done():
 		if p.scheduler != nil {
@@ -346,25 +350,25 @@ func (p *PhysicalInitial) run(ctx context.Context) (err error) {
 
 	defer func() {
 		if err != nil {
-			if errDestroy := p.cloneManager.DestroySnapshot(snapshotName); errDestroy != nil {
-				log.Err(fmt.Sprintf("Failed to destroy the %q snapshot: %v", snapshotName, errDestroy))
+			if errDestroy := p.cloneManager.DestroySnapshot(snapshotName, thinclones.DestroyOptions{}); errDestroy != nil {
+				log.Err(fmt.Sprintf("failed to destroy %q snapshot: %v", snapshotName, errDestroy))
 			}
 		}
 	}()
 
-	if err := p.cloneManager.CreateClone(cloneName, snapshotName); err != nil {
+	if err := p.cloneManager.CreateClone(branching.DefaultBranch, cloneName, snapshotName, branching.DefaultRevision); err != nil {
 		return errors.Wrapf(err, "failed to create \"pre\" clone %s", cloneName)
 	}
 
-	cloneDataDir := path.Join(p.fsPool.ClonesDir(), cloneName, p.fsPool.DataSubDir)
+	cloneDataDir := path.Join(p.fsPool.CloneLocation(branching.DefaultBranch, cloneName, branching.DefaultRevision), p.fsPool.DataSubDir)
 	if err := fs.CleanupLogsDir(cloneDataDir); err != nil {
 		log.Warn("Failed to clean up logs directory:", err.Error())
 	}
 
 	defer func() {
 		if err != nil {
-			if errDestroy := p.cloneManager.DestroyClone(cloneName); errDestroy != nil {
-				log.Err(fmt.Sprintf("Failed to destroy clone %q: %v", cloneName, errDestroy))
+			if errDestroy := p.cloneManager.DestroyClone(branching.DefaultBranch, cloneName, branching.DefaultRevision); errDestroy != nil {
+				log.Err(fmt.Sprintf("failed to destroy clone %q: %v", cloneName, errDestroy))
 			}
 		}
 	}()
@@ -389,8 +393,9 @@ func (p *PhysicalInitial) run(ctx context.Context) (err error) {
 	}
 
 	// Create a snapshot.
-	if _, err := p.cloneManager.CreateSnapshot(cloneName, p.dbMark.DataStateAt); err != nil {
-		return errors.Wrap(err, "failed to create a snapshot")
+	fullClonePath := path.Join(branching.BranchDir, branching.DefaultBranch, cloneName, branching.RevisionSegment(branching.DefaultRevision))
+	if _, err := p.cloneManager.CreateSnapshot(fullClonePath, p.dbMark.DataStateAt); err != nil {
+		return errors.Wrap(err, "failed to create snapshot")
 	}
 
 	p.updateDataStateAt()
@@ -568,7 +573,7 @@ func (p *PhysicalInitial) promoteInstance(ctx context.Context, clonePath string,
 	if syState.Err != nil {
 		recoveryConfig = buildRecoveryConfig(recoveryFileConfig, p.options.Promotion.Recovery)
 
-		if err := cfgManager.ApplyRecovery(recoveryFileConfig); err != nil {
+		if err := cfgManager.ApplyRecovery(recoveryConfig); err != nil {
 			return errors.Wrap(err, "failed to apply recovery configuration")
 		}
 	} else if err := cfgManager.RemoveRecoveryConfig(); err != nil {
@@ -621,7 +626,7 @@ func (p *PhysicalInitial) promoteInstance(ctx context.Context, clonePath string,
 					Value: fmt.Sprintf("%s=%s", cont.DBLabControlLabel, cont.DBLabPromoteLabel)})
 
 			if err := diagnostic.CollectDiagnostics(ctx, p.dockerClient, filterArgs, p.promoteContainerName(), clonePath); err != nil {
-				log.Err("Failed to collect container diagnostics", err)
+				log.Err("failed to collect container diagnostics", err)
 			}
 		}
 	}()
@@ -1102,7 +1107,7 @@ func (p *PhysicalInitial) markDatabaseData() error {
 func (p *PhysicalInitial) updateDataStateAt() {
 	dsaTime, err := time.Parse(util.DataStateAtFormat, p.dbMark.DataStateAt)
 	if err != nil {
-		log.Err("Invalid value for DataStateAt: ", p.dbMark.DataStateAt)
+		log.Err("invalid value for DataStateAt: ", p.dbMark.DataStateAt)
 		return
 	}
 
diff --git a/engine/internal/retrieval/engine/postgres/tools/query/preprocessor.go b/engine/internal/retrieval/engine/postgres/tools/query/preprocessor.go
index 2e09da6e..00d48552 100644
--- a/engine/internal/retrieval/engine/postgres/tools/query/preprocessor.go
+++ b/engine/internal/retrieval/engine/postgres/tools/query/preprocessor.go
@@ -138,7 +138,7 @@ func (q *Processor) runParallel(ctx context.Context, containerID, parallelDir st
 				errCh <- err
 
 				cancel()
-				log.Err("Preprocessing query: ", err)
+				log.Err("preprocessing query: ", err)
 
 				return
 			}
diff --git a/engine/internal/retrieval/engine/postgres/tools/tools.go b/engine/internal/retrieval/engine/postgres/tools/tools.go
index 4a0bbe2d..1fe2cefe 100644
--- a/engine/internal/retrieval/engine/postgres/tools/tools.go
+++ b/engine/internal/retrieval/engine/postgres/tools/tools.go
@@ -15,6 +15,7 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
@@ -95,6 +96,24 @@ func IsEmptyDirectory(dir string) (bool, error) {
 	return len(names) == 0, nil
 }
 
+// CleanupDir removes content of the directory.
+func CleanupDir(dir string) error {
+	entries, err := os.ReadDir(dir)
+	if err != nil {
+		return fmt.Errorf("failed to read directory %s: %w", dir, err)
+	}
+
+	for _, entry := range entries {
+		entryName := filepath.Join(dir, entry.Name())
+
+		if err := os.RemoveAll(entryName); err != nil {
+			return fmt.Errorf("failed to remove %s: %w", entryName, err)
+		}
+	}
+
+	return nil
+}
+
 // TouchFile creates an empty file.
 func TouchFile(filename string) error {
 	file, err := os.Create(filename)
@@ -445,7 +464,7 @@ func StopContainer(ctx context.Context, dockerClient *client.Client, containerID
 	log.Msg(fmt.Sprintf("Stopping container ID: %v", containerID))
 
 	if err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{Timeout: pointer.ToInt(stopTimeout)}); err != nil {
-		log.Err("Failed to stop container: ", err)
+		log.Err("failed to stop container: ", err)
 	}
 
 	log.Msg(fmt.Sprintf("Container %q has been stopped", containerID))
@@ -456,7 +475,7 @@ func RemoveContainer(ctx context.Context, dockerClient *client.Client, container
 	log.Msg(fmt.Sprintf("Removing container ID: %v", containerID))
 
 	if err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{Timeout: pointer.ToInt(stopTimeout)}); err != nil {
-		log.Err("Failed to stop container: ", err)
+		log.Err("failed to stop container: ", err)
 	}
 
 	log.Msg(fmt.Sprintf("Container %q has been stopped", containerID))
@@ -465,7 +484,7 @@ func RemoveContainer(ctx context.Context, dockerClient *client.Client, container
 		RemoveVolumes: true,
 		Force:         true,
 	}); err != nil {
-		log.Err("Failed to remove container: ", err)
+		log.Err("failed to remove container: ", err)
 
 		return
 	}
@@ -495,7 +514,7 @@ func PullImage(ctx context.Context, dockerClient *client.Client, image string) e
 	defer func() { _ = pullOutput.Close() }()
 
 	if err := jsonmessage.DisplayJSONMessagesToStream(pullOutput, streams.NewOut(os.Stdout), nil); err != nil {
-		log.Err("Failed to render pull image output: ", err)
+		log.Err("failed to render pull image output: ", err)
 	}
 
 	return nil
@@ -667,7 +686,7 @@ func CopyContainerLogs(ctx context.Context, docker *client.Client, containerName
 	defer func() {
 		err := reader.Close()
 		if err != nil {
-			log.Err("Failed to close container output reader", err)
+			log.Err("failed to close container output reader", err)
 		}
 	}()
 
@@ -679,7 +698,7 @@ func CopyContainerLogs(ctx context.Context, docker *client.Client, containerName
 	defer func() {
 		err := writeFile.Close()
 		if err != nil {
-			log.Err("Failed to close container output file", err)
+			log.Err("failed to close container output file", err)
 		}
 	}()
 
diff --git a/engine/internal/retrieval/retrieval.go b/engine/internal/retrieval/retrieval.go
index 78b1f8fa..cb4f8423 100644
--- a/engine/internal/retrieval/retrieval.go
+++ b/engine/internal/retrieval/retrieval.go
@@ -10,7 +10,6 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
-	"strings"
 	"time"
 
 	"github.com/docker/docker/api/types"
@@ -22,6 +21,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/components"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/config"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/dbmarker"
@@ -51,6 +51,8 @@ const (
 	pendingFilename = "pending.retrieval"
 )
 
+var errNoJobs = errors.New("no jobs to snapshot pool data")
+
 type jobGroup string
 
 // Retrieval describes a data retrieval.
@@ -75,6 +77,12 @@ type Scheduler struct {
 	Spec cron.Schedule
 }
 
+var (
+	ErrRefreshInProgress = errors.New("The data refresh/snapshot is currently in progress. Skip a new data refresh iteration")
+	ErrRefreshPending    = errors.New("Data retrieving suspended because Retrieval state is pending")
+	ErrNoAvailablePool   = errors.New("Pool to perform full refresh not found. Skip refreshing")
+)
+
 // New creates a new data retrieval.
 func New(cfg *dblabCfg.Config, engineProps *global.EngineProps, docker *client.Client, pm *pool.Manager, tm *telemetry.Agent,
 	runner runners.Runner) (*Retrieval, error) {
@@ -174,7 +182,7 @@ func (r *Retrieval) reloadStatefulJobs() {
 		// todo should we remove if jobs are not there ?
 		// todo should we check for completion before ?
 		if err := job.Reload(cfg.Options); err != nil {
-			log.Err("Failed to reload configuration of the retrieval job", job.Name(), err)
+			log.Err("failed to reload configuration of retrieval job", job.Name(), err)
 		}
 	}
 }
@@ -350,7 +358,9 @@ func (r *Retrieval) run(ctx context.Context, fsm pool.FSManager) (err error) {
 		r.State.cleanAlerts()
 	}
 
-	if err := r.SnapshotData(ctx, poolName); err != nil {
+	var existsErr *thinclones.SnapshotExistsError
+
+	if err := r.SnapshotData(ctx, poolName); err != nil && (err != errNoJobs || !errors.As(err, &existsErr)) {
 		return err
 	}
 
@@ -359,6 +369,10 @@ func (r *Retrieval) run(ctx context.Context, fsm pool.FSManager) (err error) {
 		r.State.cleanAlerts()
 	}
 
+	if err := fsm.InitBranching(); err != nil {
+		return fmt.Errorf("failed to init branching: %w", err)
+	}
+
 	return nil
 }
 
@@ -406,12 +420,6 @@ func (r *Retrieval) RefreshData(ctx context.Context, poolName string) error {
 		r.State.CurrentJob = nil
 	}()
 
-	if r.State.Mode == models.Logical {
-		if err := preparePoolToRefresh(fsm, r.runner); err != nil {
-			return fmt.Errorf("failed to prepare pool for initial refresh: %w", err)
-		}
-	}
-
 	for _, j := range jobs {
 		r.State.CurrentJob = j
 
@@ -446,8 +454,8 @@ func (r *Retrieval) SnapshotData(ctx context.Context, poolName string) error {
 	}
 
 	if len(jobs) == 0 {
-		log.Dbg("no jobs to snapshot pool data:", fsm.Pool())
-		return nil
+		log.Dbg(errNoJobs, fsm.Pool())
+		return errNoJobs
 	}
 
 	log.Dbg("Taking a snapshot on the pool: ", fsm.Pool())
@@ -457,7 +465,9 @@ func (r *Retrieval) SnapshotData(ctx context.Context, poolName string) error {
 	defer func() {
 		r.State.Status = models.Finished
 
-		if err != nil {
+		var existsErr *thinclones.SnapshotExistsError
+
+		if err != nil && !errors.As(err, &existsErr) {
 			r.State.Status = models.Failed
 			r.State.addAlert(telemetry.Alert{
 				Level:   models.RefreshFailed,
@@ -580,20 +590,20 @@ func (r *Retrieval) refreshFunc(ctx context.Context) func() {
 
 // FullRefresh performs full refresh for an unused storage pool and makes it active.
 func (r *Retrieval) FullRefresh(ctx context.Context) error {
-	if r.State.Status == models.Refreshing || r.State.Status == models.Snapshotting {
-		alert := telemetry.Alert{
-			Level:   models.RefreshSkipped,
-			Message: "The data refresh/snapshot is currently in progress. Skip a new data refresh iteration",
-		}
-		r.State.addAlert(alert)
-		r.tm.SendEvent(ctx, telemetry.AlertEvent, alert)
-		log.Msg(alert.Message)
-
-		return nil
-	}
+	if err := r.CanStartRefresh(); err != nil {
+		switch {
+		case errors.Is(err, ErrRefreshInProgress):
+			alert := telemetry.Alert{
+				Level:   models.RefreshSkipped,
+				Message: err.Error(),
+			}
+			r.State.addAlert(alert)
+			r.tm.SendEvent(ctx, telemetry.AlertEvent, alert)
+			log.Msg(alert.Message)
 
-	if r.State.Status == models.Pending {
-		log.Msg("Data retrieving suspended because Retrieval state is pending")
+		case errors.Is(err, ErrRefreshPending):
+			log.Msg(err.Error())
+		}
 
 		return nil
 	}
@@ -605,31 +615,32 @@ func (r *Retrieval) FullRefresh(ctx context.Context) error {
 
 	runCtx, cancel := context.WithCancel(ctx)
 	r.ctxCancel = cancel
-	elementToUpdate := r.poolManager.GetPoolToUpdate()
 
-	if elementToUpdate == nil || elementToUpdate.Value == nil {
+	if err := r.HasAvailablePool(); err != nil {
 		alert := telemetry.Alert{
 			Level:   models.RefreshSkipped,
-			Message: "Pool to perform full refresh not found. Skip refreshing",
+			Message: err.Error(),
 		}
 		r.State.addAlert(alert)
 		r.tm.SendEvent(ctx, telemetry.AlertEvent, alert)
-		log.Msg(alert.Message + ". Hint: Check that there is at least one pool that does not have clones running. " +
+		log.Msg(err.Error() + ". Hint: Check that there is at least one pool that does not have clones running. " +
 			"Refresh can be performed only to a pool without clones.")
 
 		return nil
 	}
 
+	elementToUpdate := r.poolManager.GetPoolToUpdate()
+
 	poolToUpdate, err := r.poolManager.GetFSManager(elementToUpdate.Value.(string))
 	if err != nil {
 		return errors.Wrap(err, "failed to get FSManager")
 	}
 
-	log.Msg("Pool to a full refresh: ", poolToUpdate.Pool())
+	log.Msg("Pool selected to perform full refresh: ", poolToUpdate.Pool())
 
 	// Stop service containers: sync-instance, etc.
 	if cleanUpErr := cont.CleanUpControlContainers(runCtx, r.docker, r.engineProps.InstanceID); cleanUpErr != nil {
-		log.Err("Failed to clean up service containers:", cleanUpErr)
+		log.Err("failed to clean up service containers:", cleanUpErr)
 
 		return cleanUpErr
 	}
@@ -656,44 +667,6 @@ func (r *Retrieval) stopScheduler() {
 	}
 }
 
-func preparePoolToRefresh(poolToUpdate pool.FSManager, runner runners.Runner) error {
-	cloneList, err := poolToUpdate.ListClonesNames()
-	if err != nil {
-		return errors.Wrap(err, "failed to check running clones")
-	}
-
-	if len(cloneList) > 0 {
-		return errors.Errorf("there are active clones in the requested pool: %s\nDestroy them to perform a full refresh",
-			strings.Join(cloneList, " "))
-	}
-
-	if _, err := runner.Run(fmt.Sprintf("rm -rf %s %s",
-		filepath.Join(poolToUpdate.Pool().DataDir(), "*"),
-		filepath.Join(poolToUpdate.Pool().DataDir(), dbmarker.ConfigDir))); err != nil {
-		return errors.Wrap(err, "failed to clean unix socket directory")
-	}
-
-	poolToUpdate.RefreshSnapshotList()
-
-	snapshots := poolToUpdate.SnapshotList()
-	if len(snapshots) == 0 {
-		log.Msg(fmt.Sprintf("no snapshots for pool %s", poolToUpdate.Pool().Name))
-		return nil
-	}
-
-	log.Msg("Preparing pool for full data refresh; existing snapshots are to be destroyed")
-
-	for _, snapshotEntry := range snapshots {
-		log.Msg("Destroying snapshot:", snapshotEntry.ID)
-
-		if err := poolToUpdate.DestroySnapshot(snapshotEntry.ID); err != nil {
-			return errors.Wrap(err, "failed to destroy the existing snapshot")
-		}
-	}
-
-	return nil
-}
-
 // ReportState collects the current restore state.
 func (r *Retrieval) ReportState() telemetry.Restore {
 	var refreshingTimetable string
@@ -827,3 +800,24 @@ func (r *Retrieval) reportContainerSyncStatus(ctx context.Context, containerID s
 
 	return value, nil
 }
+
+func (r *Retrieval) CanStartRefresh() error {
+	if r.State.Status == models.Refreshing || r.State.Status == models.Snapshotting {
+		return ErrRefreshInProgress
+	}
+
+	if r.State.Status == models.Pending {
+		return ErrRefreshPending
+	}
+
+	return nil
+}
+
+func (r *Retrieval) HasAvailablePool() error {
+	element := r.poolManager.GetPoolToUpdate()
+	if element == nil || element.Value == nil {
+		return ErrNoAvailablePool
+	}
+
+	return nil
+}
diff --git a/engine/internal/runci/handlers.go b/engine/internal/runci/handlers.go
index 120b795a..35236a49 100644
--- a/engine/internal/runci/handlers.go
+++ b/engine/internal/runci/handlers.go
@@ -30,7 +30,6 @@ import (
 	dblab_types "gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
-	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
 	"gitlab.com/postgres-ai/database-lab/v3/version"
 )
 
@@ -266,7 +265,7 @@ func (s *Server) runCommands(ctx context.Context, clone *models.Clone, runID str
 func (s *Server) buildContainerConfig(clone *models.Clone, migrationEnvs []string) *container.Config {
 	host := clone.DB.Host
 	if host == s.dle.URL("").Hostname() || host == "127.0.0.1" || host == "localhost" {
-		host = util.GetCloneNameStr(clone.DB.Port)
+		host = clone.ID
 	}
 
 	return &container.Config{
diff --git a/engine/internal/srv/branch.go b/engine/internal/srv/branch.go
new file mode 100644
index 00000000..389b931c
--- /dev/null
+++ b/engine/internal/srv/branch.go
@@ -0,0 +1,699 @@
+package srv
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"regexp"
+	"strings"
+	"time"
+
+	"github.com/gorilla/mux"
+
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/srv/api"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/webhooks"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
+)
+
+var branchNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9_][a-zA-Z0-9_-]*$`)
+
+// listBranches returns branch list.
+func (s *Server) listBranches(w http.ResponseWriter, r *http.Request) {
+	fsm := s.pm.First()
+
+	if fsm == nil {
+		api.SendBadRequestError(w, r, "no available pools")
+		return
+	}
+
+	branches, err := s.getAllAvailableBranches(fsm)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	repo, err := fsm.GetAllRepo()
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	branchDetails := make([]models.BranchView, 0, len(branches))
+
+	// branchRegistry is used to display the "main" branch with only the most recent snapshot.
+	branchRegistry := make(map[string]int, 0)
+
+	for _, branchEntity := range branches {
+		snapshotDetails, ok := repo.Snapshots[branchEntity.SnapshotID]
+		if !ok {
+			continue
+		}
+
+		numSnapshots, parentSnapshot := findBranchParent(repo.Snapshots, snapshotDetails.ID, branchEntity.Name)
+
+		branchView := models.BranchView{
+			Name:         branchEntity.Name,
+			Parent:       parentSnapshot,
+			DataStateAt:  snapshotDetails.DataStateAt,
+			SnapshotID:   snapshotDetails.ID,
+			Dataset:      snapshotDetails.Dataset,
+			NumSnapshots: numSnapshots,
+		}
+
+		if position, ok := branchRegistry[branchEntity.Name]; ok {
+			if branchView.DataStateAt > branchDetails[position].DataStateAt {
+				branchDetails[position] = branchView
+			}
+
+			continue
+		}
+
+		branchRegistry[branchView.Name] = len(branchDetails)
+		branchDetails = append(branchDetails, branchView)
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, branchDetails); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func (s *Server) getAllAvailableBranches(fsm pool.FSManager) ([]models.BranchEntity, error) {
+	if fsm == nil {
+		return nil, fmt.Errorf("no available pools")
+	}
+
+	// Filter by available pools in case if two or more DLE is running on the same pool and use the selectedPool feature.
+	poolNames := []string{}
+
+	for _, fsManager := range s.pm.GetFSManagerList() {
+		poolNames = append(poolNames, fsManager.Pool().Name)
+	}
+
+	return fsm.ListAllBranches(poolNames)
+}
+
+func findBranchParent(snapshots map[string]models.SnapshotDetails, parentID, branch string) (int, string) {
+	snapshotCounter := 0
+
+	for i := len(snapshots); i > 0; i-- {
+		snapshotPointer := snapshots[parentID]
+		snapshotCounter++
+
+		if containsString(snapshotPointer.Root, branch) {
+			if len(snapshotPointer.Branch) > 0 {
+				return snapshotCounter, snapshotPointer.Branch[0]
+			}
+
+			break
+		}
+
+		if snapshotPointer.Parent == "-" {
+			break
+		}
+
+		parentID = snapshotPointer.Parent
+	}
+
+	return snapshotCounter, "-"
+}
+
+func containsString(slice []string, s string) bool {
+	for _, str := range slice {
+		if str == s {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (s *Server) getFSManagerForBranch(branchName string) (pool.FSManager, error) {
+	allBranches, err := s.getAllAvailableBranches(s.pm.First())
+	if err != nil {
+		return nil, fmt.Errorf("failed to get branch list: %w", err)
+	}
+
+	for _, branchEntity := range allBranches {
+		if branchEntity.Name == branchName { // TODO: filter by pool name as well because branch name is ambiguous.
+			return s.getFSManagerForSnapshot(branchEntity.SnapshotID)
+		}
+	}
+
+	return nil, fmt.Errorf("failed to found dataset of the branch: %s", branchName)
+}
+
+func (s *Server) createBranch(w http.ResponseWriter, r *http.Request) {
+	var createRequest types.BranchCreateRequest
+	if err := api.ReadJSON(r, &createRequest); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if createRequest.BranchName == "" {
+		api.SendBadRequestError(w, r, "The branch name must not be empty")
+		return
+	}
+
+	if createRequest.BranchName == createRequest.BaseBranch {
+		api.SendBadRequestError(w, r, "new and base branches must have different names")
+		return
+	}
+
+	if !isValidBranchName(createRequest.BranchName) {
+		api.SendBadRequestError(w, r, "The branch name must start with a letter, number, or underscore, "+
+			"and contain only letters, numbers, underscores, and hyphens. Spaces and slashes are not allowed")
+		return
+	}
+
+	var err error
+
+	fsm := s.pm.First()
+
+	if createRequest.BaseBranch != "" {
+		fsm, err = s.getFSManagerForBranch(createRequest.BaseBranch)
+		if err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+	}
+
+	if fsm == nil {
+		api.SendBadRequestError(w, r, "no pool manager found")
+		return
+	}
+
+	branches, err := fsm.ListBranches()
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if _, ok := branches[createRequest.BranchName]; ok {
+		api.SendBadRequestError(w, r, fmt.Sprintf("branch '%s' already exists", createRequest.BranchName))
+		return
+	}
+
+	snapshotID := createRequest.SnapshotID
+
+	if snapshotID == "" {
+		if createRequest.BaseBranch == "" {
+			api.SendBadRequestError(w, r, "either base branch name or base snapshot ID must be specified")
+			return
+		}
+
+		branchPointer, ok := branches[createRequest.BaseBranch]
+		if !ok {
+			api.SendBadRequestError(w, r, "base branch not found")
+			return
+		}
+
+		snapshotID = branchPointer
+	}
+
+	poolName, err := s.detectPoolName(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	brName := fsm.Pool().BranchName(poolName, createRequest.BranchName)
+	dataStateAt := time.Now().Format(util.DataStateAtFormat)
+
+	if err := fsm.CreateBranch(brName, snapshotID); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	branchSnapshot := fmt.Sprintf("%s@%s", brName, dataStateAt)
+
+	if err := fsm.Snapshot(branchSnapshot); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.AddBranchProp(createRequest.BranchName, branchSnapshot); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetRoot(createRequest.BranchName, snapshotID); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetRelation(snapshotID, branchSnapshot); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetDSA(dataStateAt, branchSnapshot); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	fsm.RefreshSnapshotList()
+
+	branch := models.Branch{Name: createRequest.BranchName}
+
+	s.webhookCh <- webhooks.BasicEvent{
+		EventType: webhooks.BranchCreateEvent,
+		EntityID:  branch.Name,
+	}
+
+	s.tm.SendEvent(context.Background(), telemetry.BranchCreatedEvent, telemetry.BranchCreated{
+		Name: branch.Name,
+	})
+
+	if err := api.WriteJSON(w, http.StatusOK, branch); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func isValidBranchName(branchName string) bool {
+	return branchNameRegexp.MatchString(branchName)
+}
+
+func (s *Server) getSnapshot(w http.ResponseWriter, r *http.Request) {
+	snapshotID := mux.Vars(r)["id"]
+
+	if snapshotID == "" {
+		api.SendBadRequestError(w, r, "snapshotID must not be empty")
+		return
+	}
+
+	snapshot, err := s.Cloning.GetSnapshotByID(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, snapshot); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func (s *Server) getCommit(w http.ResponseWriter, r *http.Request) {
+	snapshotID := mux.Vars(r)["id"]
+
+	if snapshotID == "" {
+		api.SendBadRequestError(w, r, "snapshotID must not be empty")
+		return
+	}
+
+	fsm, err := s.getFSManagerForSnapshot(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	repo, err := fsm.GetRepo()
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	snapshotPointer, ok := repo.Snapshots[snapshotID]
+
+	if !ok {
+		api.SendNotFoundError(w, r)
+		return
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, snapshotPointer); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func (s *Server) getFSManagerForSnapshot(snapshotID string) (pool.FSManager, error) {
+	poolName, err := s.detectPoolName(snapshotID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to detect pool name for the snapshot %s: %w", snapshotID, err)
+	}
+
+	fsm, err := s.pm.GetFSManager(poolName)
+	if err != nil {
+		return nil, fmt.Errorf("pool manager not available %s: %w", poolName, err)
+	}
+
+	return fsm, nil
+}
+
+func (s *Server) snapshot(w http.ResponseWriter, r *http.Request) {
+	var snapshotRequest types.SnapshotCloneCreateRequest
+	if err := api.ReadJSON(r, &snapshotRequest); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	clone, err := s.Cloning.GetClone(snapshotRequest.CloneID)
+	if err != nil {
+		api.SendBadRequestError(w, r, "clone not found")
+		return
+	}
+
+	if clone.Branch == "" {
+		api.SendBadRequestError(w, r, "clone was not created on branch")
+		return
+	}
+
+	fsm, err := s.pm.GetFSManager(clone.Snapshot.Pool)
+
+	if err != nil {
+		api.SendBadRequestError(w, r, fmt.Sprintf("pool %q not found", clone.Snapshot.Pool))
+		return
+	}
+
+	branches, err := fsm.ListBranches()
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	currentSnapshotID, ok := branches[clone.Branch]
+	if !ok {
+		api.SendBadRequestError(w, r, "branch not found: "+clone.Branch)
+		return
+	}
+
+	log.Dbg("Current snapshot ID", currentSnapshotID)
+
+	dataStateAt := time.Now().Format(util.DataStateAtFormat)
+	snapshotBase := fsm.Pool().CloneName(clone.Branch, clone.ID, clone.Revision)
+	snapshotName := fmt.Sprintf("%s@%s", snapshotBase, dataStateAt)
+
+	if err := fsm.Snapshot(snapshotName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetDSA(dataStateAt, snapshotName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.AddBranchProp(clone.Branch, snapshotName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.DeleteBranchProp(clone.Branch, currentSnapshotID); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetRelation(currentSnapshotID, snapshotName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetDSA(dataStateAt, snapshotName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := fsm.SetMessage(snapshotRequest.Message, snapshotName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	fsm.RefreshSnapshotList()
+
+	if err := s.Cloning.ReloadSnapshots(); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	snapshot, err := s.Cloning.GetSnapshotByID(snapshotName)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := s.Cloning.UpdateCloneSnapshot(clone.ID, snapshot); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	s.tm.SendEvent(context.Background(), telemetry.SnapshotCreatedEvent, telemetry.SnapshotCreated{})
+
+	if err := api.WriteJSON(w, http.StatusOK, types.SnapshotResponse{SnapshotID: snapshotName}); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func filterSnapshotsByBranch(pool *resources.Pool, branch string, snapshots []models.Snapshot) []models.Snapshot {
+	filtered := make([]models.Snapshot, 0)
+
+	branchName := pool.BranchName(pool.Name, branch)
+
+	for _, sn := range snapshots {
+		dataset, _, found := strings.Cut(sn.ID, "@")
+		if !found {
+			continue
+		}
+
+		if strings.HasPrefix(dataset, branchName) || (branch == branching.DefaultBranch && pool.Name == dataset) {
+			filtered = append(filtered, sn)
+		}
+	}
+
+	return filtered
+}
+
+func (s *Server) log(w http.ResponseWriter, r *http.Request) {
+	branchName := mux.Vars(r)["branchName"]
+
+	fsm, err := s.getFSManagerForBranch(branchName)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if fsm == nil {
+		api.SendBadRequestError(w, r, "no pool manager found")
+		return
+	}
+
+	repo, err := fsm.GetRepo()
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	snapshotID, ok := repo.Branches[branchName]
+	if !ok {
+		api.SendBadRequestError(w, r, "branch not found: "+branchName)
+		return
+	}
+
+	snapshotPointer := repo.Snapshots[snapshotID]
+
+	logList := []models.SnapshotDetails{snapshotPointer}
+
+	// Limit the number of iterations to the number of snapshots.
+	for i := len(repo.Snapshots); i > 1; i-- {
+		if snapshotPointer.Parent == "-" || snapshotPointer.Parent == "" {
+			break
+		}
+
+		snapshotPointer = repo.Snapshots[snapshotPointer.Parent]
+		logList = append(logList, snapshotPointer)
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, logList); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func (s *Server) deleteBranch(w http.ResponseWriter, r *http.Request) {
+	branchName := mux.Vars(r)["branchName"]
+
+	fsm, err := s.getFSManagerForBranch(branchName)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if fsm == nil {
+		api.SendBadRequestError(w, r, "no pool manager found")
+		return
+	}
+
+	repo, err := fsm.GetRepo()
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if branchName == branching.DefaultBranch {
+		api.SendBadRequestError(w, r, fmt.Sprintf("cannot delete default branch: %s", branching.DefaultBranch))
+		return
+	}
+
+	snapshotID, ok := repo.Branches[branchName]
+	if !ok {
+		api.SendBadRequestError(w, r, "branch not found: "+branchName)
+		return
+	}
+
+	toRemove := snapshotsToRemove(repo, snapshotID, branchName)
+
+	log.Dbg("Snapshots to remove", toRemove)
+
+	if len(toRemove) > 0 {
+		// Pre-check.
+		preCheckList := make(map[string]int)
+
+		for _, snapshotID := range toRemove {
+			if cloneNum := s.Cloning.GetCloneNumber(snapshotID); cloneNum > 0 {
+				preCheckList[snapshotID] = cloneNum
+			}
+		}
+
+		if len(preCheckList) > 0 {
+			errMsg := fmt.Sprintf("cannot delete branch %q because", branchName)
+
+			for snapID, cloneNum := range preCheckList {
+				errMsg += fmt.Sprintf(" snapshot %q contains %d clone(s)", snapID, cloneNum)
+			}
+
+			log.Warn(errMsg)
+			api.SendBadRequestError(w, r, errMsg)
+
+			return
+		}
+	}
+
+	if err := s.destroyBranchDataset(fsm, branchName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, models.Response{
+		Status:  models.ResponseOK,
+		Message: "Deleted branch",
+	}); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func cleanupSnapshotProperties(repo *models.Repo, fsm pool.FSManager, branchName string) error {
+	for _, snap := range repo.Snapshots {
+		for _, rootBranch := range snap.Root {
+			if rootBranch == branchName {
+				if err := fsm.DeleteRootProp(branchName, snap.ID); err != nil {
+					return err
+				}
+
+				if err := fsm.DeleteBranchProp(branchName, snap.ID); err != nil {
+					return err
+				}
+
+				for _, child := range snap.Child {
+					if _, ok := repo.Snapshots[child]; !ok {
+						if err := fsm.DeleteChildProp(child, snap.ID); err != nil {
+							return err
+						}
+					}
+				}
+
+				break
+			}
+		}
+	}
+
+	return nil
+}
+
+func snapshotsToRemove(repo *models.Repo, snapshotID, branchName string) []string {
+	removingList := []string{}
+
+	// Traverse up the snapshot tree
+	removingList = append(removingList, traverseUp(repo, snapshotID, branchName)...)
+
+	// Traverse down the snapshot tree
+	removingList = append(removingList, traverseDown(repo, snapshotID)...)
+
+	return removingList
+}
+
+func traverseUp(repo *models.Repo, snapshotID, branchName string) []string {
+	snapshotPointer := repo.Snapshots[snapshotID]
+
+	removingList := []string{}
+
+	for snapshotPointer.Parent != "-" {
+		for _, snapshotRoot := range snapshotPointer.Root {
+			if snapshotRoot == branchName {
+				return removingList
+			}
+		}
+
+		removingList = append(removingList, snapshotPointer.ID)
+		snapshotPointer = repo.Snapshots[snapshotPointer.Parent]
+	}
+
+	return removingList
+}
+
+func traverseDown(repo *models.Repo, snapshotID string) []string {
+	snapshotPointer := repo.Snapshots[snapshotID]
+
+	removingList := []string{}
+
+	for _, snapshotChild := range snapshotPointer.Child {
+		removingList = append(removingList, snapshotChild)
+		removingList = append(removingList, traverseDown(repo, snapshotChild)...)
+	}
+
+	return removingList
+}
+
+func (s *Server) destroyBranchDataset(fsm pool.FSManager, branchName string) error {
+	branchDatasetName := fsm.Pool().BranchName(fsm.Pool().Name, branchName)
+
+	if err := fsm.DestroyDataset(branchDatasetName); err != nil {
+		log.Warn(fmt.Sprintf("failed to remove dataset %q:", branchDatasetName), err)
+
+		return err
+	}
+
+	// Re-request the repository as the list of snapshots may change significantly.
+	repo, err := fsm.GetRepo()
+	if err != nil {
+		return err
+	}
+
+	if err := cleanupSnapshotProperties(repo, fsm, branchName); err != nil {
+		return err
+	}
+
+	fsm.RefreshSnapshotList()
+
+	s.webhookCh <- webhooks.BasicEvent{
+		EventType: webhooks.BranchDeleteEvent,
+		EntityID:  branchName,
+	}
+
+	s.tm.SendEvent(context.Background(), telemetry.BranchDestroyedEvent, telemetry.BranchDestroyed{
+		Name: branchName,
+	})
+
+	log.Dbg(fmt.Sprintf("Branch %s has been deleted", branchName))
+
+	return nil
+}
diff --git a/engine/internal/srv/branch_test.go b/engine/internal/srv/branch_test.go
new file mode 100644
index 00000000..1a7dc420
--- /dev/null
+++ b/engine/internal/srv/branch_test.go
@@ -0,0 +1,79 @@
+package srv
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
+)
+
+func TestBranchNames(t *testing.T) {
+	t.Run("valid branches", func(t *testing.T) {
+		testCases := []struct {
+			branchName string
+		}{
+			{branchName: "001-branch"},
+			{branchName: "001_branch"},
+			{branchName: "001_"},
+			{branchName: "_branch"},
+			{branchName: "branch"},
+			{branchName: "001"},
+			{branchName: "a-branch"},
+			{branchName: "branch-001"},
+		}
+
+		for _, tc := range testCases {
+			require.True(t, isValidBranchName(tc.branchName))
+		}
+	})
+
+	t.Run("invalid branches", func(t *testing.T) {
+		testCases := []struct {
+			branchName string
+		}{
+			{branchName: "001 branch"},
+			{branchName: ""},
+			{branchName: "branch 001"},
+			{branchName: "branch/001"},
+			{branchName: "-branch"},
+			{branchName: "tři"},
+		}
+
+		for _, tc := range testCases {
+			require.False(t, isValidBranchName(tc.branchName))
+		}
+	})
+
+}
+
+func TestSnapshotFiltering(t *testing.T) {
+	t.Run("filter snapshots", func(t *testing.T) {
+		pool := &resources.Pool{Name: "pool1/pg14"}
+		input := []models.Snapshot{
+			{ID: "pool1/pg14@snapshot_20240912082141", Pool: "pool1/pg14"},
+			{ID: "pool1/pg14@snapshot_20240912082987", Pool: "pool1/pg14"},
+			{ID: "pool5/pg14@snapshot_20240912082987", Pool: "pool5/pg14"},
+			{ID: "pool1/pg14/branch/main@snapshot_20240912082333", Pool: "pool1/pg14"},
+			{ID: "pool1/pg14/branch/dev001@snapshot_20240912082141", Pool: "pool1/pg14"},
+			{ID: "pool1/pg14/branch/dev001/20240912082141@20240912082141", Pool: "pool1/pg14"},
+			{ID: "pool5/pg14/branch/dev001@snapshot_20240912082141", Pool: "pool5/pg14"},
+			{ID: "pool1/pg14/branch/dev002/20240912082141@20240912082141", Pool: "pool1/pg14"},
+		}
+
+		outputDev001 := []models.Snapshot{
+			{ID: "pool1/pg14/branch/dev001@snapshot_20240912082141", Pool: "pool1/pg14"},
+			{ID: "pool1/pg14/branch/dev001/20240912082141@20240912082141", Pool: "pool1/pg14"},
+		}
+
+		outputMain := []models.Snapshot{
+			{ID: "pool1/pg14@snapshot_20240912082141", Pool: "pool1/pg14"},
+			{ID: "pool1/pg14@snapshot_20240912082987", Pool: "pool1/pg14"},
+			{ID: "pool1/pg14/branch/main@snapshot_20240912082333", Pool: "pool1/pg14"},
+		}
+
+		require.Equal(t, outputDev001, filterSnapshotsByBranch(pool, "dev001", input))
+		require.Equal(t, outputMain, filterSnapshotsByBranch(pool, "main", input))
+	})
+}
diff --git a/engine/internal/srv/config.go b/engine/internal/srv/config.go
index a8d34f7b..e10bcbf8 100644
--- a/engine/internal/srv/config.go
+++ b/engine/internal/srv/config.go
@@ -17,6 +17,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/logical"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/db"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/srv/api"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
@@ -78,6 +79,8 @@ func (s *Server) setProjectedAdminConfig(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	s.tm.SendEvent(context.Background(), telemetry.ConfigUpdatedEvent, telemetry.ConfigUpdated{})
+
 	retrievalStatus := s.Retrieval.State.Status
 
 	if err := s.Retrieval.RemovePendingMarker(); err != nil {
@@ -288,7 +291,7 @@ func (s *Server) applyProjectedAdminConfig(ctx context.Context, obj interface{})
 
 		err = config.RotateConfig(cfgData)
 		if err != nil {
-			log.Errf("Failed to backup config: %v", err)
+			log.Errf("failed to backup config: %v", err)
 			return nil, err
 		}
 
diff --git a/engine/internal/srv/routes.go b/engine/internal/srv/routes.go
index b2dab871..15f2ab56 100644
--- a/engine/internal/srv/routes.go
+++ b/engine/internal/srv/routes.go
@@ -6,22 +6,29 @@ import (
 	"fmt"
 	"net/http"
 	"os"
+	"sort"
 	"strconv"
+	"strings"
 	"time"
 
 	"github.com/gorilla/mux"
 	"github.com/pkg/errors"
 
 	"gitlab.com/postgres-ai/database-lab/v3/internal/observer"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/activity"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/srv/api"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/webhooks"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/platform"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/util"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/util/branching"
 	"gitlab.com/postgres-ai/database-lab/v3/version"
 )
 
@@ -101,12 +108,369 @@ func (s *Server) getSnapshots(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if branchRequest := r.URL.Query().Get("branch"); branchRequest != "" {
+		fsm, err := s.getFSManagerForBranch(branchRequest)
+		if err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+
+		if fsm == nil {
+			api.SendBadRequestError(w, r, "no pool manager found")
+			return
+		}
+
+		snapshots = filterSnapshotsByBranch(fsm.Pool(), branchRequest, snapshots)
+	}
+
 	if err = api.WriteJSON(w, http.StatusOK, snapshots); err != nil {
 		api.SendError(w, r, err)
 		return
 	}
 }
 
+func (s *Server) createSnapshot(w http.ResponseWriter, r *http.Request) {
+	var poolName string
+
+	if r.Body != http.NoBody {
+		var createRequest types.SnapshotCreateRequest
+		if err := api.ReadJSON(r, &createRequest); err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+
+		poolName = createRequest.PoolName
+	}
+
+	if poolName == "" {
+		firstFSM := s.pm.First()
+
+		if firstFSM == nil || firstFSM.Pool() == nil {
+			api.SendBadRequestError(w, r, pool.ErrNoPools.Error())
+			return
+		}
+
+		poolName = firstFSM.Pool().Name
+	}
+
+	if err := s.Retrieval.SnapshotData(context.Background(), poolName); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	fsManager, err := s.pm.GetFSManager(poolName)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	fsManager.RefreshSnapshotList()
+
+	snapshotList := fsManager.SnapshotList()
+
+	if len(snapshotList) == 0 {
+		api.SendBadRequestError(w, r, "No snapshots at pool: "+poolName)
+		return
+	}
+
+	sort.SliceStable(snapshotList, func(i, j int) bool {
+		return snapshotList[i].CreatedAt.After(snapshotList[j].CreatedAt)
+	})
+
+	if err := fsManager.InitBranching(); err != nil {
+		api.SendBadRequestError(w, r, "Cannot verify branch metadata: "+err.Error())
+		return
+	}
+
+	// TODO: set branching metadata.
+
+	latestSnapshot := snapshotList[0]
+
+	s.webhookCh <- webhooks.BasicEvent{
+		EventType: webhooks.SnapshotCreateEvent,
+		EntityID:  latestSnapshot.ID,
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, latestSnapshot); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func (s *Server) deleteSnapshot(w http.ResponseWriter, r *http.Request) {
+	snapshotID := mux.Vars(r)["id"]
+	if snapshotID == "" {
+		api.SendBadRequestError(w, r, "snapshot ID must not be empty")
+		return
+	}
+
+	forceParam := r.URL.Query().Get("force")
+	force := false
+
+	if forceParam != "" {
+		var err error
+		force, err = strconv.ParseBool(forceParam)
+
+		if err != nil {
+			api.SendBadRequestError(w, r, "invalid value for `force`, must be boolean")
+			return
+		}
+	}
+
+	poolName, err := s.detectPoolName(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if poolName == "" {
+		api.SendBadRequestError(w, r, fmt.Sprintf("pool for requested snapshot (%s) not found", snapshotID))
+		return
+	}
+
+	fsm, err := s.pm.GetFSManager(poolName)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	// Prevent deletion of automatic snapshots in the pool.
+	if fullDataset, _, found := strings.Cut(snapshotID, "@"); found && fullDataset == poolName {
+		api.SendBadRequestError(w, r, "cannot destroy automatic snapshot in the pool")
+		return
+	}
+
+	// Check if snapshot exists.
+	if _, err := fsm.GetSnapshotProperties(snapshotID); err != nil {
+		if runnerError, ok := err.(runners.RunnerError); ok {
+			api.SendBadRequestError(w, r, runnerError.Stderr)
+		} else {
+			api.SendBadRequestError(w, r, err.Error())
+		}
+
+		return
+	}
+
+	cloneIDs := []string{}
+	protectedClones := []string{}
+
+	dependentCloneDatasets, err := fsm.HasDependentEntity(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	for _, cloneDataset := range dependentCloneDatasets {
+		cloneID, ok := branching.ParseCloneName(cloneDataset, poolName)
+		if !ok {
+			log.Dbg(fmt.Sprintf("cannot parse clone ID from %q", cloneDataset))
+			continue
+		}
+
+		clone, err := s.Cloning.GetClone(cloneID)
+
+		if err != nil {
+			continue
+		}
+
+		cloneIDs = append(cloneIDs, clone.ID)
+
+		if clone.Protected {
+			protectedClones = append(protectedClones, clone.ID)
+		}
+	}
+
+	if len(protectedClones) != 0 {
+		api.SendBadRequestError(w, r, fmt.Sprintf("cannot delete snapshot %s because it has dependent protected clones: %s",
+			snapshotID, strings.Join(protectedClones, ",")))
+		return
+	}
+
+	if len(cloneIDs) != 0 && !force {
+		api.SendBadRequestError(w, r, fmt.Sprintf("cannot delete snapshot %s because it has dependent clones: %s",
+			snapshotID, strings.Join(cloneIDs, ",")))
+		return
+	}
+
+	snapshotProperties, err := fsm.GetSnapshotProperties(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if snapshotProperties.Clones != "" && !force {
+		api.SendBadRequestError(w, r, fmt.Sprintf("cannot delete snapshot %s because it has dependent datasets: %s",
+			snapshotID, snapshotProperties.Clones))
+		return
+	}
+
+	// Remove dependent clones.
+	for _, cloneID := range cloneIDs {
+		if err = s.Cloning.DestroyCloneSync(cloneID); err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+	}
+
+	// Remove snapshot and dependent datasets.
+	if !force {
+		if err := fsm.KeepRelation(snapshotID); err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+	}
+
+	if err = fsm.DestroySnapshot(snapshotID, thinclones.DestroyOptions{Force: force}); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	snapshot, err := s.Cloning.GetSnapshotByID(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if snapshotProperties.Clones == "" && snapshot.NumClones == 0 {
+		// Destroy dataset if there are no related objects
+		if fullDataset, _, found := strings.Cut(snapshotID, "@"); found && fullDataset != poolName {
+			if err = fsm.DestroyDataset(fullDataset); err != nil {
+				api.SendBadRequestError(w, r, err.Error())
+				return
+			}
+
+			// Remove dle:branch and dle:root from parent snapshot
+			if snapshotProperties.Parent != "" {
+				branchName := snapshotProperties.Branch
+				if branchName == "" {
+					branchName, _ = branching.ParseBranchName(fullDataset, poolName)
+				}
+
+				if branchName != "" {
+					if err := fsm.DeleteBranchProp(branchName, snapshotProperties.Parent); err != nil {
+						log.Err(err.Error())
+					}
+
+					if err := fsm.DeleteRootProp(branchName, snapshotProperties.Parent); err != nil {
+						log.Err(err.Error())
+					}
+				}
+			}
+
+			// TODO: review all available revisions. Destroy base dataset only if there no any revision.
+			if baseDataset, found := strings.CutSuffix(fullDataset, "/r0"); found {
+				if err = fsm.DestroyDataset(baseDataset); err != nil {
+					api.SendBadRequestError(w, r, err.Error())
+					return
+				}
+			}
+		}
+	}
+
+	log.Dbg(fmt.Sprintf("Snapshot %s has been deleted", snapshotID))
+
+	if err := api.WriteJSON(w, http.StatusOK, models.Response{
+		Status:  models.ResponseOK,
+		Message: "Deleted snapshot",
+	}); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+
+	fsm.RefreshSnapshotList()
+
+	if err := s.Cloning.ReloadSnapshots(); err != nil {
+		log.Dbg("Failed to reload snapshots", err.Error())
+	}
+
+	s.webhookCh <- webhooks.BasicEvent{
+		EventType: webhooks.SnapshotDeleteEvent,
+		EntityID:  snapshotID,
+	}
+}
+
+func (s *Server) detectPoolName(snapshotID string) (string, error) {
+	const snapshotParts = 2
+
+	parts := strings.Split(snapshotID, "@")
+	if len(parts) != snapshotParts {
+		return "", fmt.Errorf("invalid snapshot name given: %s. Should contain `dataset@snapname`", snapshotID)
+	}
+
+	poolName := ""
+
+	for _, fsm := range s.pm.GetFSManagerList() {
+		if strings.HasPrefix(parts[0], fsm.Pool().Name) {
+			poolName = fsm.Pool().Name
+			break
+		}
+	}
+
+	return poolName, nil
+}
+
+func (s *Server) createSnapshotClone(w http.ResponseWriter, r *http.Request) {
+	if r.Body == http.NoBody {
+		api.SendBadRequestError(w, r, "request body cannot be empty")
+		return
+	}
+
+	var createRequest types.SnapshotCloneCreateRequest
+	if err := api.ReadJSON(r, &createRequest); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if createRequest.CloneID == "" {
+		api.SendBadRequestError(w, r, "cloneID cannot be empty")
+		return
+	}
+
+	clone, err := s.Cloning.GetClone(createRequest.CloneID)
+	if err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	fsm, err := s.pm.GetFSManager(clone.Snapshot.Pool)
+	if err != nil {
+		api.SendBadRequestError(w, r, fmt.Sprintf("failed to find filesystem manager: %s", err.Error()))
+		return
+	}
+
+	cloneName := clone.ID
+
+	snapshotID, err := fsm.CreateSnapshot(cloneName, time.Now().Format(util.DataStateAtFormat))
+	if err != nil {
+		api.SendBadRequestError(w, r, fmt.Sprintf("failed to create a snapshot: %s", err.Error()))
+		return
+	}
+
+	if err := s.Cloning.ReloadSnapshots(); err != nil {
+		log.Dbg("Failed to reload snapshots", err.Error())
+	}
+
+	snapshot, err := s.Cloning.GetSnapshotByID(snapshotID)
+	if err != nil {
+		api.SendBadRequestError(w, r, fmt.Sprintf("failed to find a new snapshot: %s", err.Error()))
+		return
+	}
+
+	if err := api.WriteJSON(w, http.StatusOK, snapshot); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
+func (s *Server) clones(w http.ResponseWriter, r *http.Request) {
+	cloningState := s.Cloning.GetCloningState()
+
+	if err := api.WriteJSON(w, http.StatusOK, cloningState.Clones); err != nil {
+		api.SendError(w, r, err)
+		return
+	}
+}
+
 func (s *Server) createClone(w http.ResponseWriter, r *http.Request) {
 	if s.engProps.GetEdition() == global.StandardEdition {
 		if err := s.engProps.CheckBilling(); err != nil {
@@ -126,6 +490,67 @@ func (s *Server) createClone(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if cloneRequest.Snapshot != nil && cloneRequest.Snapshot.ID != "" {
+		fsm, err := s.getFSManagerForSnapshot(cloneRequest.Snapshot.ID)
+		if err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+
+		if fsm == nil {
+			api.SendBadRequestError(w, r, "no pool manager found")
+			return
+		}
+
+		branch := branching.ParseBranchNameFromSnapshot(cloneRequest.Snapshot.ID, fsm.Pool().Name)
+		if branch == "" {
+			branch = branching.DefaultBranch
+		}
+
+		// Snapshot ID takes precedence over the branch name.
+		cloneRequest.Branch = branch
+	} else {
+		if cloneRequest.Branch == "" {
+			cloneRequest.Branch = branching.DefaultBranch
+		}
+
+		fsm, err := s.getFSManagerForBranch(cloneRequest.Branch)
+		if err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+
+		if fsm == nil {
+			api.SendBadRequestError(w, r, "no pool manager found")
+			return
+		}
+
+		branches, err := fsm.ListBranches()
+		if err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+
+		snapshotID, ok := branches[cloneRequest.Branch]
+		if !ok {
+			api.SendBadRequestError(w, r, "branch not found")
+			return
+		}
+
+		cloneRequest.Snapshot = &types.SnapshotCloneFieldRequest{ID: snapshotID}
+	}
+
+	if cloneRequest.ID != "" {
+		fsm, err := s.getFSManagerForBranch(cloneRequest.Branch)
+		if err != nil {
+			api.SendBadRequestError(w, r, err.Error())
+			return
+		}
+
+		// Check if there is any clone revision under the dataset.
+		cloneRequest.Revision = findMaxCloneRevision(fsm.Pool().CloneRevisionLocation(cloneRequest.Branch, cloneRequest.ID))
+	}
+
 	newClone, err := s.Cloning.CreateClone(cloneRequest)
 	if err != nil {
 		var reqErr *models.Error
@@ -153,6 +578,39 @@ func (s *Server) createClone(w http.ResponseWriter, r *http.Request) {
 	log.Dbg(fmt.Sprintf("Clone ID=%s is being created", newClone.ID))
 }
 
+func findMaxCloneRevision(path string) int {
+	files, err := os.ReadDir(path)
+	if err != nil {
+		log.Err(err)
+		return 0
+	}
+
+	maxIndex := -1
+
+	for _, file := range files {
+		if !file.IsDir() {
+			continue
+		}
+
+		revisionIndex, ok := strings.CutPrefix(file.Name(), "r")
+		if !ok {
+			continue
+		}
+
+		index, err := strconv.Atoi(revisionIndex)
+		if err != nil {
+			log.Err(err)
+			continue
+		}
+
+		if index > maxIndex {
+			maxIndex = index
+		}
+	}
+
+	return maxIndex + 1
+}
+
 func (s *Server) destroyClone(w http.ResponseWriter, r *http.Request) {
 	cloneID := mux.Vars(r)["id"]
 
@@ -194,6 +652,11 @@ func (s *Server) patchClone(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	s.tm.SendEvent(context.Background(), telemetry.CloneUpdatedEvent, telemetry.CloneUpdated{
+		ID:        util.HashID(cloneID),
+		Protected: patchClone.Protected,
+	})
+
 	if err := api.WriteJSON(w, http.StatusOK, updatedClone); err != nil {
 		api.SendError(w, r, err)
 		return
@@ -285,7 +748,7 @@ func (s *Server) startObservation(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	s.Observer.AddObservingClone(clone.ID, uint(port), observingClone)
+	s.Observer.AddObservingClone(clone.ID, clone.Branch, clone.Revision, uint(port), observingClone)
 
 	// Start session on the Platform.
 	platformRequest := platform.StartObservationRequest{
@@ -343,8 +806,7 @@ func (s *Server) stopObservation(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	clone, err := s.Cloning.GetClone(observationRequest.CloneID)
-	if err != nil {
+	if _, err := s.Cloning.GetClone(observationRequest.CloneID); err != nil {
 		api.SendNotFoundError(w, r)
 		return
 	}
@@ -389,14 +851,14 @@ func (s *Server) stopObservation(w http.ResponseWriter, r *http.Request) {
 
 	sessionID := strconv.FormatUint(session.SessionID, 10)
 
-	logs, err := s.Observer.GetCloneLog(context.TODO(), clone.DB.Port, observingClone)
+	logs, err := s.Observer.GetCloneLog(context.TODO(), observingClone)
 	if err != nil {
-		log.Err("Failed to get observation logs", err)
+		log.Err("failed to get observation logs", err)
 	}
 
 	if len(logs) > 0 {
 		if err := s.Platform.Client.UploadObservationLogs(context.Background(), logs, sessionID); err != nil {
-			log.Err("Failed to upload observation logs", err)
+			log.Err("failed to upload observation logs", err)
 		}
 	}
 
@@ -410,7 +872,7 @@ func (s *Server) stopObservation(w http.ResponseWriter, r *http.Request) {
 		}
 
 		if err := s.Platform.Client.UploadObservationArtifact(context.Background(), data, sessionID, artifactType); err != nil {
-			log.Err("Failed to upload observation artifact", err)
+			log.Err("failed to upload observation artifact", err)
 		}
 	}
 
@@ -493,3 +955,28 @@ func (s *Server) healthCheck(w http.ResponseWriter, _ *http.Request) {
 		return
 	}
 }
+
+func (s *Server) refresh(w http.ResponseWriter, r *http.Request) {
+	if err := s.Retrieval.CanStartRefresh(); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	if err := s.Retrieval.HasAvailablePool(); err != nil {
+		api.SendBadRequestError(w, r, err.Error())
+		return
+	}
+
+	go func() {
+		if err := s.Retrieval.FullRefresh(context.Background()); err != nil {
+			log.Err("failed to initiate full refresh", err)
+		}
+	}()
+
+	if err := api.WriteJSON(w, http.StatusOK, models.Response{
+		Status:  models.ResponseOK,
+		Message: "Full refresh started",
+	}); err != nil {
+		api.SendError(w, r, err)
+	}
+}
diff --git a/engine/internal/srv/server.go b/engine/internal/srv/server.go
index 04525053..af11b633 100644
--- a/engine/internal/srv/server.go
+++ b/engine/internal/srv/server.go
@@ -32,6 +32,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/srv/ws"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
 	"gitlab.com/postgres-ai/database-lab/v3/internal/validator"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/webhooks"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
@@ -59,6 +60,7 @@ type Server struct {
 	startedAt   *models.LocalTime
 	filtering   *log.Filtering
 	reloadFn    func(server *Server) error
+	webhookCh   chan webhooks.EventTyper
 }
 
 // WSService defines a service to manage web-sockets.
@@ -73,7 +75,8 @@ func NewServer(cfg *srvCfg.Config, globalCfg *global.Config, engineProps *global
 	dockerClient *client.Client, cloning *cloning.Base, provisioner *provision.Provisioner,
 	retrievalSvc *retrieval.Retrieval, platform *platform.Service, billingSvc *billing.Billing, observer *observer.Observer,
 	pm *pool.Manager, tm *telemetry.Agent, tokenKeeper *ws.TokenKeeper,
-	filtering *log.Filtering, uiManager *embeddedui.UIManager, reloadConfigFn func(server *Server) error) *Server {
+	filtering *log.Filtering, uiManager *embeddedui.UIManager, reloadConfigFn func(server *Server) error,
+	webhookCh chan webhooks.EventTyper) *Server {
 	server := &Server{
 		Config:      cfg,
 		Global:      globalCfg,
@@ -95,6 +98,7 @@ func NewServer(cfg *srvCfg.Config, globalCfg *global.Config, engineProps *global
 		filtering:  filtering,
 		startedAt:  &models.LocalTime{Time: time.Now().Truncate(time.Second)},
 		reloadFn:   reloadConfigFn,
+		webhookCh:  webhookCh,
 	}
 
 	return server
@@ -193,6 +197,11 @@ func (s *Server) InitHandlers() {
 
 	r.HandleFunc("/status", authMW.Authorized(s.getInstanceStatus)).Methods(http.MethodGet)
 	r.HandleFunc("/snapshots", authMW.Authorized(s.getSnapshots)).Methods(http.MethodGet)
+	r.HandleFunc("/snapshot/{id:.*}", authMW.Authorized(s.getSnapshot)).Methods(http.MethodGet)
+	r.HandleFunc("/snapshot", authMW.Authorized(s.createSnapshot)).Methods(http.MethodPost)
+	r.HandleFunc("/snapshot/{id:.*}", authMW.Authorized(s.deleteSnapshot)).Methods(http.MethodDelete)
+	r.HandleFunc("/snapshot/clone", authMW.Authorized(s.createSnapshotClone)).Methods(http.MethodPost)
+	r.HandleFunc("/clones", authMW.Authorized(s.clones)).Methods(http.MethodGet)
 	r.HandleFunc("/clone", authMW.Authorized(s.createClone)).Methods(http.MethodPost)
 	r.HandleFunc("/clone/{id}", authMW.Authorized(s.destroyClone)).Methods(http.MethodDelete)
 	r.HandleFunc("/clone/{id}", authMW.Authorized(s.patchClone)).Methods(http.MethodPatch)
@@ -204,6 +213,13 @@ func (s *Server) InitHandlers() {
 	r.HandleFunc("/observation/download", authMW.Authorized(s.downloadArtifact)).Methods(http.MethodGet)
 	r.HandleFunc("/instance/retrieval", authMW.Authorized(s.retrievalState)).Methods(http.MethodGet)
 
+	r.HandleFunc("/branches", authMW.Authorized(s.listBranches)).Methods(http.MethodGet)
+	r.HandleFunc("/branch/snapshot/{id:.*}", authMW.Authorized(s.getCommit)).Methods(http.MethodGet)
+	r.HandleFunc("/branch", authMW.Authorized(s.createBranch)).Methods(http.MethodPost)
+	r.HandleFunc("/branch/snapshot", authMW.Authorized(s.snapshot)).Methods(http.MethodPost)
+	r.HandleFunc("/branch/{branchName}/log", authMW.Authorized(s.log)).Methods(http.MethodGet)
+	r.HandleFunc("/branch/{branchName}", authMW.Authorized(s.deleteBranch)).Methods(http.MethodDelete)
+
 	// Sub-route /admin
 	adminR := r.PathPrefix("/admin").Subrouter()
 	adminR.Use(authMW.AdminMW)
@@ -218,16 +234,19 @@ func (s *Server) InitHandlers() {
 	r.HandleFunc("/instance/logs", authMW.WebSocketsMW(s.wsService.tokenKeeper, s.instanceLogs))
 
 	// Health check.
-	r.HandleFunc("/healthz", s.healthCheck).Methods(http.MethodGet)
+	r.HandleFunc("/healthz", s.healthCheck).Methods(http.MethodGet, http.MethodPost)
+
+	// Full refresh
+	r.HandleFunc("/full-refresh", authMW.Authorized(s.refresh)).Methods(http.MethodPost)
 
 	// Show Swagger UI on index page.
 	if err := attachAPI(r); err != nil {
-		log.Err("Cannot load API description.")
+		log.Err("cannot load API description")
 	}
 
 	// Show Swagger UI on index page.
 	if err := attachSwaggerUI(r); err != nil {
-		log.Err("Cannot start Swagger UI.")
+		log.Err("cannot start Swagger UI")
 	}
 
 	// Show not found error for all other possible routes.
@@ -262,7 +281,3 @@ func (s *Server) Uptime() float64 {
 func reportLaunching(cfg *srvCfg.Config) {
 	log.Msg(fmt.Sprintf("API server started listening on %s:%d.", cfg.Host, cfg.Port))
 }
-
-func (s *Server) initLogRegExp() {
-	s.filtering.ReloadLogRegExp([]string{s.Config.VerificationToken, s.Platform.AccessToken(), s.Platform.OrgKey()})
-}
diff --git a/engine/internal/srv/ws.go b/engine/internal/srv/ws.go
index 64f58211..60da6a08 100644
--- a/engine/internal/srv/ws.go
+++ b/engine/internal/srv/ws.go
@@ -75,7 +75,7 @@ func (s *Server) instanceLogs(w http.ResponseWriter, r *http.Request) {
 		Follow:     true,
 	})
 	if err != nil {
-		log.Err("Failed to get container logs", err)
+		log.Err("failed to get container logs", err)
 
 		if writingErr := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); writingErr != nil {
 			log.Dbg("Failed to report about error", err)
diff --git a/engine/internal/srv/ws_test.go b/engine/internal/srv/ws_test.go
index a6fd1132..77e078a8 100644
--- a/engine/internal/srv/ws_test.go
+++ b/engine/internal/srv/ws_test.go
@@ -21,7 +21,8 @@ func TestLogLineFiltering(t *testing.T) {
 		Platform:  pl,
 		filtering: log.GetFilter(),
 	}
-	s.initLogRegExp()
+
+	s.filtering.ReloadLogRegExp([]string{"secretToken"})
 
 	testCases := []struct {
 		input  []byte
@@ -75,6 +76,10 @@ func TestLogLineFiltering(t *testing.T) {
 			input:  []byte(`AWS_ACCESS_KEY_ID:password`),
 			output: []byte(`AWS_********`),
 		},
+		{
+			input:  []byte(`secret: "secret_token"`),
+			output: []byte(`********`),
+		},
 	}
 
 	for _, tc := range testCases {
diff --git a/engine/internal/telemetry/events.go b/engine/internal/telemetry/events.go
index 76703232..82b6f54c 100644
--- a/engine/internal/telemetry/events.go
+++ b/engine/internal/telemetry/events.go
@@ -49,11 +49,30 @@ type CloneCreated struct {
 	DSADiff     *float64 `json:"dsa_diff,omitempty"`
 }
 
+// CloneUpdated describes the clone updates.
+type CloneUpdated struct {
+	ID        string `json:"id"`
+	Protected bool   `json:"protected"`
+}
+
 // CloneDestroyed describes a clone destruction event.
 type CloneDestroyed struct {
 	ID string `json:"id"`
 }
 
+// BranchCreated describes a branch creation event.
+type BranchCreated struct {
+	Name string `json:"name"`
+}
+
+// BranchDestroyed describes a branch destruction event.
+type BranchDestroyed struct {
+	Name string `json:"name"`
+}
+
+// ConfigUpdated describes the config updates.
+type ConfigUpdated struct{}
+
 // Alert describes alert events.
 type Alert struct {
 	Level   models.AlertType `json:"level"`
diff --git a/engine/internal/telemetry/telemetry.go b/engine/internal/telemetry/telemetry.go
index 37ceea72..5feeb3fa 100644
--- a/engine/internal/telemetry/telemetry.go
+++ b/engine/internal/telemetry/telemetry.go
@@ -29,9 +29,20 @@ const (
 	// CloneDestroyedEvent describes a clone destruction event.
 	CloneDestroyedEvent = "clone_destroyed"
 
+	// CloneUpdatedEvent describes a clone update event.
+	CloneUpdatedEvent = "clone_updated"
+
 	// SnapshotCreatedEvent describes a snapshot creation event.
 	SnapshotCreatedEvent = "snapshot_created"
 
+	// BranchCreatedEvent describes a branch creation event.
+	BranchCreatedEvent = "branch_created"
+
+	// BranchDestroyedEvent describes a branch destruction event.
+	BranchDestroyedEvent = "branch_destroyed"
+
+	ConfigUpdatedEvent = "config_updated"
+
 	// AlertEvent describes alert events.
 	AlertEvent = "alert"
 )
@@ -63,6 +74,6 @@ func (a *Agent) SendEvent(ctx context.Context, eventType string, payload interfa
 	})
 
 	if err != nil {
-		log.Err("Failed to send telemetry event", err)
+		log.Err("failed to send telemetry event", err)
 	}
 }
diff --git a/engine/internal/validator/validator.go b/engine/internal/validator/validator.go
index 87656c2b..bf31e2a9 100644
--- a/engine/internal/validator/validator.go
+++ b/engine/internal/validator/validator.go
@@ -6,10 +6,10 @@
 package validator
 
 import (
+	"errors"
 	"fmt"
-	"strings"
+	"regexp"
 
-	"github.com/pkg/errors"
 	passwordvalidator "github.com/wagslane/go-password-validator"
 
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
@@ -17,6 +17,8 @@ import (
 
 const minEntropyBits = 60
 
+var cloneIDRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]*$`)
+
 // Service provides a validation service.
 type Service struct {
 }
@@ -35,8 +37,8 @@ func (v Service) ValidateCloneRequest(cloneRequest *types.CloneCreateRequest) er
 		return errors.New("missing DB password")
 	}
 
-	if cloneRequest.ID != "" && strings.Contains(cloneRequest.ID, "/") {
-		return errors.New("Clone ID cannot contain slash ('/'). Please choose another ID")
+	if cloneRequest.ID != "" && !cloneIDRegexp.MatchString(cloneRequest.ID) {
+		return errors.New("clone ID must start with a letter or number and can only contain letters, numbers, underscores, periods, and hyphens")
 	}
 
 	if err := passwordvalidator.Validate(cloneRequest.DB.Password, minEntropyBits); err != nil {
diff --git a/engine/internal/validator/validator_test.go b/engine/internal/validator/validator_test.go
index a510319e..df68fb12 100644
--- a/engine/internal/validator/validator_test.go
+++ b/engine/internal/validator/validator_test.go
@@ -19,7 +19,8 @@ func TestValidationCloneRequest(t *testing.T) {
 			DB: &types.DatabaseRequest{
 				Username: "username",
 				Password: "secret_password",
-			}})
+			},
+		})
 
 	assert.Nil(t, err)
 }
@@ -31,7 +32,8 @@ func TestWeakPassword(t *testing.T) {
 			DB: &types.DatabaseRequest{
 				Username: "username",
 				Password: "password",
-			}})
+			},
+		})
 
 	assert.ErrorContains(t, err, "insecure password")
 }
@@ -60,7 +62,7 @@ func TestValidationCloneRequestErrors(t *testing.T) {
 				DB: &types.DatabaseRequest{Username: "user", Password: "password"},
 				ID: "test/ID",
 			},
-			error: "Clone ID cannot contain slash ('/'). Please choose another ID",
+			error: "clone ID must start with a letter or number and can only contain letters, numbers, underscores, periods, and hyphens",
 		},
 	}
 
diff --git a/engine/internal/webhooks/events.go b/engine/internal/webhooks/events.go
new file mode 100644
index 00000000..bf5e8f1e
--- /dev/null
+++ b/engine/internal/webhooks/events.go
@@ -0,0 +1,48 @@
+package webhooks
+
+const (
+	// CloneCreatedEvent defines the clone create event type.
+	CloneCreatedEvent = "clone_create"
+	// CloneResetEvent defines the clone reset event type.
+	CloneResetEvent = "clone_reset"
+	// CloneDeleteEvent defines the clone delete event type.
+	CloneDeleteEvent = "clone_delete"
+
+	// SnapshotCreateEvent defines the snapshot create event type.
+	SnapshotCreateEvent = "snapshot_create"
+
+	// SnapshotDeleteEvent defines the snapshot delete event type.
+	SnapshotDeleteEvent = "snapshot_delete"
+
+	// BranchCreateEvent defines the branch create event type.
+	BranchCreateEvent = "branch_create"
+
+	// BranchDeleteEvent defines the branch delete event type.
+	BranchDeleteEvent = "branch_delete"
+)
+
+// EventTyper unifies webhook events.
+type EventTyper interface {
+	GetType() string
+}
+
+// BasicEvent defines payload of basic webhook event.
+type BasicEvent struct {
+	EventType string `json:"event_type"`
+	EntityID  string `json:"entity_id"`
+}
+
+// GetType returns type of the event.
+func (e BasicEvent) GetType() string {
+	return e.EventType
+}
+
+// CloneEvent defines clone webhook events payload.
+type CloneEvent struct {
+	BasicEvent
+	Host          string `json:"host,omitempty"`
+	Port          uint   `json:"port,omitempty"`
+	Username      string `json:"username,omitempty"`
+	DBName        string `json:"dbname,omitempty"`
+	ContainerName string `json:"container_name,omitempty"`
+}
diff --git a/engine/internal/webhooks/webhooks.go b/engine/internal/webhooks/webhooks.go
new file mode 100644
index 00000000..b2c6b4c2
--- /dev/null
+++ b/engine/internal/webhooks/webhooks.go
@@ -0,0 +1,149 @@
+// Package webhooks configures the webhooks that will be called by the DBLab Engine when an event occurs.
+package webhooks
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
+)
+
+const (
+	// DLEWebhookTokenHeader defines the HTTP header name to send secret with the webhook request.
+	DLEWebhookTokenHeader = "DBLab-Webhook-Token"
+)
+
+// Config defines webhooks configuration.
+type Config struct {
+	Hooks []Hook `yaml:"hooks"`
+}
+
+// Hook defines structure of the webhook configuration.
+type Hook struct {
+	URL     string   `yaml:"url"`
+	Secret  string   `yaml:"secret"`
+	Trigger []string `yaml:"trigger"`
+}
+
+// Service listens events and performs webhooks requests.
+type Service struct {
+	client        *http.Client
+	hooksRegistry map[string][]Hook
+	eventCh       <-chan EventTyper
+}
+
+// NewService creates a new Webhook Service.
+func NewService(cfg *Config, eventCh <-chan EventTyper) *Service {
+	whs := &Service{
+		client: &http.Client{
+			Transport: &http.Transport{},
+		},
+		hooksRegistry: make(map[string][]Hook),
+		eventCh:       eventCh,
+	}
+
+	whs.Reload(cfg)
+
+	return whs
+}
+
+// Reload reloads Webhook Service configuration.
+func (s *Service) Reload(cfg *Config) {
+	s.hooksRegistry = make(map[string][]Hook)
+
+	for _, hook := range cfg.Hooks {
+		if err := validateURL(hook.URL); err != nil {
+			log.Msg("Skip webhook processing:", err)
+			continue
+		}
+
+		for _, event := range hook.Trigger {
+			s.hooksRegistry[event] = append(s.hooksRegistry[event], hook)
+		}
+	}
+
+	log.Dbg("Registered webhooks", s.hooksRegistry)
+}
+
+func validateURL(hookURL string) error {
+	parsedURL, err := url.ParseRequestURI(hookURL)
+	if err != nil {
+		return fmt.Errorf("URL %q is invalid: %w", hookURL, err)
+	}
+
+	if parsedURL.Scheme == "" {
+		return fmt.Errorf("no scheme found in %q", hookURL)
+	}
+
+	if parsedURL.Host == "" {
+		return fmt.Errorf("no host found in %q", hookURL)
+	}
+
+	return nil
+}
+
+// Run starts webhook listener.
+func (s *Service) Run(ctx context.Context) {
+	for whEvent := range s.eventCh {
+		hooks, ok := s.hooksRegistry[whEvent.GetType()]
+		if !ok {
+			log.Dbg("Skipped unknown hook: ", whEvent.GetType())
+
+			continue
+		}
+
+		log.Dbg("Trigger event:", whEvent)
+
+		for _, hook := range hooks {
+			go s.triggerWebhook(ctx, hook, whEvent)
+		}
+	}
+}
+
+func (s *Service) triggerWebhook(ctx context.Context, hook Hook, whEvent EventTyper) {
+	log.Msg("Webhook request: ", hook.URL)
+
+	resp, err := s.makeRequest(ctx, hook, whEvent)
+
+	if err != nil {
+		log.Err("webhook error:", err)
+		return
+	}
+
+	log.Dbg("Webhook status code: ", resp.StatusCode)
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		log.Err("webhook error:", err)
+		return
+	}
+
+	log.Dbg("Webhook response: ", string(body))
+}
+
+func (s *Service) makeRequest(ctx context.Context, hook Hook, whEvent EventTyper) (*http.Response, error) {
+	payload, err := json.Marshal(whEvent)
+	if err != nil {
+		return nil, err
+	}
+
+	log.Dbg("Webhook payload: ", string(payload))
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, hook.URL, bytes.NewReader(payload))
+	if err != nil {
+		return nil, err
+	}
+
+	if hook.Secret != "" {
+		req.Header.Add(DLEWebhookTokenHeader, hook.Secret)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+
+	return s.client.Do(req)
+}
diff --git a/engine/pkg/client/dblabapi/branch.go b/engine/pkg/client/dblabapi/branch.go
new file mode 100644
index 00000000..b0505b6d
--- /dev/null
+++ b/engine/pkg/client/dblabapi/branch.go
@@ -0,0 +1,162 @@
+/*
+2019 © Postgres.ai
+*/
+
+package dblabapi
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"sort"
+
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
+)
+
+// ListBranches returns branches list.
+func (c *Client) ListBranches(ctx context.Context) ([]string, error) {
+	u := c.URL("/branches")
+
+	request, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make a request: %w", err)
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	branches := make([]models.BranchView, 0)
+
+	if err := json.NewDecoder(response.Body).Decode(&branches); err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	listBranches := make([]string, 0, len(branches))
+
+	for _, branchView := range branches {
+		listBranches = append(listBranches, branchView.Name)
+	}
+
+	sort.Strings(listBranches)
+
+	return listBranches, nil
+}
+
+// CreateBranch creates a new DLE data branch.
+//
+//nolint:dupl
+func (c *Client) CreateBranch(ctx context.Context, branchRequest types.BranchCreateRequest) (*models.Branch, error) {
+	u := c.URL("/branch")
+
+	body := bytes.NewBuffer(nil)
+	if err := json.NewEncoder(body).Encode(branchRequest); err != nil {
+		return nil, fmt.Errorf("failed to encode BranchCreateRequest: %w", err)
+	}
+
+	request, err := http.NewRequest(http.MethodPost, u.String(), body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make a request: %w", err)
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	var branch *models.Branch
+
+	if err := json.NewDecoder(response.Body).Decode(&branch); err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	return branch, nil
+}
+
+// CreateSnapshotForBranch creates a new snapshot for branch.
+//
+//nolint:dupl
+func (c *Client) CreateSnapshotForBranch(
+	ctx context.Context,
+	snapshotRequest types.SnapshotCloneCreateRequest) (*types.SnapshotResponse, error) {
+	u := c.URL("/branch/snapshot")
+
+	body := bytes.NewBuffer(nil)
+	if err := json.NewEncoder(body).Encode(snapshotRequest); err != nil {
+		return nil, fmt.Errorf("failed to encode SnapshotCreateRequest: %w", err)
+	}
+
+	request, err := http.NewRequest(http.MethodPost, u.String(), body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make a request: %w", err)
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	var snapshot *types.SnapshotResponse
+
+	if err := json.NewDecoder(response.Body).Decode(&snapshot); err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	return snapshot, nil
+}
+
+// BranchLog provides snapshot list for branch.
+func (c *Client) BranchLog(ctx context.Context, logRequest types.LogRequest) ([]models.SnapshotDetails, error) {
+	u := c.URL(fmt.Sprintf("/branch/%s/log", logRequest.BranchName))
+
+	request, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make a request: %w", err)
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	var snapshots []models.SnapshotDetails
+
+	if err := json.NewDecoder(response.Body).Decode(&snapshots); err != nil {
+		return nil, fmt.Errorf("failed to get response: %w", err)
+	}
+
+	return snapshots, nil
+}
+
+// DeleteBranch deletes data branch.
+//
+//nolint:dupl
+func (c *Client) DeleteBranch(ctx context.Context, r types.BranchDeleteRequest) error {
+	u := c.URL(fmt.Sprintf("/branch/%s", r.BranchName))
+
+	request, err := http.NewRequest(http.MethodDelete, u.String(), nil)
+	if err != nil {
+		return fmt.Errorf("failed to make a request: %w", err)
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return err
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	return nil
+}
diff --git a/engine/pkg/client/dblabapi/client.go b/engine/pkg/client/dblabapi/client.go
index 342ad931..9dc2b5f2 100644
--- a/engine/pkg/client/dblabapi/client.go
+++ b/engine/pkg/client/dblabapi/client.go
@@ -18,8 +18,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/pkg/errors"
-
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 )
@@ -136,7 +134,7 @@ func (c *Client) Do(ctx context.Context, request *http.Request) (response *http.
 
 		errModel := models.Error{}
 		if err = json.Unmarshal(b, &errModel); err != nil {
-			return response, errors.Wrapf(err, "failed to parse an error message: %s", (string(b)))
+			return response, fmt.Errorf("failed to parse an error message: %s, %w", string(b), err)
 		}
 
 		return response, errModel
diff --git a/engine/pkg/client/dblabapi/snapshot.go b/engine/pkg/client/dblabapi/snapshot.go
index 8e2a5cfd..0b9e607f 100644
--- a/engine/pkg/client/dblabapi/snapshot.go
+++ b/engine/pkg/client/dblabapi/snapshot.go
@@ -5,13 +5,17 @@
 package dblabapi
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
+	"fmt"
 	"io"
 	"net/http"
+	"net/url"
 
 	"github.com/pkg/errors"
 
+	"gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
 )
 
@@ -49,3 +53,67 @@ func (c *Client) ListSnapshotsRaw(ctx context.Context) (io.ReadCloser, error) {
 
 	return response.Body, nil
 }
+
+// CreateSnapshot creates a new snapshot.
+func (c *Client) CreateSnapshot(ctx context.Context, snapshotRequest types.SnapshotCreateRequest) (*models.Snapshot, error) {
+	u := c.URL("/snapshot")
+
+	return c.createRequest(ctx, snapshotRequest, u)
+}
+
+// CreateSnapshotFromClone creates a new snapshot from clone.
+func (c *Client) CreateSnapshotFromClone(
+	ctx context.Context,
+	snapshotRequest types.SnapshotCloneCreateRequest) (*models.Snapshot, error) {
+	u := c.URL("/snapshot/clone")
+
+	return c.createRequest(ctx, snapshotRequest, u)
+}
+
+func (c *Client) createRequest(ctx context.Context, snapshotRequest any, u *url.URL) (*models.Snapshot, error) {
+	body := bytes.NewBuffer(nil)
+	if err := json.NewEncoder(body).Encode(snapshotRequest); err != nil {
+		return nil, errors.Wrap(err, "failed to encode SnapshotCreateRequest")
+	}
+
+	request, err := http.NewRequest(http.MethodPost, u.String(), body)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to make a request")
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get response")
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	var snapshot *models.Snapshot
+
+	if err := json.NewDecoder(response.Body).Decode(&snapshot); err != nil {
+		return nil, errors.Wrap(err, "failed to get response")
+	}
+
+	return snapshot, nil
+}
+
+// DeleteSnapshot deletes snapshot.
+//
+//nolint:dupl
+func (c *Client) DeleteSnapshot(ctx context.Context, snapshotRequest types.SnapshotDestroyRequest) error {
+	u := c.URL(fmt.Sprintf("/snapshot/%s", snapshotRequest.SnapshotID))
+
+	request, err := http.NewRequest(http.MethodDelete, u.String(), nil)
+	if err != nil {
+		return fmt.Errorf("failed to make a request: %w", err)
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return fmt.Errorf("failed to get response: %w", err)
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	return nil
+}
diff --git a/engine/pkg/client/dblabapi/status.go b/engine/pkg/client/dblabapi/status.go
index 74c31a15..2493e2b1 100644
--- a/engine/pkg/client/dblabapi/status.go
+++ b/engine/pkg/client/dblabapi/status.go
@@ -72,3 +72,27 @@ func (c *Client) Health(ctx context.Context) (*models.Engine, error) {
 
 	return &engine, nil
 }
+
+// FullRefresh triggers a full refresh of the dataset.
+func (c *Client) FullRefresh(ctx context.Context) (*models.Response, error) {
+	u := c.URL("/full-refresh")
+
+	request, err := http.NewRequest(http.MethodPost, u.String(), nil)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to make a request")
+	}
+
+	response, err := c.Do(ctx, request)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get response")
+	}
+
+	defer func() { _ = response.Body.Close() }()
+
+	var result models.Response
+	if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
+		return nil, errors.Wrap(err, "failed to get response")
+	}
+
+	return &result, nil
+}
diff --git a/engine/pkg/client/dblabapi/status_test.go b/engine/pkg/client/dblabapi/status_test.go
index c9cd9cca..92d91bcd 100644
--- a/engine/pkg/client/dblabapi/status_test.go
+++ b/engine/pkg/client/dblabapi/status_test.go
@@ -111,3 +111,58 @@ func TestClientStatusWithFailedRequest(t *testing.T) {
 	require.EqualError(t, err, "failed to get response: EOF")
 	require.Nil(t, status)
 }
+
+func TestClientFullRefresh(t *testing.T) {
+	expectedResponse := &models.Response{
+		Status:  "OK",
+		Message: "Full refresh started",
+	}
+
+	mockClient := NewTestClient(func(req *http.Request) *http.Response {
+		assert.Equal(t, req.URL.String(), "https://example.com/full-refresh")
+		assert.Equal(t, req.Method, http.MethodPost)
+
+		body, err := json.Marshal(expectedResponse)
+		require.NoError(t, err)
+
+		return &http.Response{
+			StatusCode: http.StatusOK,
+			Body:       io.NopCloser(bytes.NewBuffer(body)),
+			Header:     make(http.Header),
+		}
+	})
+
+	c, err := NewClient(Options{
+		Host:              "https://example.com/",
+		VerificationToken: "testVerify",
+	})
+	require.NoError(t, err)
+
+	c.client = mockClient
+
+	resp, err := c.FullRefresh(context.Background())
+	require.NoError(t, err)
+	assert.EqualValues(t, expectedResponse, resp)
+}
+
+func TestClientFullRefreshWithFailedDecode(t *testing.T) {
+	mockClient := NewTestClient(func(req *http.Request) *http.Response {
+		return &http.Response{
+			StatusCode: http.StatusOK,
+			Body:       io.NopCloser(bytes.NewBuffer([]byte{})),
+			Header:     make(http.Header),
+		}
+	})
+
+	c, err := NewClient(Options{
+		Host:              "https://example.com/",
+		VerificationToken: "testVerify",
+	})
+	require.NoError(t, err)
+
+	c.client = mockClient
+
+	resp, err := c.FullRefresh(context.Background())
+	require.EqualError(t, err, "failed to get response: EOF")
+	require.Nil(t, resp)
+}
diff --git a/engine/pkg/client/dblabapi/types/clone.go b/engine/pkg/client/dblabapi/types/clone.go
index c9b9e7b4..442d5e22 100644
--- a/engine/pkg/client/dblabapi/types/clone.go
+++ b/engine/pkg/client/dblabapi/types/clone.go
@@ -12,6 +12,8 @@ type CloneCreateRequest struct {
 	DB        *DatabaseRequest           `json:"db"`
 	Snapshot  *SnapshotCloneFieldRequest `json:"snapshot"`
 	ExtraConf map[string]string          `json:"extra_conf"`
+	Branch    string                     `json:"branch"`
+	Revision  int                        `json:"-"`
 }
 
 // CloneUpdateRequest represents params of an update request.
@@ -37,3 +39,47 @@ type ResetCloneRequest struct {
 	SnapshotID string `json:"snapshotID"`
 	Latest     bool   `json:"latest"`
 }
+
+// SnapshotCreateRequest describes params for creating snapshot request.
+type SnapshotCreateRequest struct {
+	PoolName string `json:"poolName"`
+}
+
+// SnapshotDestroyRequest describes params for destroying snapshot request.
+type SnapshotDestroyRequest struct {
+	SnapshotID string `json:"snapshotID"`
+	Force      bool   `json:"force"`
+}
+
+// SnapshotCloneCreateRequest describes params for creating snapshot request from clone.
+type SnapshotCloneCreateRequest struct {
+	CloneID string `json:"cloneID"`
+	Message string `json:"message"`
+}
+
+// BranchCreateRequest describes params for creating branch request.
+type BranchCreateRequest struct {
+	BranchName string `json:"branchName"`
+	BaseBranch string `json:"baseBranch"`
+	SnapshotID string `json:"snapshotID"`
+}
+
+// SnapshotResponse describes commit response.
+type SnapshotResponse struct {
+	SnapshotID string `json:"snapshotID"`
+}
+
+// ResetRequest describes params for reset request.
+type ResetRequest struct {
+	SnapshotID string `json:"snapshotID"`
+}
+
+// LogRequest describes params for log request.
+type LogRequest struct {
+	BranchName string `json:"branchName"`
+}
+
+// BranchDeleteRequest describes params for deleting branch request.
+type BranchDeleteRequest struct {
+	BranchName string `json:"branchName"`
+}
diff --git a/engine/pkg/config/config.go b/engine/pkg/config/config.go
index 747873f3..92be33fc 100644
--- a/engine/pkg/config/config.go
+++ b/engine/pkg/config/config.go
@@ -15,6 +15,7 @@ import (
 	"gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
 	retConfig "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/config"
 	srvCfg "gitlab.com/postgres-ai/database-lab/v3/internal/srv/config"
+	"gitlab.com/postgres-ai/database-lab/v3/internal/webhooks"
 	"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
 )
 
@@ -35,4 +36,5 @@ type Config struct {
 	PoolManager pool.Config       `yaml:"poolManager"`
 	EmbeddedUI  embeddedui.Config `yaml:"embeddedUI"`
 	Diagnostic  diagnostic.Config `yaml:"diagnostic"`
+	Webhooks    webhooks.Config   `yaml:"webhooks"`
 }
diff --git a/engine/pkg/log/filtering.go b/engine/pkg/log/filtering.go
index c5fef4eb..c294aefb 100644
--- a/engine/pkg/log/filtering.go
+++ b/engine/pkg/log/filtering.go
@@ -39,6 +39,7 @@ func (f *Filtering) ReloadLogRegExp(secretStings []string) {
 		"accessToken:\\s?(\\S+)",
 		"orgKey:\\s?(\\S+)",
 		"ACCESS_KEY(_ID)?:\\s?(\\S+)",
+		"secret:\\s?(\\S+)",
 	}
 
 	for _, secret := range secretStings {
diff --git a/engine/pkg/log/log.go b/engine/pkg/log/log.go
index c175003f..dd77cba9 100644
--- a/engine/pkg/log/log.go
+++ b/engine/pkg/log/log.go
@@ -70,7 +70,7 @@ func prepareMessage(v ...interface{}) string {
 	builder := strings.Builder{}
 
 	for _, value := range v {
-		builder.WriteString(" " + filter.re.ReplaceAllString(toString(value), replacingMask))
+		builder.WriteString(" " + toString(value))
 	}
 
 	return builder.String()
diff --git a/engine/pkg/models/branch.go b/engine/pkg/models/branch.go
new file mode 100644
index 00000000..e29f3cc7
--- /dev/null
+++ b/engine/pkg/models/branch.go
@@ -0,0 +1,49 @@
+package models
+
+// Branch defines a branch entity.
+type Branch struct {
+	Name string `json:"name"`
+}
+
+// Repo describes data repository with details about snapshots and branches.
+type Repo struct {
+	Snapshots map[string]SnapshotDetails `json:"snapshots"`
+	Branches  map[string]string          `json:"branches"`
+}
+
+// NewRepo creates a new Repo.
+func NewRepo() *Repo {
+	return &Repo{
+		Snapshots: make(map[string]SnapshotDetails),
+		Branches:  make(map[string]string),
+	}
+}
+
+// SnapshotDetails describes snapshot.
+type SnapshotDetails struct {
+	ID          string   `json:"id"`
+	Parent      string   `json:"parent"`
+	Child       []string `json:"child"`
+	Branch      []string `json:"branch"`
+	Root        []string `json:"root"`
+	DataStateAt string   `json:"dataStateAt"`
+	Message     string   `json:"message"`
+	Dataset     string   `json:"dataset"`
+	Clones      []string `json:"clones"`
+}
+
+// BranchView describes branch view.
+type BranchView struct {
+	Name         string `json:"name"`
+	Parent       string `json:"parent"`
+	DataStateAt  string `json:"dataStateAt"`
+	SnapshotID   string `json:"snapshotID"`
+	Dataset      string `json:"dataset"`
+	NumSnapshots int    `json:"numSnapshots"`
+}
+
+// BranchEntity defines a branch-snapshot pair.
+type BranchEntity struct {
+	Name       string
+	SnapshotID string
+}
diff --git a/engine/pkg/models/clone.go b/engine/pkg/models/clone.go
index 6b4520ff..b7300175 100644
--- a/engine/pkg/models/clone.go
+++ b/engine/pkg/models/clone.go
@@ -6,14 +6,17 @@ package models
 
 // Clone defines a clone model.
 type Clone struct {
-	ID        string        `json:"id"`
-	Snapshot  *Snapshot     `json:"snapshot"`
-	Protected bool          `json:"protected"`
-	DeleteAt  *LocalTime    `json:"deleteAt"`
-	CreatedAt *LocalTime    `json:"createdAt"`
-	Status    Status        `json:"status"`
-	DB        Database      `json:"db"`
-	Metadata  CloneMetadata `json:"metadata"`
+	ID           string        `json:"id"`
+	Snapshot     *Snapshot     `json:"snapshot"`
+	Branch       string        `json:"branch"`
+	Revision     int           `json:"revision"`
+	HasDependent bool          `json:"hasDependent"`
+	Protected    bool          `json:"protected"`
+	DeleteAt     *LocalTime    `json:"deleteAt"`
+	CreatedAt    *LocalTime    `json:"createdAt"`
+	Status       Status        `json:"status"`
+	DB           Database      `json:"db"`
+	Metadata     CloneMetadata `json:"metadata"`
 }
 
 // CloneMetadata contains fields describing a clone model.
diff --git a/engine/pkg/models/snapshot.go b/engine/pkg/models/snapshot.go
index fe1ce8a4..5299e4ad 100644
--- a/engine/pkg/models/snapshot.go
+++ b/engine/pkg/models/snapshot.go
@@ -13,6 +13,9 @@ type Snapshot struct {
 	LogicalSize  uint64     `json:"logicalSize"`
 	Pool         string     `json:"pool"`
 	NumClones    int        `json:"numClones"`
+	Clones       []string   `json:"clones"`
+	Branch       string     `json:"branch"`
+	Message      string     `json:"message"`
 }
 
 // SnapshotView represents a view of snapshot.
diff --git a/engine/pkg/models/status.go b/engine/pkg/models/status.go
index 784d7667..4e5d890a 100644
--- a/engine/pkg/models/status.go
+++ b/engine/pkg/models/status.go
@@ -10,6 +10,12 @@ type Status struct {
 	Message string     `json:"message"`
 }
 
+// Response defines the response structure.
+type Response struct {
+	Status  string `json:"status"`
+	Message string `json:"message"`
+}
+
 // StatusCode defines the status code of clones and instance.
 type StatusCode string
 
@@ -37,4 +43,6 @@ const (
 	SyncStatusDown         StatusCode = "Down"
 	SyncStatusNotAvailable StatusCode = "Not available"
 	SyncStatusError        StatusCode = "Error"
+
+	ResponseOK = "OK"
 )
diff --git a/engine/pkg/util/branching/branching.go b/engine/pkg/util/branching/branching.go
new file mode 100644
index 00000000..75053856
--- /dev/null
+++ b/engine/pkg/util/branching/branching.go
@@ -0,0 +1,110 @@
+/*
+2023 © Postgres.ai
+*/
+
+// Package branching contains branching tools and types.
+package branching
+
+import (
+	"fmt"
+	"path"
+	"strings"
+)
+
+const (
+	// DefaultBranch defines the name of the default branch.
+	DefaultBranch = "main"
+
+	// DefaultRevison defines default clone revision.
+	DefaultRevision = 0
+
+	// BranchDir defines branch directory in the pool.
+	BranchDir = "branch"
+)
+
+// BranchName returns a full branch name in the data pool.
+func BranchName(poolName, branchName string) string {
+	return path.Join(poolName, BranchDir, branchName)
+}
+
+// CloneDataset returns a full clone dataset in the data pool.
+func CloneDataset(poolName, branchName, cloneName string) string {
+	return path.Join(BranchName(poolName, branchName), cloneName)
+}
+
+// CloneName returns a full clone name in the data pool.
+func CloneName(poolName, branchName, cloneName string, revision int) string {
+	return path.Join(BranchName(poolName, branchName), cloneName, RevisionSegment(revision))
+}
+
+// RevisionSegment returns a clone path suffix depends on its revision.
+func RevisionSegment(revision int) string {
+	return fmt.Sprintf("r%d", revision)
+}
+
+// ParseCloneName parses clone name from the clone dataset.
+func ParseCloneName(cloneDataset, poolName string) (string, bool) {
+	const cloneSegmentNumber = 2
+
+	splits := parseCloneDataset(cloneDataset, poolName)
+
+	if len(splits) < cloneSegmentNumber {
+		return "", false
+	}
+
+	cloneID := splits[1]
+
+	return cloneID, true
+}
+
+// ParseBranchName parses branch name from the clone dataset.
+func ParseBranchName(cloneDataset, poolName string) (string, bool) {
+	splits := parseCloneDataset(cloneDataset, poolName)
+
+	if len(splits) < 1 {
+		return "", false
+	}
+
+	branch := splits[0]
+
+	return branch, true
+}
+
+func parseCloneDataset(cloneDataset, poolName string) []string {
+	const splitParts = 3
+
+	// bcrStr contains branch, clone and revision.
+	bcrStr := strings.TrimPrefix(cloneDataset, poolName+"/"+BranchDir+"/")
+
+	// Parse branchName/cloneID/revision.
+	splits := strings.SplitN(bcrStr, "/", splitParts)
+	if len(splits) != splitParts {
+		return nil
+	}
+
+	return splits
+}
+
+// ParseBranchNameFromSnapshot parses branch name from the snapshot ID.
+func ParseBranchNameFromSnapshot(snapshot, poolName string) string {
+	dataset, _, found := strings.Cut(snapshot, "@")
+	if !found {
+		return ""
+	}
+
+	branchPrefix := poolName + "/" + BranchDir + "/"
+	if !strings.HasPrefix(dataset, branchPrefix) {
+		return ""
+	}
+
+	trimmedDataset := strings.TrimPrefix(dataset, branchPrefix)
+
+	splits := strings.SplitN(trimmedDataset, "/", 2)
+	if len(splits) < 1 {
+		return ""
+	}
+
+	branch := splits[0]
+
+	return branch
+}
diff --git a/engine/pkg/util/branching/branching_test.go b/engine/pkg/util/branching/branching_test.go
new file mode 100644
index 00000000..661ff82b
--- /dev/null
+++ b/engine/pkg/util/branching/branching_test.go
@@ -0,0 +1,35 @@
+package branching
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParsingBranchNameFromSnapshot(t *testing.T) {
+	const poolName = "pool/pg17"
+
+	testCases := []struct {
+		input    string
+		expected string
+	}{
+		{
+			input:    "pool/pg17@snapshot_20250407101616",
+			expected: "",
+		},
+		{
+			input:    "pool/pg17/branch/dev@20250407101828",
+			expected: "dev",
+		},
+		{
+			input:    "pool/pg17/branch/main/cvpqe8gn9i6s73b49e3g/r0@20250407102140",
+			expected: "main",
+		},
+	}
+
+	for _, tc := range testCases {
+		branchName := ParseBranchNameFromSnapshot(tc.input, poolName)
+
+		assert.Equal(t, tc.expected, branchName)
+	}
+}
diff --git a/engine/pkg/util/clones.go b/engine/pkg/util/clones.go
index 4e868651..0a798c51 100644
--- a/engine/pkg/util/clones.go
+++ b/engine/pkg/util/clones.go
@@ -4,21 +4,12 @@
 
 package util
 
-import (
-	"strconv"
-)
-
 const (
 	// ClonePrefix defines a Database Lab clone prefix.
 	ClonePrefix = "dblab_clone_"
 )
 
-// GetCloneName returns a clone name.
-func GetCloneName(port uint) string {
-	return ClonePrefix + strconv.FormatUint(uint64(port), 10)
-}
-
-// GetCloneNameStr returns a clone name.
-func GetCloneNameStr(port string) string {
-	return ClonePrefix + port
+// GetPoolName returns pool name.
+func GetPoolName(basePool, snapshotSuffix string) string {
+	return basePool + "/" + snapshotSuffix
 }
diff --git a/engine/scripts/init-zfs-colima.sh b/engine/scripts/init-zfs-colima.sh
new file mode 100755
index 00000000..ac96b8a9
--- /dev/null
+++ b/engine/scripts/init-zfs-colima.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+set -e
+
+POOL_NAME="dblab_pool"
+POOL_MNT="/var/lib/dblab/dblab_pool"
+DISK_FILE="/zfs-disk"
+DATASETS=(dataset_1 dataset_2 dataset_3)
+
+echo "🔍 Checking if zfsutils-linux is installed..."
+if ! command -v zfs >/dev/null 2>&1; then
+  echo "📦 Installing zfsutils-linux..."
+  sudo apt update
+  sudo apt install -y zfsutils-linux
+else
+  echo "✅ ZFS already installed"
+fi
+
+if [ ! -f "$DISK_FILE" ]; then
+  echo "🧱 Creating virtual ZFS disk at $DISK_FILE..."
+  sudo truncate -s 5G "$DISK_FILE"
+else
+  echo "✅ ZFS disk file already exists"
+fi
+
+echo "🔗 Setting up loop device..."
+sudo losetup -fP "$DISK_FILE"
+LOOP=$(sudo losetup -j "$DISK_FILE" | cut -d: -f1)
+
+echo "📂 Checking if pool '$POOL_NAME' exists..."
+if ! zpool list | grep -q "$POOL_NAME"; then
+  echo "🚀 Creating ZFS pool $POOL_NAME..."
+  sudo zpool create -f \
+    -O compression=on \
+    -O atime=off \
+    -O recordsize=128k \
+    -O logbias=throughput \
+    -m "$POOL_MNT" \
+    "$POOL_NAME" \
+    "$LOOP"
+else
+  echo "✅ ZFS pool '$POOL_NAME' already exists"
+fi
+
+echo "📦 Creating base datasets..."
+for DATASET in "${DATASETS[@]}"; do
+  if ! zfs list | grep -q "${POOL_NAME}/${DATASET}"; then
+    echo "📁 Creating dataset ${POOL_NAME}/${DATASET}"
+    sudo zfs create -o mountpoint="${POOL_MNT}/${DATASET}" "${POOL_NAME}/${DATASET}"
+  else
+    echo "⚠️ Dataset '${DATASET}' already exists"
+  fi
+done
+
+echo "✅ ZFS setup complete."
\ No newline at end of file
diff --git a/engine/test/1.synthetic.sh b/engine/test/1.synthetic.sh
index 5470eb51..7e49636a 100644
--- a/engine/test/1.synthetic.sh
+++ b/engine/test/1.synthetic.sh
@@ -66,11 +66,18 @@ sudo docker rm dblab_pg_initdb
 
 configDir="$HOME/.dblab/engine/configs"
 metaDir="$HOME/.dblab/engine/meta"
+logsDir="$HOME/.dblab/engine/logs"
 
 # Copy the contents of configuration example
 mkdir -p "${configDir}"
-
-curl https://gitlab.com/postgres-ai/database-lab/-/raw/"${TAG:-master}"/engine/configs/config.example.logical_generic.yml \
+mkdir -p "${metaDir}"
+mkdir -p "${logsDir}"
+
+# Use CI_COMMIT_REF_NAME to get the original branch name, as CI_COMMIT_REF_SLUG replaces "/" with "-".
+# Fallback to TAG (which is CI_COMMIT_REF_SLUG) or "master".
+BRANCH_FOR_URL="${CI_COMMIT_REF_NAME:-${TAG:-master}}"
+ENCODED_BRANCH_FOR_URL=$(echo "${BRANCH_FOR_URL}" | sed 's|/|%2F|g')
+curl https://gitlab.com/postgres-ai/database-lab/-/raw/"${ENCODED_BRANCH_FOR_URL}"/engine/configs/config.example.logical_generic.yml \
  --output "${configDir}/server.yml"
 
 # TODO: replace the dockerImage tag back to 'postgresai/extended-postgres' after releasing a new version with custom port and unix socket dir.
@@ -116,6 +123,7 @@ sudo docker run \
   --volume ${DLE_TEST_MOUNT_DIR}:${DLE_TEST_MOUNT_DIR}/:rshared \
   --volume "${configDir}":/home/dblab/configs \
   --volume "${metaDir}":/home/dblab/meta \
+  --volume "${logsDir}":/home/dblab/logs \
   --env DOCKER_API_VERSION=1.39 \
   --detach \
   "${IMAGE2TEST}"
@@ -156,18 +164,23 @@ dblab init \
 dblab instance status
 
 # Check the snapshot list
- if [[ $(dblab snapshot list | jq length) -eq 0 ]] ; then
-   echo "No snapshot found" && exit 1
- fi
+if [[ $(dblab snapshot list | jq length) -eq 0 ]] ; then
+  echo "No snapshot found" && exit 1
+fi
 
 ## Create a clone
+CLONE_ID="testclone"
+
 dblab clone create \
   --username dblab_user_1 \
   --password secret_password \
-  --id testclone
+  --id ${CLONE_ID}
 
 ### Check that database system was properly shut down (clone data dir)
-CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/clones/dblab_clone_"${DLE_PORT_POOL_FROM}"/data/log
+BRANCH_MAIN="main"
+REVISION_0="r0"
+# /var/lib/test/dblab_mount/test_dblab_pool/branch/main/testclone/r0
+CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/branch/"${BRANCH_MAIN}"/"${CLONE_ID}"/"${REVISION_0}"/data/log
 LOG_FILE_CSV=$(sudo ls -t "$CLONE_LOG_DIR" | grep .csv | head -n 1)
 if sudo test -d "$CLONE_LOG_DIR"
 then
@@ -231,6 +244,55 @@ PGPASSWORD=secret_password psql \
 dblab clone destroy testclone
 dblab clone list
 
+### Data branching.
+dblab branch || (echo "Failed when data branching is not initialized" && exit 1)
+dblab branch 001-branch || (echo "Failed to create a data branch" && exit 1)
+dblab branch
+
+dblab clone create \
+  --username john \
+  --password secret_test_123 \
+  --branch 001-branch \
+  --id branchclone001 || (echo "Failed to create a clone on branch" && exit 1)
+
+dblab commit --clone-id branchclone001 --message branchclone001 || (echo "Failed to create a snapshot" && exit 1)
+
+dblab clone create \
+  --username alice \
+  --password secret_password_123 \
+  --branch 001-branch \
+  --id branchclone002 || (echo "Failed to create a clone on branch" && exit 1)
+
+dblab commit --clone-id branchclone002 -m branchclone002 || (echo "Failed to create a snapshot" && exit 1)
+
+dblab log 001-branch || (echo "Failed to show branch history" && exit 1)
+
+dblab clone destroy branchclone001 || (echo "Failed to destroy clone" && exit 1)
+dblab clone destroy branchclone002 || (echo "Failed to destroy clone" && exit 1)
+
+sudo docker wait branchclone001 branchclone002 || echo "Clones have been removed"
+
+dblab clone list
+dblab snapshot list
+
+dblab switch main
+
+dblab clone create \
+  --username alice \
+  --password secret_password_123 \
+  --branch 001-branch \
+  --id branchclone003 || (echo "Failed to create a clone on branch" && exit 1)
+
+dblab commit --clone-id branchclone003 --message branchclone001 || (echo "Failed to create a snapshot" && exit 1)
+
+dblab snapshot delete "$(dblab snapshot list | jq -r .[0].id)" || (echo "Failed to delete a snapshot" && exit 1)
+
+dblab clone destroy branchclone003 || (echo "Failed to destroy clone" && exit 1)
+
+dblab branch --delete 001-branch || (echo "Failed to delete data branch" && exit 1)
+
+dblab branch
+
 ## Stop DLE.
 sudo docker stop ${DLE_SERVER_NAME}
 
diff --git a/engine/test/2.logical_generic.sh b/engine/test/2.logical_generic.sh
index eb185c7a..93fdb268 100644
--- a/engine/test/2.logical_generic.sh
+++ b/engine/test/2.logical_generic.sh
@@ -4,6 +4,7 @@ set -euxo pipefail
 TAG=${TAG:-${CI_COMMIT_REF_SLUG:-"master"}}
 IMAGE2TEST="registry.gitlab.com/postgres-ai/database-lab/dblab-server:${TAG}"
 DLE_SERVER_NAME="dblab_server_test"
+export EXTENDED_IMAGE_TAG="-minor-update" # -0.5.3
 
 # Environment variables for replacement rules
 export SOURCE_DBNAME="${SOURCE_DBNAME:-test}"
@@ -77,12 +78,18 @@ source "${DIR}/_zfs.file.sh"
 
 configDir="$HOME/.dblab/engine/configs"
 metaDir="$HOME/.dblab/engine/meta"
+logsDir="$HOME/.dblab/engine/logs"
 
 # Copy the contents of configuration example 
 mkdir -p "${configDir}"
 mkdir -p "${metaDir}"
+mkdir -p "${logsDir}"
 
-curl https://gitlab.com/postgres-ai/database-lab/-/raw/"${TAG:-master}"/engine/configs/config.example.logical_generic.yml \
+# Use CI_COMMIT_REF_NAME to get the original branch name, as CI_COMMIT_REF_SLUG replaces "/" with "-".
+# Fallback to TAG (which is CI_COMMIT_REF_SLUG) or "master".
+BRANCH_FOR_URL="${CI_COMMIT_REF_NAME:-${TAG:-master}}"
+ENCODED_BRANCH_FOR_URL=$(echo "${BRANCH_FOR_URL}" | sed 's|/|%2F|g')
+curl https://gitlab.com/postgres-ai/database-lab/-/raw/"${ENCODED_BRANCH_FOR_URL}"/engine/configs/config.example.logical_generic.yml \
  --output "${configDir}/server.yml"
 
 # Edit the following options
@@ -96,7 +103,7 @@ yq eval -i '
   .provision.portPool.to = env(DLE_PORT_POOL_TO) |
   .retrieval.spec.logicalDump.options.dumpLocation = env(DLE_TEST_MOUNT_DIR) + "/" + env(DLE_TEST_POOL_NAME) + "/dump" |
   .retrieval.spec.logicalRestore.options.dumpLocation = env(DLE_TEST_MOUNT_DIR) + "/" + env(DLE_TEST_POOL_NAME) + "/dump" |
-  .databaseContainer.dockerImage = "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:" + strenv(POSTGRES_VERSION)
+  .databaseContainer.dockerImage = "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:" + strenv(POSTGRES_VERSION) + env(EXTENDED_IMAGE_TAG)
 ' "${configDir}/server.yml"
 
 SHARED_PRELOAD_LIBRARIES="pg_stat_statements, auto_explain, pgaudit, logerrors, pg_stat_kcache"
@@ -130,6 +137,7 @@ sudo docker run \
   --volume ${DLE_TEST_MOUNT_DIR}:${DLE_TEST_MOUNT_DIR}/:rshared \
   --volume "${configDir}":/home/dblab/configs \
   --volume "${metaDir}":/home/dblab/meta \
+  --volume "${logsDir}":/home/dblab/logs \
   --env DOCKER_API_VERSION=1.39 \
   --detach \
   "${IMAGE2TEST}"
@@ -173,7 +181,7 @@ PATCH_CONFIG_DATA=$(jq -n -c \
   --arg username "$SOURCE_USERNAME" \
   --arg password "$SOURCE_PASSWORD" \
   --arg spl "$SHARED_PRELOAD_LIBRARIES" \
-  --arg dockerImage "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:${POSTGRES_VERSION}" \
+  --arg dockerImage "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:${POSTGRES_VERSION}${EXTENDED_IMAGE_TAG}" \
 '{
   "global": {
     "debug": true
@@ -244,7 +252,7 @@ if [[ $(yq eval '.retrieval.spec.logicalDump.options.source.connection.dbname' $
       $(yq eval '.retrieval.spec.logicalDump.options.source.connection.username' ${configDir}/server.yml) != "$SOURCE_USERNAME" ||
       $(yq eval '.retrieval.spec.logicalDump.options.source.connection.password' ${configDir}/server.yml) != "$SOURCE_PASSWORD" ||
       $(yq eval '.retrieval.refresh.timetable' ${configDir}/server.yml) != "5 0 * * 1" ||
-      $(yq eval '.databaseContainer.dockerImage' ${configDir}/server.yml) != "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:${POSTGRES_VERSION}" ||
+      $(yq eval '.databaseContainer.dockerImage' ${configDir}/server.yml) != "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:${POSTGRES_VERSION}${EXTENDED_IMAGE_TAG}" ||
       $(yq eval '.databaseConfigs.configs.shared_buffers' ${configDir}/server.yml) != "256MB" ]] ; then
   echo "Configuration has not been updated properly"
   exit 1
@@ -284,13 +292,18 @@ dblab instance status
 
 
 ## Create a clone
+CLONE_ID="testclone"
+
 dblab clone create \
   --username dblab_user_1 \
   --password secret_password \
-  --id testclone
+  --id ${CLONE_ID}
 
 ### Check that database system was properly shut down (clone data dir)
-CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/clones/dblab_clone_"${DLE_PORT_POOL_FROM}"/data/log
+BRANCH_MAIN="main"
+REVISION_0="r0"
+# /var/lib/test/dblab_mount/test_dblab_pool/branch/main/testclone/r0
+CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/branch/"${BRANCH_MAIN}"/"${CLONE_ID}"/"${REVISION_0}"/data/log
 LOG_FILE_CSV=$(sudo ls -t "$CLONE_LOG_DIR" | grep .csv | head -n 1)
 if sudo test -d "$CLONE_LOG_DIR"
 then
diff --git a/engine/test/3.physical_walg.sh b/engine/test/3.physical_walg.sh
index a311367d..f3c5e8bc 100644
--- a/engine/test/3.physical_walg.sh
+++ b/engine/test/3.physical_walg.sh
@@ -174,13 +174,17 @@ dblab instance status
 
 
 ## Create a clone
+CLONE_ID="testclone"
+
 dblab clone create \
   --username dblab_user_1 \
   --password secret_password \
-  --id testclone
+  --id ${CLONE_ID}
 
 ### Check that database system was properly shut down (clone data dir)
-CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/clones/dblab_clone_"${DLE_PORT_POOL_FROM}"/data/log
+BRANCH_MAIN="main"
+REVISION_0="r0"
+CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/branch/"${BRANCH_MAIN}"/"${CLONE_ID}"/"${REVISION_0}"/data/log
 LOG_FILE_CSV=$(sudo ls -t "$CLONE_LOG_DIR" | grep .csv | head -n 1)
 if sudo test -d "$CLONE_LOG_DIR"
 then
diff --git a/engine/test/4.physical_basebackup.sh b/engine/test/4.physical_basebackup.sh
index f72508b5..eb562197 100644
--- a/engine/test/4.physical_basebackup.sh
+++ b/engine/test/4.physical_basebackup.sh
@@ -4,6 +4,7 @@ set -euxo pipefail
 TAG=${TAG:-${CI_COMMIT_REF_SLUG:-"master"}}
 IMAGE2TEST="registry.gitlab.com/postgres-ai/database-lab/dblab-server:${TAG}"
 DLE_SERVER_NAME="dblab_server_test"
+export EXTENDED_IMAGE_TAG="-minor-update" # -0.5.3
 
 # Environment variables for replacement rules
 export SOURCE_HOST="${SOURCE_HOST:-172.17.0.1}"
@@ -92,11 +93,17 @@ source "${DIR}/_zfs.file.sh"
 
 configDir="$HOME/.dblab/engine/configs"
 metaDir="$HOME/.dblab/engine/meta"
+logsDir="$HOME/.dblab/engine/logs"
 
 # Copy the contents of configuration example 
 mkdir -p "${configDir}"
+mkdir -p "${logsDir}"
 
-curl https://gitlab.com/postgres-ai/database-lab/-/raw/"${TAG:-master}"/engine/configs/config.example.physical_generic.yml \
+# Use CI_COMMIT_REF_NAME to get the original branch name, as CI_COMMIT_REF_SLUG replaces "/" with "-".
+# Fallback to TAG (which is CI_COMMIT_REF_SLUG) or "master".
+BRANCH_FOR_URL="${CI_COMMIT_REF_NAME:-${TAG:-master}}"
+ENCODED_BRANCH_FOR_URL=$(echo "${BRANCH_FOR_URL}" | sed 's|/|%2F|g')
+curl https://gitlab.com/postgres-ai/database-lab/-/raw/"${ENCODED_BRANCH_FOR_URL}"/engine/configs/config.example.physical_generic.yml \
  --output "${configDir}/server.yml"
 
 # Edit the following options
@@ -108,7 +115,7 @@ yq eval -i '
   .poolManager.mountDir = env(DLE_TEST_MOUNT_DIR) |
   .provision.portPool.from = env(DLE_PORT_POOL_FROM) |
   .provision.portPool.to = env(DLE_PORT_POOL_TO) |
-  .databaseContainer.dockerImage = "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:" + strenv(POSTGRES_VERSION) |
+  .databaseContainer.dockerImage = "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:" + strenv(POSTGRES_VERSION) + env(EXTENDED_IMAGE_TAG) |
   .retrieval.spec.physicalRestore.options.envs.PGUSER = strenv(SOURCE_USERNAME) |
   .retrieval.spec.physicalRestore.options.envs.PGPASSWORD = strenv(SOURCE_PASSWORD) |
   .retrieval.spec.physicalRestore.options.envs.PGHOST = strenv(SOURCE_HOST) |
@@ -144,6 +151,7 @@ sudo docker run \
   --volume ${DLE_TEST_MOUNT_DIR}:${DLE_TEST_MOUNT_DIR}/:rshared \
   --volume "${configDir}":/home/dblab/configs \
   --volume "${metaDir}":/home/dblab/meta \
+  --volume "${logsDir}":/home/dblab/logs \
   --env DOCKER_API_VERSION=1.39 \
   --detach \
   "${IMAGE2TEST}"
@@ -191,13 +199,17 @@ dblab instance status
 
 
 ## Create a clone
+CLONE_ID="testclone"
+
 dblab clone create \
   --username dblab_user_1 \
   --password secret_password \
-  --id testclone
+  --id ${CLONE_ID}
 
 ### Check that database system was properly shut down (clone data dir)
-CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/clones/dblab_clone_"${DLE_PORT_POOL_FROM}"/data/log
+BRANCH_MAIN="main"
+REVISION_0="r0"
+CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/branch/"${BRANCH_MAIN}"/"${CLONE_ID}"/"${REVISION_0}"/data/log
 LOG_FILE_CSV=$(sudo ls -t "$CLONE_LOG_DIR" | grep .csv | head -n 1)
 if sudo test -d "$CLONE_LOG_DIR"
 then
diff --git a/engine/test/5.logical_rds.sh b/engine/test/5.logical_rds.sh
index a05e325d..02ed2de2 100644
--- a/engine/test/5.logical_rds.sh
+++ b/engine/test/5.logical_rds.sh
@@ -4,6 +4,7 @@ set -euxo pipefail
 TAG="${TAG:-"master"}"
 IMAGE2TEST="registry.gitlab.com/postgres-ai/database-lab/dblab-server:${TAG}"
 DLE_SERVER_NAME="dblab_server_test"
+export EXTENDED_IMAGE_TAG="-minor-update" # -0.5.3
 
 # Environment variables for replacement rules
 export DLE_TEST_MOUNT_DIR="/var/lib/test/dblab_mount"
@@ -48,7 +49,7 @@ yq eval -i '
   .poolManager.mountDir = env(DLE_TEST_MOUNT_DIR) |
   .provision.portPool.from = env(DLE_PORT_POOL_FROM) |
   .provision.portPool.to = env(DLE_PORT_POOL_TO) |
-  .databaseContainer.dockerImage = "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:" + strenv(POSTGRES_VERSION) |
+  .databaseContainer.dockerImage = "registry.gitlab.com/postgres-ai/custom-images/extended-postgres:" + strenv(POSTGRES_VERSION) + env(EXTENDED_IMAGE_TAG) |
   .retrieval.spec.logicalDump.options.dumpLocation = env(DLE_TEST_MOUNT_DIR) + "/" + env(DLE_TEST_POOL_NAME) + "/dump" |
   .retrieval.spec.logicalDump.options.source.connection.dbname = strenv(SOURCE_DBNAME) |
   .retrieval.spec.logicalDump.options.source.connection.username = strenv(SOURCE_USERNAME) |
@@ -125,13 +126,17 @@ dblab instance status
 
 
 ## Create a clone
+CLONE_ID="testclone"
+
 dblab clone create \
   --username dblab_user_1 \
   --password secret_password \
-  --id testclone
+  --id ${CLONE_ID}
 
 ### Check that database system was properly shut down (clone data dir)
-CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/clones/dblab_clone_"${DLE_PORT_POOL_FROM}"/data/log
+BRANCH_MAIN="main"
+REVISION_0="r0"
+CLONE_LOG_DIR="${DLE_TEST_MOUNT_DIR}"/"${DLE_TEST_POOL_NAME}"/branch/"${BRANCH_MAIN}"/"${CLONE_ID}"/"${REVISION_0}"/data/log
 LOG_FILE_CSV=$(sudo ls -t "$CLONE_LOG_DIR" | grep .csv | head -n 1)
 if sudo test -d "$CLONE_LOG_DIR"
 then
diff --git a/engine/test/_cleanup.sh b/engine/test/_cleanup.sh
index 6fb304a7..6e9ccca6 100644
--- a/engine/test/_cleanup.sh
+++ b/engine/test/_cleanup.sh
@@ -3,11 +3,14 @@ set -euxo pipefail
 
 DLE_TEST_MOUNT_DIR="/var/lib/test/dblab_mount"
 DLE_TEST_POOL_NAME="test_dblab_pool"
+TMP_DATA_DIR="/tmp/dle_test/logical_generic"
 ZFS_FILE="$(pwd)/zfs_file"
 
 # Stop and remove test Docker containers
 sudo docker ps -aq --filter label="test_dblab_pool" | xargs --no-run-if-empty sudo docker rm -f \
   || echo "Failed to remove test Docker containers, continuing..."
+sudo docker ps -aq --filter label="dblab_clone=test_dblab_pool" | xargs --no-run-if-empty sudo docker rm -f \
+  || echo "Failed to remove test Docker containers, continuing..."
 sudo docker ps -aq --filter label="dblab_test" | xargs --no-run-if-empty sudo docker rm -f \
   || echo "Failed to remove dblab_test Docker containers, continuing..."
 
@@ -19,6 +22,10 @@ sudo docker images --filter=reference='registry.gitlab.com/postgres-ai/database-
 sudo rm -rf ${DLE_TEST_MOUNT_DIR}/${DLE_TEST_POOL_NAME}/data/* \
   || echo "Data directory cleanup finished with errors but continuing..."
 
+# Clean up branch directory
+sudo rm -rf ${DLE_TEST_MOUNT_DIR}/${DLE_TEST_POOL_NAME}/branch/* \
+  || echo "Branch directory cleanup finished with errors but continuing..."
+
 # Remove dump directory
 sudo umount ${DLE_TEST_MOUNT_DIR}/${DLE_TEST_POOL_NAME}/dump \
   || echo "Unmounting dump directory finished with errors but it is OK to ignore them."
@@ -40,3 +47,7 @@ sudo rm -f "${ZFS_FILE}" \
 # Remove CLI configuration
 dblab config remove test \
   || echo "Removing CLI configuration finished with errors but it is OK to ignore them."
+
+# Clean up tmp source database
+sudo rm -rf ${TMP_DATA_DIR}/postgresql/* \
+  || echo "Cleaning up tmp source directory finished with errors but it is OK to ignore them."
diff --git a/ui/.dockerignore b/ui/.dockerignore
index 88026b98..3ec5991a 100644
--- a/ui/.dockerignore
+++ b/ui/.dockerignore
@@ -6,5 +6,4 @@
 **/build/**
 ui/node_modules/
 ui/packages/ce/node_modules/
-ui/packages/shared/node_modules/
-ui/packages/platform/node_modules/
+ui/packages/shared/node_modules/
\ No newline at end of file
diff --git a/ui/.gitlab-ci.yml b/ui/.gitlab-ci.yml
index b101c8a6..ca9f08da 100644
--- a/ui/.gitlab-ci.yml
+++ b/ui/.gitlab-ci.yml
@@ -1,6 +1,6 @@
 include:
   - local: 'ui/packages/ce/.gitlab-ci.yml'
-  - local: 'ui/packages/platform/.gitlab-ci.yml'
+  - local: 'ui/packages/shared/.gitlab-ci.yml'
 
 .ui_checks: &ui_checks
   rules:
@@ -10,7 +10,9 @@ include:
     - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
 
 .ui_cache: &ui_cache
-  image: node:21.1.0-alpine
+  image:
+    name: node:21.1.0-alpine
+    pull_policy: if-not-present
   cache: &cache
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -31,7 +33,6 @@ check-code-style:
   script:
     - pnpm --dir ui/ i
     - pnpm --dir ui/ --filter @postgres.ai/ce lint
-    - pnpm --dir ui/ --filter @postgres.ai/platform lint
   interruptible: true
   cache:
     <<: *cache
@@ -39,7 +40,9 @@ check-code-style:
 
 semgrep-sast:
   stage: test
-  image: returntocorp/semgrep
+  image:
+    name: returntocorp/semgrep
+    pull_policy: if-not-present
   <<: *ui_checks
   <<: *ui_cache
   variables:
@@ -60,7 +63,9 @@ semgrep-sast:
 
 e2e-ce-ui-test:
   <<: *ui_checks
-  image: node:21.1.0
+  image:
+    name: node:21.1.0
+    pull_policy: if-not-present
   stage: integration-test
   variables:
     CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress'
@@ -69,9 +74,14 @@ e2e-ce-ui-test:
     - apt install -y curl libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
     - npm install -g wait-on
     - npm install -g pnpm
+    - pnpm config set verify-store-integrity false
     # TODO: Set up caching.
-#    - pnpm config set store-dir /builds/postgres-ai/database-lab/.pnpm-store/
+    #- pnpm config set store-dir /builds/postgres-ai/database-lab/.pnpm-store/
   script:
-    - pnpm --dir ui/ i --no-frozen-lockfile
-    - pnpm --dir ui/ --filter @postgres.ai/ce start & wait-on http://localhost:3001
+    - pnpm --dir ui/ --filter @postgres.ai/ce install
+    - pnpm --dir ui/ --filter @postgres.ai/ce build
+    - pnpm --dir ui/ --filter @postgres.ai/ce exec cypress install
+    - npx serve -s ui/packages/ce/build -l 3001 > server.log 2>&1 &
+    - sleep 20
+    - timeout 120s wait-on http://localhost:3001 || (echo "❌ UI didn't start in time"; cat server.log; exit 1)
     - pnpm --dir ui/ --filter @postgres.ai/ce cy:run
diff --git a/ui/README.md b/ui/README.md
index 9930d4ca..a433276e 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -2,94 +2,75 @@
 
 ## DBLab - thin database clones and database branching for faster development
 
-_Proceed to [Database Lab Engine repository](https://gitlab.com/postgres-ai/database-lab) for more information about technology itself._
-Database Lab Engine (DLE) is an open-source (Apache 2.0) technology that allows blazing-fast cloning of Postgres databases of any size in seconds. This helps solve many problems such as:
+_See the [Database Lab Engine repository](https://gitlab.com/postgres-ai/database-lab) for more information about the underlying technology._
+Database Lab Engine (DLE) is an open-source (Apache 2.0) solution that enables blazing-fast cloning of PostgreSQL databases of any size in seconds. This capability helps solve common challenges, such as:
 
-- build dev/QA/staging environments involving full-size production-like databases,
-- provide temporary full-size database clones for SQL query analysis optimization,
-- automatically verify database migrations (DB schema changes) and massive data operations in CI/CD pipelines to minimize risks of downtime and performance degradation.
+- Build dev/QA/staging environments with full-size, production-like databases.
+- Provide temporary full-size database clones for SQL query analysis and optimization.
+- Automatically verify database migrations (schema changes) and large data operations in CI/CD pipelines to minimize the risk of downtime and performance degradation.
 
-As an example, cloning a 10 TiB PostgreSQL database can take less than 2 seconds.
+For example, cloning a 10 TiB PostgreSQL database can take less than 2 seconds.
 
 ## Development
 
-### List packages:
+### List of packages:
 
-- `@postgres.ai/platform` - platform version of UI
-- `@postgres.ai/ce` - community edition version of UI
-- `@postgres.ai/shared` - common modules
+- `@postgres.ai/ce` - Community Edition UI package
+- `@postgres.ai/shared` - Shared modules and utilities
 
-### How to operate
+## UI Development Documentation
 
-At the root:
+At the repository root, you can run commands for all packages or individual packages:
 
-- `<npm command> -ws` - for all packages
-- `<npm command> -w <package-name>` - for specific package
+- `<npm command> -ws` – run the specified command on all packages.
+- `<npm command> -w <package-name>` – run the specified command on a single package.
 
 #### Examples
-- `npm ci -ws` - install deps of all packages
-- `npm run build -ws` - build all packages
-- `npm run start -w @postgres.ai/platform` - run platform UI locally in dev mode
-- `npm run start -w @postgres.ai/ce` - run community edition UI locally in dev mode
-
-_Important note: don't use commands for `@postgres.ai/shared` - it's dependent package, which can't be running or built_
-
-### How to start Platform UI with a predefined JWT token
-- `cd ui/packages/platform`
-- `cp .env_example_dev .env`
-- edit `.env` setting:
-    - `REACT_APP_API_URL_PREFIX` to point to dev API server (e.g., staging API server: `https://v2.postgres.ai/api/general`).
-    - `REACT_APP_TOKEN_DEBUG` to have your JWT ready work with the same server. Note that it has an expiration date so it needs to be periodically refreshed.
-- `pnpm install` - to install packages if they are not found
-- `pnpm run start` - start Platform for local debugging/development
-
-### How to start "ce"
-- `cd ui`
-- `npm ci -ws` - install dependencies, must be done once to install dependencies for all packages
-- `npm run start -w @postgres.ai/ce` - start dev server
+- `npm ci -ws` – install all dependencies.
+- `npm run build -ws` – build all packages.
+- `npm run start -w @postgres.ai/ce` – run the Community Edition UI locally in development mode.
 
-### How to build "platform"
+_Important note: do not run or build the `@postgres.ai/shared` package directly; it is a dependency._
 
+### How to start the Community Edition UI
 - `cd ui`
-- `npm ci -ws` - install dependencies, must be done once to install dependencies for all packages
-- `source packages/platform/deploy/configs/production.sh` - set up environment variables, should be run for each new terminal session
-- `npm run build -w @postgres.ai/platform`
+- `npm ci -ws` – install dependencies for all packages (run once).
+- `npm run start -w @postgres.ai/ce` – start the development server.
 
-### How to build "ce"
+### How to build the Community Edition UI
 
 - `cd ui`
-- `npm ci -ws` - install dependencies, must be done once to install dependencies for all packages
-- `npm run build -w @postgres.ai/ce` - start dev server
+- `npm ci -ws` – install dependencies for all packages (run once).
+- `npm run build -w @postgres.ai/ce` – build the Community Edition UI.
 
 ### CI pipelines for UI code
 
-To deploy UI changes, tag the commit with `ui/` prefix and push it. For example:
+To deploy UI changes, tag the commit with a `ui/` prefix and push it. For example:
 
 ```shell
 git tag ui/1.0.12
 git push origin ui/1.0.12
 ```
 
-## Vulnerability issues
-Vulnerabilities, CVEs, security issues can be reported on GitLab or on GitHub by various tools/bots we use to ensure that DLE code is safe and secure. They may be of various kinds – here we consider two types, a known CVE reported for particular package we use for DLE UI code during analysis of dependencies, and some issue in code that was detected by a static analysis tool.
+## Vulnerability Issues
+Vulnerabilities, CVEs, and security issues can be reported on GitLab or GitHub through the tools and bots we use to ensure that DLE code remains safe and secure. Below we outline two primary categories: known CVEs in dependencies and issues detected by static analysis tools.
 
-### Packages issues
-Ways to resolve (ordered by preference in descending order):
-1. Update a package - try to look for a newer package in npm, probably this vulnerability is already fixed.
-2. If vulnerability is detected in a sub-package - try to replace it using [npm-force-resolutions](https://www.npmjs.com/package/npm-force-resolutions). Be careful using this way - it may break a project as in a build phase as at runtime. Full e2e definitely should be done in this case.
-3. Fork the package and put it locally in this repo.
-4. If you are sure this is a falsy vulnerability - try to ignore it using special commands for your SAST tool. **This is considered as the least preferable option – try to apply any of the ways described above first.**
+#### Package Issues
+Ways to resolve (in descending order of preference):
+1. Update the package – search npm for a newer version, as the vulnerability may already be fixed.
+2. If the vulnerability is in a sub-package, use [npm-force-resolutions](https://www.npmjs.com/package/npm-force-resolutions) to override it. Use this technique with caution—it may break the project during build or at runtime. Perform a full end-to-end test afterward.
+3. Fork the package and include it locally in this repository.
+4. If the issue is a false positive vulnerability, ignore it using your SAST tool's ignore directives. **This should be the last resort; apply other solutions first.**
 
-### Code issues
-Ways to resolve (ordered by preference):
-1. If the part of source code is written on `.js` try to rewrite it on `.ts` or `.tsx` - it should fix a lot of potential security issues.
-2. Follow the recommendations of your SAST tool - fix it manually or automatically.
-3. If you are sure this is a falsy vulnerability - try to ignore it using special commands for your SAST tool. **This is considered as the least preferable option – try to apply any of the ways described above first.**
+#### Code Issues
+Ways to resolve (in descending order of preference):
+1. If a portion of the source code is written in `.js`, rewrite it in `.ts` or `.tsx`—this can resolve many potential security issues.
+2. Follow your SAST tool's recommendations and apply fixes manually or automatically.
+3. If the finding is a false positive, ignore it using your SAST tool's ignore directives. **This should be the last resort; apply other solutions first.**
 
 <!-- TODO: move this ^ to the main README.md and CONTRIBUTING.md -->
 
-## Moving to Typescript
-- `@postgres.ai/shared` is written on Typescript
-- `@postgres.ai/ce` is written on Typescript
-- `@postgres.ai/platform` is written on JavaScript and patially on Typescript. The target - is moving `@postgres.ai/platform` to Typescript fully. It should takes approximately 120-160 hours.
-- There are potential problems with typing - old versions of packages may don't have their typings. Recommended to update them or replace. If it's impossible you can write your own typing in file named like `<package-name>.d.ts` inside `src` directory of the selected package.
+## Migrating to TypeScript
+- `@postgres.ai/shared` is written in TypeScript.
+- `@postgres.ai/ce` is written in TypeScript.
+- There may be typing issues: older packages might lack type definitions. It is recommended to update or replace them. If that is not possible, write a custom definition file named `<package-name>.d.ts` in the `src` directory of the appropriate package.
diff --git a/ui/cspell.json b/ui/cspell.json
index 857ef8f8..64382e04 100644
--- a/ui/cspell.json
+++ b/ui/cspell.json
@@ -200,6 +200,10 @@
     "TSQL",
     "sparql",
     "SPARQL",
-    "subtransactions"
+    "subtransactions",
+    "mbox",
+    "SIEM",
+    "toolcall",
+    "thinkblock"
   ]
 }
diff --git a/ui/package.json b/ui/package.json
index 82b2d887..63a3af14 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -7,7 +7,6 @@
   },
   "scripts": {
     "preinstall": "npx only-allow pnpm",
-    "start:platform": "source ./packages/platform/deploy/configs/production.sh && npm run start -w @postgres.ai/platform",
     "start:ce": "npm run start -w @postgres.ai/ce"
   },
   "pnpm": {
@@ -35,7 +34,7 @@
       "semver@>=7.0.0 <7.5.2": ">=7.5.2",
       "semver@<5.7.2": ">=5.7.2",
       "semver@>=6.0.0 <6.3.1": ">=6.3.1",
-      "minimatch@<3.0.5": ">=3.0.5",
+      "minimatch": "3.1.2",
       "json5@<1.0.2": ">=1.0.2",
       "json5@>=2.0.0 <2.2.2": ">=2.2.2",
       "ip@<1.1.9": ">=1.1.9",
diff --git a/ui/packages/ce/.dockerignore b/ui/packages/ce/.dockerignore
index 19d960ff..ce733752 100644
--- a/ui/packages/ce/.dockerignore
+++ b/ui/packages/ce/.dockerignore
@@ -7,4 +7,3 @@
 /ui/node_modules/
 /ui/packages/ce/node_modules/
 /ui/packages/shared/node_modules/
-/ui/packages/platform/node_modules/
diff --git a/ui/packages/ce/.gitlab-ci.yml b/ui/packages/ce/.gitlab-ci.yml
index f31a1c96..6e79f978 100644
--- a/ui/packages/ce/.gitlab-ci.yml
+++ b/ui/packages/ce/.gitlab-ci.yml
@@ -12,7 +12,9 @@
     - if: $CI_COMMIT_TAG =~ /^v[a-zA-Z0-9_.-]*/
 
 .ui_cache: &ui_cache
-  image: node:lts-alpine
+  image:
+    name: node:lts-alpine
+    pull_policy: if-not-present
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -22,10 +24,15 @@
 # Jobs templates.
 .build_definition: &build_definition
   <<: *ui_cache
-  image: docker:20.10.12
+  image:
+    name: docker:24
+    pull_policy: if-not-present
   stage: build
   services:
-    - docker:dind
+    - name: docker:24-dind
+      alias: docker
+      command: [ "--tls=false" ]
+      pull_policy: if-not-present
   script:
     - apk add --no-cache bash
     - bash ./ui/packages/ce/ci_docker_build_push.sh
diff --git a/ui/packages/ce/cypress/e2e/tabs.cy.js b/ui/packages/ce/cypress/e2e/tabs.cy.js
index d9c6dc59..db2afe82 100644
--- a/ui/packages/ce/cypress/e2e/tabs.cy.js
+++ b/ui/packages/ce/cypress/e2e/tabs.cy.js
@@ -4,7 +4,6 @@ Cypress.on('uncaught:exception', () => {
   return false
 })
 
-// Function to set up intercepts for the requests
 function setupIntercepts() {
   const exceptions = [
     '/healthz',
@@ -44,7 +43,6 @@ function setupIntercepts() {
     },
   })
 
-  // Intercept all fetch requests and return a 200
   cy.intercept('GET', '*', (req) => {
     if (
       req.resourceType === 'fetch' &&
@@ -61,7 +59,6 @@ function setupIntercepts() {
 }
 
 describe('Configuration tab', () => {
-  // It should intercept the requests
   beforeEach(() => {
     setupIntercepts()
   })
@@ -71,19 +68,10 @@ describe('Configuration tab', () => {
       retryOnStatusCodeFailure: true,
       onLoad: () => {
         cy.get('.MuiTabs-flexContainer')
-          .contains('Configuration', { timeout: 10000 })
+          .contains('Configuration')
           .should('be.visible')
           .click({ force: true })
       },
     })
   })
-
-  it('should have form inputs in the "Configuration" tab', () => {
-    cy.get('.MuiTabs-flexContainer')
-      .contains('Configuration', { timeout: 10000 })
-      .should('be.visible')
-      .click({ force: true })
-
-    cy.get('button[type="button"]').should('exist')
-  })
 })
diff --git a/ui/packages/ce/package.json b/ui/packages/ce/package.json
index 232deab9..55e54843 100644
--- a/ui/packages/ce/package.json
+++ b/ui/packages/ce/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@postgres.ai/ce",
-  "version": "1.0.0",
+  "version": "4.0.0",
   "private": true,
   "dependencies": {
     "@craco/craco": "^6.4.3",
@@ -19,6 +19,7 @@
     "@types/react-dom": "^17.0.10",
     "@types/react-router": "^5.1.17",
     "@types/react-router-dom": "^5.3.1",
+    "@types/react-syntax-highlighter": "^15.5.6",
     "byte-size": "^8.1.0",
     "classnames": "^2.3.1",
     "clsx": "^1.1.1",
@@ -39,6 +40,7 @@
     "react-router": "^5.1.2",
     "react-router-dom": "^5.1.2",
     "react-scripts": "^5.0.0",
+    "react-syntax-highlighter": "^15.5.0",
     "stream-browserify": "^3.0.0",
     "typescript": "^4.4.4",
     "use-timer": "^2.0.1",
@@ -90,5 +92,5 @@
     "stylelint-config-standard-scss": "^2.0.1",
     "stylelint-prettier": "^2.0.0"
   },
-  "proxy": "https://demo.aws.postgres.ai:446"
+  "proxy": "https://demo.dblab.dev:446"
 }
diff --git a/ui/packages/ce/src/App/Instance/Branches/Branch/index.tsx b/ui/packages/ce/src/App/Instance/Branches/Branch/index.tsx
new file mode 100644
index 00000000..8da308a3
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Branches/Branch/index.tsx
@@ -0,0 +1,59 @@
+import { useParams } from 'react-router-dom'
+
+import { getBranches } from 'api/branches/getBranches'
+import { deleteBranch } from 'api/branches/deleteBranch'
+import { getSnapshotList } from 'api/branches/getSnapshotList'
+import { initWS } from 'api/engine/initWS'
+
+import { PageContainer } from 'components/PageContainer'
+import { NavPath } from 'components/NavPath'
+import { ROUTES } from 'config/routes'
+import { BranchesPage } from '@postgres.ai/shared/pages/Branches/Branch'
+
+type Params = {
+  branchId: string
+}
+
+export const Branch = () => {
+  const { branchId } = useParams<Params>()
+
+  const api = {
+    getBranches,
+    deleteBranch,
+    getSnapshotList,
+    initWS
+  }
+
+  const elements = {
+    breadcrumbs: (
+      <NavPath
+        routes={[
+          ROUTES,
+          ROUTES.INSTANCE.BRANCHES.BRANCHES,
+          {
+            name: `${ROUTES.INSTANCE.BRANCHES.BRANCH.name}/${branchId}`,
+            path: ROUTES.INSTANCE.BRANCHES.BRANCH.createPath(branchId),
+          },
+        ]}
+      />
+    ),
+  }
+
+  return (
+    <PageContainer>
+      <BranchesPage
+        instanceId=""
+        api={api}
+        elements={elements}
+        branchId={branchId}
+        routes={{
+          branch: () => ROUTES.INSTANCE.BRANCHES.BRANCHES.path,
+          branches: () => ROUTES.INSTANCE.BRANCHES.BRANCHES.path,
+          snapshot: (snapshotId: string) =>
+            ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.createPath(snapshotId),
+          createClone: (branchId: string) => ROUTES.INSTANCE.CLONES.CREATE.createPath(branchId),
+        }}
+      />
+    </PageContainer>
+  )
+}
diff --git a/ui/packages/ce/src/App/Instance/Branches/CreateBranch/index.tsx b/ui/packages/ce/src/App/Instance/Branches/CreateBranch/index.tsx
new file mode 100644
index 00000000..e0533e05
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Branches/CreateBranch/index.tsx
@@ -0,0 +1,47 @@
+import { getBranches } from 'api/branches/getBranches'
+import { createBranch } from 'api/branches/createBranch'
+import { getSnapshots } from 'api/snapshots/getSnapshots'
+import { initWS } from 'api/engine/initWS'
+
+import { CreateBranchPage } from '@postgres.ai/shared/pages/CreateBranch'
+
+import { PageContainer } from 'components/PageContainer'
+import { NavPath } from 'components/NavPath'
+import { ROUTES } from 'config/routes'
+
+export const CreateBranch = () => {
+  const routes = {
+    branch: (branchName: string) =>
+      ROUTES.INSTANCE.BRANCHES.BRANCH.createPath(branchName),
+  }
+
+  const api = {
+    getBranches,
+    createBranch,
+    getSnapshots,
+    initWS
+  }
+
+  const elements = {
+    breadcrumbs: (
+      <NavPath
+        routes={[
+          ROUTES,
+          ROUTES.INSTANCE.BRANCHES.BRANCHES,
+          ROUTES.INSTANCE.BRANCHES.CREATE,
+        ]}
+      />
+    ),
+  }
+
+  return (
+    <PageContainer>
+      <CreateBranchPage
+        instanceId={''}
+        routes={routes}
+        api={api}
+        elements={elements}
+      />
+    </PageContainer>
+  )
+}
diff --git a/ui/packages/ce/src/App/Instance/Branches/index.tsx b/ui/packages/ce/src/App/Instance/Branches/index.tsx
new file mode 100644
index 00000000..ecf327b9
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Branches/index.tsx
@@ -0,0 +1,25 @@
+import { Switch, Route, Redirect } from 'react-router-dom'
+
+import { ROUTES } from 'config/routes'
+import { TABS_INDEX } from '@postgres.ai/shared/pages/Instance/Tabs'
+
+import { Page } from '../Page'
+import { Branch } from './Branch'
+import { CreateBranch } from './CreateBranch'
+
+export const Branches = () => {
+  return (
+    <Switch>
+      <Route exact path={ROUTES.INSTANCE.BRANCHES.CREATE.path}>
+        <CreateBranch />
+      </Route>
+      <Route exact path={ROUTES.INSTANCE.BRANCHES.BRANCHES.path}>
+        <Page renderCurrentTab={TABS_INDEX.BRANCHES} />
+      </Route>
+      <Route exact path={ROUTES.INSTANCE.BRANCHES.BRANCH.createPath()}>
+        <Branch />
+      </Route>
+      <Redirect to={ROUTES.path} />
+    </Switch>
+  )
+}
diff --git a/ui/packages/ce/src/App/Instance/Clones/Clone/index.tsx b/ui/packages/ce/src/App/Instance/Clones/Clone/index.tsx
index f5bc914d..96c8b8a1 100644
--- a/ui/packages/ce/src/App/Instance/Clones/Clone/index.tsx
+++ b/ui/packages/ce/src/App/Instance/Clones/Clone/index.tsx
@@ -9,6 +9,10 @@ import { getClone } from 'api/clones/getClone'
 import { resetClone } from 'api/clones/resetClone'
 import { destroyClone } from 'api/clones/destroyClone'
 import { updateClone } from 'api/clones/updateClone'
+import { createSnapshot } from 'api/snapshots/createSnapshot'
+import { initWS } from 'api/engine/initWS'
+import { destroySnapshot } from 'api/snapshots/destroySnapshot'
+
 import { PageContainer } from 'components/PageContainer'
 import { NavPath } from 'components/NavPath'
 import { ROUTES } from 'config/routes'
@@ -27,7 +31,10 @@ export const Clone = () => {
     getClone,
     resetClone,
     destroyClone,
+    destroySnapshot,
     updateClone,
+    createSnapshot,
+    initWS,
   }
 
   const elements = {
@@ -35,9 +42,9 @@ export const Clone = () => {
       <NavPath
         routes={[
           ROUTES,
-          ROUTES.INSTANCE,
+          ROUTES.INSTANCE.CLONES.CLONES,
           {
-            name: ROUTES.INSTANCE.CLONES.CLONE.name,
+            name: `${ROUTES.INSTANCE.CLONES.CLONE.name}/${cloneId}`,
             path: ROUTES.INSTANCE.CLONES.CLONE.createPath(cloneId),
           },
         ]}
@@ -52,6 +59,9 @@ export const Clone = () => {
         cloneId={cloneId}
         routes={{
           instance: () => ROUTES.INSTANCE.path,
+          snapshot: (snapshotId: string) =>
+            ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.createPath(snapshotId),
+          createSnapshot: (cloneId: string) => ROUTES.INSTANCE.SNAPSHOTS.CREATE.createPath(cloneId),
         }}
         api={api}
         elements={elements}
diff --git a/ui/packages/ce/src/App/Instance/Clones/CreateClone/index.tsx b/ui/packages/ce/src/App/Instance/Clones/CreateClone/index.tsx
index bf5ccebc..aa17c80c 100644
--- a/ui/packages/ce/src/App/Instance/Clones/CreateClone/index.tsx
+++ b/ui/packages/ce/src/App/Instance/Clones/CreateClone/index.tsx
@@ -5,9 +5,11 @@ import { NavPath } from 'components/NavPath'
 import { ROUTES } from 'config/routes'
 import { getInstance } from 'api/instances/getInstance'
 import { getInstanceRetrieval } from 'api/instances/getInstanceRetrieval'
-import { getSnapshots } from 'api/snapshots/getSnapshots'
 import { createClone } from 'api/clones/createClone'
 import { getClone } from 'api/clones/getClone'
+import { getBranches } from 'api/branches/getBranches'
+import { getSnapshots } from 'api/snapshots/getSnapshots'
+import { initWS } from 'api/engine/initWS'
 
 export const CreateClone = () => {
   const routes = {
@@ -16,17 +18,23 @@ export const CreateClone = () => {
   }
 
   const api = {
-    getSnapshots,
     getInstance,
     getInstanceRetrieval,
     createClone,
     getClone,
+    getBranches,
+    getSnapshots,
+    initWS
   }
 
   const elements = {
     breadcrumbs: (
       <NavPath
-        routes={[ROUTES, ROUTES.INSTANCE, ROUTES.INSTANCE.CLONES.CREATE]}
+        routes={[
+          ROUTES,
+          ROUTES.INSTANCE.CLONES.CLONES,
+          ROUTES.INSTANCE.CLONES.CREATE,
+        ]}
       />
     ),
   }
diff --git a/ui/packages/ce/src/App/Instance/Clones/index.tsx b/ui/packages/ce/src/App/Instance/Clones/index.tsx
index 390f3e11..a39efa94 100644
--- a/ui/packages/ce/src/App/Instance/Clones/index.tsx
+++ b/ui/packages/ce/src/App/Instance/Clones/index.tsx
@@ -1,9 +1,12 @@
 import { Switch, Route, Redirect } from 'react-router-dom'
 
+import { TABS_INDEX } from '@postgres.ai/shared/pages/Instance/Tabs'
+
 import { ROUTES } from 'config/routes'
 
 import { CreateClone } from './CreateClone'
 import { Clone } from './Clone'
+import { Page } from '../Page'
 
 export const Clones = () => {
   return (
@@ -16,6 +19,10 @@ export const Clones = () => {
         <Clone />
       </Route>
 
+      <Route exact path={ROUTES.INSTANCE.CLONES.CLONES.path}>
+        <Page renderCurrentTab={TABS_INDEX.CLONES} />
+      </Route>
+
       <Redirect to={ROUTES.path} />
     </Switch>
   )
diff --git a/ui/packages/ce/src/App/Instance/Configuration/index.tsx b/ui/packages/ce/src/App/Instance/Configuration/index.tsx
new file mode 100644
index 00000000..93981d6c
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Configuration/index.tsx
@@ -0,0 +1,10 @@
+import { TABS_INDEX } from '@postgres.ai/shared/pages/Instance/Tabs'
+import { ROUTES } from 'config/routes'
+import { Route } from 'react-router'
+import { Page } from '../Page'
+
+export const Configuration = () => (
+  <Route exact path={ROUTES.INSTANCE.CONFIGURATION.path}>
+    <Page renderCurrentTab={TABS_INDEX.CONFIGURATION} />
+  </Route>
+)
diff --git a/ui/packages/ce/src/App/Instance/Logs/index.tsx b/ui/packages/ce/src/App/Instance/Logs/index.tsx
new file mode 100644
index 00000000..584494b6
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Logs/index.tsx
@@ -0,0 +1,10 @@
+import { TABS_INDEX } from '@postgres.ai/shared/pages/Instance/Tabs'
+import { ROUTES } from 'config/routes'
+import { Route } from 'react-router'
+import { Page } from '../Page'
+
+export const Logs = () => (
+  <Route exact path={ROUTES.INSTANCE.LOGS.path}>
+    <Page renderCurrentTab={TABS_INDEX.LOGS} />
+  </Route>
+)
diff --git a/ui/packages/ce/src/App/Instance/Page/index.tsx b/ui/packages/ce/src/App/Instance/Page/index.tsx
index 60a92f16..a44b559b 100644
--- a/ui/packages/ce/src/App/Instance/Page/index.tsx
+++ b/ui/packages/ce/src/App/Instance/Page/index.tsx
@@ -6,6 +6,7 @@ import { ROUTES } from 'config/routes'
 import { getInstance } from 'api/instances/getInstance'
 import { getInstanceRetrieval } from 'api/instances/getInstanceRetrieval'
 import { getSnapshots } from 'api/snapshots/getSnapshots'
+import { createSnapshot } from 'api/snapshots/createSnapshot'
 import { destroyClone } from 'api/clones/destroyClone'
 import { resetClone } from 'api/clones/resetClone'
 import { getWSToken } from 'api/engine/getWSToken'
@@ -16,18 +17,33 @@ import { getSeImages } from 'api/configs/getSeImages'
 import { updateConfig } from 'api/configs/updateConfig'
 import { testDbSource } from 'api/configs/testDbSource'
 import { getEngine } from 'api/engine/getEngine'
+import { createBranch } from 'api/branches/createBranch'
+import { getBranches } from 'api/branches/getBranches'
+import { getSnapshotList } from 'api/branches/getSnapshotList'
+import { deleteBranch } from 'api/branches/deleteBranch'
+import { destroySnapshot } from 'api/snapshots/destroySnapshot'
+import { fullRefresh } from 'api/instances/fullRefresh'
 
-export const Page = () => {
+export const Page = ({ renderCurrentTab }: { renderCurrentTab?: number }) => {
   const routes = {
     createClone: () => ROUTES.INSTANCE.CLONES.CREATE.path,
+    createBranch: () => ROUTES.INSTANCE.BRANCHES.CREATE.path,
+    createSnapshot: () => ROUTES.INSTANCE.SNAPSHOTS.CREATE.path,
     clone: (cloneId: string) =>
       ROUTES.INSTANCE.CLONES.CLONE.createPath(cloneId),
+    branch: (branchId: string) =>
+      ROUTES.INSTANCE.BRANCHES.BRANCH.createPath(branchId),
+    branches: () => ROUTES.INSTANCE.BRANCHES.path,
+    snapshots: () => ROUTES.INSTANCE.SNAPSHOTS.path,
+    snapshot: (snapshotId: string) =>
+      ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.createPath(snapshotId),
   }
 
   const api = {
     getInstance,
     getInstanceRetrieval,
     getSnapshots,
+    createSnapshot,
     destroyClone,
     resetClone,
     getWSToken,
@@ -38,6 +54,12 @@ export const Page = () => {
     testDbSource,
     initWS,
     getEngine,
+    createBranch,
+    getBranches,
+    getSnapshotList,
+    deleteBranch,
+    destroySnapshot,
+    fullRefresh,
   }
 
   const elements = {
@@ -52,6 +74,7 @@ export const Page = () => {
         routes={routes}
         api={api}
         elements={elements}
+        renderCurrentTab={renderCurrentTab}
       />
     </PageContainer>
   )
diff --git a/ui/packages/ce/src/App/Instance/Snapshots/CreateSnapshot/index.tsx b/ui/packages/ce/src/App/Instance/Snapshots/CreateSnapshot/index.tsx
new file mode 100644
index 00000000..55598d36
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Snapshots/CreateSnapshot/index.tsx
@@ -0,0 +1,43 @@
+import { createSnapshot } from 'api/snapshots/createSnapshot'
+import { getInstance } from 'api/instances/getInstance'
+import { initWS } from 'api/engine/initWS'
+
+import { CreateSnapshotPage } from '@postgres.ai/shared/pages/CreateSnapshot'
+
+import { PageContainer } from 'components/PageContainer'
+import { NavPath } from 'components/NavPath'
+import { ROUTES } from 'config/routes'
+
+export const CreateSnapshot = () => {
+  const api = {
+    createSnapshot,
+    getInstance,
+    initWS
+  }
+
+  const elements = {
+    breadcrumbs: (
+      <NavPath
+        routes={[
+          ROUTES,
+          ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOTS,
+          ROUTES.INSTANCE.SNAPSHOTS.CREATE,
+        ]}
+      />
+    ),
+  }
+
+  return (
+    <PageContainer>
+      <CreateSnapshotPage
+        instanceId=""
+        api={api}
+        elements={elements}
+        routes={{
+          snapshot: (snapshotId: string) =>
+            ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.createPath(snapshotId),
+        }}
+      />
+    </PageContainer>
+  )
+}
diff --git a/ui/packages/ce/src/App/Instance/Snapshots/Snapshot/index.tsx b/ui/packages/ce/src/App/Instance/Snapshots/Snapshot/index.tsx
new file mode 100644
index 00000000..573a0f32
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Snapshots/Snapshot/index.tsx
@@ -0,0 +1,62 @@
+import { useParams } from 'react-router-dom'
+
+import { SnapshotPage } from '@postgres.ai/shared/pages/Snapshots/Snapshot'
+
+import { NavPath } from 'components/NavPath'
+import { ROUTES } from 'config/routes'
+import { PageContainer } from 'components/PageContainer'
+
+import { destroySnapshot } from 'api/snapshots/destroySnapshot'
+import { getSnapshots } from 'api/snapshots/getSnapshots'
+import { getBranchSnapshot } from 'api/snapshots/getBranchSnapshot'
+import { initWS } from 'api/engine/initWS'
+
+type Params = {
+  snapshotId: string
+}
+
+export const Snapshot = () => {
+  const { snapshotId } = useParams<Params>()
+
+  const api = {
+    destroySnapshot,
+    getSnapshots,
+    getBranchSnapshot,
+    initWS,
+  }
+
+  const elements = {
+    breadcrumbs: (
+      <NavPath
+        routes={[
+          ROUTES,
+          ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOTS,
+          {
+            name: `${ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.name}/${snapshotId}`,
+            path: ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.createPath(snapshotId),
+          },
+        ]}
+      />
+    ),
+  }
+
+  return (
+    <PageContainer>
+      <SnapshotPage
+        instanceId={''}
+        snapshotId={snapshotId}
+        routes={{
+          snapshots: () => ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOTS.path,
+          snapshot: () => ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOTS.path,
+          branch: (branchName: string) =>
+            ROUTES.INSTANCE.BRANCHES.BRANCH.createPath(branchName),
+          clone: (cloneId: string) =>
+            ROUTES.INSTANCE.CLONES.CLONE.createPath(cloneId),
+          createClone: (branchId: string, snapshotId: string) => ROUTES.INSTANCE.CLONES.CREATE.createPath(branchId, snapshotId),
+        }}
+        api={api}
+        elements={elements}
+      />
+    </PageContainer>
+  )
+}
diff --git a/ui/packages/ce/src/App/Instance/Snapshots/index.tsx b/ui/packages/ce/src/App/Instance/Snapshots/index.tsx
new file mode 100644
index 00000000..d1521a6e
--- /dev/null
+++ b/ui/packages/ce/src/App/Instance/Snapshots/index.tsx
@@ -0,0 +1,26 @@
+import { Switch, Route, Redirect } from 'react-router-dom'
+
+import { TABS_INDEX } from '@postgres.ai/shared/pages/Instance/Tabs'
+
+import { ROUTES } from 'config/routes'
+
+import { Page } from '../Page'
+import { Snapshot } from './Snapshot'
+import { CreateSnapshot } from './CreateSnapshot'
+
+export const Snapshots = () => {
+  return (
+    <Switch>
+      <Route exact path={ROUTES.INSTANCE.SNAPSHOTS.CREATE.path}>
+        <CreateSnapshot />
+      </Route>
+      <Route exact path={ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOTS.path}>
+        <Page renderCurrentTab={TABS_INDEX.SNAPSHOTS} />
+      </Route>
+      <Route exact path={ROUTES.INSTANCE.SNAPSHOTS.SNAPSHOT.createPath()}>
+        <Snapshot />
+      </Route>
+      <Redirect to={ROUTES.path} />
+    </Switch>
+  )
+}
diff --git a/ui/packages/ce/src/App/Instance/index.tsx b/ui/packages/ce/src/App/Instance/index.tsx
index 65422988..7c26ba3d 100644
--- a/ui/packages/ce/src/App/Instance/index.tsx
+++ b/ui/packages/ce/src/App/Instance/index.tsx
@@ -2,8 +2,12 @@ import { Switch, Route, Redirect } from 'react-router-dom'
 
 import { ROUTES } from 'config/routes'
 
+import { Logs } from './Logs'
 import { Page } from './Page'
 import { Clones } from './Clones'
+import { Branches } from './Branches'
+import { Snapshots } from './Snapshots'
+import { Configuration } from './Configuration'
 
 export const Instance = () => {
   return (
@@ -14,6 +18,18 @@ export const Instance = () => {
       <Route path={ROUTES.INSTANCE.CLONES.path}>
         <Clones />
       </Route>
+      <Route path={ROUTES.INSTANCE.SNAPSHOTS.path}>
+        <Snapshots />
+      </Route>
+      <Route path={ROUTES.INSTANCE.BRANCHES.path}>
+        <Branches />
+      </Route>
+      <Route path={ROUTES.INSTANCE.LOGS.path}>
+        <Logs />
+      </Route>
+      <Route path={ROUTES.INSTANCE.CONFIGURATION.path}>
+        <Configuration />
+      </Route>
       <Redirect to={ROUTES.path} />
     </Switch>
   )
diff --git a/ui/packages/ce/src/App/Menu/Header/index.tsx b/ui/packages/ce/src/App/Menu/Header/index.tsx
index 8d961cfe..9c74e6da 100644
--- a/ui/packages/ce/src/App/Menu/Header/index.tsx
+++ b/ui/packages/ce/src/App/Menu/Header/index.tsx
@@ -1,14 +1,11 @@
 import cn from 'classnames'
 import { Link } from 'react-router-dom'
 
-import { linksConfig } from '@postgres.ai/shared/config/links'
-import { Button } from '@postgres.ai/shared/components/MenuButton'
-
 import { ROUTES } from 'config/routes'
 
 import styles from './styles.module.scss'
 import { DLEEdition } from 'helpers/edition'
-import { LogoIcon, StarsIcon } from './icons'
+import { LogoIcon } from './icons'
 
 type Props = {
   isCollapsed: boolean
@@ -31,17 +28,6 @@ export const Header = (props: Props) => {
           </h1>
         )}
       </Link>
-
-      {!props.isCollapsed && (
-        <Button
-          type="gateway-link"
-          href={linksConfig.cloudSignIn}
-          className={styles.upgradeBtn}
-        >
-          <StarsIcon className={styles.upgradeBtnIcon} />
-          Go Enterprise
-        </Button>
-      )}
     </header>
   )
 }
diff --git a/ui/packages/ce/src/App/Menu/Header/styles.module.scss b/ui/packages/ce/src/App/Menu/Header/styles.module.scss
index f08de9c0..c60279aa 100644
--- a/ui/packages/ce/src/App/Menu/Header/styles.module.scss
+++ b/ui/packages/ce/src/App/Menu/Header/styles.module.scss
@@ -20,6 +20,7 @@
   height: 32px;
   color: inherit;
   text-decoration: none;
+  align-items: center;
 
   &.collapsed {
     justify-content: center;
diff --git a/ui/packages/ce/src/App/Menu/StickyTopBar/index.tsx b/ui/packages/ce/src/App/Menu/StickyTopBar/index.tsx
index 9f121b16..26649b04 100644
--- a/ui/packages/ce/src/App/Menu/StickyTopBar/index.tsx
+++ b/ui/packages/ce/src/App/Menu/StickyTopBar/index.tsx
@@ -100,7 +100,7 @@ export const StickyTopBar = () => {
             message: 'All DBLab SE features are now active.',
             type: 'success',
           })
-        } else {
+        } else if (res.error?.message) {
           setSnackbarState({
             isOpen: true,
             message: capitalizeFirstLetter(res?.error?.message),
@@ -121,7 +121,7 @@ export const StickyTopBar = () => {
           type: 'billingInactive',
           pageUrl: res.response?.recognized_org.billing_page,
           message:
-            'No active payment methods are found for your organization on the Postgres.ai Platform; please, visit the',
+            'No active payment methods were found for your organization on the Postgres.AI Platform. Please visit the',
         })
       } else if (
         !res.response?.billing_active &&
@@ -139,7 +139,7 @@ export const StickyTopBar = () => {
     const handleNoConnection = () => {
       setState({
         type: 'noConnection',
-        message: 'No internet connection',
+        message: 'No internet connection.',
       })
     }
 
diff --git a/ui/packages/platform/src/api/clones/resetClone.ts b/ui/packages/ce/src/api/branches/createBranch.ts
similarity index 53%
rename from ui/packages/platform/src/api/clones/resetClone.ts
rename to ui/packages/ce/src/api/branches/createBranch.ts
index 4feaebbd..90d38927 100644
--- a/ui/packages/platform/src/api/clones/resetClone.ts
+++ b/ui/packages/ce/src/api/branches/createBranch.ts
@@ -5,25 +5,22 @@
  *--------------------------------------------------------------------------
  */
 
-import { ResetClone } from '@postgres.ai/shared/types/api/endpoints/resetClone'
-
 import { request } from 'helpers/request'
 
-export const resetClone: ResetClone = async (req) => {
-  const response = await request('/rpc/dblab_clone_reset', {
-    method: 'post',
+import { CreateBranchFormValues } from '@postgres.ai/shared/types/api/endpoints/createBranch'
+
+export const createBranch = async (req: CreateBranchFormValues) => {
+  const response = await request('/branch', {
+    method: 'POST',
     body: JSON.stringify({
-      instance_id: req.instanceId,
-      clone_id: req.cloneId,
-      reset_options: {
-        snapshotID: req.snapshotId,
-        latest: false,
-      },
+      branchName: req.branchName,
+      ...(req.baseBranch && { baseBranch: req.baseBranch }),
+      ...(req.snapshotID && { snapshotID: req.snapshotID }),
     }),
   })
 
   return {
-    response: response.ok ? true : null,
+    response: response.ok ? await response.json() : null,
     error: response.ok ? null : response,
   }
 }
diff --git a/ui/packages/platform/src/pages/JoeInstance/Messages/Banner/styles.module.scss b/ui/packages/ce/src/api/branches/deleteBranch.ts
similarity index 54%
rename from ui/packages/platform/src/pages/JoeInstance/Messages/Banner/styles.module.scss
rename to ui/packages/ce/src/api/branches/deleteBranch.ts
index 338f401b..ad019688 100644
--- a/ui/packages/platform/src/pages/JoeInstance/Messages/Banner/styles.module.scss
+++ b/ui/packages/ce/src/api/branches/deleteBranch.ts
@@ -5,19 +5,15 @@
  *--------------------------------------------------------------------------
  */
 
-@import '@postgres.ai/shared/styles/vars';
+import { request } from 'helpers/request'
 
-.root {
-  flex: 0 0 auto;
-  padding: 12px;
-  border-top: 1px solid $color-gray;
-  font-size: $font-size-small;
-  display: flex;
-  flex-direction: column;
-}
+export const deleteBranch = async (branchName: string) => {
+  const response = await request(`/branch/${branchName}`, {
+    method: 'DELETE'
+  })
 
-.content {
-  + .content {
-    margin-top: 12px;
+  return {
+    response: response.ok ? await response.json() : null,
+    error: response.ok ? null : await response.json(),
   }
 }
diff --git a/ui/packages/ce/src/api/branches/getBranches.ts b/ui/packages/ce/src/api/branches/getBranches.ts
new file mode 100644
index 00000000..c8185e23
--- /dev/null
+++ b/ui/packages/ce/src/api/branches/getBranches.ts
@@ -0,0 +1,18 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { request } from 'helpers/request'
+import { formatBranchesDto } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+
+export const getBranches = async () => {
+  const response = await request(`/branches`)
+
+  return {
+    response: response.ok ? formatBranchesDto(await response.json()) : null,
+    error: response.ok ? null : response,
+  }
+}
diff --git a/ui/packages/platform/src/api/cloud/getCloudRegions.ts b/ui/packages/ce/src/api/branches/getSnapshotList.ts
similarity index 66%
rename from ui/packages/platform/src/api/cloud/getCloudRegions.ts
rename to ui/packages/ce/src/api/branches/getSnapshotList.ts
index 80b0ccfc..46cd096d 100644
--- a/ui/packages/platform/src/api/cloud/getCloudRegions.ts
+++ b/ui/packages/ce/src/api/branches/getSnapshotList.ts
@@ -7,16 +7,10 @@
 
 import { request } from 'helpers/request'
 
-export interface CloudRegion {
-  api_name: string
-  cloud_provider: string
-  label: string
-  native_code: string
-  world_part: string
-}
-
-export const getCloudRegions = async (req: string) => {
-  const response = await request(`/cloud_regions?cloud_provider=eq.${req}`)
+export const getSnapshotList = async (branchName: string) => {
+  const response = await request(`/branch/${branchName}/log`, {
+    method: 'GET'
+  })
 
   return {
     response: response.ok ? await response.json() : null,
diff --git a/ui/packages/ce/src/api/clones/createClone.ts b/ui/packages/ce/src/api/clones/createClone.ts
index 5ca1f168..e3fbacd1 100644
--- a/ui/packages/ce/src/api/clones/createClone.ts
+++ b/ui/packages/ce/src/api/clones/createClone.ts
@@ -15,6 +15,7 @@ export const createClone: CreateClone = async (req) => {
         id: req.snapshotId,
       },
       protected: req.isProtected,
+      ...(req.branch && { branch: req.branch }),
       db: {
         username: req.dbUser,
         password: req.dbPassword,
diff --git a/ui/packages/ce/src/api/configs/updateConfig.ts b/ui/packages/ce/src/api/configs/updateConfig.ts
index 9c40b4f1..093c11f3 100644
--- a/ui/packages/ce/src/api/configs/updateConfig.ts
+++ b/ui/packages/ce/src/api/configs/updateConfig.ts
@@ -1,7 +1,7 @@
 import {
   postUniqueCustomOptions,
   postUniqueDatabases,
-} from '@postgres.ai/shared/pages/Configuration/utils'
+} from '@postgres.ai/shared/pages/Instance/Configuration/utils'
 import { Config } from '@postgres.ai/shared/types/api/entities/config'
 import { request } from 'helpers/request'
 
diff --git a/ui/packages/ce/src/api/instances/fullRefresh.ts b/ui/packages/ce/src/api/instances/fullRefresh.ts
new file mode 100644
index 00000000..bf63b240
--- /dev/null
+++ b/ui/packages/ce/src/api/instances/fullRefresh.ts
@@ -0,0 +1,22 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { request } from 'helpers/request'
+import { FullRefresh } from "@postgres.ai/shared/types/api/endpoints/fullRefresh";
+
+export const fullRefresh: FullRefresh = async () => {
+  const response = await request('/full-refresh', {
+    method: "POST",
+  })
+
+  const result = response.ok ? await response.json() : null
+
+  return {
+    response: result,
+    error: response.ok ? null : response,
+  }
+}
diff --git a/ui/packages/platform/src/api/clones/destroyClone.ts b/ui/packages/ce/src/api/snapshots/createSnapshot.ts
similarity index 60%
rename from ui/packages/platform/src/api/clones/destroyClone.ts
rename to ui/packages/ce/src/api/snapshots/createSnapshot.ts
index 96ebae5b..212d6245 100644
--- a/ui/packages/platform/src/api/clones/destroyClone.ts
+++ b/ui/packages/ce/src/api/snapshots/createSnapshot.ts
@@ -5,21 +5,21 @@
  *--------------------------------------------------------------------------
  */
 
-import { DestroyClone } from '@postgres.ai/shared/types/api/endpoints/destroyClone'
+import { CreateSnapshot } from '@postgres.ai/shared/types/api/endpoints/createSnapshot'
 
 import { request } from 'helpers/request'
 
-export const destroyClone: DestroyClone = async (req) => {
-  const response = await request('/rpc/dblab_clone_destroy', {
+export const createSnapshot: CreateSnapshot = async (cloneId, message) => {
+  const response = await request(`/branch/snapshot`, {
     method: 'POST',
     body: JSON.stringify({
-      instance_id: req.instanceId,
-      clone_id: req.cloneId,
+      cloneID: cloneId,
+      ...(message && { message: message }),
     }),
   })
 
   return {
-    response: response.ok ? true : null,
+    response: response.ok ? await response.json() : null,
     error: response.ok ? null : response,
   }
 }
diff --git a/ui/packages/platform/src/api/instances/refreshInstance.ts b/ui/packages/ce/src/api/snapshots/destroySnapshot.ts
similarity index 61%
rename from ui/packages/platform/src/api/instances/refreshInstance.ts
rename to ui/packages/ce/src/api/snapshots/destroySnapshot.ts
index 92777110..b076444f 100644
--- a/ui/packages/platform/src/api/instances/refreshInstance.ts
+++ b/ui/packages/ce/src/api/snapshots/destroySnapshot.ts
@@ -5,16 +5,11 @@
  *--------------------------------------------------------------------------
  */
 
-import { RefreshInstance } from '@postgres.ai/shared/types/api/endpoints/refreshInstance'
-
 import { request } from 'helpers/request'
 
-export const refreshInstance: RefreshInstance = async (req) => {
-  const response = await request('/rpc/dblab_instance_status_refresh', {
-    method: 'post',
-    body: JSON.stringify({
-      instance_id: req.instanceId,
-    }),
+export const destroySnapshot = async (snapshotId: string, forceDelete: boolean) => {
+  const response = await request(`/snapshot/${snapshotId}?force=${forceDelete}`, {
+    method: 'DELETE'
   })
 
   return {
diff --git a/ui/packages/platform/src/api/cloud/getCloudProviders.ts b/ui/packages/ce/src/api/snapshots/getBranchSnapshot.ts
similarity index 75%
rename from ui/packages/platform/src/api/cloud/getCloudProviders.ts
rename to ui/packages/ce/src/api/snapshots/getBranchSnapshot.ts
index a46983dd..26f0e2ce 100644
--- a/ui/packages/platform/src/api/cloud/getCloudProviders.ts
+++ b/ui/packages/ce/src/api/snapshots/getBranchSnapshot.ts
@@ -7,13 +7,8 @@
 
 import { request } from 'helpers/request'
 
-export interface CloudProvider {
-  api_name: string
-  label: string
-}
-
-export const getCloudProviders = async () => {
-  const response = await request('/cloud_providers')
+export const getBranchSnapshot = async (snapshotId: string) => {
+  const response = await request(`/branch/snapshot/${snapshotId}`)
 
   return {
     response: response.ok ? await response.json() : null,
diff --git a/ui/packages/ce/src/api/snapshots/getSnapshots.ts b/ui/packages/ce/src/api/snapshots/getSnapshots.ts
index d9ae5fb4..b26788eb 100644
--- a/ui/packages/ce/src/api/snapshots/getSnapshots.ts
+++ b/ui/packages/ce/src/api/snapshots/getSnapshots.ts
@@ -13,7 +13,8 @@ import {
 import { request } from 'helpers/request'
 
 export const getSnapshots: GetSnapshots = async (req) => {
-  const response = await request('/snapshots')
+  const url = `/snapshots${req.branchName ? `?branch=${req.branchName}` : ''}`;
+  const response = await request(url);
 
   return {
     response: response.ok
diff --git a/ui/packages/ce/src/components/NavPath/index.tsx b/ui/packages/ce/src/components/NavPath/index.tsx
index 1b69baaa..c999e62d 100644
--- a/ui/packages/ce/src/components/NavPath/index.tsx
+++ b/ui/packages/ce/src/components/NavPath/index.tsx
@@ -19,6 +19,7 @@ export const NavPath = (props: Props) => {
     <nav className={cn(styles.root, props.className)}>
       {props.routes.map((route, i) => {
         const isLast = (i + 1) === props.routes.length
+        const nameWithIndent = route.name.replace('/', ' / ')
 
         return (
           <React.Fragment key={i}>
@@ -28,7 +29,7 @@ export const NavPath = (props: Props) => {
               className={styles.link}
               activeClassName={styles.active}
             >
-              {route.name}
+              {nameWithIndent}
             </NavLink>
             { !isLast && <span className={styles.divider}>/</span> }
           </React.Fragment>
diff --git a/ui/packages/ce/src/config/routes.tsx b/ui/packages/ce/src/config/routes.tsx
index 47e29a6d..ab5df453 100644
--- a/ui/packages/ce/src/config/routes.tsx
+++ b/ui/packages/ce/src/config/routes.tsx
@@ -1,5 +1,5 @@
 export const ROUTES = {
-  name: 'Database Lab',
+  name: 'Database Lab Engine',
   path: '/',
 
   AUTH: {
@@ -11,12 +11,82 @@ export const ROUTES = {
     path: `/instance`,
     name: 'Instance',
 
+    CONFIGURATION: {
+      name: 'Configuration',
+      path: `/instance/configuration`,
+    },
+
+    LOGS: {
+      name: 'Logs',
+      path: `/instance/logs`,
+    },
+    
+    SNAPSHOTS: {
+      path: `/instance/snapshots`,
+
+      CREATE: {
+        name: 'Create snapshot',
+        path: `/instance/snapshots/create`,
+        createPath: (cloneId?: string) => {
+          const params = new URLSearchParams();
+          if (cloneId) params.set('clone_id', cloneId);
+
+          const query = params.toString();
+          return `/instance/snapshots/create${query ? `?${query}` : ''}`;
+        }
+      },
+
+      SNAPSHOTS: {
+        name: 'Snapshots',
+        path: `/instance/snapshots`,
+      },
+
+      SNAPSHOT: {
+        name: 'Snapshot',
+        createPath: (snapshotId = ':snapshotId') =>
+          `/instance/snapshots/${snapshotId}`,
+      },
+    },
+    BRANCHES: {
+      path: `/instance/branches`,
+
+      CREATE: {
+        name: 'Create branch',
+        path: `/instance/branches/create`,
+      },
+
+      BRANCHES: {
+        name: 'Branches',
+        path: `/instance/branches`,
+      },
+
+      BRANCH: {
+        name: 'Branches',
+        createPath: (branchId = ':branchId') =>
+          `/instance/branches/${branchId}`,
+      },
+    },
+
     CLONES: {
       path: `/instance/clones`,
 
       CREATE: {
         name: 'Create clone',
         path: `/instance/clones/create`,
+        createPath: (branchId?: string, snapshotId?: string) => {
+          const params = new URLSearchParams();
+
+          if (branchId) params.set('branch_id', branchId);
+          if (snapshotId) params.set('snapshot_id', snapshotId);
+
+          const query = params.toString();
+          return `/instance/clones/create${query ? `?${query}` : ''}`;
+        }
+      },
+
+      CLONES: {
+        name: 'Clones',
+        path: `/instance/clones`,
       },
 
       CLONE: {
diff --git a/ui/packages/platform/.dockerignore b/ui/packages/platform/.dockerignore
deleted file mode 100644
index 19d960ff..00000000
--- a/ui/packages/platform/.dockerignore
+++ /dev/null
@@ -1,10 +0,0 @@
-**/node_modules/**
-/.vscode/
-/.idea/
-/bin/
-/.git/
-**/build/**
-/ui/node_modules/
-/ui/packages/ce/node_modules/
-/ui/packages/shared/node_modules/
-/ui/packages/platform/node_modules/
diff --git a/ui/packages/platform/.env_example_dev b/ui/packages/platform/.env_example_dev
deleted file mode 100644
index 53fc9fd8..00000000
--- a/ui/packages/platform/.env_example_dev
+++ /dev/null
@@ -1,3 +0,0 @@
-REACT_APP_API_URL_PREFIX=https://v2.postgres.ai/api/general
-REACT_APP_TOKEN_DEBUG=__YOUR_JWT_TOKEN_FROM_STAGING__
-REACT_APP_WS_URL=ws://127.0.0.1:9100/
\ No newline at end of file
diff --git a/ui/packages/platform/.eslintrc b/ui/packages/platform/.eslintrc
deleted file mode 100644
index 4cc3d5ca..00000000
--- a/ui/packages/platform/.eslintrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "root": true,
-  "extends": "react-app",
-  "rules": {
-    "@typescript-eslint/no-explicit-any": "error"
-  }
-}
diff --git a/ui/packages/platform/.gitignore b/ui/packages/platform/.gitignore
deleted file mode 100644
index 4c49bd78..00000000
--- a/ui/packages/platform/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.env
diff --git a/ui/packages/platform/.gitlab-ci.yml b/ui/packages/platform/.gitlab-ci.yml
deleted file mode 100644
index 79ca948e..00000000
--- a/ui/packages/platform/.gitlab-ci.yml
+++ /dev/null
@@ -1,131 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-# Conditions.
-.only_ui_tag_release: &only_ui_tag_release
-  rules:
-    - if: $CI_COMMIT_TAG =~ /^ui\/[0-9.]+$/
-
-.only_ui_staging: &only_ui_staging
-  rules:
-    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
-      changes:
-        - ui/packages/platform/**/*
-        - ui/packages/shared/**/*
-
-.only_ui_feature: &only_ui_feature
-  rules:
-    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
-      changes:
-        - ui/packages/platform/**/*
-        - ui/packages/shared/**/*
-      when: manual
-
-.ui_cache: &ui_cache
-  image: node:lts-alpine
-  cache:
-    key: "$CI_COMMIT_REF_SLUG"
-    paths:
-      - .pnpm-store
-    policy: pull
-
-# Environments.
-.environment_production: &env_production
-  environment: 
-    name: production
-    url: https://postgres.ai
-  variables:
-    ENV: production
-    NAMESPACE: production
-    DOCKER_NAME: "gcr.io/postgres-ai/platform-web/cloud"
-  before_script:
-    - export UI_VERSION=$(echo ${CI_COMMIT_TAG#"ui/"})
-    - export TAG="${DOCKER_NAME}:${UI_VERSION}-${CI_PIPELINE_IID}"
-
-.environment_staging: &env_staging
-  environment: 
-    name: staging
-    url: https://console-v2.postgres.ai
-  variables:
-    ENV: staging
-    NAMESPACE: staging
-    DOCKER_NAME: "gcr.io/postgres-ai/platform-web/cloud"
-    TAG: "${DOCKER_NAME}:${NAMESPACE}-${CI_PIPELINE_IID}"
-
-.environment_dev: &env_dev
-  environment:
-    name: dev
-    url: https://console-dev.postgres.ai
-  variables:
-    ENV: dev
-    NAMESPACE: dev
-    DOCKER_NAME: "gcr.io/postgres-ai/platform-web/cloud"
-    TAG: "${DOCKER_NAME}:${NAMESPACE}-${CI_PIPELINE_IID}"
-
-# Jobs templates.
-.build_definition: &build_definition
-  <<: *ui_cache
-  stage: build
-  image: docker:20.10.12
-  services:
-    - docker:dind
-  script:
-    - apk add --no-cache bash
-    - bash ./ui/packages/platform/ci_docker_build_push.sh
-  needs:
-    - job: check-code-style
-      artifacts: false
-
-.deploy_definition: &deploy_definition
-  stage: deploy
-  image: dtzar/helm-kubectl:2.14.1
-  script:
-    # Substitute env variables in deploy config.
-    - bash ./ui/packages/platform/do.sh subs_envs ./ui/packages/platform/deploy/platform-console.yaml /tmp/platform-console.yaml
-    # Context
-    - kubectl config get-contexts
-    - kubectl config use-context postgres-ai/database-lab:k8s-cluster-1
-    # Deploy to k8s cluster.
-    - kubectl apply --filename /tmp/platform-console.yaml -n $NAMESPACE
-
-# Jobs.
-# Production.
-ui_build_platform_image_tag_release:
-  <<: *env_production
-  <<: *only_ui_tag_release
-  <<: *build_definition
-
-ui_deploy_platform_image_tag_release:
-  <<: *env_production
-  <<: *only_ui_tag_release
-  <<: *deploy_definition
-
-# Staging.
-ui_build_platform_image_staging:
-  <<: *env_staging
-  <<: *only_ui_staging
-  <<: *build_definition
-
-ui_deploy_platform_image_staging:
-  <<: *env_staging
-  <<: *only_ui_staging
-  <<: *deploy_definition
-
-# Dev.
-ui_build_platform_image_dev:
-  <<: *env_dev
-  <<: *only_ui_feature
-  <<: *build_definition
-  allow_failure: true # Workaround: https://gitlab.com/gitlab-org/gitlab/-/issues/20237
-
-ui_deploy_platform_image_dev:
-  <<: *env_dev
-  <<: *only_ui_feature
-  <<: *deploy_definition
-  allow_failure: true
-  needs:
-    - ui_build_platform_image_dev
diff --git a/ui/packages/platform/.npmrc b/ui/packages/platform/.npmrc
deleted file mode 100644
index 4c2f52b3..00000000
--- a/ui/packages/platform/.npmrc
+++ /dev/null
@@ -1,2 +0,0 @@
-auto-install-peers=true
-strict-peer-dependencies=false
diff --git a/ui/packages/platform/.stylelintrc b/ui/packages/platform/.stylelintrc
deleted file mode 100644
index f623bb4d..00000000
--- a/ui/packages/platform/.stylelintrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "extends": "stylelint-config-sass-guidelines",
-  "rules": {
-    "selector-class-pattern": "^[a-z][a-zA-Z0-9]+$",
-    "order/order": null,
-		"order/properties-alphabetical-order": null
-  }
-}
diff --git a/ui/packages/platform/COPYRIGHT b/ui/packages/platform/COPYRIGHT
deleted file mode 100644
index f41576fe..00000000
--- a/ui/packages/platform/COPYRIGHT
+++ /dev/null
@@ -1,6 +0,0 @@
--------------------------------------------------------------------------
-Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-All Rights Reserved
-Unauthorized copying of this file, via any medium is strictly prohibited
-Proprietary and confidential
--------------------------------------------------------------------------
diff --git a/ui/packages/platform/Dockerfile b/ui/packages/platform/Dockerfile
deleted file mode 100644
index 7367bfb0..00000000
--- a/ui/packages/platform/Dockerfile
+++ /dev/null
@@ -1,67 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-# Build phase.
-FROM node:16.14-alpine as build
-
-WORKDIR /app
-
-COPY ./ui/ .
-
-# RUN --mount=type=bind,id=pnpm,source=.pnpm-store,target=/app/.pnpm-store
-
-ARG ARG_REACT_APP_API_SERVER
-ENV REACT_APP_API_SERVER=$ARG_REACT_APP_API_SERVER
-
-ARG ARG_PUBLIC_URL
-ENV PUBLIC_URL=$ARG_PUBLIC_URL
-
-ARG ARG_REACT_APP_SIGNIN_URL
-ENV REACT_APP_SIGNIN_URL=$ARG_REACT_APP_SIGNIN_URL
-
-ARG ARG_REACT_APP_AUTH_URL
-ENV REACT_APP_AUTH_URL=$ARG_REACT_APP_AUTH_URL
-
-ARG ARG_REACT_APP_ROOT_URL
-ENV REACT_APP_ROOT_URL=$ARG_REACT_APP_ROOT_URL
-
-ARG ARG_REACT_APP_WS_SERVER
-ENV REACT_APP_WS_SERVER=$ARG_REACT_APP_WS_SERVER
-
-ARG ARG_REACT_APP_EXPLAIN_DEPESZ_SERVER
-ENV REACT_APP_EXPLAIN_DEPESZ_SERVER=$ARG_REACT_APP_EXPLAIN_DEPESZ_SERVER
-
-ARG ARG_REACT_APP_EXPLAIN_PEV2_SERVER
-ENV REACT_APP_EXPLAIN_PEV2_SERVER=$ARG_REACT_APP_EXPLAIN_PEV2_SERVER
-
-ARG ARG_REACT_APP_STRIPE_API_KEY
-ENV REACT_APP_STRIPE_API_KEY=$ARG_REACT_APP_STRIPE_API_KEY
-
-ARG ARG_REACT_APP_SENTRY_DSN
-ENV REACT_APP_SENTRY_DSN=$ARG_REACT_APP_SENTRY_DSN
-
-ARG ARG_REACT_APP_WS_URL
-ENV REACT_APP_WS_URL=$ARG_REACT_APP_WS_URL
-
-ARG ARG_REACT_APP_BOT_API_URL
-ENV REACT_APP_BOT_API_URL=$ARG_REACT_APP_BOT_API_URL
-
-RUN apk add --no-cache --update git && \
-    npm i -g pnpm@7.30.5; \
-    pnpm config set store-dir /app/.pnpm-store; \
-    pnpm set verify-store-integrity false; \
-    pnpm --filter @postgres.ai/platform i; \
-    pnpm --filter @postgres.ai/platform build
-
-# Run phase.
-FROM nginx:1.20.1-alpine as run
-
-COPY --from=build /app/packages/platform/build /srv/platform
-COPY ./ui/packages/platform/nginx.conf /etc/nginx/conf.d/platform.conf
-RUN rm -rf /etc/nginx/conf.d/default.conf
-
-CMD ["nginx", "-g", "daemon off;"]
diff --git a/ui/packages/platform/README.md b/ui/packages/platform/README.md
index 3e1e6cbd..d3d4fed3 100644
--- a/ui/packages/platform/README.md
+++ b/ui/packages/platform/README.md
@@ -1,6 +1,12 @@
-# platform-console
+# Platform Console UI
 
-To start:
+This is the Platform version of the Database Lab Engine UI.
+
+For detailed information about UI development, please refer to the main [CONTRIBUTING.md](../../../CONTRIBUTING.md#ui-development) file. The documentation has been centralized to ensure consistent and up-to-date information.
+
+## Quick Reference
+
+### Start Development Server
 Specify REST API server URL with `REACT_APP_API_SERVER` environment variable.
 
 ```
@@ -9,9 +15,7 @@ npm run build
 npm run start
 ```
 
-# Q&A
-
-## `meta.json` is missing, what to do?
-Run `npm run build`.
+## Troubleshooting
 
-<!-- TODO: move this to the main README.md and CONTRIBUTING.md -->
\ No newline at end of file
+### `meta.json` is missing, what to do?
+Run `npm run build`.
\ No newline at end of file
diff --git a/ui/packages/platform/ci_docker_build_push.sh b/ui/packages/platform/ci_docker_build_push.sh
deleted file mode 100644
index b18aec0b..00000000
--- a/ui/packages/platform/ci_docker_build_push.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/bash
-
-set -euo pipefail
-
-docker_file=${DOCKER_FILE:-""}
-tags=${TAG:-""}
-
-# Docker login for GCP.
-echo $GCP_SERVICE_ACCOUNT | base64 -d > ./key.json
-docker login -u _json_key --password-stdin https://gcr.io < ./key.json
-
-tags_build=""
-tags_push=""
-
-IFS=',' read -ra ADDR string <<EOF
-$tags
-EOF
-
-for tag in "${ADDR[@]}"; do
-  tags_build="${tags_build} --tag ${tag}"
-  tags_push="${tags_push}${tag}\n"
-done
-
-# Set envs before building the container image, because env vars
-# will not be available in user's browser.
-source "./ui/packages/platform/deploy/configs/${NAMESPACE}.sh"
-
-set -x
-DOCKER_BUILDKIT=1 docker build \
-      --build-arg ARG_REACT_APP_API_SERVER="${REACT_APP_API_SERVER}" \
-      --build-arg ARG_PUBLIC_URL="${PUBLIC_URL}" \
-      --build-arg ARG_REACT_APP_SIGNIN_URL="${REACT_APP_SIGNIN_URL}" \
-      --build-arg ARG_REACT_APP_WS_SERVER="${REACT_APP_WS_SERVER}" \
-      --build-arg ARG_REACT_APP_EXPLAIN_DEPESZ_SERVER="${REACT_APP_EXPLAIN_DEPESZ_SERVER}" \
-      --build-arg ARG_REACT_APP_EXPLAIN_PEV2_SERVER="${REACT_APP_EXPLAIN_PEV2_SERVER}" \
-      --build-arg ARG_REACT_APP_STRIPE_API_KEY="${REACT_APP_STRIPE_API_KEY}" \
-      --build-arg ARG_REACT_APP_AUTH_URL="${REACT_APP_AUTH_URL}" \
-      --build-arg ARG_REACT_APP_ROOT_URL="${REACT_APP_ROOT_URL}" \
-      --build-arg ARG_REACT_APP_SENTRY_DSN="${REACT_APP_SENTRY_DSN}" \
-      --build-arg ARG_REACT_APP_WS_URL="${REACT_APP_WS_URL}" \
-      --build-arg ARG_REACT_APP_BOT_API_URL="${REACT_APP_BOT_API_URL}" \
-      $tags_build --file ./ui/packages/platform/Dockerfile .
-set +x
-
-echo -e "$tags_push" | while read -r tag; do
-  [ -z "$tag" ] && continue
-  set -x
-  docker push $tag
-  set +x
-done
diff --git a/ui/packages/platform/craco.config.js b/ui/packages/platform/craco.config.js
deleted file mode 100644
index 5f23b16b..00000000
--- a/ui/packages/platform/craco.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const cracoConfig = require('../shared/craco.config');
-
-module.exports = cracoConfig;
diff --git a/ui/packages/platform/deploy/configs/dev.sh b/ui/packages/platform/deploy/configs/dev.sh
deleted file mode 100644
index 91b90ca8..00000000
--- a/ui/packages/platform/deploy/configs/dev.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-export PORT=3000
-export REPLICAS=1
-export REACT_APP_API_SERVER="https://v2.postgres.ai/api/general"
-export REACT_APP_WS_SERVER="wss://v2.postgres.ai/websockets"
-export REACT_APP_SIGNIN_URL="https://console-dev.postgres.ai/signin"
-export REACT_APP_EXPLAIN_DEPESZ_SERVER="https://explain-depesz.postgres.ai/"
-export REACT_APP_EXPLAIN_PEV2_SERVER="https://v2.postgres.ai/explain-pev2/"
-export REACT_APP_AUTH_URL="https://v2.postgres.ai/api/auth-dev"
-export REACT_APP_ROOT_URL="https://v2.postgres.ai"
-export PUBLIC_URL=""
-
-# Public Stripe key, it is ok to keep it here.
-export REACT_APP_STRIPE_API_KEY="xxx"
-export REACT_APP_SENTRY_DSN=""
-
-# AI Bot
-# don't forget trailing slash if GET path is used!
-export REACT_APP_WS_URL="wss://v2.postgres.ai/ai-bot-ws/" # we use staging for now, since dev env is not fullfledged yet
-export REACT_APP_BOT_API_URL="https://v2.postgres.ai/ai-bot-api/bot"
\ No newline at end of file
diff --git a/ui/packages/platform/deploy/configs/dev1.imgdata.ru.sh b/ui/packages/platform/deploy/configs/dev1.imgdata.ru.sh
deleted file mode 100644
index 6755ca16..00000000
--- a/ui/packages/platform/deploy/configs/dev1.imgdata.ru.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-export PORT=3000
-export REPLICAS=1
-export REACT_APP_API_SERVER="http://dev1.imgdata.ru:9508"
-export REACT_APP_WS_SERVER="ws://dev1.imgdata.ru:9509"
-export REACT_APP_SIGNIN_URL="http://dev1.imgdata.ru:5000"
-export REACT_APP_EXPLAIN_DEPESZ_SERVER="https://explain-depesz.postgres.ai/"
-export REACT_APP_EXPLAIN_PEV2_SERVER="https://postgres.ai/explain-pev2/"
-export REACT_APP_AUTH_URL="http://localhost:3001"
-export REACT_APP_ROOT_URL="https://postgres.ai"
-export PUBLIC_URL=""
-
-# Public Stripe key, it is ok to keep it here.
-export REACT_APP_STRIPE_API_KEY="xxx"
-export REACT_APP_SENTRY_DSN=""
diff --git a/ui/packages/platform/deploy/configs/local.sh b/ui/packages/platform/deploy/configs/local.sh
deleted file mode 100644
index d8d687dc..00000000
--- a/ui/packages/platform/deploy/configs/local.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-export PORT=3000
-export REPLICAS=1
-export REACT_APP_API_SERVER="http://localhost:9508"
-export REACT_APP_WS_SERVER="ws://localhost:9509"
-export REACT_APP_SIGNIN_URL="http://localhost:3000"
-export REACT_APP_EXPLAIN_DEPESZ_SERVER="https://explain-depesz.postgres.ai/"
-export REACT_APP_EXPLAIN_PEV2_SERVER="https://postgres.ai/explain-pev2/"
-export REACT_APP_AUTH_URL="http://localhost:3001"
-export REACT_APP_ROOT_URL="https://postgres.ai"
-export PUBLIC_URL=""
-
-# Public Stripe key, it is ok to keep it here.
-export REACT_APP_STRIPE_API_KEY="xxx"
-export REACT_APP_SENTRY_DSN=""
-
-# AI Bot
-export REACT_APP_WS_URL="ws://localhost:9100/"  # don't forget trailing slash if GET path is used!
-export REACT_APP_BOT_API_URL="https://v2.postgres.ai/ai-bot-api/bot"
\ No newline at end of file
diff --git a/ui/packages/platform/deploy/configs/production.sh b/ui/packages/platform/deploy/configs/production.sh
deleted file mode 100644
index ba5426b9..00000000
--- a/ui/packages/platform/deploy/configs/production.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-export PORT=3000
-export REPLICAS=1
-export REACT_APP_API_SERVER="https://postgres.ai/api/general"
-export REACT_APP_WS_SERVER="wss://postgres.ai/websockets"
-export REACT_APP_SIGNIN_URL="http://console.postgres.ai/signin"
-export REACT_APP_EXPLAIN_DEPESZ_SERVER="https://explain-depesz.postgres.ai/"
-export REACT_APP_EXPLAIN_PEV2_SERVER="https://postgres.ai/explain-pev2"
-export REACT_APP_AUTH_URL="https://postgres.ai/api/auth"
-export REACT_APP_ROOT_URL="https://postgres.ai"
-export PUBLIC_URL=""
-
-# Public Stripe key, it is ok to keep it here.
-export REACT_APP_STRIPE_API_KEY="xxx"
-
-# Sentry.
-export REACT_APP_SENTRY_DSN="https://91517477289e477cb8880f2f07a82632@sentry.postgres.ai/2"
-
-# AI Bot
-export REACT_APP_WS_URL="wss://postgres.ai/ai-bot-ws/" # don't forget trailing slash!
-export REACT_APP_BOT_API_URL="https://postgres.ai/ai-bot-api/bot"
\ No newline at end of file
diff --git a/ui/packages/platform/deploy/configs/staging.sh b/ui/packages/platform/deploy/configs/staging.sh
deleted file mode 100644
index c1a250ab..00000000
--- a/ui/packages/platform/deploy/configs/staging.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-export PORT=3000
-export REPLICAS=1
-export REACT_APP_API_SERVER="https://v2.postgres.ai/api/general"
-export REACT_APP_WS_SERVER="wss://v2.postgres.ai/websockets"
-export REACT_APP_SIGNIN_URL="https://console-v2.postgres.ai/signin"
-export REACT_APP_EXPLAIN_DEPESZ_SERVER="https://explain-depesz.postgres.ai/"
-export REACT_APP_EXPLAIN_PEV2_SERVER="https://v2.postgres.ai/explain-pev2/"
-export REACT_APP_AUTH_URL="https://v2.postgres.ai/api/auth"
-export REACT_APP_ROOT_URL="https://v2.postgres.ai"
-export PUBLIC_URL=""
-
-# Public Stripe key, it is ok to keep it here.
-export REACT_APP_STRIPE_API_KEY="xxx"
-export REACT_APP_SENTRY_DSN=""
-
-# AI Bot
-export REACT_APP_WS_URL="wss://v2.postgres.ai/ai-bot-ws/" # don't forget trailing slash!
-export REACT_APP_BOT_API_URL="https://v2.postgres.ai/ai-bot-api/bot"
\ No newline at end of file
diff --git a/ui/packages/platform/deploy/platform-console.yaml b/ui/packages/platform/deploy/platform-console.yaml
deleted file mode 100644
index 1db73300..00000000
--- a/ui/packages/platform/deploy/platform-console.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
-  name: platform-console
-  labels:
-    app: platform-console
-spec:
-  type: ClusterIP
-  ports:
-  - port: 80
-    targetPort: $PORT
-  selector:
-    app: platform-console
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  labels:
-    app: platform-console
-  name: platform-console
-spec:
-  replicas: $REPLICAS
-  selector:
-    matchLabels:
-      app: platform-console
-  template:
-    metadata:
-      labels:
-        app: platform-console
-    spec:
-      containers:
-      - name: platform-console
-        image: $TAG
-        imagePullPolicy: Always
-        resources:
-          requests:
-            cpu: 100m
-            memory: 100Mi
-        env:
-          - name: PORT
-            value: "$PORT"
-          - name: REACT_APP_API_SERVER
-            value: "$REACT_APP_API_SERVER"
-          - name: REACT_APP_WS_SERVER
-            value: "$REACT_APP_WS_SERVER"
-          - name: REACT_APP_EXPLAIN_DEPESZ_SERVER
-            value: "$REACT_APP_EXPLAIN_DEPESZ_SERVER"
-          - name: REACT_APP_STRIPE_API_KEY
-            value: "$REACT_APP_STRIPE_API_KEY"
-          - name: REACT_APP_WS_URL
-            value: "$REACT_APP_WS_URL"
-          - name: REACT_APP_BOT_API_URL
-            value: "$REACT_APP_BOT_API_URL"
diff --git a/ui/packages/platform/do.sh b/ui/packages/platform/do.sh
deleted file mode 100644
index cc2cbc22..00000000
--- a/ui/packages/platform/do.sh
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/bin/bash
-
-#--------------------------------------------------------------------------
-# Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-# All Rights Reserved
-# Unauthorized copying of this file, via any medium is strictly prohibited
-# Proprietary and confidential
-#--------------------------------------------------------------------------
-
-set -x
-
-install_gettext() {
-  if [[ "$OSTYPE" == "darwin"* ]]; then # macOS.
-    brew install gettext
-    brew link --force gettext
-  else  # Linux
-    if [[ "$(command -v apk)" != "" ]]; then
-      apk add gettext
-    elif [[ "$(command -v apt-get)" != "" ]]; then
-      apt-get update
-      apt-get install gettext-base
-    else
-      echo "Unsupported OS!"
-      exit 1
-    fi
-  fi
-}
-
-subs_envs() {
-  rm -f $2
-
-  if [[ "$(command -v envsubst)" == "" ]]; then
-    install_gettext
-  fi
-
-  # Import additional envs from file specified in second arg.
-  if [ ! -z ${ENV+x} ]; then
-    source "./ui/packages/platform/deploy/configs/${ENV}.sh"
-  fi
-
-  cat $1 | envsubst > $2
-}
-
-is_command_defined() {
-    type $1 2>/dev/null | grep -q 'is a function'
-}
-
-# Parse command and arguments.
-COMMAND=$1
-shift
-ARGUMENTS=${@}
-
-# Run command.
-is_command_defined $COMMAND
-if [ $? -eq 0 ]; then
-  $COMMAND $ARGUMENTS
-else
-  echo "Command not found"
-fi
diff --git a/ui/packages/platform/nginx.conf b/ui/packages/platform/nginx.conf
deleted file mode 100644
index 3b65a273..00000000
--- a/ui/packages/platform/nginx.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-server {
-    listen 3000;
-    server_name localhost;
-    root /srv/platform;
-
-    # X-Frame-Options is to prevent from clickJacking attack.
-    # Makes impossible to use website in iframe.
-    add_header X-Frame-Options SAMEORIGIN;
-
-    # Disable content-type sniffing on some browsers.
-    # Handle files strictly according to their mime types.
-    add_header X-Content-Type-Options nosniff;
-
-    # Disable sending refferer to the downgraded security level.
-    # Example: https -> http.
-    add_header Referrer-Policy 'no-referrer-when-downgrade';
-
-    # Enable gzip compression only for static files.
-    gzip_static on;
-
-    # Enables response header of "Vary: Accept-Encoding".
-    # It allows to serve both versions: compressed and not.
-    gzip_vary on;
-
-    location / {
-        # No-cache doesn’t mean “don’t cache”, it means it must revalidate with the server before using the cached resource.
-        add_header Cache-Control 'no-cache';
-
-        # Enable entity tag to revalidate cache.  
-        etag on;
-
-        # Serve files.
-        try_files $uri $uri/ /index.html;
-    }
-
-    location /static {
-        # This content can be cached as by user as by CDN's.
-        add_header Cache-Control 'public';
-
-        # Cache will be fresh for next 1 year.
-        expires 1y;
-
-        # Disable logging static files requests.
-        access_log off;
-
-        # Serve files.
-        try_files $uri $uri/;
-    }
-}
diff --git a/ui/packages/platform/package.json b/ui/packages/platform/package.json
deleted file mode 100644
index fcdb53d2..00000000
--- a/ui/packages/platform/package.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
-  "name": "@postgres.ai/platform",
-  "version": "1.0.0",
-  "private": true,
-  "dependencies": {
-    "@craco/craco": "^6.4.3",
-    "@emotion/cache": "^11.10.5",
-    "@emotion/react": "^11.10.5",
-    "@emotion/server": "^11.10.0",
-    "@emotion/styled": "^11.10.5",
-    "@juggle/resize-observer": "^3.3.1",
-    "@material-ui/core": "^4.12.3",
-    "@material-ui/icons": "^4.11.2",
-    "@material-ui/lab": "4.0.0-alpha.61",
-    "@material-ui/styles": "^4.11.4",
-    "@material-ui/system": "^4.12.2",
-    "@monaco-editor/react": "^4.4.5",
-    "@mui/material": "^5.10.12",
-    "@postgres.ai/ce": "link:../ce",
-    "@postgres.ai/platform": "link:./",
-    "@postgres.ai/shared": "link:../shared",
-    "@sentry/react": "^6.11.0",
-    "@sentry/tracing": "^6.11.0",
-    "@stripe/react-stripe-js": "^1.1.2",
-    "@stripe/stripe-js": "^1.9.0",
-    "@types/d3": "^7.4.0",
-    "@types/dompurify": "^2.3.4",
-    "@types/node": "^12.20.33",
-    "@types/qs": "^6.9.7",
-    "@types/react": "^17.0.5",
-    "@types/react-dom": "^17.0.3",
-    "@types/react-router": "^5.1.17",
-    "@types/react-router-dom": "^5.1.7",
-    "@types/react-syntax-highlighter": "^15.5.6",
-    "bootstrap": "^4.3.1",
-    "byte-size": "^7.0.1",
-    "classnames": "^2.3.1",
-    "clsx": "^1.1.1",
-    "copy-to-clipboard": "^3.3.1",
-    "create-file-webpack": "^1.0.2",
-    "crypto-browserify": "^3.12.0",
-    "d3": "^5.12.0",
-    "d3-flame-graph": "^2.1.3",
-    "date-fns": "^2.22.1",
-    "dompurify": "^2.0.12",
-    "dotenv": "^10.0.0",
-    "es6-promise": "^4.2.8",
-    "formik": "^2.2.9",
-    "get-user-locale": "^1.4.0",
-    "jwt-decode": "^3.1.2",
-    "jwt-encode": "^1.0.1",
-    "lodash": "^4.17.15",
-    "md5": "^2.2.1",
-    "mermaid": "^11.0.2",
-    "mobx": "^6.3.2",
-    "mobx-react-lite": "^3.2.0",
-    "moment": "^2.24.0",
-    "prop-types": "^15.7.2",
-    "qs": "^6.11.0",
-    "react": "^17.0.2",
-    "react-bootstrap": "^0.32.4",
-    "react-countdown-hook": "^1.1.0",
-    "react-div-100vh": "^0.6.0",
-    "react-dom": "^17.0.2",
-    "react-markdown": "^8.0.1",
-    "react-router": "^5.1.2",
-    "react-router-dom": "^5.1.2",
-    "react-router-hash-link": "^1.2.2",
-    "react-scripts": "^5.0.0",
-    "react-syntax-highlighter": "^15.5.0",
-    "react-use-websocket": "3.0.0",
-    "reflux": "^6.4.1",
-    "rehype-raw": "^6.1.1",
-    "remark-gfm": "^3.0.1",
-    "stream-browserify": "^3.0.0",
-    "typeface-roboto": "0.0.75",
-    "typescript": "^4.4.4",
-    "use-interval": "^1.3.0",
-    "use-timer": "^2.0.1",
-    "uuid": "^3.3.2",
-    "whatwg-fetch": "^3.6.2",
-    "yup": "^0.32.11"
-  },
-  "scripts": {
-    "start": "craco start",
-    "build": "craco build",
-    "test": "craco test",
-    "eject": "craco eject",
-    "lint": "npm run lint:code && npm run lint:styles && npm run lint:spelling",
-    "lint:code": "eslint './src'",
-    "lint:styles": "stylelint \"src/**/*.scss\"",
-    "lint:spelling": "cspell './src/**/*' --no-progress --no-summary"
-  },
-  "browserslist": {
-    "production": [
-      ">0.2%",
-      "not dead",
-      "not op_mini all"
-    ],
-    "development": [
-      "last 1 chrome version",
-      "last 1 firefox version",
-      "last 1 safari version"
-    ]
-  },
-  "devDependencies": {
-    "@babel/core": "^7.19.0",
-    "@babel/eslint-parser": "^7.18.9",
-    "@babel/eslint-plugin": "^7.18.10",
-    "@babel/preset-react": "^7.18.6",
-    "@tsconfig/recommended": "^1.0.1",
-    "@typescript-eslint/eslint-plugin": "^5.6.0",
-    "@typescript-eslint/parser": "^5.6.0",
-    "cspell": "^5.6.6",
-    "eslint": "^8.23.0",
-    "eslint-plugin-react": "^7.18.0",
-    "eslint-plugin-react-hooks": "^4.2.0",
-    "sass": "^1.37.5",
-    "stylelint": "^13.13.1",
-    "stylelint-config-sass-guidelines": "^8.0.0"
-  }
-}
diff --git a/ui/packages/platform/public/auth-gate.html b/ui/packages/platform/public/auth-gate.html
deleted file mode 100644
index 9621ad54..00000000
--- a/ui/packages/platform/public/auth-gate.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8" />
-    <title>Postgres.ai</title>
-  </head>
-  <body>
-    <noscript> You need to enable JavaScript to run this app. </noscript>
-    <script>
-      'use strict'
-
-      var paramsStr = window.location.search.substring(1)
-      var paramsListStr = paramsStr.split('&')
-
-      var params = paramsListStr.reduce(function (params, paramStr) {
-        var paramParts = paramStr.split('=')
-
-        var key = paramParts[0]
-        var value = paramParts[1]
-
-        return Object.assign(params, {
-          [decodeURIComponent(key)]: decodeURIComponent(value),
-        })
-      }, {})
-
-      if (params.token) window.localStorage.setItem('token', params.token)
-
-      window.location.replace('/')
-    </script>
-  </body>
-</html>
diff --git a/ui/packages/platform/public/favicon.ico b/ui/packages/platform/public/favicon.ico
deleted file mode 100644
index 808c8a49..00000000
Binary files a/ui/packages/platform/public/favicon.ico and /dev/null differ
diff --git a/ui/packages/platform/public/images/ansible.svg b/ui/packages/platform/public/images/ansible.svg
deleted file mode 100644
index 7f0480fb..00000000
--- a/ui/packages/platform/public/images/ansible.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
-<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"><title>Ansible icon</title><path d="M10.617 11.473l4.686 3.695-3.102-7.662zM12 0C5.371 0 0 5.371 0 12s5.371 12 12 12 12-5.371 12-12S18.629 0 12 0zm5.797 17.305c-.011.471-.403.842-.875.83-.236 0-.416-.09-.664-.293l-6.19-5-2.079 5.203H6.191L11.438 5.44c.124-.314.427-.52.764-.506.326-.014.63.189.742.506l4.774 11.494c.045.111.08.234.08.348-.001.009-.001.009-.001.023z"/></svg>
\ No newline at end of file
diff --git a/ui/packages/platform/public/images/avatar.jpg b/ui/packages/platform/public/images/avatar.jpg
deleted file mode 100644
index deae82ed..00000000
Binary files a/ui/packages/platform/public/images/avatar.jpg and /dev/null differ
diff --git a/ui/packages/platform/public/images/bot_avatar.png b/ui/packages/platform/public/images/bot_avatar.png
deleted file mode 100644
index 8a1e5b4d..00000000
Binary files a/ui/packages/platform/public/images/bot_avatar.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/dblab.svg b/ui/packages/platform/public/images/dblab.svg
deleted file mode 100644
index b0b5416f..00000000
--- a/ui/packages/platform/public/images/dblab.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<svg width="62" height="47" viewBox="0 0 62 47" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M36.5182 10.0273C27.6546 0.454555 3.54551 5.77274 2.48189 10.0273V36.9727C8.1546 44.7727 33.3273 43 36.5182 36.9727V10.0273Z" fill="#FF6112"/>
-<path d="M37.1237 10.0344C36.3026 6.99236 30.191 4 19.5 4C8.80904 4 2.69668 6.99236 1.87626 10.0344C1.8167 10.1386 1.77274 10.2528 1.77274 10.3818V10.7364V18.8909V19.2455V19.6V27.4V27.7545V28.1091V36.6182C1.77274 36.7331 1.80323 36.8415 1.8557 36.938C2.69314 40.409 10.1854 43 19.5 43C28.7933 43 36.2736 40.4196 37.1386 36.9614C37.1954 36.86 37.2273 36.743 37.2273 36.6182V28.1091V27.7545V27.4V19.6V19.2455V18.8909V10.7364V10.3818C37.2273 10.2528 37.1833 10.1386 37.1237 10.0344ZM35.7531 28.0985C35.7332 28.1843 35.7056 28.2693 35.6708 28.3559C35.6425 28.4261 35.6077 28.4963 35.5694 28.5665C35.5219 28.6537 35.4687 28.7409 35.4056 28.8281C35.3588 28.8941 35.3056 28.9593 35.2503 29.0245C35.1716 29.1174 35.0879 29.2103 34.9929 29.3025C34.9355 29.3585 34.8724 29.4138 34.8086 29.4698C34.6923 29.5712 34.571 29.6719 34.4356 29.7719C34.3774 29.8152 34.315 29.857 34.2533 29.8995C34.0931 30.0102 33.9258 30.1201 33.7421 30.2279C33.6939 30.2569 33.6421 30.2846 33.5918 30.3129C33.3805 30.4321 33.1599 30.5505 32.9217 30.6654C32.8912 30.6803 32.8593 30.6944 32.8281 30.7086C32.5622 30.8341 32.2842 30.9575 31.9871 31.0766C31.9786 31.0802 31.9701 31.083 31.9616 31.0866C30.3512 31.7297 28.2913 32.2707 25.891 32.6253C25.879 32.6267 25.8669 32.6281 25.8549 32.6303C25.3883 32.699 24.9097 32.76 24.4183 32.8139C24.3381 32.8224 24.2537 32.8281 24.1729 32.8366C23.7439 32.8813 23.31 32.9224 22.8632 32.955C22.659 32.9699 22.4449 32.977 22.2371 32.9898C21.901 33.0096 21.5684 33.0316 21.2238 33.0437C20.6608 33.0614 20.0864 33.0727 19.5 33.0727C18.9136 33.0727 18.3392 33.0614 17.7755 33.0415C17.4309 33.0295 17.0983 33.0068 16.7622 32.9876C16.5544 32.9756 16.3403 32.9678 16.1361 32.9529C15.6894 32.9203 15.2554 32.8791 14.8264 32.8345C14.7456 32.826 14.6612 32.8203 14.581 32.8118C14.0896 32.7579 13.611 32.6962 13.1444 32.6281C13.1324 32.6267 13.1203 32.6253 13.1083 32.6232C10.708 32.2686 8.64808 31.7276 7.03773 31.0844C7.02923 31.0816 7.02072 31.0781 7.01221 31.0745C6.7151 30.9554 6.43643 30.832 6.17123 30.7065C6.14074 30.6916 6.10812 30.6774 6.07763 30.6632C5.83937 30.5484 5.61884 30.4307 5.40754 30.3108C5.3579 30.2825 5.30543 30.2548 5.25721 30.2257C5.07355 30.1179 4.90621 30.008 4.74595 29.8974C4.68497 29.8549 4.62186 29.813 4.56372 29.7698C4.42828 29.6698 4.30703 29.5691 4.19074 29.4677C4.12763 29.4124 4.06381 29.3571 4.00637 29.3004C3.91135 29.2082 3.82768 29.116 3.74897 29.0224C3.69366 28.9572 3.64048 28.8919 3.59368 28.826C3.53128 28.7388 3.4781 28.6515 3.42988 28.5643C3.39159 28.4941 3.35755 28.4239 3.32848 28.3537C3.29374 28.2679 3.26608 28.1821 3.24623 28.0963C3.21999 27.9836 3.19092 27.8687 3.19092 27.7545C3.19092 27.6744 3.20014 27.5943 3.2129 27.5149C3.23063 27.4078 3.2207 27.3007 3.19092 27.1993V21.935C3.21077 21.9535 3.23559 21.9712 3.25615 21.9896C3.41074 22.1258 3.56815 22.2612 3.74543 22.3917C6.41657 24.4119 11.7582 25.9818 19.5 25.9818C27.2092 25.9818 32.5373 24.4247 35.2198 22.4165C35.434 22.2605 35.6269 22.0995 35.8084 21.9357L35.8091 27.1993C35.7793 27.3007 35.7694 27.4078 35.7871 27.5149C35.8006 27.5943 35.8091 27.6744 35.8091 27.7545C35.8091 27.8687 35.78 27.9836 35.7531 28.0985ZM35.8091 13.4259V18.6902C35.7793 18.7916 35.7694 18.8987 35.7871 19.0058C35.8006 19.0852 35.8091 19.1653 35.8091 19.2455C35.8091 19.3596 35.78 19.4745 35.7531 19.5894C35.7332 19.6752 35.7056 19.7603 35.6708 19.8468C35.6425 19.917 35.6077 19.9872 35.5694 20.0574C35.5219 20.1446 35.4687 20.2318 35.4056 20.319C35.3588 20.385 35.3056 20.4502 35.2503 20.5154C35.1716 20.6083 35.0879 20.7012 34.9929 20.7934C34.9355 20.8494 34.8724 20.9047 34.8086 20.9607C34.6923 21.0621 34.571 21.1628 34.4356 21.2628C34.3774 21.3061 34.315 21.3479 34.2533 21.3905C34.0931 21.5011 33.9258 21.611 33.7421 21.7188C33.6939 21.7478 33.6421 21.7755 33.5918 21.8039C33.3805 21.923 33.1599 22.0414 32.9217 22.1563C32.8912 22.1712 32.8593 22.1853 32.8281 22.1995C32.5622 22.325 32.2842 22.4484 31.9871 22.5675C31.9786 22.5711 31.9701 22.5739 31.9616 22.5775C30.3512 23.2206 28.2913 23.7617 25.891 24.1162C25.879 24.1176 25.8669 24.119 25.8549 24.1212C25.3883 24.1899 24.9097 24.2509 24.4183 24.3048C24.3381 24.3133 24.2537 24.319 24.1729 24.3275C23.7439 24.3722 23.31 24.4133 22.8632 24.4459C22.659 24.4608 22.4449 24.4679 22.2371 24.4807C21.901 24.5005 21.5684 24.5225 21.2238 24.5346C20.6608 24.5523 20.0864 24.5636 19.5 24.5636C18.9136 24.5636 18.3392 24.5523 17.7755 24.5324C17.4309 24.5204 17.0983 24.4977 16.7622 24.4785C16.5544 24.4665 16.3403 24.4587 16.1361 24.4438C15.6894 24.4112 15.2554 24.3701 14.8264 24.3254C14.7456 24.3169 14.6612 24.3112 14.581 24.3027C14.0896 24.2488 13.611 24.1871 13.1444 24.119C13.1324 24.1176 13.1203 24.1162 13.1083 24.1141C10.708 23.7595 8.64808 23.2185 7.03773 22.5753C7.02923 22.5725 7.02072 22.569 7.01221 22.5654C6.7151 22.4463 6.43643 22.3229 6.17123 22.1974C6.14074 22.1825 6.10812 22.1683 6.07763 22.1541C5.83937 22.0393 5.61884 21.9216 5.40754 21.8017C5.3579 21.7734 5.30543 21.7457 5.25721 21.7166C5.07355 21.6089 4.90621 21.4989 4.74595 21.3883C4.68497 21.3458 4.62186 21.3039 4.56372 21.2607C4.42828 21.1607 4.30703 21.06 4.19074 20.9586C4.12763 20.9033 4.06381 20.848 4.00637 20.7913C3.91135 20.6991 3.82768 20.6069 3.74897 20.5133C3.69366 20.4481 3.64048 20.3828 3.59368 20.3169C3.53128 20.2297 3.4781 20.1425 3.42988 20.0552C3.39159 19.985 3.35755 19.9148 3.32848 19.8446C3.29374 19.7588 3.26608 19.673 3.24623 19.5872C3.21999 19.4745 3.19092 19.3596 3.19092 19.2455C3.19092 19.1653 3.20014 19.0852 3.2129 19.0058C3.23063 18.8987 3.2207 18.7916 3.19092 18.6902V13.4259C3.27601 13.5032 3.37315 13.5791 3.46534 13.6557C3.51639 13.6982 3.5639 13.7408 3.61779 13.7833C3.83052 13.9507 4.06026 14.1159 4.30915 14.2769C4.36092 14.3102 4.41906 14.3428 4.47295 14.3761C4.68143 14.5059 4.90195 14.6328 5.13383 14.7576C5.21963 14.8037 5.30543 14.8491 5.39406 14.8945C5.65288 15.0264 5.92446 15.1547 6.21023 15.2788C6.25703 15.2994 6.29957 15.3206 6.34708 15.3405C6.67964 15.4816 7.03277 15.6163 7.40008 15.7468C7.50148 15.783 7.60784 15.817 7.71208 15.8525C8.00423 15.951 8.3063 16.0453 8.61901 16.1368C8.73175 16.1694 8.84237 16.2035 8.95795 16.2347C9.37277 16.3495 9.80106 16.4587 10.2506 16.5594C10.3123 16.5736 10.379 16.5849 10.4414 16.5984C10.8378 16.6849 11.249 16.7651 11.6716 16.8402C11.8149 16.8657 11.9602 16.8899 12.1063 16.914C12.5084 16.9799 12.9225 17.0402 13.3472 17.0955C13.4564 17.1097 13.5607 17.126 13.6713 17.1395C14.1988 17.2033 14.7448 17.2579 15.3057 17.3047C15.444 17.316 15.588 17.3245 15.7284 17.3345C16.1772 17.3671 16.636 17.394 17.1054 17.4153C17.2763 17.4231 17.4465 17.4309 17.6209 17.4373C18.2322 17.4578 18.8547 17.4727 19.5 17.4727C20.1453 17.4727 20.7679 17.4578 21.3791 17.4373C21.5528 17.4309 21.723 17.4231 21.8946 17.4153C22.364 17.394 22.8228 17.3671 23.2717 17.3345C23.4121 17.3238 23.5553 17.316 23.6943 17.3047C24.2552 17.2586 24.8012 17.2033 25.3287 17.1395C25.4394 17.126 25.5436 17.1097 25.6528 17.0955C26.0775 17.0402 26.4909 16.9799 26.8937 16.914C27.0398 16.8899 27.1851 16.8657 27.3284 16.8402C27.751 16.7651 28.1623 16.6849 28.5586 16.5984C28.621 16.5849 28.6877 16.5729 28.7494 16.5594C29.1989 16.4587 29.6272 16.3488 30.0421 16.2347C30.1576 16.2027 30.2683 16.1694 30.381 16.1368C30.6937 16.0453 30.9958 15.951 31.2879 15.8525C31.3922 15.817 31.4985 15.783 31.5999 15.7468C31.9672 15.6163 32.3211 15.4816 32.6529 15.3405C32.7004 15.3206 32.743 15.2994 32.7898 15.2788C33.0756 15.154 33.3471 15.0264 33.606 14.8945C33.6946 14.8491 33.7811 14.8037 33.8662 14.7576C34.0981 14.6335 34.3186 14.5066 34.5271 14.3761C34.581 14.3428 34.6391 14.3102 34.6909 14.2769C34.9398 14.1159 35.1695 13.9514 35.3822 13.7833C35.4354 13.7408 35.4829 13.6982 35.5347 13.6557C35.6269 13.5791 35.724 13.5032 35.8091 13.4259ZM19.5 5.41818C29.1117 5.41818 35.8091 8.22051 35.8091 10.7364C35.8091 13.2522 29.1117 16.0545 19.5 16.0545C9.88828 16.0545 3.19092 13.2522 3.19092 10.7364C3.19092 8.22051 9.88828 5.41818 19.5 5.41818ZM35.8091 36.4076C35.7992 36.4388 35.7921 36.4707 35.7864 36.5033C35.3843 38.9561 28.7437 41.5818 19.5 41.5818C10.2563 41.5818 3.61566 38.9561 3.21361 36.5033C3.20794 36.4714 3.20014 36.4402 3.19092 36.4104V30.4441C3.21077 30.4626 3.23559 30.4803 3.25615 30.4987C3.41074 30.6349 3.56815 30.7703 3.74543 30.9008C6.41657 32.921 11.7582 34.4909 19.5 34.4909C27.2092 34.4909 32.5373 32.9337 35.2198 30.9256C35.434 30.7696 35.6269 30.6086 35.8084 30.4448L35.8091 36.4076Z" fill="white"/>
-<path fill-rule="evenodd" clipRule="evenodd" d="M39.186 40.0248C42.0085 39.2559 44.2898 37.7856 44.9553 35.8285V11.0008C43.5404 8.62185 41.4691 7.24099 39.186 6.56979V40.0248Z" fill="#FF6112"/>
-<path fill-rule="evenodd" clipRule="evenodd" d="M39.186 40.8581C42.5543 39.9882 44.9536 38.113 45.3224 35.8182C45.3559 35.7248 45.3748 35.617 45.3748 35.502V27.6617V27.335V27.0083V19.8214V19.4947V19.168V11.6544V11.3277C45.3748 11.2088 45.3488 11.1036 45.3136 11.0075C44.9514 8.91905 42.8509 6.85596 39.186 5.93993V7.29349C42.472 8.1719 44.5355 9.98183 44.5355 11.6544C44.5355 13.3269 42.472 15.1368 39.186 16.0152V17.3648C39.2103 17.3587 39.2346 17.3526 39.2588 17.3464C39.3452 17.3242 39.4313 17.302 39.516 17.2785C39.7662 17.2092 40.0096 17.1354 40.2442 17.0557C40.2621 17.0497 40.2806 17.044 40.2991 17.0383C40.3187 17.0322 40.3383 17.0262 40.3571 17.0198C40.6232 16.927 40.8767 16.8257 41.1222 16.7205C41.1689 16.7004 41.2143 16.6797 41.2596 16.659C41.2806 16.6495 41.3016 16.6399 41.3228 16.6304C41.5079 16.5461 41.6866 16.4592 41.8596 16.3684C41.8818 16.3566 41.9041 16.345 41.9265 16.3334C41.9662 16.3129 42.0058 16.2923 42.0442 16.271C42.2616 16.1508 42.471 16.0266 42.6675 15.8966C42.6838 15.886 42.6991 15.8749 42.7146 15.8638C42.7257 15.8558 42.7368 15.8477 42.7485 15.8398C42.9176 15.7248 43.0783 15.6072 43.2315 15.4857C43.284 15.4439 43.3352 15.402 43.3855 15.3596C43.5228 15.2452 43.6533 15.1283 43.7767 15.0081C43.79 14.9952 43.8038 14.9825 43.8176 14.9698C43.8367 14.9521 43.8558 14.9345 43.8736 14.9166C44.0209 14.7683 44.1569 14.6167 44.2828 14.4619C44.3008 14.4394 44.3177 14.417 44.3346 14.3946C44.3472 14.3778 44.3599 14.361 44.373 14.3442C44.3893 14.3233 44.4057 14.3024 44.4222 14.2815C44.4611 14.232 44.5001 14.1826 44.5355 14.1326V18.9831C44.5178 19.0765 44.512 19.1752 44.5225 19.2738C44.5304 19.347 44.5355 19.4209 44.5355 19.4947C44.5355 19.5927 44.5205 19.6912 44.5056 19.7898C44.5045 19.7971 44.5034 19.8043 44.5023 19.8116C44.4906 19.8906 44.4742 19.969 44.4536 20.0487C44.4368 20.1134 44.4163 20.1781 44.3936 20.2428C44.3655 20.3231 44.334 20.4035 44.2967 20.4839C44.269 20.5446 44.2375 20.6047 44.2048 20.6649C44.1582 20.7504 44.1086 20.836 44.0524 20.921C44.0189 20.9719 43.9821 21.0221 43.9449 21.073L43.9433 21.0752C43.8745 21.1686 43.8027 21.2614 43.7225 21.3535C43.6956 21.3847 43.667 21.4152 43.6384 21.4457C43.6305 21.4542 43.6226 21.4626 43.6147 21.4711C43.5198 21.573 43.4208 21.6743 43.3121 21.7736C43.2923 21.7922 43.2715 21.8101 43.2507 21.8281C43.2415 21.8361 43.2322 21.844 43.2231 21.852C43.098 21.9618 42.9675 22.0709 42.8265 22.1767C42.8119 22.1878 42.7968 22.1985 42.7818 22.209C42.7782 22.2116 42.7747 22.2141 42.7711 22.2166C42.6137 22.3322 42.4492 22.4459 42.2734 22.5557C42.2709 22.5573 42.2683 22.5588 42.2658 22.5602C42.2633 22.5617 42.2608 22.5632 42.2583 22.5648C41.4237 23.0837 40.3852 23.5305 39.186 23.8529V25.2088C41.4792 24.6354 43.1602 23.6128 44.1867 22.4165C44.3135 22.2728 44.4276 22.1245 44.535 21.9735L44.5355 26.8234C44.5178 26.9169 44.512 27.0155 44.5225 27.1142C44.5304 27.1873 44.5355 27.2612 44.5355 27.335C44.5355 27.433 44.5205 27.5315 44.5056 27.6301C44.5045 27.6374 44.5034 27.6446 44.5023 27.6519C44.4906 27.7309 44.4742 27.8093 44.4536 27.8891C44.4368 27.9537 44.4163 28.0184 44.3936 28.0831C44.3655 28.1635 44.334 28.2438 44.2967 28.3242C44.269 28.385 44.2375 28.4451 44.2048 28.5052C44.1582 28.5908 44.1086 28.6764 44.0524 28.7613C44.0189 28.8122 43.9821 28.8624 43.9449 28.9133L43.9433 28.9155C43.8745 29.0089 43.8027 29.1017 43.7225 29.1938C43.6956 29.2251 43.667 29.2555 43.6384 29.2861C43.6305 29.2945 43.6226 29.3029 43.6147 29.3114C43.5198 29.4133 43.4208 29.5146 43.3121 29.6139C43.2923 29.6325 43.2715 29.6505 43.2507 29.6684C43.2414 29.6764 43.2322 29.6843 43.2231 29.6923C43.098 29.8021 42.9675 29.9112 42.8265 30.0171C42.8119 30.0281 42.7968 30.0388 42.7818 30.0494C42.7782 30.0519 42.7747 30.0544 42.7711 30.0569C42.6137 30.1726 42.4492 30.2862 42.2734 30.396C42.2708 30.3976 42.2683 30.3991 42.2658 30.4006C42.2633 30.4021 42.2608 30.4035 42.2583 30.4052C41.4237 30.924 40.3852 31.3708 39.186 31.6932V33.0492C41.4792 32.4757 43.1602 31.4531 44.1867 30.2568C44.3135 30.1131 44.4276 29.9648 44.535 29.8139L44.5355 35.308C44.5296 35.3367 44.5254 35.3661 44.522 35.3962C44.3528 37.0034 42.3162 38.6912 39.186 39.5321V40.8581Z" fill="white"/>
-<path fill-rule="evenodd" clipRule="evenodd" d="M47.3336 40.0248C50.156 39.2559 52.4373 37.7856 53.1028 35.8285V11.0008C51.6879 8.62185 49.6166 7.24099 47.3336 6.56979V40.0248Z" fill="#FF6112"/>
-<path fill-rule="evenodd" clipRule="evenodd" d="M47.3335 40.8581C50.7019 39.9883 53.1012 38.113 53.4699 35.8183C53.5035 35.7248 53.5224 35.617 53.5224 35.502V27.6617V27.335V27.0083V19.8214V19.4947V19.168V11.6544V11.3277C53.5224 11.2088 53.4964 11.1036 53.4611 11.0075C53.099 8.91905 50.9984 6.85595 47.3335 5.93993V7.29349C50.6195 8.17189 52.683 9.98184 52.683 11.6544C52.683 13.3269 50.6195 15.1369 47.3335 16.0153V17.3648C47.3579 17.3587 47.3821 17.3526 47.4064 17.3464C47.4928 17.3242 47.5788 17.302 47.6636 17.2785C47.9138 17.2092 48.1572 17.1354 48.3918 17.0557C48.4097 17.0497 48.4282 17.044 48.4467 17.0383C48.4663 17.0322 48.4859 17.0262 48.5047 17.0198C48.7707 16.927 49.0242 16.8257 49.2697 16.7205C49.3165 16.7004 49.3619 16.6797 49.4072 16.6591C49.4282 16.6495 49.4492 16.6399 49.4704 16.6304C49.6554 16.5461 49.8342 16.4592 50.0071 16.3684C50.0293 16.3566 50.0517 16.345 50.0741 16.3334C50.1138 16.3129 50.1534 16.2923 50.1918 16.271C50.4092 16.1508 50.6186 16.0267 50.815 15.8966C50.8314 15.886 50.8467 15.8749 50.8621 15.8638C50.8733 15.8558 50.8844 15.8477 50.896 15.8398C51.0652 15.7248 51.2259 15.6072 51.3791 15.4857C51.4315 15.4439 51.4827 15.402 51.5331 15.3596C51.6703 15.2452 51.8009 15.1283 51.9243 15.0081C51.9376 14.9952 51.9514 14.9825 51.9651 14.9698C51.9843 14.9521 52.0034 14.9345 52.0212 14.9166C52.1685 14.7683 52.3045 14.6167 52.4304 14.4619C52.4484 14.4395 52.4653 14.4171 52.4822 14.3947C52.4948 14.3779 52.5075 14.3611 52.5206 14.3443C52.5368 14.3233 52.5533 14.3024 52.5698 14.2815C52.6087 14.232 52.6476 14.1826 52.683 14.1326V18.9831C52.6654 19.0765 52.6595 19.1752 52.67 19.2739C52.678 19.347 52.683 19.4209 52.683 19.4947C52.683 19.5927 52.6681 19.6912 52.6532 19.7898C52.6521 19.7971 52.651 19.8043 52.6499 19.8116C52.6381 19.8906 52.6218 19.969 52.6012 20.0487C52.5844 20.1134 52.5638 20.1781 52.5412 20.2428C52.5131 20.3232 52.4816 20.4035 52.4442 20.4839C52.4165 20.5446 52.3851 20.6048 52.3523 20.6649C52.3057 20.7505 52.2562 20.836 52.2 20.921C52.1665 20.9719 52.1297 21.0221 52.0925 21.073L52.0909 21.0752C52.022 21.1686 51.9503 21.2614 51.8701 21.3535C51.8431 21.3847 51.8146 21.4152 51.786 21.4458C51.7781 21.4542 51.7701 21.4626 51.7623 21.4711C51.6674 21.573 51.5684 21.6743 51.4597 21.7736C51.4399 21.7922 51.4191 21.8101 51.3982 21.8281C51.389 21.8361 51.3798 21.844 51.3707 21.852C51.2456 21.9618 51.1151 22.0709 50.9741 22.1767C50.9595 22.1878 50.9444 22.1985 50.9294 22.2091C50.9258 22.2116 50.9222 22.2141 50.9187 22.2166C50.7613 22.3322 50.5968 22.4459 50.4209 22.5557C50.4184 22.5573 50.4159 22.5588 50.4134 22.5603C50.4109 22.5617 50.4084 22.5632 50.4058 22.5648C49.5713 23.0837 48.5327 23.5305 47.3335 23.8529V25.2089C49.6268 24.6354 51.3078 23.6128 52.3343 22.4165C52.461 22.2728 52.5752 22.1245 52.6826 21.9735L52.683 26.8234C52.6654 26.9169 52.6595 27.0155 52.67 27.1142C52.678 27.1874 52.683 27.2612 52.683 27.335C52.683 27.433 52.6681 27.5316 52.6532 27.6301C52.6521 27.6374 52.651 27.6446 52.6499 27.6519C52.6381 27.731 52.6218 27.8094 52.6012 27.8891C52.5844 27.9538 52.5638 28.0184 52.5412 28.0831C52.5131 28.1635 52.4816 28.2438 52.4442 28.3242C52.4165 28.385 52.3851 28.4451 52.3523 28.5052C52.3057 28.5908 52.2562 28.6764 52.2 28.7613C52.1665 28.8122 52.1297 28.8624 52.0925 28.9133L52.0909 28.9155C52.022 29.0089 51.9503 29.1017 51.8701 29.1938C51.8431 29.2251 51.8146 29.2555 51.786 29.2861C51.7781 29.2945 51.7701 29.303 51.7623 29.3114C51.6674 29.4134 51.5684 29.5146 51.4597 29.6139C51.4399 29.6325 51.4191 29.6505 51.3982 29.6684C51.389 29.6764 51.3798 29.6843 51.3707 29.6923C51.2456 29.8021 51.1151 29.9112 50.9741 30.0171C50.9595 30.0281 50.9444 30.0388 50.9294 30.0494C50.9258 30.0519 50.9222 30.0544 50.9187 30.0569C50.7613 30.1726 50.5968 30.2862 50.4209 30.396C50.4184 30.3976 50.4159 30.3991 50.4134 30.4006C50.4109 30.4021 50.4084 30.4035 50.4058 30.4052C49.5713 30.9241 48.5327 31.3708 47.3335 31.6932V33.0492C49.6268 32.4757 51.3078 31.4531 52.3343 30.2568C52.461 30.1131 52.5752 29.9648 52.6826 29.8139L52.683 35.308C52.6772 35.3367 52.673 35.3661 52.6696 35.3962C52.5004 37.0034 50.4637 38.6912 47.3335 39.5322V40.8581Z" fill="white"/>
-<path fill-rule="evenodd" clipRule="evenodd" d="M55.481 40.0248C58.3035 39.2559 60.5848 37.7856 61.2503 35.8285V11.0008C59.8354 8.62185 57.7641 7.24098 55.481 6.56979V40.0248Z" fill="#FF6112"/>
-<path fill-rule="evenodd" clipRule="evenodd" d="M55.4811 40.8581C58.8495 39.9883 61.2488 38.113 61.6175 35.8182C61.6511 35.7248 61.67 35.617 61.67 35.502V27.6617V27.335V27.0083V19.8214V19.4947V19.168V11.6544V11.3277C61.67 11.2088 61.6439 11.1036 61.6087 11.0075C61.2466 8.91905 59.146 6.85596 55.4811 5.93993V7.29349C58.7671 8.17189 60.8306 9.98183 60.8306 11.6544C60.8306 13.3269 58.7671 15.1368 55.4811 16.0152V17.3648C55.5054 17.3587 55.5297 17.3526 55.5539 17.3464C55.6404 17.3242 55.7264 17.302 55.8112 17.2785C56.0613 17.2092 56.3047 17.1354 56.5393 17.0557C56.5572 17.0497 56.5757 17.044 56.5942 17.0383C56.6138 17.0322 56.6334 17.0262 56.6522 17.0198C56.9183 16.927 57.1718 16.8257 57.4173 16.7205C57.464 16.7004 57.5094 16.6797 57.5547 16.6591C57.5758 16.6495 57.5968 16.6399 57.6179 16.6304C57.803 16.5461 57.9818 16.4592 58.1547 16.3684C58.1769 16.3566 58.1993 16.345 58.2216 16.3334C58.2613 16.3129 58.3009 16.2923 58.3393 16.271C58.5567 16.1508 58.7662 16.0267 58.9626 15.8966C58.9789 15.886 58.9943 15.8749 59.0097 15.8638C59.0208 15.8558 59.032 15.8477 59.0436 15.8398C59.2127 15.7248 59.3734 15.6072 59.5266 15.4857C59.5791 15.4439 59.6303 15.402 59.6807 15.3596C59.8179 15.2452 59.9484 15.1283 60.0718 15.0081C60.0851 14.9952 60.0989 14.9825 60.1127 14.9698C60.1318 14.9521 60.1509 14.9345 60.1687 14.9166C60.3161 14.7683 60.452 14.6167 60.5779 14.4619C60.5959 14.4395 60.6128 14.4171 60.6297 14.3947C60.6424 14.3779 60.655 14.3611 60.6682 14.3443C60.6844 14.3233 60.7009 14.3024 60.7173 14.2815C60.7563 14.232 60.7952 14.1826 60.8306 14.1326V18.9831C60.813 19.0765 60.8071 19.1752 60.8176 19.2739C60.8256 19.347 60.8306 19.4209 60.8306 19.4947C60.8306 19.5927 60.8157 19.6912 60.8007 19.7898C60.7996 19.7971 60.7985 19.8043 60.7974 19.8116C60.7857 19.8906 60.7693 19.969 60.7487 20.0487C60.732 20.1134 60.7114 20.1781 60.6887 20.2428C60.6606 20.3232 60.6291 20.4035 60.5918 20.4839C60.5641 20.5446 60.5326 20.6047 60.4999 20.6649C60.4533 20.7504 60.4038 20.836 60.3475 20.921C60.314 20.9719 60.2772 21.0221 60.24 21.073L60.2384 21.0752C60.1696 21.1686 60.0978 21.2614 60.0177 21.3535C59.9907 21.3847 59.9622 21.4152 59.9335 21.4458C59.9256 21.4542 59.9177 21.4626 59.9098 21.4711C59.815 21.573 59.7159 21.6743 59.6072 21.7736C59.5874 21.7922 59.5666 21.8101 59.5458 21.8281C59.5366 21.8361 59.5274 21.844 59.5182 21.852C59.3932 21.9618 59.2627 22.0709 59.1216 22.1767C59.1071 22.1878 59.0919 22.1985 59.0769 22.2091C59.0733 22.2116 59.0698 22.2141 59.0662 22.2166C58.9089 22.3322 58.7443 22.4459 58.5685 22.5557C58.566 22.5573 58.5635 22.5588 58.561 22.5602C58.5584 22.5617 58.5559 22.5632 58.5534 22.5648C57.7188 23.0837 56.6803 23.5305 55.4811 23.8529V25.2089C57.7743 24.6354 59.4553 23.6128 60.4818 22.4165C60.6086 22.2728 60.7227 22.1245 60.8302 21.9735L60.8306 26.8234C60.813 26.9169 60.8071 27.0155 60.8176 27.1142C60.8256 27.1874 60.8306 27.2612 60.8306 27.335C60.8306 27.433 60.8157 27.5316 60.8007 27.6301C60.7996 27.6374 60.7985 27.6446 60.7974 27.6519C60.7857 27.7309 60.7693 27.8094 60.7487 27.8891C60.732 27.9537 60.7114 28.0184 60.6887 28.0831C60.6606 28.1635 60.6291 28.2438 60.5918 28.3242C60.5641 28.385 60.5326 28.4451 60.4999 28.5052C60.4533 28.5908 60.4038 28.6764 60.3475 28.7613C60.314 28.8122 60.2773 28.8624 60.24 28.9133L60.2384 28.9155C60.1696 29.0089 60.0978 29.1017 60.0177 29.1938C59.9907 29.2251 59.9622 29.2555 59.9335 29.2861C59.9256 29.2945 59.9177 29.303 59.9098 29.3114C59.815 29.4134 59.7159 29.5146 59.6072 29.6139C59.5874 29.6325 59.5666 29.6505 59.5458 29.6684C59.5366 29.6764 59.5274 29.6843 59.5182 29.6923C59.3932 29.8021 59.2627 29.9112 59.1216 30.0171C59.1071 30.0281 59.0919 30.0388 59.0769 30.0494C59.0733 30.0519 59.0698 30.0544 59.0662 30.0569C58.9089 30.1726 58.7443 30.2862 58.5685 30.396C58.5635 30.3993 58.5584 30.4019 58.5534 30.4052C57.7188 30.924 56.6803 31.3708 55.4811 31.6932V33.0492C57.7743 32.4757 59.4553 31.4531 60.4818 30.2568C60.6086 30.1131 60.7227 29.9648 60.8302 29.8139L60.8306 35.308C60.8247 35.3367 60.8205 35.3661 60.8172 35.3962C60.6479 37.0034 58.6113 38.6912 55.4811 39.5322V40.8581Z" fill="white"/>
-</svg>
diff --git a/ui/packages/platform/public/images/docker.svg b/ui/packages/platform/public/images/docker.svg
deleted file mode 100644
index 2dd944c7..00000000
--- a/ui/packages/platform/public/images/docker.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
-<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-  <path fill="#0DB7ED" fill-rule="evenodd" d="M6.94221099,14.9002344 C6.9980621,14.9002344 7.05128211,14.9107588 7.10043586,14.9297745 C7.04721586,14.9606302 7.01109801,15.018335 7.01109801,15.0842919 C7.01109801,15.1828984 7.09098782,15.2626686 7.18959432,15.2626686 C7.25710599,15.2626686 7.31570779,15.2251754 7.34608506,15.1698027 C7.36743286,15.2214082 7.37939241,15.2780367 7.37939241,15.3374756 C7.37939241,15.578939 7.18361455,15.774657 6.94221099,15.774657 C6.70080744,15.774657 6.50496978,15.578939 6.50496978,15.3374756 C6.50496978,15.0959525 6.70080744,14.9002344 6.94221099,14.9002344 L6.94221099,14.9002344 Z M6.94221099,16.0853662 C6.52978585,16.0853662 6.19420083,15.7499008 6.19420083,15.3374756 C6.19420083,14.9250505 6.52978585,14.5895253 6.94221099,14.5895253 C7.35457634,14.5895253 7.69010156,14.9250505 7.69010156,15.3374756 C7.69010156,15.7499008 7.35457634,16.0853662 6.94221099,16.0853662 L6.94221099,16.0853662 Z M20.3859431,11.1838037 C18.2619865,16.8117894 13.4653093,19.318631 7.81023526,19.318631 C5.13823222,19.318631 3.00656172,18.3995992 1.64323262,16.8672219 L1.65327865,16.8605843 C2.04609012,16.880497 2.39758135,16.8872541 2.75439457,16.8872541 C3.08065114,16.8872541 3.39979178,16.8838457 3.6953721,16.8672219 C3.72108514,16.8657867 3.75325633,16.8621989 3.77878997,16.8605843 C3.77902916,16.8605245 3.86998155,16.8546046 3.82549202,16.853887 C4.57667146,16.8075437 5.15892224,16.7031368 5.70188589,16.5482008 C5.70200548,16.548141 5.70212508,16.548141 5.70224467,16.5480812 C5.80091098,16.5198567 5.89658739,16.4901372 5.98825735,16.4583846 C6.09081051,16.4228049 6.14510687,16.3108635 6.109587,16.2083104 C6.07406714,16.1056974 5.96218553,16.0512815 5.85957258,16.0869807 C5.16992503,16.3259326 4.26010213,16.4574876 3.14505333,16.4821841 L3.14475434,16.4821841 C2.57739321,16.4947416 1.95717085,16.4797922 1.28450587,16.4365584 L1.28444607,16.4365584 C1.14529669,16.2507668 1.01649231,16.0576798 0.89869073,15.8577161 L0.71248051,15.5172277 C0.149903198,14.4112083 -0.0964037696,13.1191582 0.0343141305,11.7160038 L16.3965356,11.7160038 C17.7407294,11.7160038 19.0534696,11.2143604 19.6764427,10.6609919 C18.5601381,9.75332174 18.670764,7.59731356 19.3822377,6.774616 C19.9997093,7.270758 20.9954018,8.31584342 20.824141,9.64622396 C21.6011531,9.255625 22.9506091,9.06259783 24,9.66816973 C23.3411483,10.9541803 21.8929064,11.3383809 20.3859431,11.1838037 L20.3859431,11.1838037 Z M2.25508329,11.3188869 L4.46771995,11.3188869 L4.46771995,9.1061306 L2.25508329,9.1061306 L2.25508329,11.3188869 Z M4.80808879,11.3188869 L7.02096464,11.3188869 L7.02096464,9.1061306 L4.80808879,9.1061306 L4.80808879,11.3188869 Z M4.80808879,8.76576176 L7.02096464,8.76576176 L7.02096464,6.5530653 L4.80808879,6.5530653 L4.80808879,8.76576176 Z M7.36127369,11.3188869 L9.57402994,11.3188869 L9.57402994,9.1061306 L7.36127369,9.1061306 L7.36127369,11.3188869 Z M7.36127369,8.76576176 L9.57402994,8.76576176 L9.57402994,6.5530653 L7.36127369,6.5530653 L7.36127369,8.76576176 Z M9.91433899,11.3188869 L12.1270952,11.3188869 L12.1270952,9.1061306 L9.91433899,9.1061306 L9.91433899,11.3188869 Z M9.91433899,8.76576176 L12.1270952,8.76576176 L12.1270952,6.5530653 L9.91433899,6.5530653 L9.91433899,8.76576176 Z M9.91433899,6.21275626 L12.1270952,6.21275626 L12.1270952,4 L9.91433899,4 L9.91433899,6.21275626 Z M12.4674043,11.3188869 L14.6801605,11.3188869 L14.6801605,9.1061306 L12.4674043,9.1061306 L12.4674043,11.3188869 Z"/>
-</svg>
\ No newline at end of file
diff --git a/ui/packages/platform/public/images/globe.svg b/ui/packages/platform/public/images/globe.svg
deleted file mode 100644
index f2f0671c..00000000
--- a/ui/packages/platform/public/images/globe.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
-<svg fill="#000000" width="800px" height="800px" viewBox="-1 0 19 19" xmlns="http://www.w3.org/2000/svg" class="cf-icon-svg"><path d="M16.5 9.5a8 8 0 1 1-8-8 8 8 0 0 1 8 8zm-2.97.006a5.03 5.03 0 1 0-5.03 5.03 5.03 5.03 0 0 0 5.03-5.03zm-7.383-.4H4.289a4.237 4.237 0 0 1 2.565-3.498q.1-.042.2-.079a7.702 7.702 0 0 0-.907 3.577zm0 .8a7.7 7.7 0 0 0 .908 3.577q-.102-.037-.201-.079a4.225 4.225 0 0 1-2.565-3.498zm.8-.8a9.04 9.04 0 0 1 .163-1.402 6.164 6.164 0 0 1 .445-1.415c.289-.615.66-1.013.945-1.013.285 0 .656.398.945 1.013a6.18 6.18 0 0 1 .445 1.415 9.078 9.078 0 0 1 .163 1.402zm3.106.8a9.073 9.073 0 0 1-.163 1.402 6.187 6.187 0 0 1-.445 1.415c-.289.616-.66 1.013-.945 1.013-.285 0-.656-.397-.945-1.013a6.172 6.172 0 0 1-.445-1.415 9.036 9.036 0 0 1-.163-1.402zm1.438-3.391a4.211 4.211 0 0 1 1.22 2.591h-1.858a7.698 7.698 0 0 0-.908-3.577q.102.037.201.08a4.208 4.208 0 0 1 1.345.906zm-.638 3.391h1.858a4.238 4.238 0 0 1-2.565 3.498q-.1.043-.2.08a7.697 7.697 0 0 0 .907-3.578z"/></svg>
\ No newline at end of file
diff --git a/ui/packages/platform/public/images/infosrc.png b/ui/packages/platform/public/images/infosrc.png
deleted file mode 100644
index 244c86ab..00000000
Binary files a/ui/packages/platform/public/images/infosrc.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/oauth-github-logo.png b/ui/packages/platform/public/images/oauth-github-logo.png
deleted file mode 100644
index 1fa19c55..00000000
Binary files a/ui/packages/platform/public/images/oauth-github-logo.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/oauth-gitlab-logo.png b/ui/packages/platform/public/images/oauth-gitlab-logo.png
deleted file mode 100644
index 7ab02d6a..00000000
Binary files a/ui/packages/platform/public/images/oauth-gitlab-logo.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/oauth-google-logo.png b/ui/packages/platform/public/images/oauth-google-logo.png
deleted file mode 100644
index 389c1cd5..00000000
Binary files a/ui/packages/platform/public/images/oauth-google-logo.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/oauth-linkedin-logo.png b/ui/packages/platform/public/images/oauth-linkedin-logo.png
deleted file mode 100644
index 9086cb15..00000000
Binary files a/ui/packages/platform/public/images/oauth-linkedin-logo.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/amex.png b/ui/packages/platform/public/images/paymentMethods/amex.png
deleted file mode 100644
index 366f6e2b..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/amex.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/diners.png b/ui/packages/platform/public/images/paymentMethods/diners.png
deleted file mode 100644
index 0134bf0b..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/diners.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/discover.png b/ui/packages/platform/public/images/paymentMethods/discover.png
deleted file mode 100644
index ac653f02..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/discover.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/maestro.png b/ui/packages/platform/public/images/paymentMethods/maestro.png
deleted file mode 100644
index 5aba59ba..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/maestro.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/mastercard.png b/ui/packages/platform/public/images/paymentMethods/mastercard.png
deleted file mode 100644
index b274d7e7..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/mastercard.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/unionpay.png b/ui/packages/platform/public/images/paymentMethods/unionpay.png
deleted file mode 100644
index e9205908..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/unionpay.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/paymentMethods/visa.png b/ui/packages/platform/public/images/paymentMethods/visa.png
deleted file mode 100644
index 7d21c22c..00000000
Binary files a/ui/packages/platform/public/images/paymentMethods/visa.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/service-providers/aws.png b/ui/packages/platform/public/images/service-providers/aws.png
deleted file mode 100644
index 868a91f5..00000000
Binary files a/ui/packages/platform/public/images/service-providers/aws.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/service-providers/digitalocean.png b/ui/packages/platform/public/images/service-providers/digitalocean.png
deleted file mode 100644
index 3df53ee4..00000000
Binary files a/ui/packages/platform/public/images/service-providers/digitalocean.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/service-providers/gcp.png b/ui/packages/platform/public/images/service-providers/gcp.png
deleted file mode 100644
index 04abaf7c..00000000
Binary files a/ui/packages/platform/public/images/service-providers/gcp.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/service-providers/hetzner.png b/ui/packages/platform/public/images/service-providers/hetzner.png
deleted file mode 100644
index e0131d5e..00000000
Binary files a/ui/packages/platform/public/images/service-providers/hetzner.png and /dev/null differ
diff --git a/ui/packages/platform/public/images/simple.svg b/ui/packages/platform/public/images/simple.svg
deleted file mode 100644
index 624100b7..00000000
--- a/ui/packages/platform/public/images/simple.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
-<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
-<g><g><path fill="#000000" d="M182.2,79.5h-49.2v43.2h55.6C188.1,107.2,185.9,92.6,182.2,79.5z"/><path fill="#000000" d="M132.9,235.9c19.8-2.9,36.8-22.7,46.7-51.3h-46.7V235.9z"/><path fill="#000000" d="M132.9,20.3v48.5h45.8C168.7,41.6,152.1,23.1,132.9,20.3z"/><path fill="#000000" d="M123.1,133.5H67.5c0.4,15.8,2.7,30.7,6.6,44h49L123.1,133.5L123.1,133.5z"/><path fill="#000000" d="M188.6,133.5h-55.6v44h49C185.8,164.2,188.1,149.3,188.6,133.5z"/><path fill="#000000" d="M192.1,177.5h43.2c6.3-13.5,10-28.3,10.7-44h-47.6C198,149.1,195.8,164,192.1,177.5z"/><path fill="#000000" d="M67,68.7c12.3-35.1,35-58.8,61-58.8s48.7,23.7,60.9,58.8h41.3c-20.5-35.2-58.6-59-102.2-59c-43.6,0-81.7,23.8-102.2,59H67z"/><path fill="#000000" d="M192.3,79.5c3.6,13.3,5.7,27.9,6.2,43.2H246c-0.7-15.3-4.2-29.9-10.3-43.2H192.3z"/><path fill="#000000" d="M66,184.7H24.2c20.1,36.7,59.1,61.6,103.8,61.6C101.3,246.3,78,221.3,66,184.7z"/><path fill="#000000" d="M190,184.7c-12,36.7-35.3,61.6-62,61.6c44.7,0,83.7-24.9,103.8-61.6H190z"/><path fill="#000000" d="M20.3,79.5c-6,13.3-9.6,27.9-10.3,43.2h47.6c0.4-15.4,2.6-29.9,6.2-43.2H20.3z"/><path fill="#000000" d="M123.1,20.3C103.9,23,87.3,41.6,77.3,68.7h45.8V20.3z"/><path fill="#000000" d="M123.1,79.5H73.8c-3.7,13.1-5.9,27.7-6.4,43.2h55.6V79.5L123.1,79.5z"/><path fill="#000000" d="M123.1,235.9v-51.3H76.3C86.2,213.3,103.3,233.1,123.1,235.9z"/><path fill="#000000" d="M57.6,133.5H10c0.7,15.7,4.5,30.5,10.7,44h43.2C60.2,164,58,149.1,57.6,133.5z"/></g></g>
-</svg>
\ No newline at end of file
diff --git a/ui/packages/platform/public/images/warning.png b/ui/packages/platform/public/images/warning.png
deleted file mode 100644
index cd747ffe..00000000
Binary files a/ui/packages/platform/public/images/warning.png and /dev/null differ
diff --git a/ui/packages/platform/public/index.html b/ui/packages/platform/public/index.html
deleted file mode 100644
index 87b69b9c..00000000
--- a/ui/packages/platform/public/index.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <!-- Tag "meta" with charset should be first,
-      see https://code.google.com/archive/p/doctype-mirror/wikis/MetaCharsetAttribute.wiki. -->
-    <meta charset="utf-8">
-
-    <!-- Google tag (gtag.js) -->
-    <script async src="https://www.googletagmanager.com/gtag/js?id=G-SM4CXEQJYY"></script>
-    <script>
-      window.dataLayer = window.dataLayer || [];
-      function gtag(){dataLayer.push(arguments);}
-      gtag('js', new Date());
-
-      gtag('config', 'G-SM4CXEQJYY');
-    </script>
-    <!-- End of Google tag (gtag.js) -->
-
-    <!-- Google fonts: preconnect -->
-    <link rel="preconnect" href="https://fonts.gstatic.com">
-
-    <!-- Google fonts: Roboto -->
-    <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
-
-    <!-- Google fonts: Mono -->
-    <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
-
-    <!-- Google fonts: Fira Code -->
-    <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@500&display=swap" rel="stylesheet">
-
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-    <meta name="theme-color" content="#000000">
-    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
-    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
-    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
-    <title>Postgres.ai</title>
-  </head>
-  <body>
-    <!-- Google Tag Manager (noscript) -->
-    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5M85JPS"
-    height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
-    <!-- End Google Tag Manager (noscript) -->
-
-    <noscript>
-      You need to enable JavaScript to run this app.
-    </noscript>
-    <div id="root"></div>
-  </body>
-</html>
diff --git a/ui/packages/platform/public/manifest.json b/ui/packages/platform/public/manifest.json
deleted file mode 100644
index 56180c0d..00000000
--- a/ui/packages/platform/public/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "short_name": "Database Lab Platform",
-  "name": "Database Lab Platform",
-  "icons": [
-    {
-      "src": "favicon.ico",
-      "sizes": "64x64 32x32 24x24 16x16",
-      "type": "image/x-icon"
-    }
-  ],
-  "start_url": "./index.html",
-  "display": "standalone",
-  "theme_color": "#000000",
-  "background_color": "#ffffff"
-}
diff --git a/ui/packages/platform/src/App.jsx b/ui/packages/platform/src/App.jsx
deleted file mode 100644
index a8859920..00000000
--- a/ui/packages/platform/src/App.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { loadStripe } from '@stripe/stripe-js'
-import { Elements } from '@stripe/react-stripe-js'
-import { BrowserRouter as Router, Route } from 'react-router-dom'
-import {
-  createGenerateClassName,
-  StylesProvider,
-  ThemeProvider,
-} from '@material-ui/core/styles'
-
-import { ROUTES } from 'config/routes'
-
-import { IndexPageWrapper } from 'components/IndexPage/IndexPageWrapper'
-import { theme } from '@postgres.ai/shared/styles/theme'
-import settings from 'utils/settings'
-
-const stripePromise = loadStripe(settings.stripeApiKey, {
-  locale: 'en',
-})
-
-class App extends Component {
-  render() {
-    const generateClassName = createGenerateClassName({
-      productionPrefix: 'p',
-    })
-
-    return (
-      <Router basename={process.env.PUBLIC_URL}>
-        <Route path={ROUTES.ROOT.path}>
-          <StylesProvider generateClassName={generateClassName}>
-            <ThemeProvider theme={theme}>
-              <Elements stripe={stripePromise}>
-                <IndexPageWrapper />
-              </Elements>
-            </ThemeProvider>
-          </StylesProvider>
-        </Route>
-      </Router>
-    )
-  }
-}
-
-export default App
diff --git a/ui/packages/platform/src/App.test.js b/ui/packages/platform/src/App.test.js
deleted file mode 100644
index 076db353..00000000
--- a/ui/packages/platform/src/App.test.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-/* eslint-disable */
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './App';
-
-it('renders without crashing', () => {
-  const div = document.createElement('div');
-  ReactDOM.render(<App />, div);
-  ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/ui/packages/platform/src/actions/actions.js b/ui/packages/platform/src/actions/actions.js
deleted file mode 100644
index 507200e2..00000000
--- a/ui/packages/platform/src/actions/actions.js
+++ /dev/null
@@ -1,1574 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import Reflux from 'reflux';
-
-import { localStorage } from 'helpers/localStorage';
-
-import Api from '../api/api';
-import ExplainDepeszApi from '../api/explain/depesz';
-import ExplainPev2Api from '../api/explain/pev2';
-import settings from '../utils/settings';
-import { visualizeTypes } from '../assets/visualizeTypes';
-
-let api;
-let explainDepeszApi;
-let explainPev2Api;
-
-settings.init(() => {
-  api = new Api(settings);
-  explainDepeszApi = new ExplainDepeszApi(settings);
-  explainPev2Api = new ExplainPev2Api(settings);
-});
-
-// Timeout: 30 sec.
-const REQUEST_TIMEOUT = 30000;
-const ASYNC_ACTION = {
-  asyncResult: true,
-  children: ['progressed', 'completed', 'failed']
-};
-
-const Actions = Reflux.createActions([{
-  auth: {},
-  signOut: {},
-  ASYNC_ACTION: ASYNC_ACTION,
-  doAuth: ASYNC_ACTION,
-  getUserProfile: ASYNC_ACTION,
-  getAccessTokens: ASYNC_ACTION,
-  getAccessToken: ASYNC_ACTION,
-  hideGeneratedAccessToken: {},
-  revokeAccessToken: ASYNC_ACTION,
-  getCheckupReports: ASYNC_ACTION,
-  getCheckupReportFiles: ASYNC_ACTION,
-  getCheckupReportFile: ASYNC_ACTION,
-  getJoeSessions: ASYNC_ACTION,
-  getJoeSessionCommands: ASYNC_ACTION,
-  getJoeSessionCommand: ASYNC_ACTION,
-  getProjects: ASYNC_ACTION,
-  getOrgs: ASYNC_ACTION,
-  getOrgUsers: ASYNC_ACTION,
-  updateOrg: ASYNC_ACTION,
-  createOrg: ASYNC_ACTION,
-  updateAiBotSettings: ASYNC_ACTION,
-  inviteUser: ASYNC_ACTION,
-  useDemoData: ASYNC_ACTION,
-  setReportsProject: {},
-  setSessionsProject: {},
-  setDbLabInstancesProject: {},
-  refresh: {},
-  getDbLabInstances: ASYNC_ACTION,
-  addDbLabInstance: ASYNC_ACTION,
-  editDbLabInstance: ASYNC_ACTION,
-  destroyDbLabInstance: ASYNC_ACTION,
-  resetNewDbLabInstance: {},
-  getDbLabInstanceStatus: ASYNC_ACTION,
-  checkDbLabInstanceUrl: ASYNC_ACTION,
-  downloadReportJsonFiles: ASYNC_ACTION,
-  addOrgDomain: ASYNC_ACTION,
-  deleteOrgDomain: ASYNC_ACTION,
-  showNotification: {},
-  hideNotification: {},
-  setJoeInstancesProject: {},
-  getJoeInstances: ASYNC_ACTION,
-  getJoeInstanceChannels: ASYNC_ACTION,
-  sendJoeInstanceCommand: ASYNC_ACTION,
-  initJoeWebSocketConnection: {},
-  getJoeInstanceMessages: ASYNC_ACTION,
-  getJoeMessageArtifacts: ASYNC_ACTION,
-  resetNewJoeInstance: {},
-  checkJoeInstanceUrl: ASYNC_ACTION,
-  addJoeInstance: ASYNC_ACTION,
-  destroyJoeInstance: ASYNC_ACTION,
-  closeJoeWebSocketConnection: {},
-  getExternalVisualizationData: ASYNC_ACTION,
-  closeExternalVisualization: {},
-  deleteJoeSessions: ASYNC_ACTION,
-  deleteJoeCommands: ASYNC_ACTION,
-  deleteCheckupReports: ASYNC_ACTION,
-  joeCommandFavorite: ASYNC_ACTION,
-  clearJoeInstanceChannelMessages: {},
-  getSharedUrlData: ASYNC_ACTION,
-  getSharedUrl: ASYNC_ACTION,
-  addSharedUrl: ASYNC_ACTION,
-  removeSharedUrl: ASYNC_ACTION,
-  showShareUrlDialog: {},
-  closeShareUrlDialog: {},
-  getBillingDataUsage: ASYNC_ACTION,
-  subscribeBilling: ASYNC_ACTION,
-  setSubscriptionError: {},
-  getDbLabInstance: ASYNC_ACTION,
-  getJoeInstance: ASYNC_ACTION,
-  updateOrgUser: ASYNC_ACTION,
-  deleteOrgUser: ASYNC_ACTION,
-  getDbLabSessions: ASYNC_ACTION,
-  getDbLabSession: ASYNC_ACTION,
-  getDbLabSessionLogs: ASYNC_ACTION,
-  getDbLabSessionArtifacts: ASYNC_ACTION,
-  getDbLabSessionArtifact: ASYNC_ACTION,
-  getAuditLog: ASYNC_ACTION,
-  downloadDblabSessionLog: ASYNC_ACTION,
-  downloadDblabSessionArtifact: ASYNC_ACTION,
-  sendUserCode: ASYNC_ACTION,
-  confirmUserEmail: ASYNC_ACTION,
-  confirmTosAgreement: ASYNC_ACTION
-}]);
-
-function timeoutPromise(ms, promise) {
-  return new Promise((resolve, reject) => {
-    const timeoutId = setTimeout(function () {
-      reject(new Error('timeout'));
-    }, ms);
-
-    promise
-      .then(
-        (res) => {
-          clearTimeout(timeoutId);
-          resolve(res);
-        },
-        (err) => {
-          clearTimeout(timeoutId);
-          reject(err);
-        });
-  });
-}
-
-function actionResult(promise, cb, errorCb) {
-  timeoutPromise(REQUEST_TIMEOUT, promise)
-    .then(result => {
-      let count;
-      try {
-        let range = result.headers.get('content-range');
-        if (range) {
-          range = range.split('/');
-          if (Array.isArray(range) && range.length) {
-            range = range[range.length - 1];
-            count = parseInt(range, 10);
-          }
-        }
-      } catch (e) {
-        console.log('Range is empty');
-      }
-
-      result.json()
-        .then(json => {
-          if (!json) {
-            if (errorCb) {
-              errorCb(new Error('wrong_reply'));
-            } else {
-              this.failed(new Error('wrong_reply'));
-            }
-          }
-
-          if (cb) {
-            cb(json, count);
-          } else {
-            this.completed(json, count);
-          }
-        })
-        .catch(err => {
-          console.error(err);
-
-          if (errorCb) {
-            errorCb(new Error('wrong_reply'));
-          } else {
-            this.failed(new Error('wrong_reply'));
-          }
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      let actionErr = new Error('wrong_reply');
-
-      if (err && err.message && err.message === 'timeout') {
-        actionErr = new Error('failed_fetch');
-      }
-
-      if (errorCb) {
-        errorCb(new Error(actionErr));
-      } else {
-        this.failed(actionErr);
-      }
-    });
-}
-
-Actions.doAuth.listen(function (email, password) {
-  const action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  if (!email && !password) {
-    // Token should be passed through /auth.html page.
-    // Example: /auth-gate.html?token=some-token
-    const token = localStorage.getAuthToken();
-
-    if (token) {
-      action.completed({ token: token });
-    } else {
-      action.failed(new Error('empty_request'));
-    }
-
-    return;
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.login(email, password))
-    .then(result => {
-      if (result.status !== 200) {
-        action.failed(new Error('wrong_reply'));
-      }
-
-      result.json()
-        .then(json => {
-          if (json) {
-            if (typeof json === 'object') {
-              action.failed(new Error(json.message));
-            } else {
-              action.completed({ token: json });
-            }
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getUserProfile.listen(function (token) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.getUserProfile(token),
-    (json) => {
-      this.completed(json);
-    }
-  );
-});
-
-Actions.getAccessTokens.listen(function (token, orgId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getAccessTokens(token, orgId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, orgId: orgId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getAccessToken.listen(function (token, name, expires, orgId, isPersonal) {
-  let requestExpires = expires.split('-').join('') + 'T235959-0330';
-
-  this.progressed();
-  actionResult.bind(this)(
-    api.getAccessToken(token, name, requestExpires, orgId, isPersonal),
-    (json) => {
-      this.completed({ orgId: orgId, data: json });
-    });
-});
-
-Actions.revokeAccessToken.listen(function (token, orgId, id) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed(id);
-
-  timeoutPromise(REQUEST_TIMEOUT, api.revokeAccessToken(token, id))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ orgId: orgId, data: json });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getCheckupReports.listen(function (token, orgId, projectId, reportId) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.getCheckupReports(token, orgId, projectId, reportId),
-    (json) => {
-      this.completed({
-        data: json,
-        orgId: orgId,
-        projectId: projectId,
-        reportId: reportId
-      });
-    }
-  );
-});
-
-Actions.getCheckupReportFiles.listen(function (token, reportId, type, orderBy,
-  orderDirection) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.getCheckupReportFiles(token, reportId, type, orderBy, orderDirection),
-    (json) => {
-      this.completed({ reportId: reportId, data: json, type: type });
-    }
-  );
-});
-
-Actions.getCheckupReportFile.listen(function (token, projectId, reportId, fileId, type) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.getCheckupReportFile(token, projectId, reportId, fileId, type),
-    (json) => {
-      this.completed(fileId, json);
-    }
-  );
-});
-
-Actions.getJoeSessions.listen(function (token, orgId, projectId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getJoeSessions(token, orgId, projectId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, orgId: orgId, projectId: projectId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getJoeSessionCommands.listen(function (token, params) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed(params);
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getJoeSessionCommands(token, params))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(json, params);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getJoeSessionCommand.listen(function (token, orgId, commandId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getJoeSessionCommand(token, orgId, commandId))
-
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            const command = json[0];
-            action.completed(command);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getProjects.listen(function (token, orgId) {
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  let action = this;
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getProjects(token, orgId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, orgId: orgId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getOrgs.listen(function (token, orgId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ orgId });
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getOrgs(token, orgId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, orgId: orgId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getOrgUsers.listen(function (token, orgId) {
-  this.progressed(orgId);
-
-  actionResult.bind(this)(
-    api.getOrgUsers(token, orgId),
-    (json) => {
-      this.completed(orgId, json);
-    },
-    (err) => {
-      this.failed(orgId, err);
-    }
-  );
-});
-
-Actions.updateOrg.listen(function (token, orgId, orgData) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ orgId } + orgData);
-  timeoutPromise(REQUEST_TIMEOUT, api.updateOrg(token, orgId, orgData))
-
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(json);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.updateAiBotSettings.listen(function (token, orgId, orgData) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ orgId } + orgData);
-  timeoutPromise(REQUEST_TIMEOUT, api.updateAiBotSettings(token, orgId, orgData))
-
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(json);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.createOrg.listen(function (token, orgData) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed(orgData);
-  timeoutPromise(REQUEST_TIMEOUT, api.createOrg(token, orgData))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(json);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.inviteUser.listen(function (token, orgId, email) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ orgId, email });
-  timeoutPromise(REQUEST_TIMEOUT, api.inviteUser(token, orgId, email))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(json);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.useDemoData.listen(function (token) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.useDemoData(token),
-    (json) => {
-      this.completed(json);
-    },
-    (err) => {
-      this.failed(err);
-    }
-  );
-});
-
-Actions.getDbLabInstances.listen(function (token, orgId, projectId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-  timeoutPromise(REQUEST_TIMEOUT, api.getDbLabInstances(token, orgId, projectId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, orgId: orgId, projectId: projectId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.addDbLabInstance.listen(function (token, instanceData) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.addDbLabInstance(token, instanceData))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(
-              { data: json, orgId: instanceData.orgId, project: instanceData.project });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.editDbLabInstance.listen(function (token, instanceData) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.editDbLabInstance(token, instanceData))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(
-              { data: json, orgId: instanceData.orgId, project: instanceData.project });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.destroyDbLabInstance.listen(function (token, instanceId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ instanceId: instanceId });
-
-  timeoutPromise(REQUEST_TIMEOUT, api.destroyDbLabInstance(token, instanceId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, instanceId: instanceId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getDbLabInstanceStatus.listen(function (token, instanceId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ instanceId: instanceId });
-
-  timeoutPromise(REQUEST_TIMEOUT, api.getDbLabInstanceStatus(token, instanceId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ data: json, instanceId: instanceId });
-          } else {
-            action.failed({ instanceId: instanceId }, new Error(
-              'wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed({ instanceId: instanceId }, new Error(
-            'wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed({ instanceId: instanceId }, new Error(
-          'failed_fetch'));
-      } else {
-        action.failed({ instanceId: instanceId }, new Error(
-          'wrong_reply'));
-      }
-    });
-});
-
-Actions.checkDbLabInstanceUrl.listen(function (token, url, verifyToken, useTunnel) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed();
-
-  timeoutPromise(REQUEST_TIMEOUT, api.checkDbLabInstanceUrl(token, url, verifyToken, useTunnel))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed(json);
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.downloadBinaryFile = (action, token, fileUrl, storeParams = {}) => {
-  let xhr = new XMLHttpRequest();
-
-  action.progressed(storeParams);
-  xhr.open('GET', fileUrl);
-  xhr.setRequestHeader('authorization', 'Bearer ' + token);
-  xhr.setRequestHeader('accept', 'application/octet-stream');
-  xhr.responseType = 'arraybuffer';
-  xhr.addEventListener('readystatechange', function () {
-    if (this.readyState === 4) {
-      let blob;
-      let filename = '';
-      let type = xhr.getResponseHeader('Content-Type');
-      let disposition = xhr.getResponseHeader('Content-Disposition');
-      let url = window.URL || window.webkitURL;
-
-      if (disposition && disposition.indexOf('attachment') !== -1) {
-        let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
-        let matches = filenameRegex.exec(disposition);
-
-        if (matches !== null && matches[1]) {
-          filename = matches[1].replace(/['"]/g, '');
-        }
-      }
-
-      if (typeof File === 'function') {
-        try {
-          blob = new File([this.response], filename, { type: type });
-        } catch (e) {
-          /* IE 10 or less do not support constructor for File.
-          In this case we will use Blob on next step. */
-        }
-      }
-
-      if (this.status === 404) {
-        try {
-          const jsonBody = JSON.parse(new TextDecoder().decode(this.response));
-          action.failed(jsonBody);
-        } catch (e) {
-          action.failed({});
-        }
-        return;
-      }
-
-      if (typeof blob === 'undefined') {
-        blob = new Blob([this.response], { type: type });
-      }
-
-      action.completed();
-
-      if (typeof window.navigator.msSaveBlob !== 'undefined') {
-        window.navigator.msSaveBlob(blob, filename);
-
-        return;
-      }
-
-      let downloadUrl = url.createObjectURL(blob);
-
-      if (filename) {
-        // use HTML5 a[download] attribute to specify filename.
-        let a = document.createElement('a');
-        // safari doesn't support this yet
-        if (typeof a.download === 'undefined') {
-          window.location = downloadUrl;
-        } else {
-          a.href = downloadUrl;
-          a.download = filename;
-          document.body.appendChild(a);
-          a.click();
-        }
-
-        return;
-      }
-
-      window.location = downloadUrl;
-    }
-  });
-
-  xhr.send();
-};
-
-
-Actions.downloadReportJsonFiles.listen(function (token, reportId) {
-  let url = settings.apiServer +
-    '/rpc/checkup_report_json_download?checkup_report_id=' + reportId;
-  Actions.downloadBinaryFile(this, token, url);
-});
-
-Actions.deleteOrgDomain.listen(function (token, orgId, domainId) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ orgId, domainId });
-  timeoutPromise(REQUEST_TIMEOUT, api.deleteOrgDomain(token, domainId))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ orgId, domainId });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.addOrgDomain.listen(function (token, orgId, domain) {
-  let action = this;
-
-  if (!api) {
-    settings.init(function () {
-      api = new Api(settings);
-    });
-  }
-
-  action.progressed({ orgId, domain });
-  timeoutPromise(REQUEST_TIMEOUT, api.addOrgDomain(token, orgId, domain))
-    .then(result => {
-      result.json()
-        .then(json => {
-          if (json) {
-            action.completed({ orgId, domain });
-          } else {
-            action.failed(new Error('wrong_reply'));
-          }
-        })
-        .catch(err => {
-          console.error(err);
-          action.failed(new Error('wrong_reply'));
-        });
-    })
-    .catch(err => {
-      console.error(err);
-      if (err && err.message && err.message === 'timeout') {
-        action.failed(new Error('failed_fetch'));
-      } else {
-        action.failed(new Error('wrong_reply'));
-      }
-    });
-});
-
-Actions.getJoeInstances.listen(function (token, orgId, projectId) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.getJoeInstances(token, orgId, projectId),
-    (json) => {
-      this.completed(orgId, projectId, json);
-    });
-});
-
-Actions.getJoeInstanceChannels.listen(function (token, instanceId) {
-  this.progressed(instanceId);
-  actionResult.bind(this)(
-    api.getJoeInstanceChannels(token, instanceId),
-    (json) => {
-      this.completed(instanceId, json);
-    },
-    (err) => {
-      this.failed(instanceId, err);
-    });
-});
-
-Actions.sendJoeInstanceCommand.listen(function (token, instanceId,
-  channelId, command, sessionId) {
-  this.progressed(instanceId);
-  actionResult.bind(this)(
-    api.sendJoeInstanceCommand(token, instanceId, channelId, command, sessionId),
-    (json) => {
-      json.message = command;
-      this.completed(instanceId, json);
-    },
-    (err) => {
-      this.failed(instanceId, err);
-    }
-  );
-});
-
-Actions.getJoeInstanceMessages.listen(function (token, instanceId,
-  channelId, sessionId) {
-  this.progressed(instanceId);
-  actionResult.bind(this)(
-    api.getJoeInstanceMessages(token, channelId, sessionId),
-    (json) => {
-      this.completed(instanceId, channelId, json);
-    },
-    (err) => {
-      this.failed(instanceId, channelId, err);
-    });
-});
-
-Actions.getJoeMessageArtifacts.listen(function (token, instanceId, channelId, messageId) {
-  this.progressed(instanceId, channelId, messageId);
-  actionResult.bind(this)(
-    api.getJoeMessageArtifacts(token, messageId),
-    (json) => {
-      this.completed(instanceId, channelId, messageId, json);
-    },
-    (err) => {
-      this.failed(instanceId, channelId, messageId, err);
-    }
-  );
-});
-
-Actions.checkJoeInstanceUrl.listen(function (token, instanceData) {
-  this.progressed();
-
-  instanceData.dryRun = true;
-
-  actionResult.bind(this)(api.addJoeInstance(token, instanceData));
-});
-
-Actions.addJoeInstance.listen(function (token, instanceData) {
-  this.progressed();
-
-  instanceData.dryRun = false;
-
-  actionResult.bind(this)(
-    api.addJoeInstance(token, instanceData),
-    (json) => {
-      this.completed(instanceData.orgId, json);
-    }
-  );
-});
-
-Actions.destroyJoeInstance.listen(function (token, instanceId) {
-  this.progressed(instanceId);
-
-  actionResult.bind(this)(
-    api.destroyJoeInstance(token, instanceId),
-    (json) => {
-      this.completed(instanceId, json);
-    }
-  );
-});
-
-Actions.getExternalVisualizationData.listen(function (type, plan, query) {
-  if (type !== visualizeTypes.depesz && type !== visualizeTypes.pev2) {
-    console.log('Unsupported visualization type.');
-    return;
-  }
-
-  this.progressed(type, plan, query);
-
-  if (type === visualizeTypes.pev2) {
-    explainPev2Api.postPlan(plan.json, query)
-      .then((result) => {
-        result.json()
-          .then((response) => {
-            console.log(response.json);
-
-            if (!response || !response.id) {
-              this.failed(type);
-              return;
-            }
-
-            const url = `${settings.explainPev2Server}#${response.id}`;
-            this.completed(type, plan, query, url);
-          }).catch((error) => {
-            console.error('Error:', error);
-            this.failed(type);
-          });
-      }).catch((error) => {
-        console.error('Error:', error);
-        this.failed(type);
-      });
-  } else {
-    explainDepeszApi.postPlan(plan.text, query)
-      .then((response) => {
-        console.log(response);
-
-        const xFinalUrl = response.headers.get('x-final-url');
-
-        let url = response.url;
-        if (response.url === settings.explainDepeszServer && !!xFinalUrl) {
-          url = xFinalUrl;
-        }
-
-        this.completed(type, plan, query, url);
-      }).catch((error) => {
-        console.error('Error:', error);
-        this.failed(type);
-      });
-  }
-});
-
-Actions.deleteJoeSessions.listen(function (token, ids) {
-  this.progressed(ids);
-
-  actionResult.bind(this)(
-    api.deleteJoeSessions(token, ids),
-    (json) => {
-      this.completed(ids, json);
-    }
-  );
-});
-
-Actions.deleteJoeCommands.listen(function (token, ids) {
-  this.progressed(ids);
-
-  actionResult.bind(this)(
-    api.deleteJoeCommands(token, ids),
-    (json) => {
-      this.completed(ids, json);
-    }
-  );
-});
-
-
-Actions.deleteCheckupReports.listen(function (token, ids) {
-  this.progressed(ids);
-
-  actionResult.bind(this)(
-    api.deleteCheckupReports(token, ids),
-    (json) => {
-      this.completed(ids, json);
-    }
-  );
-});
-
-Actions.joeCommandFavorite.listen(function (token, commandId, favorite) {
-  this.progressed(commandId, favorite);
-
-  actionResult.bind(this)(
-    api.joeCommandFavorite(token, commandId, favorite),
-    (json) => {
-      this.completed(json, commandId, favorite);
-    }
-  );
-});
-
-Actions.getSharedUrlData.listen(function (uuid) {
-  this.progressed(uuid);
-
-  actionResult.bind(this)(
-    api.getSharedUrlData(uuid),
-    (json) => {
-      this.completed(uuid, json);
-    }
-  );
-});
-
-Actions.getSharedUrl.listen(function (token, orgId, objectType, objectId) {
-  this.progressed(orgId, objectType, objectId);
-
-  actionResult.bind(this)(
-    api.getSharedUrl(token, orgId, objectType, objectId),
-    (json) => {
-      this.completed(orgId, objectType, objectId, json);
-    }
-  );
-});
-
-Actions.removeSharedUrl.listen(function (token, orgId, objectType, objectId, urlId) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.removeSharedUrl(token, orgId, urlId),
-    (json) => {
-      this.completed(orgId, objectType, objectId, urlId, json);
-    }
-  );
-});
-
-Actions.addSharedUrl.listen(function (token, params) {
-  this.progressed(params);
-
-  actionResult.bind(this)(
-    api.addSharedUrl(token, params),
-    (json) => {
-      this.completed(params, json);
-    }
-  );
-});
-
-Actions.getBillingDataUsage.listen(function (token, orgId) {
-  this.progressed(orgId);
-
-  actionResult.bind(this)(
-    api.getBillingDataUsage(token, orgId),
-    (json) => {
-      this.completed(orgId, json);
-    }
-  );
-});
-
-Actions.subscribeBilling.listen(function (token, orgId, paymentMethodId) {
-  this.progressed(orgId, paymentMethodId);
-
-  actionResult.bind(this)(
-    api.subscribeBilling(token, orgId, paymentMethodId),
-    (json) => {
-      this.completed(orgId, paymentMethodId, json);
-    }
-  );
-});
-
-Actions.getDbLabInstance.listen(function (token, orgId, projectId, instanceId) {
-  this.progressed(orgId, projectId, instanceId);
-
-  actionResult.bind(this)(
-    api.getDbLabInstances(token, orgId, projectId, instanceId),
-    (json) => {
-      this.completed(orgId, projectId, instanceId, json);
-    },
-    (err) => {
-      this.failed(orgId, projectId, instanceId, err);
-    }
-  );
-});
-
-Actions.getJoeInstance.listen(function (token, orgId, projectId, instanceId) {
-  this.progressed(orgId, projectId, instanceId);
-
-  actionResult.bind(this)(
-    api.getJoeInstances(token, orgId, projectId, instanceId),
-    (json) => {
-      this.completed(orgId, projectId, instanceId, json);
-    },
-    (err) => {
-      this.failed(orgId, projectId, instanceId, err);
-    }
-  );
-});
-
-Actions.getDbLabSessions.listen(function (token, params) {
-  this.progressed(params);
-
-  actionResult.bind(this)(
-    api.getDbLabSessions(token, params),
-    (json, count) => {
-      this.completed(params, json, count);
-    },
-    (err) => {
-      this.failed(params, err);
-    }
-  );
-});
-
-Actions.getDbLabSession.listen(function (token, sessionId) {
-  this.progressed(sessionId);
-
-  actionResult.bind(this)(
-    api.getDbLabSession(token, sessionId),
-    (json) => {
-      this.completed(sessionId, json);
-    },
-    (err) => {
-      this.failed(sessionId, err);
-    }
-  );
-});
-
-Actions.getDbLabSessionLogs.listen(function (token, params) {
-  this.progressed(params);
-
-  actionResult.bind(this)(
-    api.getDbLabSessionLogs(token, params),
-    (json, count) => {
-      this.completed(params, json, count);
-    },
-    (err) => {
-      this.failed(params, err);
-    }
-  );
-});
-
-Actions.getDbLabSessionArtifacts.listen(function (token, sessionId) {
-  this.progressed(sessionId);
-
-  actionResult.bind(this)(
-    api.getDbLabSessionArtifacts(token, sessionId),
-    (json) => {
-      this.completed(sessionId, json);
-    },
-    (err) => {
-      this.failed(sessionId, err);
-    }
-  );
-});
-
-Actions.getDbLabSessionArtifact.listen(function (token, sessionId, artifactType) {
-  this.progressed(sessionId, artifactType);
-
-  actionResult.bind(this)(
-    api.getDbLabSessionArtifact(token, sessionId, artifactType),
-    (json) => {
-      this.completed(sessionId, artifactType, json);
-    },
-    (err) => {
-      this.failed(sessionId, artifactType, err);
-    }
-  );
-});
-
-
-Actions.updateOrgUser.listen(function (token, orgId, userId, role) {
-  this.progressed(orgId, userId, role);
-
-  actionResult.bind(this)(
-    api.updateOrgUser(token, orgId, userId, role),
-    (json) => {
-      this.completed(orgId, userId, role, json);
-    },
-    (err) => {
-      this.failed(orgId, userId, role, err);
-    }
-  );
-});
-
-Actions.deleteOrgUser.listen(function (token, orgId, userId) {
-  this.progressed(orgId, userId);
-
-  actionResult.bind(this)(
-    api.deleteOrgUser(token, orgId, userId),
-    (json) => {
-      this.completed(orgId, userId, json);
-    },
-    (err) => {
-      this.failed(orgId, userId, err);
-    }
-  );
-});
-
-Actions.getAuditLog.listen(function (token, params) {
-  this.progressed(params);
-
-  actionResult.bind(this)(
-    api.getAuditLog(token, params),
-    (json, count) => {
-      this.completed(params, json, count);
-    },
-    (err) => {
-      this.failed(params, err);
-    }
-  );
-});
-
-Actions.downloadDblabSessionLog.listen(function (token, sessionId) {
-  let url = settings.apiServer +
-    '/rpc/dblab_session_logs_download?dblab_session_id=' + sessionId;
-  Actions.downloadBinaryFile(this, token, url);
-});
-
-
-Actions.downloadDblabSessionArtifact.listen(function (token, sessionId, artifactType) {
-  let url = settings.apiServer +
-    '/rpc/dblab_session_artifacts_download?' +
-      'dblab_session_id=' + sessionId +
-      '&artifact_type=' + artifactType;
-  Actions.downloadBinaryFile(this, token, url, { artifactType });
-});
-
-Actions.sendUserCode.listen(function (token) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.sendUserCode(token),
-    (json, count) => {
-      this.completed(json, count);
-    },
-    (err) => {
-      this.failed(err);
-    }
-  );
-});
-
-Actions.confirmUserEmail.listen(function (token, code) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.confirmUserEmail(token, code),
-    (json) => {
-      this.completed(json);
-    },
-    (err) => {
-      this.failed(err);
-    }
-  );
-});
-
-Actions.confirmTosAgreement.listen(function (token) {
-  this.progressed();
-
-  actionResult.bind(this)(
-    api.confirmTosAgreement(token),
-    (json) => {
-      this.completed(json);
-    },
-    (err) => {
-      this.failed(err);
-    }
-  );
-});
-
-export default Actions;
diff --git a/ui/packages/platform/src/api/api.js b/ui/packages/platform/src/api/api.js
deleted file mode 100644
index 2c575f91..00000000
--- a/ui/packages/platform/src/api/api.js
+++ /dev/null
@@ -1,1004 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import 'es6-promise/auto';
-import 'whatwg-fetch';
-
-function encodeData(data) {
-  return Object.keys(data).map(function (key) {
-    return [key, data[key]].map(encodeURIComponent).join('=');
-  }).join('&');
-}
-
-class Api {
-  constructor(setting) {
-    this.server = setting.server
-    this.apiServer = process.env.REACT_APP_API_URL_PREFIX || setting.apiServer // if set in .env (e.g., for dev/debug), use it
-  }
-
-  get(url, query, options) {
-    let params = '';
-
-    if (query) {
-      params = `?${encodeData(query)}`;
-    }
-
-    if (options) {
-      options.Prefer = 'count=none';
-    }
-
-    let fetchOptions = {
-      ...options,
-      method: 'get',
-      credentials: 'include'
-    };
-
-    return fetch(`${url}${params}`, fetchOptions);
-  }
-
-  post(url, data, options = {}) {
-    let headers = options.headers || {};
-
-    let fetchOptions = {
-      ...options,
-      method: 'post',
-      credentials: 'include',
-      headers: {
-        ...headers,
-        'Content-Type': 'application/json'
-      },
-      body: JSON.stringify(data)
-    };
-
-    return fetch(url, fetchOptions);
-  }
-
-  patch(url, data, options = {}) {
-    let headers = options.headers || {};
-
-    let fetchOptions = {
-      ...options,
-      method: 'PATCH',
-      credentials: 'include',
-      headers: {
-        ...headers,
-        'Content-Type': 'application/json'
-      },
-      body: JSON.stringify(data)
-    };
-
-    return fetch(url, fetchOptions);
-  }
-
-  delete(url, data, options = {}) {
-    let headers = options.headers || {};
-
-    let fetchOptions = {
-      ...options,
-      method: 'DELETE',
-      credentials: 'include',
-      headers: {
-        ...headers,
-        'Content-Type': 'application/json'
-      },
-      body: JSON.stringify(data)
-    };
-
-    return fetch(url, fetchOptions);
-  }
-
-  login(login, password) {
-    let headers = {};
-
-    return this.post(`${this.apiServer}/rpc/login`, {
-      email: login,
-      password: password
-    }, {
-      headers: headers
-    });
-  }
-
-  getUserProfile(token) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.get(`${this.apiServer}/user_get`, {}, {
-      headers: headers
-    });
-  }
-
-  getAccessTokens(token, orgId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId !== null && orgId !== 0) {
-      params.org_id = `eq.${orgId}`;
-    }
-
-    return this.get(`${this.apiServer}/api_tokens`, params, {
-      headers: headers
-    });
-  }
-
-  getAccessToken(token, name, expires, orgId, isPersonal) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/api_token_create`, {
-      name: name,
-      expires: expires,
-      org_id: orgId,
-      is_personal: isPersonal
-    }, {
-      headers: headers
-    });
-  }
-
-  revokeAccessToken(token, id) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/api_token_revoke`, { id: id }, {
-      headers: headers
-    });
-  }
-
-  getCheckupReports(token, orgId, projectId, reportId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId !== null && orgId !== 0) {
-      params.org_id = `eq.${orgId}`;
-    }
-
-    if (projectId !== null && projectId !== 0) {
-      params.project_id = `eq.${projectId}`;
-    }
-
-    if (typeof reportId !== 'undefined' && reportId !== 0) {
-      params.id = `eq.${reportId}`;
-    }
-
-    return this.get(`${this.apiServer}/checkup_reports`, params, {
-      headers: headers
-    });
-  }
-
-  getCheckupReportFiles(token, reportId, type, orderBy, orderDirection) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-    let params = {
-      checkup_report_id: `eq.${reportId}`
-    };
-
-    if (type) {
-      params.type = `eq.${type}`;
-    }
-
-    if (orderBy && orderDirection) {
-      params.order = `${orderBy}.${orderDirection}`;
-    }
-
-    return this.get(`${this.apiServer}/checkup_report_files`, params, {
-      headers: headers
-    });
-  }
-
-  getCheckupReportFile(token, projectId, reportId, fileId, type) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-    let params = {
-      project_id: `eq.${projectId}`,
-      checkup_report_id: `eq.${reportId}`,
-      type: `eq.${type}`
-    };
-
-    if (fileId === parseInt(fileId, 10)) {
-      params.id = `eq.${fileId}`;
-    } else {
-      params.filename = `eq.${fileId}`;
-    }
-
-    return this.get(`${this.apiServer}/checkup_report_file_data`, params, {
-      headers: headers
-    });
-  }
-
-  getProjects(token, orgId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId) {
-      params.org_id = `eq.${orgId}`;
-    }
-
-    return this.get(`${this.apiServer}/projects`, params, {
-      headers: headers
-    });
-  }
-
-  getJoeSessions(token, orgId, projectId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId !== null && orgId !== 0) {
-      params.org_id = `eq.${orgId}`;
-    }
-
-    if (projectId !== null && projectId !== 0) {
-      params.project_id = `eq.${projectId}`;
-    }
-
-    return this.get(`${this.apiServer}/joe_sessions`, params, {
-      headers: headers
-    });
-  }
-
-  getJoeSessionCommands(token,
-    { orgId, session, fingerprint, command, project,
-      author, startAt, limit, lastId, search, isFavorite }) {
-    const params = {};
-    const headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId && orgId !== 0) {
-      params['org_id'] = `eq.${orgId}`;
-    }
-
-    if (session && session !== 0) {
-      params['joe_session_id'] = `eq.${session}`;
-    }
-
-    if (fingerprint) {
-      params.fingerprint = `ilike.${fingerprint}*`;
-    }
-
-    if (command) {
-      params.command = `eq.${command}`;
-    }
-
-    if (startAt) {
-      params.created_at = `gt.${startAt}`;
-    }
-
-    if (limit) {
-      params.limit = limit;
-    }
-
-    if (lastId) {
-      // backend order by id.desc
-      params.id = `lt.${lastId}`;
-    }
-
-    if (project) {
-      // backend order by id.desc
-      params.project_name = `ilike.${project}*`;
-    }
-
-    if (author) {
-      params.or = `(username.ilike.${author}*,` +
-        `useremail.ilike.${author}*,` +
-        `slack_username.ilike.${author}*)`;
-    }
-
-    if (search) {
-      let searchText = encodeURIComponent(search);
-      params.tsv = `fts(simple).${searchText}`;
-    }
-
-    if (isFavorite) {
-      params.is_favorite = `gt.0`;
-    }
-
-
-    return this.get(`${this.apiServer}/joe_session_commands`, params, {
-      headers: headers
-    });
-  }
-
-  getJoeSessionCommand(token, orgId, commandId) {
-    let params = { org_id: `eq.${orgId}` };
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (!!commandId && commandId !== 0) {
-      params.id = `eq.${commandId}`;
-    }
-
-    return this.get(`${this.apiServer}/joe_session_commands`, params, {
-      headers: headers
-    });
-  }
-
-  getOrgs(token, orgId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId) {
-      params.id = `eq.${orgId}`;
-    }
-
-    return this.get(`${this.apiServer}/orgs`, params, {
-      headers: headers
-    });
-  }
-
-  getOrgUsers(token, orgId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId) {
-      params.id = `eq.${orgId}`;
-    }
-
-    return this.get(`${this.apiServer}/org_users`, params, {
-      headers: headers
-    });
-  }
-
-  updateOrg(token, orgId, orgData) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      prefer: 'return=representation'
-    };
-
-    if (orgData.name) {
-      params.name = orgData.name;
-    }
-
-    if (orgData.alias) {
-      params.alias = orgData.alias;
-    }
-
-    if (typeof orgData.users_autojoin !== 'undefined') {
-      params.users_autojoin = orgData.users_autojoin;
-    }
-
-    if (typeof orgData.onboarding_text !== 'undefined') {
-      params.onboarding_text = orgData.onboarding_text;
-    }
-
-    if (typeof orgData.oauth_allow_google !== 'undefined') {
-      params.oauth_allow_google = orgData.oauth_allow_google;
-    }
-
-    if (typeof orgData.oauth_allow_linkedin !== 'undefined') {
-      params.oauth_allow_linkedin = orgData.oauth_allow_linkedin;
-    }
-
-    if (typeof orgData.oauth_allow_github !== 'undefined') {
-      params.oauth_allow_github = orgData.oauth_allow_github;
-    }
-
-    if (typeof orgData.oauth_allow_gitlab !== 'undefined') {
-      params.oauth_allow_gitlab = orgData.oauth_allow_gitlab;
-    }
-
-    return this.patch(`${this.apiServer}/orgs?id=eq.` + orgId, params, {
-      headers: headers
-    });
-  }
-
-  createOrg(token, orgData) {
-    let params = {
-      name: orgData.name,
-      alias: orgData.alias
-    };
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgData.email_domain_autojoin) {
-      params.org_domain = orgData.email_domain_autojoin;
-    }
-
-    if (typeof orgData.users_autojoin !== 'undefined') {
-      params.users_autojoin = orgData.users_autojoin;
-    }
-
-    return this.post(`${this.apiServer}/rpc/user_create_org`, params, {
-      headers: headers
-    });
-  }
-
-  addOrgDomain(token, orgId, domain) {
-    let params = {
-      org_id: orgId,
-      domain_name: domain
-    };
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      prefer: 'return=representation'
-    };
-
-    return this.post(`${this.apiServer}/org_domains`, params, {
-      headers: headers
-    });
-  }
-
-  deleteOrgDomain(token, domainId) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      prefer: 'return=representation'
-    };
-
-    return this.delete(`${this.apiServer}/org_domains?id=eq.${domainId}`, {}, {
-      headers: headers
-    });
-  }
-
-  updateAiBotSettings(token, orgId, orgData) {
-    let params = {};
-    let data = {};
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      prefer: 'return=representation'
-    };
-
-    // if (typeof orgData.custom_prompt !== 'undefined') {
-    //   data.custom_prompt = orgData.custom_prompt;
-    // }
-    if (typeof orgData.model !== 'undefined') {
-      data.ai_model = orgData.model;
-    }
-    params.data = {
-      ai_bot: data
-    }
-
-    return this.patch(`${this.apiServer}/orgs?id=eq.` + orgId, params, {
-      headers: headers
-    });
-  }
-
-  inviteUser(token, orgId, email) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/user_invite_org_user`, {
-      org_id: orgId,
-      email: email
-    }, {
-      headers: headers
-    });
-  }
-
-  useDemoData(token) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/use_demo_data`, {}, {
-      headers: headers
-    });
-  }
-
-  getDbLabInstances(token, orgId, projectId, instanceId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId !== null && orgId !== 0) {
-      params.org_id = `eq.${orgId}`;
-    }
-    if (projectId !== null && projectId !== 0) {
-      params.project_id = `eq.${projectId}`;
-    }
-    if (typeof instanceId !== 'undefined' && instanceId !== 0) {
-      params.id = `eq.${instanceId}`;
-    }
-
-    return this.get(`${this.apiServer}/dblab_instances`, params, {
-      headers: headers
-    });
-  }
-
-  addDbLabInstance(token, instanceData) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-    let params = {
-      url: instanceData.url,
-      org_id: instanceData.orgId,
-      token: instanceData.instanceToken,
-      project: instanceData.project,
-      project_label: instanceData.projectLabel,
-      use_tunnel: instanceData.useTunnel,
-      ssh_server_url: instanceData.sshServerUrl
-    };
-
-    return this.post(`${this.apiServer}/rpc/dblab_instance_create`, params, {
-      headers: headers
-    });
-  }
-
-  editDbLabInstance(token, instanceData) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-    }
-    let params = {
-      url: instanceData.url,
-      instance_id: Number(instanceData.instanceId),
-      project_name: instanceData.project,
-      project_label: instanceData.projectLabel,
-      use_tunnel: instanceData.useTunnel,
-      ssh_server_url: instanceData.sshServerUrl,
-    }
-
-    return this.post(`${this.apiServer}/rpc/dblab_instance_edit`, params, {
-      headers: headers,
-    })
-  }
-
-  destroyDbLabInstance(token, instanceId) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/dblab_instance_destroy`, {
-      instance_id: instanceId
-    }, {
-      headers: headers
-    });
-  }
-
-  getDbLabInstanceStatus(token, instanceId) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/dblab_instance_status_refresh`, {
-      instance_id: instanceId
-    }, {
-      headers: headers
-    });
-  }
-
-  checkDbLabInstanceUrl(token, url, verifyToken, useTunnel) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/dblab_instance_status`, {
-      url: url,
-      verify_token: verifyToken,
-      use_tunnel: useTunnel
-    }, {
-      headers: headers
-    });
-  }
-
-  getJoeInstances(token, orgId, projectId, instanceId) {
-    let params = {};
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (orgId !== null && orgId !== 0) {
-      params.org_id = `eq.${orgId}`;
-    }
-    if (typeof projectId !== 'undefined' && projectId !== 0) {
-      params.project_id = `eq.${projectId}`;
-    }
-    if (typeof instanceId !== 'undefined' && instanceId !== 0) {
-      params.id = `eq.${instanceId}`;
-    }
-
-    return this.get(`${this.apiServer}/joe_instances`, params, {
-      headers: headers
-    });
-  }
-
-  getJoeInstanceChannels(token, instanceId) {
-    let params = {
-      instance_id: instanceId
-    };
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.get(`${this.apiServer}/rpc/joe_instance_channels_get`, params, {
-      headers: headers
-    });
-  }
-
-  sendJoeInstanceCommand(token, instanceId, channelId, command, sessionId) {
-    let params = {
-      instance_id: instanceId,
-      channel_id: channelId,
-      command: command
-    };
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    if (sessionId !== null && sessionId !== 0) {
-      params.session_id = sessionId;
-    }
-
-    return this.post(`${this.apiServer}/rpc/joe_command_send`, params, {
-      headers: headers
-    });
-  }
-
-  getJoeInstanceMessages(token, channelId, sessionId) {
-    let params = {
-      channel_id: `eq.${channelId}`,
-      session_id: `eq.${sessionId}`
-    };
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.get(`${this.apiServer}/joe_messages`, params, {
-      headers: headers
-    });
-  }
-
-  getJoeMessageArtifacts(token, messageId) {
-    let params = {
-      message_id: `eq.${messageId}`
-    };
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.get(`${this.apiServer}/joe_message_artifacts`, params, {
-      headers: headers
-    });
-  }
-
-  addJoeInstance(token, instanceData) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-    let params = {
-      url: instanceData.url,
-      org_id: instanceData.orgId,
-      token: instanceData.verifyToken,
-      project: instanceData.project,
-      use_tunnel: instanceData.useTunnel,
-      dry_run: instanceData.dryRun
-    };
-
-    if (instanceData.useTunnel && instanceData.sshServerUrl) {
-      params.ssh_server_url = instanceData.sshServerUrl;
-    }
-
-    return this.post(`${this.apiServer}/rpc/joe_instance_create`, params, {
-      headers: headers
-    });
-  }
-
-  destroyJoeInstance(token, instanceId) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/joe_instance_destroy`, {
-      instance_id: instanceId
-    }, {
-      headers: headers
-    });
-  }
-
-  deleteJoeSessions(token, ids) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/joe_session_delete`, {
-      ids: ids
-    }, {
-      headers: headers
-    });
-  }
-
-  deleteJoeCommands(token, ids) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/joe_command_delete`, { ids },
-      { headers });
-  }
-
-
-  deleteCheckupReports(token, ids) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/checkup_report_delete`, {
-      ids: ids
-    }, {
-      headers: headers
-    });
-  }
-
-  joeCommandFavorite(token, commandId, favorite) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/joe_command_favorite`, {
-      command_id: parseInt(commandId, 10),
-      favorite
-    }, { headers });
-  }
-
-  getSharedUrlData(uuid) {
-    return this.get(`${this.apiServer}/rpc/shared_url_get_data`, { uuid }, {});
-  }
-
-  getSharedUrl(token, org_id, object_type, object_id) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/shared_url_get`, {
-      org_id,
-      object_type,
-      object_id
-    }, { headers });
-  }
-
-  addSharedUrl(token, urlParams) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-    let params = {
-      org_id: urlParams.orgId,
-      url: urlParams.url,
-      object_type: urlParams.objectType,
-      object_id: urlParams.objectId
-    };
-
-    if (urlParams.uuid) {
-      params['uuid'] = urlParams.uuid;
-    }
-
-    return this.post(`${this.apiServer}/rpc/shared_url_add`, params, { headers });
-  }
-
-  removeSharedUrl(token, org_id, id) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(`${this.apiServer}/rpc/shared_url_remove`, { org_id, id }, { headers });
-  }
-
-  subscribeBilling(token, org_id, payment_method_id) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(
-      `${this.apiServer}/rpc/billing_subscribe`,
-      { org_id, payment_method_id },
-      { headers }
-    );
-  }
-
-  getBillingDataUsage(token, orgId) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.get(
-      `${this.apiServer}/billing_data_usage`,
-      { org_id: `eq.${orgId}` },
-      { headers }
-    );
-  }
-
-  getDbLabSessions(token, { orgId, projectId, instanceId, limit, lastId }) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      Prefer: 'count=exact'
-    };
-
-    let params = {
-      org_id: `eq.${orgId}`
-    };
-
-    if (typeof projectId !== 'undefined' && projectId) {
-      params.project_id = `eq.$(projectId)`;
-    }
-
-    if (typeof instanceId !== 'undefined' && instanceId) {
-      params.instance_id = `eq.$(instanceId)`;
-    }
-
-    if (lastId) {
-      params.id = `lt.${lastId}`;
-    }
-
-    if (limit) {
-      params.limit = limit;
-    }
-
-    return this.get(`${this.apiServer}/dblab_sessions`, params, {
-      headers: headers
-    });
-  }
-
-  getDbLabSession(token, sessionId) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    let params = {
-      id: `eq.${sessionId}`
-    };
-
-    return this.get(`${this.apiServer}/dblab_sessions`, params, {
-      headers: headers
-    });
-  }
-
-  getDbLabSessionLogs(token, { sessionId, limit, lastId }) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      Prefer: 'count=exact'
-    };
-
-    let params = {
-      dblab_session_id: `eq.${sessionId}`
-    };
-
-    if (lastId) {
-      params.id = `lt.${lastId}`;
-    }
-
-    if (limit) {
-      params.limit = limit;
-    }
-
-    return this.get(`${this.apiServer}/dblab_session_logs`, params, {
-      headers: headers
-    });
-  }
-
-  getDbLabSessionArtifacts(token, sessionId) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      Prefer: 'count=exact'
-    };
-
-    let params = {
-      dblab_session_id: `eq.${sessionId}`
-    };
-
-    return this.get(`${this.apiServer}/dblab_session_artifacts`, params, {
-      headers: headers
-    });
-  }
-
-  getDbLabSessionArtifact(token, sessionId, artifactType) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      Prefer: 'count=exact'
-    };
-
-    let params = {
-      dblab_session_id: `eq.${sessionId}`,
-      artifact_type: `eq.${artifactType}`
-    };
-
-    return this.get(`${this.apiServer}/dblab_session_artifacts_data`, params, {
-      headers: headers
-    });
-  }
-
-  updateOrgUser(token, org_id, user_id, role_id) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(
-      `${this.apiServer}/rpc/user_update_org_user`,
-      { org_id, user_id, role_id },
-      { headers }
-    );
-  }
-
-  deleteOrgUser(token, org_id, user_id) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(
-      `${this.apiServer}/rpc/user_delete_org_user`,
-      { org_id, user_id },
-      { headers }
-    );
-  }
-
-  getAuditLog(token, { orgId, lastId, limit }) {
-    let headers = {
-      Authorization: 'Bearer ' + token,
-      Prefer: 'count=exact'
-    };
-
-    let params = {
-      org_id: `eq.${orgId}`
-    };
-
-    if (lastId) {
-      params.id = `lt.${lastId}`;
-    }
-
-    if (limit) {
-      params.limit = limit;
-    }
-
-    return this.get(`${this.apiServer}/audit_log`, params, {
-      headers: headers
-    });
-  }
-
-  sendUserCode(token) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(
-      `${this.apiServer}/rpc/user_send_code`,
-      {},
-      { headers }
-    );
-  }
-
-  confirmUserEmail(token, verification_code) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(
-      `${this.apiServer}/rpc/user_confirm_email`,
-      { verification_code },
-      { headers }
-    );
-  }
-
-  confirmTosAgreement(token) {
-    let headers = {
-      Authorization: 'Bearer ' + token
-    };
-
-    return this.post(
-      `${this.apiServer}/rpc/user_confirm_tos_agreement`,
-      {},
-      { headers }
-    );
-  }
-}
-
-export default Api;
diff --git a/ui/packages/platform/src/api/billing/getPaymentMethods.ts b/ui/packages/platform/src/api/billing/getPaymentMethods.ts
deleted file mode 100644
index 51191867..00000000
--- a/ui/packages/platform/src/api/billing/getPaymentMethods.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { request } from 'helpers/request'
-
-export const getPaymentMethods = async (orgId: number) => {
-  const response = await request(`/rpc/billing_payment_methods`, {
-    headers: {
-      Accept: 'application/vnd.pgrst.object+json',
-    },
-    method: 'POST',
-    body: JSON.stringify({
-      org_id: orgId,
-    }),
-  })
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/billing/getSubscription.ts b/ui/packages/platform/src/api/billing/getSubscription.ts
deleted file mode 100644
index 2dc4cbf8..00000000
--- a/ui/packages/platform/src/api/billing/getSubscription.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { request } from 'helpers/request'
-
-export const getSubscription = async (orgId: number) => {
-  const response = await request(`/rpc/billing_subscriptions`, {
-    headers: {
-      Accept: 'application/vnd.pgrst.object+json',
-    },
-    method: 'POST',
-    body: JSON.stringify({
-      org_id: orgId,
-    }),
-  })
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/billing/startBillingSession.ts b/ui/packages/platform/src/api/billing/startBillingSession.ts
deleted file mode 100644
index fcfcd81e..00000000
--- a/ui/packages/platform/src/api/billing/startBillingSession.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { request } from 'helpers/request'
-
-export const startBillingSession = async (orgId: number, returnUrl: string) => {
-  const response = await request(`/rpc/billing_portal_start_session`, {
-    headers: {
-      Accept: 'application/vnd.pgrst.object+json',
-    },
-    method: 'POST',
-    body: JSON.stringify({
-      org_id: orgId,
-      return_url: returnUrl,
-    }),
-  })
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : await response.json(),
-  }
-}
diff --git a/ui/packages/platform/src/api/bot/convertThread.ts b/ui/packages/platform/src/api/bot/convertThread.ts
deleted file mode 100644
index bd9d9b2a..00000000
--- a/ui/packages/platform/src/api/bot/convertThread.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {request} from "../../helpers/request";
-
-export const convertThread = (thread_id: string): Promise<{ response: { final_thread_id: string, msg: string } | null; error: Response | null }> => {
-  const apiServer = process.env.REACT_APP_BOT_API_URL || '';
-
-  return request(
-    `/convert_thread`,
-    {
-      method: 'POST',
-      body: JSON.stringify({ thread_id }),
-    },
-    apiServer
-  )
-    .then(async (response) => {
-      if (!response.ok) {
-        return { response: null, error: response };
-      }
-      const responseData = await response.json();
-      return { response: responseData, error: null };
-    })
-    .catch((error: Response) => {
-      return { response: null, error };
-    });
-};
\ No newline at end of file
diff --git a/ui/packages/platform/src/api/bot/getAiModels.ts b/ui/packages/platform/src/api/bot/getAiModels.ts
deleted file mode 100644
index 2e4e2056..00000000
--- a/ui/packages/platform/src/api/bot/getAiModels.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {request} from "../../helpers/request";
-import { AiModel } from "../../types/api/entities/bot";
-
-export const getAiModels = async (): Promise<{ response: AiModel[] | null; error: Response | null }> => {
-  const apiServer = process.env.REACT_APP_API_URL_PREFIX || '';
-
-  try {
-    const response = await request(`${apiServer}/llm_models`, {
-      method: 'GET',
-    });
-
-    if (!response.ok) {
-      return { response: null, error: response };
-    }
-
-    const responseData: AiModel[] = await response.json();
-
-    return { response: responseData, error: null };
-
-  } catch (error) {
-    return { response: null, error: error as Response };
-  }
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/api/bot/getChats.ts b/ui/packages/platform/src/api/bot/getChats.ts
deleted file mode 100644
index 1a47da61..00000000
--- a/ui/packages/platform/src/api/bot/getChats.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import {request} from "../../helpers/request";
-import {BotMessage} from "../../types/api/entities/bot";
-
-type Req = {
-  query?: string
-}
-
-export const getChats = async (req: Req): Promise<{ response: BotMessage[] | null; error: Response | null }> => {
-  const { query } = req;
-
-  const apiServer = process.env.REACT_APP_API_URL_PREFIX || '';
-
-  try {
-    const response = await request(`${apiServer}/chats_auth${query ? query : ''}`, {
-      method: 'GET',
-    });
-
-    if (!response.ok) {
-      return { response: null, error: response };
-    }
-
-    const responseData: BotMessage[] = await response.json();
-
-    return { response: responseData, error: null };
-
-  } catch (error) {
-    return { response: null, error: error as Response };
-  }
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/api/bot/getChatsWithWholeThreads.ts b/ui/packages/platform/src/api/bot/getChatsWithWholeThreads.ts
deleted file mode 100644
index 2485eba8..00000000
--- a/ui/packages/platform/src/api/bot/getChatsWithWholeThreads.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import {request} from "../../helpers/request";
-import {BotMessage} from "../../types/api/entities/bot";
-
-type Req = {
-  id: string
-}
-
-export const getChatsWithWholeThreads = async (req: Req): Promise<{ response: BotMessage[] | null; error: Response | null }> => {
-  const { id } = req;
-
-  const apiServer = process.env.REACT_APP_API_URL_PREFIX || '';
-
-  try {
-    const response = await request(`${apiServer}/rpc/chats_ancestors_and_descendants`, {
-      method: 'POST',
-      body: JSON.stringify({ id }),
-    });
-
-    if (!response.ok) {
-      return { response: null, error: response };
-    }
-
-    const responseData: BotMessage[] = await response.json();
-
-    return { response: responseData, error: null };
-
-  } catch (error) {
-    return { response: null, error: error as Response };
-  }
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/api/bot/getDebugMessages.ts b/ui/packages/platform/src/api/bot/getDebugMessages.ts
deleted file mode 100644
index 8d566312..00000000
--- a/ui/packages/platform/src/api/bot/getDebugMessages.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import {request} from "../../helpers/request";
-import { DebugMessage } from "../../types/api/entities/bot";
-
-type Req =
-  | { thread_id: string; message_id?: string }
-  | { thread_id?: string; message_id: string };
-
-export const getDebugMessages = async (req: Req): Promise<{ response: DebugMessage[] | null; error: Response | null }> => {
-  const { thread_id, message_id } = req;
-
-  const params: { [key: string]: string } = {};
-
-  if (thread_id) {
-    params['chat_thread_id'] = `eq.${thread_id}`;
-  }
-
-  if (message_id) {
-    params['chat_msg_id'] = `eq.${message_id}`;
-  }
-
-  const queryString = new URLSearchParams(params).toString();
-
-  const apiServer = process.env.REACT_APP_API_URL_PREFIX || '';
-
-  try {
-    const response = await request(`${apiServer}/chat_debug_messages?${queryString}`);
-
-    if (!response.ok) {
-      return { response: null, error: response };
-    }
-
-    const responseData: DebugMessage[] = await response.json();
-
-    return { response: responseData, error: null };
-
-  } catch (error) {
-    return { response: null, error: error as Response };
-  }
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/api/bot/updateChatVisibility.ts b/ui/packages/platform/src/api/bot/updateChatVisibility.ts
deleted file mode 100644
index 9a7dd486..00000000
--- a/ui/packages/platform/src/api/bot/updateChatVisibility.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import {request} from "../../helpers/request";
-import {BotMessage} from "../../types/api/entities/bot";
-
-type Req = {
-  thread_id: string,
-  is_public: boolean
-}
-
-export const updateChatVisibility = async (req: Req): Promise<{ response: BotMessage | null; error: Response | null }> => {
-  const { thread_id, is_public } = req;
-
-  const apiServer = process.env.REACT_APP_API_URL_PREFIX || '';
-
-  try {
-    const response = await request(`${apiServer}/chats_auth?thread_id=eq.${thread_id}`, {
-      method: 'PATCH',
-      headers: {
-        Prefer: 'return=representation'
-      },
-      body: JSON.stringify({
-        is_public,
-      })
-    });
-
-    if (!response.ok) {
-      return { response: null, error: response };
-    }
-
-    const responseData: BotMessage = await response.json();
-
-    return { response: responseData, error: null };
-
-  } catch (error) {
-    return { response: null, error: error as Response };
-  }
-}
diff --git a/ui/packages/platform/src/api/clones/createClone.ts b/ui/packages/platform/src/api/clones/createClone.ts
deleted file mode 100644
index 8a8b1f8b..00000000
--- a/ui/packages/platform/src/api/clones/createClone.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { CloneDto, formatCloneDto } from '@postgres.ai/shared/types/api/entities/clone'
-
-import { request } from 'helpers/request'
-
-type Req = {
-  instanceId: string
-  cloneId: string
-  snapshotId: string
-  dbUser: string
-  dbPassword: string
-  isProtected: boolean
-}
-
-export const createClone = async (req: Req) => {
-  const response = await request('/rpc/dblab_clone_create', {
-    method: 'POST',
-    body: JSON.stringify({
-      instance_id: req.instanceId,
-      clone_data: {
-        id: req.cloneId,
-        snapshot: {
-          id: req.snapshotId,
-        },
-        db: {
-          username: req.dbUser,
-          password: req.dbPassword,
-        },
-        protected: req.isProtected,
-      },
-    }),
-  })
-
-  return {
-    response: response.ok ? formatCloneDto(await response.json() as CloneDto) : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/clones/getClone.ts b/ui/packages/platform/src/api/clones/getClone.ts
deleted file mode 100644
index 067c7a96..00000000
--- a/ui/packages/platform/src/api/clones/getClone.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
-  CloneDto,
-  formatCloneDto,
-} from '@postgres.ai/shared/types/api/entities/clone'
-
-import { request } from 'helpers/request'
-
-type Request = {
-  instanceId: string
-  cloneId: string
-}
-
-export const getClone = async (req: Request) => {
-  const response = (await request('/rpc/dblab_clone_status', {
-    method: 'POST',
-    body: JSON.stringify({
-      instance_id: req.instanceId,
-      clone_id: req.cloneId,
-    }),
-  }))
-
-  return {
-    response: response.ok
-      ? formatCloneDto((await response.json()) as CloneDto)
-      : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/clones/updateClone.ts b/ui/packages/platform/src/api/clones/updateClone.ts
deleted file mode 100644
index fb61b1ae..00000000
--- a/ui/packages/platform/src/api/clones/updateClone.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { UpdateClone } from '@postgres.ai/shared/types/api/endpoints/updateClone'
-
-import { request } from 'helpers/request'
-
-export const updateClone: UpdateClone = async (req) => {
-  const response = await request('/rpc/dblab_clone_update', {
-    method: 'POST',
-    body: JSON.stringify({
-      instance_id: req.instanceId,
-      clone_id: req.cloneId,
-      clone: {
-        protected: req.clone.isProtected,
-      },
-    }),
-  })
-
-  return {
-    response: response.ok ? true : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/cloud/getCloudImages.ts b/ui/packages/platform/src/api/cloud/getCloudImages.ts
deleted file mode 100644
index c105cfc5..00000000
--- a/ui/packages/platform/src/api/cloud/getCloudImages.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { request } from 'helpers/request'
-
-export interface CloudImage {
-  api_name: string
-  os_name: string
-  os_version: string
-  arch: string
-  cloud_provider: string
-  region: string
-  native_os_image: string
-  release: string
-}
-
-export interface CloudImagesRequest {
-  os_name: string
-  os_version: string
-  arch: string
-  cloud_provider: string
-  region: string
-}
-
-export const getCloudImages = async (req: CloudImagesRequest) => {
-  const response = await request(
-    `/cloud_os_images?os_name=eq.${req.os_name}&os_version=eq.${req.os_version}&arch=eq.${req.arch}&cloud_provider=eq.${req.cloud_provider}&region=eq.${req.region}`,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/cloud/getCloudInstances.ts b/ui/packages/platform/src/api/cloud/getCloudInstances.ts
deleted file mode 100644
index 7467dbea..00000000
--- a/ui/packages/platform/src/api/cloud/getCloudInstances.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { request } from 'helpers/request'
-
-export interface CloudInstance {
-  api_name: string
-  arch: string
-  vcpus: number
-  ram_gib: number
-  dle_se_price_hourly: number
-  cloud_provider: string
-  only_in_regions: boolean | null
-  native_name: string
-  native_vcpus: number
-  native_ram_gib: number
-  native_reference_price_hourly: number
-  native_reference_price_currency: string
-  native_reference_price_region: string
-  native_reference_price_revision_date: string
-}
-
-export interface CloudInstancesRequest {
-  provider: string
-  region: string
-}
-
-export const getCloudInstances = async (req: CloudInstancesRequest) => {
-  const response = await request(
-    `/cloud_instances?cloud_provider=eq.${req.provider}&only_in_regions&only_in_regions=ov.{all,${req.region}}&order=vcpus.asc,ram_gib.asc`,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/cloud/getCloudVolumes.ts b/ui/packages/platform/src/api/cloud/getCloudVolumes.ts
deleted file mode 100644
index 68c2d4c3..00000000
--- a/ui/packages/platform/src/api/cloud/getCloudVolumes.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { request } from 'helpers/request'
-
-export interface CloudVolumes {
-  api_name: string
-  type: string
-  cloud_provider: string
-  native_name: string
-  native_reference_price_per_1000gib_per_hour: number
-  native_reference_price_currency: string
-  native_reference_price_region: string
-  native_reference_price_revision_date: string
-}
-
-export const getCloudVolumes = async (cloud_provider: string) => {
-  const response = await request(
-    `/cloud_volumes?cloud_provider=eq.${cloud_provider}`,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/cloud/getOrgKeys.ts b/ui/packages/platform/src/api/cloud/getOrgKeys.ts
deleted file mode 100644
index 804befc5..00000000
--- a/ui/packages/platform/src/api/cloud/getOrgKeys.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { request } from 'helpers/request'
-
-export const getOrgKeys = async (org_id: number) => {
-  const response = await request(`/org_keys?org_id=eq.${org_id}`)
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/getConfig.ts b/ui/packages/platform/src/api/configs/getConfig.ts
deleted file mode 100644
index 22ed8fb4..00000000
--- a/ui/packages/platform/src/api/configs/getConfig.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { formatConfig } from '@postgres.ai/shared/types/api/entities/config'
-import { request } from 'helpers/request'
-
-export const getConfig = async () => {
-  const response = await request('/admin/config')
-
-  return {
-    response: response.ok ? formatConfig(await response.json()) : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/getFullConfig.ts b/ui/packages/platform/src/api/configs/getFullConfig.ts
deleted file mode 100644
index abf0338d..00000000
--- a/ui/packages/platform/src/api/configs/getFullConfig.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { request } from 'helpers/request'
-export const getFullConfig = async () => {
-  const response = await request('/admin/config.yaml')
-    .then((res) => res.blob())
-    .then((blob) => blob.text())
-    .then((yamlAsString) => {
-      return yamlAsString
-    })
-
-  return {
-    response: response ? response : null,
-    error: response && null,
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/getSeImages.ts b/ui/packages/platform/src/api/configs/getSeImages.ts
deleted file mode 100644
index 17f2af97..00000000
--- a/ui/packages/platform/src/api/configs/getSeImages.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { request } from 'helpers/request'
-
-export const getSeImages = async ({
-  packageGroup,
-  platformUrl,
-}: {
-  packageGroup: string
-  platformUrl?: string
-}) => {
-  const response = await request(
-    `/dblab_se_images?package_group=eq.${packageGroup}
-    `,
-    {},
-    platformUrl,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/getTaskState.ts b/ui/packages/platform/src/api/configs/getTaskState.ts
deleted file mode 100644
index a614c105..00000000
--- a/ui/packages/platform/src/api/configs/getTaskState.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { simpleInstallRequest } from 'helpers/simpleInstallRequest'
-
-export const getTaskState = async (req: { taskID: string; userID?: number }) => {
-  const response = await simpleInstallRequest(
-    `/state/${req.taskID}`,
-    {
-      method: 'GET',
-    },
-    req?.userID,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : await response.json(),
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/initStreamLogs.ts b/ui/packages/platform/src/api/configs/initStreamLogs.ts
deleted file mode 100644
index 4e010ccb..00000000
--- a/ui/packages/platform/src/api/configs/initStreamLogs.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { SI_API_SERVER } from 'helpers/simpleInstallRequest'
-
-export const initStreamLogs = (taskId: string, otCode: string): WebSocket => {
-  let url = new URL(
-    `${SI_API_SERVER.replace(
-      'https',
-      'wss',
-    )}/stream-logs/${taskId}?otCode=${otCode}`,
-  )
-  return new WebSocket(url)
-}
diff --git a/ui/packages/platform/src/api/configs/launchDeploy.ts b/ui/packages/platform/src/api/configs/launchDeploy.ts
deleted file mode 100644
index b593d98d..00000000
--- a/ui/packages/platform/src/api/configs/launchDeploy.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import {
-  DEBUG_API_SERVER,
-  sePackageTag,
-} from 'components/DbLabInstanceForm/utils'
-import { simpleInstallRequest } from 'helpers/simpleInstallRequest'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-const API_SERVER = process.env.REACT_APP_API_SERVER
-
-const formatExtraEnvs = (extraEnvs: { [key: string]: string }) => {
-  return Object.entries(extraEnvs)
-    .filter(([key, value]) => value)
-    .map(([key, value]) => {
-      if (key === 'GCP_SERVICE_ACCOUNT_CONTENTS') {
-        return `${key}=${value.replace(/\n\s+/g, '')}`
-      }
-      return `${key}=${value}`
-    })
-}
-
-export const launchDeploy = async ({
-  state,
-  userID,
-  orgKey,
-  extraEnvs,
-  cloudImage,
-  launchType,
-}: {
-  state: useCloudProviderProps['initialState']
-  orgKey: string
-  userID?: number
-  extraEnvs: {
-    [key: string]: string
-  }
-  cloudImage: string
-  launchType: 'cluster' | 'instance'
-}) => {
-  const instanceExtraVars = [
-    `provision=${state.provider}`,
-    `server_name=${state.name}`,
-    `platform_project_name=${state.name}`,
-    `server_type=${state.instanceType.native_name}`,
-    `server_image=${cloudImage}`,
-    `server_location=${state.location.native_code}`,
-    `volume_size=${state.storage}`,
-    `dblab_engine_version=${state.tag}`,
-    `zpool_datasets_number=${state.snapshots}`,
-    `dblab_engine_verification_token=${state.verificationToken}`,
-    `platform_org_key=${orgKey}`,
-  ]
-
-  const instanceOptionalVars = [
-    state.publicKeys && `ssh_public_keys="${state.publicKeys}"`,
-    API_SERVER === DEBUG_API_SERVER &&
-      `platform_url=https://v2.postgres.ai/api/general`,
-  ].filter(Boolean)
-
-  const instanceBody = {
-    playbook: 'deploy_dle.yml',
-    provision: state.provider,
-    server: {
-      name: state.name,
-      serverType: state.instanceType.native_name,
-      image: cloudImage,
-      location: state.location.native_code,
-    },
-    image: `postgresai/dle-se-ansible:${sePackageTag}`,
-    extraVars: [...instanceExtraVars, ...instanceOptionalVars],
-    extraEnvs: formatExtraEnvs(extraEnvs),
-  }
-
-  const user = state.provider === 'aws' ? 'ubuntu' : 'root'
-
-  const extraVars = [
-    `ansible_user=${user}`,
-    `provision=${state.provider}`,
-    `servers_count=${state.numberOfInstances}`,
-    `server_type=${state.instanceType.native_name}`,
-    `server_image=${cloudImage}`,
-    `server_location=${state.location.native_code}`,
-    `volume_size=${state.storage}`,
-    `postgresql_version=${state.version}`,
-    `database_public_access=${state.database_public_access}`,
-    `with_haproxy_load_balancing=${state.with_haproxy_load_balancing}`,
-    `pgbouncer_install=${state.pgbouncer_install}`,
-    `pg_data_mount_fstype=${state.fileSystem}`,
-    `synchronous_mode=${state.synchronous_mode}`,
-    `netdata_install=${state.netdata_install}`,
-    `patroni_cluster_name=${state.name}`,
-    `platform_org_key=${orgKey}`,
-  ]
-
-  const optionalVars = [
-    state.synchronous_mode &&
-      `synchronous_node_count=${state.synchronous_node_count}`,
-    state.pg_repack && `enable_pg_repack=${state.pg_repack}`,
-    state.pg_cron && `enable_pg_cron=${state.pg_cron}`,
-    state.pgaudit && `enable_pgaudit=${state.pgaudit}`,
-    state.version !== 10 &&
-      state.pgvector &&
-      `enable_pgvector=${state.pgvector}`,
-    state.postgis && `enable_postgis=${state.postgis}`,
-    state.pgrouting && `enable_pgrouting=${state.pgrouting}`,
-    state.version !== 10 &&
-      state.version !== 11 &&
-      state.timescaledb &&
-      `enable_timescaledb=${state.timescaledb}`,
-    state.version !== 10 && state.citus && `enable_citus=${state.citus}`,
-    state.pg_partman && `enable_pg_partman=${state.pg_partman}`,
-    state.pg_stat_kcache && `enable_pg_stat_kcache=${state.pg_stat_kcache}`,
-    state.pg_wait_sampling &&
-      `enable_pg_wait_sampling=${state.pg_wait_sampling}`,
-    state.publicKeys && `ssh_public_keys="${state.publicKeys}"`,
-    API_SERVER === DEBUG_API_SERVER &&
-      `platform_url=https://v2.postgres.ai/api/general`,
-  ].filter(Boolean)
-
-  const clusterBody = {
-    playbook: 'deploy_pgcluster.yml',
-    provision: state.provider,
-    server: {
-      name: state.name,
-      serverType: state.instanceType.native_name,
-      image: cloudImage,
-      location: state.location.native_code,
-    },
-    image: 'vitabaks/postgresql_cluster:cloud',
-    extraVars: [...extraVars, ...optionalVars],
-    extraEnvs: formatExtraEnvs(extraEnvs),
-  }
-
-  const response = await simpleInstallRequest(
-    '/launch',
-    {
-      method: 'POST',
-      body: JSON.stringify(
-        launchType === 'cluster' ? clusterBody : instanceBody,
-      ),
-    },
-    userID,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : await response.json(),
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/regenerateCode.ts b/ui/packages/platform/src/api/configs/regenerateCode.ts
deleted file mode 100644
index 0e15b0a2..00000000
--- a/ui/packages/platform/src/api/configs/regenerateCode.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { simpleInstallRequest } from 'helpers/simpleInstallRequest'
-
-export const regenerateCode = async (req: {
-  taskID: string
-  userID?: number
-}) => {
-  const response = await simpleInstallRequest(
-    '/regenerate-code',
-    {
-      method: 'POST',
-      body: JSON.stringify({
-        taskID: req.taskID,
-      }),
-    },
-    req?.userID,
-  )
-
-  return {
-    response: response.ok ? await response.json() : null,
-    error: response.ok ? null : await response.json(),
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/testDbSource.ts b/ui/packages/platform/src/api/configs/testDbSource.ts
deleted file mode 100644
index 07817587..00000000
--- a/ui/packages/platform/src/api/configs/testDbSource.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { dbSource } from '@postgres.ai/shared/types/api/entities/dbSource'
-import { request } from 'helpers/request'
-
-export const testDbSource = async (req: dbSource) => {
-  const response = await request('/admin/test-db-source', {
-    method: 'POST',
-    body: JSON.stringify({
-      host: req.host,
-      port: req.port.toString(),
-      dbname: req.dbname,
-      username: req.username,
-      password: req.password,
-      db_list: req.db_list
-    }),
-  })
-
-  return {
-    response: response.ok ? await response.json(): null,
-    error: response.ok ? null : await response.json()
-  }
-}
diff --git a/ui/packages/platform/src/api/configs/updateConfig.ts b/ui/packages/platform/src/api/configs/updateConfig.ts
deleted file mode 100644
index 9c40b4f1..00000000
--- a/ui/packages/platform/src/api/configs/updateConfig.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import {
-  postUniqueCustomOptions,
-  postUniqueDatabases,
-} from '@postgres.ai/shared/pages/Configuration/utils'
-import { Config } from '@postgres.ai/shared/types/api/entities/config'
-import { request } from 'helpers/request'
-
-export const updateConfig = async (req: Config) => {
-  const response = await request('/admin/config', {
-    method: 'POST',
-    body: JSON.stringify({
-      global: {
-        debug: req.debug,
-      },
-      databaseContainer: {
-        dockerImage: req.dockerPath,
-      },
-      databaseConfigs: {
-        configs: {
-          shared_buffers: req.sharedBuffers,
-          shared_preload_libraries: req.sharedPreloadLibraries,
-          ...(req.tuningParams as unknown as { [key: string]: string }),
-        },
-      },
-      retrieval: {
-        refresh: {
-          timetable: req.timetable,
-        },
-        spec: {
-          logicalDump: {
-            options: {
-              databases: postUniqueDatabases(req.databases),
-              customOptions: postUniqueCustomOptions(req.pgDumpCustomOptions),
-              parallelJobs: req.dumpParallelJobs,
-              ignoreErrors: req.dumpIgnoreErrors,
-              source: {
-                connection: {
-                  dbname: req.dbname,
-                  host: req.host,
-                  port: req.port,
-                  username: req.username,
-                  password: req.password,
-                },
-              },
-            },
-          },
-          logicalRestore: {
-            options: {
-              customOptions: postUniqueCustomOptions(
-                req.pgRestoreCustomOptions,
-              ),
-              parallelJobs: req.restoreParallelJobs,
-              ignoreErrors: req.restoreIgnoreErrors,
-            },
-          },
-        },
-      },
-    }),
-  })
-
-  return {
-    response: response.ok ? response : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/engine/getEngine.ts b/ui/packages/platform/src/api/engine/getEngine.ts
deleted file mode 100644
index 59680981..00000000
--- a/ui/packages/platform/src/api/engine/getEngine.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { request } from 'helpers/request'
-import {
-  EngineDto,
-  formatEngineDto,
-} from '@postgres.ai/shared/types/api/endpoints/getEngine'
-
-export const getEngine = async () => {
-  const response = await request('/healthz')
-
-  return {
-    response: response.ok
-      ? formatEngineDto((await response.json()) as EngineDto)
-      : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/engine/getWSToken.ts b/ui/packages/platform/src/api/engine/getWSToken.ts
deleted file mode 100644
index 3a7872ce..00000000
--- a/ui/packages/platform/src/api/engine/getWSToken.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { request } from 'helpers/request'
-import { formatWSTokenDto, WSTokenDTO } from '@postgres.ai/shared/types/api/entities/wsToken'
-import { GetWSToken } from "@postgres.ai/shared/types/api/endpoints/getWSToken";
-
-export const getWSToken: GetWSToken = async (req ) => {
-  const response = await request('/admin/ws-auth')
-
-  return {
-    response: response.ok
-      ? formatWSTokenDto((await response.json()) as WSTokenDTO)
-      : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/engine/initWS.ts b/ui/packages/platform/src/api/engine/initWS.ts
deleted file mode 100644
index 74fd0164..00000000
--- a/ui/packages/platform/src/api/engine/initWS.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { InitWS } from "@postgres.ai/shared/types/api/endpoints/initWS";
-import { WS_URL_PREFIX } from 'config/env'
-
-export const initWS: InitWS = (path: string, token: string): WebSocket => {
-    let url = new URL(WS_URL_PREFIX + path, window.location.href);
-    url.protocol = url.protocol.replace('http', 'ws');
-    const wsAddr = url.href + '?token=' + token;
-
-    return new WebSocket(wsAddr)
-}
diff --git a/ui/packages/platform/src/api/explain/depesz.js b/ui/packages/platform/src/api/explain/depesz.js
deleted file mode 100644
index 13dde5b0..00000000
--- a/ui/packages/platform/src/api/explain/depesz.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import 'es6-promise/auto';
-import 'whatwg-fetch';
-
-class ExplainDepeszApi {
-  constructor(setting) {
-    this.server = setting.explainDepeszServer;
-  }
-
-  post(url, data, options = {}) {
-    let fetchOptions = {
-      ...options,
-      method: 'post',
-      body: data
-    };
-
-    return fetch(url, fetchOptions);
-  }
-
-  postPlan(plan, query) {
-    const formData = new FormData();
-    formData.append('is_public', '0');
-    formData.append('plan', plan);
-    formData.append('query', query);
-
-    return this.post(this.server, formData);
-  }
-}
-
-export default ExplainDepeszApi;
diff --git a/ui/packages/platform/src/api/explain/pev2.js b/ui/packages/platform/src/api/explain/pev2.js
deleted file mode 100644
index 92ceb51f..00000000
--- a/ui/packages/platform/src/api/explain/pev2.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import 'es6-promise/auto';
-import 'whatwg-fetch';
-
-class ExplainPev2Api {
-  constructor(setting) {
-    this.server = setting.explainPev2Server;
-  }
-
-  post(url, data, options = {}) {
-    let fetchOptions = {
-      ...options,
-      method: 'post',
-      headers: {
-        'Content-Type': 'application/json'
-      },
-      body: JSON.stringify(data)
-    };
-
-    return fetch(url, fetchOptions);
-  }
-
-  postPlan(plan, query) {
-    return this.post(`${this.server}/api/rpc/post_plan`, {
-      plan: plan,
-      query: query || ''
-    });
-  }
-}
-
-export default ExplainPev2Api;
diff --git a/ui/packages/platform/src/api/getMeta.ts b/ui/packages/platform/src/api/getMeta.ts
deleted file mode 100644
index d88573c6..00000000
--- a/ui/packages/platform/src/api/getMeta.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { request } from '@postgres.ai/shared/helpers/request'
-
-import { MetaDto, formatMetaDto } from 'types/api/entities/meta'
-
-export const getMeta = async () => {
-  const response = await request('/meta.json')
-
-  return {
-    response: response.ok ? formatMetaDto(await response.json() as MetaDto) : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/instances/getInstance.ts b/ui/packages/platform/src/api/instances/getInstance.ts
deleted file mode 100644
index fdaf3354..00000000
--- a/ui/packages/platform/src/api/instances/getInstance.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  formatInstanceDto,
-  InstanceDto,
-} from '@postgres.ai/shared/types/api/entities/instance'
-import { GetInstance } from '@postgres.ai/shared/types/api/endpoints/getInstance'
-
-import { request } from 'helpers/request'
-
-export const getInstance: GetInstance = async (req) => {
-  const response = await request('/dblab_instances', {
-    params: {
-      id: `eq.${req.instanceId}`,
-    },
-  })
-
-  return {
-    response: response.ok
-      ? ((await response.json()) as InstanceDto[]).map(formatInstanceDto)[0] ??
-        null
-      : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/api/instances/getWSToken.ts b/ui/packages/platform/src/api/instances/getWSToken.ts
deleted file mode 100644
index d5a67a3e..00000000
--- a/ui/packages/platform/src/api/instances/getWSToken.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2022, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { request } from 'helpers/request';
-import { localStorage } from "helpers/localStorage";
-
-import { GetWSToken } from "@postgres.ai/shared/types/api/endpoints/getWSToken";
-import { formatWSTokenDto, WSTokenDTO } from "@postgres.ai/shared/types/api/entities/wsToken";
-import { request as requestCore } from "@postgres.ai/shared/helpers/request";
-import { formatInstanceDto, InstanceDto } from "@postgres.ai/shared/types/api/entities/instance";
-
-export const getWSToken: GetWSToken = async (req) => {
-    // TODO: define instance and get a websocket token.
-    const instanceResponse = await request('/dblab_instances', {
-        params: {
-            id: `eq.${req.instanceId}`,
-        },
-    })
-
-    if (!instanceResponse.ok) {
-        return {
-            response: null,
-            error: instanceResponse,
-        }
-    }
-
-    const instance = (await instanceResponse.json() as InstanceDto[]).map(formatInstanceDto)[0]
-
-    const authToken = localStorage.getAuthToken()
-
-    if (instance.useTunnel) {
-        return {
-            response: null,
-            error: new Response(null, {
-                status: 400,
-                statusText: `Cannot connect to an instance that is using a tunnel`,
-            })
-        }
-    }
-
-    const response = await requestCore(instance.url + '/admin/ws-auth', {
-        headers: {
-            ...(authToken && {'Verification-Token': authToken}),
-        },
-    })
-
-    return {
-        response: response.ok
-            ? formatWSTokenDto((await response.json()) as WSTokenDTO)
-            : null,
-        error: response.ok ? null : response,
-    }
-}
-
-
diff --git a/ui/packages/platform/src/api/snapshots/getSnapshots.ts b/ui/packages/platform/src/api/snapshots/getSnapshots.ts
deleted file mode 100644
index 35d08eb3..00000000
--- a/ui/packages/platform/src/api/snapshots/getSnapshots.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  SnapshotDto,
-  formatSnapshotDto,
-} from '@postgres.ai/shared/types/api/entities/snapshot'
-import { GetSnapshots } from '@postgres.ai/shared/types/api/endpoints/getSnapshots'
-
-import { request } from 'helpers/request'
-
-export const getSnapshots: GetSnapshots = async (req) => {
-  const response = await request('/rpc/dblab_instance_snapshots', {
-    method: 'POST',
-    body: JSON.stringify({
-      instance_id: req.instanceId,
-    }),
-  })
-
-  return {
-    response: response.ok
-      ? ((await response.json()) as SnapshotDto[]).map(formatSnapshotDto)
-      : null,
-    error: response.ok ? null : response,
-  }
-}
diff --git a/ui/packages/platform/src/assets/explainSamples.ts b/ui/packages/platform/src/assets/explainSamples.ts
deleted file mode 100644
index 87590585..00000000
--- a/ui/packages/platform/src/assets/explainSamples.ts
+++ /dev/null
@@ -1,600 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-const sampleExplain1 =
-  `[
-  {
-    "Plan": {
-      "Node Type": "Limit",
-      "Startup Cost": 17024.84,
-      "Total Cost": 17024.87,
-      "Plan Rows": 10,
-      "Plan Width": 133,
-      "Actual Startup Time": 725.773,
-      "Actual Total Time": 725.775,
-      "Actual Rows": 10,
-      "Actual Loops": 1,
-      "Output": ["c.state", "cat.categoryname", "(sum(o.netamount))", "(sum(o.totalamount))"],
-      "Shared Hit Blocks": 23,
-      "Shared Read Blocks": 1392,
-      "Shared Dirtied Blocks": 0,
-      "Shared Written Blocks": 0,
-      "Local Hit Blocks": 0,
-      "Local Read Blocks": 0,
-      "Local Dirtied Blocks": 0,
-      "Local Written Blocks": 0,
-      "Temp Read Blocks": 0,
-      "Temp Written Blocks": 0,
-      "I/O Read Time": 0.000,
-      "I/O Write Time": 0.000,
-      "Plans": [
-        {
-          "Node Type": "Sort",
-          "Parent Relationship": "Outer",
-          "Startup Cost": 17024.84,
-          "Total Cost": 17026.88,
-          "Plan Rows": 816,
-          "Plan Width": 133,
-          "Actual Startup Time": 725.771,
-          "Actual Total Time": 725.772,
-          "Actual Rows": 11,
-          "Actual Loops": 1,
-          "Output": ["c.state", "cat.categoryname", "(sum(o.netamount))", "(sum(o.totalamount))"],
-          "Sort Key": ["c.state", "(sum(o.totalamount))"],
-          "Sort Method": "top-N heapsort",
-          "Sort Space Used": 25,
-          "Sort Space Type": "Memory",
-          "Shared Hit Blocks": 23,
-          "Shared Read Blocks": 1392,
-          "Shared Dirtied Blocks": 0,
-          "Shared Written Blocks": 0,
-          "Local Hit Blocks": 0,
-          "Local Read Blocks": 0,
-          "Local Dirtied Blocks": 0,
-          "Local Written Blocks": 0,
-          "Temp Read Blocks": 0,
-          "Temp Written Blocks": 0,
-          "I/O Read Time": 0.000,
-          "I/O Write Time": 0.000,
-          "Plans": [
-            {
-              "Node Type": "Aggregate",
-              "Strategy": "Hashed",
-              "Parent Relationship": "Outer",
-              "Startup Cost": 16994.41,
-              "Total Cost": 17006.65,
-              "Plan Rows": 816,
-              "Plan Width": 133,
-              "Actual Startup Time": 723.877,
-              "Actual Total Time": 724.417,
-              "Actual Rows": 832,
-              "Actual Loops": 1,
-              "Output": ["c.state", "cat.categoryname", "sum(o.netamount)", "sum(o.totalamount)"],
-              "Group Key": ["c.state", "cat.categoryname"],
-              "Shared Hit Blocks": 13,
-              "Shared Read Blocks": 1392,
-              "Shared Dirtied Blocks": 0,
-              "Shared Written Blocks": 0,
-              "Local Hit Blocks": 0,
-              "Local Read Blocks": 0,
-              "Local Dirtied Blocks": 0,
-              "Local Written Blocks": 0,
-              "Temp Read Blocks": 0,
-              "Temp Written Blocks": 0,
-              "I/O Read Time": 0.000,
-              "I/O Write Time": 0.000,
-              "Plans": [
-                {
-                  "Node Type": "Hash Join",
-                  "Parent Relationship": "Outer",
-                  "Join Type": "Inner",
-                  "Startup Cost": 4966.48,
-                  "Total Cost": 13742.65,
-                  "Plan Rows": 325176,
-                  "Plan Width": 133,
-                  "Actual Startup Time": 118.314,
-                  "Actual Total Time": 354.285,
-                  "Actual Rows": 383270,
-                  "Actual Loops": 1,
-                  "Output": ["c.state", "o.netamount", "o.totalamount", "cat.categoryname"],
-                  "Hash Cond": "(o.orderid = ch.orderid)",
-                  "Shared Hit Blocks": 13,
-                  "Shared Read Blocks": 1392,
-                  "Shared Dirtied Blocks": 0,
-                  "Shared Written Blocks": 0,
-                  "Local Hit Blocks": 0,
-                  "Local Read Blocks": 0,
-                  "Local Dirtied Blocks": 0,
-                  "Local Written Blocks": 0,
-                  "Temp Read Blocks": 0,
-                  "Temp Written Blocks": 0,
-                  "I/O Read Time": 0.000,
-                  "I/O Write Time": 0.000,
-                  "Plans": [
-                    {
-                      "Node Type": "Hash Join",
-                      "Parent Relationship": "Outer",
-                      "Join Type": "Inner",
-                      "Startup Cost": 834.86,
-                      "Total Cost": 4539.11,
-                      "Plan Rows": 60350,
-                      "Plan Width": 138,
-                      "Actual Startup Time": 22.651,
-                      "Actual Total Time": 133.484,
-                      "Actual Rows": 60350,
-                      "Actual Loops": 1,
-                      "Output": [
-                        "o.netamount",
-                        "o.totalamount",
-                        "o.orderid",
-                        "ol.orderid",
-                        "cat.categoryname"
-                      ],
-                      "Hash Cond": "(ol.orderid = o.orderid)",
-                      "Shared Hit Blocks": 9,
-                      "Shared Read Blocks": 581,
-                      "Shared Dirtied Blocks": 0,
-                      "Shared Written Blocks": 0,
-                      "Local Hit Blocks": 0,
-                      "Local Read Blocks": 0,
-                      "Local Dirtied Blocks": 0,
-                      "Local Written Blocks": 0,
-                      "Temp Read Blocks": 0,
-                      "Temp Written Blocks": 0,
-                      "I/O Read Time": 0.000,
-                      "I/O Write Time": 0.000,
-                      "Plans": [
-                        {
-                          "Node Type": "Hash Join",
-                          "Parent Relationship": "Outer",
-                          "Join Type": "Inner",
-                          "Startup Cost": 464.86,
-                          "Total Cost": 2962.11,
-                          "Plan Rows": 60350,
-                          "Plan Width": 122,
-                          "Actual Startup Time": 12.467,
-                          "Actual Total Time": 85.647,
-                          "Actual Rows": 60350,
-                          "Actual Loops": 1,
-                          "Output": ["ol.orderid", "cat.categoryname"],
-                          "Hash Cond": "(ol.prod_id = p.prod_id)",
-                          "Shared Hit Blocks": 4,
-                          "Shared Read Blocks": 483,
-                          "Shared Dirtied Blocks": 0,
-                          "Shared Written Blocks": 0,
-                          "Local Hit Blocks": 0,
-                          "Local Read Blocks": 0,
-                          "Local Dirtied Blocks": 0,
-                          "Local Written Blocks": 0,
-                          "Temp Read Blocks": 0,
-                          "Temp Written Blocks": 0,
-                          "I/O Read Time": 0.000,
-                          "I/O Write Time": 0.000,
-                          "Plans": [
-                            {
-                              "Node Type": "Seq Scan",
-                              "Parent Relationship": "Outer",
-                              "Relation Name": "orderlines",
-                              "Schema": "public",
-                              "Alias": "ol",
-                              "Startup Cost": 0.00,
-                              "Total Cost": 988.50,
-                              "Plan Rows": 60350,
-                              "Plan Width": 8,
-                              "Actual Startup Time": 0.005,
-                              "Actual Total Time": 14.054,
-                              "Actual Rows": 60350,
-                              "Actual Loops": 1,
-                              "Output": [
-                                "ol.orderlineid",
-                                "ol.orderid",
-                                "ol.prod_id",
-                                "ol.quantity",
-                                "ol.orderdate"
-                              ],
-                              "Shared Hit Blocks": 2,
-                              "Shared Read Blocks": 383,
-                              "Shared Dirtied Blocks": 0,
-                              "Shared Written Blocks": 0,
-                              "Local Hit Blocks": 0,
-                              "Local Read Blocks": 0,
-                              "Local Dirtied Blocks": 0,
-                              "Local Written Blocks": 0,
-                              "Temp Read Blocks": 0,
-                              "Temp Written Blocks": 0,
-                              "I/O Read Time": 0.000,
-                              "I/O Write Time": 0.000
-                            },
-                            {
-                              "Node Type": "Hash",
-                              "Parent Relationship": "Inner",
-                              "Startup Cost": 339.86,
-                              "Total Cost": 339.86,
-                              "Plan Rows": 10000,
-                              "Plan Width": 122,
-                              "Actual Startup Time": 12.446,
-                              "Actual Total Time": 12.446,
-                              "Actual Rows": 10000,
-                              "Actual Loops": 1,
-                              "Output": ["p.prod_id", "cat.categoryname"],
-                              "Hash Buckets": 1024,
-                              "Hash Batches": 1,
-                              "Original Hash Batches": 1,
-                              "Peak Memory Usage": 425,
-                              "Shared Hit Blocks": 2,
-                              "Shared Read Blocks": 100,
-                              "Shared Dirtied Blocks": 0,
-                              "Shared Written Blocks": 0,
-                              "Local Hit Blocks": 0,
-                              "Local Read Blocks": 0,
-                              "Local Dirtied Blocks": 0,
-                              "Local Written Blocks": 0,
-                              "Temp Read Blocks": 0,
-                              "Temp Written Blocks": 0,
-                              "I/O Read Time": 0.000,
-                              "I/O Write Time": 0.000,
-                              "Plans": [
-                                {
-                                  "Node Type": "Hash Join",
-                                  "Parent Relationship": "Outer",
-                                  "Join Type": "Inner",
-                                  "Startup Cost": 1.36,
-                                  "Total Cost": 339.86,
-                                  "Plan Rows": 10000,
-                                  "Plan Width": 122,
-                                  "Actual Startup Time": 0.283,
-                                  "Actual Total Time": 9.015,
-                                  "Actual Rows": 10000,
-                                  "Actual Loops": 1,
-                                  "Output": ["p.prod_id", "cat.categoryname"],
-                                  "Hash Cond": "(p.category = cat.category)",
-                                  "Shared Hit Blocks": 2,
-                                  "Shared Read Blocks": 100,
-                                  "Shared Dirtied Blocks": 0,
-                                  "Shared Written Blocks": 0,
-                                  "Local Hit Blocks": 0,
-                                  "Local Read Blocks": 0,
-                                  "Local Dirtied Blocks": 0,
-                                  "Local Written Blocks": 0,
-                                  "Temp Read Blocks": 0,
-                                  "Temp Written Blocks": 0,
-                                  "I/O Read Time": 0.000,
-                                  "I/O Write Time": 0.000,
-                                  "Plans": [
-                                    {
-                                      "Node Type": "Seq Scan",
-                                      "Parent Relationship": "Outer",
-                                      "Relation Name": "products",
-                                      "Schema": "public",
-                                      "Alias": "p",
-                                      "Startup Cost": 0.00,
-                                      "Total Cost": 201.00,
-                                      "Plan Rows": 10000,
-                                      "Plan Width": 8,
-                                      "Actual Startup Time": 0.003,
-                                      "Actual Total Time": 4.330,
-                                      "Actual Rows": 10000,
-                                      "Actual Loops": 1,
-                                      "Output": [
-                                        "p.prod_id",
-                                        "p.category",
-                                        "p.title",
-                                        "p.actor",
-                                        "p.price",
-                                        "p.special",
-                                        "p.common_prod_id"
-                                      ],
-                                      "Shared Hit Blocks": 2,
-                                      "Shared Read Blocks": 99,
-                                      "Shared Dirtied Blocks": 0,
-                                      "Shared Written Blocks": 0,
-                                      "Local Hit Blocks": 0,
-                                      "Local Read Blocks": 0,
-                                      "Local Dirtied Blocks": 0,
-                                      "Local Written Blocks": 0,
-                                      "Temp Read Blocks": 0,
-                                      "Temp Written Blocks": 0,
-                                      "I/O Read Time": 0.000,
-                                      "I/O Write Time": 0.000
-                                    },
-                                    {
-                                      "Node Type": "Hash",
-                                      "Parent Relationship": "Inner",
-                                      "Startup Cost": 1.16,
-                                      "Total Cost": 1.16,
-                                      "Plan Rows": 16,
-                                      "Plan Width": 122,
-                                      "Actual Startup Time": 0.265,
-                                      "Actual Total Time": 0.265,
-                                      "Actual Rows": 16,
-                                      "Actual Loops": 1,
-                                      "Output": [
-                                        "cat.categoryname",
-                                        "cat.category"
-                                      ],
-                                      "Hash Buckets": 1024,
-                                      "Hash Batches": 1,
-                                      "Original Hash Batches": 1,
-                                      "Peak Memory Usage": 1,
-                                      "Shared Hit Blocks": 0,
-                                      "Shared Read Blocks": 1,
-                                      "Shared Dirtied Blocks": 0,
-                                      "Shared Written Blocks": 0,
-                                      "Local Hit Blocks": 0,
-                                      "Local Read Blocks": 0,
-                                      "Local Dirtied Blocks": 0,
-                                      "Local Written Blocks": 0,
-                                      "Temp Read Blocks": 0,
-                                      "Temp Written Blocks": 0,
-                                      "I/O Read Time": 0.000,
-                                      "I/O Write Time": 0.000,
-                                      "Plans": [
-                                        {
-                                          "Node Type": "Seq Scan",
-                                          "Parent Relationship": "Outer",
-                                          "Relation Name": "categories",
-                                          "Schema": "public",
-                                          "Alias": "cat",
-                                          "Startup Cost": 0.00,
-                                          "Total Cost": 1.16,
-                                          "Plan Rows": 16,
-                                          "Plan Width": 122,
-                                          "Actual Startup Time": 0.250,
-                                          "Actual Total Time": 0.252,
-                                          "Actual Rows": 16,
-                                          "Actual Loops": 1,
-                                          "Output": ["cat.categoryname", "cat.category"],
-                                          "Shared Hit Blocks": 0,
-                                          "Shared Read Blocks": 1,
-                                          "Shared Dirtied Blocks": 0,
-                                          "Shared Written Blocks": 0,
-                                          "Local Hit Blocks": 0,
-                                          "Local Read Blocks": 0,
-                                          "Local Dirtied Blocks": 0,
-                                          "Local Written Blocks": 0,
-                                          "Temp Read Blocks": 0,
-                                          "Temp Written Blocks": 0,
-                                          "I/O Read Time": 0.000,
-                                          "I/O Write Time": 0.000
-                                        }
-                                      ]
-                                    }
-                                  ]
-                                }
-                              ]
-                            }
-                          ]
-                        },
-                        {
-                          "Node Type": "Hash",
-                          "Parent Relationship": "Inner",
-                          "Startup Cost": 220.00,
-                          "Total Cost": 220.00,
-                          "Plan Rows": 12000,
-                          "Plan Width": 16,
-                          "Actual Startup Time": 10.159,
-                          "Actual Total Time": 10.159,
-                          "Actual Rows": 12000,
-                          "Actual Loops": 1,
-                          "Output": ["o.netamount", "o.totalamount", "o.orderid"],
-                          "Hash Buckets": 2048,
-                          "Hash Batches": 1,
-                          "Original Hash Batches": 1,
-                          "Peak Memory Usage": 609,
-                          "Shared Hit Blocks": 2,
-                          "Shared Read Blocks": 98,
-                          "Shared Dirtied Blocks": 0,
-                          "Shared Written Blocks": 0,
-                          "Local Hit Blocks": 0,
-                          "Local Read Blocks": 0,
-                          "Local Dirtied Blocks": 0,
-                          "Local Written Blocks": 0,
-                          "Temp Read Blocks": 0,
-                          "Temp Written Blocks": 0,
-                          "I/O Read Time": 0.000,
-                          "I/O Write Time": 0.000,
-                          "Plans": [
-                            {
-                              "Node Type": "Seq Scan",
-                              "Parent Relationship": "Outer",
-                              "Relation Name": "orders",
-                              "Schema": "public",
-                              "Alias": "o",
-                              "Startup Cost": 0.00,
-                              "Total Cost": 220.00,
-                              "Plan Rows": 12000,
-                              "Plan Width": 16,
-                              "Actual Startup Time": 0.008,
-                              "Actual Total Time": 5.548,
-                              "Actual Rows": 12000,
-                              "Actual Loops": 1,
-                              "Output": ["o.netamount", "o.totalamount", "o.orderid"],
-                              "Shared Hit Blocks": 2,
-                              "Shared Read Blocks": 98,
-                              "Shared Dirtied Blocks": 0,
-                              "Shared Written Blocks": 0,
-                              "Local Hit Blocks": 0,
-                              "Local Read Blocks": 0,
-                              "Local Dirtied Blocks": 0,
-                              "Local Written Blocks": 0,
-                              "Temp Read Blocks": 0,
-                              "Temp Written Blocks": 0,
-                              "I/O Read Time": 0.000,
-                              "I/O Write Time": 0.000
-                            }
-                          ]
-                        }
-                      ]
-                    },
-                    {
-                      "Node Type": "Hash",
-                      "Parent Relationship": "Inner",
-                      "Startup Cost": 3377.25,
-                      "Total Cost": 3377.25,
-                      "Plan Rows": 60350,
-                      "Plan Width": 7,
-                      "Actual Startup Time": 95.610,
-                      "Actual Total Time": 95.610,
-                      "Actual Rows": 60350,
-                      "Actual Loops": 1,
-                      "Output": ["c.state", "ch.orderid"],
-                      "Hash Buckets": 8192,
-                      "Hash Batches": 1,
-                      "Original Hash Batches": 1,
-                      "Peak Memory Usage": 2239,
-                      "Shared Hit Blocks": 4,
-                      "Shared Read Blocks": 811,
-                      "Shared Dirtied Blocks": 0,
-                      "Shared Written Blocks": 0,
-                      "Local Hit Blocks": 0,
-                      "Local Read Blocks": 0,
-                      "Local Dirtied Blocks": 0,
-                      "Local Written Blocks": 0,
-                      "Temp Read Blocks": 0,
-                      "Temp Written Blocks": 0,
-                      "I/O Read Time": 0.000,
-                      "I/O Write Time": 0.000,
-                      "Plans": [
-                        {
-                          "Node Type": "Hash Join",
-                          "Parent Relationship": "Outer",
-                          "Join Type": "Inner",
-                          "Startup Cost": 938.00,
-                          "Total Cost": 3377.25,
-                          "Plan Rows": 60350,
-                          "Plan Width": 7,
-                          "Actual Startup Time": 24.115,
-                          "Actual Total Time": 74.639,
-                          "Actual Rows": 60350,
-                          "Actual Loops": 1,
-                          "Output": ["c.state", "ch.orderid"],
-                          "Hash Cond": "(ch.customerid = c.customerid)",
-                          "Shared Hit Blocks": 4,
-                          "Shared Read Blocks": 811,
-                          "Shared Dirtied Blocks": 0,
-                          "Shared Written Blocks": 0,
-                          "Local Hit Blocks": 0,
-                          "Local Read Blocks": 0,
-                          "Local Dirtied Blocks": 0,
-                          "Local Written Blocks": 0,
-                          "Temp Read Blocks": 0,
-                          "Temp Written Blocks": 0,
-                          "I/O Read Time": 0.000,
-                          "I/O Write Time": 0.000,
-                          "Plans": [
-                            {
-                              "Node Type": "Seq Scan",
-                              "Parent Relationship": "Outer",
-                              "Relation Name": "cust_hist",
-                              "Schema": "public",
-                              "Alias": "ch",
-                              "Startup Cost": 0.00,
-                              "Total Cost": 930.50,
-                              "Plan Rows": 60350,
-                              "Plan Width": 8,
-                              "Actual Startup Time": 0.294,
-                              "Actual Total Time": 11.812,
-                              "Actual Rows": 60350,
-                              "Actual Loops": 1,
-                              "Output": ["ch.customerid", "ch.orderid", "ch.prod_id"],
-                              "Shared Hit Blocks": 2,
-                              "Shared Read Blocks": 325,
-                              "Shared Dirtied Blocks": 0,
-                              "Shared Written Blocks": 0,
-                              "Local Hit Blocks": 0,
-                              "Local Read Blocks": 0,
-                              "Local Dirtied Blocks": 0,
-                              "Local Written Blocks": 0,
-                              "Temp Read Blocks": 0,
-                              "Temp Written Blocks": 0,
-                              "I/O Read Time": 0.000,
-                              "I/O Write Time": 0.000
-                            },
-                            {
-                              "Node Type": "Hash",
-                              "Parent Relationship": "Inner",
-                              "Startup Cost": 688.00,
-                              "Total Cost": 688.00,
-                              "Plan Rows": 20000,
-                              "Plan Width": 7,
-                              "Actual Startup Time": 23.786,
-                              "Actual Total Time": 23.786,
-                              "Actual Rows": 20000,
-                              "Actual Loops": 1,
-                              "Output": ["c.state", "c.customerid"],
-                              "Hash Buckets": 2048,
-                              "Hash Batches": 1,
-                              "Original Hash Batches": 1,
-                              "Peak Memory Usage": 743,
-                              "Shared Hit Blocks": 2,
-                              "Shared Read Blocks": 486,
-                              "Shared Dirtied Blocks": 0,
-                              "Shared Written Blocks": 0,
-                              "Local Hit Blocks": 0,
-                              "Local Read Blocks": 0,
-                              "Local Dirtied Blocks": 0,
-                              "Local Written Blocks": 0,
-                              "Temp Read Blocks": 0,
-                              "Temp Written Blocks": 0,
-                              "I/O Read Time": 0.000,
-                              "I/O Write Time": 0.000,
-                              "Plans": [
-                                {
-                                  "Node Type": "Seq Scan",
-                                  "Parent Relationship": "Outer",
-                                  "Relation Name": "customers",
-                                  "Schema": "public",
-                                  "Alias": "c",
-                                  "Startup Cost": 0.00,
-                                  "Total Cost": 688.00,
-                                  "Plan Rows": 20000,
-                                  "Plan Width": 7,
-                                  "Actual Startup Time": 0.005,
-                                  "Actual Total Time": 16.771,
-                                  "Actual Rows": 20000,
-                                  "Actual Loops": 1,
-                                  "Output": ["c.state", "c.customerid"],
-                                  "Shared Hit Blocks": 2,
-                                  "Shared Read Blocks": 486,
-                                  "Shared Dirtied Blocks": 0,
-                                  "Shared Written Blocks": 0,
-                                  "Local Hit Blocks": 0,
-                                  "Local Read Blocks": 0,
-                                  "Local Dirtied Blocks": 0,
-                                  "Local Written Blocks": 0,
-                                  "Temp Read Blocks": 0,
-                                  "Temp Written Blocks": 0,
-                                  "I/O Read Time": 0.000,
-                                  "I/O Write Time": 0.000
-                                }
-                              ]
-                            }
-                          ]
-                        }
-                      ]
-                    }
-                  ]
-                }
-              ]
-            }
-          ]
-        }
-      ]
-    },
-    "Planning Time": 26.171,
-    "Triggers": [
-    ],
-    "Execution Time": 726.800
-  }
-]`;
-
-const explainSamples = [{
-  value: sampleExplain1,
-  label: 'Sample 1'
-}];
-
-export default explainSamples;
diff --git a/ui/packages/platform/src/assets/messages.ts b/ui/packages/platform/src/assets/messages.ts
deleted file mode 100644
index c136a318..00000000
--- a/ui/packages/platform/src/assets/messages.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const messages = {
-  noPermission: 'You do not have permission to do this.',
-  noPermissionPage: 'You do not have permission to view this page.',
-}
diff --git a/ui/packages/platform/src/assets/plans.ts b/ui/packages/platform/src/assets/plans.ts
deleted file mode 100644
index 86d0b075..00000000
--- a/ui/packages/platform/src/assets/plans.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { colors } from '@postgres.ai/shared/styles/colors'
-
-export const plans = {
-  ce: {
-    id: 'ce',
-    name: 'CE',
-    title: 'Postgres.ai Community Edition',
-    color: colors.secondary2.main,
-    limits: {
-      maxDblabInstances: 1,
-      maxJoeInstances: 1,
-      daysJoeHistory: 14,
-      emailDomainRestricted: true,
-    },
-  },
-  ee_gold_monthly: {
-    color: colors.pgaiOrange,
-  },
-}
diff --git a/ui/packages/platform/src/assets/visualizeTypes.ts b/ui/packages/platform/src/assets/visualizeTypes.ts
deleted file mode 100644
index 42ddfc93..00000000
--- a/ui/packages/platform/src/assets/visualizeTypes.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const visualizeTypes = {
-  depesz: 'depesz',
-  pev2: 'pev2',
-  flame: 'flame-graph',
-}
diff --git a/ui/packages/platform/src/components/AccessTokens/AccessTokens.tsx b/ui/packages/platform/src/components/AccessTokens/AccessTokens.tsx
deleted file mode 100644
index 734e24bb..00000000
--- a/ui/packages/platform/src/components/AccessTokens/AccessTokens.tsx
+++ /dev/null
@@ -1,545 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, MouseEvent } from 'react'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  TextField,
-  Button,
-  FormControlLabel,
-  Checkbox,
-} from '@material-ui/core'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import {
-  ClassesType,
-  RefluxTypes,
-  TokenRequestProps,
-} from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import ConsolePageTitle from '../ConsolePageTitle'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DisplayTokenWrapper } from 'components/DisplayToken/DisplayTokenWrapper'
-import { AccessTokensProps } from 'components/AccessTokens/AccessTokensWrapper'
-import { FilteredTableMessage } from 'components/AccessTokens/FilteredTableMessage/FilteredTableMessage'
-
-interface AccessTokensWithStylesProps extends AccessTokensProps {
-  classes: ClassesType
-}
-
-interface UserTokenData {
-  id: number
-  name: string
-  is_personal: boolean
-  username: string
-  created_formatted: string
-  expires_formatted: string
-  revoking: boolean
-}
-
-interface AccessTokensState {
-  filterValue: string
-  data: {
-    auth: {
-      token: string
-    } | null
-    userTokens: {
-      orgId: number | null
-      isProcessing: boolean
-      isProcessed: boolean
-      data: UserTokenData[]
-      error: {
-        message: boolean
-      }
-    }
-    tokenRequest: TokenRequestProps
-  }
-  tokenName: string | null
-  tokenExpires: string | null
-  processed: boolean
-  isPersonal: boolean
-}
-
-class AccessTokens extends Component<
-  AccessTokensWithStylesProps,
-  AccessTokensState
-> {
-  state = {
-    filterValue: '',
-    data: {
-      auth: {
-        token: '',
-      },
-      userTokens: {
-        orgId: null,
-        isProcessing: false,
-        isProcessed: false,
-        data: [],
-        error: {
-          message: false,
-        },
-      },
-      tokenRequest: {
-        isProcessing: false,
-        isProcessed: false,
-        data: {
-          name: '',
-          is_personal: false,
-          expires_at: '',
-          token: '',
-        },
-        errorMessage: '',
-        error: false,
-      },
-    },
-    tokenName: '',
-    tokenExpires: '',
-    processed: false,
-    isPersonal: true,
-  }
-
-  handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    const name = event.target.name
-    const value = event.target.value
-
-    if (name === 'tokenName') {
-      this.setState({ tokenName: value })
-    } else if (name === 'tokenExpires') {
-      this.setState({ tokenExpires: value })
-    } else if (name === 'isPersonal') {
-      this.setState({ isPersonal: event.target.checked })
-    }
-  }
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const date = new Date()
-    const expiresDate =
-      date.getFullYear() +
-      1 +
-      '-' +
-      ('0' + (date.getMonth() + 1)).slice(-2) +
-      '-' +
-      ('0' + date.getDate()).slice(-2)
-
-    document.getElementsByTagName('html')[0].style.overflow = 'hidden'
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth: AccessTokensState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const userTokens: AccessTokensState['data']['userTokens'] =
-        this.data && this.data.userTokens ? this.data.userTokens : null
-      const tokenRequest: TokenRequestProps =
-        this.data && this.data.tokenRequest ? this.data.tokenRequest : null
-
-      that.setState({ data: this.data })
-
-      if (
-        auth &&
-        auth.token &&
-        (!userTokens.isProcessed || orgId !== userTokens.orgId) &&
-        !userTokens.isProcessing &&
-        !userTokens.error
-      ) {
-        Actions.getAccessTokens(auth.token, orgId)
-      }
-
-      if (
-        tokenRequest &&
-        tokenRequest.isProcessed &&
-        !tokenRequest.error &&
-        tokenRequest.data &&
-        tokenRequest.data.name === that.state.tokenName &&
-        tokenRequest.data.expires_at &&
-        tokenRequest.data.token
-      ) {
-        that.setState({
-          tokenName: '',
-          tokenExpires: expiresDate,
-          processed: false,
-          isPersonal: true,
-        })
-      }
-    })
-
-    that.setState({
-      tokenName: '',
-      tokenExpires: expiresDate,
-      processed: false,
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    Actions.hideGeneratedAccessToken()
-    this.unsubscribe()
-  }
-
-  addToken = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const tokenRequest =
-      this.state.data && this.state.data.tokenRequest
-        ? this.state.data.tokenRequest
-        : null
-
-    if (
-      this.state.tokenName === null ||
-      this.state.tokenName === '' ||
-      this.state.tokenExpires === null ||
-      this.state.tokenExpires === ''
-    ) {
-      this.setState({ processed: true })
-      return
-    }
-
-    if (auth && auth.token && !tokenRequest?.isProcessing) {
-      Actions.getAccessToken(
-        auth.token,
-        this.state.tokenName,
-        this.state.tokenExpires,
-        orgId,
-        this.state.isPersonal,
-      )
-    }
-  }
-
-  getTodayDate() {
-    const date = new Date()
-
-    return (
-      date.getFullYear() +
-      '-' +
-      ('0' + (date.getMonth() + 1)).slice(-2) +
-      '-' +
-      ('0' + date.getDate()).slice(-2)
-    )
-  }
-
-  revokeToken = (
-    _event: MouseEvent<HTMLButtonElement>,
-    id: number,
-    name: string,
-  ) => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    /* eslint no-alert: 0 */
-    if (
-      window.confirm(
-        'Are you sure you want to revoke token "' + name + '"?',
-      ) === true
-    ) {
-      Actions.revokeAccessToken(auth?.token, orgId, id)
-    }
-  }
-
-  filterTokensInputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.setState({ filterValue: event.target.value })
-  }
-
-  render() {
-    const { classes, orgPermissions, orgId } = this.props
-    const data =
-      this.state && this.state.data ? this.state.data.userTokens : null
-    const tokenRequest =
-      this.state && this.state.data && this.state.data.tokenRequest
-        ? this.state.data.tokenRequest
-        : null
-    const filteredTokens = data?.data?.filter(
-      (token: UserTokenData) =>
-        token.name
-          ?.toLowerCase()
-          .indexOf((this.state.filterValue || '')?.toLowerCase()) !== -1,
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title="Access tokens"
-        filterProps={
-          data && data.data?.length > 0
-            ? {
-                filterValue: this.state.filterValue,
-                filterHandler: this.filterTokensInputHandler,
-                placeholder: 'Search access tokens by name',
-              }
-            : null
-        }
-      />
-    )
-
-    let tokenDisplay = null
-    if (
-      tokenRequest &&
-      tokenRequest.isProcessed &&
-      !tokenRequest.error &&
-      tokenRequest.data &&
-      tokenRequest.data.name &&
-      tokenRequest.data.expires_at &&
-      tokenRequest.data.token
-    ) {
-      tokenDisplay = (
-        <div>
-          <h3>
-            {tokenRequest.data.is_personal
-              ? 'Your new personal access token'
-              : 'New administrative access token'}
-          </h3>
-          <DisplayTokenWrapper />
-        </div>
-      )
-    }
-
-    let tokenError = null
-    if (tokenRequest && tokenRequest.error) {
-      tokenError = (
-        <div className={classes?.errorMessage}>{tokenRequest.errorMessage}</div>
-      )
-    }
-
-    const tokenForm = (
-      <div>
-        <h2>Add token</h2>
-        <div className={classes?.container}>
-          {tokenError}
-
-          <TextField
-            id="tokenName"
-            variant="outlined"
-            label="Name"
-            placeholder="Token name"
-            margin="normal"
-            required={true}
-            value={this.state.tokenName}
-            disabled={tokenRequest !== null && tokenRequest.isProcessing}
-            error={
-              this.state.processed &&
-              (this.state.tokenName === null || this.state.tokenName === '')
-            }
-            className={classes?.nameField}
-            onChange={this.handleChange}
-            inputProps={{
-              name: 'tokenName',
-              id: 'tokenName',
-              shrink: 'true',
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel,
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper,
-            }}
-          />
-
-          <TextField
-            id="tokenExpires"
-            variant="outlined"
-            label="Expires"
-            type="date"
-            required={true}
-            defaultValue={this.state.tokenExpires}
-            value={this.state.tokenExpires}
-            disabled={tokenRequest !== null && tokenRequest.isProcessing}
-            className={classes?.textField}
-            error={
-              this.state.processed &&
-              (this.state.tokenExpires === null ||
-                this.state.tokenExpires === '')
-            }
-            onChange={this.handleChange}
-            inputProps={{
-              name: 'tokenExpires',
-              id: 'tokenExpires',
-              min: this.getTodayDate(),
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel,
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper,
-            }}
-          />
-
-          <FormControlLabel
-            control={
-              <Checkbox
-                checked={this.state.isPersonal}
-                onChange={this.handleChange}
-                name="isPersonal"
-                color="primary"
-                disabled={!orgPermissions.settingsTokenCreateImpersonal}
-                inputProps={{
-                  name: 'isPersonal',
-                  id: 'isPersonal',
-                }}
-              />
-            }
-            label="Personal token"
-          />
-
-          <Button
-            variant="contained"
-            color="primary"
-            className={classes?.addTokenButton}
-            disabled={tokenRequest !== null && tokenRequest.isProcessing}
-            onClick={this.addToken}
-          >
-            Add token
-          </Button>
-        </div>
-      </div>
-    )
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: 'Access tokens' }]}
-      />
-    )
-
-    if (this.state && this.state.data && this.state.data.userTokens?.error) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <h2>Access tokens</h2>
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (data && data.isProcessing) ||
-      (data && data.orgId !== orgId)
-    ) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes?.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {tokenDisplay}
-
-        {tokenForm}
-
-        <div className={classes?.remark}>
-          Users may manage their personal tokens only. Admins may manage their
-          &nbsp;personal tokens, as well as administrative (impersonal) tokens
-          &nbsp;used to organize infrastructure. Tokens of all types work in
-          &nbsp;the context of a particular organization.
-        </div>
-
-        <br />
-        <h2>Active access tokens</h2>
-
-        {filteredTokens && filteredTokens.length > 0 ? (
-          <HorizontalScrollContainer>
-            <Table className={classes?.table}>
-              <TableHead>
-                <TableRow className={classes?.row}>
-                  <TableCell>Name</TableCell>
-                  <TableCell>Type</TableCell>
-                  <TableCell>Creator</TableCell>
-                  <TableCell>Created</TableCell>
-                  <TableCell>Expires</TableCell>
-                  <TableCell align="right">Actions</TableCell>
-                </TableRow>
-              </TableHead>
-
-              <TableBody>
-                {filteredTokens &&
-                  filteredTokens.length > 0 &&
-                  filteredTokens.map((t: UserTokenData) => {
-                    return (
-                      <TableRow className={classes?.row} key={t.id}>
-                        <TableCell className={classes?.cell}>
-                          {t.name}
-                        </TableCell>
-                        <TableCell className={classes?.cell}>
-                          {t.is_personal ? 'Personal' : 'Administrative'}
-                        </TableCell>
-                        <TableCell className={classes?.cell}>
-                          {t.username}
-                        </TableCell>
-                        <TableCell className={classes?.cell}>
-                          {t.created_formatted}
-                        </TableCell>
-                        <TableCell className={classes?.cell}>
-                          {t.expires_formatted}
-                        </TableCell>
-                        <TableCell className={classes?.cell} align="right">
-                          <Button
-                            variant="outlined"
-                            color="secondary"
-                            className={classes?.revokeButton}
-                            disabled={
-                              (!t.is_personal &&
-                                !orgPermissions.settingsTokenRevokeImpersonal) ||
-                              t.revoking
-                            }
-                            onClick={(event) =>
-                              this.revokeToken(event, t.id, t.name)
-                            }
-                          >
-                            Revoke
-                          </Button>
-                        </TableCell>
-                      </TableRow>
-                    )
-                  })}
-              </TableBody>
-            </Table>
-          </HorizontalScrollContainer>
-        ) : (
-          <FilteredTableMessage
-            filteredItems={filteredTokens}
-            emptyState="This user has no active access tokens"
-            filterValue={this.state.filterValue}
-            clearFilter={() =>
-              this.setState({
-                filterValue: '',
-              })
-            }
-          />
-        )}
-
-        <div className={classes?.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default AccessTokens
diff --git a/ui/packages/platform/src/components/AccessTokens/AccessTokensWrapper.tsx b/ui/packages/platform/src/components/AccessTokens/AccessTokensWrapper.tsx
deleted file mode 100644
index 237e8e47..00000000
--- a/ui/packages/platform/src/components/AccessTokens/AccessTokensWrapper.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import AccessTokens from 'components/AccessTokens/AccessTokens'
-import { OrgPermissions } from 'components/types'
-import { styles } from '@postgres.ai/shared/styles/styles'
-
-export interface AccessTokensProps {
-  project: string | undefined
-  orgId: number
-  org: string | number
-  orgPermissions: OrgPermissions
-}
-
-export const AccessTokensWrapper = (props: AccessTokensProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        ...(styles.root as Object),
-        display: 'flex',
-        flexDirection: 'column',
-        paddingBottom: '20px',
-      },
-      container: {
-        display: 'flex',
-        flexWrap: 'wrap',
-      },
-      textField: {
-        ...styles.inputField,
-        maxWidth: 400,
-        marginBottom: 15,
-        marginRight: theme.spacing(1),
-        marginTop: '16px',
-      },
-      nameField: {
-        ...styles.inputField,
-        maxWidth: 400,
-        marginBottom: 15,
-        width: '400px',
-        marginRight: theme.spacing(1),
-      },
-      addTokenButton: {
-        marginTop: 15,
-        height: '33px',
-        marginBottom: 10,
-        maxWidth: 'max-content',
-      },
-      revokeButton: {
-        paddingRight: 5,
-        paddingLeft: 5,
-        paddingTop: 3,
-        paddingBottom: 3,
-      },
-      errorMessage: {
-        color: 'red',
-        width: '100%',
-      },
-      remark: {
-        width: '100%',
-        maxWidth: 960,
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <AccessTokens {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/AccessTokens/FilteredTableMessage/FilteredTableMessage.tsx b/ui/packages/platform/src/components/AccessTokens/FilteredTableMessage/FilteredTableMessage.tsx
deleted file mode 100644
index c10af14b..00000000
--- a/ui/packages/platform/src/components/AccessTokens/FilteredTableMessage/FilteredTableMessage.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Button } from '@material-ui/core'
-import { AuditLogData } from 'components/Audit/Audit'
-
-export const FilteredTableMessage = ({
-  filterValue,
-  filteredItems,
-  clearFilter,
-  emptyState,
-}: {
-  filterValue: string
-  filteredItems: string[] | never[] | AuditLogData[] | undefined | null
-  clearFilter: () => void
-  emptyState: string | JSX.Element
-}) => {
-  if (filterValue && filteredItems?.length === 0) {
-    return (
-      <>
-        <div>
-          No results found for <b>{filterValue}</b>
-        </div>
-        <Button
-          color="primary"
-          variant="contained"
-          onClick={clearFilter}
-          style={{
-            marginTop: 15,
-            height: '33px',
-            marginBottom: 10,
-            maxWidth: 'max-content',
-          }}
-        >
-          Clear filter
-        </Button>
-      </>
-    )
-  }
-
-  return <>{emptyState}</>
-}
diff --git a/ui/packages/platform/src/components/AddDbLabInstanceFormWrapper/AddDbLabInstanceFormWrapper.tsx b/ui/packages/platform/src/components/AddDbLabInstanceFormWrapper/AddDbLabInstanceFormWrapper.tsx
deleted file mode 100644
index 8800ba33..00000000
--- a/ui/packages/platform/src/components/AddDbLabInstanceFormWrapper/AddDbLabInstanceFormWrapper.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import AddDbLabInstanceForm from 'components/AddDbLabInstanceFormWrapper/AddDblabInstanceForm'
-import { OrgPermissions } from 'components/types'
-import { RouteComponentProps } from 'react-router'
-
-export interface DbLabInstanceFormProps {
-  edit?: boolean
-  orgId: number
-  project: string | undefined
-  history: RouteComponentProps['history']
-  orgPermissions: OrgPermissions
-}
-
-export const AddDbLabInstanceFormWrapper = (props: DbLabInstanceFormProps) => {
-  const useStyles = makeStyles(
-    {
-      textField: {
-        ...styles.inputField,
-        maxWidth: 400,
-      },
-      errorMessage: {
-        marginTop: 10,
-        color: 'red',
-      },
-      fieldBlock: {
-        width: '100%',
-      },
-      urlOkIcon: {
-        color: 'green',
-      },
-      urlOk: { display: 'flex', gap: 5, alignItems: 'center', color: 'green' },
-      urlTextMargin: {
-        marginTop: 10,
-      },
-      urlFailIcon: {
-        color: 'red',
-      },
-      urlFail: {
-        display: 'flex',
-        gap: 5,
-        alignItems: 'center',
-        color: 'red',
-      },
-      warning: {
-        color: '#801200',
-        fontSize: '0.9em',
-      },
-      warningIcon: {
-        color: '#801200',
-        fontSize: '1.2em',
-        position: 'relative',
-        marginBottom: -3,
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <AddDbLabInstanceForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/AddDbLabInstanceFormWrapper/AddDblabInstanceForm.tsx b/ui/packages/platform/src/components/AddDbLabInstanceFormWrapper/AddDblabInstanceForm.tsx
deleted file mode 100644
index 00746b31..00000000
--- a/ui/packages/platform/src/components/AddDbLabInstanceFormWrapper/AddDblabInstanceForm.tsx
+++ /dev/null
@@ -1,646 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import {
-  Checkbox,
-  Grid,
-  Button,
-  TextField,
-  FormControlLabel,
-} from '@material-ui/core'
-import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline'
-import BlockIcon from '@material-ui/icons/Block'
-import WarningIcon from '@material-ui/icons/Warning'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import {
-  ClassesType,
-  ProjectProps,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import ConsolePageTitle from './../ConsolePageTitle'
-import Store from '../../stores/store'
-import Urls from '../../utils/urls'
-import { generateToken, isHttps } from '../../utils/utils'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DbLabInstanceFormProps } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-
-interface DbLabInstanceFormWithStylesProps extends DbLabInstanceFormProps {
-  classes: ClassesType
-}
-
-interface DbLabInstanceFormState {
-  data: {
-    auth: {
-      token: string | null
-    } | null
-    projects: ProjectProps
-    newDbLabInstance: {
-      isUpdating: boolean
-      isChecking: boolean
-      isChecked: boolean
-      isCheckProcessed: boolean
-      errorMessage: string
-      error: boolean
-      isProcessed: boolean
-      data: {
-        id: string
-      }
-    } | null
-    dbLabInstances: {
-      isProcessing: boolean
-      error: boolean
-      isProcessed: boolean
-      data: unknown
-    } | null
-  } | null
-  url: string
-  token: string | null
-  useTunnel: boolean
-  instanceID: string
-  project: string
-  project_label: string
-  errorFields: string[]
-  sshServerUrl: string
-}
-
-class DbLabInstanceForm extends Component<
-  DbLabInstanceFormWithStylesProps,
-  DbLabInstanceFormState
-> {
-  state = {
-    url: 'https://',
-    token: null,
-    useTunnel: false,
-    instanceID: '',
-    project: this.props.project ? this.props.project : '',
-    project_label: '',
-    errorFields: [''],
-    sshServerUrl: '',
-    data: {
-      auth: {
-        token: null,
-      },
-      projects: {
-        data: [],
-        error: false,
-        isProcessing: false,
-        isProcessed: false,
-      },
-      newDbLabInstance: {
-        isUpdating: false,
-        isChecked: false,
-        isChecking: false,
-        isCheckProcessed: false,
-        isProcessed: false,
-        error: false,
-        errorMessage: '',
-        data: {
-          id: '',
-        },
-      },
-      dbLabInstances: {
-        isProcessing: false,
-        error: false,
-        isProcessed: false,
-        data: '',
-      },
-    },
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const { orgId } = this.props
-    const url = window.location.href.split('/')
-    const instanceID = url[url.length - 1]
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      that.setState({ data: this.data, instanceID: instanceID })
-
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const projects =
-        this.data && this.data.projects ? this.data.projects : null
-      const dbLabInstances =
-        this.data && this.data.dbLabInstances ? this.data.dbLabInstances : null
-
-      if (dbLabInstances.data) {
-        that.setState({
-          project_label:
-            that.state.project_label ||
-            dbLabInstances.data[instanceID]?.project_label_or_name,
-          token:
-            that.state.token || dbLabInstances.data[instanceID]?.verify_token,
-          useTunnel:
-            that.state.useTunnel || dbLabInstances.data[instanceID]?.use_tunnel,
-          url: that.state.url || dbLabInstances.data[instanceID]?.url,
-          sshServerUrl:
-            that.state.sshServerUrl ||
-            dbLabInstances.data[instanceID]?.ssh_server_url,
-        })
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects.isProcessing &&
-        !projects.error &&
-        !projects.isProcessed
-      ) {
-        Actions.getProjects(auth.token, orgId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !dbLabInstances?.isProcessing &&
-        !dbLabInstances?.error &&
-        !dbLabInstances?.isProcessed
-      ) {
-        Actions.getDbLabInstances(auth.token, orgId, 0)
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-    Actions.resetNewDbLabInstance()
-  }
-
-  buttonHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.newDbLabInstance : null
-    const errorFields = []
-
-    if (!this.state.url) {
-      errorFields.push('url')
-    }
-
-    if (!this.state.project) {
-      errorFields.push('project')
-    }
-
-    if (!this.state.token) {
-      errorFields.push('token')
-    }
-
-    if (errorFields.length > 0) {
-      this.setState({ errorFields: errorFields })
-      return
-    }
-
-    this.setState({ errorFields: [] })
-
-    if (
-      auth &&
-      data &&
-      !data.isUpdating &&
-      this.state.url &&
-      this.state.token &&
-      this.state.project
-    ) {
-      Actions[`${this.props.edit ? 'edit' : 'add'}DbLabInstance`](auth.token, {
-        orgId: orgId,
-        project: this.state.project,
-        instanceId: this.props.edit ? this.state.instanceID : null,
-        projectLabel: this.state.project_label,
-        url: this.state.url,
-        instanceToken: this.state.token,
-        useTunnel: this.state.useTunnel,
-        sshServerUrl: this.state.sshServerUrl,
-      })
-    }
-  }
-
-  clearFieldError = (fieldName: string) => {
-    const errorFields = this.state.errorFields.filter((field) => {
-      return field !== fieldName
-    })
-
-    this.setState({ errorFields: errorFields })
-  }
-
-  checkUrlHandler = () => {
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.newDbLabInstance : null
-    const errorFields: string[] = []
-
-    if (!this.state.url) {
-      errorFields.push('url')
-    }
-
-    if (!this.state.token) {
-      errorFields.push('token')
-    }
-
-    if (errorFields.length > 0) {
-      this.setState({ errorFields: errorFields })
-      return
-    }
-
-    if (auth && data && !data.isChecking && this.state.url) {
-      Actions.checkDbLabInstanceUrl(
-        auth.token,
-        this.state.url,
-        this.state.token,
-        this.state.useTunnel,
-      )
-    }
-  }
-
-  returnHandler = () => {
-    this.props.history.push(Urls.linkDbLabInstances(this.props))
-  }
-
-  processedHandler = () => {
-    const data = this.state.data ? this.state.data.newDbLabInstance : null
-
-    this.props.history.push(
-      Urls.linkDbLabInstance(this.props, data?.data?.id as string),
-    )
-  }
-
-  generateTokenHandler = () => {
-    this.setState({ token: generateToken() })
-    this.clearFieldError('token')
-  }
-
-  render() {
-    const { classes, orgPermissions } = this.props
-    const data =
-      this.state && this.state.data ? this.state.data.newDbLabInstance : null
-    const projects =
-      this.state && this.state.data && this.state.data.projects
-        ? this.state.data.projects
-        : null
-    const projectsList = []
-    const dbLabInstances =
-      this.state && this.state.data && this.state.data.dbLabInstances
-        ? this.state.data.dbLabInstances
-        : null
-
-    if (data && data.isProcessed && !data.error) {
-      this.processedHandler()
-      Actions.resetNewDbLabInstance()
-    }
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[
-          { name: 'Database Lab Instances', url: 'instances' },
-          { name: this.props.edit ? 'Edit instance' : 'Add instance' },
-        ]}
-      />
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title={this.props.edit ? 'Edit instance' : 'Add instance'}
-      />
-    )
-
-    const permitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-    const disabledOnEdit = this.props.edit
-    const instancesLoaded = dbLabInstances && dbLabInstances.data
-
-    if (!projects || !projects.data || !instancesLoaded) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    if (projects.data && projects.data?.length > 0) {
-      projects.data.map((p: { name: string; id: number }) => {
-        return projectsList.push({ title: p.name, value: p.id })
-      })
-    }
-
-    const isDataUpdating = data && (data.isUpdating || data.isChecking)
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {!permitted && (
-          <WarningWrapper>
-            You do not have permission to {this.props.edit ? 'edit' : 'add'}{' '}
-            Database Lab instances.
-          </WarningWrapper>
-        )}
-
-        {!disabledOnEdit && (
-          <span>
-            Database Lab provisioning is currently semi-automated.
-            <br />
-            First, you need to prepare a Database Lab instance on a
-            separate&nbsp; machine. Once the instance is ready, register it
-            here.
-          </span>
-        )}
-
-        <Grid container>
-          <div className={classes.fieldBlock}>
-            <TextField
-              disabled={!permitted}
-              variant="outlined"
-              id="project"
-              label="Project"
-              value={this.state.project}
-              required
-              className={classes.textField}
-              onChange={(e) => {
-                this.setState({
-                  project: e.target.value,
-                })
-                this.clearFieldError('project')
-                Actions.resetNewDbLabInstance()
-              }}
-              margin="normal"
-              error={this.state.errorFields.indexOf('project') !== -1}
-              fullWidth
-              inputProps={{
-                name: 'project',
-                id: 'project',
-                shrink: true,
-              }}
-              InputLabelProps={{
-                shrink: true,
-                style: styles.inputFieldLabel,
-              }}
-              FormHelperTextProps={{
-                style: styles.inputFieldHelper,
-              }}
-            />
-          </div>
-
-          <div className={classes.fieldBlock}>
-            <TextField
-              disabled={!permitted}
-              variant="outlined"
-              id="project_label"
-              label="Project label"
-              value={this.state.project_label}
-              className={classes.textField}
-              onChange={(e) => {
-                this.setState({
-                  project_label: e.target.value,
-                })
-                this.clearFieldError('project_label')
-                Actions.resetNewDbLabInstance()
-              }}
-              margin="normal"
-              error={this.state.errorFields.indexOf('project_label') !== -1}
-              fullWidth
-              inputProps={{
-                name: 'project_label',
-                id: 'project_label',
-                shrink: true,
-              }}
-              InputLabelProps={{
-                shrink: true,
-                style: styles.inputFieldLabel,
-              }}
-              FormHelperTextProps={{
-                style: styles.inputFieldHelper,
-              }}
-            />
-          </div>
-
-          {!disabledOnEdit && (
-            <div className={classes.fieldBlock}>
-              <TextField
-                disabled={!permitted}
-                variant="outlined"
-                id="token"
-                label="Verification token"
-                value={this.state.token}
-                required
-                className={classes.textField}
-                onChange={(e) => {
-                  this.setState({
-                    token: e.target.value,
-                  })
-                  this.clearFieldError('token')
-                  Actions.resetNewDbLabInstance()
-                }}
-                margin="normal"
-                error={this.state.errorFields.indexOf('token') !== -1}
-                fullWidth
-                inputProps={{
-                  name: 'token',
-                  id: 'token',
-                  shrink: true,
-                }}
-                InputLabelProps={{
-                  shrink: true,
-                  style: styles.inputFieldLabel,
-                }}
-                FormHelperTextProps={{
-                  style: styles.inputFieldHelper,
-                }}
-              />
-              <div>
-                <Button
-                  variant="contained"
-                  color="primary"
-                  disabled={isDataUpdating || !permitted || disabledOnEdit}
-                  onClick={this.generateTokenHandler}
-                >
-                  Generate
-                </Button>
-              </div>
-            </div>
-          )}
-
-          <div
-            className={classes.fieldBlock}
-            style={{ marginTop: !disabledOnEdit ? 10 : 0 }}
-          >
-            <TextField
-              disabled={!permitted}
-              variant="outlined"
-              id="url"
-              label="URL"
-              required
-              value={this.state.url}
-              className={classes.textField}
-              onChange={(e) => {
-                this.setState({
-                  url: e.target.value,
-                })
-                this.clearFieldError('url')
-                Actions.resetNewDbLabInstance()
-              }}
-              margin="normal"
-              helperText={
-                this.state.url &&
-                !isHttps(this.state.url) &&
-                !this.state.useTunnel ? (
-                  <span>
-                    <WarningIcon className={classes.warningIcon} />
-                    <span className={classes.warning}>
-                      The connection to the Database Lab API is not secure. Use
-                      HTTPS.
-                    </span>
-                  </span>
-                ) : null
-              }
-              error={this.state.errorFields.indexOf('url') !== -1}
-              fullWidth
-              inputProps={{
-                name: 'url',
-                id: 'url',
-                shrink: true,
-              }}
-              InputLabelProps={{
-                shrink: true,
-                style: styles.inputFieldLabel,
-              }}
-              FormHelperTextProps={{
-                style: styles.inputFieldHelper,
-              }}
-            />
-          </div>
-
-          <div className={classes.fieldBlock}>
-            <FormControlLabel
-              control={
-                <Checkbox
-                  disabled={!permitted}
-                  checked={this.state.useTunnel}
-                  onChange={(e) => {
-                    this.setState({
-                      useTunnel: e.target.checked,
-                    })
-                    Actions.resetNewDbLabInstance()
-                  }}
-                  id="useTunnel"
-                  name="useTunnel"
-                />
-              }
-              label="Use tunnel"
-              labelPlacement="end"
-            />
-            <div>
-              <TextField
-                variant="outlined"
-                id="token"
-                disabled={!permitted}
-                label="SSH server URL"
-                value={this.state.sshServerUrl}
-                className={classes.textField}
-                onChange={(e) => {
-                  this.setState({
-                    sshServerUrl: e.target.value,
-                  })
-                  this.clearFieldError('token')
-                  Actions.resetNewDbLabInstance()
-                }}
-                margin="normal"
-                error={this.state.errorFields.indexOf('sshServerUrl') !== -1}
-                fullWidth
-                inputProps={{
-                  name: 'sshServerUrl',
-                  id: 'sshServerUrl',
-                  shrink: true,
-                }}
-                InputLabelProps={{
-                  shrink: true,
-                  style: styles.inputFieldLabel,
-                }}
-                FormHelperTextProps={{
-                  style: styles.inputFieldHelper,
-                }}
-              />
-            </div>
-          </div>
-          <div>
-            <Button
-              variant="contained"
-              color="primary"
-              disabled={isDataUpdating || !permitted}
-              onClick={this.checkUrlHandler}
-            >
-              Verify URL
-            </Button>
-
-            <div className={classes.urlTextMargin}>
-              {data?.isCheckProcessed &&
-              data?.isChecked &&
-              (isHttps(this.state.url) || this.state.useTunnel) ? (
-                <span className={classes.urlOk}>
-                  <CheckCircleOutlineIcon className={classes.urlOkIcon} />{' '}
-                  Verified
-                </span>
-              ) : null}
-
-              {data?.isCheckProcessed &&
-              data?.isChecked &&
-              !isHttps(this.state.url) &&
-              !this.state.useTunnel ? (
-                <span className={classes.urlFail}>
-                  <BlockIcon className={classes.urlFailIcon} /> Verified but is
-                  not secure
-                </span>
-              ) : null}
-
-              {data?.isCheckProcessed && !data?.isChecked ? (
-                <span className={classes.urlFail}>
-                  <BlockIcon className={classes.urlFailIcon} /> Not available
-                </span>
-              ) : null}
-            </div>
-          </div>
-
-          <div className={classes.fieldBlock} style={{ marginTop: 40 }}>
-            <Button
-              variant="contained"
-              color="primary"
-              disabled={isDataUpdating || !permitted}
-              onClick={this.buttonHandler}
-            >
-              {this.props.edit ? 'Update' : 'Add'}
-            </Button>
-            &nbsp;&nbsp;
-            <Button
-              variant="contained"
-              color="primary"
-              disabled={isDataUpdating || !permitted}
-              onClick={this.returnHandler}
-            >
-              Cancel
-            </Button>
-          </div>
-          <div className={classes.errorMessage}>
-            {data?.errorMessage ? data.errorMessage : null}
-          </div>
-        </Grid>
-      </div>
-    )
-  }
-}
-
-export default DbLabInstanceForm
diff --git a/ui/packages/platform/src/components/AddMemberForm/AddMemberForm.tsx b/ui/packages/platform/src/components/AddMemberForm/AddMemberForm.tsx
deleted file mode 100644
index f01ff018..00000000
--- a/ui/packages/platform/src/components/AddMemberForm/AddMemberForm.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import Button from '@material-ui/core/Button'
-import Grid from '@material-ui/core/Grid'
-import TextField from '@material-ui/core/TextField'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import ConsolePageTitle from '../ConsolePageTitle'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import Store from '../../stores/store'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { messages } from '../../assets/messages'
-import { InviteFormProps } from 'components/AddMemberForm/AddMemberFormWrapper'
-import { theme } from '@postgres.ai/shared/styles/theme'
-
-interface InviteFormWithStylesProps extends InviteFormProps {
-  classes: ClassesType
-}
-
-interface InviteFormState {
-  email: string
-  data: {
-    auth: {
-      token: string
-    } | null
-    inviteUser: {
-      errorMessage: string
-      updateErrorFields: string[]
-      isUpdating: boolean
-    } | null
-    orgProfile: {
-      orgId: number
-      isProcessing: boolean
-      isProcessed: boolean
-      error: boolean
-    } | null
-  }
-}
-
-class InviteForm extends Component<InviteFormWithStylesProps, InviteFormState> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const { org, orgId } = this.props
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-
-      if (this.data.inviteUser.isProcessed && !this.data.inviteUser.error) {
-        window.location.href = '/' + org + '/members'
-      }
-
-      const auth: InviteFormState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const orgProfile: InviteFormState['data']['orgProfile'] =
-        this.data && this.data.orgProfile ? this.data.orgProfile : null
-
-      if (
-        auth &&
-        auth.token &&
-        orgProfile &&
-        orgProfile.orgId !== orgId &&
-        !orgProfile.isProcessing &&
-        !orgProfile.error
-      ) {
-        Actions.getOrgs(auth.token, orgId)
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    const value = event.target.value
-
-    this.setState({
-      email: value,
-    })
-  }
-
-  buttonHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.inviteUser : null
-
-    if (auth && data && !data.isUpdating && this.state.email) {
-      Actions.inviteUser(auth.token, orgId, this.state.email.trim())
-    }
-  }
-
-  render() {
-    const { classes, orgPermissions } = this.props
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: 'Add user' }]}
-      />
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title="Add a registered user"
-        information="Use this form to add a registered users to your organization."
-      />
-    )
-
-    if (orgPermissions && !orgPermissions.settingsMemberAdd) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <WarningWrapper>{messages.noPermissionPage}</WarningWrapper>
-        </div>
-      )
-    }
-
-    if (this.state && this.state.data && this.state.data.orgProfile?.error) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !this.state ||
-      !this.state.data ||
-      !(this.state.data.orgProfile && this.state.data.orgProfile.isProcessed)
-    ) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const inviteData = this.state.data.inviteUser
-
-    return (
-      <div className={classes?.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        <div>
-          If the person is not registered yet, ask them to register first.
-        </div>
-
-        <div className={classes?.errorMessage}>
-          {inviteData && inviteData.errorMessage ? (
-            <div className={classes?.dense}>{inviteData.errorMessage}</div>
-          ) : null}
-        </div>
-
-        <Grid container spacing={3}>
-          <Grid item xs={12} sm={12} lg={8} className={classes?.container}>
-            <Grid
-              item
-              xs
-              container
-              direction={theme.breakpoints.up('md') ? 'row' : 'column'}
-              spacing={2}
-            >
-              <Grid item xs={12} sm={6}>
-                <TextField
-                  id="email"
-                  label="Email"
-                  variant="outlined"
-                  value={this.state.email}
-                  className={classes?.textField}
-                  onChange={this.handleChange}
-                  margin="normal"
-                  error={
-                    inviteData !== null &&
-                    inviteData.updateErrorFields &&
-                    inviteData.updateErrorFields.indexOf('email') !== -1
-                  }
-                  fullWidth
-                  inputProps={{
-                    name: 'email',
-                    id: 'email',
-                  }}
-                  InputLabelProps={{
-                    shrink: true,
-                    style: styles.inputFieldLabel,
-                  }}
-                  FormHelperTextProps={{
-                    style: styles.inputFieldHelper,
-                  }}
-                />
-
-                <Button
-                  variant="contained"
-                  color="primary"
-                  disabled={inviteData !== null && inviteData.isUpdating}
-                  onClick={this.buttonHandler}
-                  className={classes?.button}
-                >
-                  Add
-                </Button>
-              </Grid>
-            </Grid>
-          </Grid>
-        </Grid>
-      </div>
-    )
-  }
-}
-
-export default InviteForm
diff --git a/ui/packages/platform/src/components/AddMemberForm/AddMemberFormWrapper.tsx b/ui/packages/platform/src/components/AddMemberForm/AddMemberFormWrapper.tsx
deleted file mode 100644
index ea9f3ba5..00000000
--- a/ui/packages/platform/src/components/AddMemberForm/AddMemberFormWrapper.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import { OrgPermissions } from 'components/types'
-import InviteForm from 'components/AddMemberForm/AddMemberForm'
-
-export interface InviteFormProps {
-  org: string | number
-  orgId: number
-  history: RouteComponentProps['history']
-  project: string | undefined
-  orgPermissions: OrgPermissions
-}
-
-export const AddMemberFormWrapper = (props: InviteFormProps) => {
-  const useStyles = makeStyles(
-    {
-      container: {
-        display: 'flex',
-        flexWrap: 'wrap',
-      },
-      textField: {
-        ...styles.inputField,
-        maxWidth: 400,
-      },
-      dense: {
-        marginTop: 10,
-      },
-      errorMessage: {
-        color: 'red',
-      },
-      button: {
-        marginTop: 17,
-        display: 'inline-block',
-        marginLeft: 7,
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <InviteForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/AppUpdateBanner/index.tsx b/ui/packages/platform/src/components/AppUpdateBanner/index.tsx
deleted file mode 100644
index 05f68684..00000000
--- a/ui/packages/platform/src/components/AppUpdateBanner/index.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { observer } from 'mobx-react-lite'
-
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { Button } from '@postgres.ai/shared/components/Button'
-
-import { appStore } from 'stores/app'
-
-import styles from './styles.module.scss'
-
-export const AppUpdateBanner = observer(() => {
-  if (!appStore.isOutdatedVersion) return null
-
-  return (
-    <div className={styles.root}>
-      <div className={styles.text}>
-        {icons.updateIcon}&nbsp;UI update is available
-      </div>
-      <Button
-        variant="primary"
-        onClick={() => window.location.reload()}
-      >
-        Apply update
-      </Button>
-    </div>
-  )
-})
diff --git a/ui/packages/platform/src/components/AppUpdateBanner/styles.module.scss b/ui/packages/platform/src/components/AppUpdateBanner/styles.module.scss
deleted file mode 100644
index cc51c1d2..00000000
--- a/ui/packages/platform/src/components/AppUpdateBanner/styles.module.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-.root {
-  display: flex;
-  align-items: center;
-  flex-wrap: wrap;
-  background-color: #d7eef2;
-  padding: 4px 14px;
-  color: #013a44;
-}
-
-.text {
-  display: flex;
-  align-items: center;
-  margin-right: 16px;
-}
diff --git a/ui/packages/platform/src/components/Audit/Audit.tsx b/ui/packages/platform/src/components/Audit/Audit.tsx
deleted file mode 100644
index 6704de25..00000000
--- a/ui/packages/platform/src/components/Audit/Audit.tsx
+++ /dev/null
@@ -1,420 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  Button,
-  ExpansionPanel,
-  ExpansionPanelSummary,
-  ExpansionPanelDetails,
-  TextField,
-} from '@material-ui/core'
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import ConsolePageTitle from '../ConsolePageTitle'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import Store from '../../stores/store'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { messages } from '../../assets/messages'
-import format from '../../utils/format'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { AuditProps } from 'components/Audit/AuditWrapper'
-import { FilteredTableMessage } from 'components/AccessTokens/FilteredTableMessage/FilteredTableMessage'
-
-const PAGE_SIZE = 20
-const auditTitle = 'Audit log'
-
-interface AuditWithStylesProps extends AuditProps {
-  classes: ClassesType
-}
-
-export interface AuditLogData {
-  id: number
-  data_before: string
-  data_after: string
-  action: string
-  actor: string
-  action_data: {
-    processed_rows_count: number
-  }
-  created_at: string
-  table_name: string
-}
-
-interface AuditState {
-  filterValue: string
-  data: {
-    auth: {
-      token: string
-    } | null
-    auditLog: {
-      isProcessing: boolean
-      orgId: number
-      error: boolean
-      isComplete: boolean
-      errorCode: number
-      errorMessage: string
-      data: AuditLogData[]
-    } | null
-  }
-}
-
-class Audit extends Component<AuditWithStylesProps, AuditState> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const orgId = this.props.orgId
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth: AuditState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const auditLog: AuditState['data']['auditLog'] =
-        this.data && this.data.auditLog ? this.data.auditLog : null
-
-      that.setState({ data: this.data })
-
-      if (
-        auth &&
-        auth.token &&
-        auditLog &&
-        !auditLog.isProcessing &&
-        !auditLog.error &&
-        !that.state
-      ) {
-        Actions.getAuditLog(auth.token, {
-          orgId,
-          limit: PAGE_SIZE,
-        })
-      }
-    })
-
-    const contentContainer = document.getElementById('content-container')
-    if (contentContainer) {
-      contentContainer.addEventListener('scroll', () => {
-        if (
-          contentContainer !== null &&
-          contentContainer.scrollTop >=
-            contentContainer.scrollHeight - contentContainer.offsetHeight
-        ) {
-          this.showMore()
-        }
-      })
-    }
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  showMore() {
-    const { orgId } = this.props
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const logs = this.state && this.state.data ? this.state.data.auditLog : null
-    let lastId = null
-
-    if (logs && logs?.data && logs.data?.length) {
-      lastId = logs.data[logs.data.length - 1].id
-    }
-
-    if (auth && auth.token && !logs?.isProcessing && lastId) {
-      Actions.getAuditLog(auth.token, {
-        orgId,
-        limit: PAGE_SIZE,
-        lastId,
-      })
-    }
-  }
-
-  formatAction = (r: AuditLogData) => {
-    const { classes } = this.props
-    let acted = r.action
-    let actor = r.actor
-    let actorSrc = ''
-    let rows = 'row'
-
-    if (!actor) {
-      actor = 'Unknown'
-      actorSrc = ' (changed directly in database) '
-    }
-
-    if (r.action_data && r.action_data.processed_rows_count) {
-      rows =
-        r.action_data.processed_rows_count +
-        ' ' +
-        (r.action_data.processed_rows_count > 1 ? 'rows' : 'row')
-    }
-
-    switch (r.action) {
-      case 'insert':
-        acted = ' added ' + rows + ' to'
-        break
-      case 'delete':
-        acted = ' deleted ' + rows + ' from'
-        break
-      default:
-        acted = ' updated ' + rows + ' in'
-    }
-
-    return (
-      <div className={classes?.actionDescription}>
-        <strong>{actor}</strong>
-        {actorSrc} {acted} table&nbsp;<strong>{r.table_name}</strong>
-      </div>
-    )
-  }
-
-  getDataSectionTitle = (r: AuditLogData, before: boolean) => {
-    switch (r.action) {
-      case 'insert':
-        return ''
-      case 'delete':
-        return ''
-      default:
-        return before ? 'Before:' : 'After:'
-    }
-  }
-
-  getChangesTitle = (r: AuditLogData) => {
-    const displayedCount = r.data_before
-      ? r.data_before?.length
-      : r.data_after?.length
-    const objCount =
-      r.action_data && r.action_data.processed_rows_count
-        ? r.action_data.processed_rows_count
-        : null
-
-    if (displayedCount && (objCount as number) > displayedCount) {
-      return (
-        'Changes (displayed ' +
-        displayedCount +
-        ' rows out of ' +
-        objCount +
-        ')'
-      )
-    }
-
-    return 'Changes'
-  }
-
-  filterInputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.setState({ filterValue: event.target.value })
-  }
-
-  render() {
-    const { classes, orgPermissions, orgId } = this.props
-    const data = this.state && this.state.data ? this.state.data.auditLog : null
-    const logsStore =
-      (this.state && this.state.data && this.state.data.auditLog) || null
-    const logs = (logsStore && logsStore.data) || []
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: auditTitle }]}
-      />
-    )
-
-    const filteredLogs = logs.filter(
-      (log) =>
-        log.actor
-          ?.toLowerCase()
-          .indexOf((this.state.filterValue || '')?.toLowerCase()) !== -1,
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title={auditTitle}
-        filterProps={
-          logs && logs.length > 0
-            ? {
-                filterValue: this.state.filterValue,
-                filterHandler: this.filterInputHandler,
-                placeholder: 'Search audit log',
-              }
-            : null
-        }
-      />
-    )
-
-    if (orgPermissions && !orgPermissions.auditLogView) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-          {pageTitle}
-          <WarningWrapper>{messages.noPermissionPage}</WarningWrapper>
-        </div>
-      )
-    }
-
-    if (
-      !logsStore ||
-      !logsStore.data ||
-      (logsStore && logsStore.orgId !== orgId)
-    ) {
-      return (
-        <div className={classes?.root}>
-          {breadcrumbs}
-          {pageTitle}
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    if (logsStore.error) {
-      return (
-        <div className={classes?.root}>
-          <ErrorWrapper
-            message={logsStore.errorMessage}
-            code={logsStore.errorCode}
-          />
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes?.root}>
-        {breadcrumbs}
-        {pageTitle}
-        {filteredLogs && filteredLogs.length > 0 ? (
-          <div>
-            <HorizontalScrollContainer>
-              <Table className={classes?.table}>
-                <TableHead>
-                  <TableRow className={classes?.row}>
-                    <TableCell>Action</TableCell>
-                    <TableCell>Time</TableCell>
-                  </TableRow>
-                </TableHead>
-                <TableBody>
-                  {logs.map((r) => {
-                    return (
-                      <TableRow hover className={classes?.row} key={r.id}>
-                        <TableCell className={classes?.cell}>
-                          {this.formatAction(r)}
-                          {(r.data_before || r.data_after) && (
-                            <div>
-                              <ExpansionPanel
-                                className={classes?.expansionPanel}
-                              >
-                                <ExpansionPanelSummary
-                                  expandIcon={<ExpandMoreIcon />}
-                                  aria-controls="panel1b-content"
-                                  id="panel1b-header"
-                                  className={classes?.expansionPanelSummary}
-                                >
-                                  {this.getChangesTitle(r)}
-                                </ExpansionPanelSummary>
-                                <ExpansionPanelDetails
-                                  className={classes?.expansionPanelDetails}
-                                >
-                                  {r.data_before && (
-                                    <div className={classes?.data}>
-                                      {this.getDataSectionTitle(r, true)}
-                                      <TextField
-                                        variant="outlined"
-                                        id={'before_data_' + r.id}
-                                        multiline
-                                        fullWidth
-                                        value={JSON.stringify(
-                                          r.data_before,
-                                          null,
-                                          4,
-                                        )}
-                                        className={classes?.code}
-                                        margin="normal"
-                                        InputProps={{
-                                          readOnly: true,
-                                        }}
-                                      />
-                                    </div>
-                                  )}
-                                  {r.data_after && (
-                                    <div className={classes?.data}>
-                                      {this.getDataSectionTitle(r, false)}
-                                      <TextField
-                                        variant="outlined"
-                                        id={'after_data_' + r.id}
-                                        multiline
-                                        fullWidth
-                                        value={JSON.stringify(
-                                          r.data_after,
-                                          null,
-                                          4,
-                                        )}
-                                        className={classes?.code}
-                                        margin="normal"
-                                        InputProps={{
-                                          readOnly: true,
-                                        }}
-                                      />
-                                    </div>
-                                  )}
-                                </ExpansionPanelDetails>
-                              </ExpansionPanel>
-                            </div>
-                          )}
-                        </TableCell>
-                        <TableCell className={classes?.timeCell}>
-                          {format.formatTimestampUtc(r.created_at)}
-                        </TableCell>
-                      </TableRow>
-                    )
-                  })}
-                </TableBody>
-              </Table>
-            </HorizontalScrollContainer>
-            <div className={classes?.showMoreContainer}>
-              {data && data.isProcessing && (
-                <Spinner className={classes?.progress} />
-              )}
-              {data && !data.isProcessing && !data.isComplete && (
-                <Button
-                  variant="outlined"
-                  color="secondary"
-                  className={classes?.button}
-                  onClick={() => this.showMore()}
-                  disabled={data && data.isProcessing}
-                >
-                  Show more
-                </Button>
-              )}
-            </div>
-          </div>
-        ) : (
-          <FilteredTableMessage
-            filteredItems={filteredLogs}
-            emptyState="Audit log records not found"
-            filterValue={this.state.filterValue}
-            clearFilter={() =>
-              this.setState({
-                filterValue: '',
-              })
-            }
-          />
-        )}
-        <div className={classes?.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default Audit
diff --git a/ui/packages/platform/src/components/Audit/AuditWrapper.tsx b/ui/packages/platform/src/components/Audit/AuditWrapper.tsx
deleted file mode 100644
index 7387d910..00000000
--- a/ui/packages/platform/src/components/Audit/AuditWrapper.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { OrgPermissions } from 'components/types'
-import Audit from 'components/Audit/Audit'
-
-export interface AuditProps {
-  orgId: number
-  org: string | number
-  project: string | undefined
-  orgPermissions: OrgPermissions
-}
-
-export const AuditWrapper = (props: AuditProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        ...(styles.root as Object),
-        display: 'flex',
-        flexDirection: 'column',
-        paddingBottom: '20px',
-      },
-      container: {
-        display: 'flex',
-        flexWrap: 'wrap',
-      },
-      timeCell: {
-        verticalAlign: 'top',
-        minWidth: 200,
-      },
-      expansionPanel: {
-        boxShadow: 'none',
-        background: 'transparent',
-        fontSize: '12px',
-        marginBottom: '5px',
-      },
-      expansionPanelSummary: {
-        display: 'inline-block',
-        padding: '0px',
-        minHeight: '22px',
-        '& .MuiExpansionPanelSummary-content': {
-          margin: '0px',
-          display: 'inline-block',
-        },
-        '&.Mui-expanded': {
-          minHeight: '22px',
-        },
-        '& .MuiExpansionPanelSummary-expandIcon': {
-          display: 'inline-block',
-          padding: '0px',
-        },
-      },
-      expansionPanelDetails: {
-        padding: '0px',
-        [theme.breakpoints.down('md')]: {
-          display: 'block',
-        },
-      },
-      actionDescription: {
-        marginBottom: '5px',
-      },
-      code: {
-        width: '100%',
-        'margin-top': 0,
-        '& > div': {
-          paddingTop: 8,
-          padding: 8,
-        },
-        'background-color': 'rgb(246, 248, 250)',
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: '12px',
-        },
-      },
-      showMoreContainer: {
-        marginTop: 20,
-        textAlign: 'center',
-      },
-      data: {
-        width: '50%',
-        [theme.breakpoints.up('md')]: {
-          width: '50%',
-          marginRight: '10px',
-        },
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Audit {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/Billing/Billing.tsx b/ui/packages/platform/src/components/Billing/Billing.tsx
deleted file mode 100644
index a6eb24ed..00000000
--- a/ui/packages/platform/src/components/Billing/Billing.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { loadStripe } from '@stripe/stripe-js'
-import { Elements } from '@stripe/react-stripe-js'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import StripeForm from '../StripeForm'
-import settings from '../../utils/settings'
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import Permissions from '../../utils/permissions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { BillingProps } from 'components/Billing/BillingWrapper'
-
-interface BillingWithStylesProps extends BillingProps {
-  classes: ClassesType
-}
-
-interface BillingState {
-  data: {
-    auth: {
-      token: string
-    } | null
-    billing: {
-      orgId: number
-      error: boolean
-      isProcessing: boolean
-      subscriptionError: boolean
-      subscriptionErrorMessage: string
-      isSubscriptionProcessing: boolean
-      primaryPaymentMethod: string
-      data: {
-        unit_amount: string
-        data_usage_estimate: string
-        data_usage_sum: string
-        data_usage: {
-          id: number
-          instance_id: string
-          day_date: Date
-          data_size_gib: number
-          to_invoice: boolean
-        }[]
-        period_start: Date
-        period_now: Date
-      }
-    }
-  }
-}
-
-const stripePromise = loadStripe(settings.stripeApiKey as string, {
-  locale: 'en',
-})
-
-const page = {
-  title: 'Billing',
-}
-
-class Billing extends Component<BillingWithStylesProps, BillingState> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const { orgId } = this.props
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth: BillingState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const billing: BillingState['data']['billing'] =
-        this.data && this.data.billing ? this.data.billing : null
-
-      that.setState({ data: this.data })
-
-      if (
-        auth &&
-        auth.token &&
-        billing &&
-        !billing.isProcessing &&
-        !billing.error &&
-        !that.state
-      ) {
-        Actions.getBillingDataUsage(auth.token, orgId)
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  toFixed(value: number) {
-    if (value && value.toFixed && value !== 0) {
-      return value.toFixed(4)
-    }
-
-    return '0.0'
-  }
-
-  render() {
-    const { classes, orgId, orgData } = this.props
-    const auth =
-      this.state && this.state.data && this.state.data.auth
-        ? this.state.data.auth
-        : null
-    const data =
-      this.state && this.state.data && this.state.data.billing
-        ? this.state.data.billing
-        : null
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[{ name: page.title }]}
-      />
-    )
-
-    if (!Permissions.isAdmin(orgData)) {
-      return (
-        <div>
-          {breadcrumbs}
-
-          {<ConsolePageTitle title={page.title} />}
-
-          <ErrorWrapper
-            message="You do not have permission to view this page."
-            code={403}
-          />
-        </div>
-      )
-    }
-
-    let mode = 'new'
-    if (orgData.is_blocked && orgData.stripe_subscription_id) {
-      mode = 'resume'
-    }
-    if (!orgData.is_blocked && orgData.stripe_subscription_id) {
-      mode = 'update'
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-        <ConsolePageTitle title={page.title} />
-        <div className={classes.subscriptionForm}>
-          <div>
-            {Permissions.isAdmin(orgData) && (
-              <div>
-                {data && data.subscriptionError && (
-                  <div className={classes.errorMessage}>
-                    {data.subscriptionErrorMessage}
-                  </div>
-                )}
-                <Elements stripe={stripePromise}>
-                  <StripeForm
-                    alias={orgData.alias}
-                    mode={mode}
-                    token={auth && auth.token ? auth.token : null}
-                    orgId={orgId}
-                    disabled={data !== null && data.isSubscriptionProcessing}
-                  />
-                </Elements>
-              </div>
-            )}
-          </div>
-        </div>
-        <div className={classes.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default Billing
diff --git a/ui/packages/platform/src/components/Billing/BillingWrapper.tsx b/ui/packages/platform/src/components/Billing/BillingWrapper.tsx
deleted file mode 100644
index 6c4497fe..00000000
--- a/ui/packages/platform/src/components/Billing/BillingWrapper.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import Billing from 'components/Billing/Billing'
-import { colors } from '@postgres.ai/shared/styles/colors'
-
-export interface BillingProps {
-  org: string | number
-  orgId: number
-  short: boolean
-  projectId: number | string | undefined
-  orgData: {
-    alias: string
-    is_priveleged: boolean
-    stripe_payment_method_primary: string
-    is_blocked: boolean
-    new_subscription: boolean
-    is_blocked_on_creation: boolean
-    stripe_subscription_id: number
-    priveleged_until: Date
-    role: {
-      id: number
-    }
-  }
-}
-
-export const BillingWrapper = (props: BillingProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        '& ul': {
-          '& > li': {
-            'list-style-position': 'inside',
-          },
-          padding: 0,
-        },
-        '& h1': {
-          fontSize: '16px!important',
-          fontWeight: 'bold',
-        },
-        '& h2': {
-          fontSize: '14px!important',
-          fontWeight: 'bold',
-        },
-        width: '100%',
-        'min-height': '100%',
-        'z-index': 1,
-        position: 'relative',
-        [theme.breakpoints.down('sm')]: {
-          maxWidth: '100vw',
-        },
-        [theme.breakpoints.up('md')]: {
-          maxWidth: 'calc(100vw - 200px)',
-        },
-        [theme.breakpoints.up('lg')]: {
-          maxWidth: 'calc(100vw - 200px)',
-        },
-        'font-size': '14px!important',
-        'font-family': '"Roboto", "Helvetica", "Arial", sans-serif',
-
-        display: 'flex',
-        flexDirection: 'column',
-        paddingBottom: '20px',
-      },
-      errorMessage: {
-        color: colors.state.error,
-        marginBottom: 10,
-      },
-      subscriptionForm: {
-        marginBottom: 20,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Billing {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/BotSettingsForm/BotSettingsForm.tsx b/ui/packages/platform/src/components/BotSettingsForm/BotSettingsForm.tsx
deleted file mode 100644
index 0bbc0aa8..00000000
--- a/ui/packages/platform/src/components/BotSettingsForm/BotSettingsForm.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { Component } from 'react'
-import {
-  Grid,
-  Button,
-  TextField,
-  InputLabel,
-  FormControl,
-  Select,
-  MenuItem,
-  RadioGroup,
-  FormControlLabel,
-  Radio
-} from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import { BotSettingsFormProps } from './BotSettingsFormWrapper'
-import { GatewayLink } from "@postgres.ai/shared/components/GatewayLink";
-
-interface BotSettingsFormWithStylesProps extends BotSettingsFormProps {
-  classes: ClassesType
-}
-
-interface BotSettingState {
-  custom_prompt: string
-  model: string
-  threadVisibility: string
-  data: {
-    auth: {
-      token: string | null
-    } | null
-    orgProfile: {
-      isUpdating: boolean
-      error: boolean
-      updateError: boolean
-      errorMessage: string | undefined
-      errorCode: number | undefined
-      updateErrorMessage: string | null
-      isProcessing: boolean
-      orgId: number | null
-      updateErrorFields: string[]
-      data: {
-        custom_prompt: string
-        ai_model: string
-      }
-    } | null
-  } | null
-}
-
-
-
-class BotSettingsForm extends Component<BotSettingsFormWithStylesProps, BotSettingState> {
-  state = {
-    custom_prompt: '',
-    model: 'gemini-1.5-pro',
-    threadVisibility: 'public',
-    data: {
-      auth: {
-        token: null,
-      },
-      orgProfile: {
-        isUpdating: false,
-        isProcessing: false,
-        error: false,
-        updateError: false,
-        errorMessage: undefined,
-        errorCode: undefined,
-        updateErrorMessage: null,
-        updateErrorFields: [''],
-        orgId: null,
-        data: {
-          custom_prompt: '',
-          ai_model: 'gemini-1.5-pro',
-        }
-      }
-    }
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const { orgId, mode } = this.props
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-       const auth = this.data && this.data.auth ? this.data.auth : null
-       const orgProfile =
-         this.data && this.data.orgProfile ? this.data.orgProfile : null
-
-       that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  buttonHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.orgProfile : null
-
-    if (auth) {
-      let params: { custom_prompt?: string, model?: string } = {
-        custom_prompt: this.state.custom_prompt,
-        model: this.state.model
-      };
-      /*if (data.data.custom_prompt !== this.state.custom_prompt) {
-        params.custom_prompt = this.state.custom_prompt;
-      }*/
-      if (data?.data?.ai_model !== this.state.model) {
-        params.model = this.state.model;
-      }
-      Actions.updateAiBotSettings(auth.token, orgId, params)
-    }
-  }
-
-  handleChangeModel = (event: React.ChangeEvent<{ value: unknown }>) => {
-    this.setState({model: event.target.value as string})
-  }
-
-  handleChangeThreadVisibility = (event: React.ChangeEvent<{ value: unknown }>) => {
-    this.setState({threadVisibility: event.target.value as string})
-  }
-
-  render() {
-    const { classes, orgPermissions, mode } = this.props
-    const orgId = this.props.orgId ? this.props.orgId : null
-
-
-    return (
-      <>
-        <ConsoleBreadcrumbsWrapper
-          org={this.props.org}
-          project={this.props.project}
-          breadcrumbs={[{ name: 'AI Bot' }]}
-        />
-
-        <ConsolePageTitle title="AI Bot settings" />
-
-        {/*<div className={classes.errorMessage}>
-          {data && data.updateErrorMessage ? data.updateErrorMessage : null}
-        </div>*/}
-
-        <Grid container spacing={3}>
-          <Grid item xs={12} sm={12} lg={12} className={classes.container}>
-            <Grid
-              xs={12}
-              sm={12}
-              lg={8}
-              item
-              container
-              direction={'column'}
-            >
-              <Grid item xs={12} sm={6}>
-                <TextField
-                  id="instructionsText"
-                  label="Custom prompt"
-                  fullWidth
-                  minRows={4}
-                  multiline
-                  variant="outlined"
-                  value={this.state.custom_prompt}
-                  className={classes.instructionsField}
-                  onChange={(e) => {
-                    this.setState({
-                      custom_prompt: e.target.value,
-                    })
-                  }}
-                  /*error={
-                    data?.updateErrorFields &&
-                    data.updateErrorFields.indexOf('instructionsText') !== -1
-                  }*/
-                  margin="normal"
-                  inputProps={{
-                    name: 'instructionsText',
-                    id: 'instructionsText',
-                    shrink: 'true',
-                  }}
-                  InputLabelProps={{
-                    shrink: true,
-                    style: styles.inputFieldLabel,
-                  }}
-                  FormHelperTextProps={{
-                    style: styles.inputFieldHelper,
-                  }}
-                  helperText={
-                    <span>
-                        Example: Our Postgres clusters are on AWS RDS, version is 15.
-                    </span>
-                  }
-                />
-                <FormControl fullWidth className={classes.selectField}>
-                  <InputLabel id="model-radio-buttons-group-label">Model</InputLabel>
-                  <RadioGroup
-                    aria-labelledby="model-radio-buttons-group-label"
-                    defaultValue="gemini-1.5-pro"
-                    name="model-radio-buttons-group"
-                    value={this.state.model}
-                    onChange={this.handleChangeModel}
-                  >
-                    <FormControlLabel value="gemini-1.5-pro" control={<Radio />} label="gemini-1.5-pro" />
-                    <FormControlLabel value="gpt-4-turbo" control={<Radio />} label="gpt-4-turbo	" />
-                    <FormControlLabel disabled value="llama-3" control={<Radio />} label="Llama 3" />
-                  </RadioGroup>
-                </FormControl>
-                <FormControl fullWidth className={classes.selectField}>
-                  <InputLabel id="visibility-radio-buttons-group-label">Default thread visibility</InputLabel>
-                  <RadioGroup
-                    aria-labelledby="visibility-radio-buttons-group-label"
-                    defaultValue="public"
-                    name="visibility-radio-buttons-group"
-                    value={this.state.threadVisibility}
-                    onChange={this.handleChangeThreadVisibility}
-                  >
-                    <FormControlLabel value="public" control={<Radio />} label="Public" />
-                    <FormControlLabel disabled value="private" control={<Radio />} label="Private" />
-                  </RadioGroup>
-                </FormControl>
-              </Grid>
-            </Grid>
-            <Grid
-              item
-              xs={12}
-              sm={12}
-              lg={8}
-              className={classes.updateButtonContainer}
-            >
-              <Button
-                variant="contained"
-                color="primary"
-                //disabled={data?.isUpdating}
-                id="orgSaveButton"
-                onClick={this.buttonHandler}
-              >
-                Save
-              </Button>
-            </Grid>
-          </Grid>
-        </Grid>
-
-        <div className={classes.bottomSpace} />
-      </>
-    )
-  }
-}
-
-export default BotSettingsForm
diff --git a/ui/packages/platform/src/components/BotSettingsForm/BotSettingsFormWrapper.tsx b/ui/packages/platform/src/components/BotSettingsForm/BotSettingsFormWrapper.tsx
deleted file mode 100644
index b7860227..00000000
--- a/ui/packages/platform/src/components/BotSettingsForm/BotSettingsFormWrapper.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import OrgForm from 'components/OrgForm/OrgForm'
-import BotSettingsForm from "./BotSettingsForm";
-
-export interface BotSettingsFormProps {
-  mode?: string | undefined
-  project?: string | undefined
-  org?: string | number
-  orgId?: number
-  orgPermissions?: {
-    settingsOrganizationUpdate?: boolean
-  }
-}
-
-const useStyles = makeStyles(
-  {
-    container: {
-      ...(styles.root as Object),
-      display: 'flex',
-      'flex-wrap': 'wrap',
-      'min-height': 0,
-      '&:not(:first-child)': {
-        'margin-top': '20px',
-      },
-    },
-    textField: {
-      ...styles.inputField,
-      maxWidth: 450,
-    },
-    instructionsField: {
-      ...styles.inputField,
-      maxWidth: 450,
-    },
-    selectField: {
-      maxWidth: 450,
-      marginTop: 20,
-      '& .MuiInputLabel-formControl': {
-        transform: 'none',
-        position: 'static'
-      }
-    },
-    updateButtonContainer: {
-      marginTop: 20,
-      textAlign: 'left',
-    },
-    errorMessage: {
-      color: 'red',
-    },
-  },
-  { index: 1 },
-)
-
-export const BotSettingsFormWrapper = (props: BotSettingsFormProps) => {
-  const classes = useStyles()
-
-  return <BotSettingsForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/CheckupAgentForm/CheckupAgentForm.tsx b/ui/packages/platform/src/components/CheckupAgentForm/CheckupAgentForm.tsx
deleted file mode 100644
index 98d568a8..00000000
--- a/ui/packages/platform/src/components/CheckupAgentForm/CheckupAgentForm.tsx
+++ /dev/null
@@ -1,1031 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import {
-  Typography,
-  IconButton,
-  TextField,
-  Chip,
-  Grid,
-  Tabs,
-  Tab,
-  Button,
-  Radio,
-  RadioGroup,
-  FormControlLabel,
-  FormLabel,
-  ExpansionPanel,
-  ExpansionPanelSummary,
-  ExpansionPanelDetails,
-} from '@material-ui/core'
-import Box from '@mui/material/Box'
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import {
-  ClassesType,
-  TabPanelProps,
-  ProjectProps,
-  TokenRequestProps,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import CfgGen, { DataType } from '../../utils/cfggen'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { CheckupAgentFormProps } from 'components/CheckupAgentForm/CheckupAgentFormWrapper'
-
-const AUTO_GENERATED_TOKEN_NAME = 'Auto-generated 1-year token'
-
-interface CheckupAgentFormWithStylesProps extends CheckupAgentFormProps {
-  classes: ClassesType
-}
-
-interface CheckupAgentFormState extends DataType {
-  tab: number
-  data: {
-    auth: {
-      token: string
-    } | null
-    tokenRequest: TokenRequestProps
-    reports: {
-      error: boolean
-      isProcessed: boolean
-      isProcessing: boolean
-    } | null
-    projects: Omit<ProjectProps, 'data'>
-  }
-}
-
-function TabPanel(props: TabPanelProps) {
-  const { children, value, index, ...other } = props
-
-  return (
-    <Typography
-      component="div"
-      role="tabpanel"
-      hidden={value !== index}
-      id={`simple-tabpanel-${index}`}
-      aria-labelledby={`simple-tab-${index}`}
-      style={{ marginTop: 0 }}
-      {...other}
-    >
-      <Box
-        p={3}
-        style={{
-          padding: 0,
-          paddingTop: 10,
-          position: 'relative',
-        }}
-      >
-        {children}
-      </Box>
-    </Typography>
-  )
-}
-
-function a11yProps(index: number) {
-  return {
-    id: `simple-tab-${index}`,
-    'aria-controls': `simple-tabpanel-${index}`,
-  }
-}
-
-class CheckupAgentForm extends Component<
-  CheckupAgentFormWithStylesProps,
-  CheckupAgentFormState
-> {
-  state = {
-    data: {
-      auth: {
-        token: '',
-      },
-      tokenRequest: {
-        isProcessing: false,
-        isProcessed: false,
-        data: {
-          name: '',
-          is_personal: false,
-          expires_at: '',
-          token: '',
-        },
-        errorMessage: '',
-        error: false,
-      },
-      reports: {
-        error: false,
-        isProcessed: false,
-        isProcessing: false,
-      },
-      projects: {
-        error: false,
-        isProcessing: false,
-        isProcessed: false,
-      },
-    },
-    hosts: '',
-    projectName: '',
-    databaseName: '',
-    databaseUserName: '',
-    ssDatabaseName: '',
-    port: null,
-    sshPort: null,
-    pgPort: null,
-    statementTimeout: null,
-    pgSocketDir: '',
-    psqlBinary: '',
-    collectPeriod: 600,
-    newHostName: '',
-    apiToken: '',
-    sshKeysPath: '',
-    password: '',
-    connectionType: '',
-    tab: 0,
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth: CheckupAgentFormState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const reports: CheckupAgentFormState['data']['reports'] =
-        this.data && this.data.reports ? this.data.reports : null
-      const projects: Omit<ProjectProps, 'data'> =
-        this.data && this.data.projects ? this.data.projects : null
-      const tokenRequest: TokenRequestProps =
-        this.data && this.data.tokenRequest ? this.data.tokenRequest : null
-
-      that.setState({ data: this.data })
-
-      if (
-        auth &&
-        auth.token &&
-        !reports?.isProcessed &&
-        !reports?.isProcessing &&
-        !reports?.error
-      ) {
-        Actions.getCheckupReports(auth.token)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects?.isProcessed &&
-        !projects?.isProcessing &&
-        !projects?.error
-      ) {
-        Actions.getProjects(auth.token)
-      }
-
-      if (
-        tokenRequest &&
-        tokenRequest.isProcessed &&
-        !tokenRequest.error &&
-        tokenRequest.data &&
-        tokenRequest.data.name &&
-        tokenRequest.data.name.startsWith(AUTO_GENERATED_TOKEN_NAME) &&
-        tokenRequest.data.expires_at &&
-        tokenRequest.data.token
-      ) {
-        that.setState({ apiToken: tokenRequest.data.token })
-      }
-    })
-
-    Actions.refresh()
-    CfgGen.generateRunCheckupSh(this.state)
-  }
-
-  componentWillUnmount() {
-    Actions.hideGeneratedAccessToken()
-    this.unsubscribe()
-  }
-
-  handleDeleteHost = (_: React.ChangeEvent<HTMLInputElement>, host: string) => {
-    const curHosts = CfgGen.uniqueHosts(this.state.hosts)
-    const curDividers = this.state.hosts.match(/[;,(\s)(\n)(\r)(\t)(\r\n)]/gm)
-    const hosts = curHosts.split(';')
-    let newHosts = ''
-
-    for (const i in hosts) {
-      if (hosts[i] !== host) {
-        newHosts =
-          newHosts +
-          hosts[i] +
-          (curDividers !== null && curDividers[i] ? curDividers[i] : '')
-      }
-    }
-
-    this.setState({ hosts: newHosts })
-  }
-
-  handleChangeTab = (_: React.ChangeEvent<{}>, tabValue: number) => {
-    this.setState({ tab: tabValue })
-  }
-
-  addToken = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const tokenRequest =
-      this.state.data && this.state.data.tokenRequest
-        ? this.state.data.tokenRequest
-        : null
-
-    if (auth && auth.token && !tokenRequest?.isProcessing) {
-      const date = new Date()
-      const expiresAt =
-        date.getFullYear() +
-        1 +
-        '-' +
-        ('0' + (date.getMonth() + 1)).slice(-2) +
-        '-' +
-        ('0' + date.getDate()).slice(-2)
-      const nowDateTime =
-        date.getFullYear() +
-        '-' +
-        ('0' + (date.getMonth() + 1)).slice(-2) +
-        '-' +
-        ('0' + date.getDate()).slice(-2) +
-        ' ' +
-        ('0' + date.getHours()).slice(-2) +
-        ':' +
-        ('0' + date.getMinutes()).slice(-2)
-      const tokenName = AUTO_GENERATED_TOKEN_NAME + ' (' + nowDateTime + ')'
-
-      Actions.getAccessToken(auth.token, tokenName, expiresAt, orgId)
-    }
-  }
-
-  copyDockerCfg = () => {
-    const copyText = document.getElementById(
-      'generatedDockerCfg',
-    ) as HTMLInputElement
-
-    if (copyText) {
-      copyText.select()
-      copyText.setSelectionRange(0, 99999)
-      document.execCommand('copy')
-      copyText.setSelectionRange(0, 0)
-    }
-  }
-
-  copySrcCfg = () => {
-    const copyText = document.getElementById(
-      'generatedSrcCfg',
-    ) as HTMLInputElement
-
-    if (copyText) {
-      copyText.select()
-      copyText.setSelectionRange(0, 99999)
-      document.execCommand('copy')
-      copyText.setSelectionRange(0, 0)
-    }
-  }
-
-  render() {
-    const that = this
-    const { classes } = this.props
-    const reports =
-      this.state.data && this.state.data.reports
-        ? this.state.data.reports
-        : null
-    const projects =
-      this.state.data && this.state.data.projects
-        ? this.state.data.projects
-        : null
-    const tokenRequest =
-      this.state.data && this.state.data.tokenRequest
-        ? this.state.data.tokenRequest
-        : null
-    let copySrcCfgBtn = null
-    let copyDockerCfgBtn = null
-    let token = null
-    let content = null
-
-    if (
-      this.state.projectName !== '' &&
-      this.state.databaseName !== '' &&
-      this.state.databaseUserName !== '' &&
-      this.state.hosts !== '' &&
-      this.state.apiToken !== ''
-    ) {
-      copySrcCfgBtn = (
-        <IconButton
-          className={classes.copyButton}
-          aria-label="Copy"
-          onClick={this.copySrcCfg}
-        >
-          {icons.copyIcon}
-        </IconButton>
-      )
-      copyDockerCfgBtn = (
-        <IconButton
-          className={classes.copyButton}
-          aria-label="Copy"
-          onClick={this.copyDockerCfg}
-        >
-          {icons.copyIcon}
-        </IconButton>
-      )
-    }
-
-    token = (
-      <div className={classes.relativeFieldBlock}>
-        <TextField
-          id="apiToken"
-          variant="outlined"
-          className={classes.tokenInput}
-          margin="normal"
-          required
-          label="API access token"
-          onChange={(e) => {
-            this.setState({
-              apiToken: e.target.value,
-            })
-          }}
-          value={this.state.apiToken}
-          helperText={
-            'Insert a token or generate a new one. ' +
-            'The auto-generated token will expire in 1 year.'
-          }
-          inputProps={{
-            name: 'apiToken',
-            id: 'apiToken',
-          }}
-          InputLabelProps={{
-            shrink: true,
-            style: styles.inputFieldLabel,
-          }}
-          FormHelperTextProps={{
-            style: styles.inputFieldHelper,
-          }}
-        />
-
-        <Button
-          variant="contained"
-          color="primary"
-          className={classes.addTokenButton}
-          disabled={tokenRequest !== null && tokenRequest?.isProcessing}
-          onClick={this.addToken}
-        >
-          Generate token
-        </Button>
-      </div>
-    )
-
-    if (
-      this.state &&
-      this.state.data &&
-      ((this.state.data.reports && this.state.data.reports.error) ||
-        (this.state.data.projects && this.state.data.projects.error))
-    ) {
-      return (
-        <div>
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (reports && reports.isProcessed && projects?.isProcessed) {
-      content = (
-        <div style={{ marginTop: 20 }}>
-          <span>
-            Use postgres-checkup to check health of your Postgres databases.
-            This page will help you to generate the configuration file. Provide
-            settings that you will use inside your private network (local
-            hostnames, IPs, etc).
-          </span>
-          <br />
-
-          <span>
-            Do not leave the page in order not to loose the configuration data.
-          </span>
-          <br />
-
-          <h2>1. Configure</h2>
-
-          <ExpansionPanel expanded={true}>
-            <ExpansionPanelSummary
-              aria-controls="panel1a-content"
-              id="panel1a-header"
-            >
-              <Typography className={classes.heading}>
-                General options
-              </Typography>
-            </ExpansionPanelSummary>
-
-            <ExpansionPanelDetails>
-              <Grid item xs={12} sm={12} md={12}>
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="projectName"
-                    label="Project name"
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        projectName: e.target.value,
-                      })
-                    }}
-                    required
-                    className={classes.textInput}
-                    value={this.state.projectName}
-                    inputProps={{
-                      name: 'projectName',
-                      id: 'projectName',
-                      shrink: true,
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldRow} style={{ marginBottom: 20 }}>
-                  <FormLabel component="legend" className={classes.legend}>
-                    Connection type *
-                  </FormLabel>
-                  <RadioGroup
-                    aria-label="connectionType"
-                    name="connectionType"
-                    value={this.state.connectionType}
-                    onChange={(e) => {
-                      this.setState({
-                        connectionType: e.target.value,
-                      })
-                    }}
-                    className={classes.radioButton}
-                  >
-                    <FormControlLabel
-                      value="ssh"
-                      control={<Radio />}
-                      label="Connect to defined host via SSH"
-                    />
-                    <FormControlLabel
-                      value="pg"
-                      control={<Radio />}
-                      label={
-                        'Connect directly to PostgreSQL (some ' +
-                        'reports won’t be available)'
-                      }
-                    />
-                  </RadioGroup>
-                </div>
-
-                <div className={classes.fieldRow} style={{ marginBottom: 15 }}>
-                  <div className={classes.fieldValue}>
-                    <div className={classes.fieldBlock}>
-                      <TextField
-                        variant="outlined"
-                        id="hostName"
-                        className={classes.textInput}
-                        helperText={
-                          'Enter hostnames or IPs, separated by commas, spaces, or line breaks. ' +
-                          'To reach your machine\'s "localhost" from the container, use "host.docker.internal". ' +
-                          'The same approach works when using SSH port forwarding from your machine to a remote host.'
-                        }
-                        margin="normal"
-                        onChange={(e) => {
-                          this.setState({
-                            hosts: e.target.value,
-                          })
-                        }}
-                        value={this.state.hosts}
-                        multiline
-                        label="Hosts"
-                        fullWidth
-                        required
-                        inputProps={{
-                          name: 'hosts',
-                          id: 'hosts',
-                        }}
-                        InputLabelProps={{
-                          shrink: true,
-                          style: styles.inputFieldLabel,
-                        }}
-                        FormHelperTextProps={{
-                          style: styles.inputFieldHelper,
-                        }}
-                      />
-                    </div>
-
-                    <div>
-                      {CfgGen.uniqueHosts(that.state.hosts)
-                        .split(';')
-                        .map((h) => {
-                          if (h !== '') {
-                            return (
-                              <Chip
-                                label={h}
-                                className={classes.hostItem}
-                                onDelete={(event) =>
-                                  this.handleDeleteHost(event, h)
-                                }
-                                color="primary"
-                              />
-                            )
-                          }
-
-                          return null
-                        })}
-                    </div>
-                  </div>
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="databaseUserName"
-                    className={classes.textInput}
-                    helperText={
-                      'You can create a new DB user named "pgai_observer", then grant ' +
-                      'the "pg_monitor" role to this user using the following SQL command: ' +
-                      'GRANT pg_monitor TO pgai_observer;'
-                    }
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        databaseUserName: e.target.value,
-                      })
-                    }}
-                    required
-                    value={this.state.databaseUserName}
-                    label="Database username"
-                    inputProps={{
-                      name: 'databaseUserName',
-                      id: 'databaseUserName',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldRow} style={{ marginBottom: 20 }}>
-                  <FormLabel component="legend" className={classes.legend}>
-                    Database password *
-                  </FormLabel>
-                  <RadioGroup
-                    aria-label="password"
-                    name="password"
-                    value={this.state.password}
-                    onChange={(e) => {
-                      this.setState({
-                        password: e.target.value,
-                      })
-                    }}
-                    className={classes.radioButton}
-                  >
-                    <FormControlLabel
-                      value="nopassword"
-                      control={<Radio />}
-                      label={
-                        'No password is required or PGPASSWORD ' +
-                        'environment variable is predefined'
-                      }
-                    />
-                    <FormControlLabel
-                      value="inputpassword"
-                      control={<Radio />}
-                      label={
-                        'I will enter the password manually ' +
-                        '(choose this only for manual testing)'
-                      }
-                    />
-                  </RadioGroup>
-                </div>
-
-                <div
-                  className={classes.fieldBlock}
-                  style={{ marginBottom: 20 }}
-                >
-                  <TextField
-                    variant="outlined"
-                    id="databaseName"
-                    label="Database name"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        databaseName: e.target.value,
-                      })
-                    }}
-                    required
-                    value={this.state.databaseName}
-                    inputProps={{
-                      name: 'databaseName',
-                      id: 'databaseName',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="collectPeriod"
-                    label="Snapshots period"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        collectPeriod: e.target.value,
-                      })
-                    }}
-                    value={this.state.collectPeriod}
-                    helperText={
-                      'The delay between collection of two ' +
-                      'statistics snapshots, sec'
-                    }
-                    type="number"
-                    inputProps={{
-                      name: 'collectPeriod',
-                      id: 'collectPeriod',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-              </Grid>
-            </ExpansionPanelDetails>
-          </ExpansionPanel>
-
-          <ExpansionPanel>
-            <ExpansionPanelSummary
-              expandIcon={<ExpandMoreIcon />}
-              aria-controls="panel1b-content"
-              id="panel1b-header"
-              className={classes.advancedExpansionPanelSummary}
-            >
-              <Typography className={classes.heading}>
-                Advanced options
-              </Typography>
-            </ExpansionPanelSummary>
-
-            <ExpansionPanelDetails>
-              <Grid item xs={12} sm={12} md={12}>
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="ssDatabaseName"
-                    className={classes.textInput}
-                    label="pg_stat_statements DB name"
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        ssDatabaseName: e.target.value,
-                      })
-                    }}
-                    value={this.state.ssDatabaseName}
-                    helperText={
-                      'Database name with enabled "pg_stat_statements"' +
-                      ' extension (for detailed query analysis)'
-                    }
-                    inputProps={{
-                      name: 'ssDatabaseName',
-                      id: 'ssDatabaseName',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="pgPort"
-                    label="Database port"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        pgPort: e.target.value,
-                      })
-                    }}
-                    value={this.state.port}
-                    helperText="PostgreSQL database server port (default: 5432)"
-                    type="number"
-                    inputProps={{
-                      name: 'pgPort',
-                      id: 'pgPort',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="sshPort"
-                    label="SSH Port"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        sshPort: e.target.value,
-                      })
-                    }}
-                    value={this.state.port}
-                    helperText="SSH server port (default: 22)"
-                    type="number"
-                    inputProps={{
-                      name: 'sshPort',
-                      id: 'sshPort',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="statementTimeout"
-                    label="Statement timeout"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        statementTimeout: e.target.value,
-                      })
-                    }}
-                    value={this.state.statementTimeout}
-                    helperText={
-                      'Statement timeout for all SQL queries ' +
-                      '(default: 30 seconds)'
-                    }
-                    type="number"
-                    inputProps={{
-                      name: 'statementTimeout',
-                      id: 'statementTimeout',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="pgSocketDir"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        pgSocketDir: e.target.value,
-                      })
-                    }}
-                    value={this.state.pgSocketDir}
-                    label="PostgreSQL domain socket directory"
-                    helperText="PostgreSQL domain socket directory (default: psql's default)"
-                    inputProps={{
-                      name: 'pgSocketDir',
-                      id: 'pgSocketDir',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="psqlBinary"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        psqlBinary: e.target.value,
-                      })
-                    }}
-                    value={this.state.psqlBinary}
-                    helperText='Path to "psql" (default: determined by "$PATH")'
-                    inputProps={{
-                      name: 'psqlBinary',
-                      id: 'psqlBinary',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-
-                <div className={classes.fieldBlock}>
-                  <TextField
-                    variant="outlined"
-                    id="sshKeysPath"
-                    className={classes.textInput}
-                    margin="normal"
-                    onChange={(e) => {
-                      this.setState({
-                        sshKeysPath: e.target.value,
-                      })
-                    }}
-                    value={this.state.sshKeysPath}
-                    helperText="Path to directory with SSH keys"
-                    inputProps={{
-                      name: 'sshKeysPath',
-                      id: 'sshKeysPath',
-                    }}
-                    fullWidth
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-              </Grid>
-            </ExpansionPanelDetails>
-          </ExpansionPanel>
-          <br />
-          <h2>
-            2. Generate token to upload postgres-checkup reports to console
-          </h2>
-          <div>{token}</div>
-
-          <h2>3. Deploy using Docker or building from source</h2>
-
-          <Tabs
-            value={this.state?.tab}
-            onChange={this.handleChangeTab}
-            aria-label="simple tabs example"
-          >
-            <Tab label="Docker" {...a11yProps(0)} />
-            <Tab label="Build from source" {...a11yProps(1)} />
-          </Tabs>
-
-          <TabPanel value={this.state?.tab} index={1}>
-            <Typography>
-              <strong>Requirements</strong>: bash, coreutils, jq, golang, awk,
-              sed, pandoc, wkhtmltopdf (see{' '}
-              <a
-                href={
-                  'https://gitlab.com/postgres-ai/postgres-checkup/' +
-                  'blob/master/README.md'
-                }
-                target="_blank"
-                rel="noreferrer"
-              >
-                {' '}
-                README{' '}
-              </a>
-              ).
-              <br />
-              <strong>Clone repo:</strong>
-              <span className={classes.codeBlock}>
-                git clone https://gitlab.com/postgres-ai/postgres-checkup.git &&
-                cd postgres-checkup
-              </span>
-              <br />
-              <strong>Start script below:</strong>
-            </Typography>
-            <div className={classes.relativeDiv}>
-              <TextField
-                variant="outlined"
-                id="startWithSource"
-                multiline
-                value={CfgGen.generateFromSourcesInstruction(this.state)}
-                className={classes.code}
-                margin="normal"
-                InputProps={{
-                  readOnly: true,
-                  id: 'generatedSrcCfg',
-                }}
-                error={!CfgGen.requiredParamsFilled(this.state)}
-                helperText={
-                  !CfgGen.requiredParamsFilled(this.state)
-                    ? 'Please fill all mandatory fields'
-                    : ''
-                }
-              />
-              {copySrcCfgBtn}
-            </div>
-          </TabPanel>
-
-          <TabPanel value={this.state?.tab} index={0}>
-            <Typography>
-              <strong>Requirements: </strong> Docker
-              <br />
-              <strong>Start script below:</strong>
-            </Typography>
-            <div className={classes.relativeDiv}>
-              <TextField
-                variant="outlined"
-                id="configCode"
-                multiline
-                value={CfgGen.generateDockerInstruction(this.state)}
-                className={classes.code}
-                margin="normal"
-                InputProps={{
-                  readOnly: true,
-                  id: 'generatedDockerCfg',
-                }}
-                error={!CfgGen.requiredParamsFilled(this.state)}
-                helperText={
-                  !CfgGen.requiredParamsFilled(this.state)
-                    ? 'Please fill all mandatory fields'
-                    : ''
-                }
-              />
-              {copyDockerCfgBtn}
-            </div>
-          </TabPanel>
-        </div>
-      )
-    } else {
-      content = (
-        <div>
-          <Spinner className={classes.progress} />
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {
-          <ConsoleBreadcrumbsWrapper
-            {...this.props}
-            breadcrumbs={[{ name: 'Checkup configuration' }]}
-          />
-        }
-
-        {content}
-      </div>
-    )
-  }
-}
-
-export default CheckupAgentForm
diff --git a/ui/packages/platform/src/components/CheckupAgentForm/CheckupAgentFormWrapper.tsx b/ui/packages/platform/src/components/CheckupAgentForm/CheckupAgentFormWrapper.tsx
deleted file mode 100644
index 334b13b1..00000000
--- a/ui/packages/platform/src/components/CheckupAgentForm/CheckupAgentFormWrapper.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { theme } from '@postgres.ai/shared/styles/theme'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import CheckupAgentForm from 'components/CheckupAgentForm/CheckupAgentForm'
-
-export interface CheckupAgentFormProps {
-  orgId: number
-}
-
-export const CheckupAgentFormWrapper = (props: CheckupAgentFormProps) => {
-  const useStyles = makeStyles(
-    (muiTheme) => ({
-      root: {
-        'min-height': '100%',
-        'z-index': 1,
-        position: 'relative',
-        [muiTheme.breakpoints.down('sm')]: {
-          maxWidth: '100vw',
-        },
-        [muiTheme.breakpoints.up('md')]: {
-          maxWidth: 'calc(100vw - 200px)',
-        },
-        [muiTheme.breakpoints.up('lg')]: {
-          maxWidth: 'calc(100vw - 200px)',
-        },
-        '& h2': {
-          ...theme.typography.h2,
-        },
-        '& h3': {
-          ...theme.typography.h3,
-        },
-        '& h4': {
-          ...theme.typography.h4,
-        },
-        '& .MuiExpansionPanelSummary-root.Mui-expanded': {
-          minHeight: 24,
-        },
-      },
-      heading: {
-        ...theme.typography.h3,
-      } as {
-        [key: string]: string
-      },
-      fieldValue: {
-        display: 'inline-block',
-        width: '100%',
-      },
-      tokenInput: {
-        ...styles.inputField,
-        margin: 0,
-        'margin-top': 10,
-        'margin-bottom': 10,
-      },
-      textInput: {
-        ...styles.inputField,
-        margin: 0,
-        marginTop: 0,
-        marginBottom: 10,
-      },
-      hostItem: {
-        marginRight: 10,
-        marginBottom: 5,
-        marginTop: 5,
-      },
-      fieldRow: {
-        marginBottom: 10,
-        display: 'block',
-      },
-      fieldBlock: {
-        width: '100%',
-        'max-width': 600,
-        'margin-bottom': 15,
-        '& > div.MuiFormControl- > label': {
-          fontSize: 20,
-        },
-        '& input, & .MuiOutlinedInput-multiline': {
-          padding: 13,
-        },
-      },
-      relativeFieldBlock: {
-        marginBottom: 10,
-        marginRight: 20,
-        position: 'relative',
-      },
-      addTokenButton: {
-        marginLeft: 10,
-        marginTop: 10,
-      },
-      code: {
-        width: '100%',
-        'margin-top': 0,
-        '& > div': {
-          paddingTop: 12,
-        },
-        'background-color': 'rgb(246, 248, 250)',
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: 14,
-        },
-      },
-      codeBlock: {
-        fontFamily:
-          '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-          ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-        width: '100%',
-        padding: 3,
-        marginTop: 0,
-        border: 'rgb(204, 204, 204);',
-        borderRadius: 3,
-        color: 'black',
-        backgroundColor: 'rgb(246, 248, 250)',
-      },
-      details: {
-        display: 'block',
-      },
-      copyButton: {
-        position: 'absolute',
-        top: 6,
-        right: 6,
-        fontSize: 20,
-      },
-      relativeDiv: {
-        position: 'relative',
-      },
-      radioButton: {
-        '& > label > span.MuiFormControlLabel-label': {
-          fontSize: '0.9rem',
-        },
-      },
-      legend: {
-        fontSize: '10px',
-      },
-      advancedExpansionPanelSummary: {
-        'justify-content': 'left',
-        '& div.MuiExpansionPanelSummary-content': {
-          'flex-grow': 0,
-        },
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <CheckupAgentForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ConsoleBreadcrumbs/ConsoleBreadcrumbs.tsx b/ui/packages/platform/src/components/ConsoleBreadcrumbs/ConsoleBreadcrumbs.tsx
deleted file mode 100644
index be8497c0..00000000
--- a/ui/packages/platform/src/components/ConsoleBreadcrumbs/ConsoleBreadcrumbs.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { NavLink } from 'react-router-dom'
-import { Typography, Paper, Breadcrumbs } from '@material-ui/core'
-import clsx from 'clsx'
-
-import { Head, createTitle as createTitleBase } from 'components/Head'
-import { ConsoleBreadcrumbsProps } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import Urls from '../../utils/urls'
-
-interface ConsoleBreadcrumbsState {
-  data: {
-    userProfile: {
-      data: {
-        orgs: {
-          [org: string]: {
-            name: string
-            projects: {
-              [project: string]: {
-                name: string
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-interface ConsoleBreadcrumbsWithStylesProps extends ConsoleBreadcrumbsProps {
-  classes: ClassesType
-}
-
-const createTitle = (parts: string[]) => {
-  const filteredParts = parts.filter((part) => part !== 'Organizations')
-  return createTitleBase(filteredParts)
-}
-
-class ConsoleBreadcrumbs extends Component<
-  ConsoleBreadcrumbsWithStylesProps,
-  ConsoleBreadcrumbsState
-> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-    })
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  render() {
-    const { classes, hasDivider = false, breadcrumbs } = this.props
-    const org = this.props.org ? this.props.org : null
-    const project = this.props.project ? this.props.project : null
-    const orgs =
-      this.state &&
-      this.state.data &&
-      this.state.data.userProfile.data &&
-      this.state.data.userProfile.data.orgs
-        ? this.state.data.userProfile.data.orgs
-        : null
-    const paths = []
-    let lastUrl = ''
-
-    if (!breadcrumbs.length || Urls.isSharedUrl()) {
-      return null
-    }
-
-    if (org && orgs && orgs[org]) {
-      if (orgs[org].name) {
-        paths.push({ name: 'Organizations', url: '/' })
-        paths.push({ name: orgs[org].name, url: '/' + org })
-        lastUrl = '/' + org
-      }
-
-      if (project && orgs[org].projects && orgs[org].projects[project]) {
-        paths.push({ name: orgs[org].projects[project].name, url: null })
-        lastUrl = '/' + org + '/' + project
-      }
-    }
-
-    for (let i = 0; i < breadcrumbs.length; i++) {
-      if (breadcrumbs[i].url && breadcrumbs[i].url?.indexOf('/') === -1) {
-        breadcrumbs[i].url = lastUrl + '/' + breadcrumbs[i].url
-        lastUrl = breadcrumbs[i].url as string
-      }
-      breadcrumbs[i].isLast = i === breadcrumbs.length - 1
-      paths.push(breadcrumbs[i])
-    }
-
-    return (
-      <>
-        <Head title={createTitle(paths.map((path) => path.name))} />
-        <Paper
-          elevation={0}
-          className={clsx(
-            classes?.breadcrumbPaper,
-            hasDivider && classes?.breadcrumbPaperWithDivider,
-          )}
-        >
-          <Breadcrumbs aria-label="breadcrumb">
-            {paths.map(
-              (
-                b: { name: string; url?: string | null; isLast?: boolean },
-                index,
-              ) => {
-                return (
-                  <span key={index}>
-                    {b.url ? (
-                      <NavLink
-                        color="inherit"
-                        to={b.url}
-                        className={classes?.breadcrumbsLink}
-                      >
-                        {b.name}
-                      </NavLink>
-                    ) : (
-                      <Typography
-                        color="textPrimary"
-                        className={
-                          b.isLast
-                            ? classes?.breadcrumbsActiveItem
-                            : classes?.breadcrumbsItem
-                        }
-                      >
-                        {b.name}
-                      </Typography>
-                    )}
-                  </span>
-                )
-              },
-            )}
-          </Breadcrumbs>
-        </Paper>
-      </>
-    )
-  }
-}
-
-export default ConsoleBreadcrumbs
diff --git a/ui/packages/platform/src/components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper.tsx b/ui/packages/platform/src/components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper.tsx
deleted file mode 100644
index e7414f06..00000000
--- a/ui/packages/platform/src/components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import ConsoleBreadcrumbs from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbs'
-
-export interface ConsoleBreadcrumbsProps {
-  hasDivider?: boolean
-  org?: string | number
-  project?: string
-  breadcrumbs: { name: string; url?: string | null; isLast?: boolean }[]
-}
-
-export const ConsoleBreadcrumbsWrapper = (props: ConsoleBreadcrumbsProps) => {
-  const useStyles = makeStyles(
-    {
-      pointerLink: {
-        cursor: 'pointer',
-      },
-      breadcrumbsLink: {
-        maxWidth: 150,
-        textOverflow: 'ellipsis',
-        overflow: 'hidden',
-        display: 'block',
-        cursor: 'pointer',
-        whiteSpace: 'nowrap',
-        fontSize: '12px',
-        lineHeight: '14px',
-        textDecoration: 'none',
-        color: colors.consoleFadedFont,
-      },
-      breadcrumbsItem: {
-        fontSize: '12px',
-        lineHeight: '14px',
-        color: colors.consoleFadedFont,
-      },
-      breadcrumbsActiveItem: {
-        fontSize: '12px',
-        lineHeight: '14px',
-        color: '#000000',
-      },
-      breadcrumbPaper: {
-        '& a, & a:visited': {
-          color: colors.consoleFadedFont,
-        },
-        'padding-bottom': '8px',
-        marginTop: '-10px',
-        'font-size': '12px',
-        borderRadius: 0,
-      },
-      breadcrumbPaperWithDivider: {
-        borderBottom: `1px solid ${colors.consoleStroke}`,
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <ConsoleBreadcrumbs {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ConsoleButton/ConsoleButton.tsx b/ui/packages/platform/src/components/ConsoleButton/ConsoleButton.tsx
deleted file mode 100644
index e1be66f7..00000000
--- a/ui/packages/platform/src/components/ConsoleButton/ConsoleButton.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { Tooltip, Button } from '@material-ui/core'
-
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { ConsoleButtonProps } from 'components/ConsoleButton/ConsoleButtonWrapper'
-
-interface ConsoleButtonWithStylesProps extends ConsoleButtonProps {
-  classes: ClassesType
-}
-
-class ConsoleButton extends Component<ConsoleButtonWithStylesProps> {
-  render() {
-    const { classes, title, children, ...other } = this.props
-
-    // We have to use external tooltip component as disable button cannot show tooltip.
-    // Details: https://material-ui.com/components/tooltips/#disabled-elements.
-    return (
-      <Tooltip classes={{ tooltip: classes.tooltip }} title={title}>
-        <span>
-          <Button {...other}>{children}</Button>
-        </span>
-      </Tooltip>
-    )
-  }
-}
-
-export default ConsoleButton
diff --git a/ui/packages/platform/src/components/ConsoleButton/ConsoleButtonWrapper.tsx b/ui/packages/platform/src/components/ConsoleButton/ConsoleButtonWrapper.tsx
deleted file mode 100644
index 23d4674b..00000000
--- a/ui/packages/platform/src/components/ConsoleButton/ConsoleButtonWrapper.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import ConsoleButton from 'components/ConsoleButton/ConsoleButton'
-
-export interface ConsoleButtonProps {
-  title: string
-  children: React.ReactNode
-  className?: string
-  disabled?: boolean
-  variant: 'text' | 'outlined' | 'contained' | undefined
-  color: 'primary' | 'secondary' | undefined
-  onClick: () => void
-  id?: string
-}
-
-export const ConsoleButtonWrapper = (props: ConsoleButtonProps) => {
-  const useStyles = makeStyles(
-    {
-      tooltip: {
-        fontSize: '10px!important',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <ConsoleButton {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ConsolePageTitle.tsx b/ui/packages/platform/src/components/ConsolePageTitle.tsx
deleted file mode 100644
index 4c170526..00000000
--- a/ui/packages/platform/src/components/ConsolePageTitle.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  makeStyles,
-  Tooltip,
-  TextField,
-  InputAdornment,
-} from '@material-ui/core'
-import SearchIcon from '@material-ui/icons/Search'
-
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { icons } from '@postgres.ai/shared/styles/icons'
-
-interface ConsolePageTitleProps {
-  title: string
-  information?: string | JSX.Element
-  label?: string
-  actions?: JSX.Element[] | string[]
-  top?: boolean
-  filterProps?: {
-    filterValue: string
-    filterHandler: (event: React.ChangeEvent<HTMLInputElement>) => void
-    placeholder: string
-  } | null
-}
-
-const useStyles = makeStyles(
-  {
-    pageTitle: {
-      flex: '0 0 auto',
-      '& > h1': {
-        display: 'inline-block',
-        fontSize: '16px',
-        lineHeight: '19px',
-        marginRight: '10px',
-      },
-      'border-top': '1px solid ' + colors.consoleStroke,
-      'border-bottom': '1px solid ' + colors.consoleStroke,
-      'padding-top': '8px',
-      'padding-bottom': '8px',
-      display: 'block',
-      overflow: 'auto',
-      'margin-bottom': '20px',
-      'max-width': '100%',
-    },
-    pageTitleTop: {
-      flex: '0 0 auto',
-      '& > h1': {
-        display: 'inline-block',
-        fontSize: '16px',
-        lineHeight: '19px',
-        marginRight: '10px',
-      },
-      'border-bottom': '1px solid ' + colors.consoleStroke,
-      'padding-top': '0px',
-      'margin-top': '-10px',
-      'padding-bottom': '8px',
-      display: 'block',
-      overflow: 'auto',
-      'margin-bottom': '20px',
-    },
-    pageTitleActions: {
-      display: 'flex',
-      alignItems: 'center',
-      height: '100%',
-      float: 'right',
-    },
-    pageTitleActionContainer: {
-      marginLeft: '10px',
-      display: 'inline-block',
-      height: "36px",
-
-      "& > span, button": {
-        height: '100%',
-      },
-    },
-    tooltip: {
-      fontSize: '10px!important',
-    },
-    label: {
-      backgroundColor: colors.primary.main,
-      color: colors.primary.contrastText,
-      display: 'inline-block',
-      borderRadius: 3,
-      fontSize: 10,
-      lineHeight: '12px',
-      padding: 2,
-      paddingLeft: 3,
-      paddingRight: 3,
-      verticalAlign: 'text-top',
-    },
-  },
-  { index: 1 },
-)
-
-const ConsolePageTitle = ({
-  title,
-  information,
-  label,
-  actions,
-  top,
-  filterProps,
-}: ConsolePageTitleProps) => {
-  const classes = useStyles()
-
-  if (!title) {
-    return null
-  }
-
-  return (
-    <div className={top ? classes.pageTitleTop : classes.pageTitle}>
-      <h1>{title}</h1>
-      {information ? (
-        <Tooltip
-          title={information}
-          classes={{ tooltip: classes.tooltip }}
-          enterTouchDelay={0}
-        >
-          {icons.infoIcon}
-        </Tooltip>
-      ) : null}
-      {label ? <span className={classes.label}>{label}</span> : null}
-      {(actions && actions?.length > 0) || filterProps ? (
-        <span className={classes.pageTitleActions}>
-          {filterProps ? (
-            <TextField
-              id="filterOrgsInput"
-              variant="outlined"
-              size="small"
-              style={{ minWidth: '260px' }}
-              className="filterOrgsInput"
-              placeholder={filterProps.placeholder}
-              value={filterProps.filterValue}
-              onChange={filterProps.filterHandler}
-              InputProps={{
-                endAdornment: (
-                  <InputAdornment position="start">
-                    <SearchIcon />
-                  </InputAdornment>
-                ),
-              }}
-            />
-          ) : null}
-          {actions?.map((a, index) => {
-            return (
-              <span key={index} className={classes.pageTitleActionContainer}>
-                {a}
-              </span>
-            )
-          })}
-        </span>
-      ) : null}
-    </div>
-  )
-}
-
-export default ConsolePageTitle
diff --git a/ui/packages/platform/src/components/ContentLayout/DemoOrgNotice/index.tsx b/ui/packages/platform/src/components/ContentLayout/DemoOrgNotice/index.tsx
deleted file mode 100644
index 2ada292b..00000000
--- a/ui/packages/platform/src/components/ContentLayout/DemoOrgNotice/index.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { makeStyles, Button } from '@material-ui/core'
-import { useHistory } from 'react-router-dom'
-
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { icons } from '@postgres.ai/shared/styles/icons'
-
-import { ROUTES } from 'config/routes'
-
-const useStyles = makeStyles(
-  {
-    demoNoticeText: {
-      marginLeft: '0px',
-      display: 'inline-block',
-      position: 'relative',
-      backgroundColor: colors.blue,
-      color: colors.secondary2.darkDark,
-      width: '100%',
-      fontSize: '12px',
-      lineHeight: '24px',
-      fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-      paddingLeft: '10px',
-      paddingTop: '4px',
-      paddingBottom: '4px',
-      '& > svg': {
-        verticalAlign: 'baseline',
-        marginBottom: '-1px',
-        marginLeft: '0px',
-        marginRight: '4px',
-      },
-    },
-    demoOrgNoticeButton: {
-      padding: '2px',
-      paddingLeft: '6px',
-      paddingRight: '6px',
-      borderRadius: '3px',
-      marginLeft: '5px',
-      marginTop: '-2px',
-      height: '20px',
-      lineHeight: '20px',
-      fontSize: '12px',
-      fontWeight: 'bold',
-    },
-    noWrap: {
-      whiteSpace: 'nowrap',
-    },
-  },
-  { index: 1 },
-)
-
-export const DemoOrgNotice = () => {
-  const classes = useStyles()
-  const history = useHistory()
-
-  const goToOrgForm = () => history.push(ROUTES.CREATE_ORG.path)
-
-  return (
-    <div className={classes.demoNoticeText}>
-      {icons.infoIconBlue}&nbsp;This is a demo organization. All the data here is public.
-      <Button
-        variant="contained"
-        color="primary"
-        className={classes.demoOrgNoticeButton}
-        onClick={goToOrgForm}
-      >
-        Create your own organization
-      </Button>
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/components/ContentLayout/DeprecatedApiBanner/index.tsx b/ui/packages/platform/src/components/ContentLayout/DeprecatedApiBanner/index.tsx
deleted file mode 100644
index 47f00d86..00000000
--- a/ui/packages/platform/src/components/ContentLayout/DeprecatedApiBanner/index.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-
-import { Status } from '@postgres.ai/shared/components/Status'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import { colors } from '@postgres.ai/shared/styles/vars'
-
-const useStyles = makeStyles(
-  {
-    root: {
-      background: colors.status.warning,
-      color: colors.white,
-      fontSize: '12px',
-      padding: '4px 10px',
-      lineHeight: '1.5',
-    },
-    status: {
-      color: 'inherit',
-    },
-    link: {
-      color: 'inherit',
-    },
-  },
-  { index: 1 },
-)
-
-export const DeprecatedApiBanner = () => {
-  const classes = useStyles()
-
-  return (
-    <div className={classes.root}>
-      <Status type="warning" className={classes.status} disableColor>
-        The version of your DBLab instance is deprecated.
-      </Status>{' '}
-      Some information about DBLab, disks, clones, and snapshots may be
-      unavailable.
-      <br />
-      Please upgrade your DBLab to&nbsp;
-      <GatewayLink
-        href="https://gitlab.com/postgres-ai/database-lab/-/releases"
-        className={classes.link}
-      >
-        the latest available version
-      </GatewayLink>
-      .
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/components/ContentLayout/Footer/index.tsx b/ui/packages/platform/src/components/ContentLayout/Footer/index.tsx
deleted file mode 100644
index 8ef280ca..00000000
--- a/ui/packages/platform/src/components/ContentLayout/Footer/index.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { makeStyles, useMediaQuery } from '@material-ui/core'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import { useMemo } from 'react'
-import { useLocation } from 'react-router-dom'
-
-import settings from 'utils/settings'
-import cn from "classnames";
-
-const useStyles = makeStyles(
-  (theme) => ({
-    footer: {
-      flex: '0 0 auto',
-      backgroundColor: 'rgb(68, 79, 96)',
-      color: '#fff',
-      display: 'flex',
-      justifyContent: 'center',
-      padding: '16px 20px',
-      [theme.breakpoints.down('sm')]: {
-        padding: '12px 12px',
-        flexDirection: 'column',
-      },
-    },
-    hidden: {
-      display: 'none'
-    },
-    footerCopyrightItem: {
-      marginRight: 50,
-      [theme.breakpoints.down('sm')]: {
-        marginBottom: 10,
-      },
-    },
-    footerLinks: {
-      display: 'flex',
-      [theme.breakpoints.down('sm')]: {
-        flexDirection: 'column',
-        flexWrap: 'wrap',
-        maxHeight: '80px',
-      },
-    },
-    footerItem: {
-      marginLeft: 10,
-      marginRight: 10,
-      color: '#fff',
-      '& a': {
-        color: '#fff',
-        textDecoration: 'none',
-      },
-      '& a:hover': {
-        textDecoration: 'none',
-      },
-      [theme.breakpoints.down('sm')]: {
-        marginLeft: 0,
-        marginBottom: 5,
-      },
-    },
-    footerItemSeparator: {
-      display: 'inline-block',
-      [theme.breakpoints.down('sm')]: {
-        display: 'none',
-      },
-    },
-  }),
-  { index: 1 },
-)
-
-export const Footer = () => {
-  const classes = useStyles()
-  const location = useLocation();
-  const isMobile = useMediaQuery('(max-width:480px)');
-
-  const isAssistantPage = useMemo(() => {
-    return /^\/[^\/]+\/assistant(\/[^\/]+)?\/?$/.test(location.pathname);
-  }, [location.pathname]);
-
-  return (
-    <footer className={cn(classes.footer, {[classes.hidden]: isAssistantPage && isMobile})}>
-      <div className={classes.footerCopyrightItem}>
-        {new Date().getFullYear()} © Postgres.AI
-      </div>
-      <div className={classes.footerLinks}>
-        <div className={classes.footerItem}>
-          <GatewayLink href={settings.rootUrl + '/docs'}>
-            Documentation
-          </GatewayLink>
-        </div>
-        <div className={classes.footerItemSeparator}>|</div>
-        <div className={classes.footerItem}>
-          <GatewayLink href={settings.rootUrl + '/blog'}>
-            News
-          </GatewayLink>
-        </div>
-        <div className={classes.footerItemSeparator}>|</div>
-        <div className={classes.footerItem}>
-          <GatewayLink href={settings.rootUrl + '/tos'}>
-            Terms of Service
-          </GatewayLink>
-        </div>
-        <div className={classes.footerItemSeparator}>|</div>
-        <div className={classes.footerItem}>
-          <GatewayLink href={settings.rootUrl + '/privacy'}>
-            Privacy Policy
-          </GatewayLink>
-        </div>
-        <div className={classes.footerItemSeparator}>|</div>
-        <div className={classes.footerItem}>
-          <GatewayLink href={settings.rootUrl + '/contact'}>
-            Ask support
-          </GatewayLink>
-        </div>
-      </div>
-    </footer>
-  )
-}
diff --git a/ui/packages/platform/src/components/ContentLayout/index.tsx b/ui/packages/platform/src/components/ContentLayout/index.tsx
deleted file mode 100644
index 888551f9..00000000
--- a/ui/packages/platform/src/components/ContentLayout/index.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/* eslint-disable react-hooks/rules-of-hooks */
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React from 'react'
-import { useRouteMatch } from 'react-router-dom'
-import clsx from 'clsx'
-import { observer } from 'mobx-react-lite'
-
-import { ROUTES } from 'config/routes'
-import settings from 'utils/settings'
-import { bannersStore } from 'stores/banners'
-
-import { DemoOrgNotice } from './DemoOrgNotice'
-import { DeprecatedApiBanner } from './DeprecatedApiBanner'
-import { Footer } from './Footer'
-
-import styles from './styles.module.scss'
-
-type Props = {
-  children: React.ReactNode
-}
-
-export const ContentLayout = React.memo(observer((props: Props) => {
-  const { children } = props
-
-  const isOrgJoeInstance = Boolean(
-    useRouteMatch(ROUTES.ORG.JOE_INSTANCES.JOE_INSTANCE.createPath()),
-  )
-
-  const isProjectJoeInstance = Boolean(
-    useRouteMatch(ROUTES.ORG.PROJECT.JOE_INSTANCES.JOE_INSTANCE.createPath()),
-  )
-
-  const isDemoOrg = Boolean(useRouteMatch(`/${settings.demoOrgAlias}`))
-
-  const isHiddenFooter = isOrgJoeInstance || isProjectJoeInstance
-
-  return (
-    <div className={styles.root}>
-      {isDemoOrg && <DemoOrgNotice />}
-      { bannersStore.isOpenDeprecatedApi && <DeprecatedApiBanner /> }
-
-      <div className={styles.wrapper} id="content-container">
-        <main
-          className={clsx(styles.content, isHiddenFooter && styles.fullScreen)}
-        >
-          {children}
-        </main>
-        <Footer />
-      </div>
-    </div>
-  )
-}))
diff --git a/ui/packages/platform/src/components/ContentLayout/styles.module.scss b/ui/packages/platform/src/components/ContentLayout/styles.module.scss
deleted file mode 100644
index 0df13e06..00000000
--- a/ui/packages/platform/src/components/ContentLayout/styles.module.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-@import '@postgres.ai/shared/styles/mixins';
-
-.root {
-  display: flex;
-  flex-direction: column;
-  flex: 1 1 100%;
-  padding-top: 40px;
-  width: 100%;
-  // Flexbox bug fix - https://bugzilla.mozilla.org/show_bug.cgi?id=1086218#c4.
-  min-width: 0;
-}
-
-.wrapper {
-  flex: 1 1 100%;
-  overflow: auto;
-  display: flex;
-  flex-direction: column;
-}
-
-.content {
-  flex: 1 1 100%;
-  display: flex;
-  flex-direction: column;
-  padding: 20px;
-
-  &.fullScreen {
-    flex-shrink: 0;
-  }
-
-  @include sm {
-    padding: 20px 12px;
-  }
-}
diff --git a/ui/packages/platform/src/components/CreateClusterCards/CreateClusterCards.tsx b/ui/packages/platform/src/components/CreateClusterCards/CreateClusterCards.tsx
deleted file mode 100644
index 91fcabed..00000000
--- a/ui/packages/platform/src/components/CreateClusterCards/CreateClusterCards.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import classNames from 'classnames'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-import { DashboardProps } from 'components/Dashboard/DashboardWrapper'
-
-import Urls from '../../utils/urls'
-import { messages } from '../../assets/messages'
-import { useStyles } from 'components/CreateDbLabCards/CreateDbLabCards'
-
-export const CreateClusterCards = ({
-  isModal,
-  props,
-  dblabPermitted,
-}: {
-  isModal?: boolean
-  props: DashboardProps
-  dblabPermitted: boolean | undefined
-}) => {
-  const classes = useStyles()
-
-  const createClusterInstanceButton = (provider: string) => {
-    props.history.push(Urls.linkClusterInstanceAdd(props, provider))
-  }
-
-  const CreateButton = ({ type, title }: { type: string; title: string }) => (
-    <ConsoleButtonWrapper
-      disabled={!dblabPermitted}
-      variant="contained"
-      color="primary"
-      onClick={() => createClusterInstanceButton(type)}
-      title={dblabPermitted ? title : messages.noPermission}
-    >
-      {type === 'create' ? 'Create' : 'Install'}
-    </ConsoleButtonWrapper>
-  )
-
-  const productData = [
-    {
-      title: 'Create Postgres Cluster in your cloud',
-      renderDescription: () => (
-        <>
-          <p>
-            Supported cloud platforms include AWS, GCP, Digital Ocean, and
-            Hetzner Cloud.
-          </p>
-          <p>All components are installed within your cloud account.</p>
-          <p>Your data remains secure and never leaves your infrastructure.</p>
-        </>
-      ),
-      icon: icons.createDLEIcon,
-      actions: [
-        {
-          id: 'createDblabInstanceButton',
-          content: (
-            <CreateButton type="create" title="Create Cluster in your cloud" />
-          ),
-        },
-      ],
-    },
-    {
-      title: 'BYOM (Bring Your Own Machines)',
-      renderDescription: () => (
-        <>
-          <p>
-            Install on your existing resources, regardless of the machine or
-            location. Compatible with both cloud and bare metal infrastructures.
-            Your data remains secure and never leaves your infrastructure.
-          </p>
-          <p>Requirements:</p>
-          <ul>
-            <li>
-              Three or more servers running a supported Linux distro: Ubuntu
-              (20.04/22.04), Debian (11/12), CentOS Stream (8/9), Rocky Linux
-              (8/9), AlmaLinux (8/9), or Red Hat Enterprise Linux (8/9).
-            </li>
-            <li>Internet connectivity</li>
-          </ul>
-        </>
-      ),
-      icon: icons.installDLEIcon,
-      actions: [
-        {
-          id: 'createDblabInstanceButton',
-          content: (
-            <CreateButton
-              type="install"
-              title="Install Cluster on an existing machine"
-            />
-          ),
-        },
-      ],
-    },
-  ]
-
-  return (
-    <StubContainer
-      className={classNames(
-        !isModal && classes.zeroMaxHeight,
-        classes.stubContainerProjects,
-      )}
-    >
-      {productData.map((product) => (
-        <ProductCardWrapper
-          inline
-          className={classes.productCardProjects}
-          title={product.title}
-          actions={product.actions}
-          icon={product.icon}
-        >
-          <div>{product.renderDescription()}</div>
-        </ProductCardWrapper>
-      ))}
-    </StubContainer>
-  )
-}
diff --git a/ui/packages/platform/src/components/CreateDbLabCards/CreateDbLabCards.tsx b/ui/packages/platform/src/components/CreateDbLabCards/CreateDbLabCards.tsx
deleted file mode 100644
index 8529fd3a..00000000
--- a/ui/packages/platform/src/components/CreateDbLabCards/CreateDbLabCards.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import classNames from 'classnames'
-import { makeStyles } from '@material-ui/core'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-import { DashboardProps } from 'components/Dashboard/DashboardWrapper'
-
-import Urls from '../../utils/urls'
-import { messages } from '../../assets/messages'
-
-export const useStyles = makeStyles((theme) => ({
-  zeroMaxHeight: {
-    maxHeight: 0,
-  },
-  stubContainerProjects: {
-    marginRight: '-20px',
-    padding: '0 40px',
-    alignItems: 'initial !important',
-
-    '& > div:nth-child(1), & > div:nth-child(2)': {
-      minHeight: '350px',
-    },
-
-    [theme.breakpoints.down('sm')]: {
-      flexDirection: 'column',
-    },
-  },
-  productCardProjects: {
-    flex: '1 1 0',
-    marginRight: '20px',
-    height: 'maxContent',
-    gap: 20,
-    maxHeight: '100%',
-    '& ul': {
-      marginBlockStart: '-10px',
-      paddingInlineStart: '30px',
-    },
-
-    '& svg': {
-      width: '206px',
-      height: '130px',
-    },
-
-    '&:nth-child(1) svg': {
-      marginBottom: '-5px',
-    },
-
-    '&:nth-child(2) svg': {
-      marginBottom: '-20px',
-    },
-
-    '& li': {
-      listStyleType: 'none',
-      position: 'relative',
-
-      '&::before': {
-        content: '"-"',
-        position: 'absolute',
-        left: '-10px',
-        top: '0',
-      },
-    },
-
-    [theme.breakpoints.down('sm')]: {
-      flex: '100%',
-      marginTop: '20px',
-      minHeight: 'auto !important',
-
-      '&:nth-child(1) svg': {
-        marginBottom: 0,
-      },
-
-      '&:nth-child(2) svg': {
-        marginBottom: 0,
-      },
-    },
-  },
-}))
-
-export const CreatedDbLabCards = ({
-  isModal,
-  props,
-  dblabPermitted,
-}: {
-  isModal?: boolean
-  props: DashboardProps
-  dblabPermitted: boolean | undefined
-}) => {
-  const classes = useStyles()
-
-  const createDblabInstanceButtonHandler = (provider: string) => {
-    props.history.push(Urls.linkDbLabInstanceAdd(props, provider))
-  }
-
-  const CreateButton = ({ type, title }: { type: string; title: string }) => (
-    <ConsoleButtonWrapper
-      disabled={!dblabPermitted}
-      variant="contained"
-      color="primary"
-      onClick={() => createDblabInstanceButtonHandler(type)}
-      title={dblabPermitted ? title : messages.noPermission}
-    >
-      {type === 'create' ? 'Create' : 'Install'}
-    </ConsoleButtonWrapper>
-  )
-
-  const productData = [
-    {
-      title: 'Create DBLab in your cloud',
-      renderDescription: () => (
-        <>
-          <p>
-            Supported cloud platforms include AWS, GCP, Digital Ocean, and
-            Hetzner Cloud.
-          </p>
-          <p>All components are installed within your cloud account.</p>
-          <p>Your data remains secure and never leaves your infrastructure.</p>
-        </>
-      ),
-      icon: icons.createDLEIcon,
-      actions: [
-        {
-          id: 'createDblabInstanceButton',
-          content: (
-            <CreateButton type="create" title="Create DBLab in your cloud" />
-          ),
-        },
-      ],
-    },
-    {
-      title: 'BYOM (Bring Your Own Machine)',
-      renderDescription: () => (
-        <>
-          <p>
-            Install on your existing resources, regardless of the machine or
-            location. Compatible with both cloud and bare metal infrastructures.
-            Your data remains secure and never leaves your infrastructure.
-          </p>
-          <p>Requirements:</p>
-          <ul>
-            <li>Ubuntu 20.04 or newer</li>
-            <li>Internet connectivity</li>
-          </ul>
-        </>
-      ),
-      icon: icons.installDLEIcon,
-      actions: [
-        {
-          id: 'createDblabInstanceButton',
-          content: (
-            <CreateButton
-              type="install"
-              title="Install DBLab on an existing machine"
-            />
-          ),
-        },
-      ],
-    },
-  ]
-
-  return (
-    <StubContainer
-      className={classNames(
-        !isModal && classes.zeroMaxHeight,
-        classes.stubContainerProjects,
-      )}
-    >
-      {productData.map((product) => (
-        <ProductCardWrapper
-          inline
-          className={classes.productCardProjects}
-          title={product.title}
-          actions={product.actions}
-          icon={product.icon}
-        >
-          <div>{product.renderDescription()}</div>
-        </ProductCardWrapper>
-      ))}
-    </StubContainer>
-  )
-}
diff --git a/ui/packages/platform/src/components/Dashboard/Dashboard.tsx b/ui/packages/platform/src/components/Dashboard/Dashboard.tsx
deleted file mode 100644
index c587878f..00000000
--- a/ui/packages/platform/src/components/Dashboard/Dashboard.tsx
+++ /dev/null
@@ -1,618 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, MouseEvent } from 'react'
-import { NavLink } from 'react-router-dom'
-import Brightness1Icon from '@material-ui/icons/Brightness1'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  Button,
-  Grid,
-} from '@material-ui/core'
-import ReactMarkdown from 'react-markdown'
-import rehypeRaw from 'rehype-raw'
-import remarkGfm from 'remark-gfm'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import {
-  ClassesType,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import { ROUTES } from 'config/routes'
-
-import Actions from '../../actions/actions'
-import ConsolePageTitle from '../ConsolePageTitle'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import Store from '../../stores/store'
-import Urls from '../../utils/urls'
-import settings from '../../utils/settings'
-import format from '../../utils/format'
-
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-import { DashboardProps } from 'components/Dashboard/DashboardWrapper'
-import { FilteredTableMessage } from 'components/AccessTokens/FilteredTableMessage/FilteredTableMessage'
-import { CreatedDbLabCards } from 'components/CreateDbLabCards/CreateDbLabCards'
-import { convertThread } from "../../api/bot/convertThread";
-
-interface DashboardWithStylesProps extends DashboardProps {
-  classes: ClassesType
-}
-
-interface DashboardState {
-  filterValue: string
-  data: {
-    auth: {
-      token: string
-    } | null
-    projects: {
-      error: boolean
-      isProcessing: boolean
-      orgId: number
-      data: {
-        label: string
-        project_label_or_name: string
-        id: number
-        name: string
-        alias: string
-      }[]
-    } | null
-    orgId: number
-    userProfile: {
-      data: {
-        platform_onboarding_text: string
-        orgs: {
-          [org: string]: {
-            is_blocked: boolean
-            created_at: string
-            id: number
-            alias: string
-            name: string
-            onboarding_text: string
-            projects: Object
-          }
-        }
-      }
-      isProcessing: boolean
-      isProcessed: boolean
-      error: boolean
-    } | null
-    useDemoData: {
-      isProcessing: boolean
-      isProcessed: boolean
-    } | null
-    dashboard: {
-      profileUpdateInitAfterDemo: boolean
-    } | null
-  }
-}
-
-class Dashboard extends Component<DashboardWithStylesProps, DashboardState> {
-  isThreadConverted = false;
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const orgId = this.props.orgId
-    const onlyProjects = this.props.onlyProjects
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      that.setState({ data: this.data })
-
-      const auth: DashboardState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const userProfile: DashboardState['data']['userProfile'] =
-        this.data && this.data.userProfile ? this.data.userProfile : null
-
-      const cookieName = "pgai_tmp_thread_id=";
-      const cookies = document.cookie.split(';').map(cookie => cookie.trim());
-      const pgaiTmpThreadId = cookies.find(cookie => cookie.startsWith(cookieName))?.substring(cookieName.length) || null;
-      if (pgaiTmpThreadId && !that.isThreadConverted) {
-        that.isThreadConverted = true;
-        try {
-          convertThread(pgaiTmpThreadId)
-            .then(({response, error}) => {
-              if (response?.final_thread_id) {
-                document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname.split('.').slice(-2).join('.')}`;
-                if (userProfile && userProfile.data && userProfile.data.orgs) {
-                  if (userProfile.data.orgs.hasOwnProperty('demo')) {
-                    that.props.history.push(`demo/assistant/${response.final_thread_id}`);
-                  }
-                }
-              }
-            })
-        } catch (error) {
-          console.error('Error converting thread:', error);
-        }
-      } else {
-        document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname.split('.').slice(-2).join('.')}`;
-      }
-
-      if (onlyProjects) {
-        const projects: DashboardState['data']['projects'] =
-          this.data && this.data.projects ? this.data.projects : null
-
-        if (
-          auth &&
-          auth.token &&
-          !projects?.isProcessing &&
-          !projects?.error &&
-          !that.state
-        ) {
-          Actions.getProjects(auth.token, orgId)
-        }
-
-        if (
-          auth &&
-          !that.state &&
-          !userProfile?.isProcessing &&
-          !userProfile?.error
-        ) {
-          Actions.getUserProfile(auth.token)
-        }
-      } else {
-        const useDemoData =
-          this.data && this.data.useDemoData ? this.data.useDemoData : null
-        const profileUpdateInitAfterDemo =
-          this.data && this.data.dashboard
-            ? this.data.dashboard.profileUpdateInitAfterDemo
-            : null
-
-        if (
-          auth &&
-          auth.token &&
-          ((!userProfile?.isProcessed &&
-            !userProfile?.isProcessing &&
-            !userProfile?.error) ||
-            (!profileUpdateInitAfterDemo &&
-              useDemoData.isProcessed &&
-              !useDemoData.error))
-        ) {
-          if (useDemoData.isProcessed) {
-            this.data.dashboard.profileUpdateInitAfterDemo = true
-          }
-
-          Actions.getUserProfile(auth.token)
-        }
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleClick = (
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    alias?: string,
-  ) => {
-    this.props.history.push('/' + alias)
-  }
-
-  useDemoDataButtonHandler = () => {
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    Actions.useDemoData(auth?.token)
-  }
-
-  addOrgButtonHandler = () => {
-    this.props.history.push(ROUTES.CREATE_ORG.path)
-  }
-
-  addCheckupAgentButtonHandler = () => {
-    this.props.history.push(Urls.linkCheckupAgentAdd(this.props))
-  }
-
-  dblabInstancesButtonHandler = (org: string | number, project: string) => {
-    return () => {
-      this.props.history.push(Urls.linkDbLabInstances({ org, project }))
-    }
-  }
-
-  joeInstancesButtonHandler = (org: string | number, project: string) => {
-    return () => {
-      this.props.history.push(Urls.linkJoeInstances({ org, project }))
-    }
-  }
-
-  checkupReportsButtonHandler = (org: string | number, project: string) => {
-    return () => {
-      this.props.history.push(Urls.linkReports({ org, project }))
-    }
-  }
-
-  filterOrgsInputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.setState({ filterValue: event.target.value })
-  }
-
-  render() {
-    const renderProjects = this.props.onlyProjects
-
-    if (renderProjects) {
-      return this.renderProjects()
-    }
-
-    // TODO(anatoly): Move organization to a separate page component.
-    return this.renderOrgs()
-  }
-
-  renderProjects() {
-    const { classes } = this.props
-    const org = this.props.org as string | number
-    const orgId = this.props.orgId
-    const projectsData =
-      this.state && this.state.data && this.state.data.projects
-        ? this.state.data.projects
-        : null
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: 'Dashboard' }]}
-      />
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title="Dashboard"
-        information={
-          'Project is a workspace for a specific Postgres cluster. ' +
-          'Currently projects can be created only during Checkup agent, or ' +
-          'Database Lab, or Joe instance configuration.'
-        }
-      />
-    )
-
-    if (projectsData && projectsData.error) {
-      return (
-        <>
-          {breadcrumbs}
-          <ErrorWrapper />
-        </>
-      )
-    }
-
-    if (!projectsData || !projectsData.data || projectsData.orgId !== orgId) {
-      return (
-        <>
-          {breadcrumbs}
-          <PageSpinner />
-        </>
-      )
-    }
-
-    const projects = projectsData.data
-
-    const dblabPermitted = this.props.orgPermissions?.dblabInstanceCreate
-
-    let table = (
-      <CreatedDbLabCards props={this.props} dblabPermitted={dblabPermitted} />
-    )
-
-    if (projects.length > 0) {
-      table = (
-        <HorizontalScrollContainer>
-          <Table className={classes.table}>
-            <TableHead>
-              <TableRow className={classes.row}>
-                <TableCell>Project</TableCell>
-                <TableCell>Activity</TableCell>
-              </TableRow>
-            </TableHead>
-            <TableBody>
-              {projects.map((p) => {
-                return (
-                  <TableRow hover className={classes.row} key={p.id}>
-                    <TableCell className={classes.cell}>
-                      {p.project_label_or_name || p.label || p.name}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      <Button
-                        variant="outlined"
-                        className={classes.activityButton}
-                        onClick={this.dblabInstancesButtonHandler(org, p.alias)}
-                      >
-                        Database Lab instances
-                      </Button>
-                      <Button
-                        variant="outlined"
-                        className={classes.activityButton}
-                        onClick={this.joeInstancesButtonHandler(org, p.alias)}
-                      >
-                        Joe instances
-                      </Button>
-                      <Button
-                        variant="outlined"
-                        className={classes.activityButton}
-                        onClick={this.checkupReportsButtonHandler(org, p.alias)}
-                      >
-                        Checkup reports
-                      </Button>
-                    </TableCell>
-                  </TableRow>
-                )
-              })}
-            </TableBody>
-          </Table>
-        </HorizontalScrollContainer>
-      )
-    }
-
-    let onboarding = null
-    if (
-      this.state.data &&
-      this.state.data.userProfile &&
-      this.state.data.userProfile.data &&
-      this.state.data.userProfile.data.orgs &&
-      this.state.data.userProfile.data.orgs[org] &&
-      this.state.data.userProfile.data.orgs[org].projects &&
-      this.state.data.userProfile.data.orgs[org].onboarding_text
-    ) {
-      onboarding = (
-        <div>
-          <Grid container spacing={2} id="usefulContainer">
-            <Grid item xs={12} sm={12}>
-              <div className={classes.onboardingCard}>
-                <h1>Getting started</h1>
-                <ReactMarkdown
-                  className={classes.onboarding}
-                  children={
-                    this.state.data.userProfile.data.orgs[org].onboarding_text
-                  }
-                  rehypePlugins={[rehypeRaw]}
-                  remarkPlugins={[remarkGfm]}
-                  components={{
-                    a: (props) => {
-                      const { href, target, children } = props
-                      return (
-                        <GatewayLink href={String(href)} target={target}>
-                          {String(children)}
-                        </GatewayLink>
-                      )
-                    },
-                  }}
-                />
-              </div>
-            </Grid>
-          </Grid>
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {onboarding}
-
-        {table}
-      </div>
-    )
-  }
-
-  renderOrgs() {
-    const { classes } = this.props
-    const profile =
-      this.state && this.state.data ? this.state.data.userProfile : null
-    const useDemoData =
-      this.state && this.state.data ? this.state.data.useDemoData : null
-    const profileUpdateInitAfterDemo =
-      this.state && this.state.data && this.state.data.dashboard
-        ? this.state.data.dashboard.profileUpdateInitAfterDemo
-        : null
-
-    const filteredItems =
-      profile?.data?.orgs &&
-      Object.keys(profile?.data?.orgs)?.filter(
-        (org) =>
-          org
-            ?.toLowerCase()
-            .indexOf((this.state.filterValue || '')?.toLowerCase()) !== -1,
-      )
-
-    // Show organizations.
-    if (this.state && this.state.data.projects?.error) {
-      return (
-        <div>
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !profile ||
-      profile.isProcessing ||
-      (profile && !profile.data) ||
-      !useDemoData ||
-      useDemoData.isProcessing ||
-      (useDemoData.isProcessed && !profileUpdateInitAfterDemo)
-    ) {
-      return (
-        <>
-          <PageSpinner />
-        </>
-      )
-    }
-
-    const useDemoDataButton = (
-      <ConsoleButtonWrapper
-        variant="outlined"
-        color="primary"
-        onClick={this.useDemoDataButtonHandler}
-        id="useDemoDataButton"
-        title=""
-      >
-        Join demo organization
-      </ConsoleButtonWrapper>
-    )
-
-    const createOrgButton = (
-      <ConsoleButtonWrapper
-        variant="contained"
-        color="primary"
-        onClick={this.addOrgButtonHandler}
-        id="createOrgButton"
-        className={classes.createOrgButton}
-        title=""
-      >
-        Create new organization
-      </ConsoleButtonWrapper>
-    )
-
-    const orgsPlaceholder = (
-      <StubContainer>
-        <ProductCardWrapper
-          inline
-          title={'Create or join an organization'}
-          actions={[
-            {
-              id: 'createOrgButton',
-              content: createOrgButton,
-            },
-            {
-              id: 'useDemoDataButton',
-              content: useDemoDataButton,
-            },
-          ]}
-        >
-          <p>
-            An organization represents a workspace for you and your colleagues.
-            Organizations allow you to manage users and collaborate across
-            multiple projects.
-          </p>
-          <p>
-            You can create a new organization, join the demo organization or ask
-            existing members of your organization to invite you.
-          </p>
-        </ProductCardWrapper>
-      </StubContainer>
-    )
-
-    const pageActions = []
-    if (
-      Object.keys(profile?.data?.orgs).length > 0 &&
-      (!profile.data?.orgs || !profile.data?.orgs[settings.demoOrgAlias])
-    ) {
-      pageActions.push(useDemoDataButton)
-    }
-
-    if (Object.keys(profile?.data?.orgs).length > 0) {
-      pageActions.push(createOrgButton)
-    }
-
-    return (
-      <div className={classes.root}>
-        <ConsolePageTitle
-          top={true}
-          title="Your organizations"
-          information="Your own organizations and organizations of which you are a member"
-          actions={filteredItems && pageActions}
-          filterProps={
-            Object.keys(profile?.data?.orgs).length > 0
-              ? {
-                  filterValue: this.state.filterValue,
-                  filterHandler: this.filterOrgsInputHandler,
-                  placeholder: 'Search organizations by name',
-                }
-              : null
-          }
-        />
-        {profile.data?.orgs && filteredItems && filteredItems.length > 0 ? (
-          <HorizontalScrollContainer>
-            <Table className={classes.table} id="orgsTable">
-              <TableHead>
-                <TableRow className={classes.row}>
-                  <TableCell className={classes.nameColumn}>
-                    Organization
-                  </TableCell>
-                  <TableCell>Projects count</TableCell>
-                  <TableCell>Status</TableCell>
-                  <TableCell>Created at</TableCell>
-                </TableRow>
-              </TableHead>
-              <TableBody>
-                {filteredItems.map((index) => {
-                  return (
-                    <TableRow
-                      hover
-                      className={classes.row}
-                      key={profile.data?.orgs[index].id}
-                      onClick={(event) =>
-                        this.handleClick(event, profile.data?.orgs[index].alias)
-                      }
-                      style={{ cursor: 'pointer' }}
-                      data-org-id={profile.data?.orgs[index].id}
-                      data-org-alias={profile.data?.orgs[index].alias}
-                    >
-                      <TableCell className={classes.nameColumn}>
-                        <NavLink to={'/' + profile.data?.orgs[index].alias}>
-                          {profile.data?.orgs[index].name}
-                        </NavLink>
-                      </TableCell>
-                      <TableCell className={classes.cell}>
-                        <NavLink
-                          to={
-                            '/' + profile.data?.orgs[index].alias + '/projects'
-                          }
-                        >
-                          {profile.data?.orgs[index].projects
-                            ? Object.keys(profile.data?.orgs[index]?.projects)
-                                .length
-                            : '0'}
-                        </NavLink>
-                      </TableCell>
-                      <TableCell className={classes.cell}>
-                        <Brightness1Icon
-                          className={
-                            profile.data?.orgs[index].is_blocked
-                              ? classes.blockedStatus
-                              : classes.validStatus
-                          }
-                        />
-                      </TableCell>
-                      <TableCell className={classes.cell}>
-                        {format.formatDate(
-                          profile.data?.orgs[index].created_at,
-                        ) || '-'}
-                      </TableCell>
-                    </TableRow>
-                  )
-                })}
-              </TableBody>
-            </Table>
-          </HorizontalScrollContainer>
-        ) : (
-          <FilteredTableMessage
-            filteredItems={filteredItems}
-            emptyState={orgsPlaceholder}
-            filterValue={this.state.filterValue}
-            clearFilter={() =>
-              this.setState({
-                filterValue: '',
-              })
-            }
-          />
-        )}
-      </div>
-    )
-  }
-}
-
-export default Dashboard
diff --git a/ui/packages/platform/src/components/Dashboard/DashboardWrapper.tsx b/ui/packages/platform/src/components/Dashboard/DashboardWrapper.tsx
deleted file mode 100644
index b279a66d..00000000
--- a/ui/packages/platform/src/components/Dashboard/DashboardWrapper.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { RouteComponentProps } from 'react-router'
-import Dashboard from 'components/Dashboard/Dashboard'
-
-export interface DashboardProps {
-  org?: string | number
-  orgId?: number
-  onlyProjects?: boolean
-  history: RouteComponentProps['history']
-  project?: string | undefined
-  orgPermissions?: {
-    dblabInstanceCreate?: boolean
-    checkupReportConfigure?: boolean
-  }
-}
-
-export const DashboardWrapper = (props: DashboardProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      orgsHeader: {
-        position: 'relative',
-      },
-      newOrgBtn: {
-        position: 'absolute',
-        top: 0,
-        right: 10,
-      },
-      nameColumn: {
-        'word-wrap': 'break-word',
-        [theme.breakpoints.down('sm')]: {
-          maxWidth: 'calc(100vw - 150px)',
-        },
-        [theme.breakpoints.up('md')]: {
-          maxWidth: 'calc(100vw - 350px)',
-        },
-        [theme.breakpoints.up('lg')]: {
-          maxWidth: 'calc(100vw - 350px)',
-        },
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      cell: {
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      activityButton: {
-        '&:not(:first-child)': {
-          marginLeft: '15px',
-        },
-      },
-      onboardingCard: {
-        border: '1px solid ' + colors.consoleStroke,
-        borderRadius: 3,
-        padding: 15,
-        '& h1': {
-          fontSize: '16px',
-          margin: '0',
-        },
-      },
-      onboarding: {
-        '& ul': {
-          paddingInlineStart: '20px',
-        },
-      },
-      filterOrgsInput: {
-        width: '100%',
-
-        '& .MuiOutlinedInput-input': {
-          width: '200px',
-        },
-      },
-      blockedStatus: {
-        color: colors.state.error,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      validStatus: {
-        color: colors.state.ok,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      flexContainer: {
-        display: 'flex',
-        alignItems: 'center',
-        justifyContent: 'center',
-        width: '100%',
-        height: '100%',
-        gap: 40,
-        marginTop: '20px',
-
-        '& > div': {
-          maxWidth: '300px',
-          width: '100%',
-          height: '100%',
-          display: 'flex',
-          justifyContent: 'center',
-          alignItems: 'center',
-          border: '1px solid #e0e0e0',
-          padding: '20px',
-          borderRadius: '4px',
-          cursor: 'pointer',
-          fontSize: '15px',
-          transition: 'border 0.3s ease-in-out',
-
-          '&:hover': {
-            border: '1px solid #FF6212',
-          },
-        },
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Dashboard {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance.tsx
deleted file mode 100644
index 4fbc442b..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-import { Button, makeStyles } from '@material-ui/core'
-import { Box } from '@mui/material'
-import { useEffect, useState } from 'react'
-
-import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-
-import { getCloudImages } from 'api/cloud/getCloudImages'
-import { getOrgKeys } from 'api/cloud/getOrgKeys'
-
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-import {
-  getGcpAccountContents,
-  getNetworkSubnet,
-  getPlaybookCommand,
-} from 'components/DbLabInstanceForm/utils'
-import {
-  cloneRepositoryCommand,
-  getAnsibleInstallationCommand,
-} from 'components/DbLabInstanceInstallForm/utils'
-
-import {
-  cloneClusterRepositoryCommand,
-  getClusterPlaybookCommand,
-} from 'components/PostgresClusterForm/utils'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-export const formStyles = makeStyles({
-  marginTop: {
-    marginTop: '20px !important',
-  },
-  marginBottom: {
-    marginBottom: '20px',
-    display: 'block',
-  },
-  maxContentWidth: {
-    maxWidth: '800px',
-  },
-  spinner: {
-    display: 'flex',
-    justifyContent: 'center',
-    alignItems: 'center',
-    height: '100%',
-  },
-  buttonSpinner: {
-    marginRight: '8px',
-    color: '#fff',
-  },
-  title: {
-    fontWeight: 600,
-    fontSize: '15px',
-    margin: '10px 0',
-  },
-  mainTitle: {
-    fontWeight: 600,
-    fontSize: '20px',
-    borderBottom: '1px solid #eee',
-    margin: '0 0 10px 0',
-    paddingBottom: '10px',
-  },
-  note: {
-    fontSize: '12px',
-    margin: '0 0 10px 0',
-    color: '#777',
-  },
-  code: {
-    backgroundColor: '#eee',
-    borderRadius: '3px',
-    padding: '0 3px',
-    marginLeft: '0.25em',
-  },
-  ul: {
-    paddingInlineStart: '30px',
-
-    '& li': {
-      marginBottom: '5px',
-    },
-  },
-  important: {
-    fontWeight: 600,
-    margin: 0,
-  },
-  containerMargin: {
-    margin: '20px 0',
-  },
-  smallMarginTop: {
-    marginBottom: '10px',
-  },
-})
-
-export const InstanceDocumentation = ({
-  firstStep,
-  firsStepDescription,
-  documentation,
-  secondStep,
-  snippetContent,
-  classes,
-}: {
-  firstStep: string
-  firsStepDescription?: React.ReactNode
-  documentation: string
-  secondStep: React.ReactNode
-  snippetContent: string
-  classes: ReturnType<typeof formStyles>
-}) => (
-  <>
-    <p className={classes.title}>1. {firstStep}</p>
-    {firsStepDescription && <p>{firsStepDescription}</p>}
-    <p className={classes.marginBottom}>
-      Documentation:{' '}
-      <a href={documentation} target="_blank" rel="noreferrer">
-        {documentation}
-      </a>
-    </p>
-    <p className={classes.title}>2. Export {secondStep}</p>
-    <SyntaxHighlight content={snippetContent} />
-  </>
-)
-
-export const AnsibleInstance = ({
-  cluster,
-  state,
-  orgId,
-  goBack,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  cluster?: boolean
-  state: useCloudProviderProps["initialState"]
-  orgId: number
-  goBack: () => void
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-  const [orgKey, setOrgKey] = useState('')
-  const [isLoading, setIsLoading] = useState(false)
-  const [cloudImages, setCloudImages] = useState([])
-  const [orgKeyError, setOrgKeyError] = useState(false)
-
-  useEffect(() => {
-    setIsLoading(true)
-    getOrgKeys(orgId).then((data) => {
-      if (data.error !== null || !Array.isArray(data.response)) {
-        setIsLoading(false)
-        setOrgKeyError(true)
-      } else {
-        setOrgKeyError(false)
-        setOrgKey(data.response[0].value)
-      }
-    })
-    getCloudImages({
-      os_name: 'Ubuntu',
-      os_version: '22.04%20LTS',
-      arch: state.instanceType.arch,
-      cloud_provider: state.provider,
-      region: state.provider === 'aws' ? state.location.native_code : 'all',
-    }).then((data) => {
-      setIsLoading(false)
-      setOrgKeyError(false)
-      setCloudImages(data.response)
-    })
-  }, [
-    orgId,
-    state.instanceType.arch,
-    state.location.native_code,
-    state.provider,
-  ])
-
-  const AnsibleInstallation = () => (
-    <>
-      <p className={classes.title}>
-        3. Install Ansible on your working machine (which could easily be a
-        laptop)
-      </p>
-      <p>example of installing the latest version:</p>
-      <SyntaxHighlight content={getAnsibleInstallationCommand()} />
-      <span className={classes.marginBottom}>
-        for more instructions on installing ansible, see{' '}
-        <a
-          target="_blank"
-          href="https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html"
-          rel="noreferrer"
-        >
-          here
-        </a>
-        .
-      </span>
-      <p className={classes.title}>
-        4. Clone the {cluster ? 'postgresql_cluster' : 'dle-se-ansible'}{' '}
-        repository
-      </p>
-      <SyntaxHighlight
-        content={
-          cluster ? cloneClusterRepositoryCommand() : cloneRepositoryCommand()
-        }
-      />
-
-      {!cluster && (
-        <>
-          <p className={classes.title}>5. Install requirements</p>
-          <SyntaxHighlight
-            content={'ansible-galaxy install -r requirements.yml'}
-          />
-        </>
-      )}
-    </>
-  )
-
-  return (
-    <InstanceFormCreation formStep={formStep} setFormStep={setFormStep}>
-      {isLoading ? (
-        <span className={classes.spinner}>
-          <Spinner />
-        </span>
-      ) : (
-        <>
-          {orgKeyError ? (
-            <ErrorStub
-              title="Error 404"
-              message="Unexpected error occurred. Please try again later"
-            />
-          ) : state.provider === 'digitalocean' ? (
-            <InstanceDocumentation
-              firstStep="Create Personal Access Token"
-              documentation="https://docs.digitalocean.com/reference/api/create-personal-access-token"
-              secondStep={
-                <>
-                  <code className={classes.code}>DO_API_TOKEN</code>
-                </>
-              }
-              snippetContent="export DO_API_TOKEN=XXXXXX"
-              classes={classes}
-            />
-          ) : state.provider === 'hetzner' ? (
-            <InstanceDocumentation
-              firstStep="Create API Token"
-              documentation="https://docs.hetzner.com/cloud/api/getting-started/generating-api-token"
-              secondStep={
-                <code className={classes.code}>HCLOUD_API_TOKEN</code>
-              }
-              snippetContent="export HCLOUD_API_TOKEN=XXXXXX"
-              classes={classes}
-            />
-          ) : state.provider === 'aws' ? (
-            <InstanceDocumentation
-              firstStep="Create access key"
-              documentation="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
-              secondStep={
-                <>
-                  <code className={classes.code}>AWS_ACCESS_KEY_ID </code> and
-                  <code className={classes.code}>AWS_SECRET_ACCESS_KEY</code>
-                </>
-              }
-              snippetContent={`export AWS_ACCESS_KEY_ID=XXXXXX\nexport AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXX`}
-              classes={classes}
-            />
-          ) : state.provider === 'gcp' ? (
-            <InstanceDocumentation
-              firstStep="Create a service account"
-              documentation="https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount"
-              secondStep={
-                <code className={classes.code}>
-                  GCP_SERVICE_ACCOUNT_CONTENTS
-                </code>
-              }
-              snippetContent={getGcpAccountContents()}
-              classes={classes}
-            />
-          ) : null}
-          <AnsibleInstallation />
-          <p className={classes.title}>
-            {cluster
-              ? '5. Run ansible playbook to deploy Postgres Cluster'
-              : '6. Run ansible playbook to create server and install DBLab SE'}
-          </p>
-          <SyntaxHighlight
-            content={
-              cluster
-                ? getClusterPlaybookCommand(
-                    state,
-                    cloudImages[0],
-                    orgKey,
-                  )
-                : getPlaybookCommand(state, cloudImages[0], orgKey, false)
-            }
-          />
-          {getNetworkSubnet(state.provider, classes)}
-          <p className={classes.title}>
-            {cluster
-              ? '6. After the code snippet runs successfully, follow the directions displayed in the resulting output to start using the database.'
-              : '7. After the code snippet runs successfully, follow the directions displayed in the resulting output to start using DBLab UI/API/CLI.'}
-          </p>
-          <Box
-            sx={{
-              display: 'flex',
-              gap: '10px',
-              margin: '20px 0',
-            }}
-          >
-            <Button variant="outlined" color="secondary" onClick={goBackToForm}>
-              Back to form
-            </Button>
-            <Button variant="contained" color="primary" onClick={goBack}>
-              See list of {cluster ? ' clusters' : ' instances'}
-            </Button>
-          </Box>
-        </>
-      )}
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/DockerInstance.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/DockerInstance.tsx
deleted file mode 100644
index d670be30..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/DockerInstance.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-import { Button } from '@material-ui/core'
-import { Box } from '@mui/material'
-import { useEffect, useState } from 'react'
-
-import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-
-import { getCloudImages } from 'api/cloud/getCloudImages'
-import { getOrgKeys } from 'api/cloud/getOrgKeys'
-
-import {
-  InstanceDocumentation,
-  formStyles,
-} from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-import {
-  getGcpAccountContents,
-  getNetworkSubnet,
-  getPlaybookCommand,
-} from 'components/DbLabInstanceForm/utils'
-
-import { getClusterPlaybookCommand } from 'components/PostgresClusterForm/utils'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-export const DockerInstance = ({
-  cluster,
-  state,
-  orgId,
-  goBack,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  state: useCloudProviderProps['initialState']
-  cluster?: boolean
-  orgId: number
-  goBack: () => void
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-  const [orgKey, setOrgKey] = useState('')
-  const [isLoading, setIsLoading] = useState(false)
-  const [cloudImages, setCloudImages] = useState([])
-  const [orgKeyError, setOrgKeyError] = useState(false)
-
-  useEffect(() => {
-    setIsLoading(true)
-    getOrgKeys(orgId).then((data) => {
-      if (data.error !== null || !Array.isArray(data.response)) {
-        setIsLoading(false)
-        setOrgKeyError(true)
-      } else {
-        setOrgKeyError(false)
-        setOrgKey(data.response[0].value)
-      }
-    })
-    getCloudImages({
-      os_name: 'Ubuntu',
-      os_version: '22.04%20LTS',
-      arch: state.instanceType.arch,
-      cloud_provider: state.provider,
-      region: state.provider === 'aws' ? state.location.native_code : 'all',
-    }).then((data) => {
-      setIsLoading(false)
-      setOrgKeyError(false)
-      setCloudImages(data.response)
-    })
-  }, [
-    orgId,
-    state.instanceType.arch,
-    state.location.native_code,
-    state.provider,
-  ])
-
-  return (
-    <InstanceFormCreation formStep={formStep} setFormStep={setFormStep}>
-      {isLoading ? (
-        <span className={classes.spinner}>
-          <Spinner />
-        </span>
-      ) : (
-        <>
-          {orgKeyError ? (
-            <ErrorStub title="Error 404" message="orgKey not found" />
-          ) : state.provider === 'digitalocean' ? (
-            <InstanceDocumentation
-              firstStep="Create Personal Access Token"
-              documentation="https://docs.digitalocean.com/reference/api/create-personal-access-token"
-              secondStep={<code className={classes.code}>DO_API_TOKEN</code>}
-              snippetContent="export DO_API_TOKEN=XXXXXX"
-              classes={classes}
-            />
-          ) : state.provider === 'hetzner' ? (
-            <InstanceDocumentation
-              firstStep="Create API Token"
-              documentation="https://docs.hetzner.com/cloud/api/getting-started/generating-api-token"
-              secondStep={
-                <code className={classes.code}>HCLOUD_API_TOKEN</code>
-              }
-              snippetContent="export HCLOUD_API_TOKEN=XXXXXX"
-              classes={classes}
-            />
-          ) : state.provider === 'aws' ? (
-            <InstanceDocumentation
-              firstStep="Create access key"
-              documentation="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
-              secondStep={
-                <>
-                  <code className={classes.code}>AWS_ACCESS_KEY_ID</code> and
-                  <code className={classes.code}>AWS_SECRET_ACCESS_KEY</code>
-                </>
-              }
-              snippetContent={`export AWS_ACCESS_KEY_ID=XXXXXX\nexport AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXX`}
-              classes={classes}
-            />
-          ) : state.provider === 'gcp' ? (
-            <>
-              <InstanceDocumentation
-                firstStep="Create a service account"
-                firsStepDescription={
-                  <>
-                    Create and save the JSON key for the service account and
-                    point to them using{' '}
-                    <code className={classes.code}>
-                      GCP_SERVICE_ACCOUNT_CONTENTS
-                    </code>{' '}
-                    variable.
-                  </>
-                }
-                documentation="https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount"
-                secondStep={
-                  <code className={classes.code}>
-                    GCP_SERVICE_ACCOUNT_CONTENTS
-                  </code>
-                }
-                snippetContent={getGcpAccountContents()}
-                classes={classes}
-              />
-            </>
-          ) : null}
-          <p className={classes.title}>
-            3. Run ansible playbook to{' '}
-            {cluster
-              ? 'deploy Postgres Cluster'
-              : 'create server and install DBLab SE'}
-          </p>
-          <SyntaxHighlight
-            content={
-              cluster
-                ? getClusterPlaybookCommand(state, cloudImages[0], orgKey, true)
-                : getPlaybookCommand(state, cloudImages[0], orgKey, true)
-            }
-          />
-          {getNetworkSubnet(state.provider, classes)}
-          <p className={classes.title}>
-            4. After the code snippet runs successfully, follow the directions
-            displayed in the resulting output to start using{' '}
-            {cluster ? 'the database.' : 'DBLab UI/API/CLI.'}
-          </p>
-          <Box
-            sx={{
-              display: 'flex',
-              gap: '10px',
-              margin: '20px 0',
-            }}
-          >
-            <Button variant="outlined" color="secondary" onClick={goBackToForm}>
-              Back to form
-            </Button>
-            <Button variant="contained" color="primary" onClick={goBack}>
-              See list of {cluster ? ' clusters' : ' instances'}
-            </Button>
-          </Box>
-        </>
-      )}
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation.tsx
deleted file mode 100644
index 46cf4160..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import classNames from 'classnames'
-import { makeStyles } from '@material-ui/core'
-
-const useStyles = makeStyles((theme) => ({
-  snippetContainer: {
-    width: '100%',
-    height: '100%',
-    maxWidth: '800px',
-    display: 'flex',
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    gap: 40,
-
-    [theme.breakpoints.down('sm')]: {
-      flexDirection: 'column',
-    },
-
-    '&  p:first-child': {
-      marginTop: '0',
-    },
-  },
-  fullWidth: {
-    width: '100%',
-    maxWidth: '100%',
-
-    '& .MuiTextField-root': {
-      maxWidth: '800px',
-    }
-  },
-  navigation: {
-    display: 'flex',
-    flexDirection: 'column',
-    marginLeft: '-20px',
-    flex: '0 0 220px',
-
-    [theme.breakpoints.down('sm')]: {
-      flex: 'auto',
-    },
-
-    '& span': {
-      display: 'flex',
-      alignItems: 'center',
-      gap: 10,
-      cursor: 'pointer',
-      padding: '8px 14px 8px 20px',
-      borderBottom: '1px solid #CCD7DA',
-      transition: 'background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
-
-      '&:hover': {
-        backgroundColor: '#F5F8FA',
-      },
-    },
-  },
-  form: {
-    flex: '1 1 0',
-    overflow: 'auto',
-
-    [theme.breakpoints.down('sm')]: {
-      flex: 'auto',
-    },
-  },
-  active: {
-    backgroundColor: '#F5F8FA',
-    borderRight: '4px solid #FF6212',
-  },
-}))
-
-export const InstanceFormCreation = ({
-  formStep,
-  setFormStep,
-  children,
-  install,
-  fullWidth,
-}: {
-  formStep: string
-  setFormStep: (step: string) => void
-  children: React.ReactNode
-  install?: boolean
-  fullWidth?: boolean
-}) => {
-  const classes = useStyles()
-
-  return (
-    <div
-      className={classNames(
-        fullWidth && classes.fullWidth,
-        classes.snippetContainer,
-      )}
-    >
-      <div className={classes.navigation}>
-        {!install && (
-          <span
-            className={formStep === 'simple' ? classes.active : ''}
-            onClick={() => setFormStep('simple')}
-          >
-            <img
-              src={`/images/simple.svg`}
-              width={30}
-              height="auto"
-              alt={'simple install setup'}
-            />
-            Simple setup
-          </span>
-        )}
-        <span
-          className={formStep === 'docker' ? classes.active : ''}
-          onClick={() => setFormStep('docker')}
-        >
-          <img
-            src={`/images/docker.svg`}
-            width={30}
-            height="auto"
-            alt={'docker setup'}
-          />
-          Docker
-        </span>
-        <span
-          className={formStep === 'ansible' ? classes.active : ''}
-          onClick={() => setFormStep('ansible')}
-        >
-          <img
-            src={`/images/ansible.svg`}
-            width={30}
-            height="auto"
-            alt={'ansible setup'}
-          />
-          Ansible
-        </span>
-      </div>
-      <div className={classes.form}>{children}</div>
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/SimpleInstance.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/SimpleInstance.tsx
deleted file mode 100644
index 6b0e52d0..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/SimpleInstance.tsx
+++ /dev/null
@@ -1,587 +0,0 @@
-import { Button, TextField } from '@material-ui/core'
-import { Box } from '@mui/material'
-import { useCallback, useEffect, useState } from 'react'
-
-import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-import { ResponseMessage } from '@postgres.ai/shared/pages/Configuration/ResponseMessage'
-import { formStyles } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-
-import { useWsScroll } from '@postgres.ai/shared/pages/Logs/hooks/useWsScroll'
-import { getCloudImages } from 'api/cloud/getCloudImages'
-import { getOrgKeys } from 'api/cloud/getOrgKeys'
-import { getTaskState } from 'api/configs/getTaskState'
-import { launchDeploy } from 'api/configs/launchDeploy'
-import { regenerateCode } from 'api/configs/regenerateCode'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-import { cloudProviderName } from '../utils'
-import { establishConnection } from './streamLogs'
-
-const SimpleInstanceDocumentation = ({
-  state,
-  isLoading,
-  secondStep,
-  documentation,
-  deployingState,
-  handleDeploy,
-}: {
-  isLoading: boolean
-  documentation: string
-  secondStep: JSX.Element
-  state: useCloudProviderProps['initialState']
-  handleDeploy: (e: React.FormEvent<HTMLFormElement>) => void
-  deployingState: {
-    status: string
-    error: string
-  }
-}) => {
-  const classes = formStyles()
-
-  useEffect(() => {
-    const textFields = document.querySelectorAll('input[type="text"]')
-    textFields?.forEach((textField) => {
-      textField.addEventListener('blur', () => {
-        textField.setAttribute('type', 'password')
-      })
-      textField.addEventListener('focus', () => {
-        textField.setAttribute('type', 'text')
-      })
-    })
-  }, [])
-
-  return (
-    <form onSubmit={handleDeploy} className={classes.maxContentWidth}>
-      <h1 className={classes.mainTitle}>{cloudProviderName(state.provider)}</h1>
-      <p>
-        {state.provider === 'aws' ? (
-          <>
-            {`Create a ${cloudProviderName(state.provider)} access key per`}{' '}
-            <a href={documentation} target="_blank" rel="noreferrer">
-              the official documentation.
-            </a>{' '}
-            These secrets will be used securely in a
-          </>
-        ) : state.provider === 'gcp' ? (
-          <>
-            {`Create a ${cloudProviderName(
-              state.provider,
-            )} service account per`}{' '}
-            <a href={documentation} target="_blank" rel="noreferrer">
-              the official documentation.
-            </a>{' '}
-            The service account content will be used securely in a
-          </>
-        ) : (
-          <>
-            {`Generate a ${cloudProviderName(state.provider)} API token per`}{' '}
-            <a href={documentation} target="_blank" rel="noreferrer">
-              the official documentation.
-            </a>{' '}
-            This token will be used securely in a
-          </>
-        )}{' '}
-        <a href="https://postgres.ai/" target="_blank" rel="noreferrer">
-          Postgres.ai
-        </a>{' '}
-        temporary container and will not be stored.
-      </p>
-      {secondStep}
-      <div className={classes.marginTop}>
-        <Button
-          type="submit"
-          color="primary"
-          variant="contained"
-          disabled={isLoading || deployingState.status === 'finished'}
-        >
-          {isLoading && <Spinner size="sm" className={classes.buttonSpinner} />}
-          {isLoading
-            ? 'Deploying...'
-            : deployingState.status === 'finished'
-            ? 'Deployed'
-            : 'Deploy'}
-        </Button>
-      </div>
-
-      {deployingState.error && (
-        <ResponseMessage type="error" message={deployingState.error} />
-      )}
-    </form>
-  )
-}
-
-export const SimpleInstance = ({
-  cluster,
-  state,
-  orgId,
-  userID,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  cluster?: boolean
-  state: useCloudProviderProps['initialState']
-  orgId: number
-  userID?: number
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-  const hasTaskID =
-    new URLSearchParams(window.location.search).get('taskID') === state.taskID
-  const logElement = document.getElementById('logs-container')
-  const [orgKey, setOrgKey] = useState('')
-  const [isLoading, setIsLoading] = useState(false)
-  const [taskStatus, setTaskStatus] = useState('')
-  const [isConnected, setIsConnected] = useState(false)
-  const [deployingState, setDeployingState] = useState({
-    status: 'stale',
-    error: '',
-  })
-  useWsScroll(deployingState.status === 'loading', true)
-  const [cloudImages, setCloudImages] = useState([
-    {
-      native_os_image: '',
-    },
-  ])
-  const [orgKeyError, setOrgKeyError] = useState(false)
-
-  const [extraEnvs, setExtraEnvs] = useState({
-    DO_API_TOKEN: '',
-    HCLOUD_API_TOKEN: '',
-    AWS_ACCESS_KEY_ID: '',
-    AWS_SECRET_ACCESS_KEY: '',
-    GCP_SERVICE_ACCOUNT_CONTENTS: '',
-  })
-
-  useEffect(() => {
-    if (
-      state.provider &&
-      state.location.native_code &&
-      state.instanceType?.arch
-    ) {
-      setIsLoading(true)
-      getOrgKeys(orgId).then((data) => {
-        if (data.error !== null || !Array.isArray(data.response)) {
-          setIsLoading(false)
-          setOrgKeyError(true)
-        } else {
-          setOrgKeyError(false)
-          setOrgKey(data.response[0].value)
-        }
-      })
-      getCloudImages({
-        os_name: 'Ubuntu',
-        os_version: '22.04%20LTS',
-        arch: state.instanceType.arch,
-        cloud_provider: state.provider,
-        region: state.provider === 'aws' ? state.location.native_code : 'all',
-      }).then((data) => {
-        setIsLoading(false)
-        setOrgKeyError(false)
-        setCloudImages(data.response)
-      })
-    }
-  }, [
-    orgId,
-    state.instanceType?.arch,
-    state.location.native_code,
-    state.provider,
-  ])
-
-  useEffect(() => {
-    const handleHeightChange = () => {
-      if (logElement) {
-        logElement.scrollIntoView({
-          behavior: 'smooth',
-          block: 'end',
-        })
-      }
-    }
-
-    const observer = new ResizeObserver(handleHeightChange)
-    if (logElement) {
-      observer.observe(logElement)
-    }
-
-    return () => {
-      if (logElement) {
-        observer.unobserve(logElement)
-      }
-    }
-  }, [logElement])
-
-  const establishWebsocketConnection = useCallback(
-    ({ taskId, otCode }: { taskId: string; otCode: string }) => {
-      establishConnection({
-        taskId: taskId,
-        otCode: otCode,
-        userID,
-        isConnected,
-        setIsConnected,
-      }).then(() => {
-        getTaskState({ taskID: taskId, userID }).then((status) => {
-          if (status.response) {
-            const responseStatus =
-              status.response?.state === 'error' ||
-              status.response?.state === 'finished'
-                ? 'finished'
-                : 'loading'
-            setTaskStatus(responseStatus)
-            setDeployingState({
-              status: 'finished',
-              error: '',
-            })
-          } else if (status.error) {
-            setDeployingState({
-              status: 'finished',
-              error: status.error?.Error,
-            })
-          }
-        })
-      })
-    },
-    [isConnected, userID],
-  )
-
-  useEffect(() => {
-    if (
-      hasTaskID &&
-      userID &&
-      Object.values(extraEnvs).every((x) => x === null || x === '') &&
-      taskStatus !== 'error' &&
-      taskStatus !== 'finished'
-    ) {
-      setDeployingState({
-        status: 'loading',
-        error: '',
-      })
-      getTaskState({ taskID: state.taskID, userID }).then((data) => {
-        if (data.response?.state) {
-          regenerateCode({ taskID: state.taskID, userID }).then((res) => {
-            if (res.response) {
-              establishWebsocketConnection({
-                taskId: state.taskID,
-                otCode: res.response.otCode,
-              })
-            } else if (res.error) {
-              setDeployingState({
-                status: 'finished',
-                error: res.error?.Error,
-              })
-            }
-          })
-        } else if (data.error) {
-          setDeployingState({
-            status: 'finished',
-            error: data.error?.Error,
-          })
-        }
-      })
-    }
-  }, [
-    hasTaskID,
-    state.taskID,
-    userID,
-    isConnected,
-    extraEnvs,
-    taskStatus,
-    establishWebsocketConnection,
-  ])
-
-  const handleDeploy = useCallback(
-    async (e: React.FormEvent<HTMLFormElement>) => {
-      e.preventDefault()
-      if (logElement) {
-        logElement.innerHTML = ''
-      }
-
-      setDeployingState({
-        status: 'loading',
-        error: '',
-      })
-      await launchDeploy({
-        launchType: cluster ? 'cluster' : 'instance',
-        state: state,
-        userID: userID,
-        extraEnvs: extraEnvs,
-        orgKey: orgKey,
-        cloudImage: cloudImages[0]?.native_os_image,
-      })
-        .then(async (data) => {
-          if (data.response) {
-            window.history.pushState(
-              {},
-              '',
-              `${window.location.pathname}?taskID=${data.response.taskID}&provider=${state.provider}`,
-            )
-            establishWebsocketConnection({
-              taskId: data.response.taskID,
-              otCode: data.response.otCode,
-            })
-            setDeployingState({
-              status: 'finished',
-              error: '',
-            })
-          } else if (data.error) {
-            const error =
-              data.error.Error ||
-              data.error.Errors[0] ||
-              data.error.FieldErrors.playbook
-
-            setDeployingState({
-              status: 'stale',
-              error: error,
-            })
-            if (logElement) {
-              logElement.innerHTML = error
-            }
-          }
-        })
-        .catch(() => {
-          setDeployingState({
-            ...deployingState,
-            status: 'stale',
-          })
-        })
-    },
-    [
-      state,
-      cluster,
-      extraEnvs,
-      orgKey,
-      cloudImages,
-      userID,
-      logElement,
-      deployingState,
-      establishWebsocketConnection,
-    ],
-  )
-
-  const isFormDisabled =
-    deployingState.status === 'loading' ||
-    deployingState.status === 'finished' ||
-    isConnected ||
-    (hasTaskID && Object.values(extraEnvs).every((x) => x === null || x === ''))
-
-  return (
-    <InstanceFormCreation
-      formStep={formStep}
-      setFormStep={setFormStep}
-      fullWidth
-    >
-      {isLoading ? (
-        <span className={classes.spinner}>
-          <Spinner />
-        </span>
-      ) : (
-        <>
-          {orgKeyError ? (
-            <ErrorStub title="Error 404" message="orgKey not found" />
-          ) : state.provider === 'digitalocean' ? (
-            <SimpleInstanceDocumentation
-              state={state}
-              handleDeploy={handleDeploy}
-              deployingState={deployingState}
-              isLoading={deployingState.status === 'loading' || isConnected}
-              documentation="https://docs.digitalocean.com/reference/api/create-personal-access-token"
-              secondStep={
-                <TextField
-                  type="text"
-                  required
-                  label="DO_API_TOKEN"
-                  variant="outlined"
-                  disabled={isFormDisabled}
-                  fullWidth
-                  value={
-                    isFormDisabled ? '****************' : extraEnvs.DO_API_TOKEN
-                  }
-                  className={classes.marginTop}
-                  InputLabelProps={{
-                    shrink: true,
-                  }}
-                  onChange={(e) =>
-                    setExtraEnvs({
-                      ...extraEnvs,
-                      DO_API_TOKEN: e.target.value,
-                    })
-                  }
-                />
-              }
-            />
-          ) : state.provider === 'hetzner' ? (
-            <SimpleInstanceDocumentation
-              state={state}
-              handleDeploy={handleDeploy}
-              deployingState={deployingState}
-              isLoading={deployingState.status === 'loading' || isConnected}
-              documentation="https://docs.hetzner.com/cloud/api/getting-started/generating-api-token"
-              secondStep={
-                <TextField
-                  type="text"
-                  required
-                  label="HCLOUD_API_TOKEN"
-                  disabled={isFormDisabled}
-                  variant="outlined"
-                  fullWidth
-                  value={
-                    isFormDisabled
-                      ? '****************'
-                      : extraEnvs.HCLOUD_API_TOKEN
-                  }
-                  className={classes.marginTop}
-                  InputLabelProps={{
-                    shrink: true,
-                  }}
-                  onChange={(e) =>
-                    setExtraEnvs({
-                      ...extraEnvs,
-                      HCLOUD_API_TOKEN: e.target.value,
-                    })
-                  }
-                />
-              }
-            />
-          ) : state.provider === 'aws' ? (
-            <SimpleInstanceDocumentation
-              state={state}
-              handleDeploy={handleDeploy}
-              deployingState={deployingState}
-              isLoading={deployingState.status === 'loading' || isConnected}
-              documentation="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
-              secondStep={
-                <>
-                  <TextField
-                    type="text"
-                    required
-                    id="aws_access_key_id"
-                    label="AWS_ACCESS_KEY_ID"
-                    variant="outlined"
-                    fullWidth
-                    disabled={isFormDisabled}
-                    value={
-                      isFormDisabled
-                        ? '****************'
-                        : extraEnvs.AWS_ACCESS_KEY_ID
-                    }
-                    className={classes.marginTop}
-                    InputLabelProps={{
-                      shrink: true,
-                    }}
-                    onChange={(e) =>
-                      setExtraEnvs({
-                        ...extraEnvs,
-                        AWS_ACCESS_KEY_ID: e.target.value,
-                      })
-                    }
-                  />
-                  <TextField
-                    type="text"
-                    required
-                    label="AWS_SECRET_ACCESS_KEY"
-                    variant="outlined"
-                    disabled={isFormDisabled}
-                    fullWidth
-                    value={
-                      isFormDisabled
-                        ? '****************'
-                        : extraEnvs.AWS_SECRET_ACCESS_KEY
-                    }
-                    className={classes.marginTop}
-                    InputLabelProps={{
-                      shrink: true,
-                    }}
-                    onChange={(e) =>
-                      setExtraEnvs({
-                        ...extraEnvs,
-                        AWS_SECRET_ACCESS_KEY: e.target.value,
-                      })
-                    }
-                  />
-                </>
-              }
-            />
-          ) : state.provider === 'gcp' ? (
-            <SimpleInstanceDocumentation
-              state={state}
-              handleDeploy={handleDeploy}
-              deployingState={deployingState}
-              isLoading={deployingState.status === 'loading' || isConnected}
-              documentation="https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount"
-              secondStep={
-                <TextField
-                  type="text"
-                  required
-                  label="GCP_SERVICE_ACCOUNT_CONTENTS"
-                  variant="outlined"
-                  disabled={isFormDisabled}
-                  fullWidth
-                  onBlur={(e) => {
-                    e.target.style.color = 'transparent'
-                    e.target.style.textShadow = '0 0 8px rgba(0,0,0,0.5)'
-                  }}
-                  onFocus={(e) => {
-                    e.target.style.color = 'black'
-                    e.target.style.textShadow = 'none'
-                  }}
-                  multiline
-                  value={
-                    isFormDisabled
-                      ? '****************'
-                      : extraEnvs.GCP_SERVICE_ACCOUNT_CONTENTS
-                  }
-                  className={classes.marginTop}
-                  InputLabelProps={{
-                    shrink: true,
-                  }}
-                  onChange={(e) =>
-                    setExtraEnvs({
-                      ...extraEnvs,
-                      GCP_SERVICE_ACCOUNT_CONTENTS: e.target.value,
-                    })
-                  }
-                />
-              }
-            />
-          ) : null}
-          {deployingState.status === 'loading' ||
-          deployingState.status === 'finished' ? (
-            <SyntaxHighlight
-              id="logs-container"
-              style={{
-                overflowY: 'auto',
-                maxHeight: !hasTaskID ? '350px' : '450px',
-                minHeight: '50px',
-                overflowX: 'hidden',
-                marginTop: '20px',
-                border: '1px solid #b4b4b4',
-                borderRadius: '4px',
-                backgroundColor: 'rgb(250, 250, 250)',
-              }}
-              content={
-                deployingState.status === 'loading'
-                  ? 'Deploying...'
-                  : deployingState.error
-                  ? deployingState.error
-                  : ''
-              }
-            />
-          ) : null}
-          <Box
-            sx={{
-              display: 'flex',
-              gap: '10px',
-              margin: '20px 0',
-            }}
-          >
-            <Button variant="outlined" color="secondary" onClick={goBackToForm}>
-              Back to form
-            </Button>
-          </Box>
-        </>
-      )}
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/streamLogs.ts b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/streamLogs.ts
deleted file mode 100644
index 5cffaaff..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabFormSteps/streamLogs.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { initStreamLogs } from '@postgres.ai/platform/src/api/configs/initStreamLogs'
-import { getTaskState } from 'api/configs/getTaskState'
-import { regenerateCode } from 'api/configs/regenerateCode'
-
-export const establishConnection = async ({
-  taskId,
-  otCode,
-  userID,
-  isConnected,
-  setIsConnected,
-}: {
-  taskId: string
-  otCode: string
-  userID?: number
-  isConnected: boolean
-  setIsConnected: (isConnected: boolean) => void
-}) => {
-  const logElement = document.getElementById('logs-container')
-
-  if (logElement === null) {
-    return
-  }
-
-  const appendLogElement = (logEntry: string) => {
-    const codeTag = logElement.querySelector('code')
-    if (codeTag) {
-      codeTag.appendChild(document.createTextNode(logEntry + '\n'))
-      logElement.appendChild(codeTag)
-    }
-  }
-
-  const socket = initStreamLogs(taskId, otCode)
-
-  socket.onclose = () => {
-    setIsConnected(false)
-  }
-
-  socket.onerror = () => {
-    if (!isConnected) {
-      return
-    }
-
-    setTimeout(() => {
-      getTaskState({ taskID: taskId, userID }).then((res) => {
-        if (
-          res.response?.state &&
-          res.response.state !== 'finished' &&
-          res.response.state !== 'error'
-        ) {
-          while (logElement.firstChild) {
-            logElement.removeChild(logElement.firstChild)
-          }
-          regenerateCode({ taskID: taskId, userID }).then((res) => {
-            if (res.response) {
-              establishConnection({
-                taskId,
-                otCode: res.response?.otCode,
-                userID,
-                isConnected,
-                setIsConnected,
-              })
-            }
-          })
-        }
-      })
-    }, 5000)
-  }
-
-  socket.onmessage = function (event) {
-    const logEntry = decodeURIComponent(atob(event.data))
-    appendLogElement(logEntry)
-    setIsConnected(true)
-  }
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceForm.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceForm.tsx
deleted file mode 100644
index af7e76ec..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceForm.tsx
+++ /dev/null
@@ -1,584 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  Button,
-  InputAdornment,
-  MenuItem,
-  Tab,
-  Tabs,
-  TextField,
-} from '@material-ui/core'
-import { Box } from '@mui/material'
-import cn from 'classnames'
-
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { Select } from '@postgres.ai/shared/components/Select'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex'
-
-import { CloudInstance } from 'api/cloud/getCloudInstances'
-import { CloudProvider } from 'api/cloud/getCloudProviders'
-import { CloudRegion } from 'api/cloud/getCloudRegions'
-import { CloudVolumes } from 'api/cloud/getCloudVolumes'
-import { TabPanel } from 'pages/JoeSessionCommand/TabPanel'
-
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { AnsibleInstance } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { DbLabInstanceFormSidebar } from 'components/DbLabInstanceForm/DbLabInstanceFormSidebar'
-import { StorageSlider } from 'components/DbLabInstanceForm/DbLabInstanceFormSlider'
-import { DbLabInstanceFormProps } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import { initialState, reducer } from 'components/DbLabInstanceForm/reducer'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { DockerInstance } from './DbLabFormSteps/DockerInstance'
-import { SimpleInstance } from './DbLabFormSteps/SimpleInstance'
-
-import {
-  availableTags,
-  filteredRegions,
-  uniqueRegionsByProvider,
-} from 'components/DbLabInstanceForm/utils'
-
-import urls from 'utils/urls'
-import { validateDLEName } from 'utils/utils'
-
-import { useCloudProvider } from 'hooks/useCloudProvider'
-import ConsolePageTitle from './../ConsolePageTitle'
-
-interface DbLabInstanceFormWithStylesProps extends DbLabInstanceFormProps {
-  classes: ClassesType
-  auth?: {
-    userId: number
-  }
-}
-
-const DbLabInstanceForm = (props: DbLabInstanceFormWithStylesProps) => {
-  const { classes, orgPermissions } = props
-  const {
-    state,
-    dispatch,
-    handleChangeVolume,
-    handleGenerateToken,
-    handleReturnToForm,
-    handleSetFormStep,
-  } = useCloudProvider({
-    initialState,
-    reducer,
-  })
-  const permitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-
-  const pageTitle = <ConsolePageTitle title="Create DBLab" />
-  const breadcrumbs = (
-    <ConsoleBreadcrumbsWrapper
-      {...props}
-      breadcrumbs={[
-        { name: 'Database Lab Instances', url: 'instances' },
-        { name: 'Create DBLab' },
-      ]}
-    />
-  )
-
-  const handleReturnToList = () => {
-    props.history.push(urls.linkDbLabInstances(props))
-  }
-
-  const requirePublicKeys =
-    !state.publicKeys && (state.provider === 'aws' || state.provider === 'gcp')
-
-  const calculateVolumePrice = (databaseSize: number, snapshots: number) => {
-    let storage = databaseSize * snapshots
-    if (storage > 2000) storage = 2000
-
-    return (storage * state.volumePricePerHour) / 1000
-  }
-
-  if (state.isLoading) return <StubSpinner />
-
-  return (
-    <div className={classes.root}>
-      {breadcrumbs}
-
-      {pageTitle}
-
-      {!permitted && (
-        <WarningWrapper>
-          You do not have permission to add Database Lab instances.
-        </WarningWrapper>
-      )}
-
-      <div
-        className={cn(
-          classes.container,
-          state.isReloading && classes.backgroundOverlay,
-        )}
-      >
-        {state.formStep === initialState.formStep && permitted ? (
-          <>
-            {state.isReloading && (
-              <Spinner className={classes.absoluteSpinner} />
-            )}
-            <div className={classes.form}>
-              <p className={classes.sectionTitle}>
-                1. Select your cloud provider
-              </p>
-              <div className={classes.providerFlex}>
-                {state.serviceProviders.map(
-                  (provider: CloudProvider, index: number) => (
-                    <div
-                      className={cn(
-                        classes.provider,
-                        state.provider === provider.api_name &&
-                          classes.activeBorder,
-                      )}
-                      key={index}
-                      onClick={() =>
-                        dispatch({
-                          type: 'change_provider',
-                          provider: provider.api_name,
-                          isReloading: true,
-                        })
-                      }
-                    >
-                      <img
-                        src={`/images/service-providers/${provider.api_name}.png`}
-                        width={85}
-                        height="auto"
-                        alt={provider.label}
-                      />
-                    </div>
-                  ),
-                )}
-              </div>
-              <p className={classes.sectionTitle}>
-                2. Select your cloud region
-              </p>
-              <div className={classes.sectionContainer}>
-                <Tabs
-                  value={state.region}
-                  onChange={(_: React.ChangeEvent<{}> | null, value: string) =>
-                    dispatch({
-                      type: 'change_region',
-                      region: value,
-                      location: state.cloudRegions.find(
-                        (region: CloudRegion) =>
-                          region.world_part === value &&
-                          region.cloud_provider === state.provider,
-                      ),
-                    })
-                  }
-                >
-                  {uniqueRegionsByProvider(state.cloudRegions).map(
-                    (region: string, index: number) => (
-                      <Tab
-                        key={index}
-                        label={region}
-                        value={region}
-                        className={classes.tab}
-                      />
-                    ),
-                  )}
-                </Tabs>
-              </div>
-              <TabPanel value={state.region} index={state.region}>
-                {filteredRegions(state.cloudRegions, state.region).map(
-                  (region: CloudRegion, index: number) => (
-                    <div
-                      key={index}
-                      className={cn(
-                        classes.serviceLocation,
-                        state.location?.api_name === region?.api_name &&
-                          classes.activeBorder,
-                      )}
-                      onClick={() =>
-                        dispatch({
-                          type: 'change_location',
-                          location: region,
-                        })
-                      }
-                    >
-                      <p className={classes.serviceTitle}>{region.api_name}</p>
-                      <p className={classes.serviceTitle}>🏴 {region.label}</p>
-                    </div>
-                  ),
-                )}
-              </TabPanel>
-              {state.instanceType ? (
-                <>
-                  <p className={classes.sectionTitle}>
-                    3. Choose instance type
-                  </p>
-                  <p className={classes.instanceParagraph}>
-                    A larger instance can accommodate more dev/test activities.
-                    For example, a team of 5 engineers requiring 5-10 clones
-                    during peak times should consider a minimum instance size of
-                    8 vCPUs and 32 GiB.
-                  </p>
-                  <TabPanel
-                    value={state.cloudInstances}
-                    index={state.cloudInstances}
-                  >
-                    {state.cloudInstances.map(
-                      (instance: CloudInstance, index: number) => (
-                        <div
-                          key={index}
-                          className={cn(
-                            classes.instanceSize,
-                            state.instanceType === instance &&
-                              classes.activeBorder,
-                          )}
-                          onClick={() =>
-                            dispatch({
-                              type: 'change_instance_type',
-                              instanceType: instance,
-                            })
-                          }
-                        >
-                          <p>
-                            {instance.api_name} (
-                            {state.instanceType.cloud_provider}:{' '}
-                            {instance.native_name})
-                          </p>
-                          <div>
-                            <span>🔳 {instance.native_vcpus} CPU</span>
-                            <span>🧠 {instance.native_ram_gib} GiB RAM</span>
-                          </div>
-                        </div>
-                      ),
-                    )}
-                  </TabPanel>
-                  <p className={classes.sectionTitle}>4. Database volume</p>
-                  <Box className={classes.sliderContainer}>
-                    <Box className={classes.sliderInputContainer}>
-                      <Box className={classes.sliderVolume}>
-                        <TextField
-                          value={state.volumeType}
-                          onChange={handleChangeVolume}
-                          select
-                          label="Volume type"
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          variant="outlined"
-                          className={classes.filterSelect}
-                        >
-                          {(state.volumes as CloudVolumes[]).map((p, id) => {
-                            const volumeName = `${p.api_name} (${p.cloud_provider}: ${p.native_name})`
-                            return (
-                              <MenuItem value={volumeName} key={id}>
-                                {volumeName}
-                              </MenuItem>
-                            )
-                          })}
-                        </TextField>
-                      </Box>
-                      <Box className={classes.databaseSize}>
-                        <TextField
-                          variant="outlined"
-                          fullWidth
-                          type="number"
-                          label="Database size"
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          InputProps={{
-                            inputProps: {
-                              min: 0,
-                            },
-                            endAdornment: (
-                              <InputAdornment position="end">
-                                GiB
-                              </InputAdornment>
-                            ),
-                          }}
-                          value={Number(state.databaseSize)?.toFixed(2)}
-                          className={classes.filterSelect}
-                          onChange={(
-                            event: React.ChangeEvent<
-                              HTMLTextAreaElement | HTMLInputElement
-                            >,
-                          ) => {
-                            dispatch({
-                              type: 'change_volume_price',
-                              storage: Math.min(
-                                Number(event.target.value) * state.snapshots,
-                                2000,
-                              ),
-                              databaseSize: event.target.value,
-                              volumePrice: calculateVolumePrice(
-                                Number(event.target.value),
-                                state.snapshots,
-                              ),
-                            })
-                          }}
-                        />
-                        ×
-                        <TextField
-                          variant="outlined"
-                          fullWidth
-                          type="number"
-                          InputProps={{
-                            inputProps: {
-                              min: 0,
-                            },
-                            endAdornment: (
-                              <InputAdornment position="end">
-                                {Number(state.snapshots) === 1
-                                  ? 'snapshot'
-                                  : 'snapshots'}
-                              </InputAdornment>
-                            ),
-                          }}
-                          value={state.snapshots}
-                          className={classes.filterSelect}
-                          onChange={(
-                            event: React.ChangeEvent<
-                              HTMLTextAreaElement | HTMLInputElement
-                            >,
-                          ) => {
-                            dispatch({
-                              type: 'change_snapshots',
-                              snapshots: Number(event.target.value),
-                              storage: Math.min(
-                                Number(event.target.value) * state.databaseSize,
-                                2000,
-                              ),
-                              volumePrice: calculateVolumePrice(
-                                state.databaseSize,
-                                Number(event.target.value),
-                              ),
-                            })
-                          }}
-                        />
-                      </Box>
-                    </Box>
-                    <StorageSlider
-                      sliderOptions={{
-                        min: 0,
-                        max: 2000,
-                        step: 10,
-                      }}
-                      customMarks={[
-                        {
-                          value: 30,
-                          label: '30 GiB',
-                          scaledValue: 30,
-                        },
-                        {
-                          value: 500,
-                          label: '500 GiB',
-                          scaledValue: 500,
-                        },
-                        {
-                          value: 1000,
-                          label: '1000 GiB',
-                          scaledValue: 1000,
-                        },
-                        {
-                          value: 1500,
-                          label: '1500 GiB',
-                          scaledValue: 1500,
-                        },
-                        {
-                          value: 2000,
-                          label: '2000 GiB',
-                          scaledValue: 2000,
-                        },
-                      ]}
-                      value={state.storage}
-                      onChange={(_: React.ChangeEvent<{}>, value: unknown) => {
-                        dispatch({
-                          type: 'change_volume_price',
-                          storage: value,
-                          databaseSize: Number(value) / state.snapshots,
-                          volumePrice:
-                            (Number(value) * state.volumePricePerHour) / 1000,
-                        })
-                      }}
-                    />
-                  </Box>
-                  <p className={classes.sectionTitle}>5. Provide DBLab name</p>
-                  <TextField
-                    required
-                    label="DBLab Name"
-                    variant="outlined"
-                    fullWidth
-                    value={state.name}
-                    className={classes.marginTop}
-                    InputLabelProps={{
-                      shrink: true,
-                    }}
-                    helperText={
-                      validateDLEName(state.name)
-                        ? 'Name must be lowercase and contain only letters and numbers.'
-                        : ''
-                    }
-                    error={validateDLEName(state.name)}
-                    onChange={(
-                      event: React.ChangeEvent<
-                        HTMLTextAreaElement | HTMLInputElement
-                      >,
-                    ) =>
-                      dispatch({
-                        type: 'change_name',
-                        name: event.target.value,
-                      })
-                    }
-                  />
-                  <p className={classes.sectionTitle}>
-                    6. Define DBLab verification token (keep it secret!)
-                  </p>
-                  <div className={classes.generateContainer}>
-                    <TextField
-                      required
-                      label="DBLab Verification Token"
-                      variant="outlined"
-                      fullWidth
-                      value={state.verificationToken}
-                      className={classes.marginTop}
-                      InputLabelProps={{
-                        shrink: true,
-                      }}
-                      onChange={(
-                        event: React.ChangeEvent<
-                          HTMLTextAreaElement | HTMLInputElement
-                        >,
-                      ) =>
-                        dispatch({
-                          type: 'change_verification_token',
-                          verificationToken: event.target.value,
-                        })
-                      }
-                    />
-                    <Button
-                      variant="contained"
-                      color="primary"
-                      disabled={!permitted}
-                      onClick={handleGenerateToken}
-                    >
-                      Generate random
-                    </Button>
-                  </div>
-                  <p className={classes.sectionTitle}>
-                    7. Choose DBLab version
-                  </p>
-                  <Select
-                    label="Select tag"
-                    items={
-                      availableTags.map((tag) => {
-                        const defaultTag = availableTags[0]
-
-                        return {
-                          value: tag,
-                          children:
-                            defaultTag === tag ? `${tag} (default)` : tag,
-                        }
-                      }) ?? []
-                    }
-                    value={state.tag}
-                    onChange={(
-                      e: React.ChangeEvent<
-                        HTMLTextAreaElement | HTMLInputElement
-                      >,
-                    ) =>
-                      dispatch({
-                        type: 'set_tag',
-                        tag: e.target.value,
-                      })
-                    }
-                  />
-                  <p className={classes.sectionTitle}>
-                    8. Provide SSH public keys (one per line)
-                  </p>
-                  <p className={classes.instanceParagraph}>
-                    These SSH public keys will be added to the DBLab server's
-                    &nbsp;
-                    <code className={classes.code}>~/.ssh/authorized_keys</code>
-                    &nbsp; file. Providing at least one public key is
-                    recommended to ensure access to the server after deployment.
-                  </p>
-                  <TextField
-                    label="SSH public keys"
-                    variant="outlined"
-                    fullWidth
-                    multiline
-                    required={requirePublicKeys}
-                    helperText={
-                      state.publicKeys && state.publicKeys.length < 30
-                        ? 'Public key is too short'
-                        : ''
-                    }
-                    error={state.publicKeys && state.publicKeys.length < 30}
-                    value={state.publicKeys}
-                    className={classes.marginTop}
-                    InputLabelProps={{
-                      shrink: true,
-                    }}
-                    onChange={(
-                      event: React.ChangeEvent<
-                        HTMLTextAreaElement | HTMLInputElement
-                      >,
-                    ) =>
-                      dispatch({
-                        type: 'change_public_keys',
-                        publicKeys: event.target.value,
-                      })
-                    }
-                  />
-                </>
-              ) : (
-                <div />
-              )}
-            </div>
-            <DbLabInstanceFormSidebar
-              state={state}
-              disabled={
-                validateDLEName(state.name) ||
-                requirePublicKeys ||
-                (state.publicKeys && state.publicKeys.length < 30)
-              }
-              handleCreate={() =>
-                !validateDLEName(state.name) && handleSetFormStep('simple')
-              }
-            />
-          </>
-        ) : state.formStep === 'ansible' && permitted ? (
-          <AnsibleInstance
-            state={state}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : state.formStep === 'docker' && permitted ? (
-          <DockerInstance
-            state={state}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : state.formStep === 'simple' && permitted ? (
-          <SimpleInstance
-            state={state}
-            userID={props.auth?.userId}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBackToForm={() => {
-              window.history.pushState({}, '', `${window.location.pathname}`)
-              handleReturnToForm()
-            }}
-          />
-        ) : null}
-      </div>
-    </div>
-  )
-}
-
-export default DbLabInstanceForm
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormSidebar.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormSidebar.tsx
deleted file mode 100644
index cf066e12..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormSidebar.tsx
+++ /dev/null
@@ -1,259 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Button, makeStyles } from '@material-ui/core'
-
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-import { cloudProviderName, pricingPageForProvider } from './utils'
-
-const MONTHLY_HOURS = 730
-
-const useStyles = makeStyles({
-  aside: {
-    width: '100%',
-    height: 'fit-content',
-    minHeight: '300px',
-    padding: '24px',
-    borderRadius: '4px',
-    boxShadow: '0 8px 16px #3a3a441f, 0 16px 32px #5a5b6a1f',
-    display: 'flex',
-    flexDirection: 'column',
-    justifyContent: 'flex-start',
-    flex: '1 1 0',
-    position: 'sticky',
-    top: 10,
-
-    '& > h2': {
-      fontSize: '14px',
-      fontWeight: 500,
-      margin: '0 0 10px 0',
-      height: 'fit-content',
-    },
-
-    '& > span': {
-      fontSize: '13px',
-    },
-
-    '& > button': {
-      padding: '10px 20px',
-      marginTop: '20px',
-    },
-
-    '@media (max-width: 1200px)': {
-      position: 'relative',
-      boxShadow: 'none',
-      borderRadius: '0',
-      padding: '0',
-      flex: 'auto',
-      marginBottom: '30px',
-
-      '& > button': {
-        width: 'max-content',
-      },
-    },
-  },
-  asideSection: {
-    padding: '12px 0',
-    borderBottom: '1px solid #e0e0e0',
-
-    '& > span': {
-      color: '#808080',
-    },
-
-    '& > p': {
-      margin: '5px 0 0 0',
-      fontSize: '13px',
-    },
-  },
-  flexWrap: {
-    display: 'flex',
-    flexWrap: 'wrap',
-    gap: '5px',
-  },
-  capitalize: {
-    textTransform: 'capitalize',
-  },
-  remark: {
-    fontSize: '11px',
-    marginTop: '5px',
-    display: 'block',
-    lineHeight: '1.2',
-  },
-})
-
-export const DbLabInstanceFormSidebar = ({
-  cluster,
-  state,
-  handleCreate,
-  disabled,
-}: {
-  cluster?: boolean
-  state: useCloudProviderProps['initialState']
-  handleCreate: () => void
-  disabled: boolean
-}) => {
-  const classes = useStyles()
-
-  return (
-    <div className={classes.aside}>
-      <h2>Preview</h2>
-      <span>
-        Review the specifications of the virtual machine, storage, and software
-        that will be provisioned.
-      </span>
-      {state.name && (
-        <div className={classes.asideSection}>
-          <span>Name</span>
-          <p>{state.name}</p>
-        </div>
-      )}
-      {state.tag && (
-        <div className={classes.asideSection}>
-          <span>Tag</span>
-          <p>{state.tag}</p>
-        </div>
-      )}
-      <div className={classes.asideSection}>
-        <span>Cloud provider</span>
-        <p className={classes.capitalize}>
-          {cloudProviderName(state.provider)}
-        </p>
-      </div>
-      <div className={classes.asideSection}>
-        <span>Cloud region</span>
-        <p>
-          {state.location?.native_code}: {state.location?.label}
-        </p>
-      </div>
-      <div className={classes.asideSection}>
-        <span>Instance type</span>
-        <p className={classes.flexWrap}>
-          {state.instanceType ? (
-            <>
-              {cluster && (
-                <span
-                  style={{
-                    width: '100%',
-                  }}
-                >
-                  Instances count: {state.numberOfInstances}
-                </span>
-              )}
-              <span>{state.instanceType.native_name}: </span>
-              <span>🔳 {state.instanceType.native_vcpus} CPU</span>
-              <span>🧠 {state.instanceType.native_ram_gib} GiB RAM</span>
-              <span>
-                Price: {state.instanceType.native_reference_price_currency}
-                {cluster
-                  ? (
-                      state.numberOfInstances *
-                      state.instanceType.native_reference_price_hourly
-                    )?.toFixed(4)
-                  : state.instanceType.native_reference_price_hourly?.toFixed(
-                      4,
-                    )}{' '}
-                hourly (~{state.instanceType.native_reference_price_currency}
-                {cluster
-                  ? (
-                      state.numberOfInstances *
-                      state.instanceType.native_reference_price_hourly *
-                      MONTHLY_HOURS
-                    ).toFixed(2)
-                  : (
-                      state.instanceType.native_reference_price_hourly *
-                      MONTHLY_HOURS
-                    ).toFixed(2)}{' '}
-                per month)<sup>*</sup>
-              </span>
-            </>
-          ) : (
-            <span>No instance type available for this region.</span>
-          )}
-        </p>
-      </div>
-      <div className={classes.asideSection}>
-        <span>Database volume</span>
-        <p className={classes.flexWrap}>
-          <span>Type: {state.volumeType}</span>
-          <span>Size: {Number(state.storage)?.toFixed(2)} GiB </span>
-        </p>
-        <p>
-          Price: {state.volumeCurrency}
-          {cluster
-            ? (state.volumePrice * state.numberOfInstances).toFixed(4)
-            : state.volumePrice.toFixed(4)}{' '}
-          hourly (~{state.volumeCurrency}
-          {cluster
-            ? (
-                state.volumePrice *
-                state.numberOfInstances *
-                MONTHLY_HOURS
-              ).toFixed(2)
-            : (state.volumePrice * MONTHLY_HOURS).toFixed(2)}{' '}
-          per month)
-          <sup>*</sup>
-        </p>
-        <span className={classes.remark}>
-          <sup>*</sup>Payment is made directly to the cloud provider. The
-          estimated cost is calculated for the "
-          {state.instanceType?.native_reference_price_region}" region and is not
-          guaranteed. Please refer to{' '}
-          <a
-            href={pricingPageForProvider(state.provider)}
-            target="_blank"
-            rel="noreferrer"
-          >
-            the official pricing page
-          </a>
-          &nbsp; to confirm the actual costs. .
-        </span>
-      </div>
-      <div className={classes.asideSection}>
-        <span>
-          Software: {cluster ? 'Postgres cluster' : 'DBLab SE'} (pay as you go)
-        </span>
-        <p className={classes.flexWrap}>
-          {state.instanceType && (
-            <>
-              <span>Size: {state.instanceType.api_name}</span>
-              <span>
-                Price: $
-                {cluster
-                  ? (
-                      state.instanceType.dle_se_price_hourly *
-                      state.numberOfInstances
-                    ).toFixed(4)
-                  : state.instanceType.dle_se_price_hourly?.toFixed(4)}{' '}
-                hourly (~$
-                {cluster
-                  ? (
-                      state.numberOfInstances *
-                      state.instanceType.dle_se_price_hourly *
-                      MONTHLY_HOURS
-                    ).toFixed(2)
-                  : (
-                      state.instanceType.dle_se_price_hourly * MONTHLY_HOURS
-                    ).toFixed(2)}{' '}
-                per month)
-              </span>
-            </>
-          )}
-        </p>
-      </div>
-      <Button
-        variant="contained"
-        color="primary"
-        onClick={handleCreate}
-        disabled={
-          !state.name || (!cluster && !state.verificationToken) || disabled
-        }
-      >
-        {cluster ? 'Create Postgres Cluster' : 'Create DBLab'}
-      </Button>
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormSlider.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormSlider.tsx
deleted file mode 100644
index 0c2f7e4c..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormSlider.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React from 'react'
-import Slider from '@material-ui/core/Slider'
-import { makeStyles } from '@material-ui/core'
-
-const useStyles = makeStyles({
-  root: {
-    width: '100%',
-    marginBottom: 0,
-  },
-  valueLabel: {
-    '& > span': {
-      backgroundColor: 'transparent',
-    },
-    '& > span > span': {
-      color: '#000',
-      fontWeight: 'bold',
-    },
-  },
-})
-
-export const StorageSlider = ({
-  value,
-  onChange,
-  customMarks,
-  sliderOptions,
-}: {
-  value: number
-  customMarks: { value: number; scaledValue: number; label: string | number }[]
-  sliderOptions: { [key: string]: number }
-  onChange: (event: React.ChangeEvent<{}>, value: unknown) => void
-}) => {
-  const classes = useStyles()
-
-  const scale = (value: number) => {
-      if (customMarks) {
-        const previousMarkIndex = Math.floor(value / 25)
-        const previousMark = customMarks[previousMarkIndex]
-        const remainder = value % 25
-
-        if (remainder === 0) {
-          return previousMark?.scaledValue
-        }
-
-        const nextMark = customMarks[previousMarkIndex + 1]
-        const increment = (nextMark?.scaledValue - previousMark?.scaledValue) / 25
-        return remainder * increment + previousMark?.scaledValue
-      } else {
-        return value
-      }
-  }
-
-  return (
-    <Slider
-      {...sliderOptions}
-      value={value}
-      scale={scale}
-      marks={customMarks}
-      onChange={onChange}
-      valueLabelDisplay="auto"
-      valueLabelFormat={() => value}
-      aria-labelledby="non-linear-slider"
-      classes={{ root: classes.root, valueLabel: classes.valueLabel }}
-    />
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormWrapper.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormWrapper.tsx
deleted file mode 100644
index c405ece2..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/DbLabInstanceFormWrapper.tsx
+++ /dev/null
@@ -1,311 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { RouteComponentProps } from 'react-router'
-import { makeStyles } from '@material-ui/core'
-
-import DbLabInstanceForm from 'components/DbLabInstanceForm/DbLabInstanceForm'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { OrgPermissions } from 'components/types'
-
-export interface DbLabInstanceFormProps {
-  userID?: number
-  edit?: boolean
-  orgId: number
-  project: string | undefined
-  history: RouteComponentProps['history']
-  orgPermissions: OrgPermissions
-}
-
-export const useInstanceFormStyles = makeStyles(
-  {
-    textField: {
-      ...styles.inputField,
-      maxWidth: 400,
-    },
-    errorMessage: {
-      color: 'red',
-    },
-    fieldBlock: {
-      width: '100%',
-    },
-    urlOkIcon: {
-      marginBottom: -5,
-      marginLeft: 10,
-      color: 'green',
-    },
-    urlOk: {
-      color: 'green',
-    },
-    urlFailIcon: {
-      marginBottom: -5,
-      marginLeft: 10,
-      color: 'red',
-    },
-    urlFail: {
-      color: 'red',
-    },
-    warning: {
-      color: '#801200',
-      fontSize: '0.9em',
-    },
-    warningIcon: {
-      color: '#801200',
-      fontSize: '1.2em',
-      position: 'relative',
-      marginBottom: -3,
-    },
-    container: {
-      display: 'flex',
-      flexDirection: 'row',
-      justifyContent: 'space-between',
-      marginTop: 30,
-      gap: 60,
-      width: '100%',
-      height: '95%',
-      position: 'relative',
-      '& input': {
-        padding: '13.5px 14px',
-      },
-
-      '@media (max-width: 1200px)': {
-        flexDirection: 'column',
-        height: 'auto',
-        gap: 30,
-      },
-    },
-    form: {
-      width: '100%',
-      height: '100%',
-      display: 'flex',
-      flexDirection: 'column',
-      flex: '3 1 0',
-
-      '& > [role="tabpanel"] .MuiBox-root': {
-        padding: 0,
-
-        '& > div:first-child': {
-          marginTop: '10px',
-        },
-      },
-    },
-    activeBorder: {
-      border: '1px solid #FF6212 !important',
-    },
-    providerFlex: {
-      display: 'flex',
-      gap: '10px',
-      marginBottom: '20px',
-      overflow: 'auto',
-      flexShrink: 0,
-
-      '& > div': {
-        width: '100%',
-        height: '100%',
-        display: 'flex',
-        justifyContent: 'center',
-        alignItems: 'center',
-        border: '1px solid #e0e0e0',
-        padding: '15px',
-        borderRadius: '4px',
-        cursor: 'pointer',
-        transition: 'border 0.3s ease-in-out',
-
-        '@media (max-width: 600px)': {
-          width: '122.5px',
-          height: '96px',
-        },
-
-        '&:hover': {
-          border: '1px solid #FF6212',
-        },
-
-        '& > img': {
-          margin: 'auto',
-        },
-      },
-    },
-    sectionTitle: {
-      fontSize: '14px',
-      fontWeight: 500,
-      marginTop: '20px',
-
-      '&:first-child': {
-        marginTop: 0,
-      },
-    },
-    sectionContainer: {
-      display: 'flex',
-      flexDirection: 'row',
-      alignItems: 'center',
-
-      '& > .MuiTabs-root, .MuiTabs-fixed': {
-        overflow: 'auto !important',
-      },
-
-      '& span': {
-        top: '40px',
-        height: '2px',
-      },
-    },
-    tab: {
-      minWidth: 'auto',
-      padding: '0 12px',
-    },
-    tabPanel: {
-      padding: '10px 0 0 0',
-    },
-    instanceSize: {
-      marginBottom: '10px',
-      border: '1px solid #e0e0e0',
-      borderRadius: '4px',
-      cursor: 'pointer',
-      padding: '15px',
-      transition: 'border 0.3s ease-in-out',
-      display: 'flex',
-      gap: 10,
-      flexDirection: 'column',
-
-      '&:hover': {
-        border: '1px solid #FF6212',
-      },
-
-      '& > p': {
-        margin: 0,
-      },
-
-      '& > div': {
-        display: 'flex',
-        gap: 10,
-        alignItems: 'center',
-        flexWrap: 'wrap',
-      },
-    },
-    serviceLocation: {
-      display: 'flex',
-      flexDirection: 'column',
-      gap: '5px',
-      marginBottom: '10px',
-      border: '1px solid #e0e0e0',
-      borderRadius: '4px',
-      cursor: 'pointer',
-      padding: '15px',
-      transition: 'border 0.3s ease-in-out',
-
-      '&:hover': {
-        border: '1px solid #FF6212',
-      },
-
-      '& > p': {
-        margin: 0,
-      },
-    },
-    instanceParagraph: {
-      margin: '0 0 10px 0',
-    },
-    filterSelect: {
-      flex: '2 1 0',
-
-      '& .MuiSelect-select': {
-        padding: '10px',
-      },
-
-      '& .MuiInputBase-input': {
-        padding: '10px',
-      },
-
-      '& .MuiSelect-icon': {
-        top: 'calc(50% - 9px)',
-      },
-    },
-    generateContainer: {
-      display: 'flex',
-      flexDirection: 'row',
-      alignItems: 'center',
-      gap: '10px',
-
-      '& > button': {
-        width: 'max-content',
-        marginTop: '10px',
-        flexShrink: 0,
-        height: 'calc(100% - 10px)',
-      },
-
-      '@media (max-width: 640px)': {
-        flexDirection: 'column',
-        alignItems: 'flex-start',
-        gap: 0,
-
-        '& > button': {
-          height: 'auto',
-        },
-      },
-    },
-    backgroundOverlay: {
-      '&::before': {
-        content: '""',
-        position: 'absolute',
-        top: 0,
-        left: 0,
-        width: '100%',
-        height: '100%',
-        background: 'rgba(255, 255, 255, 0.8)',
-        zIndex: 1,
-      },
-    },
-    absoluteSpinner: {
-      position: 'fixed',
-      top: '50%',
-      left: '50%',
-      transform: 'translate(-50%, -50%)',
-      zIndex: 1,
-      width: '32px !important',
-      height: '32px !important',
-    },
-    marginTop: {
-      marginTop: '10px',
-    },
-    sliderContainer: {
-      width: '100%',
-      padding: '30px 35px',
-      borderRadius: '4px',
-      border: '1px solid #e0e0e0',
-    },
-    sliderInputContainer: {
-      display: 'flex',
-      flexDirection: 'column',
-      marginBottom: '20px',
-      gap: '20px',
-      maxWidth: '350px',
-      width: '100%',
-    },
-    sliderVolume: {
-      display: 'flex',
-      flexDirection: 'row',
-      gap: '10px',
-      alignItems: 'center',
-    },
-    databaseSize: {
-      display: 'flex',
-      flexDirection: 'row',
-      gap: '10px',
-      alignItems: 'center',
-      spinner: {
-        marginLeft: 8,
-        color: 'inherit',
-      },
-    },
-  },
-  { index: 1 },
-)
-
-export const DbLabInstanceFormWrapper = (props: DbLabInstanceFormProps) => {
-  const classes = useInstanceFormStyles()
-
-  return <DbLabInstanceForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/reducer/index.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/reducer/index.tsx
deleted file mode 100644
index 9a103227..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/reducer/index.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { ReducerAction } from 'react'
-
-import { CloudInstance } from 'api/cloud/getCloudInstances'
-import { CloudRegion } from 'api/cloud/getCloudRegions'
-import { CloudVolumes } from 'api/cloud/getCloudVolumes'
-
-import { availableTags } from 'components/DbLabInstanceForm/utils'
-import { clusterExtensionsState } from 'components/PostgresClusterInstallForm/reducer'
-
-export const initialState = {
-  isLoading: false,
-  isReloading: false,
-  formStep: 'create',
-  provider: 'aws',
-  storage: 30,
-  region: 'North America',
-  tag: availableTags[0],
-  serviceProviders: [] as string[],
-  cloudRegions: [] as CloudRegion[],
-  cloudInstances: [] as CloudInstance[],
-  volumes: [] as CloudVolumes[],
-  api_name: 'ssd',
-  databaseSize: 10,
-  snapshots: 3,
-  volumeType: '',
-  volumePrice: 0,
-  volumePricePerHour: 0,
-  volumeCurrency: '',
-  location: {} as CloudRegion,
-  instanceType: {} as CloudInstance,
-  name: '',
-  publicKeys: '',
-  verificationToken: '',
-  numberOfInstances: 3,
-  version: 16,
-  database_public_access: false,
-  with_haproxy_load_balancing: false,
-  pgbouncer_install: true,
-  synchronous_mode: false,
-  synchronous_node_count: 1,
-  netdata_install: true,
-  taskID: '',
-  fileSystem: 'zfs',
-  ...clusterExtensionsState,
-}
-
-export const reducer = (
-  state: typeof initialState,
-  // @ts-ignore
-  action: ReducerAction<unknown, void>,
-) => {
-  switch (action.type) {
-    case 'set_initial_state': {
-      return {
-        ...state,
-        isLoading: action.isLoading,
-        serviceProviders: action.serviceProviders,
-        volumes: action.volumes,
-        volumeType: action.volumeType,
-        volumePrice: action.volumePrice,
-        volumePricePerHour: action.volumePricePerHour,
-        volumeCurrency: action.volumeCurrency,
-        region: initialState.region,
-        databaseSize: initialState.databaseSize,
-        snapshots: initialState.snapshots,
-        location: action.cloudRegions.find(
-          (region: CloudRegion) =>
-            region.world_part === initialState.region &&
-            region.cloud_provider === initialState.provider,
-        ),
-      }
-    }
-    case 'update_initial_state': {
-      return {
-        ...state,
-        volumes: action.volumes,
-        volumeType: action.volumeType,
-        volumePricePerHour: action.volumePricePerHour,
-        volumeCurrency: action.volumeCurrency,
-        cloudRegions: action.cloudRegions,
-        location: action.cloudRegions.find(
-          (region: CloudRegion) => region.world_part === initialState.region,
-        ),
-      }
-    }
-    case 'update_instance_type': {
-      return {
-        ...state,
-        cloudInstances: action.cloudInstances,
-        instanceType: action.instanceType,
-        isReloading: action.isReloading,
-      }
-    }
-    case 'change_provider': {
-      return {
-        ...state,
-        provider: action.provider,
-        region: initialState.region,
-        isReloading: action.isReloading,
-        databaseSize: initialState.databaseSize,
-        snapshots: initialState.snapshots,
-        storage: initialState.storage,
-      }
-    }
-    case 'change_region': {
-      return {
-        ...state,
-        region: action.region,
-        location: action.location,
-      }
-    }
-    case 'change_location': {
-      return {
-        ...state,
-        location: action.location,
-      }
-    }
-    case 'change_name': {
-      return {
-        ...state,
-        name: action.name,
-      }
-    }
-    case 'change_instance_type': {
-      return {
-        ...state,
-        instanceType: action.instanceType,
-      }
-    }
-    case 'change_verification_token': {
-      return {
-        ...state,
-        verificationToken: action.verificationToken,
-      }
-    }
-    case 'change_public_keys': {
-      return {
-        ...state,
-        publicKeys: action.publicKeys,
-      }
-    }
-    case 'change_volume_type': {
-      return {
-        ...state,
-        volumeType: action.volumeType,
-        volumePrice: action.volumePrice,
-        volumePricePerHour: action.volumePricePerHour,
-      }
-    }
-    case 'change_snapshots': {
-      return {
-        ...state,
-        snapshots: action.snapshots,
-        storage: action.storage,
-        volumePrice: action.volumePrice,
-      }
-    }
-
-    case 'change_volume_price': {
-      return {
-        ...state,
-        volumePrice: action.volumePrice,
-        databaseSize: action.databaseSize,
-        storage: action.storage,
-      }
-    }
-
-    case 'set_form_step': {
-      return {
-        ...state,
-        formStep: action.formStep,
-        ...(action.taskID ? { taskID: action.taskID } : {}),
-        ...(action.provider ? { provider: action.provider } : {}),
-      }
-    }
-    case 'set_tag': {
-      return {
-        ...state,
-        tag: action.tag,
-      }
-    }
-    default:
-      throw new Error()
-  }
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceForm/utils/index.tsx b/ui/packages/platform/src/components/DbLabInstanceForm/utils/index.tsx
deleted file mode 100644
index 5821d379..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceForm/utils/index.tsx
+++ /dev/null
@@ -1,278 +0,0 @@
-import { CloudImage } from 'api/cloud/getCloudImages'
-import { CloudRegion } from 'api/cloud/getCloudRegions'
-import { CloudVolumes } from 'api/cloud/getCloudVolumes'
-import { addIndentForDocker } from 'components/PostgresClusterInstallForm/utils'
-import { ClassesType } from 'components/types'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-const API_SERVER = process.env.REACT_APP_API_SERVER
-export const DEBUG_API_SERVER = 'https://v2.postgres.ai/api/general'
-
-export const availableTags = ['3.5.0', '3.4.0', '4.0.0-alpha.6']
-
-export const sePackageTag = 'v1.1'
-
-export const dockerRunCommand = (provider: string) => {
-  switch (provider) {
-    case 'aws':
-      return `docker run --rm -it \\\r
-  --env AWS_ACCESS_KEY_ID=\${AWS_ACCESS_KEY_ID} \\\r
-  --env AWS_SECRET_ACCESS_KEY=\${AWS_SECRET_ACCESS_KEY}`
-
-    case 'gcp':
-      return `docker run --rm -it \\\r
-  --env GCP_SERVICE_ACCOUNT_CONTENTS=\${GCP_SERVICE_ACCOUNT_CONTENTS}`
-
-    case 'hetzner':
-      return `docker run --rm -it \\\r
-  --env HCLOUD_API_TOKEN=\${HCLOUD_API_TOKEN}`
-
-    case 'digitalocean':
-      return `docker run --rm -it \\\r
-  --env DO_API_TOKEN=\${DO_API_TOKEN}`
-
-    default:
-      throw new Error('Provider is not supported')
-  }
-}
-
-export const getPlaybookCommand = (
-  state: useCloudProviderProps['initialState'],
-  cloudImages: CloudImage,
-  orgKey: string,
-  isDocker?: boolean,
-) =>{
-  const playbookCommand = `ansible-playbook deploy_dle.yml --extra-vars \\\r
-  "provision='${state.provider}' \\\r
-  server_name='${state.name}' \\\r
-  server_type='${state.instanceType.native_name}' \\\r
-  server_image='${cloudImages?.native_os_image}' \\\r
-  server_location='${state.location.native_code}' \\\r
-  volume_size='${state.storage}' \\\r
-  dblab_engine_verification_token='${state.verificationToken}' \\\r
-  dblab_engine_version='${state.tag}' \\\r
-  ${ state.snapshots > 1 ? `zpool_datasets_number='${state.snapshots}' \\\r` : `` }
-  ${ orgKey ? `platform_org_key='${orgKey}' \\\r` : `` }
-  ${ API_SERVER === DEBUG_API_SERVER ? `platform_url='${DEBUG_API_SERVER}' \\\r` : `` }
-  ${state.publicKeys ? `ssh_public_keys='${state.publicKeys}' \\\r` : ``}
-  platform_project_name='${state.name}'"`
-
-  if (isDocker) {
-    return `${dockerRunCommand(state.provider)} \\\r
-    postgresai/dle-se-ansible:${sePackageTag} \\\r
-      ${addIndentForDocker(playbookCommand)}`
-  } else {
-    return playbookCommand
-  }
-}
-
-export const getGcpAccountContents = () =>
-  `export GCP_SERVICE_ACCOUNT_CONTENTS='{
-  "type": "service_account",
-  "project_id": "my-project",
-  "private_key_id": "c764349XXXXXXXXXX72f",
-  "private_key": "XXXXXXXXXX",
-  "client_email": "my-sa@my-project.iam.gserviceaccount.com",
-  "client_id": "111111112222222",
-  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
-  "token_uri": "https://oauth2.googleapis.com/token",
-  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
-  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-sat%40my-project.iam.gserviceaccount.com"
-}'`
-
-export const cloudProviderName = (provider: string) => {
-  switch (provider) {
-    case 'aws':
-      return 'AWS'
-    case 'gcp':
-      return 'GCP'
-    case 'digitalocean':
-      return 'DigitalOcean'
-    case 'hetzner':
-      return 'Hetzner'
-    default:
-      return provider
-  }
-}
-
-export const pricingPageForProvider = (provider: string) => {
-  switch (provider) {
-    case 'aws':
-      return 'https://instances.vantage.sh/'
-    case 'gcp':
-      return 'https://cloud.google.com/compute/docs/general-purpose-machines'
-    case 'digitalocean':
-      return 'https://www.digitalocean.com/pricing/droplets'
-    case 'hetzner':
-      return 'https://www.hetzner.com/cloud'
-  }
-}
-
-export const getNetworkSubnet = (provider: string, classNames: ClassesType) => {
-  const AnchorElement = ({
-    href,
-    text = 'created',
-  }: {
-    href: string
-    text?: string
-  }) => (
-    <a
-      href={href}
-      target="blank"
-      rel="noopener noreferrer"
-      className={classNames.link}
-    >
-      {text}
-    </a>
-  )
-
-  const NetworkSubnet = ({
-    note,
-    code,
-    optionalText,
-  }: {
-    note: React.ReactNode
-    code: string
-    optionalText: React.ReactNode
-  }) => (
-    <div className={classNames.containerMargin}>
-      <p>Additional parameters:</p>
-      <ul className={classNames.smallMarginTop}>
-        <li>
-          <code className={classNames.code}>{code}</code> {optionalText} {note}.
-        </li>
-      </ul>
-    </div>
-  )
-
-  switch (provider) {
-    case 'aws':
-      return (
-        <NetworkSubnet
-          note={
-            <>
-              {' '}
-              <AnchorElement
-                href="https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html"
-                text="(More about default VPCs)"
-              />
-            </>
-          }
-          code="server_network='subnet-xxx'"
-          optionalText={
-            <>
-              (optional) Subnet ID. The VM will use this subnet ({' '}
-              <AnchorElement
-                href="https://docs.aws.amazon.com/vpc/latest/userguide/create-subnets.html"
-                text="must be created in advance"
-              />{' '}
-              in the selected region). If not specified, default VPC and subnet
-              will be used.
-            </>
-          }
-        />
-      )
-    case 'hetzner':
-      return (
-        <NetworkSubnet
-          note={
-            <>
-              Public network is always attached; this is needed to access the
-              server the installation process. The variable 'server_network' is
-              used to define an additional network will be attached
-            </>
-          }
-          code="server_network='network-xx'"
-          optionalText={
-            <>
-              (optional) Subnet ID. The VM will use this network ({' '}
-              <AnchorElement
-                href="https://docs.hetzner.com/cloud/networks/getting-started/creating-a-network/"
-                text="must be created in advance"
-              />{' '}
-              in the selected region). If not specified, no additional networks
-              will be used.
-            </>
-          }
-        />
-      )
-    case 'digitalocean':
-      return (
-        <NetworkSubnet
-          note={
-            <>
-              {' '}
-              <AnchorElement
-                href="https://docs.digitalocean.com/products/networking/vpc/"
-                text="(More about VPCs)"
-              />
-            </>
-          }
-          code="server_network='vpc-name'"
-          optionalText={
-            <>
-              (optional) VPC name. The droplet will use this VPC ({' '}
-              <AnchorElement
-                href="https://docs.digitalocean.com/products/networking/vpc/how-to/create/"
-                text="must be created in advance"
-              />{' '}
-              in the selected region). If not specified, default VPC will be
-              used.
-            </>
-          }
-        />
-      )
-    case 'gcp':
-      return (
-        <NetworkSubnet
-          note={
-            <>
-              {' '}
-              <AnchorElement
-                href="https://cloud.google.com/vpc/docs/vpc"
-                text="(More about VPCs)"
-              />
-            </>
-          }
-          code="server_network='vpc-network-name'"
-          optionalText={
-            <>
-              optional) VPC network name. The VM will use this network ({' '}
-              <AnchorElement
-                href="https://cloud.google.com/vpc/docs/create-modify-vpc-networks"
-                text="must be created in advance"
-              />{' '}
-              in the selected region). If not specified, default VPC and network
-              will be used.
-            </>
-          }
-        />
-      )
-  }
-}
-
-export const uniqueRegionsByProvider = (cloudRegions: CloudRegion[]) => {
-  return cloudRegions
-    .map((region) => region.world_part)
-    .filter((value, index, self) => self.indexOf(value) === index)
-}
-
-export const filteredRegions = (
-  cloudRegions: CloudRegion[],
-  selectedRegion: string,
-) => {
-  return cloudRegions.filter((region) => region.world_part === selectedRegion)
-}
-
-export const formatVolumeDetails = (
-  ssdCloudVolume: CloudVolumes,
-  storage: number,
-) => ({
-  volumeType: `${ssdCloudVolume.api_name} (${ssdCloudVolume.cloud_provider}: ${ssdCloudVolume.native_name})`,
-  volumeCurrency: ssdCloudVolume.native_reference_price_currency,
-  volumePricePerHour:
-    ssdCloudVolume.native_reference_price_per_1000gib_per_hour,
-  volumePrice:
-    (storage * ssdCloudVolume.native_reference_price_per_1000gib_per_hour) /
-    1000,
-})
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/AnsibleInstance.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/AnsibleInstance.tsx
deleted file mode 100644
index 6d1cb770..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/AnsibleInstance.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import { Button } from '@material-ui/core'
-import { Box } from '@mui/material'
-import { useEffect, useState } from 'react'
-
-import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-
-import { getOrgKeys } from 'api/cloud/getOrgKeys'
-
-import { formStyles } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-import { SetupStep } from 'components/DbLabInstanceInstallForm/DbLabFormSteps/SetupStep'
-import {
-  cloneRepositoryCommand,
-  getAnsibleInstallationCommand,
-  getPlaybookCommand,
-} from 'components/DbLabInstanceInstallForm/utils'
-
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-export const AnsibleInstance = ({
-  state,
-  orgId,
-  goBack,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  state: useCloudProviderProps['initialState']
-  orgId: number
-  goBack: () => void
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-  const [orgKey, setOrgKey] = useState('')
-  const [isLoading, setIsLoading] = useState(false)
-  const [orgKeyError, setOrgKeyError] = useState(false)
-
-  useEffect(() => {
-    setIsLoading(true)
-    getOrgKeys(orgId).then((data) => {
-      console.log('data', data)
-      if (data.error !== null || !Array.isArray(data.response)) {
-        setIsLoading(false)
-        setOrgKeyError(true)
-      } else {
-        setIsLoading(false)
-        setOrgKeyError(false)
-        setOrgKey(data.response[0].value)
-      }
-    })
-  }, [orgId])
-
-  return (
-    <InstanceFormCreation formStep={formStep} setFormStep={setFormStep} install>
-      {isLoading ? (
-        <span className={classes.spinner}>
-          <Spinner />
-        </span>
-      ) : (
-        <>
-          {orgKeyError ? (
-            <ErrorStub
-              title="Error 404"
-              message="Unexpected error occurred. Please try again later"
-            />
-          ) : (
-            <>
-              <SetupStep classes={classes} />
-              <p className={classes.title}>
-                2. Install Ansible on your local machine (such as a laptop)
-              </p>
-              <SyntaxHighlight content={getAnsibleInstallationCommand()} />
-              <span className={classes.marginBottom}>
-                For additional instructions on installing Ansible, please refer
-                to{' '}
-                <a
-                  target="_blank"
-                  href="https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html"
-                  rel="noreferrer"
-                >
-                  this guide
-                </a>
-                .
-              </span>
-              <p className={classes.title}>
-                3. Clone the "dle-se-ansible" repository to your local machine
-              </p>
-              <SyntaxHighlight content={cloneRepositoryCommand()} />
-              <p className={classes.title}>4. Install necessary dependencies</p>
-              <SyntaxHighlight
-                content={'ansible-galaxy install -r requirements.yml'}
-              />{' '}
-              <p className={classes.title}>
-                5. Execute the Ansible playbook to install DBLab SE on the remote
-                server
-              </p>
-              <p>
-                Replace{' '}
-                <code className={classes.code}>'user@server-ip-address'</code>
-                with the specific username and IP address of the server where
-                you will be installing DBLab.
-              </p>
-              <SyntaxHighlight
-                content={getPlaybookCommand(state, orgKey, false)}
-              />
-              <p className={classes.important}>Please be aware:</p>
-              <p>
-                The script will attempt to automatically detect an empty volume
-                by default. If needed, you can manually specify the path to your
-                desired empty disk using the{' '}
-                <code className={classes.code}>zpool_disk</code>&nbsp; variable
-                (e.g.,{' '}
-                <code className={classes.code}>zpool_disk=/dev/nvme1n1</code>).
-              </p>
-              <p className={classes.title}>
-                7. After the code snippet runs successfully, follow the
-                directions displayed in the resulting output to start using DBLab
-                UI/API/CLI.
-              </p>{' '}
-              <Box
-                sx={{
-                  display: 'flex',
-                  gap: '10px',
-                  margin: '20px 0',
-                }}
-              >
-                <Button
-                  variant="outlined"
-                  color="secondary"
-                  onClick={goBackToForm}
-                >
-                  Back to form
-                </Button>
-                <Button variant="contained" color="primary" onClick={goBack}>
-                  See list of instances
-                </Button>
-              </Box>
-            </>
-          )}
-        </>
-      )}
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/DockerInstance.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/DockerInstance.tsx
deleted file mode 100644
index d8403707..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/DockerInstance.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Button } from '@material-ui/core'
-import { Box } from '@mui/material'
-import { useEffect, useState } from 'react'
-
-import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-
-import { getOrgKeys } from 'api/cloud/getOrgKeys'
-
-import { formStyles } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-import { SetupStep } from 'components/DbLabInstanceInstallForm/DbLabFormSteps/SetupStep'
-import { getPlaybookCommand } from 'components/DbLabInstanceInstallForm/utils'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-
-export const DockerInstance = ({
-  state,
-  orgId,
-  goBack,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  state: useCloudProviderProps['initialState']
-  orgId: number
-  goBack: () => void
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-  const [orgKey, setOrgKey] = useState('')
-  const [isLoading, setIsLoading] = useState(false)
-  const [orgKeyError, setOrgKeyError] = useState(false)
-
-  useEffect(() => {
-    setIsLoading(true)
-    getOrgKeys(orgId).then((data) => {
-      console.log('data', data)
-      if (data.error !== null || !Array.isArray(data.response)) {
-        setIsLoading(false)
-        setOrgKeyError(true)
-      } else {
-        setIsLoading(false)
-        setOrgKeyError(false)
-        setOrgKey(data.response[0].value)
-      }
-    })
-  }, [orgId])
-
-  return (
-    <InstanceFormCreation formStep={formStep} setFormStep={setFormStep} install>
-      {isLoading ? (
-        <span className={classes.spinner}>
-          <Spinner />
-        </span>
-      ) : (
-        <>
-          {orgKeyError ? (
-            <ErrorStub
-              title="Error 404"
-              message="Unexpected error occurred. Please try again later"
-            />
-          ) : (
-            <>
-              <SetupStep classes={classes} />
-              <p className={classes.title}>
-                2. Execute the Ansible playbook to install DBLab SE on the remote
-                server
-              </p>
-              <p>
-                Replace{' '}
-                <code className={classes.code}>'user@server-ip-address'</code>
-                with the specific username and IP address of the server where
-                you will be installing DBLab.
-              </p>
-              <SyntaxHighlight content={getPlaybookCommand(state, orgKey, true)} />
-              <p className={classes.important}>Please be aware:</p>
-              <p>
-                The script will attempt to automatically detect an empty volume
-                by default. If needed, you can manually specify the path to your
-                desired empty disk using the{' '}
-                <code className={classes.code}>zpool_disk</code>&nbsp; variable
-                (e.g.,{' '}
-                <code className={classes.code}>zpool_disk=/dev/nvme1n1</code>).
-              </p>
-              <p className={classes.title}>
-                3. After the code snippet runs successfully, follow the
-                directions displayed in the resulting output to start using DBLab
-                AUI/API/CLI.
-              </p>{' '}
-              <Box
-                sx={{
-                  display: 'flex',
-                  gap: '10px',
-                  margin: '20px 0',
-                }}
-              >
-                <Button
-                  variant="outlined"
-                  color="secondary"
-                  onClick={goBackToForm}
-                >
-                  Back to form
-                </Button>
-                <Button variant="contained" color="primary" onClick={goBack}>
-                  See list of instances
-                </Button>
-              </Box>
-            </>
-          )}
-        </>
-      )}
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/SetupStep.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/SetupStep.tsx
deleted file mode 100644
index abd1112a..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabFormSteps/SetupStep.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ClassesType } from 'components/types'
-
-export const SetupStep = ({ classes }: { classes: ClassesType }) => (
-  <>
-    <p className={classes.title}>1. Set up your machine</p>
-    <ul className={classes.ul}>
-      <li>
-        Obtain a machine running Ubuntu 22.04 (although other versions may work,
-        we recommend using an LTS version for optimal compatibility).
-      </li>
-      <li>
-        Attach an empty disk that is at least twice the size of the database you
-        plan to use with DBLab.
-      </li>
-      <li>
-        Ensure that your SSH public key is added to the machine (in
-        <code className={classes.code}>~/.ssh/authorized_keys</code>), allowing
-        for secure SSH access.
-      </li>
-    </ul>
-  </>
-)
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallForm.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallForm.tsx
deleted file mode 100644
index 44a9608b..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallForm.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { useReducer } from 'react'
-import { TextField, Button } from '@material-ui/core'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DbLabInstanceFormProps } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import { initialState, reducer } from 'components/DbLabInstanceForm/reducer'
-import { DbLabInstanceFormInstallSidebar } from 'components/DbLabInstanceInstallForm/DbLabInstanceInstallFormSidebar'
-import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex'
-import { AnsibleInstance } from 'components/DbLabInstanceInstallForm/DbLabFormSteps/AnsibleInstance'
-import { DockerInstance } from 'components/DbLabInstanceInstallForm/DbLabFormSteps/DockerInstance'
-import { availableTags } from 'components/DbLabInstanceForm/utils'
-import { Select } from '@postgres.ai/shared/components/Select'
-
-import { generateToken, validateDLEName } from 'utils/utils'
-import urls from 'utils/urls'
-
-interface DbLabInstanceFormWithStylesProps extends DbLabInstanceFormProps {
-  classes: ClassesType
-}
-
-const DbLabInstanceInstallForm = (props: DbLabInstanceFormWithStylesProps) => {
-  const { classes, orgPermissions } = props
-  const [state, dispatch] = useReducer(reducer, initialState)
-
-  const permitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-
-  const pageTitle = <ConsolePageTitle title="Install DBLab" />
-  const breadcrumbs = (
-    <ConsoleBreadcrumbsWrapper
-      {...props}
-      breadcrumbs={[
-        { name: 'Database Lab Instances', url: 'instances' },
-        { name: 'Install DBLab' },
-      ]}
-    />
-  )
-
-  const handleGenerateToken = () => {
-    dispatch({
-      type: 'change_verification_token',
-      verificationToken: generateToken(),
-    })
-  }
-
-  const handleReturnToList = () => {
-    props.history.push(urls.linkDbLabInstances(props))
-  }
-
-  const handleSetFormStep = (step: string) => {
-    dispatch({ type: 'set_form_step', formStep: step })
-  }
-
-  const handleReturnToForm = () => {
-    dispatch({ type: 'set_form_step', formStep: initialState.formStep })
-  }
-
-  if (state.isLoading) return <StubSpinner />
-
-  return (
-    <div className={classes.root}>
-      {breadcrumbs}
-
-      {pageTitle}
-
-      {!permitted && (
-        <WarningWrapper>
-          You do not have permission to add Database Lab instances.
-        </WarningWrapper>
-      )}
-
-      <div className={classes.container}>
-        {state.formStep === initialState.formStep && permitted ? (
-          <>
-            <div className={classes.form}>
-              <p className={classes.sectionTitle}>1. Provide DBLab name</p>
-              <TextField
-                required
-                label="DBLab Name"
-                variant="outlined"
-                fullWidth
-                value={state.name}
-                className={classes.marginTop}
-                InputLabelProps={{
-                  shrink: true,
-                }}
-                helperText={
-                  validateDLEName(state.name)
-                    ? 'Name must be lowercase and contain only letters and numbers.'
-                    : ''
-                }
-                error={validateDLEName(state.name)}
-                onChange={(
-                  event: React.ChangeEvent<
-                    HTMLTextAreaElement | HTMLInputElement
-                  >,
-                ) =>
-                  dispatch({ type: 'change_name', name: event.target.value })
-                }
-              />
-              <p className={classes.sectionTitle}>
-                2. Define DBLab verification token (keep it secret!)
-              </p>
-              <div className={classes.generateContainer}>
-                <TextField
-                  required
-                  label="DBLab Verification Token"
-                  variant="outlined"
-                  fullWidth
-                  value={state.verificationToken}
-                  className={classes.marginTop}
-                  InputLabelProps={{
-                    shrink: true,
-                  }}
-                  onChange={(
-                    event: React.ChangeEvent<
-                      HTMLTextAreaElement | HTMLInputElement
-                    >,
-                  ) =>
-                    dispatch({
-                      type: 'change_verification_token',
-                      verificationToken: event.target.value,
-                    })
-                  }
-                />
-                <Button
-                  variant="contained"
-                  color="primary"
-                  disabled={!permitted}
-                  onClick={handleGenerateToken}
-                >
-                  Generate random
-                </Button>
-              </div>
-              <p className={classes.sectionTitle}>
-                3. Choose DBLab server version
-              </p>
-              <Select
-                label="Select tag"
-                items={
-                  availableTags.map((tag) => {
-                    const defaultTag = availableTags[0]
-
-                    return {
-                      value: tag,
-                      children: defaultTag === tag ? `${tag} (default)` : tag,
-                    }
-                  }) ?? []
-                }
-                value={state.tag}
-                onChange={(
-                  e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
-                ) =>
-                  dispatch({
-                    type: 'set_tag',
-                    tag: e.target.value,
-                  })
-                }
-              />
-              <p className={classes.sectionTitle}>
-                4. Provide SSH public keys (one per line)
-              </p>{' '}
-              <p className={classes.instanceParagraph}>
-                The specified ssh public keys will be added to authorized_keys
-                on the DBLab server. Add your public key here to have access to
-                the server after deployment.
-              </p>
-              <TextField
-                label="SSH public keys"
-                variant="outlined"
-                fullWidth
-                multiline
-                value={state.publicKeys}
-                helperText={
-                  state.publicKeys && state.publicKeys.length < 30
-                    ? 'Public key is too short'
-                    : ''
-                }
-                error={state.publicKeys && state.publicKeys.length < 30}
-                className={classes.marginTop}
-                InputLabelProps={{
-                  shrink: true,
-                }}
-                onChange={(
-                  event: React.ChangeEvent<
-                    HTMLTextAreaElement | HTMLInputElement
-                  >,
-                ) =>
-                  dispatch({
-                    type: 'change_public_keys',
-                    publicKeys: event.target.value,
-                  })
-                }
-              />
-            </div>
-            <DbLabInstanceFormInstallSidebar
-              state={state}
-              disabled={
-                validateDLEName(state.name) ||
-                (state.publicKeys && state.publicKeys.length < 30)
-              }
-              handleCreate={() =>
-                !validateDLEName(state.name) && handleSetFormStep('docker')
-              }
-            />
-          </>
-        ) : state.formStep === 'ansible' && permitted ? (
-          <AnsibleInstance
-            state={state}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : state.formStep === 'docker' && permitted ? (
-          <DockerInstance
-            state={state}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : null}
-      </div>
-    </div>
-  )
-}
-
-export default DbLabInstanceInstallForm
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallFormSidebar.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallFormSidebar.tsx
deleted file mode 100644
index 44f601e9..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallFormSidebar.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Button, makeStyles } from '@material-ui/core'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-
-const useStyles = makeStyles({
-  boxShadow: {
-    padding: '24px',
-    boxShadow: '0 8px 16px #3a3a441f, 0 16px 32px #5a5b6a1f',
-  },
-  aside: {
-    width: '100%',
-    height: 'fit-content',
-    borderRadius: '4px',
-    display: 'flex',
-    flexDirection: 'column',
-    justifyContent: 'flex-start',
-    flex: '1 1 0',
-    position: 'sticky',
-    top: 10,
-
-    '& h2': {
-      fontSize: '14px',
-      fontWeight: 500,
-      margin: '0 0 10px 0',
-      height: 'fit-content',
-    },
-
-    '& span': {
-      fontSize: '13px',
-    },
-
-    '& button': {
-      padding: '10px 20px',
-      marginTop: '20px',
-    },
-
-    '@media (max-width: 1200px)': {
-      position: 'relative',
-      boxShadow: 'none',
-      borderRadius: '0',
-      padding: '0',
-      flex: 'auto',
-      marginBottom: '30px',
-
-      '& button': {
-        width: 'max-content',
-      },
-    },
-  },
-  asideSection: {
-    padding: '12px 0',
-    borderBottom: '1px solid #e0e0e0',
-
-    '& span': {
-      color: '#808080',
-    },
-
-    '& p': {
-      margin: '5px 0 0 0',
-      fontSize: '13px',
-    },
-  },
-})
-
-export const DbLabInstanceFormInstallSidebar = ({
-  state,
-  handleCreate,
-  disabled,
-}: {
-  state: useCloudProviderProps['initialState']
-  handleCreate: () => void
-  disabled: boolean
-}) => {
-  const classes = useStyles()
-
-  return (
-    <div className={classes.aside}>
-      <div className={classes.boxShadow}>
-        {state.name && (
-          <div className={classes.asideSection}>
-            <span>Name</span>
-            <p>{state.name}</p>
-          </div>
-        )}
-        {state.tag && (
-          <div className={classes.asideSection}>
-            <span>Tag</span>
-            <p>{state.tag}</p>
-          </div>
-        )}
-        <Button
-          variant="contained"
-          color="primary"
-          onClick={handleCreate}
-          disabled={!state.name || !state.verificationToken || disabled}
-        >
-          Install DBLab
-        </Button>
-      </div>
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallFormWrapper.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallFormWrapper.tsx
deleted file mode 100644
index 9e930bbf..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/DbLabInstanceInstallFormWrapper.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { RouteComponentProps } from 'react-router'
-
-import DbLabInstanceInstallForm from 'components/DbLabInstanceInstallForm/DbLabInstanceInstallForm'
-
-import { useInstanceFormStyles } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import { OrgPermissions } from 'components/types'
-
-export interface DbLabInstanceFormProps {
-  edit?: boolean
-  orgId: number
-  project: string | undefined
-  history: RouteComponentProps['history']
-  orgPermissions: OrgPermissions
-}
-
-export const DbLabInstanceFormInstallWrapper = (
-  props: DbLabInstanceFormProps,
-) => {
-  const classes = useInstanceFormStyles()
-
-  return <DbLabInstanceInstallForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/reducer/index.tsx b/ui/packages/platform/src/components/DbLabInstanceInstallForm/reducer/index.tsx
deleted file mode 100644
index 83a3fe76..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/reducer/index.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-import { ReducerAction } from 'react'
-
-import { availableTags } from 'components/DbLabInstanceForm/utils'
-
-export const initialState = {
-  isLoading: false,
-  formStep: 'create',
-  api_name: 'ssd',
-  name: '',
-  publicKeys: '',
-  tag: availableTags[0],
-  verificationToken: '',
-}
-
-export const reducer = (
-  state: typeof initialState,
-  // @ts-ignore
-  action: ReducerAction<unknown, void>,
-) => {
-  switch (action.type) {
-    case 'change_name': {
-      return {
-        ...state,
-        name: action.name,
-      }
-    }
-    case 'change_verification_token': {
-      return {
-        ...state,
-        verificationToken: action.verificationToken,
-      }
-    }
-    case 'change_public_keys': {
-      return {
-        ...state,
-        publicKeys: action.publicKeys,
-      }
-    }
-    case 'set_form_step': {
-      return {
-        ...state,
-        formStep: action.formStep,
-      }
-    }
-    case 'set_tag': {
-      return {
-        ...state,
-        tag: action.tag,
-      }
-    }
-    default:
-      throw new Error()
-  }
-}
diff --git a/ui/packages/platform/src/components/DbLabInstanceInstallForm/utils/index.ts b/ui/packages/platform/src/components/DbLabInstanceInstallForm/utils/index.ts
deleted file mode 100644
index 513976d3..00000000
--- a/ui/packages/platform/src/components/DbLabInstanceInstallForm/utils/index.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { DEBUG_API_SERVER, sePackageTag } from 'components/DbLabInstanceForm/utils'
-import { addIndentForDocker } from 'components/PostgresClusterInstallForm/utils'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-const API_SERVER = process.env.REACT_APP_API_SERVER
-
-export const getPlaybookCommand = (
-  state: useCloudProviderProps['initialState'],
-  orgKey: string,
-  isDocker?: boolean,
-) => {
-  const playbookCommand = `ansible-playbook deploy_dle.yml --extra-vars \\\r
-  "dblab_engine_host='user@server-ip-address' \\\r
-  platform_project_name='${state.name}' \\\r
-  dblab_engine_version='${state.tag}' \\\r
-  ${ orgKey ? `platform_org_key='${orgKey}' \\\r` : `` }
-  ${ API_SERVER === DEBUG_API_SERVER ? `platform_url='${DEBUG_API_SERVER}' \\\r` : `` }
-  ${ state.publicKeys ? `ssh_public_keys='${state.publicKeys}' \\\r` : `` }
-  dblab_engine_verification_token='${state.verificationToken}'"`
-
-  if (isDocker) {
-    return `docker run --rm -it \\\r
-  --volume $HOME/.ssh:/root/.ssh:ro \\\r
-  --env ANSIBLE_SSH_ARGS="-F none" \\\r
-  postgresai/dle-se-ansible:${sePackageTag} \\\r
-      ${addIndentForDocker(playbookCommand)}`
-  } else {
-    return playbookCommand
-  }
-}
-
-export const getAnsibleInstallationCommand = () =>
-  `sudo apt update
-sudo apt install -y python3-pip
-pip3 install ansible`
-
-export const cloneRepositoryCommand = () =>
-  `git clone --depth 1 --branch ${sePackageTag} \\
-    https://gitlab.com/postgres-ai/dle-se-ansible.git \\
-  && cd dle-se-ansible/`
diff --git a/ui/packages/platform/src/components/DbLabInstances/DbLabInstances.tsx b/ui/packages/platform/src/components/DbLabInstances/DbLabInstances.tsx
deleted file mode 100644
index f0fd8453..00000000
--- a/ui/packages/platform/src/components/DbLabInstances/DbLabInstances.tsx
+++ /dev/null
@@ -1,625 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, MouseEvent } from 'react'
-import { formatDistanceToNowStrict } from 'date-fns'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  TextField,
-  IconButton,
-  Menu,
-  MenuItem,
-  Tooltip,
-} from '@material-ui/core'
-import MoreVertIcon from '@material-ui/icons/MoreVert'
-import WarningIcon from '@material-ui/icons/Warning'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { Modal } from '@postgres.ai/shared/components/Modal'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import {
-  ClassesType,
-  ProjectProps,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import ConsolePageTitle from './../ConsolePageTitle'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { messages } from '../../assets/messages'
-import Store from '../../stores/store'
-import Urls from '../../utils/urls'
-import format from '../../utils/format'
-import { isHttps } from '../../utils/utils'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { ProjectDataType, getProjectAliasById } from 'utils/aliases'
-import { InstanceStateDto } from '@postgres.ai/shared/types/api/entities/instanceState'
-import { InstanceDto } from '@postgres.ai/shared/types/api/entities/instance'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DbLabStatusWrapper } from 'components/DbLabStatus/DbLabStatusWrapper'
-import { DbLabInstancesProps } from 'components/DbLabInstances/DbLabInstancesWrapper'
-import { CreatedDbLabCards } from 'components/CreateDbLabCards/CreateDbLabCards'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-
-interface DbLabInstancesWithStylesProps extends DbLabInstancesProps {
-  classes: ClassesType
-}
-
-interface DbLabInstancesState {
-  modalOpen: boolean
-  data: {
-    auth: {
-      token: string
-    } | null
-    userProfile: {
-      data: {
-        orgs: ProjectDataType
-      }
-    }
-    dbLabInstances: {
-      orgId: number
-      data: {
-        [org: string]: {
-          created_at: string
-          project_label_or_name: string
-          plan: string
-          project_name: string
-          project_label: string
-          url: string
-          use_tunnel: boolean
-          isProcessing: boolean
-          id: string
-          project_alias: string
-          state: InstanceStateDto
-          dto: InstanceDto
-        }
-      }
-      isProcessing: boolean
-      projectId: string | number | undefined
-      error: boolean
-    } | null
-    dbLabInstanceStatus: {
-      instanceId: string
-      isProcessing: boolean
-    }
-    projects: Omit<ProjectProps, 'isProcessed'>
-  }
-  anchorEl: (EventTarget & HTMLButtonElement) | null
-}
-
-class DbLabInstances extends Component<
-  DbLabInstancesWithStylesProps,
-  DbLabInstancesState
-> {
-  componentDidMount() {
-    const that = this
-    const orgId = this.props.orgId ? this.props.orgId : null
-    let projectId = this.props.projectId ? this.props.projectId : null
-
-    if (!projectId) {
-      projectId =
-        this.props.match &&
-        this.props.match.params &&
-        this.props.match.params.projectId
-          ? this.props.match.params.projectId
-          : null
-    }
-
-    if (projectId) {
-      Actions.setDbLabInstancesProject(orgId, projectId)
-    } else {
-      Actions.setDbLabInstancesProject(orgId, 0)
-    }
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      const auth: DbLabInstancesState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const dbLabInstances: DbLabInstancesState['data']['dbLabInstances'] =
-        this.data && this.data.dbLabInstances ? this.data.dbLabInstances : null
-      const projects: Omit<ProjectProps, 'isProcessed'> =
-        this.data && this.data.projects ? this.data.projects : null
-
-      if (
-        auth &&
-        auth.token &&
-        !dbLabInstances?.isProcessing &&
-        !dbLabInstances?.error &&
-        !that.state
-      ) {
-        Actions.getDbLabInstances(auth.token, orgId, projectId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects.isProcessing &&
-        !projects.error &&
-        !that.state
-      ) {
-        Actions.getProjects(auth.token, orgId)
-      }
-
-      that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  unsubscribe: Function
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleClick = (
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    id: string,
-  ) => {
-    const url = Urls.linkDbLabInstance(this.props, id)
-
-    if (url) {
-      this.props.history.push(url)
-    }
-  }
-
-  handleChangeProject = (
-    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
-  ) => {
-    const org = this.props.org ? this.props.org : null
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const projectId = event.target.value
-    const project = this.state.data
-      ? getProjectAliasById(this.state.data?.userProfile?.data?.orgs, projectId)
-      : ''
-    const props = { org, orgId, projectId, project }
-
-    Actions.setDbLabInstancesProject(orgId, event.target.value)
-    this.props.history.push(Urls.linkDbLabInstances(props))
-  }
-
-  registerButtonHandler = (provider: string) => {
-    this.props.history.push(Urls.linkDbLabInstanceAdd(this.props, provider))
-  }
-
-  openMenu = (event: MouseEvent<HTMLButtonElement>) => {
-    event.stopPropagation()
-    this.setState({ anchorEl: event.currentTarget })
-  }
-
-  closeMenu = () => {
-    this.setState({ anchorEl: null })
-  }
-
-  menuHandler = (_: MouseEvent<HTMLLIElement>, action: string) => {
-    const anchorEl = this.state.anchorEl
-
-    this.closeMenu()
-
-    setTimeout(() => {
-      const auth =
-        this.state.data && this.state.data.auth ? this.state.data.auth : null
-      const data =
-        this.state.data && this.state.data.dbLabInstances
-          ? this.state.data.dbLabInstances
-          : null
-
-      if (anchorEl) {
-        const instanceId = anchorEl.getAttribute('aria-label')
-        if (!instanceId) {
-          return
-        }
-
-        let project = ''
-        if (data?.data) {
-          for (const i in data.data) {
-            if (parseInt(data.data[i].id, 10) === parseInt(instanceId, 10)) {
-              project = data.data[i].project_alias
-            }
-          }
-        }
-
-        switch (action) {
-          case 'addclone':
-            this.props.history.push(
-              Urls.linkDbLabCloneAdd(
-                { org: this.props.org, project: project },
-                instanceId,
-              ),
-            )
-
-            break
-
-          case 'destroy':
-            /* eslint no-alert: 0 */
-            if (
-              window.confirm(
-                'Are you sure you want to remove this Database Lab instance?',
-              ) === true
-            ) {
-              Actions.destroyDbLabInstance(auth?.token, instanceId)
-            }
-
-            break
-
-          case 'refresh':
-            Actions.getDbLabInstanceStatus(auth?.token, instanceId)
-
-            break
-
-          case 'editProject':
-            this.props.history.push(
-              Urls.linkDbLabInstanceEditProject(
-                { org: this.props.org, project: project },
-                instanceId,
-              ),
-            )
-
-            break
-
-          default:
-            break
-        }
-      }
-    }, 100)
-  }
-
-  render() {
-    const { classes, orgPermissions, orgId } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.dbLabInstances
-        ? this.state.data.dbLabInstances
-        : null
-    const projects =
-      this.state && this.state.data && this.state.data?.projects
-        ? this.state.data.projects
-        : null
-    const projectId = this.props.projectId ? this.props.projectId : null
-    const menuOpen = Boolean(this.state && this.state.anchorEl)
-    const title = 'Database Lab Instances'
-    const addPermitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-    const deletePermitted =
-      !orgPermissions || orgPermissions.dblabInstanceDelete
-
-    const getVersionDigits = (str: string) => {
-      if (!str) {
-        return 'N/A'
-      }
-
-      const digits = str.match(/\d+/g)
-
-      if (digits && digits.length > 0) {
-        return `${digits[0]}.${digits[1]}.${digits[2]}`
-      }
-      return ''
-    }
-
-    const addInstanceButton = (
-      <ConsoleButtonWrapper
-        disabled={!addPermitted}
-        variant="contained"
-        color="primary"
-        key="add_dblab_instance"
-        onClick={() => this.setState({ modalOpen: true })}
-        title={addPermitted ? 'Create new DBLab' : messages.noPermission}
-      >
-        New DBLab
-      </ConsoleButtonWrapper>
-    )
-    const pageTitle = (
-      <ConsolePageTitle
-        title={title}
-        actions={
-          data?.data && Object.keys(data.data).length > 0
-            ? [addInstanceButton]
-            : []
-        }
-      />
-    )
-
-    let projectFilter = null
-    if (projects && projects.data && data) {
-      projectFilter = (
-        <div>
-          <TextField
-            value={data.projectId}
-            onChange={(event) => this.handleChangeProject(event)}
-            select
-            label="Project"
-            inputProps={{
-              name: 'project',
-              id: 'project-filter',
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel,
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper,
-            }}
-            variant="outlined"
-            className={classes.filterSelect}
-          >
-            <MenuItem value={0}>All</MenuItem>
-
-            {projects.data.map((p) => {
-              return (
-                <MenuItem value={p.id} key={p.id}>
-                  {p?.project_label_or_name || p.name}
-                </MenuItem>
-              )
-            })}
-          </TextField>
-        </div>
-      )
-    }
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: title }]}
-      />
-    )
-
-    if (orgPermissions && !orgPermissions.dblabInstanceList) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <WarningWrapper>{messages.noPermissionPage}</WarningWrapper>
-        </div>
-      )
-    }
-
-    if (this.state?.data && this.state.data?.dbLabInstances?.error) {
-      return (
-        <div>
-          {breadcrumbs}
-
-          <ConsolePageTitle title={title} />
-
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (data && data.isProcessing) ||
-      data.orgId !== orgId ||
-      data.projectId !== (projectId ? projectId : 0)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          <ConsolePageTitle title={title} />
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const CardsModal = () => (
-      <Modal
-        size="md"
-        isOpen={this.state.modalOpen}
-        title="Choose the location for your DBLab SE installation"
-        onClose={() => this.setState({ modalOpen: false })}
-        aria-labelledby="simple-modal-title"
-        aria-describedby="simple-modal-description"
-      >
-        <CreatedDbLabCards isModal props={this.props} dblabPermitted={addPermitted} />
-      </Modal>
-    )
-
-    let table = (
-      <CreatedDbLabCards props={this.props} dblabPermitted={addPermitted} />
-    )
-
-    let menu = null
-    if (data.data && Object.keys(data.data).length > 0) {
-      table = (
-        <HorizontalScrollContainer>
-          <Table className={classes.table} id="dblabInstancesTable">
-            <TableHead>
-              <TableRow className={classes.row}>
-                <TableCell>Project</TableCell>
-                <TableCell>Instance ID</TableCell>
-                <TableCell>URL</TableCell>
-                <TableCell>Clones</TableCell>
-                <TableCell>Plan</TableCell>
-                <TableCell>Version</TableCell>
-                <TableCell>State</TableCell>
-                <TableCell>Created at</TableCell>
-                <TableCell>&nbsp;</TableCell>
-              </TableRow>
-            </TableHead>
-
-            <TableBody>
-              {Object.keys(data.data).map((index) => {
-                return (
-                  <TableRow
-                    hover
-                    className={classes.row}
-                    key={index}
-                    onClick={(event) =>
-                      this.handleClick(event, data.data[index].id)
-                    }
-                    style={{ cursor: 'pointer' }}
-                  >
-                    <TableCell className={classes.cell}>
-                      {data.data[index].project_label_or_name ||
-                        data.data[index].project_name}
-                    </TableCell>
-
-                    <TableCell className={classes.cell}>
-                      {data.data[index].id}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      {data.data[index].state && data.data[index].url
-                        ? data.data[index].url
-                        : 'N/A'}
-                      {!isHttps(data.data[index].url) &&
-                      data.data[index].url &&
-                      !data.data[index].use_tunnel ? (
-                        <Tooltip
-                          title="The connection to Database Lab API is not secure"
-                          aria-label="add"
-                          classes={{ tooltip: classes.tooltip }}
-                        >
-                          <WarningIcon className={classes.warningIcon} />
-                        </Tooltip>
-                      ) : null}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      {data.data[index]?.state?.cloning?.numClones ??
-                        data.data[index]?.state?.clones?.length ??
-                        'N/A'}
-                    </TableCell>
-                    <TableCell>
-                      {data.data[index] &&
-                        (data.data[index]?.plan === 'EE'
-                          ? 'Enterprise'
-                          : data.data[index]?.plan === 'SE'
-                          ? 'Standard'
-                          : data.data[index]?.plan)}
-                    </TableCell>
-                    <TableCell>
-                      {getVersionDigits(
-                        data.data[index] &&
-                          (data.data[index].state?.engine?.version as string),
-                      )}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      {data.data[index].state && data.data[index].url ? (
-                        <DbLabStatusWrapper
-                          instance={data.data[index]}
-                          onlyText={false}
-                        />
-                      ) : (
-                        'N/A'
-                      )}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      <Tooltip
-                        title={formatDistanceToNowStrict(
-                          new Date(data.data[index].created_at),
-                          {
-                            addSuffix: true,
-                          },
-                        )}
-                        classes={{ tooltip: classes.timeLabel }}
-                      >
-                        <span>
-                          {format.formatTimestampUtc(
-                            data.data[index].created_at,
-                          ) ?? ''}
-                        </span>
-                      </Tooltip>
-                    </TableCell>
-                    <TableCell align={'right'}>
-                      {data.data[index].isProcessing ||
-                      (this.state.data?.dbLabInstanceStatus.instanceId ===
-                        index &&
-                        this.state.data.dbLabInstanceStatus.isProcessing) ? (
-                        <Spinner className={classes.inTableProgress} />
-                      ) : null}
-                      <IconButton
-                        aria-label={data.data[index].id}
-                        aria-controls="instance-menu"
-                        aria-haspopup="true"
-                        onClick={this.openMenu}
-                      >
-                        <MoreVertIcon />
-                      </IconButton>
-                    </TableCell>
-                  </TableRow>
-                )
-              })}
-            </TableBody>
-          </Table>
-        </HorizontalScrollContainer>
-      )
-
-      const selectedInstance = Object.values(data.data).filter((item) => {
-        const anchorElLabel = this.state.anchorEl?.getAttribute('aria-label')
-        // eslint-disable-next-line eqeqeq
-        return anchorElLabel && item.id == anchorElLabel
-      })[0]
-
-      menu = (
-        <Menu
-          id="instance-menu"
-          anchorEl={this.state.anchorEl}
-          keepMounted
-          open={menuOpen}
-          onClose={this.closeMenu}
-          PaperProps={{
-            style: {
-              width: 200,
-            },
-          }}
-        >
-          <MenuItem
-            key={1}
-            onClick={(event) => this.menuHandler(event, 'editProject')}
-            disabled={!addPermitted || selectedInstance?.plan === 'SE'}
-          >
-            Edit
-          </MenuItem>
-          <MenuItem
-            key={2}
-            onClick={(event) => this.menuHandler(event, 'addclone')}
-            disabled={selectedInstance?.plan === 'SE'}
-          >
-            Create clone
-          </MenuItem>
-          <MenuItem
-            key={3}
-            onClick={(event) => this.menuHandler(event, 'refresh')}
-            disabled={selectedInstance?.plan === 'SE'}
-          >
-            Refresh
-          </MenuItem>
-          <MenuItem
-            disabled={!deletePermitted}
-            key={4}
-            onClick={(event) => this.menuHandler(event, 'destroy')}
-          >
-            Remove from List
-          </MenuItem>
-        </Menu>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {projectFilter}
-
-        {table}
-
-        {menu}
-
-        <CardsModal />
-      </div>
-    )
-  }
-}
-
-export default DbLabInstances
diff --git a/ui/packages/platform/src/components/DbLabInstances/DbLabInstancesWrapper.tsx b/ui/packages/platform/src/components/DbLabInstances/DbLabInstancesWrapper.tsx
deleted file mode 100644
index 0b75f20f..00000000
--- a/ui/packages/platform/src/components/DbLabInstances/DbLabInstancesWrapper.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import DbLabInstances from 'components/DbLabInstances/DbLabInstances'
-import { RouteComponentProps } from 'react-router'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { OrgPermissions } from 'components/types'
-
-export interface DbLabInstancesProps {
-  orgId: number
-  org: string | number
-  project: string | undefined
-  projectId: string | number | undefined
-  history: RouteComponentProps['history']
-  match: {
-    params: {
-      project?: string
-      projectId?: string | number | undefined
-      org?: string
-    }
-  }
-  orgPermissions: OrgPermissions
-}
-
-export const DbLabInstancesWrapper = (props: DbLabInstancesProps) => {
-  const useStyles = makeStyles(
-    {
-      root: {
-        ...(styles.root as Object),
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      filterSelect: {
-        ...styles.inputField,
-        width: 150,
-      },
-      cell: {
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      inTableProgress: {
-        width: '30px!important',
-        height: '30px!important',
-      },
-      warningIcon: {
-        color: colors.state.warning,
-        fontSize: '1.2em',
-        position: 'absolute',
-        marginLeft: 5,
-      },
-      tooltip: {
-        fontSize: '10px!important',
-      },
-      timeLabel: {
-        lineHeight: '16px',
-        fontSize: 12,
-        cursor: 'pointer',
-      },
-      buttonContainer: {
-        display: 'flex',
-        gap: 10,
-      },
-      flexContainer: {
-        display: 'flex',
-        alignItems: 'center',
-        justifyContent: 'center',
-        width: '100%',
-        height: '100%',
-        gap: 40,
-        marginTop: '20px',
-
-        '& > div': {
-          maxWidth: '300px',
-          width: '100%',
-          height: '100%',
-          display: 'flex',
-          justifyContent: 'center',
-          alignItems: 'center',
-          border: '1px solid #e0e0e0',
-          padding: '20px',
-          borderRadius: '4px',
-          cursor: 'pointer',
-          fontSize: '15px',
-          transition: 'border 0.3s ease-in-out',
-
-          '&:hover': {
-            border: '1px solid #FF6212',
-          },
-        },
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <DbLabInstances {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DbLabSession/DbLabSession.tsx b/ui/packages/platform/src/components/DbLabSession/DbLabSession.tsx
deleted file mode 100644
index e41e63f7..00000000
--- a/ui/packages/platform/src/components/DbLabSession/DbLabSession.tsx
+++ /dev/null
@@ -1,1018 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { NavLink } from 'react-router-dom'
-import {
-  Button,
-  Table,
-  TableBody,
-  TableCell,
-  TableRow,
-  TextField,
-  ExpansionPanelSummary,
-  ExpansionPanelDetails,
-  ExpansionPanel,
-  Typography,
-} from '@material-ui/core'
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
-import { formatDistanceToNowStrict } from 'date-fns'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import ConsolePageTitle from './../ConsolePageTitle'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { messages } from '../../assets/messages'
-import format from '../../utils/format'
-import urls, { PropsType } from '../../utils/urls'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import dblabutils from '../../utils/dblabutils'
-import { MomentInput } from 'moment'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DbLabStatusWrapper } from 'components/DbLabStatus/DbLabStatusWrapper'
-import { DbLabSessionProps } from 'components/DbLabSession/DbLabSessionWrapper'
-
-interface DbLabSessionWithStylesProps extends DbLabSessionProps {
-  classes: ClassesType
-}
-
-interface Session {
-  id: number
-  project_name: string
-  internal_instance_id: string | undefined
-  duration: number
-  started_at: string
-  config: {
-    [config: string | number]: number
-  }
-  tags: {
-    request_link: string
-    username_full: string
-    username_link: string
-    launched_by: string
-    dle_version: string
-    data_state_at: string
-    branch: string
-    branch_link: string
-    revision: string
-    revision_link: string
-  }
-  result: {
-    status: string
-    intervals: {
-      warning: boolean
-      started_at: MomentInput
-      duration: number
-    }[]
-    summary: {
-      elapsed: boolean
-      total_intervals: number
-      total_duration: number
-      checklist: {
-        [x: string]: { author_id: string }
-      }
-    }
-  }
-}
-
-interface DbLabSessionState {
-  logsExpanded: boolean
-  artifactsExpanded: { [x: string]: boolean }
-  configurationExpanded: boolean
-  intervalsExpanded: boolean
-  data: {
-    dbLabInstanceStatus: {
-      error: boolean
-    }
-    auth: {
-      token: string | null
-    } | null
-    dbLabSession: {
-      artifactsData: Record<string, unknown>
-      isLogsComplete: boolean
-      isArtifactsProcessed: boolean
-      isArtifactsProcessing: boolean
-      isLogDownloading: boolean
-      downloadingArtifactType: string
-      isArtifactDownloading: boolean
-      artifacts:
-        | {
-            artifact_size: number
-            artifact_type: string
-            dblab_session_id: string
-          }[]
-        | null
-      artifactData: {
-        [data: string]: {
-          isProcessing: boolean
-          isProcessed: boolean
-          data: string
-        }
-      } | null
-      logs: {
-        process_id: string
-        connection_from: string
-        session_id: string
-        session_line_num: string
-        command_tag: string
-        session_start_time: string
-        virtual_transaction_id: string
-        transaction_id: string
-        error_severity: string
-        sql_state_code: string
-        message: string
-        detail: string
-        hint: string
-        internal_query: string
-        internal_query_pos: string
-        context: string
-        query: string
-        query_pos: string
-        location: string
-        application_name: string
-        backend_type: string
-        database_name: string
-        user_name: string
-        log_time: string
-        id: number
-      }[]
-      error: boolean
-      errorMessage: string
-      errorCode: number
-      isLogsProcessing: boolean
-      isProcessing: boolean
-      data: Session
-    } | null
-  } | null
-}
-
-const PAGE_SIZE = 20
-
-class DbLabSession extends Component<
-  DbLabSessionWithStylesProps,
-  DbLabSessionState
-> {
-  unsubscribe: Function
-  componentDidMount() {
-    const sessionId = this.props.match.params.sessionId
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const dbLabSession =
-        this.data && this.data.dbLabSession ? this.data.dbLabSession : null
-
-      if (auth && auth.token && !dbLabSession?.isProcessing && !that.state) {
-        Actions.getDbLabSession(auth.token, sessionId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !dbLabSession?.isLogsProcessing &&
-        !that.state
-      ) {
-        Actions.getDbLabSessionLogs(auth.token, { sessionId, limit: PAGE_SIZE })
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !dbLabSession?.isArtifactsProcessing &&
-        !that.state
-      ) {
-        Actions.getDbLabSessionArtifacts(auth.token, sessionId)
-      }
-
-      that.setState({ data: this.data })
-
-      const contentContainer = document.getElementById('logs-container')
-      if (contentContainer && !contentContainer.getAttribute('onscroll')) {
-        contentContainer.addEventListener('scroll', () => {
-          if (
-            contentContainer.scrollTop >=
-            contentContainer.scrollHeight - contentContainer.offsetHeight
-          ) {
-            this.showMore()
-          }
-        })
-        contentContainer.setAttribute('onscroll', '1')
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  showMore() {
-    const sessionId = this.props.match.params.sessionId
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const session =
-      this.state.data && this.state.data.dbLabSession
-        ? this.state.data.dbLabSession
-        : null
-    let lastId = null
-
-    if (session && session.logs && session.logs.length) {
-      lastId = session.logs[session.logs.length - 1].id
-    }
-
-    if (auth && auth.token && !session?.isLogsProcessing && lastId) {
-      Actions.getDbLabSessionLogs(auth.token, {
-        sessionId,
-        limit: PAGE_SIZE,
-        lastId,
-      })
-    }
-  }
-
-  getCheckDetails(session: Session, check: string) {
-    let intervals = null
-    let maxIntervals = null
-    let totalDuration = null
-    let maxDuration = null
-    switch (check) {
-      case 'no_long_dangerous_locks':
-        if (
-          session &&
-          session.result &&
-          session.result.summary &&
-          session.result.summary.total_intervals
-        ) {
-          intervals = session.result.summary.total_intervals
-        }
-        if (session && session.config && session.config.observation_interval) {
-          maxIntervals = session.config.observation_interval
-        }
-        if (intervals && maxIntervals) {
-          return (
-            '(' +
-            intervals +
-            ' ' +
-            (intervals > 1 ? 'intervals' : 'interval') +
-            ' with locks of ' +
-            maxIntervals +
-            ' allowed)'
-          )
-        }
-        break
-      case 'session_duration_acceptable':
-        if (
-          session &&
-          session.result &&
-          session.result.summary &&
-          session.result.summary.total_duration
-        ) {
-          totalDuration = session.result.summary.total_duration
-        }
-        if (session && session.config && session.config.max_duration) {
-          maxDuration = session.config.max_duration
-        }
-        if (totalDuration && maxDuration) {
-          return (
-            '(spent ' +
-            format.formatSeconds(totalDuration, 0, '') +
-            ' of the allowed ' +
-            format.formatSeconds(maxDuration, 0, '') +
-            ')'
-          )
-        }
-        break
-
-      default:
-    }
-
-    return ''
-  }
-
-  downloadLog = () => {
-    const auth =
-      this.state && this.state.data && this.state.data.auth
-        ? this.state.data.auth
-        : null
-    const sessionId = this.props.match.params.sessionId
-
-    Actions.downloadDblabSessionLog(auth?.token, sessionId)
-  }
-
-  downloadArtifact = (artifactType: string) => {
-    const auth =
-      this.state && this.state.data && this.state.data.auth
-        ? this.state.data.auth
-        : null
-    const sessionId = this.props.match.params.sessionId
-
-    Actions.downloadDblabSessionArtifact(auth?.token, sessionId, artifactType)
-  }
-
-  getArtifact = (artifactType: string) => {
-    const auth =
-      this.state && this.state.data && this.state.data.auth
-        ? this.state.data.auth
-        : null
-    const sessionId = this.props.match.params.sessionId
-
-    Actions.getDbLabSessionArtifact(auth?.token, sessionId, artifactType)
-  }
-
-  render() {
-    const that = this
-    const { classes, orgPermissions } = this.props
-    const sessionId = this.props.match.params.sessionId
-    const data =
-      this.state && this.state.data && this.state.data.dbLabSession
-        ? this.state.data.dbLabSession
-        : null
-    const title = 'Database Lab observed session #' + sessionId
-
-    const pageTitle = <ConsolePageTitle title={title} label={'Experimental'} />
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[
-          { name: 'Observed sessions', url: 'observed-sessions' },
-          { name: title, url: null },
-        ]}
-      />
-    )
-
-    if (orgPermissions && !orgPermissions.dblabSessionView) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <WarningWrapper>{messages.noPermissionPage}</WarningWrapper>
-        </div>
-      )
-    }
-
-    let errorWidget = null
-    if (this.state && this.state.data?.dbLabSession?.error) {
-      errorWidget = (
-        <ErrorWrapper
-          message={this.state.data.dbLabSession.errorMessage}
-          code={this.state.data.dbLabSession.errorCode}
-        />
-      )
-    }
-
-    if (
-      this.state &&
-      (this.state.data?.dbLabSession?.error ||
-        this.state.data?.dbLabInstanceStatus?.error)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          {errorWidget}
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (this.state &&
-        this.state.data &&
-        this.state.data?.dbLabSession?.isProcessing)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const session = data && data.data ? data.data : null
-    const logs = data && data.logs ? data.logs : null
-    const artifacts = data && data.artifacts ? data.artifacts : null
-    const artifactData = data && data.artifactData ? data.artifactData : null
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        <div>
-          <div className={classes.sectionHeader}>Summary</div>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Status:</span>
-            <DbLabStatusWrapper
-              session={{
-                status:
-                  session?.result && session.result.status
-                    ? session.result.status
-                    : 'processing',
-              }}
-            />
-          </Typography>
-
-          <div className={classes.summaryDivider} />
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Session:</span>
-            {session ? '#' + session.id : '-'}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Project:</span>
-            {session?.project_name ? session.project_name : '-'}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>DBLab instance:</span>
-            {session?.internal_instance_id ? (
-              <NavLink
-                to={urls.linkDbLabInstance(
-                  this.props as PropsType,
-                  session.internal_instance_id,
-                )}
-                className={classes.link}
-              >
-                {'#' + session.internal_instance_id}
-              </NavLink>
-            ) : (
-              ''
-            )}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>DBLab version:</span>
-            {session && session.tags && session.tags.dle_version
-              ? session.tags.dle_version
-              : '-'}
-          </Typography>
-
-          <div className={classes.summaryDivider} />
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Data state at:</span>
-            {session && session.tags && session.tags.data_state_at
-              ? session.tags.data_state_at
-              : '-'}
-          </Typography>
-
-          <div className={classes.summaryDivider} />
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Duration:</span>
-            {session &&
-            session.result &&
-            session.result.summary &&
-            session.result.summary.elapsed
-              ? session.result.summary.elapsed
-              : null}
-            {!(
-              session &&
-              session.result &&
-              session.result.summary &&
-              session.result.summary.elapsed
-            ) &&
-            session &&
-            session.duration > 0
-              ? format.formatSeconds(session.duration, 0, '')
-              : null}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Created:</span>
-            {session &&
-              formatDistanceToNowStrict(new Date(session.started_at), {
-                addSuffix: true,
-              })}
-          </Typography>
-
-          <div className={classes.summaryDivider} />
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Branch:</span>
-            {session &&
-            session.tags &&
-            session.tags.branch &&
-            session.tags.branch_link ? (
-              <GatewayLink href={session.tags.branch_link} target="_blank">
-                {session.tags.branch}
-              </GatewayLink>
-            ) : (
-              <span>
-                {session && session.tags && session.tags.branch
-                  ? session.tags.branch
-                  : '-'}
-              </span>
-            )}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Commit:</span>
-            {session &&
-            session.tags &&
-            session.tags.revision &&
-            session.tags.revision_link ? (
-              <GatewayLink href={session.tags.revision_link} target="_blank">
-                {session.tags.revision}
-              </GatewayLink>
-            ) : (
-              <span>
-                {session && session.tags && session.tags.revision
-                  ? session.tags.revision
-                  : '-'}
-              </span>
-            )}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Triggered by:</span>
-            {session &&
-            session.tags &&
-            session.tags.launched_by &&
-            session.tags.username_link ? (
-              <GatewayLink href={session.tags.username_link} target="_blank">
-                {session.tags.launched_by}
-                {session.tags.username_full
-                  ? ' (' + session.tags.username_full + ')'
-                  : ''}
-              </GatewayLink>
-            ) : (
-              <span>
-                {session && session.tags && session.tags.launched_by
-                  ? session.tags.launched_by
-                  : '-'}
-              </span>
-            )}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>PR/MR:</span>
-            {session && session.tags && session.tags.request_link ? (
-              <GatewayLink href={session.tags.request_link} target="_blank">
-                {session.tags.request_link}
-              </GatewayLink>
-            ) : (
-              '-'
-            )}
-          </Typography>
-
-          <Typography component="p">
-            <span className={classes.paramTitle}>Changes:</span>
-            {session && session.tags && session.tags.request_link ? (
-              <GatewayLink href={session.tags.request_link} target="_blank">
-                {session.tags.request_link}
-              </GatewayLink>
-            ) : (
-              '-'
-            )}
-          </Typography>
-
-          {false && (
-            <Typography component="p" style={{ marginTop: 10 }}>
-              Check documentation for the details about observed sessions:
-              <GatewayLink
-                href="https://postgres.ai/docs/guides/cloning/observe-sessions"
-                target="_blank"
-              >
-                Database Lab – CI Observer
-              </GatewayLink>
-            </Typography>
-          )}
-        </div>
-
-        <br />
-
-        <div>
-          <div className={classes.sectionHeader}>Checklist</div>
-
-          {session?.result &&
-          session.result.summary &&
-          session.result.summary.checklist ? (
-            <div>
-              {Object.keys(session.result.summary.checklist).map(function (
-                key,
-              ) {
-                const details = that.getCheckDetails(session, key)
-                return (
-                  <Typography component="div" className={classes.checkListItem}>
-                    <div className={classes.checkStatusColumn}>
-                      {session.result.summary?.checklist &&
-                      session.result.summary.checklist[key] ? (
-                        <DbLabStatusWrapper session={{ status: 'passed' }} />
-                      ) : (
-                        <DbLabStatusWrapper session={{ status: 'failed' }} />
-                      )}
-                    </div>
-                    <div
-                      className={classes.checkDescriptionColumn}
-                      style={{ marginTop: !details ? 10 : 0 }}
-                    >
-                      {format.formatDbLabSessionCheck(key)}
-                      <div className={classes.checkDetails}>{details}</div>
-                    </div>
-                  </Typography>
-                )
-              })}
-            </div>
-          ) : (
-            icons.processingLargeIcon
-          )}
-        </div>
-
-        <br />
-
-        <div>
-          <div className={classes.sectionHeader}>
-            Observed intervals and details
-          </div>
-          <ExpansionPanel
-            className={classes.expansionPanel}
-            onChange={(event, expanded) => {
-              that.setState({ intervalsExpanded: expanded })
-            }}
-          >
-            <ExpansionPanelSummary
-              expandIcon={<ExpandMoreIcon />}
-              aria-controls="panel1b-content"
-              id="panel1b-header"
-              className={classes.expansionPanelSummary}
-            >
-              {that.state.intervalsExpanded
-                ? 'Hide intervals'
-                : 'Show intervals'}
-            </ExpansionPanelSummary>
-            <ExpansionPanelDetails className={classes.expansionPanelDetails}>
-              {session?.result &&
-              session.result?.intervals &&
-              session.result.intervals.length > 0 ? (
-                <div style={{ width: '100%' }}>
-                  <div className={classes.intervalsRow}>
-                    <div className={classes.intervalIcon} />
-                    <div className={classes.intervalStarted}>Started at</div>
-                    <div className={classes.intervalDuration}>Duration</div>
-                  </div>
-                  {session.result.intervals.map((i) => {
-                    return (
-                      <div className={classes.intervalsRow}>
-                        <div>
-                          <div className={classes.intervalIcon}>
-                            {i.warning
-                              ? icons.intervalWarning
-                              : icons.intervalOk}
-                          </div>
-                          <div className={classes.intervalStarted}>
-                            {format.formatTimestampUtc(i.started_at)}
-                          </div>
-                          <div className={classes.intervalDuration}>
-                            {format.formatSeconds(i.duration, 0, '')}
-                          </div>
-                        </div>
-                        {i.warning && (
-                          <div style={{ paddingLeft: 25 }}>
-                            <div className={classes.intervalWarning}>
-                              <TextField
-                                variant="outlined"
-                                id={'warning' + i.started_at}
-                                multiline
-                                fullWidth
-                                value={i.warning}
-                                className={classes.code}
-                                margin="normal"
-                                InputProps={{
-                                  readOnly: true,
-                                }}
-                              />
-                            </div>
-                          </div>
-                        )}
-                      </div>
-                    )
-                  })}
-                </div>
-              ) : (
-                <Typography component="p">
-                  <span className={classes.paramTitle}>Not specified</span>
-                </Typography>
-              )}
-            </ExpansionPanelDetails>
-          </ExpansionPanel>
-        </div>
-
-        <br />
-
-        <div>
-          <div className={classes.sectionHeader}>Configuration</div>
-
-          <ExpansionPanel
-            className={classes.expansionPanel}
-            onChange={(_, expanded) => {
-              that.setState({ configurationExpanded: expanded })
-            }}
-          >
-            <ExpansionPanelSummary
-              expandIcon={<ExpandMoreIcon />}
-              aria-controls="panel1b-content"
-              id="panel1b-header"
-              className={classes.expansionPanelSummary}
-            >
-              {that.state.configurationExpanded
-                ? 'Hide configuration'
-                : 'Show  configuration'}
-            </ExpansionPanelSummary>
-            <ExpansionPanelDetails className={classes.expansionPanelDetails}>
-              {session?.config ? (
-                <div>
-                  {Object.keys(session.config).map(function (key) {
-                    return (
-                      <Typography component="p" className={classes.cfgListItem}>
-                        <span className={classes.paramTitle}>{key}:</span>
-                        {session.config && session.config[key]}
-                      </Typography>
-                    )
-                  })}
-                </div>
-              ) : (
-                <Typography component="p">
-                  <span className={classes.paramTitle}>Not specified</span>
-                </Typography>
-              )}
-            </ExpansionPanelDetails>
-          </ExpansionPanel>
-        </div>
-
-        <br />
-
-        <br />
-
-        <div>
-          <div
-            className={classes.sectionHeader}
-            style={{ marginBottom: '0px' }}
-          >
-            Artifacts
-          </div>
-
-          {Array.isArray(artifacts) && artifacts.length ? (
-            <HorizontalScrollContainer>
-              <Table className={classes.table}>
-                <TableBody>
-                  {artifacts.map((a) => {
-                    return (
-                      <TableRow
-                        className={classes.row}
-                        key={a.dblab_session_id + '-' + a.artifact_type}
-                        onClick={() => {
-                          if (
-                            orgPermissions &&
-                            !orgPermissions.dblabSessionArtifactsView
-                          ) {
-                            return
-                          }
-                          const artifactsExpanded =
-                            that.state.artifactsExpanded || {}
-                          artifactsExpanded[a.artifact_type] =
-                            !artifactsExpanded[a.artifact_type]
-                          that.setState({
-                            artifactsExpanded: artifactsExpanded,
-                          })
-                          if (
-                            artifactsExpanded[a.artifact_type] &&
-                            a.artifact_type !== 'log' &&
-                            (!data.artifactsData ||
-                              (data.artifactsData &&
-                                !data.artifactsData[a.artifact_type]))
-                          ) {
-                            this.getArtifact(a.artifact_type)
-                          }
-                        }}
-                      >
-                        <TableCell className={classes.artifactRow}>
-                          <div>
-                            <div className={classes.artifactName}>
-                              {a.artifact_type}&nbsp;
-                              {orgPermissions &&
-                                orgPermissions.dblabSessionArtifactsView && (
-                                  <span
-                                    className={
-                                      that.state &&
-                                      typeof that.state.artifactsExpanded !==
-                                        'undefined' &&
-                                      that.state.artifactsExpanded[
-                                        a.artifact_type
-                                      ] === true
-                                        ? classes.rotate180Icon
-                                        : classes.rotate0Icon
-                                    }
-                                  >
-                                    {icons.detailsArrow}
-                                  </span>
-                                )}
-                            </div>
-                            <div className={classes.artifactDescription}>
-                              {dblabutils.getArtifactDescription(
-                                a.artifact_type,
-                              )}
-                            </div>
-                            <div className={classes.artifactSize}>
-                              {format.formatBytes(a.artifact_size, 0, true)}
-                            </div>
-                            {orgPermissions &&
-                              orgPermissions.dblabSessionArtifactsView && (
-                                <div className={classes.artifactAction}>
-                                  <Button
-                                    variant="outlined"
-                                    color="secondary"
-                                    className={classes.button}
-                                    disabled={a.artifact_size < 10}
-                                    onClick={(event) => {
-                                      event.stopPropagation()
-                                      if (a.artifact_type === 'log') {
-                                        this.downloadLog()
-                                      } else {
-                                        this.downloadArtifact(a.artifact_type)
-                                      }
-                                    }}
-                                  >
-                                    {data &&
-                                    ((data.isArtifactDownloading &&
-                                      data.downloadingArtifactType ===
-                                        a.artifact_type) ||
-                                      (a.artifact_type === 'log' &&
-                                        data.isLogDownloading)) ? (
-                                      <span>
-                                        &nbsp;
-                                        <Spinner />
-                                      </span>
-                                    ) : (
-                                      <span>
-                                        {a.artifact_size < 10
-                                          ? icons.disabledDownloadIcon
-                                          : icons.downloadIcon}
-                                      </span>
-                                    )}
-                                    &nbsp;&nbsp;Download
-                                  </Button>
-                                </div>
-                              )}
-                          </div>
-                          <div>
-                            <ExpansionPanel
-                              square
-                              className={classes.artifactExpansionPanel}
-                              expanded={
-                                that.state &&
-                                typeof that.state.artifactsExpanded !==
-                                  'undefined' &&
-                                that.state.artifactsExpanded[
-                                  a.artifact_type
-                                ] === true
-                              }
-                            >
-                              <ExpansionPanelSummary
-                                expandIcon={<ExpandMoreIcon />}
-                                aria-controls="panel1b-content"
-                                id="panel1b-header"
-                                className={
-                                  classes.artifactExpansionPanelSummary
-                                }
-                              >
-                                {that.state.logsExpanded
-                                  ? 'Hide log'
-                                  : 'Show  log'}
-                              </ExpansionPanelSummary>
-                              <ExpansionPanelDetails
-                                className={
-                                  classes.artifactsExpansionPanelDetails
-                                }
-                              >
-                                {a.artifact_type === 'log' ? (
-                                  <div>
-                                    {Array.isArray(logs) && logs.length ? (
-                                      <div
-                                        id="logs-container"
-                                        className={classes.logContainer}
-                                      >
-                                        {logs.map((r) => {
-                                          return (
-                                            <div>
-                                              {r.log_time},{r.user_name},
-                                              {r.database_name},{r.process_id},
-                                              {r.connection_from},{r.session_id}
-                                              ,{r.session_line_num},
-                                              {r.command_tag},
-                                              {r.session_start_time},
-                                              {r.virtual_transaction_id},
-                                              {r.transaction_id},
-                                              {r.error_severity},
-                                              {r.sql_state_code},{r.message},
-                                              {r.detail},{r.hint},
-                                              {r.internal_query},
-                                              {r.internal_query_pos},{r.context}
-                                              ,{r.query},{r.query_pos},
-                                              {r.location},{r.application_name},
-                                              {r.backend_type}
-                                            </div>
-                                          )
-                                        })}
-                                        <div
-                                          className={classes.showMoreContainer}
-                                        >
-                                          {data && data.isLogsProcessing && (
-                                            <Spinner
-                                              size="lg"
-                                              className={classes.progress}
-                                            />
-                                          )}
-                                          {data &&
-                                            !data.isLogsProcessing &&
-                                            !data.isLogsComplete && (
-                                              <Button
-                                                variant="outlined"
-                                                color="secondary"
-                                                className={classes.button}
-                                                onClick={() => this.showMore()}
-                                                disabled={
-                                                  data && data.isLogsProcessing
-                                                }
-                                              >
-                                                Show more
-                                              </Button>
-                                            )}
-                                        </div>
-                                      </div>
-                                    ) : (
-                                      'No log uploaded yet.'
-                                    )}
-                                  </div>
-                                ) : (
-                                  <div>
-                                    {artifactData &&
-                                    artifactData[a.artifact_type] &&
-                                    artifactData[a.artifact_type]
-                                      .isProcessing ? (
-                                      <Spinner
-                                        size="lg"
-                                        className={classes.progress}
-                                      />
-                                    ) : null}
-                                    {artifactData &&
-                                    artifactData[a.artifact_type] &&
-                                    artifactData[a.artifact_type].isProcessed &&
-                                    artifactData[a.artifact_type].data ? (
-                                      <div
-                                        id="artifact-container"
-                                        className={classes.artifactContainer}
-                                      >
-                                        {artifactData[a.artifact_type].data}
-                                      </div>
-                                    ) : null}
-                                  </div>
-                                )}
-                              </ExpansionPanelDetails>
-                            </ExpansionPanel>
-                          </div>
-                        </TableCell>
-                      </TableRow>
-                    )
-                  })}
-                </TableBody>
-              </Table>
-            </HorizontalScrollContainer>
-          ) : (
-            <span>
-              {data.isArtifactsProcessed ? 'Artifacts not found' : ''}
-              {data.isArtifactsProcessing ? (
-                <Spinner size="lg" className={classes.progress} />
-              ) : null}
-            </span>
-          )}
-        </div>
-
-        <div className={classes.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default DbLabSession
diff --git a/ui/packages/platform/src/components/DbLabSession/DbLabSessionWrapper.tsx b/ui/packages/platform/src/components/DbLabSession/DbLabSessionWrapper.tsx
deleted file mode 100644
index 781de3b8..00000000
--- a/ui/packages/platform/src/components/DbLabSession/DbLabSessionWrapper.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import DbLabSession from 'components/DbLabSession/DbLabSession'
-import { OrgPermissions } from 'components/types'
-import { RouteComponentProps } from 'react-router'
-
-interface MatchParams {
-  sessionId: string
-}
-
-export interface DbLabSessionProps extends RouteComponentProps<MatchParams> {
-  orgPermissions: OrgPermissions
-}
-
-export interface ErrorProps {
-  code?: number
-  message?: string
-}
-
-export const DbLabSessionWrapper = (props: DbLabSessionProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        ...(styles.root as Object),
-        flex: '1 1 100%',
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      summary: {
-        marginTop: 20,
-        marginBottom: 20,
-      },
-      paramTitle: {
-        display: 'inline-block',
-        width: 200,
-      },
-      sectionHeader: {
-        fontWeight: 600,
-        display: 'block',
-        paddingBottom: 10,
-        marginBottom: 10,
-        borderBottom: '1px solid ' + colors.consoleStroke,
-      },
-      logContainer: {
-        backgroundColor: 'black',
-        color: 'white',
-        fontFamily:
-          '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-          ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-        fontSize: '13px',
-        maxHeight: 'calc(100vh - 120px)',
-        overflowY: 'auto',
-        width: '100%',
-        '& > div': {
-          overflowWrap: 'anywhere',
-        },
-      },
-      artifactContainer: {
-        backgroundColor: 'black',
-        color: 'white',
-        fontFamily:
-          '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-          ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-        fontSize: '13px',
-        maxHeight: 'calc(100vh - 300px)',
-        width: '100%',
-        whiteSpace: 'break-spaces',
-        overflowWrap: 'anywhere',
-        overflow: 'auto',
-      },
-      showMoreContainer: {
-        marginTop: 20,
-        textAlign: 'center',
-      },
-      link: {
-        color: colors.secondary2.main,
-        '&:visited': {
-          color: colors.secondary2.main,
-        },
-        '&:hover': {
-          color: colors.secondary2.main,
-        },
-        '&:active': {
-          color: colors.secondary2.main,
-        },
-      },
-      checkStatusColumn: {
-        display: 'block',
-        width: 80,
-        marginTop: 10,
-        height: 30,
-        float: 'left',
-      },
-      checkDescriptionColumn: {
-        display: 'inline-block',
-      },
-      checkDetails: {
-        clear: 'both',
-        display: 'block',
-        color: colors.pgaiDarkGray,
-      },
-      checkListItem: {
-        marginBottom: 10,
-        minHeight: 30,
-      },
-      cfgListItem: {
-        marginBottom: 5,
-      },
-      expansionPanel: {
-        marginTop: '5px!important',
-        borderRadius: '0px!important',
-      },
-      expansionPanelSummary: {
-        display: 'inline-block',
-        padding: '5px',
-        paddingLeft: '12px',
-        minHeight: '30px',
-        lineHeight: '30px',
-        width: '100%',
-        '& .MuiExpansionPanelSummary-content': {
-          margin: '0px',
-          display: 'inline-block',
-        },
-        '&.Mui-expanded': {
-          minHeight: '22px',
-        },
-        '& .MuiExpansionPanelSummary-expandIcon': {
-          display: 'inline-block',
-          padding: '0px',
-          marginTop: '-1px',
-        },
-      },
-      expansionPanelDetails: {
-        padding: '12px',
-        paddingTop: '0px',
-        [theme.breakpoints.down('md')]: {
-          display: 'block',
-        },
-      },
-      intervalsRow: {
-        borderBottom: '1px solid ' + colors.consoleStroke,
-        width: '100%',
-        lineHeight: '22px',
-        '&:last-child': {
-          borderBottom: 'none',
-        },
-      },
-      intervalIcon: {
-        display: 'inline-block',
-        width: 25,
-      },
-      intervalStarted: {
-        display: 'inline-block',
-        width: 200,
-      },
-      intervalDuration: {
-        display: 'inline-block',
-      },
-      intervalWarning: {
-        display: 'inline-block',
-        width: '100%',
-      },
-      code: {
-        width: '100%',
-        'margin-top': 0,
-        '& > div': {
-          paddingTop: 8,
-          padding: 8,
-        },
-        'background-color': 'rgb(246, 248, 250)',
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: '12px',
-        },
-      },
-      button: {
-        marginTop: 5,
-        marginBottom: 10,
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-      artifactRow: {
-        padding: '5px',
-        cursor: 'pointer',
-        [theme.breakpoints.down('sm')]: {
-          paddingLeft: '0px',
-          paddingRight: '0px',
-          paddingTop: '10px',
-        },
-      },
-      artifactName: {
-        display: 'inline-block',
-        width: '20%',
-        [theme.breakpoints.down('sm')]: {
-          display: 'block',
-          width: '100%',
-          marginBottom: '10px',
-        },
-        '& svg': {
-          verticalAlign: 'middle',
-        },
-      },
-      artifactDescription: {
-        display: 'inline-block',
-        width: '40%',
-        [theme.breakpoints.down('sm')]: {
-          display: 'block',
-          width: '100%',
-          marginBottom: '10px',
-        },
-      },
-      artifactSize: {
-        display: 'inline-block',
-        width: '20%',
-        [theme.breakpoints.down('sm')]: {
-          display: 'block',
-          width: '100%',
-          marginBottom: '10px',
-        },
-      },
-      artifactAction: {
-        display: 'inline-block',
-        width: '20%',
-        textAlign: 'right',
-        '& button': {
-          marginBottom: '5px',
-        },
-        [theme.breakpoints.down('sm')]: {
-          display: 'block',
-          width: '100%',
-        },
-      },
-      artifactExpansionPanel: {
-        padding: '0px!important',
-        boxShadow: 'none',
-      },
-      artifactExpansionPanelSummary: {
-        display: 'none',
-        minHeight: '0px!important',
-      },
-      artifactsExpansionPanelDetails: {
-        padding: '0px!important',
-      },
-      summaryDivider: {
-        minHeight: '10px',
-      },
-      rotate180Icon: {
-        '& svg': {
-          transform: 'rotate(180deg)',
-        },
-      },
-      rotate0Icon: {
-        '& svg': {
-          transform: 'rotate(0deg)',
-        },
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <DbLabSession {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DbLabSessions/DbLabSessions.tsx b/ui/packages/platform/src/components/DbLabSessions/DbLabSessions.tsx
deleted file mode 100644
index fdbbc1a6..00000000
--- a/ui/packages/platform/src/components/DbLabSessions/DbLabSessions.tsx
+++ /dev/null
@@ -1,404 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, MouseEvent } from 'react'
-import { RouteComponentProps } from 'react-router'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  Button,
-} from '@material-ui/core'
-import { formatDistanceToNowStrict } from 'date-fns'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-import { DbLabStatusWrapper } from 'components/DbLabStatus/DbLabStatusWrapper'
-
-import ConsolePageTitle from './../ConsolePageTitle'
-import format from '../../utils/format'
-
-interface DbLabSessionsProps {
-  classes: ClassesType
-  org: string | number
-  orgId: number
-  history: RouteComponentProps['history']
-}
-
-interface DbLabSessionsState {
-  data: {
-    auth: {
-      token: string
-    } | null
-    dbLabSessions: {
-      isProcessing: boolean
-      isProcessed: boolean
-      isComplete: boolean
-      error: boolean
-      data: {
-        id: number
-        duration: number
-        started_at: string
-        tags: {
-          instance_id: string
-          branch: string
-          revision: string
-          launched_by: string
-          project_id: number
-        }
-        result: {
-          status: string
-          summary: {
-            checklist: { [list: string]: { string: string | boolean } }
-            elapsed: string
-          }
-        }
-      }[]
-    } | null
-  } | null
-}
-
-const PAGE_SIZE = 20
-
-class DbLabSessions extends Component<DbLabSessionsProps, DbLabSessionsState> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const { orgId } = this.props
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const sessions =
-        this.data && this.data.dbLabSessions ? this.data.dbLabSessions : null
-
-      if (
-        auth &&
-        auth.token &&
-        !sessions?.isProcessing &&
-        !sessions?.error &&
-        !that.state
-      ) {
-        Actions.getDbLabSessions(auth.token, { orgId, limit: PAGE_SIZE })
-      }
-
-      that.setState({ data: this.data })
-    })
-
-    const contentContainer = document.getElementById('content-container')
-    if (contentContainer) {
-      contentContainer.addEventListener('scroll', () => {
-        if (
-          contentContainer.scrollTop >=
-          contentContainer.scrollHeight - contentContainer.offsetHeight
-        ) {
-          this.showMore()
-        }
-      })
-    }
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  onSessionClick(
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    sessionId: string | number,
-  ) {
-    const { org } = this.props
-
-    this.props.history.push('/' + org + '/observed-sessions/' + sessionId)
-  }
-
-  formatStatus(status: string) {
-    const { classes } = this.props
-    let icon = null
-    let className = null
-    let label = status
-    if (status.length) {
-      label = status.charAt(0).toUpperCase() + status.slice(1)
-    }
-
-    switch (status) {
-      case 'passed':
-        icon = icons.okIcon
-        className = classes.passedStatus
-        break
-      case 'failed':
-        icon = icons.failedIcon
-        className = classes.failedStatus
-        break
-      default:
-        icon = icons.processingIcon
-        className = classes.processingStatus
-    }
-
-    return (
-      <div className={className}>
-        <span style={{ whiteSpace: 'nowrap' }}>
-          {icon}&nbsp;{label}
-        </span>
-      </div>
-    )
-  }
-
-  showMore() {
-    const { orgId } = this.props
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const sessions =
-      this.state.data && this.state.data.dbLabSessions
-        ? this.state.data.dbLabSessions
-        : null
-    let lastId = null
-
-    if (sessions && sessions.data && sessions.data.length) {
-      lastId = sessions.data[sessions.data.length - 1].id
-    }
-
-    if (auth && auth.token && !sessions?.isProcessing && lastId) {
-      Actions.getDbLabSessions(auth.token, {
-        orgId,
-        limit: PAGE_SIZE,
-        lastId,
-      })
-    }
-  }
-
-  render() {
-    const { classes, org } = this.props
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={org}
-        breadcrumbs={[{ name: 'Database Lab observed sessions', url: null }]}
-      />
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title="Database Lab observed sessions"
-        label={'Experimental'}
-      />
-    )
-
-    if (!this.state || !this.state.data) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const sessionsStore =
-      (this.state.data && this.state.data.dbLabSessions) || null
-    const sessions = (sessionsStore && sessionsStore.data) || []
-
-    if (sessionsStore && sessionsStore.error) {
-      return (
-        <div>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (!sessionsStore || !sessionsStore.data) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-        {pageTitle}
-
-        {sessions && sessions.length > 0 ? (
-          <div>
-            <HorizontalScrollContainer>
-              <Table className={classes.table}>
-                <TableHead>
-                  <TableCell className={classes.tableHead}>Status</TableCell>
-                  <TableCell className={classes.tableHead}>Session</TableCell>
-                  <TableCell className={classes.tableHead}>
-                    Project/Instance
-                  </TableCell>
-                  <TableCell className={classes.tableHead}>Commit</TableCell>
-                  <TableCell className={classes.tableHead}>Checklist</TableCell>
-                  <TableCell className={classes.tableHead}>&nbsp;</TableCell>
-                </TableHead>
-
-                <TableBody>
-                  {sessions.map((s) => {
-                    if (s) {
-                      return (
-                        <TableRow
-                          hover={false}
-                          className={classes.row}
-                          key={s.id}
-                          onClick={(event) => {
-                            this.onSessionClick(event, s.id)
-                            return false
-                          }}
-                          style={{ cursor: 'pointer' }}
-                        >
-                          <TableCell className={classes.tableCell}>
-                            <DbLabStatusWrapper
-                              session={{
-                                status:
-                                  s.result && s.result.status
-                                    ? s.result.status
-                                    : 'processing',
-                              }}
-                            />
-                          </TableCell>
-                          <TableCell className={classes.tableCell}>
-                            #{s.id}
-                          </TableCell>
-                          <TableCell className={classes.tableCell}>
-                            {s.tags && s.tags.project_id
-                              ? s.tags.project_id
-                              : '-'}
-                            /
-                            {s.tags && s.tags.instance_id
-                              ? s.tags.instance_id
-                              : '-'}
-                          </TableCell>
-                          <TableCell className={classes.tableCell}>
-                            {icons.branch}&nbsp;
-                            {s.tags && s.tags.branch && s.tags.revision
-                              ? s.tags.branch + '/' + s.tags.revision
-                              : '-'}
-                          </TableCell>
-                          <TableCell className={classes.tableCell}>
-                            {s.result &&
-                            s.result.summary &&
-                            s.result.summary.checklist ? (
-                              <div>
-                                {Object.keys(s.result.summary.checklist).map(
-                                  function (key) {
-                                    return (
-                                      <span>
-                                        {s.result?.summary?.checklist &&
-                                        s.result.summary.checklist[key]
-                                          ? icons.okLargeIcon
-                                          : icons.failedLargeIcon}
-                                        &nbsp;
-                                      </span>
-                                    )
-                                  },
-                                )}
-                              </div>
-                            ) : (
-                              icons.processingLargeIcon
-                            )}
-                          </TableCell>
-                          <TableCell className={classes.tableCell}>
-                            <div>
-                              {s.duration > 0 ||
-                              (s.result &&
-                                s.result.summary &&
-                                s.result.summary.elapsed) ? (
-                                <span>
-                                  {icons.timer}&nbsp;
-                                  {s.result &&
-                                  s.result.summary &&
-                                  s.result.summary.elapsed
-                                    ? s.result.summary.elapsed
-                                    : format.formatSeconds(s.duration, 0, '')}
-                                </span>
-                              ) : (
-                                '-'
-                              )}
-                            </div>
-                            <div>
-                              {icons.calendar}&nbsp;created&nbsp;
-                              {formatDistanceToNowStrict(
-                                new Date(s.started_at),
-                                { addSuffix: true },
-                              )}
-                              {s.tags && s.tags.launched_by ? (
-                                <span> by {s.tags.launched_by}</span>
-                              ) : (
-                                ''
-                              )}
-                            </div>
-                          </TableCell>
-                        </TableRow>
-                      )
-                    }
-
-                    return null
-                  })}
-                </TableBody>
-              </Table>
-            </HorizontalScrollContainer>
-            <div className={classes.showMoreContainer}>
-              {sessionsStore && sessionsStore.isProcessing && (
-                <Spinner className={classes.progress} />
-              )}
-              {sessionsStore &&
-                !sessionsStore.isProcessing &&
-                !sessionsStore.isComplete && (
-                  <Button
-                    variant="outlined"
-                    color="secondary"
-                    className={classes.button}
-                    onClick={() => this.showMore()}
-                    disabled={sessionsStore && sessionsStore.isProcessing}
-                  >
-                    Show more
-                  </Button>
-                )}
-            </div>
-          </div>
-        ) : (
-          <>
-            {sessions && sessions.length === 0 && sessionsStore.isProcessed && (
-              <StubContainer>
-                <ProductCardWrapper
-                  inline
-                  title={'There are no Database Lab observed sessions yet.'}
-                  icon={icons.databaseLabLogo}
-                />
-              </StubContainer>
-            )}
-          </>
-        )}
-      </div>
-    )
-  }
-}
-
-export default DbLabSessions
diff --git a/ui/packages/platform/src/components/DbLabSessions/DbLabSessionsWrapper.tsx b/ui/packages/platform/src/components/DbLabSessions/DbLabSessionsWrapper.tsx
deleted file mode 100644
index 9eae282a..00000000
--- a/ui/packages/platform/src/components/DbLabSessions/DbLabSessionsWrapper.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import DbLabSessions from 'components/DbLabSessions/DbLabSessions'
-
-interface DbLabSessionsProps {
-  org: string | number
-  orgId: number
-  history: RouteComponentProps['history']
-}
-
-export const DbLabSessionsWrapper = (props: DbLabSessionsProps) => {
-  const useStyles = makeStyles(
-    {
-      root: {
-        ...(styles.root as Object),
-        paddingBottom: '20px',
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      tableHead: {
-        ...(styles.tableHead as Object),
-        textAlign: 'left',
-      },
-      tableCell: {
-        textAlign: 'left',
-      },
-      showMoreContainer: {
-        marginTop: 20,
-        textAlign: 'center',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <DbLabSessions {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DbLabStatus/DbLabStatus.tsx b/ui/packages/platform/src/components/DbLabStatus/DbLabStatus.tsx
deleted file mode 100644
index ac1d51e9..00000000
--- a/ui/packages/platform/src/components/DbLabStatus/DbLabStatus.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import Brightness1Icon from '@material-ui/icons/Brightness1'
-import Tooltip from '@material-ui/core/Tooltip'
-
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-
-import Format from '../../utils/format'
-import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
-import { DbLabStatusProps } from 'components/DbLabStatus/DbLabStatusWrapper'
-
-export interface DbLabStatusInstance {
-  state: {
-    status: {
-      code: string
-      message: string
-    }
-  }
-}
-interface DbLabStatusWithStylesProps extends DbLabStatusProps {
-  classes: ClassesType
-}
-
-class DbLabStatus extends Component<DbLabStatusWithStylesProps> {
-  getCloneStatus = (
-    clone: Clone,
-    onlyText: boolean,
-    showDescription: boolean,
-  ) => {
-    const { classes } = this.props
-    let className = classes?.cloneReadyStatus
-
-    if (!clone.status) {
-      return null
-    }
-
-    switch (clone.status.code) {
-      case 'OK':
-        className = classes?.cloneReadyStatus
-        break
-      case 'CREATING':
-        className = classes?.cloneCreatingStatus
-        break
-      case 'DELETING':
-        className = classes?.cloneDeletingStatus
-        break
-      case 'RESETTING':
-        className = classes?.cloneResettingStatus
-        break
-      case 'FATAL':
-        className = classes?.cloneFatalStatus
-        break
-      default:
-        break
-    }
-
-    if (onlyText && showDescription) {
-      return (
-        <span>
-          <span style={{ display: 'block' }} className={className}>
-            <Brightness1Icon className={className} />
-            &nbsp;
-            {Format.formatStatus(clone.status.code)}
-          </span>
-          <span style={{ display: 'block' }}>
-            {clone.status.message && clone.status.message.length > 100 ? (
-              <Tooltip title={''} classes={{ tooltip: classes?.toolTip }}>
-                <span>{Format.limitStr(clone.status.message, 100)}</span>
-              </Tooltip>
-            ) : (
-              clone.status.message
-            )}
-          </span>
-        </span>
-      )
-    }
-
-    if (onlyText && !showDescription) {
-      return (
-        <span className={className} title={clone.status.message}>
-          <Tooltip
-            title={clone.status.message}
-            classes={{ tooltip: classes?.toolTip }}
-          >
-            <Brightness1Icon className={className} />
-          </Tooltip>
-          &nbsp;
-          {Format.formatStatus(clone.status.code)}
-        </span>
-      )
-    }
-
-    return (
-      <Tooltip
-        title={clone.status.message}
-        classes={{ tooltip: classes?.toolTip }}
-      >
-        <Brightness1Icon className={className} />
-      </Tooltip>
-    )
-  }
-
-  getInstanceStatus = (instance: DbLabStatusInstance, onlyText: boolean) => {
-    const { classes } = this.props
-    let className = classes?.instanceReadyStatus
-
-    if (!instance.state) {
-      return null
-    }
-
-    if (!instance.state.status) {
-      return null
-    }
-    switch (instance.state.status.code) {
-      case 'OK':
-        className = classes?.instanceReadyStatus
-        break
-      case 'WARNING':
-        className = classes?.instanceWarningStatus
-        break
-      case 'NO_RESPONSE':
-        className = classes?.instanceNoResponseStatus
-        break
-      default:
-        break
-    }
-
-    if (onlyText) {
-      return (
-        <span className={className} title={instance.state.status.message}>
-          <Tooltip
-            title={instance.state.status.message}
-            classes={{ tooltip: classes?.toolTip }}
-          >
-            <Brightness1Icon className={className} />
-          </Tooltip>
-          &nbsp;
-          {Format.formatStatus(instance.state.status.code)}
-        </span>
-      )
-    }
-
-    return (
-      <Tooltip
-        title={instance.state.status.message}
-        classes={{ tooltip: classes?.toolTip }}
-      >
-        <Brightness1Icon className={className} />
-      </Tooltip>
-    )
-  }
-
-  getSessionStatus = (session: { status: string }) => {
-    const { classes } = this.props
-    let icon = null
-    let className = null
-    let label = session.status
-    if (session.status.length) {
-      label = session.status.charAt(0).toUpperCase() + session.status.slice(1)
-    }
-
-    switch (session.status) {
-      case 'passed':
-        icon = icons.okIconWhite
-        className = classes?.sessionPassedStatus
-        break
-      case 'failed':
-        icon = icons.failedIconWhite
-        className = classes?.sessionFailedStatus
-        break
-      default:
-        icon = icons.processingIconWhite
-        className = classes?.sessionProcessingStatus
-    }
-
-    return (
-      <div className={className}>
-        <span style={{ whiteSpace: 'nowrap' }}>
-          {icon}
-          {label}
-        </span>
-      </div>
-    )
-  }
-
-  render() {
-    const { onlyText, showDescription, instance, clone, session } = this.props
-
-    if (clone) {
-      return this.getCloneStatus(
-        clone,
-        onlyText as boolean,
-        showDescription as boolean,
-      )
-    }
-
-    if (instance) {
-      return this.getInstanceStatus(instance, onlyText as boolean)
-    }
-
-    if (session) {
-      return this.getSessionStatus(session)
-    }
-
-    return null
-  }
-}
-
-export default DbLabStatus
diff --git a/ui/packages/platform/src/components/DbLabStatus/DbLabStatusWrapper.tsx b/ui/packages/platform/src/components/DbLabStatus/DbLabStatusWrapper.tsx
deleted file mode 100644
index b4c20e89..00000000
--- a/ui/packages/platform/src/components/DbLabStatus/DbLabStatusWrapper.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import DbLabStatus, {
-  DbLabStatusInstance,
-} from 'components/DbLabStatus/DbLabStatus'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
-
-export interface DbLabStatusProps {
-  session?: { status: string }
-  onlyText?: boolean
-  showDescription?: boolean
-  instance?: DbLabStatusInstance
-  clone?: Clone
-}
-
-export const DbLabStatusWrapper = (props: DbLabStatusProps) => {
-  const useStyles = makeStyles(
-    {
-      cloneReadyStatus: {
-        color: colors.state.ok,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      cloneCreatingStatus: {
-        color: colors.state.processing,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      cloneResettingStatus: {
-        color: colors.state.processing,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      cloneDeletingStatus: {
-        color: colors.state.warning,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      cloneFatalStatus: {
-        color: colors.state.error,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      instanceReadyStatus: {
-        color: colors.state.ok,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      instanceWarningStatus: {
-        color: colors.state.warning,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      instanceNoResponseStatus: {
-        color: colors.state.error,
-        fontSize: '1.1em',
-        verticalAlign: 'middle',
-        '& svg': {
-          marginTop: '-3px',
-        },
-      },
-      toolTip: {
-        fontSize: '10px!important',
-      },
-      sessionPassedStatus: {
-        display: 'inline-block',
-        border: '1px solid ' + colors.state.ok,
-        fontSize: '12px',
-        color: '#FFFFFF',
-        backgroundColor: colors.state.ok,
-        padding: '3px',
-        paddingLeft: '5px',
-        paddingRight: '5px',
-        borderRadius: 3,
-        lineHeight: '14px',
-        '& svg': {
-          width: 10,
-          height: 10,
-          marginBottom: '-1px',
-          marginRight: '5px',
-        },
-      },
-      sessionFailedStatus: {
-        display: 'inline-block',
-        border: '1px solid ' + colors.state.error,
-        fontSize: '12px',
-        color: '#FFFFFF',
-        backgroundColor: colors.state.error,
-        padding: '3px',
-        paddingLeft: '5px',
-        paddingRight: '5px',
-        borderRadius: 3,
-        lineHeight: '14px',
-        '& svg': {
-          width: 10,
-          height: 10,
-          marginBottom: '-1px',
-          marginRight: '5px',
-        },
-      },
-      sessionProcessingStatus: {
-        display: 'inline-block',
-        border: '1px solid ' + colors.state.processing,
-        fontSize: '12px',
-        color: '#FFFFFF',
-        backgroundColor: colors.state.processing,
-        padding: '3px',
-        paddingLeft: '5px',
-        paddingRight: '5px',
-        borderRadius: 3,
-        lineHeight: '14px',
-        '& svg': {
-          width: 10,
-          height: 10,
-          marginBottom: '-1px',
-          marginRight: '5px',
-        },
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <DbLabStatus {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/DisplayToken/DisplayToken.tsx b/ui/packages/platform/src/components/DisplayToken/DisplayToken.tsx
deleted file mode 100644
index 8c08e09f..00000000
--- a/ui/packages/platform/src/components/DisplayToken/DisplayToken.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { InputAdornment } from '@material-ui/core'
-import { IconButton, TextField } from '@material-ui/core'
-
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { icons } from '@postgres.ai/shared/styles/icons'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-
-interface DisplayTokenProps {
-  classes: ClassesType
-}
-
-interface DisplayTokenState {
-  data: {
-    tokenRequest: {
-      isProcessed: boolean
-      error: boolean
-      data: {
-        name: string
-        expires_at: string
-        token: string
-      }
-    } | null
-  } | null
-}
-
-class DisplayToken extends Component<DisplayTokenProps, DisplayTokenState> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-
-    document.getElementsByTagName('html')[0].style.overflow = 'hidden'
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  copyToken = () => {
-    const copyText = document.getElementById(
-      'generatedToken',
-    ) as HTMLInputElement
-
-    if (copyText) {
-      copyText.select()
-      copyText.setSelectionRange(0, 99999)
-      document.execCommand('copy')
-    }
-  }
-
-  render() {
-    const { classes } = this.props
-    const tokenRequest =
-      this.state && this.state.data && this.state.data.tokenRequest
-        ? this.state.data.tokenRequest
-        : null
-    let tokenDisplay = null
-
-    if (
-      tokenRequest &&
-      tokenRequest.isProcessed &&
-      !tokenRequest.error &&
-      tokenRequest.data &&
-      tokenRequest.data.name &&
-      tokenRequest.data.expires_at &&
-      tokenRequest.data.token
-    ) {
-      tokenDisplay = (
-        <TextField
-          id="token"
-          className={classes.textField}
-          margin="normal"
-          value={tokenRequest.data.token}
-          variant="outlined"
-          style={{ width: '100%', maxWidth: '500px' }}
-          InputProps={{
-            className: classes.input,
-            classes: {
-              input: classes.inputElement,
-            },
-            readOnly: true,
-            id: 'generatedToken',
-            endAdornment: (
-              <InputAdornment position="end" className={classes.inputAdornment}>
-                <IconButton
-                  className={classes.inputButton}
-                  aria-label="Copy"
-                  onClick={this.copyToken}
-                >
-                  {icons.copyIcon}
-                </IconButton>
-              </InputAdornment>
-            ),
-          }}
-          InputLabelProps={{
-            shrink: true,
-            style: styles.inputFieldLabel,
-          }}
-          FormHelperTextProps={{
-            style: styles.inputFieldHelper,
-          }}
-          helperText="Make sure you have saved token - you will not be able to access it again"
-        />
-      )
-    }
-
-    return <div>{tokenDisplay}</div>
-  }
-}
-
-export default DisplayToken
diff --git a/ui/packages/platform/src/components/DisplayToken/DisplayTokenWrapper.tsx b/ui/packages/platform/src/components/DisplayToken/DisplayTokenWrapper.tsx
deleted file mode 100644
index 87328243..00000000
--- a/ui/packages/platform/src/components/DisplayToken/DisplayTokenWrapper.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import DisplayToken from 'components/DisplayToken/DisplayToken'
-import { styles } from '@postgres.ai/shared/styles/styles'
-
-export const DisplayTokenWrapper = () => {
-  const useStyles = makeStyles(
-    {
-      textField: {
-        ...styles.inputField,
-        marginTop: 0,
-      },
-      input: {
-        '&.MuiOutlinedInput-adornedEnd': {
-          padding: 0,
-        },
-      },
-      inputElement: {
-        marginRight: '-8px',
-      },
-      inputAdornment: {
-        margin: 0,
-      },
-      inputButton: {
-        padding: '9px 10px',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <DisplayToken classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/Error/Error.tsx b/ui/packages/platform/src/components/Error/Error.tsx
deleted file mode 100644
index 23815d41..00000000
--- a/ui/packages/platform/src/components/Error/Error.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { Paper, Typography } from '@material-ui/core'
-
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { ErrorProps } from 'components/Error/ErrorWrapper'
-
-interface ErrorWithStylesProps extends ErrorProps {
-  classes: ClassesType
-}
-
-class Error extends Component<ErrorWithStylesProps> {
-  render() {
-    const { classes } = this.props
-
-    return (
-      <div>
-        <Paper className={classes.paper}>
-          <Typography
-            variant="h1"
-            component="div"
-            className={classes.errorHead}
-          >
-            ERROR {this.props.code ? this.props.code : null}
-          </Typography>
-
-          <br />
-
-          <Typography component="p" className={classes.errorText}>
-            {this.props.message
-              ? this.props.message
-              : 'Unknown error occurred. Please try again later.'}
-          </Typography>
-        </Paper>
-      </div>
-    )
-  }
-}
-
-export default Error
diff --git a/ui/packages/platform/src/components/Error/ErrorWrapper.tsx b/ui/packages/platform/src/components/Error/ErrorWrapper.tsx
deleted file mode 100644
index 6e840000..00000000
--- a/ui/packages/platform/src/components/Error/ErrorWrapper.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import Error from 'components/Error/Error'
-
-export interface ErrorProps {
-  code?: number
-  message?: string
-}
-
-export const ErrorWrapper = (props: ErrorProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      paper: theme.mixins.gutters({
-        paddingTop: 16,
-        paddingBottom: 16,
-        marginTop: 0,
-      }),
-      errorHead: {
-        color: '#c00111',
-        fontWeight: 'bold',
-        fontSize: '16px',
-      },
-      errorText: {
-        color: '#c00111',
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Error {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ExplainVisualization/ExplainVisualization.tsx b/ui/packages/platform/src/components/ExplainVisualization/ExplainVisualization.tsx
deleted file mode 100644
index b001c3a5..00000000
--- a/ui/packages/platform/src/components/ExplainVisualization/ExplainVisualization.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  AppBar,
-  Dialog,
-  Button,
-  IconButton,
-  TextField,
-  Toolbar,
-  Typography,
-} from '@material-ui/core'
-import CloseIcon from '@material-ui/icons/Close'
-import React, { Component } from 'react'
-import Store from '../../stores/store'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import FlameGraph from '../FlameGraph'
-import explainSamples from '../../assets/explainSamples'
-import { visualizeTypes } from '../../assets/visualizeTypes'
-
-interface ExplainVisualizationProps {
-  classes: ClassesType
-}
-
-interface ExplainVisualizationState {
-  plan: string | null
-  showFlameGraph: boolean
-  data: {
-    externalVisualization: {
-      url: string
-      type: string
-      isProcessing: boolean
-    }
-  } | null
-}
-
-class ExplainVisualization extends Component<
-  ExplainVisualizationProps,
-  ExplainVisualizationState
-> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
-    this.setState({
-      plan: event.target.value,
-    })
-  }
-
-  insertSample = () => {
-    this.setState({ plan: explainSamples[0].value })
-  }
-
-  getExternalVisualization = () => {
-    return this.state &&
-      this.state.data &&
-      this.state.data.externalVisualization
-      ? this.state.data.externalVisualization
-      : null
-  }
-
-  showExternalVisualization = (type: string) => {
-    const { plan } = this.state
-
-    if (!plan) {
-      return
-    }
-
-    Actions.getExternalVisualizationData(type, plan, '')
-  }
-
-  closeExternalVisualization = () => {
-    Actions.closeExternalVisualization()
-    this.setState({
-      showFlameGraph: false,
-    })
-  }
-
-  handleExternalVisualizationClick = (type: string) => {
-    return () => {
-      this.showExternalVisualization(type)
-    }
-  }
-
-  showFlameGraphVisualization = () => {
-    this.setState({
-      showFlameGraph: true,
-    })
-  }
-
-  render() {
-    const { classes } = this.props
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[
-          { name: 'SQL Optimization' },
-          { name: 'Plan visualization' },
-        ]}
-      />
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title={'Plan visualization'}
-        information={
-          <React.Fragment>
-            <p>
-              Visualize explain plans gathered manually. Plans gathered with Joe
-              will be automatically saved in Joe history and can be visualized
-              in command page without copy-pasting of a plan.
-            </p>
-            <p>Currently only JSON format is supported.</p>
-            <p>
-              For better results, use: explain (analyze, costs, verbose,
-              buffers, format json).
-            </p>
-          </React.Fragment>
-        }
-      />
-    )
-
-    if (!this.state || !this.state.data) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <Spinner size="lg" className={classes.progress} />
-        </div>
-      )
-    }
-
-    const { plan, showFlameGraph } = this.state
-
-    const externalVisualization = this.getExternalVisualization()
-
-    const disableVizButtons =
-      !plan || showFlameGraph || externalVisualization?.isProcessing
-    const openVizDialog =
-      showFlameGraph ||
-      (externalVisualization?.url
-        ? externalVisualization.url?.length > 0
-        : false)
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        <div className={classes.relativeFieldBlock}>
-          <Button
-            variant="outlined"
-            className={classes.button}
-            onClick={this.insertSample}
-          >
-            Use an example
-          </Button>
-        </div>
-
-        <TextField
-          id="plan"
-          label="Plan with execution (JSON)"
-          className={classes.planTextField}
-          autoFocus={true}
-          margin="normal"
-          multiline
-          rows="15"
-          variant="outlined"
-          value={this.state.plan}
-          onChange={this.handleChange}
-          InputLabelProps={{
-            shrink: true,
-            style: styles.inputFieldLabel,
-          }}
-        />
-
-        <div>
-          <Button
-            variant="contained"
-            color="primary"
-            onClick={this.handleExternalVisualizationClick(
-              visualizeTypes.depesz,
-            )}
-            disabled={disableVizButtons}
-          >
-            Explain Depesz
-            {disableVizButtons &&
-              externalVisualization?.type === visualizeTypes.depesz && (
-                <span>
-                  &nbsp;
-                  <Spinner size="sm" />
-                </span>
-              )}
-          </Button>
-
-          <Button
-            className={classes.nextButton}
-            variant="contained"
-            color="primary"
-            onClick={this.handleExternalVisualizationClick(visualizeTypes.pev2)}
-            disabled={disableVizButtons}
-          >
-            Explain PEV2
-            {disableVizButtons &&
-              externalVisualization?.type === visualizeTypes.pev2 && (
-                <span>
-                  &nbsp;
-                  <Spinner size="sm" />
-                </span>
-              )}
-          </Button>
-
-          <Button
-            className={classes.nextButton}
-            variant="contained"
-            color="primary"
-            onClick={this.showFlameGraphVisualization}
-            disabled={disableVizButtons}
-          >
-            Explain FlameGraph
-          </Button>
-        </div>
-
-        <div>
-          <Dialog fullScreen open={openVizDialog}>
-            <AppBar className={classes.appBar}>
-              <Toolbar>
-                <Typography variant="h2" className={classes.title}>
-                  Visualization
-                </Typography>
-                <IconButton
-                  edge="end"
-                  color="inherit"
-                  onClick={this.closeExternalVisualization}
-                  aria-label="close"
-                >
-                  <CloseIcon />
-                </IconButton>
-              </Toolbar>
-            </AppBar>
-
-            {showFlameGraph && (
-              <div className={classes.flameGraphContainer}>
-                <h4>Flame Graph (buffers):</h4>
-                <FlameGraph data={plan} type="buffers" name="chart-b" />
-
-                <h4>Flame Graph (timing):</h4>
-                <FlameGraph data={plan} type="time" name="chart-t" />
-              </div>
-            )}
-
-            {externalVisualization?.url && (
-              <iframe
-                id="iframeVisualization"
-                title="Visualization"
-                src={externalVisualization?.url}
-                className={classes.visFrame}
-              />
-            )}
-          </Dialog>
-        </div>
-      </div>
-    )
-  }
-}
-
-export default ExplainVisualization
diff --git a/ui/packages/platform/src/components/ExplainVisualization/ExplainVisualizationWrapper.tsx b/ui/packages/platform/src/components/ExplainVisualization/ExplainVisualizationWrapper.tsx
deleted file mode 100644
index 18ee0d5b..00000000
--- a/ui/packages/platform/src/components/ExplainVisualization/ExplainVisualizationWrapper.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import ExplainVisualization from 'components/ExplainVisualization/ExplainVisualization'
-
-export const ExplainVisualizationWrapper = () => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        width: '100%',
-        [theme.breakpoints.down('sm')]: {
-          maxWidth: 'calc(100vw - 40px)',
-        },
-        [theme.breakpoints.up('md')]: {
-          maxWidth: 'calc(100vw - 220px)',
-        },
-        [theme.breakpoints.up('lg')]: {
-          maxWidth: 'calc(100vw - 220px)',
-        },
-        minHeight: '100%',
-        zIndex: 1,
-        position: 'relative',
-      },
-      pointerLink: {
-        cursor: 'pointer',
-      },
-      breadcrumbPaper: {
-        marginBottom: 15,
-      },
-      planTextField: {
-        '& .MuiInputBase-root': {
-          width: '100%',
-        },
-        display: 'block',
-        width: '100%',
-      },
-      appBar: {
-        position: 'relative',
-      },
-      title: {
-        marginLeft: theme.spacing(2),
-        flex: 1,
-        fontSize: '16px',
-      },
-      visFrame: {
-        height: '100%',
-      },
-      nextButton: {
-        marginLeft: '10px',
-      },
-      flameGraphContainer: {
-        padding: '20px',
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <ExplainVisualization classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/FlameGraph.tsx b/ui/packages/platform/src/components/FlameGraph.tsx
deleted file mode 100644
index 4e5b0004..00000000
--- a/ui/packages/platform/src/components/FlameGraph.tsx
+++ /dev/null
@@ -1,318 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import * as d3Lib from 'd3'
-import * as d3Flamegraph from 'd3-flame-graph'
-import React, { Component } from 'react'
-
-import { FlameGraphPlanType } from '@postgres.ai/platform/src/components/types'
-
-interface FlameGraphProps {
-  name: string
-  data: string | null
-  type: string
-}
-
-interface FlameGraphState {
-  noData: boolean
-}
-
-const d3 = Object.assign({}, d3Lib, d3Flamegraph)
-
-const fgStyles = (
-  <div>
-    <style>{`
-        .flamegraph-hidden {
-            display: none;
-        }
-
-        .flamegraph{
-            margin-bottom: 40px;
-        }
-
-        .d3-flame-graph rect{
-            stroke: #EEE;
-            fill-opacity: .8;
-        }
-
-        .d3-flame-graph rect:hover{
-            stroke: #474747;
-            stroke-width: .5;
-            cursor: pointer;
-        }
-
-        .d3-flame-graph .label{
-            pointer-events: none;
-            white-space: nowrap;
-            text-overflow: ellipsis;
-            overflow: hidden;
-            font-size: 12px;
-            font-family: Verdana;
-            margin-left: 4px;
-            margin-right: 4px;
-            line-height: 1.5;
-            padding: 0;
-            font-weight: 400;
-            color: #000;
-            text-align: left;
-        }
-
-        .d3-flame-graph .fade{
-            opacity: .6!important;
-        }
-
-        .d3-flame-graph .title{
-            font-size: 20px;
-            font-family: Verdana;
-        }
-
-        .d3-flame-graph-tip{
-            z-index:  1000;
-            line-height: 1;
-            font-family: Verdana;
-            font-size: 12px;
-            padding: 12px;
-            background: rgba(0,0,0,.8);
-            color: #fff;
-            border-radius: 2px;
-            pointer-events: none;
-        }
-
-        .d3-flame-graph-tip: after{
-            box-sizing: border-box;
-            display: inline;
-            font-size: 10px;
-            width: 100%;
-            line-height: 1;
-            color: rgba(0,0,0,.8);
-            position: absolute;
-            pointer-events: none;
-        }
-
-        .d3-flame-graph-tip.n: after{
-            content: "\\25BC";
-            margin: -1px 0 0;
-            top: 100%;
-            left: 0;
-            text-align: center;
-        }
-
-        .d3-flame-graph-tip.e: after{
-            content: "\\25C0";
-            margin: -4px 0 0;
-            top: 50%;
-            left: -8px;
-        }
-
-        .d3-flame-graph-tip.s: after{
-            content: "\\25B2";
-            margin: 0 0 1px;
-            top: -8px;
-            left: 0;
-            text-align: center;
-        }
-
-        .d3-flame-graph-tip.w: after{
-            content: "\\25B6";
-            margin: -4px 0 0 -1px;
-            top: 50%;
-            left: 100%;
-        }
-    `}</style>
-  </div>
-)
-
-const TYPE_TIME = 'time'
-const TYPE_BUFFERS = 'buffers'
-
-class FlameGraph extends Component<FlameGraphProps, FlameGraphState> {
-  constructor(props: FlameGraphProps) {
-    super(props)
-    this.state = { noData: true }
-  }
-
-  componentDidMount() {
-    this.update()
-  }
-
-  componentDidUpdate(prevProps: FlameGraphProps, prevState: FlameGraphState) {
-    this.update(prevProps, prevState)
-  }
-
-  render() {
-    return (
-      <div>
-        {fgStyles}
-
-        {this.state.noData && <h5>No data</h5>}
-
-        <div
-          id={this.props.name}
-          className={
-            'flamegraph' + (this.state.noData ? ' flamegraph-hidden' : '')
-          }
-        />
-      </div>
-    )
-  }
-
-  update = (_?: FlameGraphProps, prevState?: FlameGraphState) => {
-    let explainJson
-
-    try {
-      explainJson = JSON.parse(String(this.props.data))
-    } catch (err) {
-      this.setNoData(prevState as FlameGraphState, true)
-      return
-    }
-
-    let type = TYPE_TIME
-    if (this.props.type === TYPE_BUFFERS) {
-      type = TYPE_BUFFERS
-    }
-
-    const data = this.convert(explainJson, type)
-    if (!data) {
-      this.setNoData(prevState as FlameGraphState, true)
-      return
-    }
-
-    this.setNoData(prevState as FlameGraphState, false)
-
-    this.draw(data)
-  }
-
-  setNoData = (prevState: FlameGraphState, noData: boolean) => {
-    if (!prevState) {
-      this.setState({ noData })
-      return
-    }
-
-    if (prevState.noData !== noData) {
-      this.setState({ noData })
-    }
-  }
-
-  convert = (explainJson: string, type: string) => {
-    if (
-      !explainJson ||
-      !Array.isArray(explainJson) ||
-      explainJson.length < 1 ||
-      !explainJson[0].Plan
-    ) {
-      return null
-    }
-
-    const data = this.convertRec(explainJson[0].Plan, type)
-    return data
-  }
-
-  convertRec = (plan: FlameGraphPlanType, type: string): React.ReactNode => {
-    const children = []
-
-    if (plan['Plans']) {
-      for (let i = 0; i < plan['Plans'].length; i++) {
-        children.push(
-          this.convertRec(
-            plan['Plans'][i] as unknown as FlameGraphPlanType,
-            type,
-          ),
-        )
-      }
-    }
-
-    return this.newNode(plan, type, children)
-  }
-
-  newNode = (
-    plan: FlameGraphPlanType,
-    type: string,
-    children: React.ReactNode,
-  ) => {
-    const buffersHit = plan['Shared Hit Blocks'] as string
-    const buffersRead = plan['Shared Read Blocks']
-    const timing = plan['Actual Total Time']
-
-    let name = this.buildNodeName(plan)
-    let tooltip = name
-    let value
-
-    if (type === TYPE_BUFFERS) {
-      value = buffersHit + buffersRead
-      name = name + ', ' + value
-      tooltip =
-        tooltip +
-        ', Buffers hit: ' +
-        buffersHit +
-        ', Buffers read: ' +
-        buffersRead
-    } else {
-      value = timing
-      name = name + ', ' + value + ' ms'
-      tooltip = tooltip + ', Execution time: ' + value + ' ms'
-    }
-
-    return {
-      name,
-      value,
-      tooltip,
-      buffersHit,
-      buffersRead,
-      timing,
-      children,
-    }
-  }
-
-  buildNodeName = (plan: FlameGraphPlanType) => {
-    const nodeType = plan['Node Type']
-    let name = nodeType
-
-    if (nodeType === 'Modify Table') {
-      // E.g. for Insert.
-      name = plan['Operation']
-    }
-
-    if (nodeType === 'Hash Join' && plan['Join Type'] === 'Left') {
-      name = 'Hash Left Join'
-    }
-
-    if (nodeType === 'Aggregate' && plan['Strategy'] === 'Hashed') {
-      name = 'Hash Aggregate'
-    }
-
-    if (nodeType === 'Nested Loop' && plan['Join Type'] === 'Left') {
-      name = 'Nested Loop Left Join'
-    }
-
-    if (plan['Parallel Aware']) {
-      name = 'Parallel ' + name
-    }
-
-    return name
-  }
-
-  draw = (data: unknown) => {
-    d3.select('#' + this.props.name)
-      .selectAll('*')
-      .remove()
-
-    const fg = d3
-      .flamegraph()
-      .width(600)
-      .cellHeight(18)
-      .transitionDuration(750)
-      .label((d) => {
-        return d.data && d.data.tooltip
-      })
-
-    d3.select('#' + this.props.name)
-      .datum(data)
-      .call(fg)
-  }
-}
-
-export default FlameGraph
diff --git a/ui/packages/platform/src/components/Head/index.tsx b/ui/packages/platform/src/components/Head/index.tsx
deleted file mode 100644
index d2b6067a..00000000
--- a/ui/packages/platform/src/components/Head/index.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useEffect, useRef } from 'react'
-
-import { config } from '@postgres.ai/shared/config'
-
-type Props = {
-  title: string
-}
-
-export const Head = (props: Props) => {
-  const { title } = props
-
-  const titleRef = useRef(window.document.title)
-
-  useEffect(
-    () => () => {
-      window.document.title = titleRef.current
-    },
-    [],
-  )
-
-  window.document.title = title
-
-  return null
-}
-
-export const createTitle = (parts: string[]) => {
-  parts.reverse()
-  parts.push(config.appName)
-  return parts.join(' · ');
-}
diff --git a/ui/packages/platform/src/components/IndexPage/IndexPage.tsx b/ui/packages/platform/src/components/IndexPage/IndexPage.tsx
deleted file mode 100644
index ef4f90cb..00000000
--- a/ui/packages/platform/src/components/IndexPage/IndexPage.tsx
+++ /dev/null
@@ -1,1487 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { Switch, Route, NavLink, Redirect, useRouteMatch } from 'react-router-dom'
-import {
-  AppBar,
-  Toolbar,
-  Typography,
-  Divider,
-  IconButton,
-  ListItem,
-  List,
-  Drawer,
-} from '@material-ui/core'
-import qs from 'qs'
-
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import { Box } from '@mui/material'
-import {
-  OrganizationWrapperProps,
-  OrganizationMenuProps,
-  ProjectWrapperProps,
-  QueryParamsType,
-  UserProfile,
-  OrgPermissions,
-  ClassesType,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-import { ROUTES } from 'config/routes'
-import { Instance } from 'pages/Instance'
-import { Clone } from 'pages/Clone'
-import { CreateClone } from 'pages/CreateClone'
-import { ProfileWrapper } from 'pages/Profile/ProfileWrapper'
-import { SignInWrapper } from 'pages/SignIn/SignInWrapper'
-
-import { SideNav } from 'components/SideNav'
-import { ContentLayout } from 'components/ContentLayout'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { DbLabInstanceFormWrapper } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import { AddDbLabInstanceFormWrapper } from 'components/AddDbLabInstanceFormWrapper/AddDbLabInstanceFormWrapper'
-import { DbLabInstancesWrapper } from 'components/DbLabInstances/DbLabInstancesWrapper'
-import { DbLabInstanceFormInstallWrapper } from 'components/DbLabInstanceInstallForm/DbLabInstanceInstallFormWrapper'
-import { DbLabSessionWrapper } from 'components/DbLabSession/DbLabSessionWrapper'
-import { DbLabSessionsWrapper } from 'components/DbLabSessions/DbLabSessionsWrapper'
-import { JoeInstanceFormWrapper } from 'components/JoeInstanceForm/JoeInstanceFormWrapper'
-import { JoeInstanceWrapper } from 'pages/JoeInstance/JoeInstanceWrapper'
-import { JoeInstancesWrapper } from 'components/JoeInstances/JoeInstancesWrapper'
-import { ReportFileWrapper } from 'components/ReportFile/ReportFileWrapper'
-import { ReportWrapper } from 'components/Report/ReportWrapper'
-import { ReportsWrapper } from 'components/Reports/ReportsWrapper'
-import { JoeSessionCommandWrapper } from 'pages/JoeSessionCommand/JoeSessionCommandWrapper'
-import { JoeHistoryWrapper } from 'components/JoeHistory/JoeHistoryWrapper'
-import { BillingWrapper } from 'components/Billing/BillingWrapper'
-import { AddMemberFormWrapper } from 'components/AddMemberForm/AddMemberFormWrapper'
-import { OrgFormWrapper } from 'components/OrgForm/OrgFormWrapper'
-import { AuditWrapper } from 'components/Audit/AuditWrapper'
-import { OrgMembersWrapper } from 'components/OrgMembers/OrgMembersWrapper'
-import { AccessTokensWrapper } from 'components/AccessTokens/AccessTokensWrapper'
-import { CheckupAgentFormWrapper } from 'components/CheckupAgentForm/CheckupAgentFormWrapper'
-import { ExplainVisualizationWrapper } from 'components/ExplainVisualization/ExplainVisualizationWrapper'
-import { DashboardWrapper } from 'components/Dashboard/DashboardWrapper'
-import { LoginDialogWrapper } from 'components/LoginDialog/LoginDialogWrapper'
-import { NotificationWrapper } from 'components/Notification/NotificationWrapper'
-import { SharedUrlWrapper } from 'components/SharedUrl/SharedUrlWrapper'
-import { ShareUrlDialogWrapper } from 'components/ShareUrlDialog/ShareUrlDialogWrapper'
-import { BotWrapper } from "pages/Bot/BotWrapper";
-
-import Actions from '../../actions/actions'
-import JoeConfig from '../JoeConfig'
-import Permissions from '../../utils/permissions'
-import Store from '../../stores/store'
-import Urls from '../../utils/urls'
-import settings from '../../utils/settings'
-import { AppUpdateBanner } from '../AppUpdateBanner'
-import { IndexPageProps } from 'components/IndexPage/IndexPageWrapper'
-import { PostgresClusterWrapper } from 'components/PostgresClusterForm/PostgresClusterWrapper'
-import { PostgresClusterInstallWrapper } from 'components/PostgresClusterInstallForm/PostgresClusterInstallWrapper'
-import { PostgresClustersWrapper } from 'components/PostgresClusters/PostgresClustersWrapper'
-import cn from "classnames";
-import { BotSettingsFormWrapper } from "../BotSettingsForm/BotSettingsFormWrapper";
-
-
-interface IndexPageWithStylesProps extends IndexPageProps {
-  classes: ClassesType
-}
-
-interface IndexPageState {
-  data: {
-    auth: {
-      token: string
-      userId: number
-      isProcessed: boolean
-    } | null
-    userProfile: UserProfile | null
-  } | null
-  mobileOpen: boolean
-}
-
-function ProjectWrapper(parentProps: Omit<ProjectWrapperProps, 'classes'>) {
-  const project = parentProps.match.params.project
-  const queryParams = qs.parse(parentProps.location.search, {
-    ignoreQueryPrefix: true,
-  })
-  const { session, command, author, fingerprint, search, is_favorite } =
-    queryParams
-
-  const errorMessage = (
-    <div>
-      404 Project <strong>{project}</strong> not found
-      <br />
-    </div>
-  )
-
-  if (
-    !project ||
-    !parentProps.orgData?.projects ||
-    !parentProps.orgData.projects[project]
-  ) {
-    return errorMessage
-  }
-
-  const projectId = parentProps.orgData.projects[project].id
-
-  const customProps = {
-    project,
-    projectId,
-    org: parentProps.org,
-    orgId: parentProps.orgId,
-    orgData: parentProps.orgData,
-    orgPermissions: parentProps.orgPermissions,
-    userIsOwner: parentProps.userIsOwner,
-    env: parentProps.env,
-    envData: parentProps.envData,
-    auth: parentProps.auth,
-    raw: parentProps.raw,
-  }
-
-  const queryProps = {
-    session,
-    command,
-    author,
-    fingerprint,
-    search,
-    is_favorite,
-  } as QueryParamsType
-
-  return (
-    <Switch>
-      <Route
-        path="/:org/:project/instances/add"
-        render={(props) => (
-          <AddDbLabInstanceFormWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/pg/create"
-        render={(props) => (
-          <PostgresClusterWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/pg/install"
-        render={(props) => (
-          <PostgresClusterInstallWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/instances/create"
-        render={(props) => (
-          <DbLabInstanceFormWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/instances/install"
-        render={(props) => (
-          <DbLabInstanceFormInstallWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/instances/edit/:instanceId"
-        render={(props) => (
-          <AddDbLabInstanceFormWrapper edit {...props} {...customProps} />
-        )}
-      />
-      <Route
-        exact
-        path={ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.CLONES.ADD.createPath()}
-      >
-        <CreateClone />
-      </Route>
-      <Route
-        path={ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.CLONES.CLONE.createPath()}
-      >
-        <Clone />
-      </Route>
-      <Route path={ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.createPath()}>
-        <Instance />
-      </Route>
-      <Route
-        path="/:org/:project/instances"
-        render={(props) => (
-          <DbLabInstancesWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/pg"
-        render={(props) => (
-          <PostgresClustersWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/observed-sessions/:sessionId"
-        render={(props) => <DbLabSessionWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/observed-sessions"
-        render={(props) => <DbLabSessionsWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/joe-instances/add"
-        render={(props) => (
-          <JoeInstanceFormWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/joe-instances/:instanceId"
-        render={(props) => <JoeInstanceWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/bot/:instanceId"
-        render={(props) => <JoeInstanceWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/joe-instances"
-        render={(props) => <JoeInstancesWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/reports/:reportId/files/:fileId/:fileType"
-        render={(props) => (
-          <ReportFileWrapper
-            fileType={props.match.params.fileType}
-            reportId={props.match.params.reportId}
-            {...props}
-            {...customProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/:project/reports/:reportId/:type"
-        render={(props) => <ReportWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/reports/:reportId/"
-        render={(props) => <ReportWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/reports"
-        render={(props) => <ReportsWrapper {...props} {...customProps} />}
-      />
-      <Route
-        path="/:org/:project/sessions/:sessionId/commands/:commandId"
-        render={(props) => (
-          <JoeSessionCommandWrapper {...props} {...customProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/sessions/:sessionId"
-        render={(props) => (
-          <JoeHistoryWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project/sessions"
-        render={(props) => (
-          <JoeHistoryWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project"
-        render={(props) => <ReportsWrapper {...props} {...customProps} />}
-      />
-    </Switch>
-  )
-}
-
-function OrganizationMenu(parentProps: OrganizationMenuProps) {
-  const isDemoOrg = useRouteMatch(`/${settings.demoOrgAlias}`)
-  if (
-    parentProps.env &&
-    parentProps.env.data &&
-    parentProps.match.params.org &&
-    parentProps.env.data.orgs &&
-    parentProps.env.data.orgs[parentProps.match.params.org]
-  ) {
-    let org = parentProps.match.params.org
-    let isBlocked = false
-    let orgData = null
-
-    let orgPermissions: OrgPermissions = {}
-    if (
-      parentProps.env.data &&
-      parentProps.env.data.orgs &&
-      parentProps.env.data.orgs[org]
-    ) {
-      orgData = parentProps.env.data.orgs[org]
-      isBlocked = orgData.is_blocked
-      orgPermissions = Permissions.getPermissions(orgData)
-    }
-
-    return (
-      <div style={{ height: '100%' }}>
-        <div>
-          <div className={parentProps.classes.orgHeaderContainer}>
-            <span className={parentProps.classes.orgHeader}>Organization</span>
-            <NavLink to={ROUTES.ROOT.path}>
-              <span className={parentProps.classes.orgSwitcher} id="menuSwitch">
-                switch
-              </span>
-            </NavLink>
-          </div>
-          <div className={parentProps.classes.orgNameContainer}>
-            <span className={parentProps.classes.orgName}>
-              {parentProps.env.data.orgs[org].name}
-            </span>
-          </div>
-        </div>
-
-        <Divider />
-
-        <List component="nav" className={parentProps.classes.navMenu}>
-          <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuDashboard"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={parentProps.classes.menuSectionHeaderActiveLink}
-              to={'/' + org}
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.dashboardIcon}
-              </span>
-              Dashboard
-            </NavLink>
-          </ListItem>
-
-          {Boolean(isDemoOrg) && <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuAiBotTitle"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={cn(parentProps.classes.menuSectionHeaderActiveLink, parentProps.classes.menuSingleSectionHeaderActiveLink)}
-              to={'/' + org + '/assistant'}
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.aiBotIcon}
-              </span>
-              AI Assistant<span className={cn(parentProps.classes.menuItemLabel, parentProps.classes.headerLinkMenuItemLabel)}>NEW</span>
-            </NavLink>
-          </ListItem>}
-          <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuDblabTitle"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={parentProps.classes.menuSectionHeaderActiveLink}
-              to={'/' + org + '/instances'}
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.databaseLabIcon}
-              </span>
-              Database Lab
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuDblabInstances"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/instances'}
-            >
-              Instances
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuDblabSessions"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/observed-sessions'}
-            >
-              Observed sessions
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuJoeTitle"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={parentProps.classes.menuSectionHeaderActiveLink}
-              to={'/' + org + '/joe-instances'}
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.sqlOptimizationIcon}
-              </span>
-              SQL Optimization
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuJoeInstances"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/joe-instances'}
-            >
-              Ask Joe
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuJoeSessions"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/sessions?'}
-            >
-              History
-            </NavLink>
-          </ListItem>
-          {false && (
-            <ListItem
-              button
-              className={parentProps.classes.menuItem}
-              disabled={isBlocked}
-              id="menuJoeExplain"
-            >
-              <NavLink
-                className={parentProps.classes.menuItemLink}
-                activeClassName={parentProps.classes.menuItemActiveLink}
-                to={'/' + org + '/explain'}
-              >
-                Plan visualization
-              </NavLink>
-            </ListItem>
-          )}
-
-          {/* <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuPostgresTitle"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={parentProps.classes.menuSectionHeaderActiveLink}
-              to={'/' + org + '/pg'}
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.postgresSQLIcon}
-              </span>
-              Postgres
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuDblabClusters"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/pg'}
-            >
-              Clusters
-            </NavLink>
-          </ListItem> */}
-
-          <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuCheckupTitle"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={parentProps.classes.menuSectionHeaderActiveLink}
-              to={'/' + org + '/reports'}
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.checkupIcon}
-              </span>
-              Checkup
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuCheckupReports"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/reports'}
-            >
-              Reports
-            </NavLink>
-          </ListItem>
-
-          <ListItem
-            button
-            className={parentProps.classes.menuSectionHeader}
-            disabled={isBlocked}
-            id="menuSettingsTitle"
-          >
-            <NavLink
-              className={parentProps.classes.menuSectionHeaderLink}
-              activeClassName={parentProps.classes.menuSectionHeaderActiveLink}
-              to={
-                orgPermissions && orgPermissions.settingsOrganizationUpdate
-                  ? '/' + org + '/settings'
-                  : '#'
-              }
-            >
-              <span className={parentProps.classes.menuSectionHeaderIcon}>
-                {icons.settingsIcon}
-              </span>
-              Settings
-            </NavLink>
-          </ListItem>
-          {orgPermissions && orgPermissions.settingsOrganizationUpdate && (
-            <ListItem
-              button
-              className={parentProps.classes.menuItem}
-              disabled={isBlocked}
-              id="menuSettingsGeneral"
-            >
-              <NavLink
-                className={parentProps.classes.menuItemLink}
-                activeClassName={parentProps.classes.menuItemActiveLink}
-                to={'/' + org + '/settings'}
-              >
-                General
-              </NavLink>
-            </ListItem>
-          )}
-          {/*<ListItem
-            disabled={isBlocked}
-            button
-            className={parentProps.classes.menuItem}
-            id="menuSettingsBot"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/bot-settings'}
-            >
-              AI Assistant
-            </NavLink>
-          </ListItem>*/}
-          <ListItem
-            disabled={isBlocked}
-            button
-            className={parentProps.classes.menuItem}
-            id="menuSettingsMembers"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/members'}
-            >
-              Members
-            </NavLink>
-          </ListItem>
-          <ListItem
-            button
-            className={parentProps.classes.menuItem}
-            disabled={isBlocked}
-            id="menuSettingsTokens"
-          >
-            <NavLink
-              className={parentProps.classes.menuItemLink}
-              activeClassName={parentProps.classes.menuItemActiveLink}
-              to={'/' + org + '/tokens'}
-            >
-              Access tokens
-            </NavLink>
-          </ListItem>
-          {orgData !== null && Permissions.isAdmin(orgData) && (
-            <ListItem
-              button
-              className={parentProps.classes.menuItem}
-              id="menuSettingsBilling"
-            >
-              <NavLink
-                className={parentProps.classes.menuItemLink}
-                activeClassName={parentProps.classes.menuItemActiveLink}
-                to={'/' + org + '/billing'}
-              >
-                Billing
-              </NavLink>
-            </ListItem>
-          )}
-          {orgPermissions && orgPermissions.auditLogView && (
-            <ListItem
-              disabled={
-                (orgPermissions && !orgPermissions.auditLogView) || isBlocked
-              }
-              button
-              className={parentProps.classes.menuItem}
-              id="menuSettingsAudit"
-            >
-              <NavLink
-                className={parentProps.classes.menuItemLink}
-                activeClassName={parentProps.classes.menuItemActiveLink}
-                to={'/' + org + '/audit'}
-              >
-                Audit
-              </NavLink>
-            </ListItem>
-          )}
-        </List>
-      </div>
-    )
-  }
-
-  return null
-}
-
-function OrganizationWrapper(parentProps: OrganizationWrapperProps) {
-  const org = parentProps.match.params.org
-  const envData =
-    parentProps.env && parentProps.env.data
-      ? parentProps.env.data
-      : { orgs: null }
-
-  const errorMessage = (
-    <div>
-      404 Organization <strong>{org}</strong> not found
-      <br />
-    </div>
-  )
-
-  const preloader = <PageSpinner />
-
-  if (org === 'auth' || !envData.orgs) {
-    return preloader
-  }
-
-  if (!org || (org && envData.orgs && !envData.orgs[org])) {
-    return errorMessage
-  }
-
-  const env = parentProps.env
-  const auth = parentProps.auth
-  const raw = parentProps.raw
-  const orgId = envData.orgs[org].id
-  const orgData = envData.orgs[org]
-  const isBlocked = orgData.is_blocked
-  const orgPermissions = Permissions.getPermissions(orgData)
-  const userIsOwner =
-    !env || !auth || !orgId
-      ? false
-      : Permissions.userIsOwner(auth.userId, orgId, env.data)
-
-  const queryParams = qs.parse(parentProps.location.search, {
-    ignoreQueryPrefix: true,
-  })
-  const {
-    session,
-    command,
-    author,
-    fingerprint,
-    project,
-    search,
-    is_favorite,
-  } = queryParams
-  const projectId = project && orgData.projects[project.toString()]?.id
-
-  const customProps = {
-    project,
-    projectId,
-    org,
-    orgId,
-    orgData,
-    orgPermissions,
-    userIsOwner,
-    env,
-    envData,
-    auth,
-    raw,
-  }
-
-  const queryProps = {
-    session,
-    command,
-    author,
-    fingerprint,
-    search,
-    is_favorite,
-  } as QueryParamsType
-
-  if (isBlocked && Permissions.isAdmin(orgData)) {
-    return (
-      <Switch>
-        <Route
-          path="/:org/billing"
-          render={(props) => (
-            <BillingWrapper
-              short={false}
-              {...props}
-              {...customProps}
-              {...queryProps}
-            />
-          )}
-        />
-        <Route
-          path={ROUTES.ROOT.path}
-          render={(props) => (
-            <BillingWrapper
-              {...props}
-              {...customProps}
-              {...queryProps}
-              short={true}
-            />
-          )}
-        />
-      </Switch>
-    )
-  }
-
-  return (
-    <Switch>
-      <Route
-        path="/:org/instances/add"
-        render={(props) => (
-          <AddDbLabInstanceFormWrapper
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/pg/create"
-        render={(props) => (
-          <PostgresClusterWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/pg/install"
-        render={(props) => (
-          <PostgresClusterInstallWrapper
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/instances/create"
-        render={(props) => (
-          <DbLabInstanceFormWrapper
-            userID={auth?.userId}
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/instances/install"
-        render={(props) => (
-          <DbLabInstanceFormInstallWrapper
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route exact path={ROUTES.ORG.INSTANCES.INSTANCE.CLONES.ADD.createPath()}>
-        <CreateClone />
-      </Route>
-      <Route path={ROUTES.ORG.INSTANCES.INSTANCE.CLONES.CLONE.createPath()}>
-        <Clone />
-      </Route>
-      <Route path={ROUTES.ORG.INSTANCES.INSTANCE.createPath()}>
-        <Instance />
-      </Route>
-      <Route
-        path="/:org/instances"
-        render={(props) => (
-          <DbLabInstancesWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/pg"
-        render={(props) => (
-          <PostgresClustersWrapper
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/observed-sessions/:sessionId"
-        render={(props) => (
-          <DbLabSessionWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/observed-sessions"
-        render={(props) => (
-          <DbLabSessionsWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/joe-instances/add"
-        render={(props) => (
-          <JoeInstanceFormWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/joe-instances/:instanceId"
-        render={(props) => (
-          <JoeInstanceWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/assistant/:threadId"
-        exact
-        render={(props) => (
-          <BotWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/assistant"
-        exact
-        render={(props) => (
-          <BotWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/bot/:threadId"
-        exact
-        render={(props) => {
-          const { org, threadId } = props.match.params;
-          return <Redirect to={`/${org}/assistant/${threadId}`} />;
-        }}
-      />
-      <Route
-        path="/:org/bot"
-        exact
-        render={(props) => {
-          const { org } = props.match.params;
-          return <Redirect to={`/${org}/assistant`} />;
-        }}
-      />
-      <Route
-        path="/:org/joe-instances"
-        render={(props) => (
-          <JoeInstancesWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/members/add"
-        render={(props) => (
-          <AddMemberFormWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/settings"
-        render={(props) => (
-          <OrgFormWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/assistant-settings"
-        render={(props) => (
-          <BotSettingsFormWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/billing"
-        render={(props) => (
-          <BillingWrapper
-            short={false}
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/audit"
-        render={(props) => (
-          <AuditWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/members"
-        render={(props) => (
-          <OrgMembersWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/reports"
-        render={(props) => (
-          <ReportsWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path={ROUTES.ORG.TOKENS.createPath()}
-        render={(props) => (
-          <AccessTokensWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/checkup-config"
-        render={(props) => (
-          <CheckupAgentFormWrapper
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-      <Route
-        path="/:org/joe-config"
-        render={(props) => (
-          <JoeConfig {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/explain"
-        render={() => <ExplainVisualizationWrapper />}
-      />
-      <Route
-        path="/:org/sessions"
-        render={(props) => (
-          <JoeHistoryWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org/:project"
-        render={(props) => (
-          <ProjectWrapper {...props} {...customProps} {...queryProps} />
-        )}
-      />
-      <Route
-        path="/:org"
-        render={(props) => (
-          <DashboardWrapper
-            onlyProjects
-            {...props}
-            {...customProps}
-            {...queryProps}
-          />
-        )}
-      />
-    </Switch>
-  )
-}
-
-function SupportMenu(props: { classes: ClassesType }) {
-  return (
-    <List component="nav" className={props.classes.navBottomFixedMenu}>
-      <ListItem button className={props.classes.bottomFixedMenuItem}>
-        <a
-          className={props.classes.menuSectionHeaderLink}
-          target="_blank"
-          href={settings.rootUrl + '/docs'}
-          rel="noreferrer"
-        >
-          <span className={props.classes.menuSectionHeaderIcon}>
-            {icons.docIcon}
-          </span>
-          Documentation
-        </a>
-      </ListItem>
-      <ListItem button className={props.classes.bottomFixedMenuItem}>
-        <a
-          className={props.classes.menuSectionHeaderLink}
-          target="_blank"
-          href={settings.rootUrl + '/contact'}
-          rel="noreferrer"
-        >
-          <span className={props.classes.menuSectionHeaderIcon}>
-            {icons.supportIcon}
-          </span>
-          Ask support
-        </a>
-      </ListItem>
-    </List>
-  )
-}
-
-class IndexPage extends Component<IndexPageWithStylesProps, IndexPageState> {
-  state = {
-    data: {
-      auth: {
-        token: '',
-        userId: 0,
-        isProcessed: false,
-      },
-      userProfile: {
-        data: {
-          orgs: {},
-          info: {
-            first_name: '',
-            user_name: '',
-            email: '',
-            is_tos_confirmed: false,
-            is_active: false,
-            id: null,
-          },
-        },
-        isTosAgreementConfirmProcessing: false,
-        isConfirmProcessing: false,
-        isConfirmProcessed: false,
-      },
-    },
-    mobileOpen: false,
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-
-    document.getElementsByTagName('html')[0].style.overflow = 'hidden'
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      that.setState({ data: this.data })
-
-      // redirect to new organization alias if need
-      const env = this.data ? this.data.userProfile : null
-      const orgProfile =
-        this.data && this.data.orgProfile ? this.data.orgProfile : null
-      if (
-        orgProfile &&
-        orgProfile.prevAlias &&
-        orgProfile.data.alias &&
-        env &&
-        env.data &&
-        env.data.orgs[orgProfile.data.alias]
-      ) {
-        window.location.href = '/' + orgProfile.data.alias + '/settings'
-      }
-
-      if (
-        (env.isConfirmProcessed || (env.data && env.data.info.is_active)) &&
-        Urls.isRequestedPath('confirm')
-      ) {
-        window.location.href = ROUTES.ROOT.path
-      }
-    })
-
-    Actions.doAuth(null, null)
-  }
-
-  signOut() {
-    Actions.signOut()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleDrawerToggle = () => {
-    window.innerWidth <= 768 &&
-      this.setState({ mobileOpen: !this.state.mobileOpen })
-  }
-
-  goHrefUrl = (event: {
-    target: { getAttribute: (attribute: string) => string }
-  }) => {
-    this.props.history.push(event.target.getAttribute('hrefurl'))
-    return false
-  }
-
-  resendCode = () => {
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    if (auth?.token) {
-      Actions.sendUserCode(auth.token)
-    }
-  }
-
-  confirmTosAgreement = () => {
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    if (auth?.token) {
-      Actions.confirmTosAgreement(auth.token)
-    }
-  }
-
-  addOrgButtonHandler = () => {
-    this.props.history.push(ROUTES.CREATE_ORG.path)
-  }
-
-  render() {
-    const { classes } = this.props
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const env = this.state.data ? this.state.data.userProfile : null
-    const raw =
-      this.props &&
-      this.props.location &&
-      this.props.location.search &&
-      this.props.location.search.indexOf('raw') !== -1
-
-    if (auth && auth.isProcessed && !auth.token && !Urls.isSharedUrl()) {
-      if (window.location.pathname === '/login') {
-        return (
-          <div>
-            <LoginDialogWrapper />
-          </div>
-        )
-      }
-
-      if (window.location.pathname !== '/signin') {
-        this.signOut()
-        return null
-      }
-
-      return <SignInWrapper />
-    }
-
-    const appBarLogo = (
-      <Typography color="inherit" noWrap>
-        <NavLink to={ROUTES.ROOT.path} className={classes.logo}>
-          Postgres.AI Console
-        </NavLink>
-      </Typography>
-    )
-
-    const appBarSignOut = (
-      <IconButton
-        color="inherit"
-        aria-label="Sign out"
-        onClick={this.signOut}
-        className={classes.navIconSignOut}
-      >
-        {icons.exitIcon}
-      </IconButton>
-    )
-
-    const uiUpdate = <AppUpdateBanner />
-
-    let userName = ''
-    if (env && env.data && env.data.info) {
-      if (env.data.info.first_name) {
-        userName = env.data.info.first_name
-      } else {
-        userName = env.data.info.email
-      }
-    }
-
-    if (Urls.isSharedUrl()) {
-      return (
-        <div className={classes.root}>
-          <AppBar position="absolute" className={classes.appBar}>
-            <Toolbar className={classes.topToolbar}>
-              {appBarLogo}
-              {auth && auth.token && (
-                <Typography color="inherit" noWrap className={classes.userName}>
-                  {userName}
-                </Typography>
-              )}
-              {auth && auth.token ? (
-                <NavLink
-                  to={ROUTES.PROFILE.path}
-                  className={classes.navIconArea}
-                >
-                  <IconButton
-                    color="inherit"
-                    aria-label="Profile"
-                    className={classes.navIconProfile}
-                  >
-                    {icons.userIcon}
-                  </IconButton>
-                </NavLink>
-              ) : null}
-              {auth && auth.token && appBarSignOut}
-            </Toolbar>
-          </AppBar>
-          <ContentLayout>
-            <Switch>
-              <Route
-                path="/shared/:url_uuid"
-                render={(props) => <SharedUrlWrapper {...props} />}
-              />
-              <Redirect from="*" to={ROUTES.ROOT.path} />
-            </Switch>
-          </ContentLayout>
-        </div>
-      )
-    }
-
-    if (!env || (env && !env.data)) {
-      return <PageSpinner />
-    }
-
-    if (!env.data.info['is_tos_confirmed']) {
-      return (
-        <>
-          {uiUpdate}
-
-          <div className={classes.tosWrapper}>
-            <div className={classes.tosContainer}>
-              <p>
-                Please, read and agree to our updated
-                <br />
-                <GatewayLink href="https://postgres.ai/tos" target="_blank">
-                  Terms of Service
-                </GatewayLink>
-                &nbsp;and&nbsp;
-                <GatewayLink href="https://postgres.ai/privacy" target="_blank">
-                  Privacy Policy
-                </GatewayLink>
-              </p>
-
-              <ConsoleButtonWrapper
-                variant="contained"
-                color="primary"
-                key="add_dblab_instance"
-                disabled={env.isTosAgreementConfirmProcessing}
-                onClick={this.confirmTosAgreement}
-                className={classes.tosAgree}
-                title="I Agree"
-              >
-                I Agree
-              </ConsoleButtonWrapper>
-            </div>
-            <NotificationWrapper />
-          </div>
-        </>
-      )
-    }
-
-    if (!env.data.info['is_active']) {
-      if (Urls.isRequestedPath('confirm') && Urls.getRequestParam('code')) {
-        if (!env.isConfirmProcessing && !env.isConfirmProcessed) {
-          Actions.confirmUserEmail(auth?.token, Urls.getRequestParam('code'))
-        }
-        if (!env.isConfirmProcessed) {
-          return (
-            <>
-              {uiUpdate}
-              <PageSpinner />
-            </>
-          )
-        }
-
-        this.props.history.push(ROUTES.ROOT.path)
-      }
-
-      return (
-        <>
-          {uiUpdate}
-          <AppBar position="absolute" className={classes.appBar}>
-            <Toolbar className={classes.topToolbar}>
-              {appBarLogo}
-              {appBarSignOut}
-            </Toolbar>
-          </AppBar>
-
-          <div className={classes.betaWrapper}>
-            <div className={classes.betaContainer}>
-              <div style={{ marginBottom: '10px', textAlign: 'center' }}>
-                {icons.logo}
-              </div>
-
-              <strong>
-                Hi{' '}
-                {env.data.info['first_name']
-                  ? env.data.info['first_name']
-                  : env.data.info['user_name']}
-                !
-              </strong>
-
-              <p>
-                Please confirm registration by clicking on a link we have just
-                sent to {env.data.info['email']}.
-              </p>
-
-              <p>
-                If you haven't received the email,&nbsp;
-                <GatewayLink onClick={this.resendCode} href="#">
-                  click here
-                </GatewayLink>
-                &nbsp;to resend it.
-              </p>
-            </div>
-            <NotificationWrapper />
-          </div>
-        </>
-      )
-    }
-
-    if (raw) {
-      return (
-        <>
-          <Switch>
-            <Route
-              path="/:org"
-              render={(props) => (
-                <OrganizationWrapper
-                  {...props}
-                  env={env}
-                  auth={auth}
-                  raw={raw}
-                  classes={classes}
-                />
-              )}
-            />
-            <Redirect from="*" to={ROUTES.ROOT.path} />
-          </Switch>
-        </>
-      )
-    }
-
-    const drawer = (
-      <div onClick={this.handleDrawerToggle} className={classes.menuPointer}>
-        <Divider />
-        <Switch>
-          <Route exact path={[ROUTES.ROOT.path, ROUTES.PROFILE.path]}>
-            <SideNav />
-          </Route>
-
-          <Route
-            path="/:org"
-            render={(props) => (
-              <OrganizationMenu {...props} classes={classes} env={env} />
-            )}
-          />
-        </Switch>
-        <SupportMenu {...this.props} />
-      </div>
-    )
-
-    return (
-      <>
-        {uiUpdate}
-        <div className={classes.root}>
-          <AppBar position="absolute" className={classes.appBar}>
-            <Toolbar className={classes.topToolbar}>
-              <IconButton
-                color="inherit"
-                aria-label="Open menu"
-                onClick={this.handleDrawerToggle}
-                className={`menu-pointer ${classes.navIconHide}`}
-              >
-                {icons.menuIcon}
-              </IconButton>
-              {appBarLogo}
-              <Typography color="inherit" noWrap className={classes.userName}>
-                {userName}
-              </Typography>
-              <NavLink to={ROUTES.PROFILE.path} className={classes.navIconArea}>
-                <IconButton
-                  color="inherit"
-                  aria-label="Profile"
-                  className={classes.navIconProfile}
-                >
-                  {icons.userIcon}
-                </IconButton>
-              </NavLink>
-              {appBarSignOut}
-            </Toolbar>
-          </AppBar>
-          <Box
-            sx={{
-              display: { xs: 'block', sm: 'none', md: 'none' },
-            }}
-          >
-            <Drawer
-              className={classes.drawer}
-              variant="temporary"
-              anchor="left"
-              open={this.state.mobileOpen}
-              onClose={this.handleDrawerToggle}
-              classes={{
-                paper: classes.drawerPaper,
-              }}
-              ModalProps={{
-                // Better open performance on mobile.
-                keepMounted: true,
-              }}
-            >
-              {drawer}
-            </Drawer>
-          </Box>
-          <Box sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}>
-            <Drawer
-              className={classes.drawerContainer}
-              variant="permanent"
-              open
-              classes={{
-                paper: classes.drawerPaper,
-              }}
-            >
-              {drawer}
-            </Drawer>
-          </Box>
-          <ContentLayout>
-            <ShareUrlDialogWrapper />
-            <Switch>
-              <Redirect from="/signin" to={ROUTES.ROOT.path} />
-              <Redirect from="/login" to={ROUTES.ROOT.path} />
-              <Route
-                path={ROUTES.PROFILE.path}
-                render={() => <ProfileWrapper />}
-              />
-              <Route
-                path={ROUTES.ROOT.path}
-                exact
-                render={(props) => {
-                  if (env.data && env.data?.orgs) {
-                    return <DashboardWrapper onlyProjects={false} {...props} />
-                  } else {
-                    return <OrgFormWrapper mode={'new'} {...props} />
-                  }
-                }}
-              />
-              <Route
-                path={ROUTES.CREATE_ORG.path}
-                render={(props) => <OrgFormWrapper mode={'new'} {...props} />}
-              />
-              <Route
-                path="/:org"
-                render={(props) => (
-                  <OrganizationWrapper
-                    {...props}
-                    env={env}
-                    auth={auth}
-                    classes={classes}
-                  />
-                )}
-              />
-            </Switch>
-            <NotificationWrapper />
-          </ContentLayout>
-        </div>
-      </>
-    )
-  }
-}
-
-export default IndexPage
diff --git a/ui/packages/platform/src/components/IndexPage/IndexPageWrapper.tsx b/ui/packages/platform/src/components/IndexPage/IndexPageWrapper.tsx
deleted file mode 100644
index 156abe51..00000000
--- a/ui/packages/platform/src/components/IndexPage/IndexPageWrapper.tsx
+++ /dev/null
@@ -1,359 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import IndexPage from 'components/IndexPage/IndexPage'
-import { MatchParams, ProjectWrapperProps } from 'components/types'
-import { RouteComponentProps } from 'react-router'
-
-export interface IndexPageProps
-  extends RouteComponentProps<MatchParams>,
-    Omit<ProjectWrapperProps, 'location' | 'match'> {}
-
-export const IndexPageWrapper = (props: IndexPageProps) => {
-  const drawerWidth = 185
-
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        flex: '1 1 0',
-        zIndex: 1,
-        overflow: 'hidden',
-        position: 'relative',
-        display: 'flex',
-        fontSize: '14px',
-      },
-      appBar: {
-        zIndex: theme.zIndex.drawer + 1,
-        backgroundColor: colors.secondary2.darkDark,
-      },
-      drawerPaper: {
-        [theme.breakpoints.up('md')]: {
-          paddingTop: '40px',
-        },
-        position: 'absolute',
-        overflow: 'hidden',
-        width: drawerWidth,
-        'background-color': colors.consoleMenuBackground,
-        'border-right-color': colors.consoleStroke,
-        '& hr': {
-          backgroundColor: colors.consoleStroke,
-        },
-      },
-      drawer: {
-        minWidth: drawerWidth,
-        flexShrink: 0,
-      },
-      drawerContainer: {
-        minWidth: drawerWidth,
-      },
-      navIconHide: {
-        [theme.breakpoints.down('sm')]: {
-          display: 'inline-flex',
-        },
-        [theme.breakpoints.up('md')]: {
-          display: 'none',
-        },
-        [theme.breakpoints.up('lg')]: {
-          display: 'none',
-        },
-        marginLeft: '-14px',
-        '& svg': {
-          marginTop: '-4px',
-        },
-      },
-      rightDivider: {
-        marginLeft: 30,
-      },
-      navIconSignOut: {
-        position: 'absolute',
-        right: 0,
-        padding: 8,
-      },
-      navIconArea: {
-        position: 'absolute',
-        right: 35,
-        color: 'white',
-        textDecoration: 'none',
-      },
-      navIconProfile: {
-        padding: 8,
-      },
-      toolbar: theme.mixins.toolbar,
-      topToolbar: {
-        minHeight: 40,
-        height: 40,
-        paddingLeft: 14,
-        paddingRight: 14,
-        color: '#fff',
-      },
-      logo: {
-        color: 'white',
-        textDecoration: 'none',
-        fontSize: 16,
-      },
-      userName: {
-        position: 'absolute',
-        right: 77,
-        fontSize: 14,
-        [theme.breakpoints.down('sm')]: {
-          display: 'none',
-        },
-        [theme.breakpoints.up('md')]: {
-          display: 'block',
-        },
-        [theme.breakpoints.up('lg')]: {
-          display: 'block',
-        },
-      },
-      orgHeaderContainer: {
-        position: 'relative',
-        height: 40,
-      },
-      orgHeader: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        position: 'absolute',
-        left: '15px',
-        top: '15px',
-        fontStyle: 'normal',
-        fontWeight: 'normal',
-        fontSize: '10px',
-        lineHeight: '12px',
-        color: '#000000',
-      },
-      orgSwitcher: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        position: 'absolute',
-        right: '10px',
-        top: '10px',
-        border: '1px solid #CCD7DA',
-        borderRadius: '3px',
-        fontStyle: 'normal',
-        fontWeight: 'normal',
-        fontSize: '10px',
-        lineHeight: '12px',
-        display: 'flex',
-        alignItems: 'center',
-        textAlign: 'center',
-        color: '#808080',
-        padding: 3,
-        textTransform: 'capitalize',
-        cursor: 'pointer',
-      },
-      orgNameContainer: {
-        paddingLeft: '15px',
-        height: '35px',
-        position: 'relative',
-      },
-      orgName: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        fontStyle: 'normal',
-        fontWeight: 'bold',
-        fontSize: '14px',
-        lineHeight: '16px',
-        color: '#000000',
-        maxWidth: '125px',
-        whiteSpace: 'nowrap',
-        textOverflow: 'ellipsis',
-        overflow: 'hidden',
-        display: 'inline-block',
-      },
-      orgPlan: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        fontStyle: 'normal',
-        fontWeight: 'normal',
-        fontSize: '10px',
-        lineHeight: '11px',
-        alignItems: 'center',
-        textAlign: 'center',
-        color: '#FFFFFF',
-        backgroundColor: colors.secondary2.main,
-        padding: '1px',
-        borderRadius: '4px',
-        paddingLeft: '3px',
-        paddingRight: '3px',
-        marginLeft: 10,
-        position: 'absolute',
-        top: '2px',
-      },
-      menuItemLabel: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        fontStyle: 'normal',
-        fontWeight: 'normal',
-        fontSize: '10px',
-        lineHeight: '11px',
-        alignItems: 'center',
-        textAlign: 'center',
-        color: '#FFFFFF',
-        backgroundColor: colors.pgaiOrange,
-        padding: '1px',
-        borderRadius: '4px',
-        paddingLeft: '3px',
-        paddingRight: '3px',
-        marginLeft: 10,
-        position: 'absolute',
-        top: '10px',
-      },
-      headerLinkMenuItemLabel: {
-        position: 'static'
-      },
-      menuSectionHeader: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        fontStyle: 'normal',
-        fontWeight: 'bold',
-        fontSize: '14px',
-        lineHeight: '16px',
-        color: '#000000',
-        padding: '0px',
-        marginTop: '10px',
-      },
-      bottomFixedMenuItem: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        fontStyle: 'normal',
-        fontWeight: 'bold',
-        fontSize: '14px',
-        lineHeight: '16px',
-        color: '#000000',
-        padding: '0px',
-        marginTop: '0px',
-      },
-      menuSectionHeaderLink: {
-        textDecoration: 'none',
-        paddingTop: 12,
-        paddingBottom: 12,
-        paddingRight: 14,
-        width: '100%',
-        paddingLeft: '15px',
-        color: '#000000',
-        display: 'inline-flex',
-        alignItems: 'center',
-        whiteSpace: 'nowrap'
-      },
-      menuSectionHeaderActiveLink: {
-        textDecoration: 'none',
-        paddingTop: 12,
-        paddingBottom: 12,
-        paddingRight: 14,
-        width: '100%',
-        paddingLeft: '15px',
-        color: '#000000',
-      },
-      menuSingleSectionHeaderActiveLink: {
-        backgroundColor: colors.consoleStroke,
-      },
-      menuPointer: {
-        height: '100%',
-      },
-      navMenu: {
-        padding: '0px',
-        height: 'calc(100% - 160px)',
-        overflowY: 'auto',
-        
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      menuSectionHeaderIcon: {
-        marginRight: '13px',
-      },
-      menuItem: {
-        fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-        fontStyle: 'normal',
-        fontWeight: 'normal',
-        fontSize: '14px',
-        lineHeight: '16px',
-        color: '#000000',
-        padding: '0px',
-        position: 'relative',
-      },
-      menuItemLink: {
-        textDecoration: 'none',
-        paddingTop: 8,
-        paddingBottom: 8,
-        paddingRight: 14,
-        width: '100%',
-        paddingLeft: '43px',
-        color: '#000000',
-        position: 'relative',
-      },
-      menuItemActiveLink: {
-        textDecoration: 'none',
-        paddingTop: 8,
-        paddingBottom: 8,
-        paddingRight: 14,
-        backgroundColor: colors.consoleStroke,
-        width: '100%',
-        paddingLeft: '43px',
-        color: '#000000',
-        position: 'relative',
-      },
-      betaContainer: {
-        '& > svg': {
-          display: 'block',
-          margin: 'auto',
-        },
-        'font-family': '"Roboto", "Helvetica", "Arial", sans-serif',
-        'font-style': 'normal',
-        'font-weight': 'normal',
-        'font-size': '16px',
-        'max-width': '500px',
-        background: '#ffffff',
-        border: '1px solid ' + colors.consoleStroke,
-        margin: 'auto',
-        'border-radius': '3px',
-        padding: '40px',
-      },
-      betaWrapper: {
-        background: colors.consoleMenuBackground,
-        height: '100vh',
-        display: 'flex',
-        'align-items': 'center',
-      },
-      tosContainer: {
-        '& > svg': {
-          display: 'block',
-          margin: 'auto',
-        },
-        'font-family': '"Roboto", "Helvetica", "Arial", sans-serif',
-        'font-style': 'normal',
-        'font-weight': 'normal',
-        'font-size': '14px',
-        width: '330px',
-        background: '#ffffff',
-        border: '1px solid ' + colors.consoleStroke,
-        margin: 'auto',
-        'border-radius': '3px',
-        paddingTop: '35px',
-        paddingBottom: '35px',
-        textAlign: 'center',
-        '& > p': {
-          marginTop: '0px',
-        },
-      },
-      tosWrapper: {
-        background: colors.consoleMenuBackground,
-        height: '100vh',
-        display: 'flex',
-        'align-items': 'center',
-        '& a': {
-          textDecoration: 'underline',
-        },
-      },
-      tosAgree: {
-        marginTop: '15px',
-        display: 'inline-block',
-      },
-      navBottomFixedMenu: {
-        width: '100%',
-        borderTop: '1px solid',
-        borderColor: colors.consoleStroke,
-        padding: 0,
-        position: 'absolute',
-        bottom: 0,
-        backgroundColor: colors.consoleMenuBackground,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <IndexPage {...props} classes={classes} />
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/components/JoeConfig.tsx b/ui/packages/platform/src/components/JoeConfig.tsx
deleted file mode 100644
index 674c0e9a..00000000
--- a/ui/packages/platform/src/components/JoeConfig.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-class JoeConfig extends Component {
-  render() {
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[{ name: 'SQL Optimization' }, { name: 'Configuration' }]}
-      />
-    )
-
-    return (
-      <div>
-        {breadcrumbs}
-        <br />
-        See configuration guides in&nbsp;
-        <a href="https://gitlab.com/postgres-ai/joe" target="blank">
-          postgres-ai/joe
-        </a>
-        &nbsp;repository.
-      </div>
-    )
-  }
-}
-
-export default JoeConfig
diff --git a/ui/packages/platform/src/components/JoeHistory/JoeHistory.tsx b/ui/packages/platform/src/components/JoeHistory/JoeHistory.tsx
deleted file mode 100644
index 8811aa99..00000000
--- a/ui/packages/platform/src/components/JoeHistory/JoeHistory.tsx
+++ /dev/null
@@ -1,1069 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import qs from 'qs'
-import React, { Component, MouseEvent } from 'react'
-import dompurify from 'dompurify'
-import { formatDistanceToNowStrict } from 'date-fns'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  Button,
-  Checkbox,
-  InputAdornment,
-  IconButton,
-  OutlinedInput,
-  Tooltip,
-} from '@material-ui/core'
-import Box from '@mui/material/Box'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import {
-  ClassesType,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import Urls from '../../utils/urls'
-import format from '../../utils/format'
-import { JoeHistoryProps } from 'components/JoeHistory/JoeHistoryWrapper'
-
-interface JoeHistoryWithStylesProps extends JoeHistoryProps {
-  classes: ClassesType
-}
-
-interface TagProps {
-  classes: ClassesType
-  name: string
-  tooltip: string
-  value: string
-  onClick: (
-    event:
-      | React.MouseEvent<HTMLDivElement>
-      | React.MouseEvent<HTMLDivElement, MouseEvent>,
-  ) => void
-}
-
-interface UserProfile {
-  data: {
-    info: {
-      first_name: string
-      last_name: string
-      email: string
-    }
-  }
-}
-
-interface CommandDataProps {
-  command: string
-  created_at: Date
-  is_favorite: boolean
-  query: string
-  fingerprint: string
-  slack_username: string
-  slack_channel: string
-  username: string
-  useremail: string
-  project_name: string
-  project_id: number
-  joe_session_id: number
-  id: number
-}
-
-interface JoeHistoryState {
-  searchFilter: string
-  selectedRows: {
-    [rows: number]: number | boolean
-  }
-  data: {
-    auth?: {
-      token: string | null
-    } | null
-    userProfile: UserProfile | null
-    commands: {
-      error: {
-        isProcessing: boolean
-        isProcessed: boolean
-        isComplete: boolean
-        isHistoryExists: {
-          [id: number]: boolean
-        }[]
-        isDeleting: boolean
-      } | null
-      data: CommandDataProps[]
-      isProcessing: boolean
-      isProcessed: boolean
-      isComplete: boolean
-      isDeleting: boolean
-      isHistoryExists: {
-        [id: number]: boolean
-      }[]
-    } | null
-    projects: {
-      isProcessing: boolean
-      error: boolean
-      data: {
-        id: number
-        alias: string
-        name: string
-      }[]
-    } | null
-  } | null
-}
-
-interface queryParamsProps {
-  [params: string]: string | boolean
-}
-
-const PAGE_SIZE = 20
-
-function Tag(props: TagProps) {
-  return (
-    <div
-      className={props.classes.tag}
-      onClick={(event) => {
-        if (props.onClick) {
-          props.onClick(event)
-        }
-        event.stopPropagation()
-        return false
-      }}
-    >
-      {props.name && (
-        <span className={props.classes.tagName}>{props.name}</span>
-      )}
-      {props.tooltip ? (
-        <Tooltip
-          title={props.tooltip}
-          classes={{ tooltip: props.classes.toolTip }}
-        >
-          <span className={props.classes.tagValue}>{props.value}</span>
-        </Tooltip>
-      ) : (
-        <span className={props.classes.tagValue}>{props.value}</span>
-      )}
-    </div>
-  )
-}
-
-class JoeHistory extends Component<JoeHistoryWithStylesProps, JoeHistoryState> {
-  buildFilter() {
-    const {
-      session,
-      project,
-      command,
-      fingerprint,
-      author,
-      search,
-      is_favorite,
-    } = this.props
-    let filters = []
-    let filter = ''
-
-    if (author) {
-      filters.push('author:' + format.quoteStr(author))
-    }
-    if (command) {
-      filters.push('command:' + command)
-    }
-    if (fingerprint) {
-      filters.push('fingerprint:' + fingerprint)
-    }
-    if (project) {
-      filters.push('project:' + format.quoteStr(project))
-    }
-    if (session) {
-      filters.push('session:' + session)
-    }
-    if (search) {
-      filters.push(search)
-    }
-    if (is_favorite) {
-      filters.push('is:favorite')
-    }
-
-    filter = filters.join(' ')
-    setTimeout(
-      () => this.setState({ searchFilter: filter, selectedRows: {} }),
-      1,
-    )
-
-    return filter
-  }
-
-  addInstance() {
-    this.props.history.push(Urls.linkJoeInstanceAdd(this.props))
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const {
-      session,
-      project,
-      command,
-      fingerprint,
-      author,
-      orgId,
-      search,
-      is_favorite,
-    } = this.props
-    const dataAvailabilityFrom = null
-
-    this.buildFilter()
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const commands =
-        this.data && this.data.commands ? this.data.commands : null
-      const projects =
-        this.data && this.data.projects ? this.data.projects : null
-
-      if (
-        auth &&
-        auth.token &&
-        !commands?.isProcessing &&
-        !commands?.error &&
-        !that.state
-      ) {
-        Actions.getJoeSessionCommands(auth.token, {
-          orgId,
-          session,
-          fingerprint,
-          command,
-          project,
-          author,
-          search,
-          isFavorite: is_favorite,
-          startAt: dataAvailabilityFrom,
-          limit: PAGE_SIZE,
-        })
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects?.isProcessing &&
-        !projects?.error &&
-        !that.state
-      ) {
-        Actions.getProjects(auth.token, orgId)
-      }
-
-      that.setState({ data: this.data })
-    })
-
-    let contentContainer = document.getElementById(
-      'content-container',
-    ) as HTMLElement
-    if (contentContainer) {
-      contentContainer.addEventListener('scroll', () => {
-        if (
-          contentContainer.scrollTop >=
-          contentContainer.scrollHeight - contentContainer.offsetHeight && !this.state.data?.commands?.isComplete
-        ) {
-          this.showMore()
-        }
-      })
-    }
-
-    Actions.refresh()
-  }
-
-  showMore() {
-    const {
-      orgId,
-      session,
-      project,
-      command,
-      fingerprint,
-      author,
-      is_favorite,
-      search,
-    } = this.props
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const commands =
-      this.state.data && this.state.data.commands
-        ? this.state.data.commands
-        : null
-    const dataAvailabilityFrom = null
-    let lastId = null
-
-    if (commands && commands.data && commands.data.length) {
-      lastId = commands.data[commands.data.length - 1].id
-    }
-
-    if (auth && auth.token && !commands?.isProcessing && lastId) {
-      Actions.getJoeSessionCommands(auth.token, {
-        orgId,
-        session,
-        fingerprint,
-        command,
-        project,
-        author,
-        search,
-        isFavorite: is_favorite,
-        startAt: dataAvailabilityFrom,
-        limit: PAGE_SIZE,
-        lastId,
-      })
-    }
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  onCommandClick(
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    project: string,
-    sessionId: number,
-    id: number,
-  ) {
-    const { org } = this.props
-
-    this.props.history.push(
-      '/' + org + '/' + project + '/sessions/' + sessionId + '/commands/' + id,
-    )
-  }
-
-  setFilter(value: string) {
-    this.setState({
-      searchFilter: value,
-    })
-
-    this.applyFilter(value)
-  }
-
-  getQueryParams(filterValue: string) {
-    let queryParams: queryParamsProps = {}
-    let filters = filterValue.split(/[;,(\s)]/)
-
-    for (let f in filters) {
-      if (!filters.hasOwnProperty(f)) {
-        continue
-      }
-
-      let filter = filters[f].split(/[:=]/)
-
-      if (filter.length > 1 && filter[1].length) {
-        if (filter[0].trim().toLowerCase().startsWith('is')) {
-          queryParams[filter[0].trim() + '_' + filter[1]] = true
-        } else {
-          queryParams[filter[0].trim()] = filter[1]
-        }
-      }
-    }
-
-    return queryParams
-  }
-
-  applyFilter(value?: string, changeUrl = true) {
-    const { orgId } = this.props
-    let filterValue =
-      typeof value !== 'undefined' ? value : this.state.searchFilter
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const dataAvailabilityFrom = null
-    let queryParams: queryParamsProps = {}
-
-    let filters = filterValue?.split('"')
-    if (Array.isArray(filters) && filters.length > 1) {
-      for (let i = 0; i < filters.length; i++) {
-        if (
-          i > 0 &&
-          (filters[i - 1].endsWith('=') || filters[i - 1].endsWith(':'))
-        ) {
-          let paramName: string | string[] = filters[i - 1].split(/[;,(\s)]/)
-          if (Array.isArray(paramName)) {
-            paramName = paramName[paramName.length - 1].trim()
-            paramName = paramName.substring(0, paramName.length - 1)
-          }
-          queryParams[paramName] = filters[i]
-        } else {
-          queryParams = {
-            ...queryParams,
-            ...this.getQueryParams(filters[i]),
-          }
-        }
-      }
-    } else {
-      queryParams = this.getQueryParams(filterValue)
-    }
-
-    let search = filterValue
-    for (let f in queryParams) {
-      if (!queryParams.hasOwnProperty(f)) {
-        continue
-      }
-      if (f.startsWith('is')) {
-        search = search?.split(f.replace('_', ':')).join('')
-      } else {
-        search = search
-          ?.split(f + ':' + format.quoteStr(queryParams[f]))
-          .join('')
-        search = search?.split(f + ':"' + queryParams[f] + '"').join('')
-        search = search
-          ?.split(f + '=' + format.quoteStr(queryParams[f]))
-          .join('')
-        search = search?.split(f + '="' + queryParams[f] + '"').join('')
-      }
-    }
-
-    queryParams.search = search?.trim()
-
-    Object.keys(queryParams).forEach(
-      (key) =>
-        (queryParams[key] === null || queryParams[key] === '') &&
-        delete queryParams[key],
-    )
-
-    if (changeUrl) {
-      let url = ''
-      let org = this.props.org
-      url = '/' + org + '/sessions/'
-      url = url + '?' + qs.stringify(queryParams)
-      this.props.history.push(url)
-    }
-
-    if (auth && auth.token) {
-      Actions.getJoeSessionCommands(auth.token, {
-        ...queryParams,
-        orgId: orgId,
-        isFavorite: queryParams['is_favorite'],
-        startAt: dataAvailabilityFrom,
-        limit: PAGE_SIZE,
-      })
-    }
-  }
-
-  clearFilter() {
-    this.setState({ searchFilter: '' })
-    this.applyFilter('')
-  }
-
-  clearFilters() {
-    const { auth, org, orgId } = this.props
-    const dataAvailabilityFrom = null
-
-    const url = '/' + org + '/sessions/'
-    this.props.history.push(url)
-
-    if (auth && auth.token) {
-      Actions.getJoeSessionCommands(auth.token, {
-        orgId,
-        startAt: dataAvailabilityFrom,
-        limit: PAGE_SIZE,
-      })
-    }
-  }
-
-  getAuthor(command: CommandDataProps) {
-    if (command['slack_username']) {
-      return command['slack_username']
-    }
-
-    if (command.username) {
-      return command.username
-    }
-
-    return command.useremail
-  }
-
-  getCurrentUser() {
-    let userName: string | string[] = []
-    if (this.state.data?.userProfile?.data.info.first_name) {
-      userName.push(this.state.data.userProfile.data.info.first_name)
-    }
-    if (this.state.data?.userProfile?.data.info.last_name) {
-      userName.push(this.state.data.userProfile.data.info.last_name)
-    }
-
-    userName = userName.join(' ').trim()
-    if (userName) {
-      return userName
-    }
-
-    return String(this.state.data?.userProfile?.data.info.email)
-  }
-
-  getSessionId(command: CommandDataProps) {
-    return command['joe_session_id']
-  }
-
-  getProject(command: CommandDataProps) {
-    return command['project_name']
-  }
-
-  getChannel(command: CommandDataProps) {
-    return command['slack_channel']
-  }
-
-  onSelectRow(event: React.ChangeEvent<HTMLInputElement>, rowId: number) {
-    let selectedRows = this.state.selectedRows
-
-    if (selectedRows[rowId] && !event.target.checked) {
-      delete selectedRows[rowId]
-    } else {
-      selectedRows[rowId] = event.target.checked
-    }
-
-    this.setState({ selectedRows: selectedRows })
-  }
-
-  onSelectAllClick(
-    event: React.ChangeEvent<HTMLInputElement>,
-    commands: CommandDataProps[],
-  ) {
-    if (!event.target.checked) {
-      this.setState({ selectedRows: {} })
-      return
-    }
-
-    let selectedRows: { [rows: number]: number | boolean } = {}
-    for (let i in commands) {
-      if (commands.hasOwnProperty(i)) {
-        selectedRows[commands[i].id] = true
-      }
-    }
-
-    this.setState({ selectedRows: selectedRows })
-  }
-
-  deleteCommands() {
-    const count = Object.keys(this.state.selectedRows).length
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    /* eslint no-alert: 0 */
-    if (
-      window.confirm(
-        'Are you sure you want to delete ' + count + ' command(s)?',
-      ) === true
-    ) {
-      let commands = []
-      for (let i in this.state.selectedRows) {
-        if (this.state.selectedRows.hasOwnProperty(i)) {
-          commands.push(parseInt(i, 10))
-        }
-      }
-
-      Actions.deleteJoeCommands(auth?.token, commands)
-      this.setState({ selectedRows: {} })
-    }
-  }
-
-  render() {
-    const that = this
-    const { classes, auth, org, orgId } = this.props
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={org}
-        breadcrumbs={[{ name: 'SQL optimization history', url: null }]}
-      />
-    )
-
-    const pageTitle = <ConsolePageTitle title="SQL optimization history" />
-
-    if (!this.state || !this.state.data) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const commandStore = this.state.data.commands || null
-    const projectsStore = this.state.data.projects || null
-    const projects = projectsStore?.data || []
-    const commands = commandStore?.data || []
-
-    const isFilterAvailable =
-      (commandStore &&
-        commandStore.isHistoryExists &&
-        commandStore.isHistoryExists[orgId]) ||
-      commands.length > 0 ||
-      (commands.length === 0 &&
-        (this.state.searchFilter ? this.state.searchFilter : '') !== '')
-    const searchFilter = (
-      <div className={classes.filterContainer}>
-        <Button
-          variant="outlined"
-          className={classes.filterButton}
-          disabled={commandStore !== null && commandStore.isProcessing}
-          onClick={() =>
-            this.setFilter('author:' + format.quoteStr(this.getCurrentUser()))
-          }
-        >
-          <span className={classes.whiteSpace}>My history</span>
-        </Button>
-        {
-          <Button
-            variant="outlined"
-            className={classes.filterButton}
-            disabled={commandStore !== null && commandStore.isProcessing}
-            onClick={() => this.setFilter('is:favorite')}
-          >
-            <span className={classes.whiteSpace}>Favorites</span>
-          </Button>
-        }
-        <OutlinedInput
-          value={that.state.searchFilter}
-          onChange={(event) =>
-            this.setState({ searchFilter: event.target.value })
-          }
-          onKeyDown={(event) => {
-            if (event.keyCode === 13) {
-              this.applyFilter()
-            }
-          }}
-          inputProps={{
-            name: 'searchFilter',
-            id: 'searchFilter',
-          }}
-          endAdornment={
-            that.state.searchFilter && that.state.searchFilter.length ? (
-              <InputAdornment position="end">
-                <IconButton
-                  onClick={() => this.clearFilter()}
-                  onMouseDown={() => this.clearFilter()}
-                  edge="end"
-                >
-                  {icons.closeIcon}
-                </IconButton>
-              </InputAdornment>
-            ) : null
-          }
-          startAdornment={
-            <InputAdornment position="start" className={classes.searchIcon}>
-              <IconButton edge="start" disabled className={classes.searchIcon}>
-                {icons.searchIcon}
-              </IconButton>
-            </InputAdornment>
-          }
-          fullWidth
-          className={classes.searchFilter}
-        />
-      </div>
-    )
-
-    if (commandStore && commandStore.error) {
-      return (
-        <div>
-          {breadcrumbs}
-          {pageTitle}
-          {isFilterAvailable && searchFilter}
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (!commandStore || !commandStore.data) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-        {pageTitle}
-
-        <div>{isFilterAvailable && searchFilter}</div>
-
-        {commandStore && commandStore.isProcessing && (
-          <div>
-            <Box className={classes.centeredBox}>
-              <Spinner size="lg" className={classes.centeredProgress} />
-            </Box>
-          </div>
-        )}
-
-        {commands && commands.length > 0 ? (
-          <div>
-            <HorizontalScrollContainer>
-              <Table className={classes.table}>
-                {this.props.userIsOwner && (
-                  <TableHead>
-                    <TableRow className={classes.row}>
-                      {this.props.userIsOwner && (
-                        <TableCell className={classes.headCheckboxTableCell}>
-                          <Checkbox
-                            indeterminate={
-                              Object.keys(this.state.selectedRows).length > 0
-                            }
-                            checked={
-                              Object.keys(this.state.selectedRows).length ===
-                              commands.length
-                            }
-                            onChange={(event) =>
-                              this.onSelectAllClick(event, commands)
-                            }
-                          />
-                        </TableCell>
-                      )}
-                      <TableCell className={classes.tableHead}>
-                        {this.props.userIsOwner && (
-                          <div>
-                            {commandStore.isDeleting ? (
-                              <span>Processing...</span>
-                            ) : (
-                              <div>
-                                {Object.keys(this.state.selectedRows).length >
-                                0 ? (
-                                  <span>
-                                    Selected:{' '}
-                                    {
-                                      Object.keys(this.state.selectedRows)
-                                        .length
-                                    }{' '}
-                                    rows
-                                  </span>
-                                ) : (
-                                  'Select table rows to process them'
-                                )}
-                              </div>
-                            )}
-                            <div className={classes.tableHeadActions}>
-                              <Button
-                                variant="contained"
-                                color="primary"
-                                disabled={
-                                  Object.keys(this.state.selectedRows)
-                                    .length === 0 || commandStore.isDeleting
-                                }
-                                onClick={() => this.deleteCommands()}
-                              >
-                                Delete
-                                {commandStore && commandStore.isDeleting ? (
-                                  <span>
-                                    &nbsp;
-                                    <Spinner size="sm" />
-                                  </span>
-                                ) : (
-                                  ''
-                                )}
-                              </Button>
-                            </div>
-                          </div>
-                        )}
-                      </TableCell>
-                    </TableRow>
-                  </TableHead>
-                )}
-
-                <TableBody>
-                  {commands.map((c: CommandDataProps) => {
-                    if (c) {
-                      const project = projects?.find(project => project.id === c['project_id']);
-                      const projectAlias = project?.alias || project?.name || '';
-
-                      return (
-                        <TableRow
-                          hover={false}
-                          className={classes.row}
-                          key={c.id}
-                          onClick={(event) => {
-                            this.onCommandClick(
-                              event,
-                              projectAlias,
-                              this.getSessionId(c),
-                              c.id,
-                            )
-                            return false
-                          }}
-                          style={{ cursor: 'pointer' }}
-                        >
-                          {this.props.userIsOwner && (
-                            <TableCell className={classes.checkboxTableCell}>
-                              <Checkbox
-                                checked={!!this.state.selectedRows[c.id]}
-                                onChange={(event) =>
-                                  this.onSelectRow(event, c.id)
-                                }
-                              />
-                            </TableCell>
-                          )}
-                          <TableCell className={classes.cardCell}>
-                            <div className={classes.twoSideRow}>
-                              <div className={classes.twoSideCol1}>
-                                {c.command && (
-                                  <Tag
-                                    classes={classes}
-                                    name="command"
-                                    value={c.command.substring(0, 30)}
-                                    onClick={() =>
-                                      this.setFilter('command:' + c.command)
-                                    }
-                                    tooltip={''}
-                                  />
-                                )}
-
-                                {this.getAuthor(c) && (
-                                  <Tag
-                                    classes={classes}
-                                    name="author"
-                                    value={this.getAuthor(c)}
-                                    onClick={() =>
-                                      this.setFilter(
-                                        'author:' +
-                                          format.quoteStr(this.getAuthor(c)),
-                                      )
-                                    }
-                                    tooltip={''}
-                                  />
-                                )}
-
-                                <Tooltip
-                                  title={
-                                    format.formatTimestampUtc(
-                                      c['created_at'],
-                                    ) ?? ''
-                                  }
-                                  classes={{ tooltip: classes.toolTip }}
-                                >
-                                  <span className={classes.timeLabel}>
-                                    {formatDistanceToNowStrict(
-                                      new Date(c.created_at),
-                                      { addSuffix: true },
-                                    )}
-                                  </span>
-                                </Tooltip>
-                              </div>
-                              {
-                                <div className={classes.twoSideCol2}>
-                                  {!!c.is_favorite && (
-                                    <span
-                                      onClick={(event) => {
-                                        Actions.joeCommandFavorite(
-                                          auth?.token,
-                                          c.id,
-                                          !c.is_favorite,
-                                        )
-                                        event.stopPropagation()
-                                      }}
-                                    >
-                                      {icons.favoriteOnIcon}
-                                    </span>
-                                  )}
-                                  {!c.is_favorite && (
-                                    <span
-                                      onClick={(event) => {
-                                        Actions.joeCommandFavorite(
-                                          auth?.token,
-                                          c.id,
-                                          !c.is_favorite,
-                                        )
-                                        event.stopPropagation()
-                                      }}
-                                    >
-                                      {icons.favoriteOffIcon}
-                                    </span>
-                                  )}
-                                </div>
-                              }
-                            </div>
-
-                            {c.query && (
-                              <div
-                                className={classes.query}
-                                dangerouslySetInnerHTML={{
-                                  __html: dompurify.sanitize(
-                                    format.formatSql(c.query),
-                                  ),
-                                }}
-                              />
-                            )}
-
-                            <div className={classes.twoSideRow}>
-                              <div className={classes.twoSideCol1}>
-                                <Tag
-                                  classes={classes}
-                                  name="project"
-                                  value={this.getProject(c)}
-                                  onClick={() =>
-                                    this.setFilter(
-                                      'project:' + this.getProject(c),
-                                    )
-                                  }
-                                  tooltip={''}
-                                />
-
-                                {this.getChannel(c) && (
-                                  <Tag
-                                    classes={classes}
-                                    name="channel"
-                                    value={this.getChannel(c)}
-                                    onClick={() =>
-                                      this.setFilter(
-                                        'channel:' + this.getChannel(c),
-                                      )
-                                    }
-                                    tooltip={''}
-                                  />
-                                )}
-
-                                <Tag
-                                  classes={classes}
-                                  name="session"
-                                  value={'#' + this.getSessionId(c)}
-                                  onClick={() =>
-                                    this.setFilter(
-                                      'session:' + this.getSessionId(c),
-                                    )
-                                  }
-                                  tooltip={''}
-                                />
-                              </div>
-                              <div className={classes.twoSideCol2}>
-                                {c.fingerprint && (
-                                  <Tag
-                                    classes={classes}
-                                    value="find similar"
-                                    tooltip={'fingerprint: ' + c.fingerprint}
-                                    onClick={() =>
-                                      this.setFilter(
-                                        'fingerprint:' + c.fingerprint,
-                                      )
-                                    }
-                                    name={'fingerprint'}
-                                  />
-                                )}
-                              </div>
-                            </div>
-                          </TableCell>
-                        </TableRow>
-                      )
-                    }
-
-                    return null
-                  })}
-                </TableBody>
-              </Table>
-            </HorizontalScrollContainer>
-            <div className={classes.showMoreContainer}>
-              {commandStore && commandStore.isProcessing && !commandStore.isComplete && (
-                <Spinner size="lg" className={classes.progress} />
-              )}
-              {commandStore &&
-                !commandStore.isProcessing &&
-                !commandStore.isComplete && (
-                  <Button
-                    variant="outlined"
-                    color="secondary"
-                    className={classes.button}
-                    onClick={() => this.showMore()}
-                    disabled={commandStore && commandStore.isProcessing}
-                  >
-                    Show more
-                  </Button>
-                )}
-            </div>
-          </div>
-        ) : null}
-
-        {commands && commands.length === 0 && commandStore.isProcessed && (
-          <StubContainer>
-            <ProductCardWrapper
-              inline
-              title={
-                this.state.searchFilter === ''
-                  ? 'No bot – no history (yet)'
-                  : 'No commands matching the filters.'
-              }
-              actions={
-                this.state.searchFilter !== ''
-                  ? [
-                      {
-                        id: 'clearFiltersButton',
-                        content: (
-                          <Button
-                            variant="contained"
-                            color="primary"
-                            disabled={commandStore && commandStore.isProcessing}
-                            onClick={() => this.clearFilter()}
-                          >
-                            <span className={classes.whiteSpace}>
-                              Clear filters
-                            </span>
-                          </Button>
-                        ),
-                      },
-                    ]
-                  : [
-                      {
-                        id: 'addInstanceButton',
-                        content: (
-                          <Button
-                            variant="contained"
-                            color="primary"
-                            disabled={commandStore && commandStore.isProcessing}
-                            onClick={() => this.addInstance()}
-                          >
-                            <span className={classes.whiteSpace}>Add Joe</span>
-                          </Button>
-                        ),
-                      },
-                    ]
-              }
-              icon={icons.joeHistoryLogo}
-            >
-              {this.state.searchFilter === '' ? (
-                <p>
-                  Joe Bot is a virtual DBA helping engineers analyze and
-                  optimize PostgreSQL queries. It provides a convenient,
-                  chat-like interface to full-size Postgres clones that are
-                  provisioned and seconds, behaves as production (same execution
-                  plans, same data volumes), writable and isolated for safe
-                  "what if" experiments to check various optimization ideas (
-                  <GatewayLink
-                    href="https://postgres.ai/docs/joe"
-                    target="_blank"
-                  >
-                    Learn more
-                  </GatewayLink>
-                  ).
-                </p>
-              ) : (
-                <p>
-                  We couldn't find any commands matching your filters, try
-                  another.
-                </p>
-              )}
-            </ProductCardWrapper>
-          </StubContainer>
-        )}
-      </div>
-    )
-  }
-}
-
-export default JoeHistory
diff --git a/ui/packages/platform/src/components/JoeHistory/JoeHistoryWrapper.tsx b/ui/packages/platform/src/components/JoeHistory/JoeHistoryWrapper.tsx
deleted file mode 100644
index 3a078743..00000000
--- a/ui/packages/platform/src/components/JoeHistory/JoeHistoryWrapper.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import JoeHistory from 'components/JoeHistory/JoeHistory'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { QueryParamsType } from 'components/types'
-import { RouteComponentProps } from 'react-router'
-
-export interface JoeHistoryProps extends QueryParamsType {
-  auth: {
-    token: string
-  } | null
-  orgId: number
-  org: string | number
-  userIsOwner: boolean
-  history: RouteComponentProps['history']
-}
-
-export const JoeHistoryWrapper = (props: JoeHistoryProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        ...(styles.root as Object),
-        display: 'flex',
-        flexDirection: 'column',
-        paddingBottom: '20px',
-      },
-      filterSelect: {
-        ...styles.inputField,
-        width: 150,
-        marginRight: '15px',
-      },
-      searchFilter: {
-        ...styles.inputField,
-        '& input.MuiInputBase-input': {
-          paddingLeft: '0px!important',
-        },
-      },
-      checkboxTableCell: {
-        width: '30px',
-        padding: 0,
-        paddingTop: 10,
-        verticalAlign: 'top',
-      },
-      tag: {
-        border: '1px solid ' + colors.consoleStroke,
-        borderRadius: 3,
-        fontSize: 10,
-        lineHeight: '16px',
-        display: 'inline-block',
-        marginRight: 8,
-        backgroundColor: 'white',
-        '&:last-child': {
-          marginRight: 0,
-        },
-      },
-      tagName: {
-        fontSize: 10,
-        color: colors.pgaiDarkGray,
-        borderRight: '1px solid ' + colors.consoleStroke,
-        paddingLeft: 3,
-        paddingRight: 3,
-        backgroundColor: 'white',
-      },
-      tagValue: {
-        fontSize: 10,
-        color: colors.secondary2.main,
-        paddingLeft: 3,
-        paddingRight: 3,
-        backgroundColor: 'white',
-      },
-      twoSideRow: {
-        display: 'flex',
-      },
-      twoSideCol1: {},
-      twoSideCol2: {
-        flexGrow: 1,
-        textAlign: 'right',
-      },
-      timeLabel: {
-        lineHeight: '16px',
-        fontSize: 12,
-        color: colors.pgaiDarkGray,
-      },
-      query: {
-        marginTop: 10,
-        marginBottom: 10,
-        fontSize: 14,
-        lineHeight: '16px',
-        fontFamily:
-          '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-          ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-        color: 'black',
-        '& > b': {
-          color: colors.secondary2.main,
-        },
-        height: 32,
-        overflow: 'hidden',
-        'text-overflow': 'ellipsis',
-        '-webkit-line-clamp': '2',
-        '-webkit-box-orient': 'vertical',
-        display: '-webkit-box',
-      },
-      showMoreContainer: {
-        marginTop: 20,
-        textAlign: 'center',
-      },
-      cardCell: {
-        paddingLeft: 0,
-        paddingRight: 0,
-      },
-      toolTip: {
-        fontSize: '10px!important',
-        maxWidth: '420px!important',
-      },
-      searchIcon: {
-        marginRight: 0,
-      },
-      filterContainer: {
-        display: 'flex',
-        [theme.breakpoints.down('sm')]: {
-          display: 'block',
-        },
-      },
-      filterButton: {
-        height: '32px',
-        marginRight: 10,
-        marginBottom: 10,
-      },
-      tableHead: {
-        ...(styles.tableHead as Object),
-        fontWeight: 'normal',
-        padding: 0,
-        paddingBottom: 5,
-        paddingTop: 5,
-      },
-      tableHeadActions: {
-        ...(styles.tableHeadActions as Object),
-      },
-      headCheckboxTableCell: {
-        width: '30px',
-        padding: 0,
-        verticalAlign: 'top',
-        paddingBottom: 5,
-        paddingTop: 5,
-      },
-      centeredBox: {
-        display: 'flex',
-        justifyContent: 'center',
-        height: 'calc(100vh - 250px)',
-        mixHeight: '220px',
-      },
-      centeredProgress: {
-        marginTop: 'calc((100vh - 250px) / 2 - 30px)',
-      },
-      whiteSpace: {
-        whiteSpace: 'nowrap',
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <JoeHistory {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/JoeInstanceForm/JoeInstanceForm.tsx b/ui/packages/platform/src/components/JoeInstanceForm/JoeInstanceForm.tsx
deleted file mode 100644
index 39c9c078..00000000
--- a/ui/packages/platform/src/components/JoeInstanceForm/JoeInstanceForm.tsx
+++ /dev/null
@@ -1,502 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { Checkbox, Grid, Button, TextField } from '@material-ui/core'
-import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline'
-import BlockIcon from '@material-ui/icons/Block'
-import WarningIcon from '@material-ui/icons/Warning'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from './../ConsolePageTitle'
-import Urls from '../../utils/urls'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import FormControlLabel from '@material-ui/core/FormControlLabel'
-import { generateToken, isHttps } from 'utils/utils'
-import { JoeInstanceFormProps } from 'components/JoeInstanceForm/JoeInstanceFormWrapper'
-
-interface JoeInstanceFormWithStylesProps extends JoeInstanceFormProps {
-  classes: ClassesType
-}
-
-interface JoeInstanceFormState {
-  data: {
-    auth: {
-      token: string
-    } | null
-    newJoeInstance: {
-      isUpdating: boolean
-      isChecking: boolean
-      isCheckProcessed: boolean
-      isChecked: boolean
-      isProcessed: boolean
-      isProcessing: boolean
-      error: boolean
-      errorMessage: string | null
-    } | null
-    joeInstances: {
-      isProcessed: boolean
-      isProcessing: boolean
-      error: boolean
-      data: unknown | null
-    } | null
-  } | null
-  url: string
-  useTunnel: boolean
-  token: string
-  project: string
-  errorFields: string[]
-  sshServerUrl: string
-}
-
-class JoeInstanceForm extends Component<
-  JoeInstanceFormWithStylesProps,
-  JoeInstanceFormState
-> {
-  state = {
-    url: 'https://',
-    useTunnel: false,
-    token: '',
-    project: this.props.project ? this.props.project : '',
-    errorFields: [''],
-    sshServerUrl: '',
-    data: {
-      auth: {
-        token: '',
-      },
-      newJoeInstance: {
-        isUpdating: false,
-        isChecking: false,
-        isCheckProcessed: false,
-        isChecked: false,
-        isProcessed: false,
-        isProcessing: false,
-        error: false,
-        errorMessage: null,
-      },
-      joeInstances: {
-        isProcessed: false,
-        isProcessing: false,
-        error: false,
-        data: null,
-      },
-    },
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const { orgId } = this.props
-
-    this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const joeInstances =
-        this.data && this.data.joeInstances ? this.data.joeInstances : null
-
-      if (
-        auth &&
-        auth.token &&
-        !joeInstances?.isProcessing &&
-        !joeInstances?.error &&
-        !joeInstances?.isProcessed
-      ) {
-        Actions.getJoeInstances(auth.token, orgId, 0)
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-    Actions.resetNewJoeInstance()
-  }
-
-  buttonHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.newJoeInstance : null
-    let errorFields: JoeInstanceFormState['errorFields'] = []
-
-    if (!this.state.url) {
-      errorFields.push('url')
-    }
-
-    if (!this.state.project) {
-      errorFields.push('project')
-    }
-
-    if (!this.state.token) {
-      errorFields.push('token')
-    }
-
-    if (errorFields.length > 0) {
-      this.setState({ errorFields: errorFields })
-      return
-    }
-
-    this.setState({ errorFields: [] })
-
-    if (
-      auth &&
-      data &&
-      !data.isUpdating &&
-      this.state.url &&
-      this.state.token &&
-      this.state.token &&
-      this.state.project
-    ) {
-      Actions.addJoeInstance(auth.token, {
-        orgId: orgId,
-        project: this.state.project,
-        url: this.state.url,
-        verifyToken: this.state.token,
-        useTunnel: this.state.useTunnel,
-        sshServerUrl: this.state.sshServerUrl,
-      })
-    }
-  }
-
-  checkUrlHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.newJoeInstance : null
-    let errorFields: JoeInstanceFormState['errorFields'] = []
-
-    if (!this.state.url) {
-      errorFields.push('url')
-      return
-    }
-
-    if (auth && data && !data.isChecking && this.state.url) {
-      Actions.checkJoeInstanceUrl(auth.token, {
-        orgId: orgId,
-        project: this.state.project,
-        url: this.state.url,
-        verifyToken: this.state.token,
-        useTunnel: this.state.useTunnel,
-      })
-    }
-  }
-
-  returnHandler = () => {
-    this.props.history.push(Urls.linkJoeInstances(this.props))
-  }
-
-  generateTokenHandler = () => {
-    this.setState({ token: generateToken() })
-  }
-
-  render() {
-    const { classes, orgPermissions } = this.props
-    const data =
-      this.state && this.state.data ? this.state.data.newJoeInstance : null
-
-    if (data && data.isProcessed && !data.error) {
-      this.returnHandler()
-      Actions.resetNewJoeInstance()
-    }
-
-    const joeInstances =
-      this.state && this.state.data && this.state.data.joeInstances
-        ? this.state.data.joeInstances
-        : null
-
-    const permitted = !orgPermissions || orgPermissions.joeInstanceCreate
-
-    const instancesLoaded = joeInstances !== null && joeInstances?.data
-
-    if (!data || !instancesLoaded) {
-      return (
-        <div>
-          <Spinner size="lg" className={classes.progress} />
-        </div>
-      )
-    }
-
-    const isDataUpdating = data && (data.isUpdating || data.isChecking)
-
-    return (
-      <div className={classes.root}>
-        <ConsoleBreadcrumbsWrapper
-          org={this.props.org}
-          project={this.props.project}
-          breadcrumbs={[
-            { name: 'SQL Optimization', url: null },
-            { name: 'Joe Instances', url: 'joe-instances' },
-            { name: 'Add instance' },
-          ]}
-        />
-
-        <ConsolePageTitle title="Add instance" />
-
-        {!permitted && (
-          <WarningWrapper>
-            You do not have permission to add Joe instances.
-          </WarningWrapper>
-        )}
-
-        <span>
-          Joe provisioning is currently semi-automated.
-          <br />
-          First, you need to prepare a Joe instance on a separate&nbsp; machine.
-          Once the instance is ready, register it here.
-        </span>
-
-        <div className={classes.errorMessage}>
-          {data.errorMessage ? data.errorMessage : null}
-        </div>
-
-        <Grid container>
-          <div className={classes.fieldBlock}>
-            <TextField
-              variant="outlined"
-              id="project"
-              disabled={!permitted}
-              label="Project"
-              value={this.state.project}
-              required
-              className={classes.textField}
-              onChange={(e) => {
-                this.setState({
-                  project: e.target.value,
-                })
-                Actions.resetNewJoeInstance()
-              }}
-              margin="normal"
-              error={this.state.errorFields.indexOf('project') !== -1}
-              fullWidth
-              inputProps={{
-                name: 'project',
-                id: 'project',
-                shrink: 'true',
-              }}
-              InputLabelProps={{
-                shrink: true,
-                style: styles.inputFieldLabel,
-              }}
-              FormHelperTextProps={{
-                style: styles.inputFieldHelper,
-              }}
-            />
-          </div>
-
-          <div className={classes.fieldBlock}>
-            <TextField
-              variant="outlined"
-              id="token"
-              disabled={!permitted}
-              label="Signing secret"
-              value={this.state.token}
-              required
-              className={classes.textField}
-              onChange={(e) => {
-                this.setState({
-                  token: e.target.value,
-                })
-                Actions.resetNewJoeInstance()
-              }}
-              margin="normal"
-              error={this.state.errorFields.indexOf('token') !== -1}
-              fullWidth
-              inputProps={{
-                name: 'token',
-                id: 'token',
-                shrink: 'true',
-              }}
-              InputLabelProps={{
-                shrink: true,
-                style: styles.inputFieldLabel,
-              }}
-              FormHelperTextProps={{
-                style: styles.inputFieldHelper,
-              }}
-            />
-            <div>
-              <Button
-                variant="contained"
-                color="primary"
-                disabled={isDataUpdating || !permitted}
-                onClick={this.generateTokenHandler}
-              >
-                Generate
-              </Button>
-            </div>
-          </div>
-
-          <div className={classes.fieldBlock} style={{ marginTop: 10 }}>
-            <TextField
-              variant="outlined"
-              id="url"
-              disabled={!permitted}
-              label="URL"
-              required
-              value={this.state.url}
-              className={classes.textField}
-              onChange={(e) => {
-                this.setState({
-                  url: e.target.value,
-                })
-                Actions.resetNewJoeInstance()
-              }}
-              margin="normal"
-              helperText={
-                this.state.url &&
-                !isHttps(this.state.url) &&
-                !this.state.useTunnel ? (
-                  <span>
-                    <WarningIcon className={classes.warningIcon} />
-                    <span className={classes.warning}>
-                      The connection to the Joe API is not secure. Use HTTPS.
-                    </span>
-                  </span>
-                ) : null
-              }
-              error={this.state.errorFields.indexOf('url') !== -1}
-              fullWidth
-              inputProps={{
-                name: 'url',
-                id: 'url',
-                shrink: 'true',
-              }}
-              InputLabelProps={{
-                shrink: true,
-                style: styles.inputFieldLabel,
-              }}
-              FormHelperTextProps={{
-                style: styles.inputFieldHelper,
-              }}
-            />
-
-            <div className={classes.fieldBlock}>
-              <FormControlLabel
-                control={
-                  <Checkbox
-                    disabled={!permitted}
-                    checked={this.state.useTunnel}
-                    onChange={(e) => {
-                      this.setState({
-                        useTunnel: e.target.checked,
-                      })
-                      Actions.resetNewJoeInstance()
-                    }}
-                    id="useTunnel"
-                    name="useTunnel"
-                  />
-                }
-                label="Use tunnel"
-                labelPlacement="end"
-              />
-              {this.state.useTunnel && (
-                <div>
-                  <TextField
-                    variant="outlined"
-                    id="token"
-                    label="SSH server URL"
-                    value={this.state.sshServerUrl}
-                    className={classes.textField}
-                    onChange={(e) => {
-                      this.setState({
-                        sshServerUrl: e.target.value,
-                      })
-                      Actions.resetNewJoeInstance()
-                    }}
-                    margin="normal"
-                    error={
-                      this.state.errorFields.indexOf('sshServerUrl') !== -1
-                    }
-                    fullWidth
-                    inputProps={{
-                      name: 'sshServerUrl',
-                      id: 'sshServerUrl',
-                      shrink: true,
-                    }}
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                </div>
-              )}
-            </div>
-          </div>
-
-          <div>
-            <Button
-              variant="contained"
-              color="primary"
-              disabled={isDataUpdating || !permitted}
-              onClick={this.checkUrlHandler}
-            >
-              Verify URL
-            </Button>
-
-            {data.isCheckProcessed &&
-            data.isChecked &&
-            (isHttps(this.state.url) || this.state.useTunnel) ? (
-              <span className={classes.urlOk}>
-                <CheckCircleOutlineIcon className={classes.urlOkIcon} />{' '}
-                Verified
-              </span>
-            ) : null}
-
-            {data.isCheckProcessed &&
-            data.isChecked &&
-            !isHttps(this.state.url) &&
-            !this.state.useTunnel ? (
-              <span className={classes.urlFail}>
-                <BlockIcon className={classes.urlFailIcon} /> Verified but is
-                not secure
-              </span>
-            ) : null}
-
-            {data.isCheckProcessed && !data.isChecked ? (
-              <span className={classes.urlFail}>
-                <BlockIcon className={classes.urlFailIcon} /> Not available
-              </span>
-            ) : null}
-          </div>
-
-          <div className={classes.fieldBlock} style={{ marginTop: 40 }}>
-            <Button
-              variant="contained"
-              color="primary"
-              disabled={isDataUpdating || !permitted}
-              onClick={this.buttonHandler}
-            >
-              Add
-            </Button>
-            &nbsp;&nbsp;
-            <Button
-              variant="contained"
-              color="primary"
-              disabled={isDataUpdating || !permitted}
-              onClick={this.returnHandler}
-            >
-              Cancel
-            </Button>
-          </div>
-        </Grid>
-      </div>
-    )
-  }
-}
-
-export default JoeInstanceForm
diff --git a/ui/packages/platform/src/components/JoeInstanceForm/JoeInstanceFormWrapper.tsx b/ui/packages/platform/src/components/JoeInstanceForm/JoeInstanceFormWrapper.tsx
deleted file mode 100644
index 10a6f258..00000000
--- a/ui/packages/platform/src/components/JoeInstanceForm/JoeInstanceFormWrapper.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import JoeInstanceForm from 'components/JoeInstanceForm/JoeInstanceForm'
-
-export interface JoeInstanceFormProps {
-  project: string | undefined
-  orgId: number
-  org: string | number
-  history: RouteComponentProps['history']
-  orgPermissions?: {
-    joeInstanceCreate?: boolean
-  }
-}
-
-export const JoeInstanceFormWrapper = (props: JoeInstanceFormProps) => {
-  const useStyles = makeStyles(
-    {
-      textField: {
-        ...styles.inputField,
-        maxWidth: 400,
-      },
-      errorMessage: {
-        color: 'red',
-        marginTop: 10,
-      },
-      fieldBlock: {
-        width: '100%',
-      },
-      urlOkIcon: {
-        marginBottom: -5,
-        marginLeft: 10,
-        color: 'green',
-      },
-      urlOk: {
-        color: 'green',
-      },
-      urlFailIcon: {
-        marginBottom: -5,
-        marginLeft: 10,
-        color: 'red',
-      },
-      urlFail: {
-        color: 'red',
-      },
-      warning: {
-        color: '#801200',
-        fontSize: '0.9em',
-      },
-      warningIcon: {
-        color: '#801200',
-        fontSize: '1.2em',
-        position: 'relative',
-        marginBottom: -3,
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <JoeInstanceForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/JoeInstances/JoeInstances.tsx b/ui/packages/platform/src/components/JoeInstances/JoeInstances.tsx
deleted file mode 100644
index 2277f7af..00000000
--- a/ui/packages/platform/src/components/JoeInstances/JoeInstances.tsx
+++ /dev/null
@@ -1,482 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, MouseEvent } from 'react'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  TextField,
-  Menu,
-  MenuItem,
-  Tooltip,
-  IconButton,
-} from '@material-ui/core'
-import WarningIcon from '@material-ui/icons/Warning'
-import MoreVertIcon from '@material-ui/icons/MoreVert'
-import { styles } from '@postgres.ai/shared/styles/styles'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import {
-  ClassesType,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from './../ConsolePageTitle'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { messages } from '../../assets/messages'
-import Store from '../../stores/store'
-import Urls from '../../utils/urls'
-import { isHttps } from 'utils/utils'
-import { getProjectAliasById, ProjectDataType } from 'utils/aliases'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-import { JoeInstancesProps } from 'components/JoeInstances/JoeInstancesWrapper'
-
-interface JoeInstancesWithStylesProps extends JoeInstancesProps {
-  classes: ClassesType
-}
-
-interface JoeInstancesState {
-  anchorEl: (EventTarget & HTMLButtonElement) | null
-  data: {
-    auth: {
-      token: string
-    } | null
-    joeInstances: {
-      isProcessing: boolean
-      projectId: string | number | undefined
-      error: boolean
-      orgId: number
-      data: {
-        [instance: string]: {
-          id: number
-          project_name: string
-          project_label: string
-          project_label_or_name: string
-          url: string
-          use_tunnel: boolean
-        }
-      }
-    } | null
-    userProfile: {
-      data: {
-        orgs: ProjectDataType
-      }
-    } | null
-    projects: {
-      isProcessing: boolean
-      isProcessed: boolean
-      error: boolean
-      data: {
-        id: number
-        name: string
-        label: string
-        project_label_or_name: string
-      }[]
-    } | null
-  } | null
-}
-
-class JoeInstances extends Component<
-  JoeInstancesWithStylesProps,
-  JoeInstancesState
-> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    let orgId = this.props.orgId ? this.props.orgId : null
-    let projectId = this.props.projectId ? this.props.projectId : null
-
-    if (!projectId) {
-      projectId =
-        this.props.match &&
-        this.props.match.params &&
-        this.props.match.params.projectId
-          ? this.props.match.params.projectId
-          : null
-    }
-
-    if (projectId) {
-      Actions.setJoeInstancesProject(orgId, projectId)
-    } else {
-      Actions.setJoeInstancesProject(orgId, 0)
-    }
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const joeInstances =
-        this.data && this.data.joeInstances ? this.data.joeInstances : null
-      const projects =
-        this.data && this.data.projects ? this.data.projects : null
-
-      if (
-        auth &&
-        auth.token &&
-        !joeInstances?.isProcessing &&
-        !joeInstances?.error &&
-        !that.state
-      ) {
-        Actions.getJoeInstances(auth.token, orgId, projectId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects?.isProcessing &&
-        !projects?.error &&
-        !that.state
-      ) {
-        Actions.getProjects(auth.token, orgId)
-      }
-
-      that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleClick = (
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    id: number,
-  ) => {
-    const url = Urls.linkJoeInstance(this.props, id)
-
-    if (url) {
-      this.props.history.push(url)
-    }
-  }
-
-  handleChangeProject = (
-    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
-  ) => {
-    const org = this.props.org ? this.props.org : null
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const projectId = event.target.value
-    const project = getProjectAliasById(
-      this.state.data?.userProfile?.data.orgs as ProjectDataType,
-      projectId,
-    )
-    const props = { org, orgId, projectId, project }
-
-    Actions.setJoeInstancesProject(orgId, event.target.value)
-    this.props.history.push(Urls.linkJoeInstances(props))
-  }
-
-  registerButtonHandler = () => {
-    this.props.history.push(Urls.linkJoeInstanceAdd(this.props))
-  }
-
-  openMenu = (event: MouseEvent<HTMLButtonElement>) => {
-    event.stopPropagation()
-    this.setState({ anchorEl: event.currentTarget })
-  }
-
-  closeMenu = () => {
-    this.setState({ anchorEl: null })
-  }
-
-  menuHandler = (_: MouseEvent<HTMLLIElement>, action: string) => {
-    const anchorEl = this.state.anchorEl
-
-    this.closeMenu()
-
-    setTimeout(() => {
-      const auth =
-        this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-      if (anchorEl) {
-        let instanceId = anchorEl.getAttribute('aria-rowindex')
-        if (!instanceId) {
-          return
-        }
-
-        switch (action) {
-          case 'destroy':
-            /* eslint no-alert: 0 */
-            if (
-              window.confirm(
-                'Are you sure you want to remove this Joe Bot instance?',
-              ) === true
-            ) {
-              Actions.destroyJoeInstance(auth?.token, instanceId)
-            }
-
-            break
-          default:
-            break
-        }
-      }
-    }, 100)
-  }
-
-  render() {
-    const { classes, orgPermissions, orgId } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.joeInstances
-        ? this.state.data.joeInstances
-        : null
-    const projects =
-      this.state && this.state.data && this.state.data.projects
-        ? this.state.data.projects
-        : null
-    let projectId = this.props.projectId ? this.props.projectId : null
-    const menuOpen = Boolean(this.state && this.state.anchorEl)
-    const title = 'Joe Bot instances'
-
-    if (!projectId) {
-      projectId =
-        this.props.match &&
-        this.props.match.params &&
-        this.props.match.params.projectId
-          ? this.props.match.params.projectId
-          : null
-    }
-
-    const createPermitted = !orgPermissions || orgPermissions.joeInstanceCreate
-    const deletePermitted = !orgPermissions || orgPermissions.joeInstanceDelete
-
-    let projectFilter = null
-    if (projects && projects.data && data) {
-      projectFilter = (
-        <div>
-          <TextField
-            value={data.projectId}
-            onChange={(event) => this.handleChangeProject(event)}
-            select
-            label="Project"
-            inputProps={{
-              name: 'project',
-              id: 'project-filter',
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel,
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper,
-            }}
-            variant="outlined"
-            className={classes.filterSelect}
-          >
-            <MenuItem value={0}>All</MenuItem>
-
-            {projects.data.map((p) => {
-              return (
-                <MenuItem value={p.id} key={p.id}>
-                  {p.project_label_or_name || p.label || p.name}
-                </MenuItem>
-              )
-            })}
-          </TextField>
-        </div>
-      )
-    }
-
-    let breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: 'SQL Optimization', url: null }, { name: title }]}
-      />
-    )
-
-    if (this.state && this.state.data?.joeInstances?.error) {
-      return (
-        <div>
-          {breadcrumbs}
-
-          <ConsolePageTitle title={title} />
-
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (data && data.isProcessing) ||
-      data.orgId !== orgId ||
-      data.projectId !== (projectId ? projectId : 0)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          <ConsolePageTitle title={title} />
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const addInstanceButton = (
-      <ConsoleButtonWrapper
-        disabled={!createPermitted}
-        variant="contained"
-        color="primary"
-        key="add_instance"
-        onClick={this.registerButtonHandler}
-        title={
-          createPermitted ? 'Add a new Joe Bot instance' : messages.noPermission
-        }
-      >
-        Add instance
-      </ConsoleButtonWrapper>
-    )
-
-    const emptyListTitle = projectId
-      ? 'There are no Joe Bot instances in this project yet'
-      : 'There are no Joe Bot instances yet'
-
-    let table = (
-      <StubContainer>
-        <ProductCardWrapper
-          inline
-          title={emptyListTitle}
-          actions={[
-            {
-              id: 'addInstanceButton',
-              content: addInstanceButton,
-            },
-          ]}
-          icon={icons.joeLogo}
-        >
-          <p>
-            Joe Bot is a virtual DBA for SQL Optimization. Joe helps engineers
-            quickly troubleshoot and optimize SQL. Joe runs on top of the DBLab
-            Engine. (
-            <GatewayLink
-              href="https://postgres.ai/docs/joe-bot"
-              target="_blank"
-            >
-              Learn more
-            </GatewayLink>
-            ).
-          </p>
-        </ProductCardWrapper>
-      </StubContainer>
-    )
-
-    if (data.data && Object.keys(data.data).length > 0) {
-      table = (
-        <HorizontalScrollContainer>
-          <Table className={classes.table} id="joeInstancesTable">
-            <TableHead>
-              <TableRow className={classes.row}>
-                <TableCell>Project</TableCell>
-                <TableCell>URL</TableCell>
-                <TableCell>&nbsp;</TableCell>
-              </TableRow>
-            </TableHead>
-
-            <TableBody>
-              {Object.keys(data.data).map((i) => {
-                return (
-                  <TableRow
-                    hover
-                    className={classes.row}
-                    key={data.data[i].id}
-                    onClick={(event) =>
-                      this.handleClick(event, data.data[i].id)
-                    }
-                    style={{ cursor: 'pointer' }}
-                  >
-                    <TableCell className={classes.cell}>
-                      {data.data[i].project_label_or_name ||
-                        data.data[i].project_label ||
-                        data.data[i].project_name}
-                    </TableCell>
-
-                    <TableCell className={classes.cell}>
-                      {data.data[i].url ? data.data[i].url : 'N/A'}
-                      {data.data[i].url &&
-                      !isHttps(data.data[i].url) &&
-                      !data.data[i].use_tunnel ? (
-                        <Tooltip
-                          title="The connection to Joe Bot API is not secure"
-                          aria-label="add"
-                          classes={{ tooltip: classes.toolTip }}
-                        >
-                          <WarningIcon className={classes.warningIcon} />
-                        </Tooltip>
-                      ) : null}
-                    </TableCell>
-
-                    <TableCell align={'right'}>
-                      <IconButton
-                        aria-label="more"
-                        aria-controls="instance-menu"
-                        aria-haspopup="true"
-                        aria-rowindex={data.data[i].id}
-                        onClick={this.openMenu}
-                      >
-                        <MoreVertIcon />
-                      </IconButton>
-                    </TableCell>
-                  </TableRow>
-                )
-              })}
-            </TableBody>
-          </Table>
-        </HorizontalScrollContainer>
-      )
-    }
-
-    const menu = (
-      <Menu
-        id="instance-menu"
-        anchorEl={this.state.anchorEl}
-        keepMounted
-        open={menuOpen}
-        onClose={this.closeMenu}
-        PaperProps={{
-          style: {
-            width: 200,
-          },
-        }}
-      >
-        <MenuItem
-          disabled={!deletePermitted}
-          key={1}
-          onClick={(event) => this.menuHandler(event, 'destroy')}
-        >
-          Remove
-        </MenuItem>
-      </Menu>
-    )
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        <ConsolePageTitle title={title} actions={[addInstanceButton]} />
-
-        {projectFilter}
-
-        {table}
-
-        {menu}
-      </div>
-    )
-  }
-}
-
-export default JoeInstances
diff --git a/ui/packages/platform/src/components/JoeInstances/JoeInstancesWrapper.tsx b/ui/packages/platform/src/components/JoeInstances/JoeInstancesWrapper.tsx
deleted file mode 100644
index 6dd62224..00000000
--- a/ui/packages/platform/src/components/JoeInstances/JoeInstancesWrapper.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { RouteComponentProps } from 'react-router'
-import JoeInstances from 'components/JoeInstances/JoeInstances'
-
-export interface JoeInstancesProps {
-  orgId: number
-  org: string | number
-  project: string | undefined
-  projectId: number | string | undefined
-  history: RouteComponentProps['history']
-  orgPermissions?: {
-    joeInstanceCreate?: boolean
-    joeInstanceDelete?: boolean
-  }
-  match: {
-    params: {
-      org?: string
-      project?: string
-      projectId?: string | number | undefined
-    }
-  }
-}
-export const JoeInstancesWrapper = (props: JoeInstancesProps) => {
-  const useStyles = makeStyles(
-    {
-      root: {
-        ...(styles.root as Object),
-        paddingBottom: '20px',
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      filterSelect: {
-        ...styles.inputField,
-        width: 150,
-      },
-      cell: {
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      warningIcon: {
-        color: colors.state.warning,
-        fontSize: '1.2em',
-        position: 'absolute',
-        marginLeft: 5,
-      },
-      toolTip: {
-        fontSize: '10px!important',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <JoeInstances {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/LoginDialog/LoginDialog.tsx b/ui/packages/platform/src/components/LoginDialog/LoginDialog.tsx
deleted file mode 100644
index 1c702936..00000000
--- a/ui/packages/platform/src/components/LoginDialog/LoginDialog.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React from 'react'
-import Button from '@material-ui/core/Button'
-import TextField from '@material-ui/core/TextField'
-import {
-  Dialog,
-  DialogActions,
-  DialogContent,
-  DialogContentText,
-  DialogTitle,
-} from '@material-ui/core'
-
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-
-interface LoginDialogProps {
-  classes: ClassesType
-}
-
-interface LoginDialogState {
-  open: boolean
-  emailError: boolean
-  passwordError: boolean
-  isProcessing: boolean
-  email: string | null
-  password: string | null
-  data: {
-    auth: {
-      isProcessing: boolean
-      error: boolean
-      status: string | null
-      errorMessage: string | null
-    }
-  } | null
-}
-
-class LoginDialog extends React.Component<LoginDialogProps, LoginDialogState> {
-  state = {
-    data: {
-      auth: {
-        status: null,
-        isProcessing: false,
-        error: false,
-        errorMessage: null,
-      },
-    },
-    open: true,
-    email: null,
-    password: null,
-    emailError: false,
-    passwordError: false,
-    isProcessing: false,
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-    })
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleNext = () => {
-    const state = this.state
-
-    if (!state.email) {
-      this.setState({ emailError: true })
-    } else {
-      this.setState({ emailError: false })
-    }
-
-    if (!state.password) {
-      this.setState({ passwordError: true })
-    } else {
-      this.setState({ passwordError: false })
-    }
-
-    if (state.email && state.password) {
-      this.setState({ isProcessing: true })
-      this.setState({ emailError: false, passwordError: false })
-      Actions.doAuth(state.email, state.password)
-    }
-  }
-
-  handleCancel = () => {
-    this.setState({ open: false })
-    window.location.href = '/'
-  }
-
-  render() {
-    const { classes } = this.props
-    const auth =
-      this.state && this.state.data && this.state.data.auth
-        ? this.state.data.auth
-        : { isProcessing: false, status: null, error: null }
-    let errorMessage = null
-
-    if (
-      auth &&
-      auth.error &&
-      auth.errorMessage !== 'wrong_reply' &&
-      auth.errorMessage !== 'empty_request'
-    ) {
-      errorMessage = (
-        <div>
-          <DialogContentText>
-            <div className={classes.error}>{auth.errorMessage}</div>
-          </DialogContentText>
-        </div>
-      )
-    }
-
-    let password = (
-      <div>
-        <TextField
-          required
-          id="password-input"
-          label="Password"
-          name="password"
-          type="password"
-          placeholder="Input password here"
-          fullWidth
-          margin="normal"
-          onChange={(e) => {
-            this.setState({
-              password: e.target.value,
-            })
-          }}
-          disabled={auth.isProcessing}
-          error={this.state.passwordError}
-        />
-      </div>
-    )
-
-    return (
-      <div>
-        <Dialog open={this.state.open} aria-labelledby="form-dialog-title">
-          <DialogTitle id="form-dialog-title">Login</DialogTitle>
-          <DialogContent>
-            <DialogContentText>
-              To enter to dashboard, please enter your email address here. We
-              will send you password.
-            </DialogContentText>
-            <TextField
-              required
-              autoFocus
-              margin="dense"
-              id="email"
-              label="Email Address"
-              type="email"
-              name="email"
-              fullWidth
-              placeholder="Input login here"
-              onChange={(e) => {
-                this.setState({
-                  email: e.target.value,
-                })
-              }}
-              error={this.state.emailError}
-              disabled={auth.isProcessing || auth.status === 'password_sent'}
-            />
-            {password}
-            {errorMessage}
-          </DialogContent>
-          <DialogActions>
-            <Button
-              onClick={this.handleCancel}
-              color="primary"
-              disabled={auth.isProcessing}
-            >
-              Cancel
-            </Button>
-            <Button
-              onClick={this.handleNext}
-              color="primary"
-              disabled={auth.isProcessing}
-            >
-              Login
-            </Button>
-          </DialogActions>
-        </Dialog>
-      </div>
-    )
-  }
-}
-
-export default LoginDialog
diff --git a/ui/packages/platform/src/components/LoginDialog/LoginDialogWrapper.tsx b/ui/packages/platform/src/components/LoginDialog/LoginDialogWrapper.tsx
deleted file mode 100644
index d58f62b0..00000000
--- a/ui/packages/platform/src/components/LoginDialog/LoginDialogWrapper.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import LoginDialog from 'components/LoginDialog/LoginDialog'
-
-export const LoginDialogWrapper = () => {
-  const useStyles = makeStyles(
-    {
-      error: {
-        color: 'red',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <LoginDialog classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/Notification/Notification.tsx b/ui/packages/platform/src/components/Notification/Notification.tsx
deleted file mode 100644
index 8ed736ab..00000000
--- a/ui/packages/platform/src/components/Notification/Notification.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { Component } from 'react'
-import { Snackbar, IconButton } from '@material-ui/core'
-import CloseIcon from '@material-ui/icons/Close'
-import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'
-import WarningIcon from '@material-ui/icons/Warning'
-import InfoIcon from '@material-ui/icons/Info'
-import CheckCircleIcon from '@material-ui/icons/CheckCircle'
-
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-
-interface NotificationProps {
-  classes: ClassesType
-}
-
-interface NotificationState {
-  data: {
-    notification: {
-      type: string
-      message: string
-      duration: number
-    }
-  }
-}
-
-class Notification extends Component<NotificationProps, NotificationState> {
-  componentDidMount() {
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      that.setState({ data: this.data })
-    })
-  }
-
-  unsubscribe: Function
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  closeNotification = (
-    _: React.SyntheticEvent<HTMLButtonElement>,
-    reason: string,
-  ) => {
-    if (reason === 'clickaway') {
-      return
-    }
-
-    Actions.hideNotification()
-  }
-
-  render() {
-    const { classes } = this.props
-
-    if (
-      this.state &&
-      this.state.data &&
-      this.state.data.notification &&
-      this.state.data.notification.message
-    ) {
-      let className
-      let message
-
-      switch (this.state.data.notification.type) {
-        case 'error':
-          className = classes.errorNotification
-          message = (
-            <span>
-              <ErrorOutlineIcon className={classes.svgIcon} />
-              {this.state.data.notification.message}
-            </span>
-          )
-          break
-        case 'warning':
-          // Warning
-          className = classes.warningNotification
-          message = (
-            <span>
-              <WarningIcon className={classes.svgIcon} />
-              {this.state.data.notification.message}
-            </span>
-          )
-          break
-        case 'information':
-          // Info
-          className = classes.informationNotification
-          message = (
-            <span>
-              <InfoIcon className={classes.svgIcon} />
-              {this.state.data.notification.message}
-            </span>
-          )
-          break
-        case 'success':
-          // CheckCircle
-          className = classes.successNotification
-          message = (
-            <span>
-              <CheckCircleIcon className={classes.svgIcon} />
-              {this.state.data.notification.message}
-            </span>
-          )
-          break
-        default:
-          className = classes.defaultNotification
-          message = this.state.data.notification.message
-      }
-
-      return (
-        <Snackbar
-          open={true}
-          onClose={this.closeNotification}
-          message={message}
-          className={className}
-          autoHideDuration={this.state.data.notification.duration}
-          action={
-            <React.Fragment>
-              <IconButton size="small" aria-label="close" color="inherit">
-                <CloseIcon fontSize="small" />
-              </IconButton>
-            </React.Fragment>
-          }
-        />
-      )
-    }
-
-    return null
-  }
-}
-
-export default Notification
diff --git a/ui/packages/platform/src/components/Notification/NotificationWrapper.tsx b/ui/packages/platform/src/components/Notification/NotificationWrapper.tsx
deleted file mode 100644
index dbbfea2a..00000000
--- a/ui/packages/platform/src/components/Notification/NotificationWrapper.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import Notification from 'components/Notification/Notification'
-
-export const NotificationWrapper = () => {
-  const useStyles = makeStyles(
-    {
-      defaultNotification: {},
-      errorNotification: {
-        '& > div.MuiSnackbarContent-root': {
-          backgroundColor: '#f44336!important',
-        },
-      },
-      informationNotification: {
-        '& > div.MuiSnackbarContent-root': {
-          backgroundColor: '#2196f3!important',
-        },
-      },
-      warningNotification: {
-        '& > div.MuiSnackbarContent-root': {
-          backgroundColor: '#ff9800!important',
-        },
-      },
-      successNotification: {
-        '& > div.MuiSnackbarContent-root': {
-          backgroundColor: '#4caf50!important',
-        },
-      },
-      svgIcon: {
-        marginBottom: -2,
-        marginRight: 5,
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Notification classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/OrgForm/OrgForm.tsx b/ui/packages/platform/src/components/OrgForm/OrgForm.tsx
deleted file mode 100644
index 17361188..00000000
--- a/ui/packages/platform/src/components/OrgForm/OrgForm.tsx
+++ /dev/null
@@ -1,802 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import {
-  Grid,
-  Button,
-  TextField,
-  FormControlLabel,
-  Checkbox,
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  IconButton,
-} from '@material-ui/core'
-import DeleteIcon from '@material-ui/icons/Delete'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-import { theme } from '@postgres.ai/shared/styles/theme'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { messages } from '../../assets/messages'
-import { formatAlias } from 'utils/aliases'
-import { OrgFormProps } from 'components/OrgForm/OrgFormWrapper'
-import { linksConfig } from "@postgres.ai/shared/config/links";
-
-interface OrgFormWithStylesProps extends OrgFormProps {
-  classes: ClassesType
-}
-
-interface DomainsType {
-  [domain: string]: {
-    confirmed: boolean
-    id: number | null
-  }
-}
-
-interface OrgFormState {
-  id: number | null
-  name: string
-  alias: string
-  onboardingText: string
-  domain: string
-  orgChanged: boolean
-  usersAutojoin: boolean
-  oauthAllowGoogle: boolean
-  oauthAllowLinkedin: boolean
-  oauthAllowGitlab: boolean
-  oauthAllowGithub: boolean
-  data: {
-    auth: {
-      token: string | null
-    } | null
-    orgProfile: {
-      isUpdating: boolean
-      error: boolean
-      updateError: boolean
-      errorMessage: string | undefined
-      errorCode: number | undefined
-      updateErrorMessage: string | null
-      isProcessing: boolean
-      orgId: number | null
-      updateErrorFields: string[]
-      orgDomains: {
-        isProcessing: boolean
-        isProcessed: boolean
-        isDeleting: boolean
-        domainId: number | null
-      }
-      data: {
-        id: number | null
-        name: string
-        alias: string
-        updateName: string
-        updateAlias: string
-        isProcessing: boolean
-        updateUsersAutojoin: boolean
-        onboarding_text: string
-        users_autojoin: boolean
-        domains: DomainsType | null
-      } | null
-    } | null
-  } | null
-}
-
-class OrgForm extends Component<OrgFormWithStylesProps, OrgFormState> {
-  state = {
-    id: null,
-    name: '',
-    alias: '',
-    onboardingText: '',
-    domain: '',
-    orgChanged: true,
-    usersAutojoin: false,
-    oauthAllowGoogle: false,
-    oauthAllowLinkedin: false,
-    oauthAllowGitlab: false,
-    oauthAllowGithub: false,
-    data: {
-      auth: {
-        token: null,
-      },
-      orgProfile: {
-        isUpdating: false,
-        isProcessing: false,
-        error: false,
-        updateError: false,
-        errorMessage: undefined,
-        errorCode: undefined,
-        updateErrorMessage: null,
-        updateErrorFields: [''],
-        orgId: null,
-        orgDomains: {
-          isProcessing: false,
-          isProcessed: false,
-          isDeleting: false,
-          domainId: null,
-        },
-        data: {
-          id: null,
-          name: '',
-          alias: '',
-          updateName: '',
-          updateAlias: '',
-          isProcessing: false,
-          updateUsersAutojoin: false,
-          onboarding_text: '',
-          users_autojoin: false,
-          domains: null,
-        },
-      },
-    },
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const { orgId, mode } = this.props
-    const that = this
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const orgProfile =
-        this.data && this.data.orgProfile ? this.data.orgProfile : null
-
-      that.setState({ data: this.data })
-
-      if (
-        auth &&
-        auth.token &&
-        orgProfile &&
-        orgProfile.orgId !== orgId &&
-        !orgProfile.isProcessing
-      ) {
-        Actions.getOrgs(auth.token, orgId)
-      }
-
-      if (!that.isAdd(mode) && orgProfile && orgProfile.data) {
-        if (orgProfile.orgDomains && orgProfile.orgDomains.isProcessed) {
-          that.setState({
-            domain: '',
-          })
-        }
-
-        if (orgProfile.data.updateName && orgProfile.data.updateAlias) {
-          that.setState({
-            id: orgProfile.orgId,
-            name: orgProfile.data.updateName,
-            alias: orgProfile.data.updateAlias,
-            usersAutojoin: orgProfile.data.updateUsersAutojoin,
-            oauthAllowGoogle: orgProfile.data['oauth_allow_google'],
-            oauthAllowLinkedin: orgProfile.data['oauth_allow_linkedin'],
-            oauthAllowGitlab: orgProfile.data['oauth_allow_gitlab'],
-            oauthAllowGithub: orgProfile.data['oauth_allow_github'],
-            onboardingText: orgProfile.data['onboarding_text'],
-            orgChanged: false,
-          })
-        } else {
-          if (that.state.id && that.state.id === orgId) {
-            return
-          }
-
-          if (orgProfile.isUpdating || orgProfile.updateError) {
-            return
-          }
-
-          that.setState({
-            id: orgProfile.data.id,
-            name: orgProfile.data.name,
-            alias: orgProfile.data.alias,
-            usersAutojoin: orgProfile.data['users_autojoin'],
-            oauthAllowGoogle: orgProfile.data['oauth_allow_google'],
-            oauthAllowLinkedin: orgProfile.data['oauth_allow_linkedin'],
-            oauthAllowGitlab: orgProfile.data['oauth_allow_gitlab'],
-            oauthAllowGithub: orgProfile.data['oauth_allow_github'],
-            onboardingText: orgProfile.data['onboarding_text'],
-            orgChanged: false,
-          })
-        }
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  buttonHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.orgProfile : null
-
-    if (
-      auth &&
-      data &&
-      !data.isUpdating &&
-      (this.state.name || this.state.alias)
-    ) {
-      if (this.isAdd(this.props.mode)) {
-        Actions.createOrg(auth.token, {
-          name: this.state.name,
-          alias: this.state.alias,
-          email_domain_autojoin: this.state.domain,
-          users_autojoin: this.state.usersAutojoin,
-        })
-      } else if (this.state.orgChanged) {
-        Actions.updateOrg(auth.token, orgId, {
-          name: this.state.name,
-          alias: this.state.alias,
-          users_autojoin: this.state.usersAutojoin,
-          oauth_allow_google: this.state.oauthAllowGoogle,
-          oauth_allow_linkedin: this.state.oauthAllowLinkedin,
-          oauth_allow_gitlab: this.state.oauthAllowGitlab,
-          oauth_allow_github: this.state.oauthAllowGithub,
-          onboarding_text: this.state.onboardingText,
-        })
-      }
-    }
-  }
-
-  domainButtonHandler = () => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-    const data = this.state.data ? this.state.data.orgProfile : null
-
-    if (
-      auth &&
-      data &&
-      !data.isUpdating &&
-      !this.isAdd(this.props.mode) &&
-      (this.state.name || this.state.alias)
-    ) {
-      if (this.state.domain) {
-        Actions.addOrgDomain(auth.token, orgId, this.state.domain)
-      }
-    }
-  }
-
-  deleteDomainHandler = (domainId: number | null, confirmed: boolean) => {
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    /* eslint no-alert: 0 */
-    if (
-      confirmed &&
-      window.confirm(
-        'Are you sure you want to delete already confirmed domain?',
-      ) === false
-    ) {
-      return
-    }
-
-    Actions.deleteOrgDomain(auth?.token, orgId, domainId)
-  }
-
-  isAdd = (mode: OrgFormProps['mode']) => {
-    return mode === 'new'
-  }
-
-  render() {
-    const { classes, orgPermissions, mode } = this.props
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const data =
-      this.state && this.state.data ? this.state.data.orgProfile : null
-    const breadcrumbs = this.isAdd(mode) ? (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[
-          { name: 'Organizations', url: '/' },
-          { name: 'Create organization' },
-        ]}
-      />
-    ) : (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: 'Settings' }]}
-      />
-    )
-    const pageTitle = this.isAdd(mode) ? (
-      <ConsolePageTitle title="Create organization" />
-    ) : (
-      <ConsolePageTitle title="Settings" />
-    )
-
-    if (orgPermissions && !orgPermissions.settingsOrganizationUpdate) {
-      return (
-        <>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <WarningWrapper>{messages.noPermissionPage}</WarningWrapper>
-        </>
-      )
-    }
-
-    let domains = <div>No entries.</div>
-
-    let domainField = (
-      <div>
-        <TextField
-          id="domain"
-          label="Organization domain"
-          variant="outlined"
-          disabled={data?.isUpdating}
-          value={this.state.domain}
-          className={
-            !this.isAdd(mode) ? classes.editDomainField : classes.newDomainField
-          }
-          onChange={(e) => {
-            this.setState({
-              domain: e.target.value,
-            })
-          }}
-          error={
-            data?.updateErrorFields &&
-            data.updateErrorFields.indexOf('domain') !== -1
-          }
-          margin="normal"
-          fullWidth
-          inputProps={{
-            name: 'domain',
-            id: 'domain',
-            shrink: 'true',
-          }}
-          InputLabelProps={{
-            shrink: true,
-            style: styles.inputFieldLabel,
-          }}
-          FormHelperTextProps={{
-            style: styles.inputFieldHelper,
-          }}
-        />
-
-        {!this.isAdd(mode) ? (
-          <Button
-            variant="contained"
-            color="primary"
-            disabled={data?.orgDomains.isProcessing}
-            onClick={this.domainButtonHandler}
-            className={classes.domainButton}
-          >
-            Add
-          </Button>
-        ) : null}
-      </div>
-    )
-
-    const hasConfirmedDomains =
-      data?.data &&
-      data.data?.domains &&
-      Object.keys(data?.data?.domains as unknown as DomainsType).some(
-        (key) => (data?.data?.domains as unknown as DomainsType)[key].confirmed,
-      )
-
-    let authCheckboxes = (
-      <>
-        {this.state.usersAutojoin && hasConfirmedDomains && (
-          <>
-            <div className={classes.checkboxContainer}>
-              <FormControlLabel
-                className={classes.autoJoinCheckbox}
-                control={
-                  <Checkbox
-                    checked={this.state.oauthAllowGoogle}
-                    onChange={(e) => {
-                      this.setState({
-                        orgChanged: true,
-                        oauthAllowGoogle: e.target.checked,
-                      })
-                    }}
-                    disabled={data?.isUpdating}
-                    name="oauthAllowGoogle"
-                  />
-                }
-                label="Allow Google login"
-              />
-              <FormControlLabel
-                className={classes.autoJoinCheckbox}
-                control={
-                  <Checkbox
-                    checked={this.state.oauthAllowLinkedin}
-                    onChange={(e) => {
-                      this.setState({
-                        orgChanged: true,
-                        oauthAllowLinkedin: e.target.checked,
-                      })
-                    }}
-                    disabled={data?.isUpdating}
-                    name="oauthAllowLinkedin"
-                  />
-                }
-                label="Allow LinkedIn login"
-              />
-              <FormControlLabel
-                className={classes.autoJoinCheckbox}
-                control={
-                  <Checkbox
-                    checked={this.state.oauthAllowGitlab}
-                    onChange={(e) => {
-                      this.setState({
-                        orgChanged: true,
-                        oauthAllowGitlab: e.target.checked,
-                      })
-                    }}
-                    disabled={data?.isUpdating}
-                    name="oauthAllowGitlab"
-                  />
-                }
-                label="Allow GitLab login"
-              />
-              <FormControlLabel
-                className={classes.autoJoinCheckbox}
-                control={
-                  <Checkbox
-                    checked={this.state.oauthAllowGithub}
-                    onChange={(e) => {
-                      this.setState({
-                        orgChanged: true,
-                        oauthAllowGithub: e.target.checked,
-                      })
-                    }}
-                    disabled={data?.isUpdating}
-                    name="oauthAllowGithub"
-                  />
-                }
-                label="Allow GitHub login"
-              />
-            </div>
-            <br />
-          </>
-        )}
-      </>
-    )
-
-    let buttonTitle = 'Save'
-    if (this.isAdd(mode)) {
-      buttonTitle = 'Create'
-    }
-
-    if (
-      this.state &&
-      this.state.data &&
-      this.state.data.orgProfile.error &&
-      this.state.data.orgProfile.orgId === orgId &&
-      !this.state.data.orgProfile.isProcessing
-    ) {
-      return (
-        <div>
-          <ErrorWrapper message={data?.errorMessage} code={data?.errorCode} />
-        </div>
-      )
-    }
-
-    if (
-      !this.isAdd(mode) &&
-      (!data || !data.data || data.data.id !== orgId || data.data.isProcessing)
-    ) {
-      return (
-        <>
-          {breadcrumbs}
-          {pageTitle}
-
-          <PageSpinner />
-        </>
-      )
-    }
-
-    if (
-      !this.isAdd(mode) &&
-      data &&
-      data?.data?.domains &&
-      Object.keys(data.data.domains).length > 0
-    ) {
-      const domainsData: DomainsType = data?.data?.domains
-      domains = (
-        <div>
-          <HorizontalScrollContainer>
-            <Table className={classes.table}>
-              <TableHead>
-                <TableRow className={classes.row}>
-                  <TableCell className={classes.tableCell}>Domain</TableCell>
-                  <TableCell className={classes.tableCell}>Confirmed</TableCell>
-                  <TableCell className={classes.tableCell}>&nbsp;</TableCell>
-                </TableRow>
-              </TableHead>
-              <TableBody>
-                {Object.keys(domainsData).map((d) => {
-                  return (
-                    <TableRow key={d}>
-                      <TableCell className={classes.tableCell}>{d}</TableCell>
-                      <TableCell className={classes.tableCell}>
-                        {domainsData[d].confirmed ? (
-                          'Yes'
-                        ) : (
-                          <span>
-                            No&nbsp;&nbsp;
-                            <span className={classes.errorMessage}>
-                              To confirm&nbsp;
-                              <a
-                                className={classes.supportLink}
-                                href={linksConfig.support}
-                                target="_blank"
-                              >
-                                contact support
-                              </a>
-                            </span>
-                          </span>
-                        )}
-                      </TableCell>
-                      <TableCell className={classes.tableCellRight}>
-                        {data !== null &&
-                        domainsData &&
-                        data?.orgDomains &&
-                        data.orgDomains.isDeleting &&
-                        data.orgDomains.domainId === domainsData[d].id ? (
-                          <Spinner className={classes.inTableProgress} />
-                        ) : null}
-                        <IconButton
-                          className={classes.tableActionButton}
-                          color="primary"
-                          aria-label="Delete domain"
-                          onClick={() => {
-                            this.deleteDomainHandler(
-                              domainsData[d].id,
-                              domainsData[d].confirmed,
-                            )
-                          }}
-                          component="span"
-                          disabled={
-                            data &&
-                            data.orgDomains &&
-                            data.orgDomains.isDeleting
-                          }
-                        >
-                          <DeleteIcon />
-                        </IconButton>
-                      </TableCell>
-                    </TableRow>
-                  )
-                })}
-              </TableBody>
-            </Table>
-          </HorizontalScrollContainer>
-        </div>
-      )
-    }
-
-    return (
-      <>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        <div className={classes.errorMessage}>
-          {data && data.updateErrorMessage ? data.updateErrorMessage : null}
-        </div>
-
-        <Grid container spacing={3}>
-          <Grid item xs={12} sm={12} lg={12} className={classes.container}>
-            <Grid
-              xs={12}
-              sm={12}
-              lg={8}
-              item
-              container
-              direction={theme.breakpoints.up('md') ? 'row' : 'column'}
-            >
-              <Grid item xs={12} sm={6}>
-                <TextField
-                  id="orgNameTextField"
-                  label="Organization name"
-                  variant="outlined"
-                  disabled={data?.isUpdating}
-                  value={this.state.name}
-                  className={classes.textField}
-                  onChange={(e) => {
-                    if (this.isAdd(this.props.mode)) {
-                      this.setState({ alias: formatAlias(e.target.value) })
-                    }
-                    this.setState({
-                      orgChanged: true,
-                      name: e.target.value,
-                    })
-                  }}
-                  margin="normal"
-                  error={
-                    data?.updateErrorFields &&
-                    data.updateErrorFields.indexOf('name') !== -1
-                  }
-                  fullWidth
-                  inputProps={{
-                    name: 'name',
-                    id: 'orgNameTextField',
-                    shrink: 'true',
-                  }}
-                  InputLabelProps={{
-                    shrink: true,
-                    style: styles.inputFieldLabel,
-                  }}
-                  FormHelperTextProps={{
-                    style: styles.inputFieldHelper,
-                  }}
-                />
-
-                <TextField
-                  id="orgAliasTextField"
-                  label="Organization unique ID"
-                  fullWidth
-                  variant="outlined"
-                  disabled={data?.isUpdating}
-                  value={this.state.alias}
-                  className={classes.textField}
-                  onChange={(e) => {
-                    this.setState({
-                      orgChanged: true,
-                      alias: formatAlias(e.target.value),
-                    })
-                  }}
-                  error={
-                    data?.updateErrorFields &&
-                    data.updateErrorFields.indexOf('alias') !== -1
-                  }
-                  margin="normal"
-                  inputProps={{
-                    name: 'alias',
-                    id: 'orgAliasTextField',
-                    shrink: 'true',
-                  }}
-                  InputLabelProps={{
-                    shrink: true,
-                    style: styles.inputFieldLabel,
-                  }}
-                  FormHelperTextProps={{
-                    style: styles.inputFieldHelper,
-                  }}
-                />
-
-                {!this.isAdd(this.props.mode) ? (
-                  <TextField
-                    id="onboardingText"
-                    label="Getting started text"
-                    fullWidth
-                    multiline
-                    variant="outlined"
-                    disabled={data?.isUpdating}
-                    value={this.state.onboardingText}
-                    className={classes.onboardingField}
-                    onChange={(e) => {
-                      this.setState({
-                        orgChanged: true,
-                        onboardingText: e.target.value,
-                      })
-                    }}
-                    helperText={
-                      <span>
-                        Format:&nbsp;
-                        <GatewayLink
-                          href={'https://commonmark.org/help'}
-                          target={'_blank'}
-                        >
-                          Markdown
-                        </GatewayLink>
-                      </span>
-                    }
-                    error={
-                      data?.updateErrorFields &&
-                      data.updateErrorFields.indexOf('onboardingText') !== -1
-                    }
-                    margin="normal"
-                    inputProps={{
-                      name: 'onboardingText',
-                      id: 'onboardingText',
-                      shrink: 'true',
-                    }}
-                    InputLabelProps={{
-                      shrink: true,
-                      style: styles.inputFieldLabel,
-                    }}
-                    FormHelperTextProps={{
-                      style: styles.inputFieldHelper,
-                    }}
-                  />
-                ) : null}
-              </Grid>
-            </Grid>
-
-            {!this.isAdd(mode) && (
-              <Grid item xs={12} sm={12} lg={8} className={classes.container}>
-                <Grid item xs={12} sm={6}>
-                  <FormControlLabel
-                    className={classes.autoJoinCheckbox}
-                    control={
-                      <Checkbox
-                        checked={this.state.usersAutojoin}
-                        onChange={(e) => {
-                          this.setState({
-                            orgChanged: true,
-                            usersAutojoin: e.target.checked,
-                          })
-                        }}
-                        disabled={data?.isUpdating}
-                        name="usersAutojoin"
-                      />
-                    }
-                    label="Auto-join based on email domain"
-                  />
-
-                  <div>
-                    <div className={classes.comment}>
-                      Users that have email addresses with the following domain
-                      name(s)&nbsp; automatically join the organization when
-                      registering in the system.
-                      <br />
-                    </div>
-
-                    <br />
-
-                    {domains}
-
-                    <br />
-
-                    {domainField}
-
-                    <br />
-
-                    {authCheckboxes}
-                  </div>
-                </Grid>
-              </Grid>
-            )}
-
-            <Grid
-              item
-              xs={12}
-              sm={12}
-              lg={8}
-              className={classes.updateButtonContainer}
-            >
-              <Button
-                variant="contained"
-                color="primary"
-                disabled={data?.isUpdating}
-                id="orgSaveButton"
-                onClick={this.buttonHandler}
-              >
-                {buttonTitle}
-              </Button>
-            </Grid>
-          </Grid>
-        </Grid>
-
-        <div className={classes.bottomSpace} />
-      </>
-    )
-  }
-}
-
-export default OrgForm
diff --git a/ui/packages/platform/src/components/OrgForm/OrgFormWrapper.tsx b/ui/packages/platform/src/components/OrgForm/OrgFormWrapper.tsx
deleted file mode 100644
index f20aa0cc..00000000
--- a/ui/packages/platform/src/components/OrgForm/OrgFormWrapper.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import OrgForm from 'components/OrgForm/OrgForm'
-
-export interface OrgFormProps {
-  mode?: string | undefined
-  project?: string | undefined
-  org?: string | number
-  orgId?: number
-  orgPermissions?: {
-    settingsOrganizationUpdate?: boolean
-  }
-}
-
-export const OrgFormWrapper = (props: OrgFormProps) => {
-  const useStyles = makeStyles(
-    {
-      container: {
-        ...(styles.root as Object),
-        display: 'flex',
-        'flex-wrap': 'wrap',
-        'min-height': 0,
-        '&:not(:first-child)': {
-          'margin-top': '20px',
-        },
-      },
-      textField: {
-        ...styles.inputField,
-        maxWidth: 450,
-      },
-      onboardingField: {
-        ...styles.inputField,
-        maxWidth: 450,
-        '& #onboardingText-helper-text': {
-          marginTop: 5,
-          fontSize: '10px',
-          letterSpacing: 0,
-        },
-      },
-      updateButtonContainer: {
-        marginTop: 10,
-        textAlign: 'left',
-      },
-      errorMessage: {
-        color: 'red',
-      },
-      autoJoinCheckbox: {
-        ...styles.checkbox,
-        'margin-top': 5,
-        'margin-bottom': 10,
-        'margin-Left': 0,
-        width: 300,
-      },
-      comment: {
-        fontSize: 14,
-        marginLeft: 0,
-        width: '80%',
-      },
-      domainsHeader: {
-        lineHeight: '42px',
-      },
-      editDomainField: {
-        ...styles.inputField,
-        'margin-left': 0,
-        'margin-top': 10,
-        width: 'calc(80% - 80px)',
-        '& > div.MuiFormControl- > label': {
-          fontSize: 20,
-        },
-        '& input, & .MuiOutlinedInput-multiline, & .MuiSelect-select': {
-          padding: 13,
-        },
-      },
-      domainButton: {
-        marginTop: 11,
-        marginLeft: 10,
-        width: 70,
-      },
-      newDomainField: {
-        ...styles.inputField,
-        'margin-left': 0,
-        'margin-top': 10,
-        width: '80%',
-        '& > div.MuiFormControl- > label': {
-          fontSize: 20,
-        },
-        '& input, & .MuiOutlinedInput-multiline, & .MuiSelect-select': {
-          padding: 13,
-        },
-      },
-      tableCell: {
-        padding: 7,
-        lineHeight: '16px',
-        fontSize: 14,
-      },
-      tableCellRight: {
-        fontSize: 14,
-        padding: 7,
-        lineHeight: '16px',
-        textAlign: 'right',
-      },
-      table: {
-        width: '80%',
-      },
-      tableActionButton: {
-        padding: 0,
-        fontSize: 16,
-      },
-      supportLink: {
-        cursor: 'pointer',
-        fontWeight: 'bold',
-        textDecoration: 'underline',
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-      checkboxContainer: {
-        '& > label': {
-          marginBottom: 0,
-        },
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <OrgForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/OrgMembers/OrgMembers.tsx b/ui/packages/platform/src/components/OrgMembers/OrgMembers.tsx
deleted file mode 100644
index a86bb6f1..00000000
--- a/ui/packages/platform/src/components/OrgMembers/OrgMembers.tsx
+++ /dev/null
@@ -1,437 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  IconButton,
-  Select,
-  MenuItem,
-} from '@material-ui/core'
-import DeleteIcon from '@material-ui/icons/Delete'
-import ExitIcon from '@material-ui/icons/ExitToApp'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Actions from '../../actions/actions'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import Store from '../../stores/store'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { messages } from '../../assets/messages'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { OrgSettingsProps } from 'components/OrgMembers/OrgMembersWrapper'
-
-interface OrgSettingsWithStylesProps extends OrgSettingsProps {
-  classes: ClassesType
-}
-
-interface UsersType {
-  id: number
-  role_id: number
-  email: string
-  first_name: string
-  last_name: string
-  is_owner: boolean
-}
-
-interface OrgSettingsState {
-  filterValue: string
-  changes: {
-    [change: number]: number
-  }
-  data: {
-    auth: {
-      token: string
-    } | null
-    orgUsers: {
-      data: {
-        roles: { name: string; id: number }[]
-        users: UsersType[]
-      }
-      orgId: number
-      isRefresh: boolean
-      isRefreshed: boolean
-      isUpdated: boolean
-      isDeleted: boolean
-      deleteUserId: number
-      updateUserId: number
-      error: boolean
-      errorMessage: string
-      errorCode: number
-      isProcessing: boolean
-      isUpdating: boolean
-      isDeleting: boolean
-    } | null
-    userProfile: {
-      data: {
-        info: UsersType
-      }
-    } | null
-  } | null
-}
-
-const membersTitle = 'Members'
-
-class OrgSettings extends Component<
-  OrgSettingsWithStylesProps,
-  OrgSettingsState
-> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const { orgId, orgPermissions } = this.props
-    const hasListMembersPermission =
-      !orgPermissions || orgPermissions.settingsMemberList
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const orgUsers =
-        this.data && this.data.orgUsers ? this.data.orgUsers : null
-
-      that.setState({ data: this.data })
-
-      if (!hasListMembersPermission) {
-        return
-      }
-
-      if (orgUsers?.isRefreshed) {
-        that.setState({
-          changes: {},
-        })
-      }
-
-      if (orgUsers?.isUpdated || orgUsers?.isDeleted) {
-        // Updated users list after deleting or updating.
-        Actions.getOrgUsers(auth?.token, orgId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        orgUsers &&
-        !orgUsers.isProcessing &&
-        !orgUsers.error &&
-        !that.state
-      ) {
-        // Initial loading of users.
-        Actions.getOrgUsers(auth.token, orgId)
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  buttonAddHandler = () => {
-    const org = this.props.org ? this.props.org : null
-
-    this.props.history.push('/' + org + '/members/add')
-  }
-
-  deleteHandler = (_: React.MouseEvent<HTMLButtonElement>, u: UsersType) => {
-    const { orgId, env } = this.props
-    /* eslint no-alert: 0 */
-    let userName: string | string[] = []
-    if (u.first_name) {
-      userName.push(u.first_name)
-    }
-    if (u.last_name) {
-      userName.push(u.last_name)
-    }
-    userName = userName.join(' ')
-    if (!userName) {
-      userName = 'with email ' + u.email
-    } else {
-      userName = userName + ' (' + u.email + ')'
-    }
-    let message =
-      'Are you sure you want to remove ' + userName + ' from the organization?'
-
-    if (u.id === env.data.info.id) {
-      message = 'Are you sure you want to leave the organization?'
-    }
-
-    if (window.confirm(message) === true) {
-      Actions.deleteOrgUser(this.state.data?.auth?.token, orgId, u.id)
-    }
-  }
-
-  changeRoleHandler = (userId: number, userRoleId: number) => {
-    const { orgId } = this.props
-
-    Actions.updateOrgUser(
-      this.state.data?.auth?.token,
-      orgId,
-      userId,
-      userRoleId,
-    )
-
-    this.setState({
-      changes: {
-        [userId]: userRoleId,
-      },
-    })
-  }
-
-  roleSelector(u: UsersType) {
-    const that = this
-    const { classes, orgPermissions } = this.props
-    const roles =
-      this.state.data &&
-      this.state.data.orgUsers &&
-      this.state.data.orgUsers.data &&
-      this.state.data.orgUsers.data.roles
-        ? this.state.data.orgUsers.data.roles
-        : []
-
-    if (orgPermissions?.settingsMemberUpdate && !u.is_owner) {
-      return (
-        <span>
-          <Select
-            value={
-              that.state && that.state.changes && that.state.changes[u.id]
-                ? that.state.changes[u.id]
-                : u.role_id
-            }
-            onChange={(event) => {
-              if (u.role_id !== event.target.value) {
-                that.changeRoleHandler(u.id, event.target.value as number)
-              }
-            }}
-            className={classes.roleSelector}
-            variant="outlined"
-          >
-            {roles.map((r) => {
-              return (
-                <MenuItem value={r.id} className={classes.roleSelectorItem}>
-                  {r.name}
-                </MenuItem>
-              )
-            })}
-          </Select>
-        </span>
-      )
-    }
-
-    // Just output role name.
-    let role = '-'
-    for (let i in roles) {
-      if (roles[i].id === u.role_id) {
-        role = roles[i].name
-      }
-    }
-
-    return (
-      <span>
-        {role}
-        {u.is_owner ? ' (Owner)' : ''}
-      </span>
-    )
-  }
-
-  filterInputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.setState({ filterValue: event.target.value })
-  }
-
-  render() {
-    const { classes, orgPermissions, orgId, env } = this.props
-    const data = this.state && this.state.data ? this.state.data.orgUsers : null
-    const userProfile =
-      this.state && this.state.data ? this.state.data.userProfile : null
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: membersTitle }]}
-      />
-    )
-
-    const hasAddMemberPermission =
-      !orgPermissions || orgPermissions.settingsMemberAdd
-    const hasListMembersPermission =
-      !orgPermissions || orgPermissions.settingsMemberList
-
-    const actions = [
-      <ConsoleButtonWrapper
-        disabled={!hasAddMemberPermission}
-        title={
-          hasAddMemberPermission ? 'Add a new member' : messages.noPermission
-        }
-        variant="contained"
-        color="primary"
-        onClick={this.buttonAddHandler}
-      >
-        Add
-      </ConsoleButtonWrapper>,
-    ]
-
-    let users: UsersType[] = []
-    if (hasListMembersPermission) {
-      if (data && data.data && data.data.users && data.data.users.length > 0) {
-        users = data.data.users
-      }
-    } else if (userProfile && userProfile.data && userProfile.data.info) {
-      users = [userProfile.data.info]
-    }
-
-    const filteredUsers = users?.filter((user) => {
-      const fullName = (user.first_name || '') + ' ' + (user.last_name || '')
-      return (
-        fullName
-          ?.toLowerCase()
-          .indexOf((this.state.filterValue || '')?.toLowerCase()) !== -1
-      )
-    })
-
-    const pageTitle = (
-      <ConsolePageTitle
-        title={membersTitle}
-        actions={actions}
-        filterProps={
-          users && users.length > 0
-            ? {
-                filterValue: this.state.filterValue,
-                filterHandler: this.filterInputHandler,
-                placeholder: 'Search users by name',
-              }
-            : null
-        }
-      />
-    )
-
-    if (
-      !data ||
-      (hasListMembersPermission && data.orgId !== orgId) ||
-      (data.isProcessing && data.isRefresh === false)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    if (data.error) {
-      return (
-        <div className={classes.root}>
-          <ErrorWrapper message={data.errorMessage} code={data.errorCode} />
-        </div>
-      )
-    }
-
-    // If user does not have "ListMembersPermission" we will fill the list only
-    // with his data without making getOrgUsers request.
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {!hasListMembersPermission && (
-          <WarningWrapper>
-            You do not have permission to view the full list of members
-          </WarningWrapper>
-        )}
-
-        {filteredUsers && filteredUsers.length > 0 ? (
-          <HorizontalScrollContainer>
-            <Table className={classes.table}>
-              <TableHead>
-                <TableRow className={classes.row}>
-                  <TableCell>Email</TableCell>
-                  <TableCell>Role</TableCell>
-                  <TableCell>First name</TableCell>
-                  <TableCell>Last name</TableCell>
-                  <TableCell />
-                </TableRow>
-              </TableHead>
-              <TableBody>
-                {filteredUsers.map((u: UsersType) => {
-                  return (
-                    <TableRow hover className={classes.row} key={u.id}>
-                      <TableCell className={classes.cell}>{u.email}</TableCell>
-                      <TableCell className={classes.cell}>
-                        {this.roleSelector(u)}
-                        {u.id === data.updateUserId && data.isUpdating && (
-                          <Spinner
-                            size="lg"
-                            className={classes.inTableProgress}
-                          />
-                        )}
-                      </TableCell>
-                      <TableCell className={classes.cell}>
-                        {u.first_name}
-                      </TableCell>
-                      <TableCell className={classes.cell}>
-                        {u.last_name}
-                      </TableCell>
-                      <TableCell className={classes.actionCell}>
-                        {!u.is_owner &&
-                          data.isDeleting &&
-                          data.deleteUserId === u.id && (
-                            <Spinner
-                              size="lg"
-                              className={classes.inTableProgress}
-                            />
-                          )}
-                        {!u.is_owner && u.id !== env.data.info.id && (
-                          <IconButton
-                            aria-label="delete"
-                            title="Delete user from the organization"
-                            disabled={data.isDeleting}
-                            onClick={(event) => this.deleteHandler(event, u)}
-                          >
-                            <DeleteIcon />
-                          </IconButton>
-                        )}
-                        {u.id === env.data.info.id && (
-                          <IconButton
-                            aria-label="delete"
-                            title="Leave the organization"
-                            disabled={data.isDeleting || u.is_owner}
-                            onClick={(event) => this.deleteHandler(event, u)}
-                          >
-                            <ExitIcon />
-                          </IconButton>
-                        )}
-                      </TableCell>
-                    </TableRow>
-                  )
-                })}
-              </TableBody>
-            </Table>
-          </HorizontalScrollContainer>
-        ) : (
-          'Members not found'
-        )}
-
-        <div className={classes.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default OrgSettings
diff --git a/ui/packages/platform/src/components/OrgMembers/OrgMembersWrapper.tsx b/ui/packages/platform/src/components/OrgMembers/OrgMembersWrapper.tsx
deleted file mode 100644
index 780fe321..00000000
--- a/ui/packages/platform/src/components/OrgMembers/OrgMembersWrapper.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import OrgSettings from 'components/OrgMembers/OrgMembers'
-import { OrgPermissions } from 'components/types'
-
-export interface OrgSettingsProps {
-  project: string | undefined
-  history: RouteComponentProps['history']
-  org: string | number
-  orgId: number
-  orgPermissions: OrgPermissions
-  env: {
-    data: {
-      info: {
-        id: number | null
-      }
-    }
-  }
-}
-
-export const OrgMembersWrapper = (props: OrgSettingsProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        ...(styles.root as Object),
-        display: 'flex',
-        flexDirection: 'column',
-        paddingBottom: '20px',
-      },
-      container: {
-        display: 'flex',
-        flexWrap: 'wrap',
-      },
-      textField: {
-        marginLeft: theme.spacing(1),
-        marginRight: theme.spacing(1),
-        width: '80%',
-      },
-      actionCell: {
-        textAlign: 'right',
-        padding: 0,
-        paddingRight: 16,
-      },
-      iconButton: {
-        margin: '-12px',
-        marginLeft: 5,
-      },
-      inTableProgress: {
-        width: '15px!important',
-        height: '15px!important',
-        marginLeft: 5,
-        verticalAlign: 'middle',
-      },
-      roleSelector: {
-        height: 24,
-        width: 190,
-        '& svg': {
-          top: 5,
-          right: 3,
-        },
-        '& .MuiSelect-select': {
-          padding: 8,
-          paddingRight: 20,
-        },
-      },
-      roleSelectorItem: {
-        fontSize: 14,
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <OrgSettings {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterForm/PostgresCluster.tsx b/ui/packages/platform/src/components/PostgresClusterForm/PostgresCluster.tsx
deleted file mode 100644
index 7eb0647a..00000000
--- a/ui/packages/platform/src/components/PostgresClusterForm/PostgresCluster.tsx
+++ /dev/null
@@ -1,809 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  Accordion,
-  AccordionDetails,
-  AccordionSummary,
-  Checkbox,
-  FormControlLabel,
-  InputAdornment,
-  MenuItem,
-  Tab,
-  Tabs,
-  TextField,
-} from '@material-ui/core'
-import { Box } from '@mui/material'
-import cn from 'classnames'
-
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { Select } from '@postgres.ai/shared/components/Select'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex'
-import { CloudProvider } from 'api/cloud/getCloudProviders'
-import { CloudVolumes } from 'api/cloud/getCloudVolumes'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DbLabInstanceFormSidebar } from 'components/DbLabInstanceForm/DbLabInstanceFormSidebar'
-import { StorageSlider } from 'components/DbLabInstanceForm/DbLabInstanceFormSlider'
-import { DbLabInstanceFormProps } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import { initialState, reducer } from 'components/PostgresClusterForm/reducer'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { TabPanel } from 'pages/JoeSessionCommand/TabPanel'
-import ConsolePageTitle from '../ConsolePageTitle'
-
-import urls from 'utils/urls'
-import { validateDLEName } from 'utils/utils'
-
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { CloudInstance } from 'api/cloud/getCloudInstances'
-import { CloudRegion } from 'api/cloud/getCloudRegions'
-import { AnsibleInstance } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { DockerInstance } from 'components/DbLabInstanceForm/DbLabFormSteps/DockerInstance'
-import { SimpleInstance } from 'components/DbLabInstanceForm/DbLabFormSteps/SimpleInstance'
-import {
-  filteredRegions,
-  uniqueRegionsByProvider,
-} from 'components/DbLabInstanceForm/utils'
-import { ClusterExtensionAccordion } from 'components/PostgresClusterForm/PostgresClusterSteps'
-import { useCloudProvider } from 'hooks/useCloudProvider'
-
-interface PostgresClusterProps extends DbLabInstanceFormProps {
-  classes: ClassesType
-  auth?: {
-    userId: number
-  }
-}
-
-const PostgresCluster = (props: PostgresClusterProps) => {
-  const { classes, orgPermissions } = props
-  const {
-    state,
-    dispatch,
-    handleChangeVolume,
-    handleSetFormStep,
-    handleReturnToForm,
-  } = useCloudProvider({ initialState, reducer })
-
-  const permitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-  const requirePublicKeys =
-    !state.publicKeys && (state.provider === 'aws' || state.provider === 'gcp')
-
-  const pageTitle = <ConsolePageTitle title="Create Postgres Cluster" />
-  const breadcrumbs = (
-    <ConsoleBreadcrumbsWrapper
-      {...props}
-      breadcrumbs={[
-        { name: 'Postgres Clusters', url: 'pg' },
-        { name: 'Create Postgres Cluster' },
-      ]}
-    />
-  )
-
-  const handleReturnToList = () => {
-    props.history.push(urls.linkClusters(props))
-  }
-
-  const checkSyncStandbyCount = () => {
-    if (state.synchronous_mode) {
-      if (Number(state.numberOfInstances) === 1) {
-        return state.synchronous_node_count > state.numberOfInstances
-      } else {
-        return state.synchronous_node_count > state.numberOfInstances - 1
-      }
-    }
-  }
-
-  const disableSubmitButton =
-    validateDLEName(state.name) ||
-    requirePublicKeys ||
-    state.numberOfInstances > 32 ||
-    checkSyncStandbyCount() ||
-    (state.publicKeys && state.publicKeys.length < 30)
-
-  if (state.isLoading) return <StubSpinner />
-
-  return (
-    <div className={classes.root}>
-      {breadcrumbs}
-
-      {pageTitle}
-
-      {!permitted && (
-        <WarningWrapper>
-          You do not have permission to add Database Lab instances.
-        </WarningWrapper>
-      )}
-
-      <div
-        className={cn(
-          classes.container,
-          state.isReloading && classes.backgroundOverlay,
-        )}
-      >
-        {state.formStep === initialState.formStep && permitted ? (
-          <>
-            {state.isReloading && (
-              <Spinner className={classes.absoluteSpinner} />
-            )}
-            <div className={classes.form}>
-              <p className={classes.sectionTitle}>
-                1. Select your cloud provider
-              </p>
-              <div className={classes.providerFlex}>
-                {state.serviceProviders.map(
-                  (provider: CloudProvider, index: number) => (
-                    <div
-                      className={cn(
-                        classes.provider,
-                        state.provider === provider.api_name &&
-                          classes.activeBorder,
-                      )}
-                      key={index}
-                      onClick={() =>
-                        dispatch({
-                          type: 'change_provider',
-                          provider: provider.api_name,
-                          isReloading: true,
-                        })
-                      }
-                    >
-                      <img
-                        src={`/images/service-providers/${provider.api_name}.png`}
-                        width={85}
-                        height="auto"
-                        alt={provider.label}
-                      />
-                    </div>
-                  ),
-                )}
-              </div>
-              <p className={classes.sectionTitle}>
-                2. Select your cloud region
-              </p>
-              <div className={classes.sectionContainer}>
-                <Tabs
-                  value={state.region}
-                  onChange={(_: React.ChangeEvent<{}> | null, value: string) =>
-                    dispatch({
-                      type: 'change_region',
-                      region: value,
-                      location: state.cloudRegions.find(
-                        (region: CloudRegion) =>
-                          region.world_part === value &&
-                          region.cloud_provider === state.provider,
-                      ),
-                    })
-                  }
-                >
-                  {uniqueRegionsByProvider(state.cloudRegions).map(
-                    (region: string, index: number) => (
-                      <Tab
-                        key={index}
-                        label={region}
-                        value={region}
-                        className={classes.tab}
-                      />
-                    ),
-                  )}
-                </Tabs>
-              </div>
-              <TabPanel value={state.region} index={state.region}>
-                {filteredRegions(state.cloudRegions, state.region).map(
-                  (region: CloudRegion, index: number) => (
-                    <div
-                      key={index}
-                      className={cn(
-                        classes.serviceLocation,
-                        state.location?.api_name === region?.api_name &&
-                          classes.activeBorder,
-                      )}
-                      onClick={() =>
-                        dispatch({
-                          type: 'change_location',
-                          location: region,
-                        })
-                      }
-                    >
-                      <p className={classes.serviceTitle}>{region.api_name}</p>
-                      <p className={classes.serviceTitle}>🏴 {region.label}</p>
-                    </div>
-                  ),
-                )}
-              </TabPanel>
-              {state.instanceType ? (
-                <>
-                  <p className={classes.sectionTitle}>
-                    3. Choose instance type
-                  </p>
-                  <TabPanel
-                    value={state.cloudInstances}
-                    index={state.cloudInstances}
-                  >
-                    {state.cloudInstances.map(
-                      (instance: CloudInstance, index: number) => (
-                        <div
-                          key={index}
-                          className={cn(
-                            classes.instanceSize,
-                            state.instanceType === instance &&
-                              classes.activeBorder,
-                          )}
-                          onClick={() =>
-                            dispatch({
-                              type: 'change_instance_type',
-                              instanceType: instance,
-                            })
-                          }
-                        >
-                          <p>
-                            {instance.api_name} (
-                            {state.instanceType.cloud_provider}:{' '}
-                            {instance.native_name})
-                          </p>
-                          <div>
-                            <span>🔳 {instance.native_vcpus} CPU</span>
-                            <span>🧠 {instance.native_ram_gib} GiB RAM</span>
-                          </div>
-                        </div>
-                      ),
-                    )}
-                  </TabPanel>
-                  <p className={classes.sectionTitle}>4. Number of instances</p>
-                  <p className={classes.instanceParagraph}>
-                    Number of servers in the Postgres cluster
-                  </p>
-                  <Box className={classes.sliderContainer}>
-                    <Box className={classes.sliderInputContainer}>
-                      <Box className={classes.databaseSize}>
-                        <TextField
-                          variant="outlined"
-                          fullWidth
-                          type="number"
-                          label="Number of instances"
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          helperText={
-                            state.numberOfInstances > 32 &&
-                            'Maximum 32 instances'
-                          }
-                          error={state.numberOfInstances > 32}
-                          value={state.numberOfInstances}
-                          className={classes.filterSelect}
-                          onChange={(
-                            event: React.ChangeEvent<
-                              HTMLTextAreaElement | HTMLInputElement
-                            >,
-                          ) => {
-                            dispatch({
-                              type: 'change_number_of_instances',
-                              number: event.target.value,
-                            })
-                          }}
-                        />
-                      </Box>
-                    </Box>
-                    <StorageSlider
-                      value={state.numberOfInstances}
-                      sliderOptions={{
-                        min: 1,
-                        max: 32,
-                        step: 1,
-                      }}
-                      customMarks={[
-                        {
-                          value: 1,
-                          label: '1',
-                          scaledValue: 1,
-                        },
-                        {
-                          value: 5,
-                          label: '5',
-                          scaledValue: 5,
-                        },
-                        {
-                          value: 9,
-                          label: '9',
-                          scaledValue: 9,
-                        },
-                        {
-                          value: 13,
-                          label: '13',
-                          scaledValue: 13,
-                        },
-                        {
-                          value: 17,
-                          label: '17',
-                          scaledValue: 17,
-                        },
-                        {
-                          value: 21,
-                          label: '21',
-                          scaledValue: 21,
-                        },
-                        {
-                          value: 25,
-                          label: '25',
-                          scaledValue: 25,
-                        },
-                        {
-                          value: 29,
-                          label: '29',
-                          scaledValue: 29,
-                        },
-                        {
-                          value: 32,
-                          label: '32',
-                          scaledValue: 32,
-                        },
-                      ]}
-                      onChange={(_: React.ChangeEvent<{}>, value: unknown) => {
-                        dispatch({
-                          type: 'change_number_of_instances',
-                          number: value,
-                        })
-                      }}
-                    />
-                  </Box>
-                  <p className={classes.sectionTitle}>5. Database volume</p>
-                  <Box className={classes.sliderContainer}>
-                    <Box className={classes.sliderInputContainer}>
-                      <Box className={classes.sliderVolume}>
-                        <TextField
-                          value={state.fileSystem}
-                          onChange={(e) =>
-                            dispatch({
-                              type: 'change_file_system',
-                              fileSystem: e.target.value,
-                            })
-                          }
-                          select
-                          label="Filesystem"
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          variant="outlined"
-                          className={classes.filterSelect}
-                        >
-                          {state.fileSystemArray.map(
-                            (p: string, id: number) => {
-                              return (
-                                <MenuItem value={p} key={id}>
-                                  {p}
-                                </MenuItem>
-                              )
-                            },
-                          )}
-                        </TextField>
-                      </Box>
-                      <Box className={classes.sliderVolume}>
-                        <TextField
-                          value={state.volumeType}
-                          onChange={handleChangeVolume}
-                          select
-                          label="Volume type"
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          variant="outlined"
-                          className={classes.filterSelect}
-                        >
-                          {(state.volumes as CloudVolumes[]).map((p, id) => {
-                            const volumeName = `${p.api_name} (${p.cloud_provider}: ${p.native_name})`
-                            return (
-                              <MenuItem value={volumeName} key={id}>
-                                {volumeName}
-                              </MenuItem>
-                            )
-                          })}
-                        </TextField>
-                      </Box>
-                      <Box className={classes.databaseSize}>
-                        <TextField
-                          variant="outlined"
-                          fullWidth
-                          type="number"
-                          label="Volume size"
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          InputProps={{
-                            inputProps: {
-                              min: 0,
-                            },
-                            endAdornment: (
-                              <InputAdornment position="end">
-                                GiB
-                              </InputAdornment>
-                            ),
-                          }}
-                          value={state.storage}
-                          className={classes.filterSelect}
-                          onChange={(
-                            event: React.ChangeEvent<
-                              HTMLTextAreaElement | HTMLInputElement
-                            >,
-                          ) => {
-                            dispatch({
-                              type: 'change_volume_price',
-                              volumeSize: event.target.value,
-                              volumePrice: event.target.value,
-                            })
-                          }}
-                        />
-                      </Box>
-                    </Box>
-                    <StorageSlider
-                      value={state.storage}
-                      sliderOptions={{
-                        min: 0,
-                        max: 10000,
-                        step: 10,
-                      }}
-                      customMarks={[
-                        {
-                          value: 0,
-                          label: '0',
-                          scaledValue: 0,
-                        },
-                        {
-                          value: 1000,
-                          label: '1000 GiB',
-                          scaledValue: 1000,
-                        },
-                        {
-                          value: 2000,
-                          label: '2000 GiB',
-                          scaledValue: 2000,
-                        },
-                        {
-                          value: 3000,
-                          label: '3000 GiB',
-                          scaledValue: 3000,
-                        },
-                        {
-                          value: 4000,
-                          label: '4000 GiB',
-                          scaledValue: 4000,
-                        },
-                        {
-                          value: 5000,
-                          label: '5000 GiB',
-                          scaledValue: 5000,
-                        },
-                        {
-                          value: 6000,
-                          label: '6000 GiB',
-                          scaledValue: 6000,
-                        },
-                        {
-                          value: 7000,
-                          label: '7000 GiB',
-                          scaledValue: 7000,
-                        },
-                        {
-                          value: 8000,
-                          label: '8000 GiB',
-                          scaledValue: 8000,
-                        },
-                        {
-                          value: 9000,
-                          label: '9000 GiB',
-                          scaledValue: 9000,
-                        },
-                        {
-                          value: 10000,
-                          label: '10 TB',
-                          scaledValue: 10000,
-                        },
-                      ]}
-                      onChange={(_: React.ChangeEvent<{}>, value: unknown) => {
-                        dispatch({
-                          type: 'change_volume_price',
-                          volumeSize: value,
-                          volumePrice:
-                            (Number(value) * state.volumePricePerHour) / 1000,
-                        })
-                      }}
-                    />
-                  </Box>
-                  <p className={classes.sectionTitle}>
-                    6. Provide cluster name
-                  </p>
-                  <TextField
-                    required
-                    label="Cluster Name"
-                    variant="outlined"
-                    fullWidth
-                    value={state.name}
-                    className={classes.marginTop}
-                    InputLabelProps={{
-                      shrink: true,
-                    }}
-                    helperText={
-                      validateDLEName(state.name)
-                        ? 'Name must be lowercase and contain only letters and numbers.'
-                        : ''
-                    }
-                    error={validateDLEName(state.name)}
-                    onChange={(
-                      event: React.ChangeEvent<
-                        HTMLTextAreaElement | HTMLInputElement
-                      >,
-                    ) =>
-                      dispatch({
-                        type: 'change_name',
-                        name: event.target.value,
-                      })
-                    }
-                  />
-                  <p className={classes.sectionTitle}>
-                    7. Choose Postgres version
-                  </p>
-                  <Select
-                    label="Select version"
-                    items={Array.from({ length: 7 }, (_, i) => i + 10).map(
-                      (version) => {
-                        return {
-                          value: version,
-                          children: version,
-                        }
-                      },
-                    )}
-                    value={state.version}
-                    onChange={(
-                      e: React.ChangeEvent<
-                        HTMLTextAreaElement | HTMLInputElement
-                      >,
-                    ) =>
-                      dispatch({
-                        type: 'set_version',
-                        version: e.target.value,
-                      })
-                    }
-                  />
-                  <ClusterExtensionAccordion
-                    step={8}
-                    state={Object.fromEntries(
-                      Object.entries(state).filter(([key]) => key !== 'region'),
-                    )}
-                    classes={classes}
-                    dispatch={dispatch}
-                  />
-                  <Accordion className={classes.sectionTitle}>
-                    <AccordionSummary
-                      aria-controls="panel1a-content"
-                      id="panel1a-header"
-                      expandIcon={icons.sortArrowDown}
-                    >
-                      9. Advanced options
-                    </AccordionSummary>
-                    <AccordionDetails>
-                      <Box
-                        sx={{
-                          display: 'flex',
-                          flexDirection: 'column',
-                        }}
-                      >
-                        <FormControlLabel
-                          control={
-                            <Checkbox
-                              name="database_public_access"
-                              checked={state.database_public_access}
-                              onChange={(e) =>
-                                dispatch({
-                                  type: 'change_database_public_access',
-                                  database_public_access: e.target.checked,
-                                })
-                              }
-                              classes={{
-                                root: classes.checkboxRoot,
-                              }}
-                            />
-                          }
-                          label={'Database public access'}
-                        />
-                        <FormControlLabel
-                          control={
-                            <Checkbox
-                              name="with_haproxy_load_balancing"
-                              checked={state.with_haproxy_load_balancing}
-                              onChange={(e) =>
-                                dispatch({
-                                  type: 'change_with_haproxy_load_balancing',
-                                  with_haproxy_load_balancing: e.target.checked,
-                                })
-                              }
-                              classes={{
-                                root: classes.checkboxRoot,
-                              }}
-                            />
-                          }
-                          label={'Haproxy load balancing'}
-                        />
-                        <FormControlLabel
-                          control={
-                            <Checkbox
-                              name="pgbouncer_install"
-                              checked={state.pgbouncer_install}
-                              onChange={(e) =>
-                                dispatch({
-                                  type: 'change_pgbouncer_install',
-                                  pgbouncer_install: e.target.checked,
-                                })
-                              }
-                              classes={{
-                                root: classes.checkboxRoot,
-                              }}
-                            />
-                          }
-                          label={'PgBouncer connection pooler'}
-                        />
-                        <FormControlLabel
-                          control={
-                            <Checkbox
-                              name="synchronous_mode"
-                              checked={state.synchronous_mode}
-                              onChange={(e) =>
-                                dispatch({
-                                  type: 'change_synchronous_mode',
-                                  synchronous_mode: e.target.checked,
-                                })
-                              }
-                              classes={{
-                                root: classes.checkboxRoot,
-                              }}
-                            />
-                          }
-                          label={'Enable synchronous replication'}
-                        />
-                        <TextField
-                          label="Number of synchronous standbys"
-                          variant="outlined"
-                          fullWidth
-                          type="number"
-                          helperText={
-                            checkSyncStandbyCount() &&
-                            `Maximum ${
-                              Number(state.numberOfInstances) === 1
-                                ? state.numberOfInstances
-                                : state.numberOfInstances - 1
-                            } synchronous standbys`
-                          }
-                          error={checkSyncStandbyCount()}
-                          value={state.synchronous_node_count}
-                          disabled={!state.synchronous_mode}
-                          className={classes.marginTop}
-                          InputLabelProps={{
-                            shrink: true,
-                          }}
-                          onChange={(
-                            e: React.ChangeEvent<
-                              HTMLTextAreaElement | HTMLInputElement
-                            >,
-                          ) =>
-                            dispatch({
-                              type: 'change_synchronous_node_count',
-                              synchronous_node_count: e.target.value,
-                            })
-                          }
-                        />
-                        <FormControlLabel
-                          className={classes.marginTop}
-                          control={
-                            <Checkbox
-                              name="netdata_install"
-                              checked={state.netdata_install}
-                              onChange={(e) =>
-                                dispatch({
-                                  type: 'change_netdata_install',
-                                  netdata_install: e.target.checked,
-                                })
-                              }
-                              classes={{
-                                root: classes.checkboxRoot,
-                              }}
-                            />
-                          }
-                          label={'Netdata monitoring'}
-                        />
-                      </Box>
-                    </AccordionDetails>
-                  </Accordion>
-                  <p className={classes.sectionTitle}>
-                    10. Provide SSH public keys (one per line)
-                  </p>
-                  <p className={classes.instanceParagraph}>
-                    These SSH public keys will be added to the DBLab server's
-                    &nbsp;
-                    <code className={classes.code}>~/.ssh/authorized_keys</code>
-                    &nbsp; file. Providing at least one public key is
-                    recommended to ensure access to the server after deployment.
-                  </p>
-                  <TextField
-                    label="SSH public keys"
-                    variant="outlined"
-                    fullWidth
-                    multiline
-                    helperText={
-                      state.publicKeys && state.publicKeys.length < 30
-                        ? 'Public key is too short'
-                        : ''
-                    }
-                    error={state.publicKeys && state.publicKeys.length < 30}
-                    value={state.publicKeys}
-                    required={requirePublicKeys}
-                    className={classes.marginTop}
-                    InputLabelProps={{
-                      shrink: true,
-                    }}
-                    onChange={(
-                      event: React.ChangeEvent<
-                        HTMLTextAreaElement | HTMLInputElement
-                      >,
-                    ) =>
-                      dispatch({
-                        type: 'change_public_keys',
-                        publicKeys: event.target.value,
-                      })
-                    }
-                  />
-                </>
-              ) : (
-                <p className={classes.errorMessage}>
-                  No instance types available for this cloud region. Please try
-                  another region.
-                </p>
-              )}
-            </div>
-            <DbLabInstanceFormSidebar
-              cluster
-              state={state}
-              disabled={disableSubmitButton}
-              handleCreate={() => handleSetFormStep('simple')}
-            />
-          </>
-        ) : state.formStep === 'ansible' && permitted ? (
-          <AnsibleInstance
-            cluster
-            state={state}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : state.formStep === 'docker' && permitted ? (
-          <DockerInstance
-            cluster
-            state={state}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : state.formStep === 'simple' && permitted ? (
-          <SimpleInstance
-            cluster
-            state={state}
-            userID={props.auth?.userId}
-            orgId={props.orgId}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBackToForm={() => {
-              window.history.pushState({}, '', `${window.location.pathname}`)
-              handleReturnToForm()
-            }}
-          />
-        ) : null}
-      </div>
-    </div>
-  )
-}
-
-export default PostgresCluster
diff --git a/ui/packages/platform/src/components/PostgresClusterForm/PostgresClusterSteps/index.tsx b/ui/packages/platform/src/components/PostgresClusterForm/PostgresClusterSteps/index.tsx
deleted file mode 100644
index 7215aa65..00000000
--- a/ui/packages/platform/src/components/PostgresClusterForm/PostgresClusterSteps/index.tsx
+++ /dev/null
@@ -1,252 +0,0 @@
-import {
-  Accordion,
-  AccordionDetails,
-  AccordionSummary,
-  Checkbox,
-  FormControlLabel,
-} from '@material-ui/core'
-import { Box } from '@mui/material'
-import { ReducerAction } from 'react'
-
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-export const ClusterExtensionAccordion = ({
-  step,
-  state,
-  classes,
-  dispatch,
-}: {
-  step: number
-  state: useCloudProviderProps['initialState']
-  classes: ClassesType
-  // @ts-ignore
-  dispatch: ReducerAction<unknown, void>
-}) => (
-  <Accordion className={classes.sectionTitle}>
-    <AccordionSummary
-      aria-controls="extension-content"
-      id="extension-header"
-      expandIcon={icons.sortArrowDown}
-    >
-      {step}. Extensions
-    </AccordionSummary>
-    <AccordionDetails>
-      <Box
-        sx={{
-          display: 'flex',
-          flexDirection: 'column',
-          width: '100%',
-          fontWeight: 'normal',
-        }}
-      >
-        <FormControlLabel
-          className={classes.marginTop}
-          control={
-            <Checkbox
-              name="pg_repack"
-              checked={state.pg_repack}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pg_repack',
-                  pg_repack: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pg_repack'}
-        />
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="pg_cron"
-              checked={state.pg_cron}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pg_cron',
-                  pg_cron: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pg_cron'}
-        />
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="pgaudit"
-              checked={state.pgaudit}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pgaudit',
-                  pgaudit: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pgaudit'}
-        />
-        {state.version !== 10 && (
-          <FormControlLabel
-            control={
-              <Checkbox
-                name="pgvector"
-                checked={state.pgvector}
-                onChange={(e) =>
-                  dispatch({
-                    type: 'change_pgvector',
-                    pgvector: e.target.checked,
-                  })
-                }
-                classes={{
-                  root: classes.checkboxRoot,
-                }}
-              />
-            }
-            label={'pgvector'}
-          />
-        )}
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="postgis"
-              checked={state.postgis}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_postgis',
-                  postgis: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'postgis'}
-        />
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="pgrouting"
-              checked={state.pgrouting}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pgrouting',
-                  pgrouting: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pgrouting'}
-        />
-        {state.version !== 10 && state.version !== 11 && (
-          <FormControlLabel
-            control={
-              <Checkbox
-                name="timescaledb"
-                checked={state.timescaledb}
-                onChange={(e) =>
-                  dispatch({
-                    type: 'change_timescaledb',
-                    timescaledb: e.target.checked,
-                  })
-                }
-                classes={{
-                  root: classes.checkboxRoot,
-                }}
-              />
-            }
-            label={'timescaledb'}
-          />
-        )}
-        {state.version !== 10 && (
-          <FormControlLabel
-            control={
-              <Checkbox
-                name="citus"
-                checked={state.citus}
-                onChange={(e) =>
-                  dispatch({
-                    type: 'change_citus',
-                    citus: e.target.checked,
-                  })
-                }
-                classes={{
-                  root: classes.checkboxRoot,
-                }}
-              />
-            }
-            label={'citus'}
-          />
-        )}
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="pg_partman"
-              checked={state.pg_partman}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pg_partman',
-                  pg_partman: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pg_partman'}
-        />
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="pg_stat_kcache"
-              checked={state.pg_stat_kcache}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pg_stat_kcache',
-                  pg_stat_kcache: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pg_stat_kcache'}
-        />
-        <FormControlLabel
-          control={
-            <Checkbox
-              name="pg_wait_sampling"
-              checked={state.pg_wait_sampling}
-              onChange={(e) =>
-                dispatch({
-                  type: 'change_pg_wait_sampling',
-                  pg_wait_sampling: e.target.checked,
-                })
-              }
-              classes={{
-                root: classes.checkboxRoot,
-              }}
-            />
-          }
-          label={'pg_wait_sampling'}
-        />
-      </Box>
-    </AccordionDetails>
-  </Accordion>
-)
diff --git a/ui/packages/platform/src/components/PostgresClusterForm/PostgresClusterWrapper.tsx b/ui/packages/platform/src/components/PostgresClusterForm/PostgresClusterWrapper.tsx
deleted file mode 100644
index 8c720e24..00000000
--- a/ui/packages/platform/src/components/PostgresClusterForm/PostgresClusterWrapper.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { RouteComponentProps } from 'react-router'
-
-import { useInstanceFormStyles } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import PostgresCluster from './PostgresCluster'
-import { OrgPermissions } from 'components/types'
-
-export interface PostgresClusterWrapperProps {
-  edit?: boolean
-  orgId: number
-  project: string | undefined
-  history: RouteComponentProps['history']
-  orgPermissions: OrgPermissions
-}
-
-export const PostgresClusterWrapper = (props: PostgresClusterWrapperProps) => {
-  const classes = useInstanceFormStyles()
-
-  return <PostgresCluster {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterForm/reducer/index.tsx b/ui/packages/platform/src/components/PostgresClusterForm/reducer/index.tsx
deleted file mode 100644
index 2b13bcf0..00000000
--- a/ui/packages/platform/src/components/PostgresClusterForm/reducer/index.tsx
+++ /dev/null
@@ -1,296 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { ReducerAction } from 'react'
-
-import { CloudInstance } from 'api/cloud/getCloudInstances'
-import { CloudRegion } from 'api/cloud/getCloudRegions'
-import { CloudVolumes } from 'api/cloud/getCloudVolumes'
-import { clusterExtensionsState } from 'components/PostgresClusterInstallForm/reducer'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-export const initialState = {
-  isLoading: false,
-  isReloading: false,
-  formStep: 'create',
-  provider: 'aws',
-  storage: 100,
-  region: 'North America',
-  version: 16,
-  serviceProviders: [],
-  cloudRegions: [],
-  cloudInstances: [],
-  volumes: [] as CloudVolumes[],
-  api_name: 'ssd',
-  databaseSize: 10,
-  snapshots: 3,
-  volumeType: '',
-  volumePrice: 0,
-  volumePricePerHour: 0,
-  volumeCurrency: '',
-  location: {} as CloudRegion,
-  instanceType: {} as CloudInstance,
-  name: '',
-  tag: '',
-  numberOfInstances: 3,
-  publicKeys: '',
-  verificationToken: '',
-  seImageVersions: [],
-  database_public_access: false,
-  with_haproxy_load_balancing: false,
-  pgbouncer_install: true,
-  synchronous_mode: false,
-  synchronous_node_count: 1,
-  netdata_install: true,
-  taskID: '',
-  fileSystem: 'zfs',
-  fileSystemArray: ['zfs', 'ext4', 'xfs'],
-  ...clusterExtensionsState,
-}
-
-export const reducer = (
-  state: useCloudProviderProps['initialState'],
-  // @ts-ignore
-  action: ReducerAction<unknown, void>,
-) => {
-  switch (action.type) {
-    case 'set_initial_state': {
-      return {
-        ...state,
-        isLoading: action.isLoading,
-        serviceProviders: action.serviceProviders,
-        volumes: action.volumes,
-        volumeType: action.volumeType,
-        volumePrice: action.volumePrice,
-        volumePricePerHour: action.volumePricePerHour,
-        volumeCurrency: action.volumeCurrency,
-        region: initialState.region,
-        location: action.cloudRegions.find(
-          (region: CloudRegion) =>
-            region.world_part === initialState.region &&
-            region.cloud_provider === initialState.provider,
-        ),
-      }
-    }
-    case 'update_initial_state': {
-      return {
-        ...state,
-        volumes: action.volumes,
-        volumeType: action.volumeType,
-        volumePricePerHour: action.volumePricePerHour,
-        volumeCurrency: action.volumeCurrency,
-        cloudRegions: action.cloudRegions,
-        location: action.cloudRegions.find(
-          (region: CloudRegion) => region.world_part === initialState.region,
-        ),
-      }
-    }
-    case 'update_instance_type': {
-      return {
-        ...state,
-        cloudInstances: action.cloudInstances,
-        instanceType: action.instanceType,
-        isReloading: action.isReloading,
-      }
-    }
-    case 'change_provider': {
-      return {
-        ...state,
-        provider: action.provider,
-        region: initialState.region,
-        storage: initialState.storage,
-      }
-    }
-    case 'change_region': {
-      return {
-        ...state,
-        region: action.region,
-        location: action.location,
-      }
-    }
-    case 'change_location': {
-      return {
-        ...state,
-        location: action.location,
-      }
-    }
-
-    case 'change_name': {
-      return {
-        ...state,
-        name: action.name,
-      }
-    }
-    case 'change_instance_type': {
-      return {
-        ...state,
-        instanceType: action.instanceType,
-      }
-    }
-    case 'change_public_keys': {
-      return {
-        ...state,
-        publicKeys: action.publicKeys,
-      }
-    }
-    case 'change_number_of_instances': {
-      return {
-        ...state,
-        numberOfInstances: action.number,
-      }
-    }
-    case 'change_volume_type': {
-      return {
-        ...state,
-        volumeType: action.volumeType,
-        volumePrice: action.volumePrice,
-        volumePricePerHour: action.volumePricePerHour,
-      }
-    }
-
-    case 'change_volume_price': {
-      return {
-        ...state,
-        volumePrice: action.volumePrice,
-        storage: action.volumeSize,
-      }
-    }
-
-    case 'set_form_step': {
-      return {
-        ...state,
-        formStep: action.formStep,
-        ...(action.taskID ? { taskID: action.taskID } : {}),
-        ...(action.provider ? { provider: action.provider } : {}),
-      }
-    }
-    case 'set_version': {
-      return {
-        ...state,
-        version: action.version,
-      }
-    }
-    case 'change_database_public_access': {
-      return {
-        ...state,
-        database_public_access: action.database_public_access,
-      }
-    }
-
-    case 'change_with_haproxy_load_balancing': {
-      return {
-        ...state,
-        with_haproxy_load_balancing: action.with_haproxy_load_balancing,
-      }
-    }
-
-    case 'change_pgbouncer_install': {
-      return {
-        ...state,
-        pgbouncer_install: action.pgbouncer_install,
-      }
-    }
-
-    case 'change_synchronous_mode': {
-      return {
-        ...state,
-        synchronous_mode: action.synchronous_mode,
-      }
-    }
-
-    case 'change_synchronous_node_count': {
-      return {
-        ...state,
-        synchronous_node_count: action.synchronous_node_count,
-      }
-    }
-
-    case 'change_netdata_install': {
-      return {
-        ...state,
-        netdata_install: action.netdata_install,
-      }
-    }
-
-    case 'change_pg_repack': {
-      return {
-        ...state,
-        pg_repack: action.pg_repack,
-      }
-    }
-    case 'change_pg_cron': {
-      return {
-        ...state,
-        pg_cron: action.pg_cron,
-      }
-    }
-    case 'change_pgaudit': {
-      return {
-        ...state,
-        pgaudit: action.pgaudit,
-      }
-    }
-    case 'change_pgvector': {
-      return {
-        ...state,
-        pgvector: action.pgvector,
-      }
-    }
-    case 'change_postgis': {
-      return {
-        ...state,
-        postgis: action.postgis,
-      }
-    }
-    case 'change_pgrouting': {
-      return {
-        ...state,
-        pgrouting: action.pgrouting,
-      }
-    }
-    case 'change_timescaledb': {
-      return {
-        ...state,
-        timescaledb: action.timescaledb,
-      }
-    }
-    case 'change_citus': {
-      return {
-        ...state,
-        citus: action.citus,
-      }
-    }
-    case 'change_pg_partman': {
-      return {
-        ...state,
-        pg_partman: action.pg_partman,
-      }
-    }
-    case 'change_pg_stat_kcache': {
-      return {
-        ...state,
-        pg_stat_kcache: action.pg_stat_kcache,
-      }
-    }
-    case 'change_pg_wait_sampling': {
-      return {
-        ...state,
-        pg_wait_sampling: action.pg_wait_sampling,
-      }
-    }
-
-    case 'change_file_system': {
-      return {
-        ...state,
-        fileSystem: action.fileSystem,
-      }
-    }
-
-    default:
-      throw new Error()
-  }
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterForm/utils/index.tsx b/ui/packages/platform/src/components/PostgresClusterForm/utils/index.tsx
deleted file mode 100644
index 98bc1504..00000000
--- a/ui/packages/platform/src/components/PostgresClusterForm/utils/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { CloudImage } from 'api/cloud/getCloudImages';
-import { DEBUG_API_SERVER, dockerRunCommand } from "components/DbLabInstanceForm/utils";
-import { addIndentForAnsible, addIndentForDocker, clusterExtensions } from "components/PostgresClusterInstallForm/utils";
-import { useCloudProviderProps } from "hooks/useCloudProvider";
-
-const API_SERVER = process.env.REACT_APP_API_SERVER
-
-const isDebugServer = API_SERVER === DEBUG_API_SERVER
-
-export const getClusterPlaybookCommand = (
-  state: useCloudProviderProps["initialState"],
-  cloudImages: CloudImage,
-  orgKey: string,
-  isDocker?: boolean,
-) => {
-  const playbookVariables = `ansible-playbook deploy_pgcluster.yml --extra-vars \\\r
-  "ansible_user='${state.provider === "aws" ? 'ubuntu' : 'root'}' \\\r
-  provision='${state.provider}' \\\r
-  servers_count='${state.numberOfInstances}' \\\r
-  server_type='${state.instanceType.native_name}' \\\r
-  server_image='${cloudImages?.native_os_image}' \\\r
-  server_location='${state.location.native_code}' \\\r
-  volume_size='${state.storage}' \\\r
-  postgresql_version='${state.version}' \\\r
-  database_public_access='${state.database_public_access}' \\\r
-  with_haproxy_load_balancing='${state.with_haproxy_load_balancing}' \\\r
-  pgbouncer_install='${state.pgbouncer_install}' \\\r
-  pg_data_mount_fstype='${state.fileSystem}' \\\r
-  synchronous_mode='${state.synchronous_mode}' \\\r
-  ${state.synchronous_mode ? `synchronous_node_count='${state.synchronous_node_count}' \\\r` : ``}
-  netdata_install='${state.netdata_install}' \\\r
-  patroni_cluster_name='${state.name}' \\\r
-  ${addIndentForAnsible(clusterExtensions(state as unknown as {[key: string]: boolean | string | number} ))}
-  ${state.publicKeys ? `ssh_public_keys='${state.publicKeys}' \\\r` : ``}
-  ${orgKey ? `platform_org_key='${orgKey}'${isDebugServer ? `\\\r` : `"`}` : ``}
-  ${isDebugServer ? `platform_url='${DEBUG_API_SERVER}'"` : ``}`
-
-  if (isDocker) {
-    return `${dockerRunCommand(state.provider)} \\\r
-    vitabaks/postgresql_cluster:cloud \\\r
-      ${addIndentForDocker(playbookVariables)}`
-  }
-  return playbookVariables
-}
-
-export const cloneClusterRepositoryCommand = () =>
-  `git clone --depth 1 --branch cloud \\\r
-  https://github.com/vitabaks/postgresql_cluster.git \\\r
-&& cd postgresql_cluster/`
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallForm.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallForm.tsx
deleted file mode 100644
index 62ba81a2..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallForm.tsx
+++ /dev/null
@@ -1,360 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Box } from '@mui/material'
-import { useReducer } from 'react'
-import {
-  TextField,
-  Accordion,
-  AccordionSummary,
-  AccordionDetails,
-  FormControlLabel,
-  Checkbox,
-} from '@material-ui/core'
-
-import ConsolePageTitle from '../ConsolePageTitle'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { DbLabInstanceFormProps } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import {
-  initialState,
-  reducer,
-} from 'components/PostgresClusterInstallForm/reducer'
-import { PostgresClusterInstallFormSidebar } from 'components/PostgresClusterInstallForm/PostgresClusterInstallFormSidebar'
-import { AnsibleInstance } from 'components/PostgresClusterInstallForm/PostgresClusterSteps/AnsibleInstance'
-import { DockerInstance } from 'components/PostgresClusterInstallForm/PostgresClusterSteps/DockerInstance'
-import { ClusterExtensionAccordion } from 'components/PostgresClusterForm/PostgresClusterSteps'
-import { Select } from '@postgres.ai/shared/components/Select'
-
-import { validateDLEName } from 'utils/utils'
-import urls from 'utils/urls'
-import { isIPAddress } from './utils'
-import { icons } from '@postgres.ai/shared/styles/icons'
-
-interface PostgresClusterInstallFormProps extends DbLabInstanceFormProps {
-  classes: ClassesType
-}
-
-const PostgresClusterInstallForm = (props: PostgresClusterInstallFormProps) => {
-  const { classes, orgPermissions } = props
-  const [state, dispatch] = useReducer(reducer, initialState)
-
-  const permitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-
-  const pageTitle = <ConsolePageTitle title="Install Postgres Cluster" />
-  const breadcrumbs = (
-    <ConsoleBreadcrumbsWrapper
-      {...props}
-      breadcrumbs={[
-        { name: 'Postgres Clusters', url: 'pg' },
-        { name: 'Install Postgres Cluster' },
-      ]}
-    />
-  )
-
-  const handleReturnToList = () => {
-    props.history.push(urls.linkClusters(props))
-  }
-
-  const handleSetFormStep = (step: string) => {
-    dispatch({ type: 'set_form_step', formStep: step })
-  }
-
-  const handleReturnToForm = () => {
-    dispatch({ type: 'set_form_step', formStep: initialState.formStep })
-  }
-
-  return (
-    <div className={classes.root}>
-      {breadcrumbs}
-
-      {pageTitle}
-
-      {!permitted && (
-        <WarningWrapper>
-          You do not have permission to add clusters to this organization.
-        </WarningWrapper>
-      )}
-
-      <div className={classes.container}>
-        {state.formStep === initialState.formStep && permitted ? (
-          <>
-            <div className={classes.form}>
-              <p className={classes.sectionTitle}>1. Provide cluster name</p>
-              <TextField
-                required
-                label="Cluster Name"
-                variant="outlined"
-                fullWidth
-                value={state.patroni_cluster_name}
-                className={classes.marginTop}
-                InputLabelProps={{
-                  shrink: true,
-                }}
-                helperText={
-                  validateDLEName(state.patroni_cluster_name)
-                    ? 'Name must be lowercase and contain only letters and numbers.'
-                    : ''
-                }
-                error={validateDLEName(state.patroni_cluster_name)}
-                onChange={(
-                  event: React.ChangeEvent<
-                    HTMLTextAreaElement | HTMLInputElement
-                  >,
-                ) =>
-                  dispatch({
-                    type: 'change_patroni_cluster_name',
-                    patroni_cluster_name: event.target.value,
-                  })
-                }
-              />
-              <p className={classes.sectionTitle}>2. Choose Postgres version</p>
-              <Select
-                label="Select version"
-                items={Array.from({ length: 7 }, (_, i) => i + 10).map(
-                  (version) => {
-                    return {
-                      value: version,
-                      children: version,
-                    }
-                  },
-                )}
-                value={state.version}
-                onChange={(
-                  e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
-                ) =>
-                  dispatch({
-                    type: 'change_version',
-                    version: e.target.value,
-                  })
-                }
-              />
-              <p className={classes.sectionTitle}>3. Data directory</p>
-              <p className={classes.instanceParagraph}>
-                If you want to place the data on a separate disk, you can
-                specify an alternative path to the data directory.
-              </p>
-              <TextField
-                required
-                label="PGDATA"
-                variant="outlined"
-                fullWidth
-                value={state.postgresql_data_dir}
-                className={classes.marginTop}
-                InputLabelProps={{
-                  shrink: true,
-                }}
-                onChange={(
-                  event: React.ChangeEvent<
-                    HTMLTextAreaElement | HTMLInputElement
-                  >,
-                ) =>
-                  dispatch({
-                    type: 'change_postgresql_data_dir',
-                    postgresql_data_dir: event.target.value,
-                  })
-                }
-              />
-              <ClusterExtensionAccordion
-                step={8}
-                state={Object.fromEntries(
-                  Object.entries(state).filter(
-                    ([key]) => key !== 'postgresql_data_dir',
-                  ),
-                )}
-                classes={classes}
-                dispatch={dispatch}
-              />
-              <Accordion className={classes.sectionTitle}>
-                <AccordionSummary
-                  aria-controls="panel1a-content"
-                  id="panel1a-header"
-                  expandIcon={icons.sortArrowDown}
-                >
-                  5. Advanced options
-                </AccordionSummary>
-                <AccordionDetails>
-                  <Box
-                    sx={{
-                      display: 'flex',
-                      flexDirection: 'column',
-                      width: '100%',
-                      fontWeight: 'normal',
-                    }}
-                  >
-                    <p className={classes.instanceParagraph}>
-                      Optional. Specify here the IP address that will be used as
-                      a single entry point for client access to databases in the
-                      cluster (not for cloud environments).
-                    </p>
-                    <TextField
-                      label="Cluster VIP address"
-                      variant="outlined"
-                      fullWidth
-                      error={!isIPAddress(state.cluster_vip)}
-                      helperText={
-                        isIPAddress(state.cluster_vip)
-                          ? ''
-                          : 'IP address is invalid'
-                      }
-                      value={state.cluster_vip}
-                      className={classes.marginTop}
-                      InputLabelProps={{
-                        shrink: true,
-                      }}
-                      onChange={(
-                        event: React.ChangeEvent<
-                          HTMLTextAreaElement | HTMLInputElement
-                        >,
-                      ) =>
-                        dispatch({
-                          type: 'change_cluster_vip',
-                          cluster_vip: event.target.value,
-                        })
-                      }
-                    />
-                    <FormControlLabel
-                      className={classes.marginTop}
-                      control={
-                        <Checkbox
-                          name="with_haproxy_load_balancing"
-                          checked={state.with_haproxy_load_balancing}
-                          onChange={(e) =>
-                            dispatch({
-                              type: 'change_with_haproxy_load_balancing',
-                              with_haproxy_load_balancing: e.target.checked,
-                            })
-                          }
-                          classes={{
-                            root: classes.checkboxRoot,
-                          }}
-                        />
-                      }
-                      label={'Haproxy load balancing'}
-                    />
-                    <FormControlLabel
-                      control={
-                        <Checkbox
-                          name="pgbouncer_install"
-                          checked={state.pgbouncer_install}
-                          onChange={(e) =>
-                            dispatch({
-                              type: 'change_pgbouncer_install',
-                              pgbouncer_install: e.target.checked,
-                            })
-                          }
-                          classes={{
-                            root: classes.checkboxRoot,
-                          }}
-                        />
-                      }
-                      label={'PgBouncer connection pooler'}
-                    />
-                    <FormControlLabel
-                      control={
-                        <Checkbox
-                          name="synchronous_mode"
-                          checked={state.synchronous_mode}
-                          onChange={(e) =>
-                            dispatch({
-                              type: 'change_synchronous_mode',
-                              synchronous_mode: e.target.checked,
-                            })
-                          }
-                          classes={{
-                            root: classes.checkboxRoot,
-                          }}
-                        />
-                      }
-                      label={'Enable synchronous replication'}
-                    />
-                    <TextField
-                      label="Number of synchronous standbys"
-                      variant="outlined"
-                      fullWidth
-                      type="number"
-                      InputProps={{
-                        inputProps: {
-                          min: 1,
-                        },
-                      }}
-                      value={state.synchronous_node_count}
-                      disabled={!state.synchronous_mode}
-                      className={classes.marginTop}
-                      InputLabelProps={{
-                        shrink: true,
-                      }}
-                      onChange={(
-                        e: React.ChangeEvent<
-                          HTMLTextAreaElement | HTMLInputElement
-                        >,
-                      ) =>
-                        dispatch({
-                          type: 'change_synchronous_node_count',
-                          synchronous_node_count: e.target.value,
-                        })
-                      }
-                    />
-                    <FormControlLabel
-                      className={classes.marginTop}
-                      control={
-                        <Checkbox
-                          name="netdata_install"
-                          checked={state.netdata_install}
-                          onChange={(e) =>
-                            dispatch({
-                              type: 'change_netdata_install',
-                              netdata_install: e.target.checked,
-                            })
-                          }
-                          classes={{
-                            root: classes.checkboxRoot,
-                          }}
-                        />
-                      }
-                      label={'Netdata monitoring'}
-                    />
-                  </Box>
-                </AccordionDetails>
-              </Accordion>
-            </div>
-            <PostgresClusterInstallFormSidebar
-              state={state}
-              disabled={
-                validateDLEName(state.patroni_cluster_name) ||
-                !isIPAddress(state.cluster_vip)
-              }
-              handleCreate={() =>
-                !validateDLEName(state.patroni_cluster_name) &&
-                handleSetFormStep('docker')
-              }
-            />
-          </>
-        ) : state.formStep === 'ansible' && permitted ? (
-          <AnsibleInstance
-            state={state}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : state.formStep === 'docker' && permitted ? (
-          <DockerInstance
-            state={state}
-            formStep={state.formStep}
-            setFormStep={handleSetFormStep}
-            goBack={handleReturnToList}
-            goBackToForm={handleReturnToForm}
-          />
-        ) : null}
-      </div>
-    </div>
-  )
-}
-
-export default PostgresClusterInstallForm
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallFormSidebar.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallFormSidebar.tsx
deleted file mode 100644
index bbeb60b0..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallFormSidebar.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Button, makeStyles } from '@material-ui/core'
-
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-
-const useStyles = makeStyles({
-  boxShadow: {
-    padding: '24px',
-    boxShadow: '0 8px 16px #3a3a441f, 0 16px 32px #5a5b6a1f',
-  },
-  aside: {
-    width: '100%',
-    height: 'fit-content',
-    borderRadius: '4px',
-    display: 'flex',
-    flexDirection: 'column',
-    justifyContent: 'flex-start',
-    flex: '1 1 0',
-    position: 'sticky',
-    top: 10,
-
-    '& h2': {
-      fontSize: '14px',
-      fontWeight: 500,
-      margin: '0 0 10px 0',
-      height: 'fit-content',
-    },
-
-    '& span': {
-      fontSize: '13px',
-    },
-
-    '& button': {
-      padding: '10px 20px',
-      marginTop: '20px',
-    },
-
-    '@media (max-width: 1200px)': {
-      position: 'relative',
-      boxShadow: 'none',
-      borderRadius: '0',
-      padding: '0',
-      flex: 'auto',
-      marginBottom: '30px',
-
-      '& button': {
-        width: 'max-content',
-      },
-    },
-  },
-  asideSection: {
-    padding: '12px 0',
-    borderBottom: '1px solid #e0e0e0',
-
-    '& span': {
-      color: '#808080',
-    },
-
-    '& p': {
-      margin: '5px 0 0 0',
-      fontSize: '13px',
-    },
-  },
-})
-
-export const PostgresClusterInstallFormSidebar = ({
-  state,
-  handleCreate,
-  disabled,
-}: {
-  state: useCloudProviderProps['initialState']
-  handleCreate: () => void
-  disabled: boolean
-}) => {
-  const classes = useStyles()
-
-  return (
-    <div className={classes.aside}>
-      <div className={classes.boxShadow}>
-        {state.patroni_cluster_name && (
-          <div className={classes.asideSection}>
-            <span>Cluster Name</span>
-            <p>{state.patroni_cluster_name}</p>
-          </div>
-        )}
-        <div className={classes.asideSection}>
-          <span>Postgres version</span>
-          <p>{state.version}</p>
-        </div>
-        <div className={classes.asideSection}>
-          <span>Data directory</span>
-          <p>{state.postgresql_data_dir}</p>
-        </div>
-        <Button
-          variant="contained"
-          color="primary"
-          onClick={handleCreate}
-          disabled={!state.patroni_cluster_name || disabled}
-        >
-          Install Postgres Cluster
-        </Button>
-      </div>
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallWrapper.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallWrapper.tsx
deleted file mode 100644
index 5e6bea25..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterInstallWrapper.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { RouteComponentProps } from 'react-router'
-
-import PostgresClusterInstallForm from './PostgresClusterInstallForm'
-import { useInstanceFormStyles } from 'components/DbLabInstanceForm/DbLabInstanceFormWrapper'
-import { OrgPermissions } from 'components/types'
-
-export interface PostgresClusterInstallFormWrapperProps {
-  edit?: boolean
-  orgId: number
-  project: string | undefined
-  history: RouteComponentProps['history']
-  orgPermissions: OrgPermissions
-}
-
-export const PostgresClusterInstallWrapper = (
-  props: PostgresClusterInstallFormWrapperProps,
-) => {
-  const classes = useInstanceFormStyles()
-
-  return <PostgresClusterInstallForm {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterSteps/AnsibleInstance.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterSteps/AnsibleInstance.tsx
deleted file mode 100644
index b02250c1..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterSteps/AnsibleInstance.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import {
-  Accordion,
-  AccordionDetails,
-  AccordionSummary,
-  Button,
-} from '@material-ui/core'
-import { Box } from '@mui/material'
-
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { formStyles } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-import { getAnsibleInstallationCommand } from 'components/DbLabInstanceInstallForm/utils'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-import {
-  getClusterCommand,
-  getClusterExampleCommand,
-  getPostgresClusterInstallationCommand,
-} from '../utils'
-
-export const AnsibleInstance = ({
-  state,
-  goBack,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  state: useCloudProviderProps['initialState']
-  goBack: () => void
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-
-  return (
-    <InstanceFormCreation formStep={formStep} setFormStep={setFormStep} install>
-      <>
-        <p className={classes.title}>1. Set up your machine</p>
-        <ul className={classes.ul}>
-          <li>
-            Obtain a machine running on one of the supported Linux
-            distributions: Ubuntu 20.04/22.04, Debian 11/12, CentOS Stream 8/9,
-            Rocky Linux 8/9, AlmaLinux 8/9, or Red Hat Enterprise Linux 8/9.
-          </li>
-          <li>
-            (Recommended) Attach and mount the disk for the data directory.
-          </li>
-          <li>
-            Ensure that your SSH public key is added to the machine (in
-            <code className={classes.code}>~/.ssh/authorized_keys</code>),
-            allowing for secure SSH access.
-          </li>
-        </ul>
-        <p className={classes.title}>
-          2. Install Ansible on your local machine (such as a laptop)
-        </p>
-        <SyntaxHighlight content={getAnsibleInstallationCommand()} />
-        <span className={classes.marginBottom}>
-          For additional instructions on installing Ansible, please refer to{' '}
-          <a
-            target="_blank"
-            href="https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html"
-            rel="noreferrer"
-          >
-            this guide
-          </a>
-          .
-        </span>
-        <p className={classes.title}>
-          3. Clone the postgresql_cluster repository
-        </p>
-        <SyntaxHighlight content={getPostgresClusterInstallationCommand()} />
-        <p className={classes.title}>4. Prepare the inventory file</p>
-        <ul className={classes.ul}>
-          <li>
-            Specify private IP addresses (non-public) and connection settings ({' '}
-            <code className={classes.code}>ansible_user</code>,
-            <code className={classes.code}>ansible_ssh_private_key_file</code>{' '}
-            or <code className={classes.code}>ansible_ssh_pass</code> for your
-            environment.
-          </li>
-          <li>
-            For deploying via public IPs, add '{' '}
-            <code className={classes.code}>ansible_host=public_ip_address</code>
-            ' variable for each node.
-          </li>
-        </ul>
-        <SyntaxHighlight content={`nano inventory`} />
-        <Box
-          sx={{
-            marginBottom: '35px',
-          }}
-        >
-          <Accordion className={classes.marginBottom}>
-            <AccordionSummary
-              aria-controls="panel1a-content"
-              id="panel1a-header"
-              expandIcon={icons.sortArrowDown}
-            >
-              Example
-            </AccordionSummary>
-            <AccordionDetails>
-              <SyntaxHighlight content={getClusterExampleCommand()} />
-            </AccordionDetails>
-          </Accordion>
-        </Box>
-        <p className={classes.title}>
-          5. Execute the Ansible playbook to Postgres Cluster
-        </p>
-        <SyntaxHighlight wrapLines content={getClusterCommand(state)} />
-        <p className={classes.title}>
-          6. After the code snippet runs successfully, follow the directions
-          displayed in the resulting output to start using the database.
-        </p>
-        <Box
-          sx={{
-            display: 'flex',
-            gap: '10px',
-            margin: '20px 0',
-          }}
-        >
-          <Button variant="outlined" color="secondary" onClick={goBackToForm}>
-            Back to form
-          </Button>
-          <Button variant="contained" color="primary" onClick={goBack}>
-            See list of clusters
-          </Button>
-        </Box>
-      </>
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterSteps/DockerInstance.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterSteps/DockerInstance.tsx
deleted file mode 100644
index 97c1c0ae..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/PostgresClusterSteps/DockerInstance.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import {
-  Accordion,
-  AccordionDetails,
-  AccordionSummary,
-  Button,
-} from '@material-ui/core'
-import { Box } from '@mui/material'
-
-import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { formStyles } from 'components/DbLabInstanceForm/DbLabFormSteps/AnsibleInstance'
-import { InstanceFormCreation } from 'components/DbLabInstanceForm/DbLabFormSteps/InstanceFormCreation'
-import { useCloudProviderProps } from 'hooks/useCloudProvider'
-import {
-  getClusterCommand,
-  getClusterExampleCommand,
-  getInventoryPreparationCommand,
-} from '../utils'
-
-export const DockerInstance = ({
-  state,
-  goBack,
-  goBackToForm,
-  formStep,
-  setFormStep,
-}: {
-  state: useCloudProviderProps['initialState']
-  goBack: () => void
-  goBackToForm: () => void
-  formStep: string
-  setFormStep: (step: string) => void
-}) => {
-  const classes = formStyles()
-
-  return (
-    <InstanceFormCreation formStep={formStep} setFormStep={setFormStep} install>
-      <>
-        <p className={classes.title}>1. Set up your machine</p>
-        <ul className={classes.ul}>
-          <li>
-            Obtain a machine running on one of the supported Linux
-            distributions: Ubuntu 20.04/22.04, Debian 11/12, CentOS Stream 8/9,
-            Rocky Linux 8/9, AlmaLinux 8/9, or Red Hat Enterprise Linux 8/9.
-          </li>
-          <li>
-            (Recommended) Attach and mount the disk for the data directory.
-          </li>
-          <li>
-            Ensure that your SSH public key is added to the machine (in
-            <code className={classes.code}>~/.ssh/authorized_keys</code>),
-            allowing for secure SSH access.
-          </li>
-        </ul>
-        <p className={classes.title}>2. Prepare the inventory file</p>
-        <ul className={classes.ul}>
-          <li>
-            Specify private IP addresses (non-public) and connection settings ({' '}
-            <code className={classes.code}>ansible_user</code>,
-            <code className={classes.code}>ansible_ssh_private_key_file</code>{' '}
-            or <code className={classes.code}>ansible_ssh_pass</code> for your
-            environment.
-          </li>
-          <li>
-            For deploying via public IPs, add '{' '}
-            <code className={classes.code}>ansible_host=public_ip_address</code>
-            ' variable for each node.
-          </li>
-        </ul>
-        <SyntaxHighlight content={getInventoryPreparationCommand()} />
-        <Box
-          sx={{
-            marginBottom: '35px',
-          }}
-        >
-          <Accordion className={classes.marginBottom}>
-            <AccordionSummary
-              aria-controls="panel1a-content"
-              id="panel1a-header"
-              expandIcon={icons.sortArrowDown}
-            >
-              Example
-            </AccordionSummary>
-            <AccordionDetails>
-              <SyntaxHighlight content={getClusterExampleCommand()} />
-            </AccordionDetails>
-          </Accordion>
-        </Box>
-        <p className={classes.title}>
-          3. Run ansible playbook to deploy Postgres Cluster
-        </p>
-        <SyntaxHighlight wrapLines content={getClusterCommand(state, true)} />
-        <p className={classes.title}>
-          4. After the code snippet runs successfully, follow the directions
-          displayed in the resulting output to start using the database.
-        </p>
-        <Box
-          sx={{
-            display: 'flex',
-            gap: '10px',
-            margin: '20px 0',
-          }}
-        >
-          <Button variant="outlined" color="secondary" onClick={goBackToForm}>
-            Back to form
-          </Button>
-          <Button variant="contained" color="primary" onClick={goBack}>
-            See list of clusters
-          </Button>
-        </Box>
-      </>
-    </InstanceFormCreation>
-  )
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/reducer/index.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/reducer/index.tsx
deleted file mode 100644
index 79a82c8e..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/reducer/index.tsx
+++ /dev/null
@@ -1,191 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { ReducerAction } from 'react'
-
-export const clusterExtensionsState = {
-  pg_repack: true,
-  pg_cron: true,
-  pgaudit: true,
-  pgvector: true,
-  postgis: false,
-  pgrouting: false,
-  timescaledb: false,
-  citus: false,
-  pg_partman: false,
-  pg_stat_kcache: false,
-  pg_wait_sampling: false,
-}
-
-export const initialState = {
-  isLoading: false,
-  isReloading: false,
-  formStep: 'create',
-  patroni_cluster_name: '',
-  version: 16,
-  postgresql_data_dir: '/var/lib/postgresql/16/<cluster-name>',
-  cluster_vip: '',
-  with_haproxy_load_balancing: false,
-  pgbouncer_install: true,
-  synchronous_mode: false,
-  synchronous_node_count: 1,
-  netdata_install: true,
-  taskID: '',
-  ...clusterExtensionsState,
-}
-
-export const reducer = (
-  state: typeof initialState,
-  // @ts-ignore
-  action: ReducerAction<unknown, void>,
-) => {
-  switch (action.type) {
-    case 'change_patroni_cluster_name': {
-      return {
-        ...state,
-        patroni_cluster_name: action.patroni_cluster_name,
-        postgresql_data_dir: `/var/lib/postgresql/${state.version}/${
-          action.patroni_cluster_name || '<cluster-name>'
-        }`,
-      }
-    }
-
-    case 'change_version': {
-      return {
-        ...state,
-        version: action.version,
-        postgresql_data_dir: `/var/lib/postgresql/${action.version}/${
-          state.patroni_cluster_name || '<cluster-name>'
-        }`,
-      }
-    }
-
-    case 'change_postgresql_data_dir': {
-      return {
-        ...state,
-        postgresql_data_dir: action.postgresql_data_dir,
-      }
-    }
-
-    case 'change_cluster_vip': {
-      return {
-        ...state,
-        cluster_vip: action.cluster_vip,
-      }
-    }
-
-    case 'change_with_haproxy_load_balancing': {
-      return {
-        ...state,
-        with_haproxy_load_balancing: action.with_haproxy_load_balancing,
-      }
-    }
-
-    case 'change_pgbouncer_install': {
-      return {
-        ...state,
-        pgbouncer_install: action.pgbouncer_install,
-      }
-    }
-
-    case 'change_synchronous_mode': {
-      return {
-        ...state,
-        synchronous_mode: action.synchronous_mode,
-      }
-    }
-
-    case 'change_synchronous_node_count': {
-      return {
-        ...state,
-        synchronous_node_count: action.synchronous_node_count,
-      }
-    }
-
-    case 'change_netdata_install': {
-      return {
-        ...state,
-        netdata_install: action.netdata_install,
-      }
-    }
-
-    case 'set_form_step': {
-      return {
-        ...state,
-        formStep: action.formStep,
-      }
-    }
-    case 'change_pg_repack': {
-      return {
-        ...state,
-        pg_repack: action.pg_repack,
-      }
-    }
-    case 'change_pg_cron': {
-      return {
-        ...state,
-        pg_cron: action.pg_cron,
-      }
-    }
-    case 'change_pgaudit': {
-      return {
-        ...state,
-        pgaudit: action.pgaudit,
-      }
-    }
-    case 'change_pgvector': {
-      return {
-        ...state,
-        pgvector: action.pgvector,
-      }
-    }
-    case 'change_postgis': {
-      return {
-        ...state,
-        postgis: action.postgis,
-      }
-    }
-    case 'change_pgrouting': {
-      return {
-        ...state,
-        pgrouting: action.pgrouting,
-      }
-    }
-    case 'change_timescaledb': {
-      return {
-        ...state,
-        timescaledb: action.timescaledb,
-      }
-    }
-    case 'change_citus': {
-      return {
-        ...state,
-        citus: action.citus,
-      }
-    }
-    case 'change_pg_partman': {
-      return {
-        ...state,
-        pg_partman: action.pg_partman,
-      }
-    }
-    case 'change_pg_stat_kcache': {
-      return {
-        ...state,
-        pg_stat_kcache: action.pg_stat_kcache,
-      }
-    }
-    case 'change_pg_wait_sampling': {
-      return {
-        ...state,
-        pg_wait_sampling: action.pg_wait_sampling,
-      }
-    }
-    default:
-      throw new Error()
-  }
-}
diff --git a/ui/packages/platform/src/components/PostgresClusterInstallForm/utils/index.tsx b/ui/packages/platform/src/components/PostgresClusterInstallForm/utils/index.tsx
deleted file mode 100644
index 1235f11c..00000000
--- a/ui/packages/platform/src/components/PostgresClusterInstallForm/utils/index.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { useCloudProviderProps } from "hooks/useCloudProvider"
-
-export const getInventoryPreparationCommand = () =>
-  `curl -fsSL https://raw.githubusercontent.com/vitabaks/postgresql_cluster/master/inventory \\\r
-  --output ~/inventory
-nano ~/inventory`
-
-export const addIndentForDocker = (command: string) =>
-  command
-    .split('\r\n')
-    .map((line, index) => {
-      if (index === 0) {
-        return `${line}`
-      } else {
-        return `${' '.repeat(8)}${line.replace(/^\s+/, '')}`
-      }
-    })
-    .join('\r\n')
-
-export const addIndentForAnsible = (command: string) =>
-command
-  .split('\r\n')
-  .map((line, index) => {
-    if (index === 0) {
-      return `${line.replace(/^\s+/, '')}`
-    } else {
-      return `${' '.repeat(2)}${line.replace(/^\s+/, '')}`
-    }
-  })
-  .join('\r\n')
-
-export const getClusterExampleCommand = () => 
-`[etcd_cluster]
-10.0.1.1 ansible_host=5.161.228.76
-10.0.1.2 ansible_host=5.161.224.229
-10.0.1.3 ansible_host=5.161.63.15
-
-[consul_instances]
-
-[balancers]
-10.0.1.1 ansible_host=5.161.228.76
-10.0.1.2 ansible_host=5.161.224.229
-10.0.1.3 ansible_host=5.161.63.15
-
-[master]
-10.0.1.1 ansible_host=5.161.228.76 hostname=pgnode01
-
-[replica]
-10.0.1.2 ansible_host=5.161.224.229 hostname=pgnode02
-10.0.1.3 ansible_host=5.161.63.15 hostname=pgnode03
-
-[postgres_cluster:children]
-master
-replica
-
-[pgbackrest]
-
-[all:vars]
-ansible_connection='ssh'
-ansible_ssh_port='22'
-ansible_user='root'
-ansible_ssh_private_key_file=/root/.ssh/id_rsa
-
-[pgbackrest:vars]
-`
-
-export const clusterExtensions = (state: {[key: string]: boolean | string | number}) => `${state.pg_repack ? `enable_pg_repack='${state.pg_repack}' \\\r` : ''}
-${state.pg_cron ? `enable_pg_cron='${state.pg_cron}' \\\r` : ''}
-${state.pgaudit ? `enable_pgaudit='${state.pgaudit}' \\\r` : ''}
-${state.version !== 10 && state.pgvector ? `enable_pgvector='${state.pgvector}' \\\r` : ''}
-${state.postgis ? `enable_postgis='${state.postgis}' \\\r` : ''}
-${state.pgrouting ? `enable_pgrouting='${state.pgrouting}' \\\r` : ''}
-${state.version !== 10 && state.version !== 11 && state.timescaledb ? `enable_timescaledb='${state.timescaledb}' \\\r` : ''}
-${state.version !== 10 && state.citus ? `enable_citus='${state.citus}' \\\r` : ''}
-${state.pg_partman ? `enable_pg_partman='${state.pg_partman}' \\\r` : ''}
-${state.pg_stat_kcache ? `enable_pg_stat_kcache='${state.pg_stat_kcache}' \\\r` : ''}
-${state.pg_wait_sampling ? `enable_pg_wait_sampling='${state.pg_wait_sampling}' \\\r` : ''}`
-
-export const getClusterCommand = (
-  state: useCloudProviderProps['initialState'],
-  isDocker?: boolean,
-) => {
-  const playbookVariables = `ansible-playbook deploy_pgcluster.yml --extra-vars \\\r
-  "postgresql_version='${state.version}' \\\r
-  patroni_cluster_name='${state.patroni_cluster_name}' \\\r
-  ${addIndentForAnsible(clusterExtensions(state))}
-  ${state.cluster_vip ? `cluster_vip='${state.cluster_vip} \\\r'` : ''}
-  with_haproxy_load_balancing='${state.with_haproxy_load_balancing}' \\\r
-  pgbouncer_install='${state.pgbouncer_install}' \\\r
-  synchronous_mode='${state.synchronous_mode}' \\\r
-  ${state.synchronous_mode ? `synchronous_node_count='${state.synchronous_node_count}' \\\r` : ''}
-  netdata_install='${state.netdata_install}' \\\r
-  postgresql_data_dir='${state.postgresql_data_dir}'"`
-
-  if (isDocker) {
-    return `docker run --rm -it \\\r
-    -v ~/inventory:/postgresql_cluster/inventory:ro \\\r
-    -v $HOME/.ssh:/root/.ssh:ro -e ANSIBLE_SSH_ARGS="-F none" \\\r
-    vitabaks/postgresql_cluster:cloud \\\r
-      ${addIndentForDocker(playbookVariables)}`
-  } 
-  
-  return playbookVariables
-}
-
-export const getPostgresClusterInstallationCommand = () => 
-`git clone --depth 1 --branch cloud \\\r
-  https://github.com/vitabaks/postgresql_cluster.git \\\r
-  && cd postgresql_cluster/
-`
-
-export function isIPAddress(input: string) {
-  if (input === '') {
-    return true
-  }
-
-  const ipPattern =
-    /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
-  return ipPattern.test(input)
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/components/PostgresClusters/PostgresClusters.tsx b/ui/packages/platform/src/components/PostgresClusters/PostgresClusters.tsx
deleted file mode 100644
index fb361911..00000000
--- a/ui/packages/platform/src/components/PostgresClusters/PostgresClusters.tsx
+++ /dev/null
@@ -1,606 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  Menu,
-  MenuItem,
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  TextField,
-} from '@material-ui/core'
-import { Component, MouseEvent } from 'react'
-
-import {
-  ClassesType,
-  ProjectProps,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { Modal } from '@postgres.ai/shared/components/Modal'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { styles } from '@postgres.ai/shared/styles/styles'
-
-import { InstanceDto } from '@postgres.ai/shared/types/api/entities/instance'
-import { InstanceStateDto } from '@postgres.ai/shared/types/api/entities/instanceState'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { CreateClusterCards } from 'components/CreateClusterCards/CreateClusterCards'
-import { DbLabInstancesProps } from 'components/DbLabInstances/DbLabInstancesWrapper'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { WarningWrapper } from 'components/Warning/WarningWrapper'
-import { ProjectDataType, getProjectAliasById } from 'utils/aliases'
-import Actions from '../../actions/actions'
-import { messages } from '../../assets/messages'
-import Store from '../../stores/store'
-import Urls from '../../utils/urls'
-import ConsolePageTitle from './../ConsolePageTitle'
-
-interface PostgresClustersProps extends DbLabInstancesProps {
-  classes: ClassesType
-}
-
-interface DbLabInstancesState {
-  modalState: {
-    open: boolean
-    type: string
-  }
-  data: {
-    auth: {
-      token: string
-    } | null
-    userProfile: {
-      data: {
-        orgs: ProjectDataType
-      }
-    }
-    dbLabInstances: {
-      orgId: number
-      data: {
-        [org: string]: {
-          created_at: string
-          project_label_or_name: string
-          plan: string
-          project_name: string
-          project_label: string
-          url: string
-          use_tunnel: boolean
-          isProcessing: boolean
-          id: string
-          project_alias: string
-          state: InstanceStateDto
-          dto: InstanceDto
-        }
-      }
-      isProcessing: boolean
-      projectId: string | number | undefined
-      error: boolean
-    } | null
-    dbLabInstanceStatus: {
-      instanceId: string
-      isProcessing: boolean
-    }
-    projects: Omit<ProjectProps, 'isProcessed'>
-  }
-  anchorEl: (EventTarget & HTMLButtonElement) | null
-}
-
-class PostgresClusters extends Component<
-  PostgresClustersProps,
-  DbLabInstancesState
-> {
-  componentDidMount() {
-    const that = this
-    const orgId = this.props.orgId ? this.props.orgId : null
-    let projectId = this.props.projectId ? this.props.projectId : null
-
-    if (!projectId) {
-      projectId =
-        this.props.match &&
-        this.props.match.params &&
-        this.props.match.params.projectId
-          ? this.props.match.params.projectId
-          : null
-    }
-
-    if (projectId) {
-      Actions.setDbLabInstancesProject(orgId, projectId)
-    } else {
-      Actions.setDbLabInstancesProject(orgId, 0)
-    }
-
-    this.unsubscribe = (Store.listen as RefluxTypes['listen'])(function () {
-      const auth: DbLabInstancesState['data']['auth'] =
-        this.data && this.data.auth ? this.data.auth : null
-      const dbLabInstances: DbLabInstancesState['data']['dbLabInstances'] =
-        this.data && this.data.dbLabInstances ? this.data.dbLabInstances : null
-      const projects: Omit<ProjectProps, 'isProcessed'> =
-        this.data && this.data.projects ? this.data.projects : null
-
-      if (
-        auth &&
-        auth.token &&
-        !dbLabInstances?.isProcessing &&
-        !dbLabInstances?.error &&
-        !that.state
-      ) {
-        Actions.getDbLabInstances(auth.token, orgId, projectId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects.isProcessing &&
-        !projects.error &&
-        !that.state
-      ) {
-        Actions.getProjects(auth.token, orgId)
-      }
-
-      that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  unsubscribe: Function
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleClick = (
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    id: string,
-  ) => {
-    const url = Urls.linkDbLabInstance(this.props, id)
-
-    if (url) {
-      this.props.history.push(url)
-    }
-  }
-
-  handleChangeProject = (
-    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
-  ) => {
-    const org = this.props.org ? this.props.org : null
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const projectId = event.target.value
-    const project = this.state.data
-      ? getProjectAliasById(this.state.data?.userProfile?.data?.orgs, projectId)
-      : ''
-    const props = { org, orgId, projectId, project }
-
-    Actions.setDbLabInstancesProject(orgId, event.target.value)
-    this.props.history.push(Urls.linkDbLabInstances(props))
-  }
-
-  registerButtonHandler = (provider: string) => {
-    this.props.history.push(Urls.linkDbLabInstanceAdd(this.props, provider))
-  }
-
-  openMenu = (event: MouseEvent<HTMLButtonElement>) => {
-    event.stopPropagation()
-    this.setState({ anchorEl: event.currentTarget })
-  }
-
-  closeMenu = () => {
-    this.setState({ anchorEl: null })
-  }
-
-  menuHandler = (_: MouseEvent<HTMLLIElement>, action: string) => {
-    const anchorEl = this.state.anchorEl
-
-    this.closeMenu()
-
-    setTimeout(() => {
-      const auth =
-        this.state.data && this.state.data.auth ? this.state.data.auth : null
-      const data =
-        this.state.data && this.state.data.dbLabInstances
-          ? this.state.data.dbLabInstances
-          : null
-
-      if (anchorEl) {
-        const instanceId = anchorEl.getAttribute('aria-label')
-        if (!instanceId) {
-          return
-        }
-
-        let project = ''
-        if (data?.data) {
-          for (const i in data.data) {
-            if (parseInt(data.data[i].id, 10) === parseInt(instanceId, 10)) {
-              project = data.data[i].project_alias
-            }
-          }
-        }
-
-        switch (action) {
-          case 'addclone':
-            this.props.history.push(
-              Urls.linkDbLabCloneAdd(
-                { org: this.props.org, project: project },
-                instanceId,
-              ),
-            )
-
-            break
-
-          case 'destroy':
-            /* eslint no-alert: 0 */
-            if (
-              window.confirm(
-                'Are you sure you want to remove this Database Lab instance?',
-              ) === true
-            ) {
-              Actions.destroyDbLabInstance(auth?.token, instanceId)
-            }
-
-            break
-
-          case 'refresh':
-            Actions.getDbLabInstanceStatus(auth?.token, instanceId)
-
-            break
-
-          case 'editProject':
-            this.props.history.push(
-              Urls.linkDbLabInstanceEditProject(
-                { org: this.props.org, project: project },
-                instanceId,
-              ),
-            )
-
-            break
-
-          default:
-            break
-        }
-      }
-    }, 100)
-  }
-
-  render() {
-    const { classes, orgPermissions, orgId } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.dbLabInstances
-        ? this.state.data.dbLabInstances
-        : null
-    const projects =
-      this.state && this.state.data && this.state.data?.projects
-        ? this.state.data.projects
-        : null
-    const projectId = this.props.projectId ? this.props.projectId : null
-    const menuOpen = Boolean(this.state && this.state.anchorEl)
-    const title = 'Postgres Clusters'
-    const addPermitted = !orgPermissions || orgPermissions.dblabInstanceCreate
-    const deletePermitted =
-      !orgPermissions || orgPermissions.dblabInstanceDelete
-
-    const addInstanceButton = (
-      <div className={classes.buttonContainer}>
-        <ConsoleButtonWrapper
-          disabled={!addPermitted}
-          variant="contained"
-          color="primary"
-          key="add-cluster"
-          onClick={() =>
-            this.setState({ modalState: { open: true, type: 'cluster' } })
-          }
-          title={addPermitted ? 'Create new cluster' : messages.noPermission}
-        >
-          New Postgres cluster
-        </ConsoleButtonWrapper>
-      </div>
-    )
-    const pageTitle = (
-      <ConsolePageTitle title={title} actions={[addInstanceButton]} />
-    )
-
-    let projectFilter = null
-    if (projects && projects.data && data) {
-      projectFilter = (
-        <div>
-          <TextField
-            value={data.projectId}
-            onChange={(event) => this.handleChangeProject(event)}
-            select
-            label="Name"
-            inputProps={{
-              name: 'project',
-              id: 'project-filter',
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel,
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper,
-            }}
-            variant="outlined"
-            className={classes.filterSelect}
-            disabled
-          >
-            <MenuItem value={0}>All</MenuItem>
-
-            {projects.data.map((p) => {
-              return (
-                <MenuItem value={p.id} key={p.id}>
-                  {p?.project_label_or_name || p.name}
-                </MenuItem>
-              )
-            })}
-          </TextField>
-        </div>
-      )
-    }
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[{ name: title }]}
-      />
-    )
-
-    if (orgPermissions && !orgPermissions.dblabInstanceList) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <WarningWrapper>{messages.noPermissionPage}</WarningWrapper>
-        </div>
-      )
-    }
-
-    if (this.state?.data && this.state.data?.dbLabInstances?.error) {
-      return (
-        <div>
-          {breadcrumbs}
-
-          <ConsolePageTitle title={title} />
-
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (data && data.isProcessing) ||
-      data.orgId !== orgId ||
-      data.projectId !== (projectId ? projectId : 0)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          <ConsolePageTitle title={title} />
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const CardsModal = () => (
-      <Modal
-        size={'md'}
-        isOpen={this.state.modalState.open}
-        title={'Choose the location for your Cluster installation'}
-        onClose={() => this.setState({ modalState: { open: false, type: '' } })}
-        aria-labelledby="simple-modal-title"
-        aria-describedby="simple-modal-description"
-      >
-        <CreateClusterCards
-          isModal
-          props={this.props}
-          dblabPermitted={addPermitted}
-        />
-      </Modal>
-    )
-
-    let table = (
-      <CreateClusterCards props={this.props} dblabPermitted={addPermitted} />
-    )
-
-    let menu = null
-    if (true) {
-      table = (
-        <HorizontalScrollContainer>
-          <Table className={classes.table} id="dblabInstancesTable">
-            <TableHead>
-              <TableRow className={classes.row}>
-                <TableCell>Cluster name</TableCell>
-                <TableCell>ID</TableCell>
-                <TableCell>Plan</TableCell>
-                <TableCell>Version</TableCell>
-                <TableCell>State</TableCell>
-                <TableCell>Created at</TableCell>
-                <TableCell>&nbsp;</TableCell>
-              </TableRow>
-            </TableHead>
-
-            <TableBody>
-              {/* {Object.keys(data.data).map((index) => {
-                return (
-                  <TableRow
-                    hover
-                    className={classes.row}
-                    key={index}
-                    onClick={(event) =>
-                      this.handleClick(event, data.data[index].id)
-                    }
-                    style={{ cursor: 'pointer' }}
-                  >
-                    <TableCell className={classes.cell}>
-                      {data.data[index].project_label_or_name ||
-                        data.data[index].project_name}
-                    </TableCell>
-
-                    <TableCell className={classes.cell}>
-                      {data.data[index].id}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      {data.data[index].state && data.data[index].url
-                        ? data.data[index].url
-                        : 'N/A'}
-                      {!isHttps(data.data[index].url) &&
-                      data.data[index].url &&
-                      !data.data[index].use_tunnel ? (
-                        <Tooltip
-                          title="The connection to Database Lab API is not secure"
-                          aria-label="add"
-                          classes={{ tooltip: classes.tooltip }}
-                        >
-                          <WarningIcon className={classes.warningIcon} />
-                        </Tooltip>
-                      ) : null}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      {data.data[index]?.state?.cloning?.numClones ??
-                        data.data[index]?.state?.clones?.length ??
-                        'N/A'}
-                    </TableCell>
-                    <TableCell>
-                      {data.data[index] &&
-                        (data.data[index]?.plan === 'EE'
-                          ? 'Enterprise'
-                          : data.data[index]?.plan === 'SE'
-                          ? 'Standard'
-                          : data.data[index]?.plan)}
-                    </TableCell>
-                    <TableCell>
-                      {getVersionDigits(
-                        data.data[index] &&
-                          (data.data[index].state?.engine?.version as string),
-                      )}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      {data.data[index].state && data.data[index].url ? (
-                        <DbLabStatusWrapper
-                          instance={data.data[index]}
-                          onlyText={false}
-                        />
-                      ) : (
-                        'N/A'
-                      )}
-                    </TableCell>
-                    <TableCell className={classes.cell}>
-                      <Tooltip
-                        title={formatDistanceToNowStrict(
-                          new Date(data.data[index].created_at),
-                          {
-                            addSuffix: true,
-                          },
-                        )}
-                        classes={{ tooltip: classes.timeLabel }}
-                      >
-                        <span>
-                          {format.formatTimestampUtc(
-                            data.data[index].created_at,
-                          ) ?? ''}
-                        </span>
-                      </Tooltip>
-                    </TableCell>
-                    <TableCell align={'right'}>
-                      {data.data[index].isProcessing ||
-                      (this.state.data?.dbLabInstanceStatus.instanceId ===
-                        index &&
-                        this.state.data.dbLabInstanceStatus.isProcessing) ? (
-                        <Spinner className={classes.inTableProgress} />
-                      ) : null}
-                      <IconButton
-                        aria-label={data.data[index].id}
-                        aria-controls="instance-menu"
-                        aria-haspopup="true"
-                        onClick={this.openMenu}
-                      >
-                        <MoreVertIcon />
-                      </IconButton>
-                    </TableCell>
-                  </TableRow>
-                )
-              })} */}
-            </TableBody>
-          </Table>
-        </HorizontalScrollContainer>
-      )
-
-      const selectedInstance = Object.values(data.data).filter((item) => {
-        const anchorElLabel = this.state.anchorEl?.getAttribute('aria-label')
-        // eslint-disable-next-line eqeqeq
-        return anchorElLabel && item.id == anchorElLabel
-      })[0]
-
-      menu = (
-        <Menu
-          id="instance-menu"
-          anchorEl={this.state.anchorEl}
-          keepMounted
-          open={menuOpen}
-          onClose={this.closeMenu}
-          PaperProps={{
-            style: {
-              width: 200,
-            },
-          }}
-        >
-          <MenuItem
-            key={1}
-            onClick={(event) => this.menuHandler(event, 'editProject')}
-            disabled={!addPermitted || selectedInstance?.plan === 'SE'}
-          >
-            Edit
-          </MenuItem>
-          <MenuItem
-            key={2}
-            onClick={(event) => this.menuHandler(event, 'addclone')}
-            disabled={selectedInstance?.plan === 'SE'}
-          >
-            Create clone
-          </MenuItem>
-          <MenuItem
-            key={3}
-            onClick={(event) => this.menuHandler(event, 'refresh')}
-            disabled={selectedInstance?.plan === 'SE'}
-          >
-            Refresh
-          </MenuItem>
-          <MenuItem
-            disabled={!deletePermitted}
-            key={4}
-            onClick={(event) => this.menuHandler(event, 'destroy')}
-          >
-            Remove from List
-          </MenuItem>
-        </Menu>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {projectFilter}
-
-        {table}
-
-        {menu}
-
-        {this.state.modalState && <CardsModal />}
-      </div>
-    )
-  }
-}
-
-export default PostgresClusters
diff --git a/ui/packages/platform/src/components/PostgresClusters/PostgresClustersWrapper.tsx b/ui/packages/platform/src/components/PostgresClusters/PostgresClustersWrapper.tsx
deleted file mode 100644
index 510e4ee5..00000000
--- a/ui/packages/platform/src/components/PostgresClusters/PostgresClustersWrapper.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import PostgresClusters from './PostgresClusters'
-import { OrgPermissions } from 'components/types'
-
-export interface DbLabInstancesProps {
-  orgId: number
-  org: string | number
-  project: string | undefined
-  projectId: string | number | undefined
-  history: RouteComponentProps['history']
-  match: {
-    params: {
-      project?: string
-      projectId?: string | number | undefined
-      org?: string
-    }
-  }
-  orgPermissions: OrgPermissions
-}
-
-export const PostgresClustersWrapper = (props: DbLabInstancesProps) => {
-  const useStyles = makeStyles(
-    {
-      root: {
-        ...(styles.root as Object),
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      filterSelect: {
-        ...styles.inputField,
-        width: 150,
-      },
-      cell: {
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      inTableProgress: {
-        width: '30px!important',
-        height: '30px!important',
-      },
-      warningIcon: {
-        color: colors.state.warning,
-        fontSize: '1.2em',
-        position: 'absolute',
-        marginLeft: 5,
-      },
-      tooltip: {
-        fontSize: '10px!important',
-      },
-      timeLabel: {
-        lineHeight: '16px',
-        fontSize: 12,
-        cursor: 'pointer',
-      },
-      buttonContainer: {
-        display: 'flex',
-        gap: 10,
-      },
-      flexContainer: {
-        display: 'flex',
-        alignItems: 'center',
-        justifyContent: 'center',
-        width: '100%',
-        height: '100%',
-        gap: 40,
-        marginTop: '20px',
-
-        '& > div': {
-          maxWidth: '300px',
-          width: '100%',
-          height: '100%',
-          display: 'flex',
-          justifyContent: 'center',
-          alignItems: 'center',
-          border: '1px solid #e0e0e0',
-          padding: '20px',
-          borderRadius: '4px',
-          cursor: 'pointer',
-          fontSize: '15px',
-          transition: 'border 0.3s ease-in-out',
-
-          '&:hover': {
-            border: '1px solid #FF6212',
-          },
-        },
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <PostgresClusters {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ProductCard/ProductCard.tsx b/ui/packages/platform/src/components/ProductCard/ProductCard.tsx
deleted file mode 100644
index 668b76a4..00000000
--- a/ui/packages/platform/src/components/ProductCard/ProductCard.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import clsx from 'clsx'
-
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-
-import { ProductCardProps } from 'components/ProductCard/ProductCardWrapper'
-
-interface ProductCardWithStylesProps extends ProductCardProps {
-  classes: ClassesType
-}
-
-class ProductCard extends Component<ProductCardWithStylesProps> {
-  render() {
-    const {
-      classes,
-      children,
-      actions,
-      inline,
-      title,
-      icon,
-      style,
-      className,
-    } = this.props
-
-    return (
-      <div
-        className={clsx(!inline && classes.block, classes.root, className)}
-        style={style}
-      >
-        <div>
-          <h1>{title}</h1>
-          <div className={classes.contentContainer}>{children}</div>
-        </div>
-
-        <div className={classes.bottomContainer}>
-          {icon}
-          <div className={classes.actionsContainer}>
-            {actions?.map((a) => {
-              return (
-                <span key={a.id} className={classes.buttonSpan}>
-                  {a.content}
-                </span>
-              )
-            })}
-          </div>
-        </div>
-      </div>
-    )
-  }
-}
-
-export default ProductCard
diff --git a/ui/packages/platform/src/components/ProductCard/ProductCardWrapper.tsx b/ui/packages/platform/src/components/ProductCard/ProductCardWrapper.tsx
deleted file mode 100644
index eb5f0647..00000000
--- a/ui/packages/platform/src/components/ProductCard/ProductCardWrapper.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { theme } from '@postgres.ai/shared/styles/theme'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { CSSProperties } from '@material-ui/styles'
-import ProductCard from 'components/ProductCard/ProductCard'
-
-export interface ProductCardProps {
-  children?: React.ReactNode
-  inline: boolean
-  title: string
-  icon?: JSX.Element | string
-  style?: CSSProperties | undefined
-  className?: string
-  actions?: {
-    id: string
-    content: JSX.Element | string
-  }[]
-}
-
-export const ProductCardWrapper = (props: ProductCardProps) => {
-  const useStyles = makeStyles(
-    (muiTheme) => ({
-      root: {
-        '& h1': {
-          fontSize: '16px',
-          margin: '0',
-        },
-        [muiTheme.breakpoints.down('xs')]: {
-          height: '100%',
-        },
-        fontFamily: theme.typography.fontFamily,
-        fontSize: '14px',
-        border: '1px solid ' + colors.consoleStroke,
-        maxWidth: '450px',
-        minHeight: '260px',
-        paddingTop: '15px',
-        paddingBottom: '15px',
-        paddingLeft: '20px',
-        paddingRight: '20px',
-        alignItems: 'center',
-        borderRadius: '3px',
-        // Flexbox.
-        display: 'flex',
-        '-webkit-flex-direction': 'column',
-        '-ms-flex-direction': 'column',
-        'flex-direction': 'column',
-        '-webkit-flex-wrap': 'nowrap',
-        '-ms-flex-wrap': 'nowrap',
-        'flex-wrap': 'nowrap',
-        '-webkit-justify-content': 'space-between',
-        '-ms-flex-pack': 'justify',
-        'justify-content': 'space-between',
-        '-webkit-align-content': 'flex-start',
-        '-ms-flex-line-pack': 'start',
-        'align-content': 'flex-start',
-        '-webkit-align-items': 'flex-start',
-        '-ms-flex-align': 'start',
-        'align-items': 'flex-start',
-      },
-      block: {
-        marginBottom: '20px',
-      },
-      icon: {
-        padding: '5px',
-      },
-      actionsContainer: {
-        marginTop: '15px',
-
-        [muiTheme.breakpoints.down('xs')]: {
-          marginTop: '5px',
-        },
-      },
-      contentContainer: {
-        marginTop: '15px',
-      },
-      bottomContainer: {
-        width: '100%',
-        display: 'flex',
-        '-webkit-flex-direction': 'row',
-        '-ms-flex-direction': 'row',
-        'flex-direction': 'row',
-        '-webkit-flex-wrap': 'wrap',
-        '-ms-flex-wrap': 'wrap',
-        'flex-wrap': 'wrap',
-        '-webkit-justify-content': 'space-between',
-        '-ms-flex-pack': 'justify',
-        'justify-content': 'space-between',
-        '-webkit-align-content': 'stretch',
-        '-ms-flex-line-pack': 'stretch',
-        'align-content': 'stretch',
-        '-webkit-align-items': 'flex-end',
-        '-ms-flex-align': 'end',
-        'align-items': 'flex-end',
-
-        [muiTheme.breakpoints.down('xs')]: {
-          flexDirection: 'column',
-          alignItems: 'center',
-        },
-      },
-      buttonSpan: {
-        '&:not(:first-child)': {
-          marginLeft: '15px',
-        },
-
-        [muiTheme.breakpoints.down('xs')]: {
-          '&:not(:first-child)': {
-            marginLeft: 0,
-          },
-
-          '& button': {
-            marginTop: '15px',
-            width: '100%',
-          },
-        },
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <ProductCard {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/Report/Report.tsx b/ui/packages/platform/src/components/Report/Report.tsx
deleted file mode 100644
index 6309b9c7..00000000
--- a/ui/packages/platform/src/components/Report/Report.tsx
+++ /dev/null
@@ -1,345 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, MouseEvent } from 'react'
-import { NavLink } from 'react-router-dom'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  Typography,
-  Button,
-} from '@material-ui/core'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import Urls from '../../utils/urls'
-import { ReportProps } from 'components/Report/ReportWrapper'
-
-interface ReportWithStylesProps extends ReportProps {
-  classes: ClassesType
-}
-
-interface ReportState {
-  data: {
-    auth: {
-      token: string
-    } | null
-    report: {
-      reportId: string
-      type: string
-      isProcessing: boolean
-      isDownloading: boolean
-      error: boolean
-      data: {
-        id: number
-        type: string
-        filename: string
-        created_formatted: string
-      }[]
-    } | null
-    reports: {
-      error: boolean
-      errorMessage: string
-      errorCode: number
-      isProcessing: boolean
-      data: {
-        id: number
-      }[]
-    } | null
-  } | null
-}
-
-class Report extends Component<ReportWithStylesProps, ReportState> {
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const reportId = this.props.match.params.reportId
-    const type = this.props.match.params.type
-      ? this.props.match.params.type
-      : 'md'
-    const orgId = this.props.orgId
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const reports = this.data && this.data.reports ? this.data.reports : null
-      const report = this.data && this.data.report ? this.data.report : null
-
-      if (auth && auth.token && !reports?.isProcessing && !that.state) {
-        Actions.getCheckupReports(auth.token, orgId, 0, reportId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        report?.reportId !== reportId &&
-        !report?.isProcessing &&
-        !report?.error &&
-        !reports?.error &&
-        reports?.data &&
-        reports.data[0].id
-      ) {
-        Actions.getCheckupReportFiles(
-          auth.token,
-          reportId,
-          type,
-          'filename',
-          'asc',
-        )
-      }
-
-      that.setState({ data: this.data })
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  componentDidUpdate(prevProps: ReportProps) {
-    const prevType = prevProps.match.params.type
-      ? prevProps.match.params.type
-      : 'md'
-    const curType = this.props.match.params.type
-      ? this.props.match.params.type
-      : 'md'
-    const reportId = this.props.match.params.reportId
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    if (prevType !== curType) {
-      const report =
-        this.state.data && this.state.data.report
-          ? this.state.data.report
-          : null
-
-      if (
-        auth &&
-        report !== null &&
-        auth.token &&
-        (report?.reportId !== reportId || report.type !== curType) &&
-        !report.isProcessing &&
-        !report.error
-      ) {
-        Actions.getCheckupReportFiles(
-          auth.token,
-          reportId,
-          curType,
-          'filename',
-          'asc',
-        )
-      }
-    }
-  }
-
-  handleClick = (
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    id: number,
-    type: string,
-  ) => {
-    this.props.history.push(this.getReportFileLink(id, type))
-  }
-
-  getReportFileLink(id: number, type: string) {
-    const reportId = this.props.match.params.reportId
-
-    return Urls.linkReportFile(this.props, reportId, id, type)
-  }
-
-  downloadJsonFiles = () => {
-    const auth =
-      this.state && this.state.data && this.state.data.auth
-        ? this.state.data.auth
-        : null
-    const reportId = this.props.match.params.reportId
-
-    Actions.downloadReportJsonFiles(auth?.token, reportId)
-  }
-
-  render() {
-    const { classes } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.report
-        ? this.state.data.report
-        : null
-    let reportId = this.props.match.params.reportId
-    const type = this.props.match.params.type
-      ? this.props.match.params.type
-      : 'md'
-
-    let breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[
-          { name: 'Reports', url: 'reports' },
-          { name: 'Report #' + reportId },
-        ]}
-      />
-    )
-
-    let menu = null
-    if (type === 'md') {
-      menu = (
-        <div>
-          <div className={classes.fileTypeMenu}>
-            <Typography
-              className={classes.activeHorizontalMenuItem}
-              color="textPrimary"
-            >
-              Markdown files
-            </Typography>
-            <NavLink
-              className={classes.horizontalMenuItem}
-              to={Urls.linkReport(this.props, reportId, 'json')}
-            >
-              JSON files
-            </NavLink>
-          </div>
-        </div>
-      )
-    }
-
-    if (type === 'json') {
-      menu = (
-        <div>
-          <div className={classes.fileTypeMenu}>
-            <NavLink
-              className={classes.horizontalMenuItem}
-              to={Urls.linkReport(this.props, reportId)}
-            >
-              Markdown files
-            </NavLink>
-            <Typography
-              className={classes.activeHorizontalMenuItem}
-              color="textPrimary"
-            >
-              JSON files
-            </Typography>
-          </div>
-
-          <Button
-            variant="contained"
-            color="primary"
-            onClick={this.downloadJsonFiles}
-            disabled={data?.isDownloading}
-          >
-            Download all
-            {data && data.isDownloading ? (
-              <span>
-                &nbsp;
-                <Spinner size="sm" />
-              </span>
-            ) : (
-              ''
-            )}
-          </Button>
-        </div>
-      )
-    }
-
-    let errorWidget = null
-    if (this.state && this.state.data?.reports?.error) {
-      errorWidget = (
-        <ErrorWrapper
-          message={this.state.data.reports.errorMessage}
-          code={this.state.data.reports.errorCode}
-        />
-      )
-    }
-
-    if (this.state && this.state.data && this.state.data?.report?.error) {
-      errorWidget = <ErrorWrapper />
-    }
-
-    if (errorWidget) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {errorWidget}
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (data && data.isProcessing) ||
-      (data && data.reportId !== reportId)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {menu}
-
-        {data.data && data.data.length > 0 ? (
-          <HorizontalScrollContainer>
-            <Table className={classes.table}>
-              <TableHead>
-                <TableRow className={classes.row}>
-                  <TableCell>Filename</TableCell>
-                  <TableCell>Type</TableCell>
-                  <TableCell>Created</TableCell>
-                </TableRow>
-              </TableHead>
-              <TableBody>
-                {data.data.map((f) => {
-                  return (
-                    <TableRow
-                      hover
-                      className={classes.row}
-                      key={f.id}
-                      onClick={(event) => this.handleClick(event, f.id, f.type)}
-                      style={{ cursor: 'pointer' }}
-                    >
-                      <TableCell className={classes.cell}>
-                        <NavLink to={this.getReportFileLink(f.id, f.type)}>
-                          {f.filename}
-                        </NavLink>
-                      </TableCell>
-                      <TableCell className={classes.cell}>{f.type}</TableCell>
-                      <TableCell className={classes.cell}>
-                        {f.created_formatted}
-                      </TableCell>
-                    </TableRow>
-                  )
-                })}
-              </TableBody>
-            </Table>
-          </HorizontalScrollContainer>
-        ) : (
-          'This report does not contain any files.'
-        )}
-
-        <div className={classes.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default Report
diff --git a/ui/packages/platform/src/components/Report/ReportWrapper.tsx b/ui/packages/platform/src/components/Report/ReportWrapper.tsx
deleted file mode 100644
index f3153398..00000000
--- a/ui/packages/platform/src/components/Report/ReportWrapper.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import Report from 'components/Report/Report'
-
-interface MatchParams {
-  reportId: string
-  type?: string
-}
-
-export interface ReportProps extends RouteComponentProps<MatchParams> {
-  orgId: number
-}
-export const ReportWrapper = (props: ReportProps) => {
-  const useStyles = makeStyles(
-    {
-      root: {
-        ...(styles.root as Object),
-        flex: '1 1 100%',
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      cell: {
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      horizontalMenuItem: {
-        display: 'inline-block',
-        marginRight: 10,
-        'font-weight': 'normal',
-        color: 'black',
-        '&:visited': {
-          color: 'black',
-        },
-      },
-      activeHorizontalMenuItem: {
-        display: 'inline-block',
-        marginRight: 10,
-        'font-weight': 'bold',
-        color: 'black',
-      },
-      fileTypeMenu: {
-        marginBottom: 10,
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Report {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ReportFile/ReportFile.tsx b/ui/packages/platform/src/components/ReportFile/ReportFile.tsx
deleted file mode 100644
index 6614b0b9..00000000
--- a/ui/packages/platform/src/components/ReportFile/ReportFile.tsx
+++ /dev/null
@@ -1,428 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component, HTMLAttributeAnchorTarget } from 'react'
-import { Button, TextField } from '@material-ui/core'
-import ReactMarkdown from 'react-markdown'
-import rehypeRaw from 'rehype-raw'
-import remarkGfm from 'remark-gfm'
-
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import Urls from '../../utils/urls'
-import { ReportFileProps } from 'components/ReportFile/ReportFileWrapper'
-
-interface LinkProps {
-  href: string | HTMLAttributeAnchorTarget
-  target: string
-  children: React.ReactNode
-}
-
-interface ReportFileWithStylesProps extends ReportFileProps {
-  classes: ClassesType
-}
-
-interface ReportFileState {
-  data: {
-    auth: {
-      token: string
-    } | null
-    reportFile: {
-      isProcessing: boolean
-      error: boolean
-      errorCode: number
-      errorMessage: string
-      files: {
-        [file: string]: {
-          filename: string
-          text: string
-          data: string
-        }
-      }
-    } | null
-    report: {
-      isProcessing: boolean
-      data: {
-        id: number
-      }[]
-    } | null
-  } | null
-}
-
-const mdViewerStyles = (
-  <style>
-    {`
-    .md-report-file-preview{
-        margin: 5px;
-        font-size: 14px;
-    }
-    .md-report-file-preview h1 {
-        margin-top: 5px;
-    }
-
-    .md-report-file-preview table{
-        border-collapse: collapse;
-        border-spacing: 0px;
-    }
-
-    .md-report-file-preview pre {
-        border: 1px solid #ccc;
-        background: #f6f8fa;
-        padding: 5px;
-    }
-
-    .md-report-file-preview blockquote {
-        color: #666;
-        margin: 0;
-        padding-left: 3em;
-        border-left: 0.5em #eee solid;
-    }
-
-    .md-report-file-preview tr {
-        border-top: 1px solid #c6cbd1;
-        background: #fff;
-    }
-
-    .md-report-file-preview th,
-    .md-report-file-preview td {
-        padding: 10px 13px;
-        border: 1px solid #dfe2e5;
-    }
-
-    .md-report-file-preview table tr:nth-child(2n) {
-        background: #f6f8fa;
-    }
-    .md-report-file-preview img.emoji{
-        margin-top: 5px;
-    }
-`}
-  </style>
-)
-
-const textAreaStyles = (
-  <style>
-    {`
-    textarea {
-      -moz-tab-size: 4;
-      -webkit-tab-size: 4;
-      -o-tab-size : 4;
-      tab-size : 4;
-    }
-`}
-  </style>
-)
-
-class ReportFile extends Component<ReportFileWithStylesProps, ReportFileState> {
-  getFileId() {
-    let fileID = this.props.match.params.fileId
-    let parseID = parseInt(fileID, 10)
-
-    // To distinct different fileIDs. For example, "72215" and "1_1.sql".
-    // {ORG_URL}/reports/268/files/72215/md
-    // {ORG_URL}/reports/268/files/1_1.sql/sql?raw
-    return (fileID.toString() == parseID.toString()) ? parseID : fileID
-  }
-
-  componentDidMount() {
-    const that = this
-    const projectId = this.props.projectId
-    const reportId = parseInt(this.props.match.params.reportId, 10)
-    const type = this.props.match.params.fileType
-    const id = this.getFileId()
-    const fileId = type.toLowerCase() + '_' + id
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const reportFile =
-        this.data && this.data.reportFile ? this.data.reportFile : null
-      const report = this.data && this.data.report ? this.data.report : null
-
-      that.setState({ data: this.data })
-
-      if (
-        auth &&
-        auth.token &&
-        !reportFile?.files[fileId] &&
-        !reportFile?.isProcessing &&
-        !reportFile?.error
-      ) {
-        Actions.getCheckupReportFile(auth.token, projectId, reportId, id, type)
-      }
-
-      if (auth && auth.token && !report?.isProcessing && !report?.data) {
-        Actions.getCheckupReportFiles(
-          auth.token,
-          reportId,
-          'md',
-          'filename',
-          'asc',
-        )
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  unsubscribe: Function
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  downloadFile(fileId: string) {
-    let type = this.props.match.params.fileType
-    const data =
-      this.state.data && this.state.data.reportFile
-        ? this.state.data.reportFile
-        : null
-    const fileName = data?.files[fileId].filename
-    let content = data?.files[fileId].text
-      ? data.files[fileId].text
-      : (data?.files[fileId].data as string)
-
-    if (type === 'json') {
-      let jsonData = null
-      try {
-        jsonData = JSON.parse(content)
-        content = JSON.stringify(jsonData, null, '\t')
-      } catch (err) {
-        content = ''
-        console.log(err)
-      }
-    }
-
-    if (content) {
-      let a: HTMLAnchorElement = document.createElement('a')
-      let file = new Blob([content], { type: 'application/json' })
-      a.href = URL.createObjectURL(file)
-      a.download = fileName as string
-      a.click()
-    }
-
-    return false
-  }
-
-  markdownLink(linkProps: LinkProps) {
-    const { href, target, children } = linkProps
-    const reportId = this.props.match.params.reportId
-    let fileName = null
-
-    let match = href.match(/..\/..\/json_reports\/(.*)\/K_query_groups\/(.*)/)
-    if (match && match.length > 2) {
-      fileName = match[2]
-    }
-
-    if (fileName) {
-      return (
-        <a
-          href={Urls.linkReportQueryFull(this.props, reportId, fileName)}
-          target={'_blank'}
-          rel="noreferrer"
-        >
-          {children}
-        </a>
-      )
-    }
-
-    if (!href.startsWith('#')) {
-      // Show external link in new tab
-      return (
-        <a href={href} target={'_blank'} rel="noreferrer">
-          {children}
-        </a>
-      )
-    }
-
-    return (
-      <a href={href} target={target}>
-        {children}
-      </a>
-    )
-  }
-
-  copyFile() {
-    let copyText = document.getElementById('fileContent') as HTMLInputElement
-    if (copyText) {
-      copyText.select()
-      copyText.setSelectionRange(0, 99999)
-      document.execCommand('copy')
-      copyText.setSelectionRange(0, 0)
-    }
-  }
-
-  render() {
-    const that = this
-    const { classes } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.reportFile
-        ? this.state.data.reportFile
-        : null
-    const reportId = this.props.match.params.reportId
-    const id = this.getFileId()
-    const type = this.props.match.params.fileType
-    const fileId: string = type.toLowerCase() + '_' + id
-    const raw = this.props.raw
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[
-          { name: 'Reports', url: 'reports' },
-          {
-            name: 'Report #' + reportId,
-            url: Urls.linkReport(this.props, reportId, type),
-          },
-          {
-            name:
-              data && data.files && data.files[fileId]
-                ? data.files[fileId].filename
-                : 'Report file',
-          },
-        ]}
-      />
-    )
-
-    if (this.state && this.state.data && this.state.data?.reportFile?.error) {
-      return (
-        <div>
-          {!raw && breadcrumbs}
-
-          <ErrorWrapper
-            message={this.state.data.reportFile.errorMessage}
-            code={this.state.data.reportFile.errorCode}
-          />
-        </div>
-      )
-    }
-
-    if (!data || (data && data.isProcessing) || (data && !data.files[fileId])) {
-      return (
-        <>
-          {!raw && breadcrumbs}
-
-          <PageSpinner />
-        </>
-      )
-    }
-
-    let fileContent = null
-    let fileLink = null
-    let copyBtn = null
-    if (type === 'md') {
-      fileContent = (
-        <div>
-          {mdViewerStyles}
-
-          <ReactMarkdown
-            className="md-report-file-preview"
-            children={data.files[fileId].data}
-            rehypePlugins={[rehypeRaw]}
-            remarkPlugins={[remarkGfm]}
-            components={{
-              a: (props) => {
-                return that.markdownLink(props as LinkProps)
-              },
-            }}
-          />
-        </div>
-      )
-    } else {
-      let content = data.files[fileId].text
-        ? data.files[fileId].text
-        : data.files[fileId].data
-
-      if (type === 'json') {
-        let jsonData = null
-
-        try {
-          jsonData = JSON.parse(content)
-          content = JSON.stringify(jsonData, null, '\t')
-        } catch (err) {
-          console.log(err)
-        }
-      }
-
-      fileContent = (
-        <div>
-          {textAreaStyles}
-
-          <TextField
-            id="fileContent"
-            multiline
-            fullWidth
-            value={content}
-            className={!raw ? classes.code : classes.rawCode}
-            variant="outlined"
-            margin="normal"
-            InputProps={{
-              readOnly: true,
-              id: 'fileContent',
-            }}
-          />
-        </div>
-      )
-
-      fileLink = (
-        <Button
-          variant="contained"
-          color="primary"
-          onClick={() => this.downloadFile(fileId)}
-        >
-          Download
-        </Button>
-      )
-
-      copyBtn = (
-        <Button
-          variant="contained"
-          color="primary"
-          style={{ marginLeft: 10 }}
-          onClick={() => this.copyFile()}
-        >
-          Copy
-        </Button>
-      )
-    }
-
-    return (
-      <div
-        className={classes.root}
-        style={
-          raw
-            ? {
-                width: '100vw',
-                height: '100vh',
-                maxWidth: '100vw',
-                maxHeight: '100vh',
-                minWidth: '100vw',
-                minHeight: '100vh',
-                overflow: 'scroll',
-              }
-            : {}
-        }
-      >
-        <div style={raw ? { padding: '10px' } : {}}>
-          {!raw && breadcrumbs}
-
-          {fileLink}
-          {raw && copyBtn}
-
-          {fileContent}
-        </div>
-
-        <div className={classes.bottomSpace} />
-      </div>
-    )
-  }
-}
-
-export default ReportFile
diff --git a/ui/packages/platform/src/components/ReportFile/ReportFileWrapper.tsx b/ui/packages/platform/src/components/ReportFile/ReportFileWrapper.tsx
deleted file mode 100644
index e221104d..00000000
--- a/ui/packages/platform/src/components/ReportFile/ReportFileWrapper.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import ReportFile from 'components/ReportFile/ReportFile'
-
-interface MatchParams {
-  fileId: string
-  fileType: string
-  reportId: string
-}
-
-export interface ReportFileProps extends RouteComponentProps<MatchParams> {
-  projectId: string | number | undefined
-  reportId: string
-  fileType: string
-  raw?: boolean
-}
-
-export const ReportFileWrapper = (props: ReportFileProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        width: '100%',
-        [theme.breakpoints.down('sm')]: {
-          maxWidth: '100vw',
-        },
-        [theme.breakpoints.up('md')]: {
-          maxWidth: 'calc(100vw - 240px)',
-        },
-        [theme.breakpoints.up('lg')]: {
-          maxWidth: 'calc(100vw - 240px)',
-        },
-        minHeight: '100%',
-        zIndex: 1,
-        position: 'relative',
-      },
-      reportFileContent: {
-        border: '1px solid silver',
-        margin: 5,
-        padding: 5,
-      },
-      code: {
-        width: '100%',
-        'background-color': 'rgb(246, 248, 250)',
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: 14,
-        },
-      },
-      rawCode: {
-        width: '100%',
-        'background-color': 'none',
-        'margin-top': '10px',
-        padding: '0px',
-        '& > .MuiOutlinedInput-multiline': {
-          padding: '0px!important',
-        },
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: 14,
-        },
-        '& > .MuiInputBase-fullWidth > fieldset': {
-          borderWidth: 'none!important',
-          borderStyle: 'none!important',
-          borderRadius: '0px!important',
-        },
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <ReportFile {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/Reports/Reports.tsx b/ui/packages/platform/src/components/Reports/Reports.tsx
deleted file mode 100644
index 1bf26489..00000000
--- a/ui/packages/platform/src/components/Reports/Reports.tsx
+++ /dev/null
@@ -1,586 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { Component, MouseEvent } from 'react'
-import { NavLink } from 'react-router-dom'
-import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableRow,
-  TextField,
-  MenuItem,
-  Button,
-  Checkbox,
-} from '@material-ui/core'
-
-import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
-import { StubContainer } from '@postgres.ai/shared/components/StubContainer'
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink'
-import {
-  ClassesType,
-  ProjectProps,
-  RefluxTypes,
-} from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import ConsolePageTitle from './../ConsolePageTitle'
-import { messages } from '../../assets/messages'
-import { getProjectAliasById, ProjectDataType } from 'utils/aliases'
-import { ConsoleButtonWrapper } from 'components/ConsoleButton/ConsoleButtonWrapper'
-import { ProductCardWrapper } from 'components/ProductCard/ProductCardWrapper'
-import { ReportsProps } from 'components/Reports/ReportsWrapper'
-
-interface ReportsWithStylesProps extends ReportsProps {
-  classes: ClassesType
-}
-
-interface ReportsType {
-  error: boolean
-  errorMessage: string
-  errorCode: number | null
-  orgId: number | null
-  projectId: string | number | undefined
-  isDeleting: boolean
-  isProcessing: boolean
-  data: {
-    id: number
-    project_id: string
-    created_formatted: string
-    project_name: string
-    project_label_or_name: string
-    project_label: string
-    epoch: string
-  }[]
-}
-
-interface ReportsState {
-  data: {
-    auth: {
-      token: string
-    } | null
-    userProfile: {
-      data: {
-        orgs: ProjectDataType
-      }
-    } | null
-    reports: ReportsType | null
-    projects: Omit<ProjectProps, 'isProcessed'>
-  } | null
-  selectedRows: {
-    [rows: number]: number | boolean
-  }
-}
-
-class Reports extends Component<ReportsWithStylesProps, ReportsState> {
-  state = {
-    data: {
-      auth: {
-        token: '',
-      },
-      userProfile: {
-        data: {
-          orgs: {},
-        },
-      },
-      reports: {
-        error: false,
-        errorMessage: '',
-        errorCode: null,
-        orgId: null,
-        projectId: undefined,
-        isDeleting: false,
-        isProcessing: false,
-        data: [],
-      },
-      projects: {
-        error: false,
-        isProcessing: false,
-        isProcessed: false,
-        data: [],
-      },
-    },
-    selectedRows: {},
-  }
-
-  onSelectRow(event: React.ChangeEvent<HTMLInputElement>, rowId: number) {
-    let selectedRows: ReportsState['selectedRows'] = this.state.selectedRows
-
-    if (selectedRows[rowId] && !event.target.checked) {
-      delete selectedRows[rowId]
-    } else {
-      selectedRows[rowId] = event.target.checked
-    }
-
-    this.setState({ selectedRows: selectedRows })
-  }
-
-  onCheckBoxClick(
-    event: React.ChangeEvent<HTMLInputElement> | MouseEvent<HTMLButtonElement>,
-  ) {
-    event.stopPropagation()
-  }
-
-  onSelectAllClick(
-    event: React.ChangeEvent<HTMLInputElement>,
-    reports: ReportsType['data'],
-  ) {
-    if (!event.target.checked) {
-      this.setState({ selectedRows: {} })
-      return
-    }
-
-    let selectedRows: ReportsState['selectedRows'] = {}
-    if (selectedRows)
-      for (let i in reports) {
-        if (reports.hasOwnProperty(i)) {
-          selectedRows[reports[i].id] = true
-        }
-      }
-
-    this.setState({ selectedRows: selectedRows })
-  }
-
-  deleteReports() {
-    const count = Object.keys(this.state.selectedRows).length
-    const auth =
-      this.state.data && this.state.data.auth ? this.state.data.auth : null
-
-    /* eslint no-alert: 0 */
-    if (
-      window.confirm(
-        'Are you sure you want to delete ' + count + ' report(s)?',
-      ) === true
-    ) {
-      let reports = []
-      for (let i in this.state.selectedRows) {
-        if (this.state.selectedRows.hasOwnProperty(i)) {
-          reports.push(parseInt(i, 10))
-        }
-      }
-
-      Actions.deleteCheckupReports(auth?.token, reports)
-      this.setState({ selectedRows: {} })
-    }
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const orgId = this.props.orgId ? this.props.orgId : null
-    let projectId = this.props.projectId ? this.props.projectId : null
-
-    if (projectId) {
-      Actions.setReportsProject(orgId, projectId)
-    } else {
-      Actions.setReportsProject(orgId, 0)
-    }
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null
-      const reports: ReportsType =
-        this.data && this.data.reports ? this.data.reports : null
-      const projects: Omit<ProjectProps, 'isProcessed'> =
-        this.data && this.data.projects ? this.data.projects : null
-
-      if (
-        auth &&
-        auth.token &&
-        !reports.isProcessing &&
-        !reports.error &&
-        !that.state
-      ) {
-        Actions.getCheckupReports(auth.token, orgId, projectId)
-      }
-
-      if (
-        auth &&
-        auth.token &&
-        !projects.isProcessing &&
-        !projects.error &&
-        !that?.state &&
-        !that?.state?.data
-      ) {
-        Actions.getProjects(auth.token, orgId)
-      }
-
-      that.setState({ data: this.data })
-    })
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  handleClick = (
-    _: MouseEvent<HTMLTableRowElement, globalThis.MouseEvent>,
-    id: number,
-    projectId: string | number,
-  ) => {
-    const url = this.getReportLink(id, projectId)
-
-    if (url) {
-      this.props.history.push(url)
-    }
-  }
-
-  getReportLink(id: number, projectId: string | number) {
-    const org = this.props.org ? this.props.org : null
-    const project = this.props.project ? this.props.project : null
-    let projectAlias = project
-
-    if (
-      !projectAlias &&
-      org &&
-      this.state.data &&
-      this.state.data.userProfile?.data.orgs
-    ) {
-      projectAlias = getProjectAliasById(
-        this.state.data.userProfile.data.orgs,
-        projectId,
-      )
-    }
-
-    if (org && projectAlias) {
-      return '/' + org + '/' + projectAlias + '/reports/' + id
-    }
-
-    return null
-  }
-
-  handleChangeProject = (
-    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
-  ) => {
-    const org = this.props.org ? this.props.org : null
-    const orgId = this.props.orgId ? this.props.orgId : null
-    const projectId = event.target.value
-    let projectAlias = null
-
-    if (org && this.state.data && this.state.data.userProfile?.data.orgs) {
-      projectAlias = getProjectAliasById(
-        this.state.data.userProfile.data.orgs,
-        projectId,
-      )
-    }
-
-    Actions.setReportsProject(orgId, event.target.value)
-    if (org && this.props.history) {
-      if (Number(event.target.value) !== 0 && projectId && projectAlias) {
-        this.props.history.push('/' + org + '/' + projectAlias + '/reports/')
-      } else {
-        this.props.history.push('/' + org + '/reports/')
-      }
-    }
-  }
-
-  render() {
-    const org = this.props.org ? this.props.org : null
-    const { classes, orgId } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.reports
-        ? this.state.data.reports
-        : null
-    const projects =
-      this.state && this.state.data && this.state.data.projects
-        ? this.state.data.projects
-        : null
-    let projectId = this.props.projectId ? this.props.projectId : null
-    let projectFilter = null
-
-    const addAgentButton = (
-      <ConsoleButtonWrapper
-        disabled={!this.props.orgPermissions?.checkupReportConfigure}
-        variant="contained"
-        color="primary"
-        onClick={() => this.props.history.push('/' + org + '/checkup-config')}
-        title={
-          this.props.orgPermissions?.checkupReportConfigure
-            ? 'Start generating new reports for your Postgres servers'
-            : messages.noPermission
-        }
-      >
-        Generate report
-      </ConsoleButtonWrapper>
-    )
-
-    const pageTitle = (
-      <ConsolePageTitle title="Checkup reports" actions={[addAgentButton]} />
-    )
-
-    if (projects && projects.data && data) {
-      projectFilter = (
-        <div>
-          <TextField
-            value={data.projectId}
-            onChange={(event) => this.handleChangeProject(event)}
-            select
-            label="Project"
-            inputProps={{
-              name: 'project',
-              id: 'project-filter',
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel,
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper,
-            }}
-            variant="outlined"
-            className={classes.filterSelect}
-          >
-            <MenuItem value={0}>All</MenuItem>
-
-            {projects.data.map(
-              (p: {
-                id: number
-                name: string
-                label: string
-                project_label_or_name: string
-              }) => {
-                return (
-                  <MenuItem value={p.id} key={p.id}>
-                    {p.project_label_or_name || p.label || p.name}
-                  </MenuItem>
-                )
-              },
-            )}
-          </TextField>
-        </div>
-      )
-    }
-
-    let breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[{ name: 'Reports' }]}
-      />
-    )
-
-    if (this.state && this.state.data && this.state.data.reports?.error) {
-      return (
-        <div>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <ErrorWrapper />
-        </div>
-      )
-    }
-
-    if (
-      !data ||
-      (data && data.isProcessing) ||
-      data.orgId !== orgId ||
-      data.projectId !== (projectId ? projectId : 0)
-    ) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          {pageTitle}
-
-          <PageSpinner />
-        </div>
-      )
-    }
-
-    const emptyListTitle = projectId
-      ? 'There are no uploaded checkup reports in this project yet'
-      : 'There are no uploaded checkup reports'
-
-    let reports = (
-      <StubContainer className={classes.stubContainer}>
-        <ProductCardWrapper
-          inline
-          title={emptyListTitle}
-          actions={[
-            {
-              id: 'addAgentButton',
-              content: addAgentButton,
-            },
-          ]}
-          icon={icons.checkupLogo}
-        >
-          <p>
-            Automated routine checkup for your PostgreSQL databases. Configure
-            Checkup agent to start collecting reports (
-            <GatewayLink
-              href="https://postgres.ai/docs/checkup"
-              target="_blank"
-            >
-              Learn more
-            </GatewayLink>
-            ).
-          </p>
-        </ProductCardWrapper>
-      </StubContainer>
-    )
-
-    if (data && data.data && data.data.length > 0) {
-      reports = (
-        <div>
-          {this.props.orgPermissions?.checkupReportDelete ? (
-            <div className={classes.tableHead}>
-              {data.isDeleting ? (
-                <span>Processing...</span>
-              ) : (
-                <div>
-                  {Object.keys(this.state.selectedRows).length > 0 ? (
-                    <span>
-                      Selected: {Object.keys(this.state.selectedRows).length}{' '}
-                      rows
-                    </span>
-                  ) : (
-                    'Select table rows to process them'
-                  )}
-                </div>
-              )}
-              <div className={classes.tableHeadActions}>
-                <Button
-                  variant="contained"
-                  color="primary"
-                  disabled={
-                    Object.keys(this.state.selectedRows).length === 0 ||
-                    data.isDeleting
-                  }
-                  onClick={() => this.deleteReports()}
-                >
-                  Delete
-                  {data && data.isDeleting ? (
-                    <span>
-                      &nbsp;
-                      <Spinner size="sm" />
-                    </span>
-                  ) : (
-                    ''
-                  )}
-                </Button>
-              </div>
-            </div>
-          ) : null}
-          <HorizontalScrollContainer>
-            <Table className={classes.table}>
-              <TableHead>
-                <TableRow className={classes.row}>
-                  {this.props.orgPermissions?.checkupReportDelete ? (
-                    <TableCell className={classes.checkboxTableCell}>
-                      <Checkbox
-                        indeterminate={
-                          Object.keys(this.state.selectedRows).length > 0
-                        }
-                        checked={
-                          Object.keys(this.state.selectedRows).length ===
-                          data.data.length
-                        }
-                        onChange={(event) =>
-                          this.onSelectAllClick(event, data.data)
-                        }
-                        onClick={(event) => this.onCheckBoxClick(event)}
-                      />
-                    </TableCell>
-                  ) : null}
-                  <TableCell>Report #</TableCell>
-                  <TableCell>Project</TableCell>
-                  <TableCell>Created</TableCell>
-                  <TableCell>Epoch</TableCell>
-                </TableRow>
-              </TableHead>
-              <TableBody>
-                {data.data.map(
-                  (r: {
-                    id: number
-                    project_id: string
-                    created_formatted: string
-                    project_name: string
-                    epoch: string
-                    project_label_or_name: string
-                    project_label: string
-                  }) => {
-                    return (
-                      <TableRow
-                        hover
-                        className={classes.row}
-                        key={r.id}
-                        onClick={(event) =>
-                          this.handleClick(event, r.id, r.project_id)
-                        }
-                        style={{ cursor: 'pointer' }}
-                      >
-                        {this.props.orgPermissions?.checkupReportDelete ? (
-                          <TableCell className={classes.checkboxTableCell}>
-                            <Checkbox
-                              checked={
-                                !!(
-                                  this.state
-                                    .selectedRows as ReportsState['selectedRows']
-                                )[r.id]
-                              }
-                              onChange={(event) =>
-                                this.onSelectRow(event, r.id)
-                              }
-                              onClick={(event) => this.onCheckBoxClick(event)}
-                            />
-                          </TableCell>
-                        ) : null}
-                        <TableCell className={classes.cell}>
-                          <NavLink
-                            to={
-                              this.getReportLink(r.id, r.project_id) as string
-                            }
-                          >
-                            {r.id}
-                          </NavLink>
-                        </TableCell>
-                        <TableCell className={classes.cell}>
-                          {r.project_label_or_name ||
-                            r.project_label ||
-                            r.project_name}
-                        </TableCell>
-                        <TableCell className={classes.cell}>
-                          {r.created_formatted}
-                        </TableCell>
-                        <TableCell className={classes.cell}>
-                          {r.epoch}
-                        </TableCell>
-                      </TableRow>
-                    )
-                  },
-                )}
-              </TableBody>
-            </Table>
-          </HorizontalScrollContainer>
-        </div>
-      )
-    }
-
-    return (
-      <div className={classes.root}>
-        {breadcrumbs}
-
-        {pageTitle}
-
-        {projectFilter}
-
-        {reports}
-      </div>
-    )
-  }
-}
-
-export default Reports
diff --git a/ui/packages/platform/src/components/Reports/ReportsWrapper.tsx b/ui/packages/platform/src/components/Reports/ReportsWrapper.tsx
deleted file mode 100644
index 84af32ec..00000000
--- a/ui/packages/platform/src/components/Reports/ReportsWrapper.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { RouteComponentProps } from 'react-router'
-import Reports from 'components/Reports/Reports'
-import { OrgPermissions } from 'components/types'
-
-export interface ReportsProps extends RouteComponentProps {
-  projectId: string | undefined | number
-  project: string | undefined
-  orgId: number
-  org: string | number
-  orgPermissions: OrgPermissions
-}
-
-export const ReportsWrapper = (props: ReportsProps) => {
-  const useStyles = makeStyles(
-    {
-      root: {
-        ...(styles.root as Object),
-        paddingBottom: '20px',
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      stubContainer: {
-        marginTop: '10px',
-      },
-      filterSelect: {
-        ...styles.inputField,
-        width: 150,
-      },
-      cell: {
-        '& > a': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-        '& > a:hover': {
-          color: 'black',
-          textDecoration: 'none',
-        },
-      },
-      tableHead: {
-        ...(styles.tableHead as Object),
-      },
-      tableHeadActions: {
-        ...(styles.tableHeadActions as Object),
-      },
-      checkboxTableCell: {
-        width: '30px',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Reports {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/ShareUrlDialog/ShareUrlDialog.tsx b/ui/packages/platform/src/components/ShareUrlDialog/ShareUrlDialog.tsx
deleted file mode 100644
index 109de175..00000000
--- a/ui/packages/platform/src/components/ShareUrlDialog/ShareUrlDialog.tsx
+++ /dev/null
@@ -1,330 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { Component } from 'react'
-import {
-  IconButton,
-  TextField,
-  Dialog,
-  Typography,
-  Radio,
-  RadioGroup,
-  FormControlLabel,
-  Button,
-  makeStyles,
-} from '@material-ui/core'
-import MuiDialogTitle from '@material-ui/core/DialogTitle'
-import MuiDialogContent from '@material-ui/core/DialogContent'
-import MuiDialogActions from '@material-ui/core/DialogActions'
-
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import Store from '../../stores/store'
-import Actions from '../../actions/actions'
-import Urls from '../../utils/urls'
-
-interface ShareUrlDialogProps {
-  classes: ClassesType
-}
-
-interface DialogTitleProps {
-  id: string
-  children: React.ReactNode
-  onClose: () => void
-}
-
-interface ShareUrlDialogState {
-  data: {
-    shareUrl: {
-      shared: string | null
-      uuid: string | null
-      remark: string | null
-      isProcessed: boolean
-      open: boolean
-      isRemoving: boolean
-      isAdding: boolean
-      data: {
-        uuid: string | null
-      } | null
-    }
-  } | null
-  shared: string | null
-  uuid: string | null
-}
-
-const DialogTitle = (props: DialogTitleProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        margin: 0,
-        padding: theme.spacing(1),
-      },
-      dialogTitle: {
-        fontSize: 16,
-        lineHeight: '19px',
-        fontWeight: 600,
-      },
-      closeButton: {
-        position: 'absolute',
-        right: theme.spacing(1),
-        top: theme.spacing(1),
-        color: theme.palette.grey[500],
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-  const { children, onClose, ...other } = props
-  return (
-    <MuiDialogTitle disableTypography className={classes.root} {...other}>
-      <Typography className={classes.dialogTitle}>{children}</Typography>
-      {onClose ? (
-        <IconButton
-          aria-label="close"
-          className={classes.closeButton}
-          onClick={onClose}
-        >
-          {icons.closeIcon}
-        </IconButton>
-      ) : null}
-    </MuiDialogTitle>
-  )
-}
-
-const DialogContent = (props: { children: React.ReactNode }) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      dialogContent: {
-        paddingTop: 10,
-        padding: theme.spacing(2),
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-  return (
-    <MuiDialogContent dividers className={classes.dialogContent}>
-      {props.children}
-    </MuiDialogContent>
-  )
-}
-
-const DialogActions = (props: { children: React.ReactNode }) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        margin: 0,
-        padding: theme.spacing(1),
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-  return (
-    <MuiDialogActions className={classes.root}>
-      {props.children}
-    </MuiDialogActions>
-  )
-}
-
-class ShareUrlDialog extends Component<
-  ShareUrlDialogProps,
-  ShareUrlDialogState
-> {
-  state: ShareUrlDialogState = {
-    shared: null,
-    uuid: null,
-    data: {
-      shareUrl: {
-        shared: null,
-        uuid: null,
-        remark: null,
-        isProcessed: false,
-        isRemoving: false,
-        isAdding: false,
-        open: false,
-        data: null,
-      },
-    },
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    // const { url_type, url_id } = this.props;
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      let stateData = { data: this.data, shared: '', uuid: '' }
-
-      if (this.data.shareUrl.isAdding) {
-        return
-      }
-
-      if (this.data.shareUrl.data && this.data.shareUrl.data.uuid) {
-        stateData.shared = 'shared'
-        stateData.uuid = this.data.shareUrl.data.uuid
-      } else {
-        stateData.shared = 'default'
-        stateData.uuid = this.data.shareUrl.uuid
-      }
-
-      that.setState(stateData)
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  copyUrl = () => {
-    let copyText = document.getElementById('sharedUrl') as HTMLInputElement
-
-    if (copyText) {
-      copyText.select()
-      copyText.setSelectionRange(0, 99999)
-      document.execCommand('copy')
-    }
-  }
-
-  closeShareDialog = (close: boolean, save: boolean) => {
-    Actions.closeShareUrlDialog(close, save, this.state.shared === 'shared')
-    if (close) {
-      this.setState({ data: null, shared: null })
-    }
-  }
-
-  render() {
-    const { classes } = this.props
-    const shareUrl =
-      this.state && this.state.data && this.state.data.shareUrl
-        ? this.state.data.shareUrl
-        : null
-
-    if (
-      !shareUrl ||
-      (shareUrl && !shareUrl.isProcessed) ||
-      (shareUrl && !shareUrl.open && !shareUrl.data) ||
-      this.state.shared === null
-    ) {
-      return null
-    }
-
-    const urlField = (
-      <div>
-        <TextField
-          id="token"
-          className={classes.textField}
-          margin="normal"
-          value={Urls.linkShared(this.state.uuid)}
-          variant="outlined"
-          style={{ width: 500 }}
-          InputProps={{
-            readOnly: true,
-            id: 'sharedUrl',
-          }}
-          InputLabelProps={{
-            shrink: true,
-            style: styles.inputFieldLabel,
-          }}
-          FormHelperTextProps={{
-            style: styles.inputFieldHelper,
-          }}
-        />
-
-        <IconButton
-          className={classes.copyButton}
-          aria-label="Copy"
-          onClick={this.copyUrl}
-        >
-          {icons.copyIcon}
-        </IconButton>
-      </div>
-    )
-
-    return (
-      <Dialog
-        onClose={() => this.closeShareDialog(true, false)}
-        aria-labelledby="customized-dialog-title"
-        open={shareUrl.open}
-        className={classes.dialog}
-      >
-        <DialogTitle
-          id="customized-dialog-title"
-          onClose={() => this.closeShareDialog(true, false)}
-        >
-          Share
-        </DialogTitle>
-        <DialogContent>
-          <RadioGroup
-            aria-label="shareUrl"
-            name="shareUrl"
-            value={this.state.shared}
-            onChange={(event) => {
-              this.setState({ shared: event.target.value })
-            }}
-            className={classes.radioLabel}
-          >
-            <FormControlLabel
-              value="default"
-              control={<Radio />}
-              label="Only members of the organization can view"
-            />
-
-            <FormControlLabel
-              value="shared"
-              control={<Radio />}
-              label="Anyone with a special link and members of thr organization can view"
-            />
-          </RadioGroup>
-          {shareUrl.remark && (
-            <Typography className={classes.remark}>
-              <span className={classes.remarkIcon}>{icons.warningIcon}</span>
-              {shareUrl.remark}
-            </Typography>
-          )}
-          {this.state.shared === 'shared' && (
-            <div className={classes.urlContainer}>{urlField}</div>
-          )}
-        </DialogContent>
-        <DialogActions>
-          <Button
-            autoFocus
-            variant="contained"
-            disabled={shareUrl.isRemoving || shareUrl.isAdding}
-            onClick={() => this.closeShareDialog(false, true)}
-            color="primary"
-          >
-            Save changes
-            {(shareUrl.isRemoving || shareUrl.isAdding) && (
-              <span>
-                &nbsp;
-                <Spinner size="sm" />
-              </span>
-            )}
-          </Button>
-          <Button
-            onClick={() => this.closeShareDialog(true, false)}
-            variant="outlined"
-            color="secondary"
-          >
-            Close
-          </Button>
-        </DialogActions>
-      </Dialog>
-    )
-  }
-}
-
-export default ShareUrlDialog
diff --git a/ui/packages/platform/src/components/ShareUrlDialog/ShareUrlDialogWrapper.tsx b/ui/packages/platform/src/components/ShareUrlDialog/ShareUrlDialogWrapper.tsx
deleted file mode 100644
index a1dbaefc..00000000
--- a/ui/packages/platform/src/components/ShareUrlDialog/ShareUrlDialogWrapper.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import ShareUrlDialog from 'components/ShareUrlDialog/ShareUrlDialog'
-
-export const ShareUrlDialogWrapper = () => {
-  const useStyles = makeStyles(
-    () => ({
-      textField: {
-        ...styles.inputField,
-        marginTop: '0px',
-        width: 480,
-      },
-      copyButton: {
-        marginTop: '-3px',
-        fontSize: '20px',
-      },
-      dialog: {},
-      remark: {
-        fontSize: 12,
-        lineHeight: '12px',
-        color: colors.state.warning,
-        paddingLeft: 20,
-      },
-      remarkIcon: {
-        display: 'block',
-        height: '20px',
-        width: '22px',
-        float: 'left',
-        paddingTop: '5px',
-      },
-      urlContainer: {
-        marginTop: 10,
-        paddingLeft: 22,
-      },
-      radioLabel: {
-        fontSize: 12,
-      },
-      dialogContent: {
-        paddingTop: 10,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <ShareUrlDialog classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/SharedUrl/SharedUrl.tsx b/ui/packages/platform/src/components/SharedUrl/SharedUrl.tsx
deleted file mode 100644
index 4352090b..00000000
--- a/ui/packages/platform/src/components/SharedUrl/SharedUrl.tsx
+++ /dev/null
@@ -1,178 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-import { Button } from '@material-ui/core'
-
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { ClassesType, RefluxTypes } from '@postgres.ai/platform/src/components/types'
-
-import { JoeSessionCommandWrapper } from 'pages/JoeSessionCommand/JoeSessionCommandWrapper'
-import Actions from '../../actions/actions'
-import { ErrorWrapper } from 'components/Error/ErrorWrapper'
-import Store from '../../stores/store'
-import settings from '../../utils/settings'
-import { SharedUrlProps } from 'components/SharedUrl/SharedUrlWrapper'
-
-interface SharedUrlWithStylesProps extends SharedUrlProps {
-  classes: ClassesType
-}
-
-interface SharedUrlState {
-  signUpBannerClosed: boolean
-  data: {
-    sharedUrlData: {
-      isProcessing: boolean
-      isProcessed: boolean
-      error: boolean
-      data: {
-        url: {
-          object_type: string | null
-          object_id: number | null
-        }
-        url_data: {
-          joe_session_id: number | null
-        }
-      }
-    } | null
-    userProfile: { data: Object | null } | null
-  } | null
-}
-
-const SIGN_UP_BANNER_PARAM = 'signUpBannerClosed'
-
-class SharedUrl extends Component<SharedUrlWithStylesProps, SharedUrlState> {
-  state = {
-    signUpBannerClosed: localStorage.getItem(SIGN_UP_BANNER_PARAM) === '1',
-    data: {
-      sharedUrlData: {
-        isProcessing: false,
-        isProcessed: false,
-        error: false,
-        data: {
-          url: {
-            object_type: null,
-            object_id: null,
-          },
-          url_data: { joe_session_id: null },
-        },
-      },
-      userProfile: {
-        data: null,
-      },
-    },
-  }
-
-  unsubscribe: Function
-  componentDidMount() {
-    const that = this
-    const uuid = this.props.match.params.url_uuid
-
-     this.unsubscribe = (Store.listen as RefluxTypes["listen"]) (function () {
-      const sharedUrlData =
-        this.data && this.data.sharedUrlData ? this.data.sharedUrlData : null
-
-      that.setState({ data: this.data })
-
-      if (
-        !sharedUrlData?.isProcessing &&
-        !sharedUrlData?.error &&
-        !sharedUrlData?.isProcessed
-      ) {
-        Actions.getSharedUrlData(uuid)
-      }
-    })
-
-    Actions.refresh()
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe()
-  }
-
-  closeBanner = () => {
-    localStorage.setItem(SIGN_UP_BANNER_PARAM, '1')
-    this.setState({ signUpBannerClosed: true })
-  }
-
-  signUp = () => {
-    window.open(settings.signinUrl, '_blank')
-  }
-
-  render() {
-    const { classes } = this.props
-    const data =
-      this.state && this.state.data && this.state.data.sharedUrlData
-        ? this.state.data.sharedUrlData
-        : null
-    const env =
-      this.state && this.state.data ? this.state.data.userProfile : null
-    const showBanner = !this.state.signUpBannerClosed
-
-    if (!data || (data && (data.isProcessing || !data.isProcessed))) {
-      return (
-        <>
-          <PageSpinner />
-        </>
-      )
-    }
-
-    if (data && data.isProcessed && data.error) {
-      return (
-        <>
-          <ErrorWrapper code={404} message={'Not found.'} />
-        </>
-      )
-    }
-
-    let page = null
-    if (data?.data?.url && data.data.url?.object_type === 'command') {
-      const customProps = {
-        commandId: data.data.url.object_id,
-        sessionId: data.data.url_data.joe_session_id,
-      }
-      page = <JoeSessionCommandWrapper {...this.props} {...customProps} />
-    }
-
-    let banner = null
-    if (!env || (env && !env.data)) {
-      banner = (
-        <div className={classes.banner}>
-          Boost your development process with&nbsp;
-          <a target="_blank" href="https://postgres.ai" rel="noreferrer">
-            Postgres.ai Platform
-          </a>
-          &nbsp;
-          <Button
-            onClick={() => this.signUp()}
-            variant="outlined"
-            color="secondary"
-            className={classes.signUpButton}
-          >
-            Sign up
-          </Button>
-          <span
-            className={classes.bannerCloseButton}
-            onClick={this.closeBanner}
-          >
-            {icons.bannerCloseIcon}
-          </span>
-        </div>
-      )
-    }
-
-    return (
-      <>
-        {page}
-        {showBanner && banner}
-      </>
-    )
-  }
-}
-
-export default SharedUrl
diff --git a/ui/packages/platform/src/components/SharedUrl/SharedUrlWrapper.tsx b/ui/packages/platform/src/components/SharedUrl/SharedUrlWrapper.tsx
deleted file mode 100644
index 7028d241..00000000
--- a/ui/packages/platform/src/components/SharedUrl/SharedUrlWrapper.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import SharedUrl from 'components/SharedUrl/SharedUrl'
-import { RouteComponentProps } from 'react-router'
-
-interface MatchProps {
-  url_uuid: string
-}
-export interface SharedUrlProps extends RouteComponentProps<MatchProps> {}
-
-export const SharedUrlWrapper = (props: SharedUrlProps) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      container: {
-        display: 'flex',
-        flexWrap: 'wrap',
-      },
-      textField: {
-        marginLeft: theme.spacing(1),
-        marginRight: theme.spacing(1),
-        width: '80%',
-      },
-      dense: {
-        marginTop: 16,
-      },
-      menu: {
-        width: 200,
-      },
-      updateButtonContainer: {
-        marginTop: 20,
-        textAlign: 'right',
-      },
-      errorMessage: {
-        color: 'red',
-      },
-      orgsHeader: {
-        position: 'relative',
-      },
-      newOrgBtn: {
-        position: 'absolute',
-        top: 0,
-        right: 10,
-      },
-      banner: {
-        height: 50,
-        position: 'absolute',
-        left: 0,
-        bottom: 0,
-        right: 0,
-        backgroundColor: 'rgba(1, 58, 68, 0.8)',
-        color: 'white',
-        zIndex: 100,
-        fontSize: 18,
-        lineHeight: '50px',
-        paddingLeft: 20,
-        '& a': {
-          color: 'white',
-          fontWeight: '600',
-        },
-        '& svg': {
-          position: 'absolute',
-          right: 18,
-          top: 18,
-          cursor: 'pointer',
-        },
-      },
-      signUpButton: {
-        backgroundColor: 'white',
-        fontWeight: 600,
-        marginLeft: 10,
-        '&:hover': {
-          backgroundColor: '#ecf6f7',
-        },
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <SharedUrl {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/SideNav/index.tsx b/ui/packages/platform/src/components/SideNav/index.tsx
deleted file mode 100644
index bf17ac4e..00000000
--- a/ui/packages/platform/src/components/SideNav/index.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { NavLink } from 'react-router-dom'
-import { ListItem, List } from '@material-ui/core'
-import { makeStyles } from '@material-ui/styles'
-
-import { colors } from '@postgres.ai/shared/styles/colors'
-
-import { ROUTES } from 'config/routes'
-
-const useStyles = makeStyles(
-  {
-    listItem: {
-      padding: 0,
-    },
-    navLink: {
-      textDecoration: 'none',
-      color: '#000000',
-      fontWeight: 'bold',
-      fontSize: '14px',
-      width: '100%',
-      padding: '12px 14px',
-    },
-    activeNavLink: {
-      backgroundColor: colors.consoleStroke,
-    },
-  },
-  { index: 1 },
-)
-
-export const SideNav = () => {
-  const classes = useStyles()
-
-  return (
-    <List component="nav">
-      <ListItem button className={classes.listItem}>
-        <NavLink
-          className={classes.navLink}
-          activeClassName={classes.activeNavLink}
-          to={ROUTES.ROOT.path}
-          exact
-        >
-          Organizations
-        </NavLink>
-      </ListItem>
-      <ListItem button className={classes.listItem}>
-        <NavLink
-          className={classes.navLink}
-          activeClassName={classes.activeNavLink}
-          to={ROUTES.PROFILE.path}
-        >
-          Profile
-        </NavLink>
-      </ListItem>
-    </List>
-  )
-}
diff --git a/ui/packages/platform/src/components/StripeForm/index.tsx b/ui/packages/platform/src/components/StripeForm/index.tsx
deleted file mode 100644
index 5c5b31c5..00000000
--- a/ui/packages/platform/src/components/StripeForm/index.tsx
+++ /dev/null
@@ -1,531 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-import { Button, Paper, Tooltip, makeStyles } from '@material-ui/core'
-import { Box } from '@mui/material'
-import { formatDistanceToNowStrict } from 'date-fns'
-import { ReducerAction, useCallback, useEffect, useReducer } from 'react'
-
-import { Link } from '@postgres.ai/shared/components/Link2'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { stripeStyles } from 'components/StripeForm/stripeStyles'
-import { ROUTES } from 'config/routes'
-
-import { getPaymentMethods } from 'api/billing/getPaymentMethods'
-import { getSubscription } from 'api/billing/getSubscription'
-import { startBillingSession } from 'api/billing/startBillingSession'
-
-import format from '../../utils/format'
-
-interface BillingSubscription {
-  status: string
-  created_at: number
-  description: string
-  plan_description: string
-  recognized_dblab_instance_id: number
-  selfassigned_instance_id: string
-  subscription_id: string
-  telemetry_last_reported: number
-  telemetry_usage_total_hours_last_3_months: string
-}
-
-interface HourEntry {
-  [key: string]: number
-}
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const useStyles = makeStyles(
-  (theme) => ({
-    paperContainer: {
-      display: 'flex',
-      flexDirection: 'row',
-      alignItems: 'flex-start',
-      gap: 20,
-      width: '100%',
-      height: '100%',
-
-      [theme.breakpoints.down('sm')]: {
-        flexDirection: 'column',
-      },
-    },
-    cardContainer: {
-      padding: 20,
-      display: 'inline-block',
-      borderWidth: 1,
-      borderColor: colors.consoleStroke,
-      borderStyle: 'solid',
-      flex: '1 1 0',
-
-      [theme.breakpoints.down('sm')]: {
-        width: '100%',
-        flex: 'auto',
-      },
-    },
-    subscriptionContainer: {
-      display: 'flex',
-      flexDirection: 'column',
-      flex: '1 1 0',
-      gap: 20,
-
-      [theme.breakpoints.down('sm')]: {
-        width: '100%',
-        flex: 'auto',
-      },
-    },
-    flexColumnContainer: {
-      display: 'flex',
-      flexDirection: 'column',
-      flex: '1.75 1 0',
-      gap: 20,
-
-      [theme.breakpoints.down('sm')]: {
-        width: '100%',
-        flex: 'auto',
-      },
-    },
-    cardContainerTitle: {
-      fontSize: 14,
-      fontWeight: 'bold',
-      margin: 0,
-    },
-    label: {
-      fontSize: 14,
-    },
-    saveButton: {
-      marginTop: 15,
-      display: 'flex',
-    },
-    error: {
-      color: '#E42548',
-      fontSize: 12,
-      marginTop: '-10px',
-    },
-    checkboxRoot: {
-      fontSize: 14,
-      marginTop: 10,
-    },
-    spinner: {
-      marginLeft: '8px',
-    },
-    emptyMethod: {
-      fontSize: 13,
-      flex: '1 1 0',
-    },
-    button: {
-      '&:hover': {
-        color: '#0F879D',
-      },
-    },
-    columnContainer: {
-      display: 'flex',
-      alignItems: 'center',
-      flexDirection: 'row',
-      padding: '10px 0',
-      borderBottom: '1px solid rgba(224, 224, 224, 1)',
-
-      '& p:first-child': {
-        flex: '1 1 0',
-        fontWeight: '500',
-      },
-
-      '& p': {
-        flex: '2 1 0',
-        margin: '0',
-        fontSize: 13,
-      },
-
-      '&:last-child': {
-        borderBottom: 'none',
-      },
-    },
-    toolTip: {
-      fontSize: '10px !important',
-      maxWidth: '100%',
-    },
-    timeLabel: {
-      lineHeight: '16px',
-      fontSize: 13,
-      cursor: 'pointer',
-      flex: '2 1 0',
-    },
-    card: {
-      display: 'flex',
-      flexDirection: 'row',
-      alignItems: 'center',
-      gap: 20,
-      position: 'relative',
-      justifyContent: 'space-between',
-      marginTop: 20,
-      minHeight: 45,
-
-      '& img': {
-        objectFit: 'contain',
-      },
-    },
-  }),
-  { index: 1 },
-)
-
-function StripeForm(props: {
-  alias: string
-  mode: string
-  token: string | null
-  orgId: number
-  disabled: boolean
-}) {
-  const classes = useStyles()
-
-  const initialState = {
-    isLoading: false,
-    isFetching: false,
-    cards: [] as { id: string; card: { exp_year: string; exp_month: string; brand: string; last4: string } }[],
-    billingInfo: [] as BillingSubscription[],
-  }
-
-  const reducer = (
-    state: typeof initialState,
-    // @ts-ignore
-    action: ReducerAction<unknown, void>,
-  ) => {
-    switch (action.type) {
-      case 'setIsLoading':
-        return { ...state, isLoading: action.isLoading }
-      case 'setIsFetching':
-        return { ...state, isFetching: action.isFetching }
-      case 'setBillingInfo':
-        return { ...state, billingInfo: action.billingInfo, isFetching: false }
-      case 'setCards':
-        const updatedCards = state.cards.concat(action.cards)
-        const uniqueCards = updatedCards.filter(
-          (card: { id: string }, index: number) =>
-            updatedCards.findIndex((c: { id: string }) => c.id === card.id) ===
-            index,
-        )
-
-        return {
-          ...state,
-          cards: uniqueCards,
-          isLoading: false,
-        }
-      default:
-        throw new Error()
-    }
-  }
-
-  const [state, dispatch] = useReducer(reducer, initialState)
-
-  const handleSubmit = async (event: { preventDefault: () => void }) => {
-    event.preventDefault()
-
-    dispatch({
-      type: 'setIsLoading',
-      isLoading: true,
-    })
-
-    startBillingSession(props.orgId, window.location.href).then((res) => {
-      dispatch({
-        type: 'setIsLoading',
-        isLoading: false,
-      })
-
-      if (res.response) {
-        window.open(res.response.details.content.url, '_blank')
-      }
-    })
-  }
-
-  const fetchPaymentMethods = useCallback(() => {
-    dispatch({
-      type: 'setIsFetching',
-      isFetching: true,
-    })
-
-    getPaymentMethods(props.orgId).then((res) => {
-      dispatch({
-        type: 'setCards',
-        cards: res.response.details.content.data,
-      })
-
-      getSubscription(props.orgId).then((res) => {
-        dispatch({
-          type: 'setBillingInfo',
-          billingInfo: res.response.summary,
-        })
-      })
-    })
-  }, [props.orgId])
-
-  useEffect(() => {
-    fetchPaymentMethods()
-
-    const handleVisibilityChange = () => {
-      if (document.visibilityState === 'visible') {
-        fetchPaymentMethods()
-      }
-    }
-
-    document.addEventListener('visibilitychange', handleVisibilityChange, false)
-
-    return () => {
-      document.removeEventListener('visibilitychange', handleVisibilityChange)
-    }
-  }, [props.orgId, fetchPaymentMethods])
-
-  if (state.isFetching) {
-    return (
-      <div className={classes.paperContainer}>
-        <Spinner />
-      </div>
-    )
-  }
-
-  const BillingDetail = ({
-    label,
-    value,
-    isDateValue,
-    isLink,
-    instanceId,
-  }: {
-    label: string
-    value: string | number
-    isDateValue?: boolean
-    isLink?: boolean
-    instanceId?: string | number
-  }) => (
-    <Box className={classes.columnContainer}>
-      <p className={classes.emptyMethod}>{label}</p>
-      {isDateValue && value ? (
-        <span className={classes.timeLabel}>
-          <Tooltip
-            title={format.formatTimestampUtc(value) ?? ''}
-            classes={{ tooltip: classes.toolTip }}
-          >
-            <span>
-              {formatDistanceToNowStrict(new Date(value), {
-                addSuffix: true,
-              })}
-            </span>
-          </Tooltip>
-        </span>
-      ) : isLink && value ? (
-        <p>
-          <Link
-            to={ROUTES.ORG.INSTANCES.INSTANCE.createPath({
-              org: props.alias,
-              instanceId: String(instanceId),
-            })}
-            className={classes.emptyMethod}
-          >
-            {instanceId}
-          </Link>
-          &nbsp;
-          {`(self-assigned: ${value})` || '---'}
-        </p>
-      ) : (
-        <p
-          className={classes.emptyMethod}
-          style={
-            label === 'Subscription status'
-              ? { textTransform: 'capitalize' }
-              : {}
-          }
-        >
-          {value || '---'}
-        </p>
-      )}
-    </Box>
-  )
-
-  const formatHoursUsed = (hours: HourEntry[] | string) => {
-    if (typeof hours === 'string' || !hours) {
-      return 'N/A'
-    }
-
-    const formattedHours = hours.map((entry: HourEntry) => {
-      const key = Object.keys(entry)[0]
-      const value = entry[key]
-      return `${key}: ${value}`
-    })
-
-    return formattedHours.join('\n')
-  }
-
-  return (
-    <div>
-      {stripeStyles}
-      <div className={classes.paperContainer}>
-        <Box className={classes.flexColumnContainer}>
-          <p className={classes?.cardContainerTitle}>Subscriptions</p>
-          {state.billingInfo.length > 0 ? (
-            state.billingInfo.map(
-              (billing: BillingSubscription, index: number) => (
-                <Paper className={classes?.cardContainer} key={index}>
-                  <BillingDetail
-                    label="Plan"
-                    value={billing?.plan_description}
-                  />
-                  <BillingDetail
-                    label="Subscription status"
-                    value={billing.status}
-                  />
-                  <BillingDetail
-                    label="Subscription ID"
-                    value={billing?.subscription_id}
-                  />
-                  <BillingDetail
-                    isLink={!!billing?.recognized_dblab_instance_id}
-                    label="Instance ID"
-                    instanceId={billing?.recognized_dblab_instance_id || 'N/A'}
-                    value={billing?.selfassigned_instance_id}
-                  />
-                  <BillingDetail
-                    isDateValue
-                    label="Registered"
-                    value={billing?.created_at}
-                  />
-                  <BillingDetail
-                    isDateValue
-                    label="Telemetry last reported"
-                    value={billing?.telemetry_last_reported}
-                  />
-                  <BillingDetail
-                    label="Description"
-                    value={billing?.description}
-                  />
-                  <BillingDetail
-                    label="Hours billed (current & last 2 months)"
-                    value={formatHoursUsed(
-                      billing?.telemetry_usage_total_hours_last_3_months,
-                    )}
-                  />
-                </Paper>
-              ),
-            )
-          ) : (
-            <Paper className={classes?.cardContainer}>
-              <BillingDetail label="Plan" value={'---'} />
-              <BillingDetail label="Subscription status" value={'---'} />
-              <BillingDetail label="Subscription ID" value={'---'} />
-              <BillingDetail label="Instance ID" value={'---'} />
-              <BillingDetail label="Registered" value={'---'} />
-              <BillingDetail label="Telemetry last reported" value={'---'} />
-              <BillingDetail label="Description" value={'---'} />
-              <BillingDetail
-                label="Hours billed (current & last 2 months)"
-                value={'---'}
-              />
-            </Paper>
-          )}
-        </Box>
-        <Box className={classes.subscriptionContainer}>
-          <p className={classes?.cardContainerTitle}>Payments</p>
-          <Paper className={classes?.cardContainer}>
-            <Box
-              sx={{
-                display: 'flex',
-                justifyContent: 'flex-start',
-                alignItems: 'center',
-                gap: '1rem',
-              }}
-            >
-              <Button
-                variant="contained"
-                color="primary"
-                disabled={props.disabled || state.isLoading}
-                onClick={handleSubmit}
-              >
-                Edit payment methods
-                {state.isLoading && (
-                  <Spinner size="sm" className={classes.spinner} />
-                )}
-              </Button>
-              {state.cards.length > 0 && (
-                <Button
-                  variant="outlined"
-                  color="secondary"
-                  className={classes.button}
-                  disabled={props.disabled || state.isLoading}
-                  onClick={handleSubmit}
-                >
-                  Invoices
-                  {state.isLoading && (
-                    <Spinner size="sm" className={classes.spinner} />
-                  )}
-                </Button>
-              )}
-            </Box>
-            {state.cards.length === 0 && !state.isFetching ? (
-              <p className={classes.emptyMethod}>No payment methods available</p>
-            ) : (
-              <>
-                {state.cards.map(
-                  (
-                    card: {
-                      id: string
-                      card: {
-                        exp_year: string
-                        exp_month: string
-                        brand: string
-                        last4: string
-                      }
-                    },
-                    index: number,
-                  ) => (
-                    <Box className={classes.card} key={index}>
-                      <Box
-                        sx={{
-                          display: 'flex',
-                          justifyContent: 'center',
-                          alignItems: 'center',
-                          gap: '20px',
-
-                          '& p': {
-                            margin: '10px 0',
-                          },
-                        }}
-                      >
-                        <img
-                          src={`/images/paymentMethods/${card.card.brand}.png`}
-                          alt={card.card.brand}
-                          width="50px"
-                          height="35px"
-                        />
-                        <Box
-                          sx={{
-                            display: 'flex',
-                            justifyContent: 'space-between',
-                            flexDirection: 'column',
-                            alignItems: 'flex-start',
-                            gap: '4px',
-                            width: '100px',
-
-                            '& > p': {
-                              margin: 0,
-                            },
-                          }}
-                        >
-                          <p className={classes.emptyMethod}>
-                            **** {card.card.last4}
-                          </p>
-                          <p className={classes.emptyMethod}>
-                            Expires {card.card.exp_month}/{card.card.exp_year}
-                          </p>
-                        </Box>
-                      </Box>
-                    </Box>
-                  ),
-                )}
-              </>
-            )}
-          </Paper>
-        </Box>
-      </div>
-    </div>
-  )
-}
-
-export default StripeForm
diff --git a/ui/packages/platform/src/components/StripeForm/stripeStyles.tsx b/ui/packages/platform/src/components/StripeForm/stripeStyles.tsx
deleted file mode 100644
index 7b37c057..00000000
--- a/ui/packages/platform/src/components/StripeForm/stripeStyles.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-export const stripeStyles = (
-  <style>
-    {`
-  label {
-  /*color: #6b7c93;*/
-  font-weight: bold;
-  letter-spacing: 0.025em;
-  font-size: 12px;
-  margin-bottom: 10px;
-  display: block;
-}
-
-button {
-  white-space: nowrap;
-  border: 0;
-  outline: 0;
-  display: inline-block;
-  height: 40px;
-  line-height: 40px;
-  padding: 0 14px;
-  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
-  color: #fff;
-  border-radius: 4px;
-  font-size: 15px;
-  font-weight: 600;
-  text-transform: uppercase;
-  letter-spacing: 0.025em;
-  background-color: #6772e5;
-  text-decoration: none;
-  -webkit-transition: all 150ms ease;
-  transition: all 150ms ease;
-  margin-top: 10px;
-}
-
-button:hover {
-  color: #fff;
-  cursor: pointer;
-  background-color: #7795f8;
-  transform: translateY(-1px);
-  box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
-}
-
-input,
-.StripeElement {
-  display: block;
-  margin: 10px 0 20px 0;
-  padding: 10px 14px;
-  font-size: 1em;
-  font-family: "Source Code Pro", monospace;
-  box-shadow: rgba(50, 50, 93, 0.14902) 0px 1px 3px,
-    rgba(0, 0, 0, 0.0196078) 0px 1px 0px;
-  border: 1px solid rgb(192, 192, 192);
-  outline: 0;
-  border-radius: 4px;
-  background: white;
-}
-
-input::placeholder {
-  color: #aab7c4;
-}
-
-.StripeElement:focus, 
-.StripeElement:hover {
-  border: 1px solid #0F879D;
-  -webkit-transition: all 150ms ease;
-  transition: all 150ms ease;
-}
-
-.StripeElement.IdealBankElement,
-.StripeElement.FpxBankElement,
-.StripeElement.PaymentRequestButton {
-  padding: 0;
-}
-
-.StripeElement.PaymentRequestButton {
-  height: 40px;
-}
-`}
-  </style>
-)
diff --git a/ui/packages/platform/src/components/Warning/Warning.tsx b/ui/packages/platform/src/components/Warning/Warning.tsx
deleted file mode 100644
index f2d312f0..00000000
--- a/ui/packages/platform/src/components/Warning/Warning.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react'
-
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { WarningProps } from 'components/Warning/WarningWrapper'
-import { ClassesType } from '@postgres.ai/platform/src/components/types'
-
-interface WarningWithStylesProps extends WarningProps {
-  classes: ClassesType
-}
-
-class Warning extends Component<WarningWithStylesProps> {
-  render() {
-    const { classes, children, actions, inline } = this.props
-
-    return (
-      <div className={(!inline ? `${classes?.block} ` : '') + classes?.root}>
-        {icons.warningIcon}
-
-        <div className={classes?.container}>{children}</div>
-
-        {actions ? (
-          <span className={classes?.actions}>
-            {actions.map((a, key) => {
-              return (
-                <span key={key} className={classes?.pageTitleActionContainer}>
-                  {a}
-                </span>
-              )
-            })}
-          </span>
-        ) : null}
-      </div>
-    )
-  }
-}
-export default Warning
diff --git a/ui/packages/platform/src/components/Warning/WarningWrapper.tsx b/ui/packages/platform/src/components/Warning/WarningWrapper.tsx
deleted file mode 100644
index d67b3440..00000000
--- a/ui/packages/platform/src/components/Warning/WarningWrapper.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import Warning from 'components/Warning/Warning'
-import { colors } from '@postgres.ai/shared/styles/colors'
-
-export interface WarningProps {
-  children: React.ReactNode
-  inline?: boolean
-  actions?: {
-    id: number
-    content: string
-  }[]
-}
-
-export const WarningWrapper = (props: WarningProps) => {
-  const linkColor = colors.secondary2.main
-
-  const useStyles = makeStyles(
-    {
-      root: {
-        backgroundColor: colors.secondary1.lightLight,
-        color: colors.secondary1.darkDark,
-        fontSize: '14px',
-        paddingTop: '5px',
-        paddingBottom: '5px',
-        paddingLeft: '10px',
-        paddingRight: '10px',
-        display: 'flex',
-        alignItems: 'center',
-        borderRadius: '3px',
-      },
-      block: {
-        marginBottom: '20px',
-      },
-      icon: {
-        padding: '5px',
-      },
-      actions: {
-        '& a': {
-          color: linkColor,
-          '&:visited': {
-            color: linkColor,
-          },
-          '&:hover': {
-            color: linkColor,
-          },
-          '&:active': {
-            color: linkColor,
-          },
-        },
-        marginRight: '15px',
-      },
-      container: {
-        marginLeft: '5px',
-        marginRight: '5px',
-        lineHeight: '24px',
-      },
-    },
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Warning {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/components/types/index.ts b/ui/packages/platform/src/components/types/index.ts
deleted file mode 100644
index 15e66e8e..00000000
--- a/ui/packages/platform/src/components/types/index.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-import { RouteComponentProps } from 'react-router'
-export interface ClassesType {
-  [classes: string]: string
-}
-export interface QueryParamsType {
-  session: string | undefined
-  command: string | undefined
-  author: string | undefined
-  fingerprint: string | undefined
-  project: string | undefined
-  search: string | undefined
-  is_favorite: string | undefined
-}
-
-export type OrgPermissions = { [permission: string]: boolean }
-
-export interface MatchParams {
-  project: string | undefined
-  projectId: string | undefined
-  org: string | undefined
-  mode: string | undefined
-  type: string | undefined
-  fileType: string | undefined
-  reportId: string | undefined
-}
-
-export interface Orgs {
-  [project: string]: {
-    alias: string
-    is_blocked: boolean
-    is_priveleged: boolean
-    new_subscription: boolean
-    is_blocked_on_creation: boolean
-    stripe_payment_method_primary: string
-    stripe_subscription_id: number
-    priveleged_until: Date
-    role: { id: number; permissions: string[] }
-    name: string
-    id: number
-    owner_user_id: number
-    projects: {
-      [project: string]: {
-        alias: string
-        name: string
-        id: number
-        org_id: string
-      }
-    }
-  }
-}
-
-export interface ProjectWrapperProps {
-  classes: ClassesType
-  location: RouteComponentProps['location']
-  match: {
-    params: {
-      org?: string
-      project?: string
-      projectId?: string
-    }
-  }
-  raw?: boolean
-  org: string | number
-  orgId: number
-  userIsOwner: boolean
-  orgPermissions: OrgPermissions
-  auth: {
-    isProcessed: boolean
-    userId: number
-    token: string
-  } | null
-  orgData: {
-    projects: {
-      [project: string]: {
-        id: number
-      }
-    }
-  }
-  env: {
-    data: {
-      orgs?: Orgs
-    }
-  }
-  envData: {
-    orgs?: Orgs
-  }
-}
-
-export interface OrganizationWrapperProps {
-  classes: ClassesType
-  match: {
-    params: {
-      org?: string | undefined
-      projectId?: string | undefined
-      project?: string | undefined
-    }
-  }
-  location: RouteComponentProps['location']
-  env: {
-    data: {
-      orgs?: Orgs
-      info: {
-        first_name: string
-        user_name: string
-        email: string
-        is_tos_confirmed: boolean
-        is_active: boolean
-        id: number | null
-      }
-    }
-  }
-  auth: {
-    isProcessed: boolean
-    userId: number
-    token: string
-  } | null
-  raw?: boolean
-}
-
-export interface OrganizationMenuProps {
-  classes: { [classes: string]: string }
-  location: RouteComponentProps['location']
-  match: {
-    params: {
-      org?: string
-      project?: string
-      projectId?: string
-    }
-  }
-  env: {
-    data: {
-      orgs?: Orgs
-      info: {
-        first_name: string
-        user_name: string
-        email: string
-        is_tos_confirmed: boolean
-        is_active: boolean
-        id: number | null
-      }
-    }
-  }
-}
-
-export interface UserProfile {
-  data: {
-    info: {
-      first_name: string
-      user_name: string
-      email: string
-      is_tos_confirmed: boolean
-      is_active: boolean
-    }
-  }
-  isConfirmProcessing: boolean
-  isConfirmProcessed: boolean
-  isTosAgreementConfirmProcessing: boolean
-}
-
-export interface TabPanelProps {
-  children: React.ReactNode
-  value: number
-  index: number
-}
-
-export interface ProjectProps {
-  error: boolean
-  isProcessing: boolean
-  isProcessed: boolean
-  data: {
-    name: string
-    id: number
-    project_label_or_name: string
-  }[]
-}
-
-export interface TokenRequestProps {
-  isProcessing: boolean
-  isProcessed: boolean
-  data: {
-    name: string
-    is_personal: boolean
-    expires_at: string
-    token: string
-  }
-  errorMessage: string
-  error: boolean | null
-}
-
-export interface FlameGraphPlanType {
-  [plan: string]: string | string[]
-}
-
-export interface RefluxTypes {
-  listen: (callback: Function) => Function
-}
diff --git a/ui/packages/platform/src/config/emoji.ts b/ui/packages/platform/src/config/emoji.ts
deleted file mode 100644
index b092b87a..00000000
--- a/ui/packages/platform/src/config/emoji.ts
+++ /dev/null
@@ -1,909 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const emoji = {
-  '100': '💯',
-  '1234': '🔢',
-  '+1': '👍',
-  '8ball': '🎱',
-  'a': '🅰️',
-  'ab': '🆎',
-  'abc': '🔤',
-  'abcd': '🔡',
-  'accept': '🉑',
-  'aerial_tramway': '🚡',
-  'airplane': '✈️',
-  'alarm_clock': '⏰',
-  'alien': '👽',
-  'ambulance': '🚑',
-  'anchor': '⚓️',
-  'angel': '👼',
-  'anger': '💢',
-  'angry': '😠',
-  'anguished': '😧',
-  'ant': '🐜',
-  'apple': '🍎',
-  'aquarius': '♒️',
-  'aries': '♈️',
-  'arrow_backward': '◀️',
-  'arrow_double_down': '⏬',
-  'arrow_double_up': '⏫',
-  'arrow_down': '⬇️',
-  'arrow_down_small': '🔽',
-  'arrow_forward': '▶️',
-  'arrow_heading_down': '⤵️',
-  'arrow_heading_up': '⤴️',
-  'arrow_left': '⬅️',
-  'arrow_lower_left': '↙️',
-  'arrow_lower_right': '↘️',
-  'arrow_right': '➡️',
-  'arrow_right_hook': '↪️',
-  'arrow_up': '⬆️',
-  'arrow_up_down': '↕️',
-  'arrow_up_small': '🔼',
-  'arrow_upper_left': '↖️',
-  'arrow_upper_right': '↗️',
-  'arrows_clockwise': '🔃',
-  'arrows_counterclockwise': '🔄',
-  'art': '🎨',
-  'articulated_lorry': '🚛',
-  'astonished': '😲',
-  'athletic_shoe': '👟',
-  'atm': '🏧',
-  'b': '🅱️',
-  'baby': '👶',
-  'baby_bottle': '🍼',
-  'baby_chick': '🐤',
-  'baby_symbol': '🚼',
-  'back': '🔙',
-  'baggage_claim': '🛄',
-  'balloon': '🎈',
-  'ballot_box_with_check': '☑️',
-  'bamboo': '🎍',
-  'banana': '🍌',
-  'bangbang': '‼️',
-  'bank': '🏦',
-  'bar_chart': '📊',
-  'barber': '💈',
-  'baseball': '⚾️',
-  'basketball': '🏀',
-  'bath': '🛀',
-  'bathtub': '🛁',
-  'battery': '🔋',
-  'bear': '🐻',
-  'bee': '🐝',
-  'beer': '🍺',
-  'beers': '🍻',
-  'beetle': '🐞',
-  'beginner': '🔰',
-  'bell': '🔔',
-  'bento': '🍱',
-  'bicyclist': '🚴',
-  'bike': '🚲',
-  'bikini': '👙',
-  'bird': '🐦',
-  'birthday': '🎂',
-  'black_circle': '⚫️',
-  'black_joker': '🃏',
-  'black_large_square': '⬛️',
-  'black_medium_small_square': '◾️',
-  'black_medium_square': '◼️',
-  'black_nib': '✒️',
-  'black_small_square': '▪️',
-  'black_square_button': '🔲',
-  'blossom': '🌼',
-  'blowfish': '🐡',
-  'blue_book': '📘',
-  'blue_car': '🚙',
-  'blue_heart': '💙',
-  'blush': '😊',
-  'boar': '🐗',
-  'boat': '⛵️',
-  'bomb': '💣',
-  'book': '📖',
-  'bookmark': '🔖',
-  'bookmark_tabs': '📑',
-  'books': '📚',
-  'boom': '💥',
-  'boot': '👢',
-  'bouquet': '💐',
-  'bow': '🙇',
-  'bow_and_arrow': '🏹',
-  'bowing_man': '🙇',
-  'bowing_woman': '🙇‍♀️',
-  'bowling': '🎳',
-  'bowtie': 'https://assets-cdn.github.com/images/icons/emoji/bowtie.png',
-  'boy': '👦',
-  'bread': '🍞',
-  'bride_with_veil': '👰',
-  'bridge_at_night': '🌉',
-  'briefcase': '💼',
-  'broken_heart': '💔',
-  'bug': '🐛',
-  'bulb': '💡',
-  'bullettrain_front': '🚅',
-  'bullettrain_side': '🚄',
-  'bus': '🚌',
-  'busstop': '🚏',
-  'bust_in_silhouette': '👤',
-  'busts_in_silhouette': '👥',
-  'cactus': '🌵',
-  'cake': '🍰',
-  'calendar': '📆',
-  'calling': '📲',
-  'camel': '🐫',
-  'camera': '📷',
-  'canada': '🇨🇦',
-  'canary_islands': '🇮🇨',
-  'cancer': '♋️',
-  'candle': '🕯',
-  'candy': '🍬',
-  'capital_abcd': '🔠',
-  'capricorn': '♑️',
-  'car': '🚗',
-  'card_index': '📇',
-  'carousel_horse': '🎠',
-  'cat': '🐱',
-  'cat2': '🐈',
-  'cd': '💿',
-  'chart': '💹',
-  'chart_with_downwards_trend': '📉',
-  'chart_with_upwards_trend': '📈',
-  'checkered_flag': '🏁',
-  'cherries': '🍒',
-  'cherry_blossom': '🌸',
-  'chestnut': '🌰',
-  'chicken': '🐔',
-  'children_crossing': '🚸',
-  'chocolate_bar': '🍫',
-  'christmas_tree': '🎄',
-  'church': '⛪️',
-  'cinema': '🎦',
-  'circus_tent': '🎪',
-  'city_sunrise': '🌇',
-  'city_sunset': '🌆',
-  'cl': '🆑',
-  'clap': '👏',
-  'clapper': '🎬',
-  'clipboard': '📋',
-  'clock1': '🕐',
-  'clock10': '🕙',
-  'clock1030': '🕥',
-  'clock11': '🕚',
-  'clock1130': '🕦',
-  'clock12': '🕛',
-  'clock1230': '🕧',
-  'clock130': '🕜',
-  'clock2': '🕑',
-  'clock230': '🕝',
-  'clock3': '🕒',
-  'clock330': '🕞',
-  'clock4': '🕓',
-  'clock430': '🕟',
-  'clock5': '🕔',
-  'clock530': '🕠',
-  'clock6': '🕕',
-  'clock630': '🕡',
-  'clock7': '🕖',
-  'clock730': '🕢',
-  'clock8': '🕗',
-  'clock830': '🕣',
-  'clock9': '🕘',
-  'clock930': '🕤',
-  'closed_book': '📕',
-  'closed_lock_with_key': '🔐',
-  'closed_umbrella': '🌂',
-  'cloud': '☁️',
-  'clubs': '♣️',
-  'cn': '🇨🇳',
-  'cocktail': '🍸',
-  'coffee': '☕️',
-  'cold_sweat': '😰',
-  'collision': '💥',
-  'computer': '💻',
-  'confetti_ball': '🎊',
-  'confounded': '😖',
-  'confused': '😕',
-  'congratulations': '㊗️',
-  'construction': '🚧',
-  'construction_worker': '👷',
-  'convenience_store': '🏪',
-  'cookie': '🍪',
-  'cool': '🆒',
-  'cop': '👮',
-  'copyright': '©️',
-  'corn': '🌽',
-  'couple': '👫',
-  'couple_with_heart': '💑',
-  'cow': '🐮',
-  'cow2': '🐄',
-  'credit_card': '💳',
-  'crescent_moon': '🌙',
-  'crocodile': '🐊',
-  'crossed_flags': '🎌',
-  'crown': '👑',
-  'cry': '😢',
-  'crying_cat_face': '😿',
-  'crystal_ball': '🔮',
-  'cupid': '💘',
-  'curly_loop': '➰',
-  'currency_exchange': '💱',
-  'curry': '🍛',
-  'custard': '🍮',
-  'customs': '🛃',
-  'cyclone': '🌀',
-  'dancer': '💃',
-  'dancers': '👯',
-  'dango': '🍡',
-  'dart': '🎯',
-  'dash': '💨',
-  'date': '📅',
-  'de': '🇩🇪',
-  'deciduous_tree': '🌳',
-  'department_store': '🏬',
-  'diamond_shape_with_a_dot_inside': '💠',
-  'diamonds': '♦️',
-  'disappointed': '😞',
-  'disappointed_relieved': '😥',
-  'dizzy': '💫',
-  'dizzy_face': '😵',
-  'do_not_litter': '🚯',
-  'dog': '🐶',
-  'dog2': '🐕',
-  'dollar': '💵',
-  'dolls': '🎎',
-  'dolphin': '🐬',
-  'door': '🚪',
-  'doughnut': '🍩',
-  'dragon': '🐉',
-  'dragon_face': '🐲',
-  'dress': '👗',
-  'dromedary_camel': '🐪',
-  'droplet': '💧',
-  'dvd': '📀',
-  'e-mail': '📧',
-  'ear': '👂',
-  'ear_of_rice': '🌾',
-  'earth_africa': '🌍',
-  'earth_americas': '🌎',
-  'earth_asia': '🌏',
-  'egg': '🥚',
-  'eggplant': '🍆',
-  'eight': '8️⃣',
-  'eight_pointed_black_star': '✴️',
-  'eight_spoked_asterisk': '✳️',
-  'electric_plug': '🔌',
-  'elephant': '🐘',
-  'email': '✉️',
-  'end': '🔚',
-  'envelope': '✉️',
-  'envelope_with_arrow': '📩',
-  'es': '🇪🇸',
-  'euro': '💶',
-  'european_castle': '🏰',
-  'european_post_office': '🏤',
-  'evergreen_tree': '🌲',
-  'exclamation': '❗️',
-  'expressionless': '😑',
-  'eyeglasses': '👓',
-  'eyes': '👀',
-  'facepunch': '👊',
-  'factory': '🏭',
-  'fallen_leaf': '🍂',
-  'family': '👪',
-  'fast_forward': '⏩',
-  'fax': '📠',
-  'fearful': '😨',
-  'feelsgood': 'https://assets-cdn.github.com/images/icons/emoji/feelsgood.png',
-  'feet': '🐾',
-  'ferris_wheel': '🎡',
-  'file_folder': '📁',
-  'finnadie': 'https://assets-cdn.github.com/images/icons/emoji/finnadie.png',
-  'fire': '🔥',
-  'fire_engine': '🚒',
-  'fireworks': '🎆',
-  'first_quarter_moon': '🌓',
-  'first_quarter_moon_with_face': '🌛',
-  'fish': '🐟',
-  'fish_cake': '🍥',
-  'fishing_pole_and_fish': '🎣',
-  'fist': '✊',
-  'five': '5️⃣',
-  'flags': '🎏',
-  'flashlight': '🔦',
-  'flipper': '🐬',
-  'floppy_disk': '💾',
-  'flower_playing_cards': '🎴',
-  'flushed': '😳',
-  'foggy': '🌁',
-  'football': '🏈',
-  'footprints': '👣',
-  'fork_and_knife': '🍴',
-  'fountain': '⛲️',
-  'four': '4️⃣',
-  'four_leaf_clover': '🍀',
-  'fr': '🇫🇷',
-  'free': '🆓',
-  'fried_shrimp': '🍤',
-  'fries': '🍟',
-  'frog': '🐸',
-  'frowning': '😦',
-  'fu': '🖕',
-  'fuelpump': '⛽️',
-  'full_moon': '🌕',
-  'full_moon_with_face': '🌝',
-  'game_die': '🎲',
-  'gb': '🇬🇧',
-  'gem': '💎',
-  'gemini': '♊️',
-  'ghost': '👻',
-  'gift': '🎁',
-  'gift_heart': '💝',
-  'girl': '👧',
-  'globe_with_meridians': '🌐',
-  'goat': '🐐',
-  'goberserk': 'https://assets-cdn.github.com/images/icons/emoji/goberserk.png',
-  'godmode': 'https://assets-cdn.github.com/images/icons/emoji/godmode.png',
-  'golf': '⛳️',
-  'grapes': '🍇',
-  'green_apple': '🍏',
-  'green_book': '📗',
-  'green_heart': '💚',
-  'grey_exclamation': '❕',
-  'grey_question': '❔',
-  'grimacing': '😬',
-  'grin': '😁',
-  'grinning': '😀',
-  'guardsman': '💂',
-  'guitar': '🎸',
-  'gun': '🔫',
-  'haircut': '💇',
-  'hamburger': '🍔',
-  'hammer': '🔨',
-  'hamster': '🐹',
-  'hand': '✋',
-  'handbag': '👜',
-  'hankey': '💩',
-  'hash': '#️⃣',
-  'hatched_chick': '🐥',
-  'hatching_chick': '🐣',
-  'headphones': '🎧',
-  'hear_no_evil': '🙉',
-  'heart': '❤️',
-  'heart_decoration': '💟',
-  'heart_eyes': '😍',
-  'heart_eyes_cat': '😻',
-  'heartbeat': '💓',
-  'heartpulse': '💗',
-  'hearts': '♥️',
-  'heavy_check_mark': '✔️',
-  'heavy_division_sign': '➗',
-  'heavy_dollar_sign': '💲',
-  'heavy_exclamation_mark': '❗️',
-  'heavy_minus_sign': '➖',
-  'heavy_multiplication_x': '✖️',
-  'heavy_plus_sign': '➕',
-  'helicopter': '🚁',
-  'herb': '🌿',
-  'hibiscus': '🌺',
-  'high_brightness': '🔆',
-  'high_heel': '👠',
-  'hocho': '🔪',
-  'honey_pot': '🍯',
-  'honeybee': '🐝',
-  'horse': '🐴',
-  'horse_racing': '🏇',
-  'hospital': '🏥',
-  'hotel': '🏨',
-  'hotsprings': '♨️',
-  'hourglass': '⌛️',
-  'hourglass_flowing_sand': '⏳',
-  'house': '🏠',
-  'house_with_garden': '🏡',
-  'hurtrealbad': 'https://assets-cdn.github.com/images/icons/emoji/hurtrealbad.png',
-  'hushed': '😯',
-  'ice_cream': '🍨',
-  'icecream': '🍦',
-  'id': '🆔',
-  'ideograph_advantage': '🉐',
-  'imp': '👿',
-  'inbox_tray': '📥',
-  'incoming_envelope': '📨',
-  'information_desk_person': '💁',
-  'information_source': 'ℹ️',
-  'innocent': '😇',
-  'interrobang': '⁉️',
-  'iphone': '📱',
-  'it': '🇮🇹',
-  'izakaya_lantern': '🏮',
-  'jack_o_lantern': '🎃',
-  'japan': '🗾',
-  'japanese_castle': '🏯',
-  'japanese_goblin': '👺',
-  'japanese_ogre': '👹',
-  'jeans': '👖',
-  'joy': '😂',
-  'joy_cat': '😹',
-  'jp': '🇯🇵',
-  'key': '🔑',
-  'keycap_ten': '🔟',
-  'kimono': '👘',
-  'kiss': '💋',
-  'kissing': '😗',
-  'kissing_cat': '😽',
-  'kissing_closed_eyes': '😚',
-  'kissing_heart': '😘',
-  'kissing_smiling_eyes': '😙',
-  'koala': '🐨',
-  'koko': '🈁',
-  'kr': '🇰🇷',
-  'lantern': '🏮',
-  'large_blue_circle': '🔵',
-  'large_blue_diamond': '🔷',
-  'large_orange_diamond': '🔶',
-  'last_quarter_moon': '🌗',
-  'last_quarter_moon_with_face': '🌜',
-  'laughing': '😆',
-  'leaves': '🍃',
-  'ledger': '📒',
-  'left_luggage': '🛅',
-  'left_right_arrow': '↔️',
-  'leftwards_arrow_with_hook': '↩️',
-  'lemon': '🍋',
-  'leo': '♌️',
-  'leopard': '🐆',
-  'libra': '♎️',
-  'light_rail': '🚈',
-  'link': '🔗',
-  'lips': '👄',
-  'lipstick': '💄',
-  'lock': '🔒',
-  'lock_with_ink_pen': '🔏',
-  'lollipop': '🍭',
-  'loop': '➿',
-  'loud_sound': '🔊',
-  'loudspeaker': '📢',
-  'love_hotel': '🏩',
-  'love_letter': '💌',
-  'low_brightness': '🔅',
-  'm': 'Ⓜ️',
-  'mag': '🔍',
-  'mag_right': '🔎',
-  'mahjong': '🀄️',
-  'mailbox': '📫',
-  'mailbox_closed': '📪',
-  'mailbox_with_mail': '📬',
-  'mailbox_with_no_mail': '📭',
-  'man': '👨',
-  'man_with_gua_pi_mao': '👲',
-  'man_with_turban': '👳',
-  'mans_shoe': '👞',
-  'maple_leaf': '🍁',
-  'mask': '😷',
-  'massage': '💆',
-  'meat_on_bone': '🍖',
-  'mega': '📣',
-  'melon': '🍈',
-  'memo': '📝',
-  'mens': '🚹',
-  'metal': '🤘',
-  'metro': '🚇',
-  'microphone': '🎤',
-  'microscope': '🔬',
-  'milky_way': '🌌',
-  'minibus': '🚐',
-  'minidisc': '💽',
-  'mobile_phone_off': '📴',
-  'money_with_wings': '💸',
-  'moneybag': '💰',
-  'monkey': '🐒',
-  'monkey_face': '🐵',
-  'monorail': '🚝',
-  'moon': '🌔',
-  'mortar_board': '🎓',
-  'mount_fuji': '🗻',
-  'mountain_bicyclist': '🚵',
-  'mountain_cableway': '🚠',
-  'mountain_railway': '🚞',
-  'mouse': '🐭',
-  'mouse2': '🐁',
-  'movie_camera': '🎥',
-  'moyai': '🗿',
-  'muscle': '💪',
-  'mushroom': '🍄',
-  'musical_keyboard': '🎹',
-  'musical_note': '🎵',
-  'musical_score': '🎼',
-  'mute': '🔇',
-  'nail_care': '💅',
-  'name_badge': '📛',
-  'neckbeard': 'https://assets-cdn.github.com/images/icons/emoji/neckbeard.png',
-  'necktie': '👔',
-  'negative_squared_cross_mark': '❎',
-  'neutral_face': '😐',
-  'new': '🆕',
-  'new_moon': '🌑',
-  'new_moon_with_face': '🌚',
-  'newspaper': '📰',
-  'ng': '🆖',
-  'night_with_stars': '🌃',
-  'nine': '9️⃣',
-  'no_bell': '🔕',
-  'no_bicycles': '🚳',
-  'no_entry': '⛔️',
-  'no_entry_sign': '🚫',
-  'no_good': '🙅',
-  'no_mobile_phones': '📵',
-  'no_mouth': '😶',
-  'no_pedestrians': '🚷',
-  'no_smoking': '🚭',
-  'non-potable_water': '🚱',
-  'nose': '👃',
-  'notebook': '📓',
-  'notebook_with_decorative_cover': '📔',
-  'notes': '🎶',
-  'nut_and_bolt': '🔩',
-  'o': '⭕️',
-  'o2': '🅾️',
-  'ocean': '🌊',
-  'octocat': 'https://assets-cdn.github.com/images/icons/emoji/octocat.png',
-  'octopus': '🐙',
-  'oden': '🍢',
-  'office': '🏢',
-  'ok': '🆗',
-  'ok_hand': '👌',
-  'ok_woman': '🙆',
-  'older_man': '👴',
-  'older_woman': '👵',
-  'on': '🔛',
-  'oncoming_automobile': '🚘',
-  'oncoming_bus': '🚍',
-  'oncoming_police_car': '🚔',
-  'oncoming_taxi': '🚖',
-  'one': '1️⃣',
-  'open_book': '📖',
-  'open_file_folder': '📂',
-  'open_hands': '👐',
-  'open_mouth': '😮',
-  'ophiuchus': '⛎',
-  'orange_book': '📙',
-  'outbox_tray': '📤',
-  'ox': '🐂',
-  'package': '📦',
-  'page_facing_up': '📄',
-  'page_with_curl': '📃',
-  'pager': '📟',
-  'palm_tree': '🌴',
-  'panda_face': '🐼',
-  'paperclip': '📎',
-  'parking': '🅿️',
-  'part_alternation_mark': '〽️',
-  'partly_sunny': '⛅️',
-  'passport_control': '🛂',
-  'paw_prints': '🐾',
-  'peach': '🍑',
-  'pear': '🍐',
-  'pencil': '📝',
-  'pencil2': '✏️',
-  'penguin': '🐧',
-  'pensive': '😔',
-  'performing_arts': '🎭',
-  'persevere': '😣',
-  'person_frowning': '🙍',
-  'person_with_blond_hair': '👱',
-  'person_with_pouting_face': '🙎',
-  'phone': '☎️',
-  'pig': '🐷',
-  'pig2': '🐖',
-  'pig_nose': '🐽',
-  'pill': '💊',
-  'pineapple': '🍍',
-  'pisces': '♓️',
-  'pizza': '🍕',
-  'point_down': '👇',
-  'point_left': '👈',
-  'point_right': '👉',
-  'point_up': '☝️',
-  'point_up_2': '👆',
-  'police_car': '🚓',
-  'poodle': '🐩',
-  'poop': '💩',
-  'post_office': '🏣',
-  'postal_horn': '📯',
-  'postbox': '📮',
-  'potable_water': '🚰',
-  'pouch': '👝',
-  'poultry_leg': '🍗',
-  'pound': '💷',
-  'pouting_cat': '😾',
-  'pray': '🙏',
-  'princess': '👸',
-  'punch': '👊',
-  'purple_heart': '💜',
-  'purse': '👛',
-  'pushpin': '📌',
-  'put_litter_in_its_place': '🚮',
-  'question': '❓',
-  'rabbit': '🐰',
-  'rabbit2': '🐇',
-  'racehorse': '🐎',
-  'radio': '📻',
-  'radio_button': '🔘',
-  'rage': '😡',
-  'rage1': 'https://assets-cdn.github.com/images/icons/emoji/rage1.png',
-  'rage2': 'https://assets-cdn.github.com/images/icons/emoji/rage2.png',
-  'rage3': 'https://assets-cdn.github.com/images/icons/emoji/rage3.png',
-  'rage4': 'https://assets-cdn.github.com/images/icons/emoji/rage4.png',
-  'railway_car': '🚃',
-  'rainbow': '🌈',
-  'raised_hand': '✋',
-  'raised_hands': '🙌',
-  'raising_hand': '🙋',
-  'ram': '🐏',
-  'ramen': '🍜',
-  'rat': '🐀',
-  'recycle': '♻️',
-  'red_car': '🚗',
-  'red_circle': '🔴',
-  'registered': '®️',
-  'relaxed': '☺️',
-  'relieved': '😌',
-  'repeat': '🔁',
-  'repeat_one': '🔂',
-  'restroom': '🚻',
-  'revolving_hearts': '💞',
-  'rewind': '⏪',
-  'ribbon': '🎀',
-  'rice': '🍚',
-  'rice_ball': '🍙',
-  'rice_cracker': '🍘',
-  'rice_scene': '🎑',
-  'ring': '💍',
-  'rocket': '🚀',
-  'roller_coaster': '🎢',
-  'rooster': '🐓',
-  'rose': '🌹',
-  'rotating_light': '🚨',
-  'round_pushpin': '📍',
-  'rowboat': '🚣',
-  'ru': '🇷🇺',
-  'rugby_football': '🏉',
-  'runner': '🏃',
-  'running': '🏃',
-  'running_shirt_with_sash': '🎽',
-  'sa': '🈂️',
-  'sagittarius': '♐️',
-  'sailboat': '⛵️',
-  'sake': '🍶',
-  'sandal': '👡',
-  'santa': '🎅',
-  'satellite': '📡',
-  'satisfied': '😆',
-  'saxophone': '🎷',
-  'school': '🏫',
-  'school_satchel': '🎒',
-  'scissors': '✂️',
-  'scorpius': '♏️',
-  'scream': '😱',
-  'scream_cat': '🙀',
-  'scroll': '📜',
-  'seat': '💺',
-  'secret': '㊙️',
-  'see_no_evil': '🙈',
-  'seedling': '🌱',
-  'seven': '7️⃣',
-  'shaved_ice': '🍧',
-  'sheep': '🐑',
-  'shell': '🐚',
-  'ship': '🚢',
-  'shipit': 'https://assets-cdn.github.com/images/icons/emoji/shipit.png',
-  'shirt': '👕',
-  'shit': '💩',
-  'shoe': '👞',
-  'shower': '🚿',
-  'signal_strength': '📶',
-  'six': '6️⃣',
-  'six_pointed_star': '🔯',
-  'ski': '🎿',
-  'skull': '💀',
-  'sleeping': '😴',
-  'sleepy': '😪',
-  'slot_machine': '🎰',
-  'small_blue_diamond': '🔹',
-  'small_orange_diamond': '🔸',
-  'small_red_triangle': '🔺',
-  'small_red_triangle_down': '🔻',
-  'smile': '😄',
-  'smile_cat': '😸',
-  'smiley': '😃',
-  'smiley_cat': '😺',
-  'smiling_imp': '😈',
-  'smirk': '😏',
-  'smirk_cat': '😼',
-  'smoking': '🚬',
-  'snail': '🐌',
-  'snake': '🐍',
-  'snowboarder': '🏂',
-  'snowflake': '❄️',
-  'snowman': '⛄️',
-  'sob': '😭',
-  'soccer': '⚽️',
-  'soon': '🔜',
-  'sos': '🆘',
-  'sound': '🔉',
-  'space_invader': '👾',
-  'spades': '♠️',
-  'spaghetti': '🍝',
-  'sparkle': '❇️',
-  'sparkler': '🎇',
-  'sparkles': '✨',
-  'sparkling_heart': '💖',
-  'speak_no_evil': '🙊',
-  'speaker': '🔈',
-  'speech_balloon': '💬',
-  'speedboat': '🚤',
-  'squirrel': 'https://assets-cdn.github.com/images/icons/emoji/shipit.png',
-  'star': '⭐️',
-  'star2': '🌟',
-  'stars': '🌠',
-  'station': '🚉',
-  'statue_of_liberty': '🗽',
-  'steam_locomotive': '🚂',
-  'stew': '🍲',
-  'straight_ruler': '📏',
-  'strawberry': '🍓',
-  'stuck_out_tongue': '😛',
-  'stuck_out_tongue_closed_eyes': '😝',
-  'stuck_out_tongue_winking_eye': '😜',
-  'sun_with_face': '🌞',
-  'sunflower': '🌻',
-  'sunglasses': '😎',
-  'sunny': '☀️',
-  'sunrise': '🌅',
-  'sunrise_over_mountains': '🌄',
-  'surfer': '🏄',
-  'sushi': '🍣',
-  'suspect': 'https://assets-cdn.github.com/images/icons/emoji/suspect.png',
-  'suspension_railway': '🚟',
-  'sweat': '😓',
-  'sweat_drops': '💦',
-  'sweat_smile': '😅',
-  'sweet_potato': '🍠',
-  'swimmer': '🏊',
-  'symbols': '🔣',
-  'syringe': '💉',
-  'tada': '🎉',
-  'tanabata_tree': '🎋',
-  'tangerine': '🍊',
-  'taurus': '♉️',
-  'taxi': '🚕',
-  'tea': '🍵',
-  'telephone': '☎️',
-  'telephone_receiver': '📞',
-  'telescope': '🔭',
-  'tennis': '🎾',
-  'tent': '⛺️',
-  'thought_balloon': '💭',
-  'three': '3️⃣',
-  'thumbsdown': '👎',
-  'thumbsup': '👍',
-  'ticket': '🎫',
-  'tiger': '🐯',
-  'tiger2': '🐅',
-  'tired_face': '😫',
-  'tm': '™️',
-  'toilet': '🚽',
-  'tokyo_tower': '🗼',
-  'tomato': '🍅',
-  'tongue': '👅',
-  'top': '🔝',
-  'tophat': '🎩',
-  'tractor': '🚜',
-  'traffic_light': '🚥',
-  'train': '🚋',
-  'train2': '🚆',
-  'tram': '🚊',
-  'triangular_flag_on_post': '🚩',
-  'triangular_ruler': '📐',
-  'trident': '🔱',
-  'triumph': '😤',
-  'trolleybus': '🚎',
-  'trollface': 'https://assets-cdn.github.com/images/icons/emoji/trollface.png',
-  'trophy': '🏆',
-  'tropical_drink': '🍹',
-  'tropical_fish': '🐠',
-  'truck': '🚚',
-  'trumpet': '🎺',
-  'tshirt': '👕',
-  'tulip': '🌷',
-  'turtle': '🐢',
-  'tv': '📺',
-  'twisted_rightwards_arrows': '🔀',
-  'two': '2️⃣',
-  'two_hearts': '💕',
-  'two_men_holding_hands': '👬',
-  'two_women_holding_hands': '👭',
-  'u5272': '🈹',
-  'u5408': '🈴',
-  'u55b6': '🈺',
-  'u6307': '🈯️',
-  'u6708': '🈷️',
-  'u6709': '🈶',
-  'u6e80': '🈵',
-  'u7121': '🈚️',
-  'u7533': '🈸',
-  'u7981': '🈲',
-  'u7a7a': '🈳',
-  'uk': '🇬🇧',
-  'ukraine': '🇺🇦',
-  'umbrella': '☔️',
-  'unamused': '😒',
-  'underage': '🔞',
-  'unlock': '🔓',
-  'up': '🆙',
-  'us': '🇺🇸',
-  'us_virgin_islands': '🇻🇮',
-  'v': '✌️',
-  'vertical_traffic_light': '🚦',
-  'vhs': '📼',
-  'vibration_mode': '📳',
-  'video_camera': '📹',
-  'video_game': '🎮',
-  'violin': '🎻',
-  'virgo': '♍️',
-  'volcano': '🌋',
-  'vs': '🆚',
-  'walking': '🚶',
-  'walking_man': '🚶',
-  'walking_woman': '🚶‍♀️',
-  'wallis_futuna': '🇼🇫',
-  'waning_crescent_moon': '🌘',
-  'waning_gibbous_moon': '🌖',
-  'warning': '⚠️',
-  'watch': '⌚️',
-  'water_buffalo': '🐃',
-  'watermelon': '🍉',
-  'wave': '👋',
-  'wavy_dash': '〰️',
-  'waxing_crescent_moon': '🌒',
-  'waxing_gibbous_moon': '🌔',
-  'wc': '🚾',
-  'weary': '😩',
-  'wedding': '💒',
-  'whale': '🐳',
-  'whale2': '🐋',
-  'wheel_of_dharma': '☸️',
-  'wheelchair': '♿️',
-  'white_check_mark': '✅',
-  'white_circle': '⚪️',
-  'white_flower': '💮',
-  'white_large_square': '⬜️',
-  'white_medium_small_square': '◽️',
-  'white_medium_square': '◻️',
-  'white_small_square': '▫️',
-  'white_square_button': '🔳',
-  'wilted_flower': '🥀',
-  'wind_chime': '🎐',
-  'wind_face': '🌬',
-  'wine_glass': '🍷',
-  'wink': '😉',
-  'wolf': '🐺',
-  'woman': '👩',
-  'womans_clothes': '👚',
-  'womans_hat': '👒',
-  'womens': '🚺',
-  'world_map': '🗺',
-  'worried': '😟',
-  'wrench': '🔧',
-  'x': '❌',
-  'yellow_heart': '💛',
-  'yen': '💴',
-  'yum': '😋',
-  'zap': '⚡️',
-  'zero': '0️⃣',
-  'zzz': '💤',
-}
diff --git a/ui/packages/platform/src/config/env.ts b/ui/packages/platform/src/config/env.ts
deleted file mode 100644
index 71894939..00000000
--- a/ui/packages/platform/src/config/env.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const NODE_ENV = process.env.NODE_ENV
-export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN
-export const API_URL_PREFIX = process.env.REACT_APP_API_SERVER ?? ''
-export const WS_URL_PREFIX = process.env.REACT_APP_WS_URL_PREFIX ?? ''
-export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP
-
-// For debug purposes or during development, allow to pre-set the JWT token.
-const token = process.env.REACT_APP_TOKEN_DEBUG;
-if (token) {
-  localStorage.setItem('token', token);
-  console.warn('WARNING: JWT token is being set from the environment variable. This appears to be a debugging or development setup.');
-}
diff --git a/ui/packages/platform/src/config/routes/clones.ts b/ui/packages/platform/src/config/routes/clones.ts
deleted file mode 100644
index a1723c21..00000000
--- a/ui/packages/platform/src/config/routes/clones.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-export const ORG_CLONES = {
-  ADD: {
-    createPath: (args?: {
-      org: string
-      instanceId: string
-    }) => {
-      const {
-        org = ':org',
-        instanceId = ':instanceId',
-      } = args ?? {}
-
-      return `/${org}/instances/${instanceId}/clones/add`
-    },
-  },
-
-  CLONE: {
-    createPath: (args?: {
-      org: string
-      instanceId: string
-      cloneId: string
-    }) => {
-      const {
-        org = ':org',
-        instanceId = ':instanceId',
-        cloneId = ':cloneId',
-      } = args ?? {}
-
-      return `/${org}/instances/${instanceId}/clones/${cloneId}`
-    },
-  },
-}
-
-export const PROJECT_CLONES = {
-  ADD: {
-    createPath: (args?: {
-      org: string
-      project: string
-      instanceId: string
-    }) => {
-      const {
-        org = ':org',
-        project = ':project',
-        instanceId = ':instanceId',
-      } = args ?? {}
-
-      return `/${org}/${project}/instances/${instanceId}/clones/add`
-    },
-  },
-
-  CLONE: {
-    createPath: (args?: {
-      org: string
-      project: string
-      instanceId: string
-      cloneId: string
-    }) => {
-      const {
-        org = ':org',
-        project = ':project',
-        instanceId = ':instanceId',
-        cloneId = ':cloneId',
-      } = args ?? {}
-
-      return `/${org}/${project}/instances/${instanceId}/clones/${cloneId}`
-    },
-  },
-}
diff --git a/ui/packages/platform/src/config/routes/index.ts b/ui/packages/platform/src/config/routes/index.ts
deleted file mode 100644
index 093423df..00000000
--- a/ui/packages/platform/src/config/routes/index.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { ORG_INSTANCES, PROJECT_INSTANCES } from './instances'
-
-export const ROUTES = {
-  ROOT: {
-    name: 'Organizations',
-    path: '/',
-  },
-
-  PROFILE: {
-    path: '/profile',
-  },
-
-  CREATE_ORG: {
-    path: '/addorg',
-  },
-
-  ORG: {
-    TOKENS: {
-      createPath: (args?: { org: string }) => {
-        const { org = ':org' } = args ?? {}
-        return `/${org}/tokens`
-      }
-    },
-
-    JOE_INSTANCES: {
-      JOE_INSTANCE: {
-        createPath: ({
-          org = ':org',
-          id = ':id',
-        }: { org?: string; id?: string } = {}) => `/${org}/joe-instances/${id}`,
-      },
-    },
-
-    INSTANCES: ORG_INSTANCES,
-
-    PROJECT: {
-      JOE_INSTANCES: {
-        JOE_INSTANCE: {
-          createPath: ({
-            org = ':org',
-            project = ':project',
-            id = ':id',
-          }: { org?: string; project?: string; id?: string } = {}) =>
-            `/${org}/${project}/joe-instances/${id}`,
-        },
-      },
-
-      INSTANCES: PROJECT_INSTANCES,
-    },
-  },
-}
diff --git a/ui/packages/platform/src/config/routes/instances.ts b/ui/packages/platform/src/config/routes/instances.ts
deleted file mode 100644
index b97c5551..00000000
--- a/ui/packages/platform/src/config/routes/instances.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { ORG_CLONES, PROJECT_CLONES } from './clones'
-
-export const ORG_INSTANCES = {
-  createPath: (args?: { org: string }) => {
-    const { org = ':org' } = args ?? {}
-    return `/${org}/instances`
-  },
-
-  INSTANCE: {
-    createPath: (args?: { org: string; instanceId: string }) => {
-      const { org = ':org', instanceId = ':instanceId' } = args ?? {}
-      return `/${org}/instances/${instanceId}`
-    },
-
-    CLONES: ORG_CLONES,
-  },
-}
-
-export const PROJECT_INSTANCES = {
-  createPath: (args?: { org: string; project: string }) => {
-    const { org = ':org', project = ':project' } = args ?? {}
-    return `/${org}/${project}/instances`
-  },
-
-  INSTANCE: {
-    createPath: (args?: {
-      org: string
-      project: string
-      instanceId: string
-    }) => {
-      const {
-        org = ':org',
-        project = ':project',
-        instanceId = ':instanceId',
-      } = args ?? {}
-      return `/${org}/${project}/instances/${instanceId}`
-    },
-
-    CLONES: PROJECT_CLONES,
-  },
-}
diff --git a/ui/packages/platform/src/helpers/localStorage.ts b/ui/packages/platform/src/helpers/localStorage.ts
deleted file mode 100644
index 4bbc8b0d..00000000
--- a/ui/packages/platform/src/helpers/localStorage.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { LocalStorage as LocalStorageShared } from '@postgres.ai/shared/helpers/localStorage'
-
-class LocalStorage extends LocalStorageShared {}
-
-export const localStorage = new LocalStorage()
diff --git a/ui/packages/platform/src/helpers/request.ts b/ui/packages/platform/src/helpers/request.ts
deleted file mode 100644
index e372ec8e..00000000
--- a/ui/packages/platform/src/helpers/request.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
-  request as requestCore,
-  RequestOptions,
-} from '@postgres.ai/shared/helpers/request'
-
-import { localStorage } from 'helpers/localStorage'
-import { API_URL_PREFIX } from 'config/env'
-
-export const request = async (
-  path: string,
-  options?: RequestOptions,
-  customPrefix?: string,
-) => {
-  const authToken = localStorage.getAuthToken()
-
-  const response = await requestCore(
-    `${customPrefix ? customPrefix?.replace(/"/g, '') : API_URL_PREFIX}${path}`,
-    {
-      ...options,
-      headers: {
-        ...(authToken && { Authorization: `Bearer ${authToken}` }),
-        ...options?.headers,
-      },
-    },
-  )
-
-  return response
-}
diff --git a/ui/packages/platform/src/helpers/simpleInstallRequest.ts b/ui/packages/platform/src/helpers/simpleInstallRequest.ts
deleted file mode 100644
index ba9be5c3..00000000
--- a/ui/packages/platform/src/helpers/simpleInstallRequest.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import {
-  RequestOptions,
-  request as requestCore,
-} from '@postgres.ai/shared/helpers/request'
-
-const sign = require('jwt-encode')
-
-export const SI_API_SERVER = 'https://si.aws.postgres.ai'
-
-export const JWT_SECRET = 'some-jwt-secret'
-export const JWT_PAYLOAD = (userID?: number) => ({
-  id: userID?.toString(),
-})
-export const JWT_HEADER = {
-  alg: 'HS256',
-  typ: 'JWT',
-}
-
-export const simpleInstallRequest = async (
-  path: string,
-  options: RequestOptions,
-  userID?: number,
-) => {
-  const jwtToken = sign(JWT_PAYLOAD(userID), JWT_SECRET, JWT_HEADER)
-
-  const response = await requestCore(`${SI_API_SERVER}${path}`, {
-    ...options,
-    headers: {
-      Authorization: `Bearer ${jwtToken}`,
-    },
-  })
-
-  return response
-}
diff --git a/ui/packages/platform/src/hooks/useCloudProvider.ts b/ui/packages/platform/src/hooks/useCloudProvider.ts
deleted file mode 100644
index 9227f533..00000000
--- a/ui/packages/platform/src/hooks/useCloudProvider.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import { Reducer, useCallback, useEffect, useReducer } from 'react'
-
-import { getCloudInstances } from 'api/cloud/getCloudInstances'
-import { getCloudProviders } from 'api/cloud/getCloudProviders'
-import { getCloudRegions } from 'api/cloud/getCloudRegions'
-import { CloudVolumes, getCloudVolumes } from 'api/cloud/getCloudVolumes'
-import { formatVolumeDetails } from 'components/DbLabInstanceForm/utils'
-import { generateToken } from 'utils/utils'
-
-interface State {
-  [key: string]: StateValue
-}
-
-type StateValue =
-  | string
-  | number
-  | boolean
-  | undefined
-  | unknown
-  | { [key: string]: string }
-
-export interface useCloudProviderProps {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  initialState: any
-  reducer: Reducer<useCloudProviderProps['initialState'], State>
-}
-
-export const useCloudProvider = ({
-  initialState,
-  reducer,
-}: useCloudProviderProps) => {
-  const [state, dispatch] = useReducer(reducer, initialState)
-
-  const urlParams = new URLSearchParams(window.location.search)
-  const urlTaskID = urlParams.get('taskID')
-  const urlProvider = urlParams.get('provider')
-
-  const fetchCloudData = useCallback(
-    async (provider: string) => {
-      const cloudRegions = await getCloudRegions(provider)
-      const cloudVolumes = await getCloudVolumes(provider)
-      const ssdCloudVolumes = cloudVolumes.response.find(
-        (volume: CloudVolumes) => volume.api_name === initialState?.api_name,
-      )
-
-      return {
-        cloudRegions: cloudRegions.response,
-        cloudVolumes: cloudVolumes.response,
-        ssdCloudVolume: ssdCloudVolumes,
-      }
-    },
-    [initialState.api_name],
-  )
-
-  useEffect(() => {
-    const action = {
-      type: 'set_form_step',
-      formStep: urlTaskID && urlProvider ? 'simple' : initialState.formStep,
-      taskID: urlTaskID || undefined,
-      provider: urlProvider || initialState.provider,
-    }
-
-    dispatch(action)
-  }, [urlTaskID, urlProvider, initialState.formStep, initialState.provider])
-
-  useEffect(() => {
-    const fetchInitialCloudDetails = async () => {
-      try {
-        const { cloudRegions, cloudVolumes, ssdCloudVolume } =
-          await fetchCloudData(initialState.provider as string)
-        const volumeDetails = formatVolumeDetails(
-          ssdCloudVolume,
-          initialState.storage as number,
-        )
-        const serviceProviders = await getCloudProviders()
-
-        dispatch({
-          type: 'set_initial_state',
-          cloudRegions,
-          volumes: cloudVolumes,
-          ...volumeDetails,
-          serviceProviders: serviceProviders.response,
-          isLoading: false,
-        })
-      } catch (error) {
-        console.error(error)
-      }
-    }
-
-    fetchInitialCloudDetails()
-  }, [initialState.provider, initialState.storage, fetchCloudData])
-
-  useEffect(() => {
-    const fetchUpdatedCloudDetails = async () => {
-      try {
-        const { cloudRegions, cloudVolumes, ssdCloudVolume } =
-          await fetchCloudData(state.provider)
-        const volumeDetails = formatVolumeDetails(
-          ssdCloudVolume,
-          initialState.storage as number,
-        )
-
-        dispatch({
-          type: 'update_initial_state',
-          cloudRegions,
-          volumes: cloudVolumes,
-          ...volumeDetails,
-        })
-      } catch (error) {
-        console.error(error)
-      }
-    }
-
-    fetchUpdatedCloudDetails()
-  }, [state.api_name, state.provider, initialState.storage, fetchCloudData])
-
-  useEffect(() => {
-    if (state.location.native_code && state.provider) {
-      const fetchUpdatedDetails = async () => {
-        try {
-          const cloudInstances = await getCloudInstances({
-            provider: state.provider,
-            region: state.location.native_code,
-          })
-
-          dispatch({
-            type: 'update_instance_type',
-            cloudInstances: cloudInstances.response,
-            instanceType: cloudInstances.response[0],
-            isReloading: false,
-          })
-        } catch (error) {
-          console.log(error)
-        }
-      }
-      fetchUpdatedDetails()
-    }
-  }, [state.location.native_code, state.provider])
-
-  const handleReturnToForm = () => {
-    dispatch({ type: 'set_form_step', formStep: initialState.formStep })
-  }
-
-  const handleSetFormStep = (step: string) => {
-    dispatch({ type: 'set_form_step', formStep: step })
-  }
-
-  const handleGenerateToken = () => {
-    dispatch({
-      type: 'change_verification_token',
-      verificationToken: generateToken(),
-    })
-  }
-
-  const handleChangeVolume = (
-    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
-  ) => {
-    const volumeApiName = event.target.value.split(' ')[0]
-    const selectedVolume = state.volumes.filter(
-      (volume: CloudVolumes) => volume.api_name === volumeApiName,
-    )[0]
-
-    dispatch({
-      type: 'change_volume_type',
-      volumeType: event.target.value,
-      volumePricePerHour:
-        selectedVolume.native_reference_price_per_1000gib_per_hour,
-      volumePrice:
-        (state.storage *
-          selectedVolume.native_reference_price_per_1000gib_per_hour) /
-        1000,
-    })
-  }
-
-  return {
-    state,
-    dispatch,
-    handleReturnToForm,
-    handleSetFormStep,
-    handleGenerateToken,
-    handleChangeVolume,
-  }
-}
diff --git a/ui/packages/platform/src/hooks/usePrev.ts b/ui/packages/platform/src/hooks/usePrev.ts
deleted file mode 100644
index 79123bbf..00000000
--- a/ui/packages/platform/src/hooks/usePrev.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { useRef, useEffect } from 'react'
-
-export const usePrev = <T>(value: T) => {
-  const ref = useRef<T>()
-  
-  useEffect(() => {
-    ref.current = value;
-  }, [value])
-
-  return ref.current
-}
diff --git a/ui/packages/platform/src/index.scss b/ui/packages/platform/src/index.scss
deleted file mode 100644
index aa02f71c..00000000
--- a/ui/packages/platform/src/index.scss
+++ /dev/null
@@ -1,27 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-@import '@postgres.ai/shared/styles/vars';
-
-* {
-  box-sizing: border-box;
-}
-
-body {
-  margin: 0;
-  padding: 0;
-  font-family: 'Roboto', sans-serif;
-  font-size: $font-size-main;
-}
-
-/* stylelint-disable-next-line */
-#root {
-  min-height: 100vh;
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-}
diff --git a/ui/packages/platform/src/index.tsx b/ui/packages/platform/src/index.tsx
deleted file mode 100644
index df43579c..00000000
--- a/ui/packages/platform/src/index.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import ReactDOM from 'react-dom'
-
-import { initConfig } from '@postgres.ai/shared/config'
-
-import App from './App'
-import { unregister } from './registerServiceWorker'
-
-import './index.scss'
-
-const main = async () => {
-  await initConfig()
-  ReactDOM.render(<App />, document.getElementById('root'))
-}
-
-main()
-
-// This func disable service worker.
-// Don't remove it,
-// because we cannot be sure that all previous users uninstalled their service workers.
-// It should be permanent, except when you want to add new service worker.
-const disableSw = () => {
-  unregister()
-
-  // clear all sw caches
-  if ('caches' in window) {
-    window.caches.keys().then((res) => {
-      res.forEach((key) => {
-        window.caches.delete(key)
-      })
-    })
-  }
-}
-
-window.addEventListener('load', disableSw)
diff --git a/ui/packages/platform/src/meta.json b/ui/packages/platform/src/meta.json
deleted file mode 100644
index 78bc307b..00000000
--- a/ui/packages/platform/src/meta.json
+++ /dev/null
@@ -1 +0,0 @@
-{"buildDate":1637930584925}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/BotWrapper.tsx b/ui/packages/platform/src/pages/Bot/BotWrapper.tsx
deleted file mode 100644
index d8676010..00000000
--- a/ui/packages/platform/src/pages/Bot/BotWrapper.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { BotPage } from "./index";
-import {RouteComponentProps} from "react-router";
-import {AlertSnackbarProvider} from "@postgres.ai/shared/components/AlertSnackbar/useAlertSnackbar";
-import { AiBotProvider } from "./hooks";
-
-export interface BotWrapperProps {
-  envData: {
-    info?: {
-      user_name?: string
-    }
-  };
-  orgData: {
-    id: number
-  },
-  history: RouteComponentProps['history']
-  project?: string
-  match: {
-    params: {
-      org?: string
-      threadId?: string
-    }
-  }
-}
-
-
-export const BotWrapper = (props: BotWrapperProps) => {
-  return (
-    <AlertSnackbarProvider>
-      <AiBotProvider args={{ threadId: props.match.params.threadId, orgId: props.orgData.id }}>
-        <BotPage {...props} />
-      </AiBotProvider>
-    </AlertSnackbarProvider>
-  )
-}
diff --git a/ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx b/ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx
deleted file mode 100644
index 4d990e6a..00000000
--- a/ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React from "react";
-import { Link } from "react-router-dom";
-import { useParams } from "react-router";
-import cn from "classnames";
-import { ListItem, ListItemIcon, makeStyles, Theme, useMediaQuery } from "@material-ui/core";
-import Drawer from '@material-ui/core/Drawer';
-import List from "@material-ui/core/List";
-import Divider from "@material-ui/core/Divider";
-import ListSubheader from '@material-ui/core/ListSubheader';
-import Box from "@mui/material/Box";
-import { Spinner } from "@postgres.ai/shared/components/Spinner";
-import { HeaderButtons, HeaderButtonsProps } from "../HeaderButtons/HeaderButtons";
-import { theme } from "@postgres.ai/shared/styles/theme";
-import { useAiBot } from "../hooks";
-import VisibilityOffIcon from '@material-ui/icons/VisibilityOff';
-
-
-const useStyles = makeStyles<Theme, ChatsListProps>((theme) => ({
-    drawerPaper: {
-      width: 240,
-      //TODO: Fix magic numbers
-      height: props => props.isDemoOrg ? 'calc(100vh - 122px)' : 'calc(100vh - 90px)',
-      marginTop: props => props.isDemoOrg ? 72 : 40,
-      [theme.breakpoints.down('sm')]: {
-        height: '100vh!important',
-        marginTop: '0!important',
-        width: 320,
-        zIndex: 9999
-      },
-      '& > ul': {
-        display: 'flex',
-        flexDirection: 'column',
-        '@supports (scrollbar-gutter: stable)': {
-          scrollbarGutter: 'stable',
-          paddingRight: 0,
-          overflow: 'hidden',
-        },
-        '&:hover': {
-          overflow: 'auto'
-        },
-        [theme.breakpoints.down('sm')]: {
-          paddingBottom: 120
-        }
-      }
-    },
-    listPadding: {
-      paddingTop: 0
-    },
-    listSubheaderRoot: {
-      background: 'white',
-      [theme.breakpoints.down('sm')]: {
-        padding: 0
-      }
-    },
-    listItemLink: {
-      fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
-      fontStyle: 'normal',
-      fontWeight: 'normal',
-      fontSize: '0.875rem',
-      lineHeight: '1rem',
-      color: '#000000',
-      width: '100%',
-      textOverflow: 'ellipsis',
-      overflow: 'hidden',
-      padding: '0.75rem 1rem',
-      whiteSpace: 'nowrap',
-      textDecoration: "none",
-      flex: '0 0 2.5rem',
-      display: 'block',
-      '&:hover': {
-        background: 'rgba(0, 0, 0, 0.04)'
-      },
-      '&:focus': {
-        outline: 'none',
-        background: 'rgba(0, 0, 0, 0.04)'
-      }
-    },
-    listItemLinkActive: {
-      background: 'rgba(0, 0, 0, 0.04)'
-    },
-    listItemIcon: {
-      transform: 'translateY(2px)',
-      marginRight: 2,
-      minWidth: 'auto',
-    },
-    loader: {
-      width: '100%',
-      height: '100%',
-      display: 'flex',
-      alignItems: 'center',
-      justifyContent: 'center'
-    }
-  })
-);
-
-type ChatsListProps = {
-  isOpen: boolean;
-  onCreateNewChat: () => void;
-  onClose: () => void;
-  isDemoOrg: boolean;
-  onLinkClick?: (targetThreadId: string) => void;
-} & HeaderButtonsProps
-
-export const ChatsList = (props: ChatsListProps) => {
-  const {
-    isOpen,
-    onCreateNewChat,
-    onClose,
-    withChatVisibilityButton,
-    onSettingsClick,
-    onLinkClick,
-    onConsoleClick
-  } = props;
-
-  const { chatsList, chatsListLoading: loading } = useAiBot();
-
-  const classes = useStyles(props);
-  const params = useParams<{ org?: string, threadId?: string }>();
-  const matches = useMediaQuery(theme.breakpoints.down('sm'));
-  const linkBuilder = (msgId: string) => {
-    if (params.org) {
-      return `/${params.org}/assistant/${msgId}`
-    } else {
-      return `/assistant/${msgId}`
-    }
-  }
-
-  const handleClick = (threadId: string) => {
-    if (onLinkClick) {
-      onLinkClick(threadId)
-      if (matches) {
-        onClose()
-      }
-    }
-  }
-
-  const handleCloseOnClickOutside = () => {
-    if (matches) {
-      onClose()
-    }
-  }
-
-  const loader = (
-    <Box className={classes.loader}>
-      <Spinner/>
-    </Box>
-  )
-
-  const list = (
-    <List
-      classes={{padding: classes.listPadding}}
-    >
-      <ListSubheader
-        classes={{root: classes.listSubheaderRoot}}
-      >
-        <HeaderButtons
-          onClose={onClose}
-          onCreateNewChat={onCreateNewChat}
-          isOpen={isOpen}
-          withChatVisibilityButton={withChatVisibilityButton}
-          onSettingsClick={onSettingsClick}
-          onConsoleClick={onConsoleClick}
-        />
-        <Divider/>
-      </ListSubheader>
-
-      {chatsList && chatsList.map((item) => {
-        const isActive = item.id === params.threadId
-        const link = linkBuilder(item.id)
-        return (
-          <ListItem
-            component={Link}
-            to={link}
-            key={item.id}
-            className={cn(classes.listItemLink, {[classes.listItemLinkActive]: isActive})}
-            id={item.id}
-            onClick={() => handleClick(item.id)}
-            autoFocus={isActive}
-          >
-            <ListItemIcon
-              className={classes.listItemIcon}
-              title={item.is_public ? 'This thread is public' : 'This thread is private'}>
-              {!item.is_public && <VisibilityOffIcon />}
-            </ListItemIcon>
-            {item.content}
-          </ListItem>
-        )
-      })
-      }
-    </List>
-  )
-
-  return (
-    <Drawer
-      variant={matches ? 'temporary' : 'persistent'}
-      anchor="right"
-      BackdropProps={{ invisible: true }}
-      elevation={1}
-      open={isOpen}
-      onClose={handleCloseOnClickOutside}
-      classes={{
-        paper: classes.drawerPaper
-      }}
-    >
-      {loading ? loader : list}
-    </Drawer>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/Command/Command.tsx b/ui/packages/platform/src/pages/Bot/Command/Command.tsx
deleted file mode 100644
index fb4aff53..00000000
--- a/ui/packages/platform/src/pages/Bot/Command/Command.tsx
+++ /dev/null
@@ -1,227 +0,0 @@
-import React, { useState, useRef, useEffect } from 'react'
-import { makeStyles } from '@material-ui/core'
-import SendRoundedIcon from '@material-ui/icons/SendRounded';
-import IconButton from "@material-ui/core/IconButton";
-import { TextField } from '@postgres.ai/shared/components/TextField'
-import { ReadyState } from "react-use-websocket";
-import { useLocation } from "react-router-dom";
-import {
-  checkIsSendCmd,
-  checkIsNewLineCmd,
-  addNewLine,
-  checkIsPrevMessageCmd,
-  checkIsNextMessageCmd,
-} from './utils'
-import { useBuffer } from './useBuffer'
-import { useCaret } from './useCaret'
-import { theme } from "@postgres.ai/shared/styles/theme";
-import { isMobileDevice } from "../../../utils/utils";
-import { useAiBot } from "../hooks";
-
-type Props = {
-  threadId?: string
-  orgId: number | null
-}
-
-
-const useStyles = makeStyles((theme) => (
-  {
-    root: {
-      display: 'flex',
-      alignItems: 'flex-end',
-      marginTop: '20px',
-      border: '1px solid rgba(0, 0, 0, 0.23)',
-      borderRadius: '8px',
-      '& .MuiOutlinedInput-root': {
-        '& fieldset': {
-          border: 'none',
-        },
-        '&:hover fieldset': {
-          border: 'none',
-        },
-        '&.Mui-focused fieldset': {
-          border: 'none',
-        }
-      }
-    },
-    field: {
-      margin: '0 8px 0 0',
-      flex: '1 1 100%',
-      fontSize: '0.875rem',
-    },
-    fieldInput: {
-      fontSize: '0.875rem',
-      lineHeight: 'normal',
-      height: 'auto',
-      padding: '12px',
-      [theme.breakpoints.down('sm')]: {
-        fontSize: '1rem'
-      }
-    },
-    iconButton: {
-      height: 40,
-      width: 40,
-      fontSize: 24,
-      transition: '.2s ease',
-      '&:hover': {
-        color: '#000'
-      }
-    },
-    button: {
-      flex: '0 0 auto',
-      height: '40px',
-    },
-  })
-)
-
-export const Command = React.memo((props: Props) => {
-  const { threadId, orgId } = props
-
-  const classes = useStyles()
-  const isMobile = isMobileDevice();
-
-  const {
-    error,
-    wsReadyState,
-    wsLoading,
-    loading,
-    sendMessage,
-    chatVisibility,
-    isStreamingInProcess
-  } = useAiBot();
-
-  const sendDisabled = error !== null || loading || wsLoading || wsReadyState !== ReadyState.OPEN || isStreamingInProcess;
-
-  // Handle value.
-  const [value, setValue] = useState('')
-
-  // Input DOM Element reference.
-  const inputRef = useRef<HTMLTextAreaElement | HTMLInputElement>()
-
-  // Messages buffer.
-  const buffer = useBuffer()
-
-  // Input caret.
-  const caret = useCaret(inputRef)
-
-  let location = useLocation<{skipReloading?: boolean}>();
-
-  const onSend = async (message: string) => {
-    await sendMessage({
-      content: message,
-      thread_id: threadId || null,
-      org_id: orgId,
-      is_public: chatVisibility === 'public'
-    })
-  }
-
-  const triggerSend = async () => {
-    if (!value.trim().length || sendDisabled) return
-
-    await onSend(value)
-    buffer.addNew()
-    setValue(buffer.getCurrent())
-  }
-
-  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-    setValue(e.target.value)
-    buffer.switchToLast()
-    buffer.setToCurrent(e.target.value)
-  }
-
-  const handleBlur = () => {
-    if ((window.innerWidth < theme.breakpoints.values.sm) && isMobile) {
-      window.scrollTo({
-        top: 0,
-        behavior: 'smooth'
-      })
-      const footer: HTMLElement | null = document.querySelector("footer")
-      if (footer) footer.style.display = 'flex';
-    }
-  }
-
-  const handleFocus = () => {
-    if ((window.innerWidth < theme.breakpoints.values.sm)  && isMobile) {
-      const footer: HTMLElement | null = document.querySelector("footer")
-      if (footer) footer.style.display = 'none';
-    }
-  }
-
-  const handleKeyDown = async (e: React.KeyboardEvent) => {
-    if (!inputRef.current) return
-
-    // Trigger to send.
-    if (checkIsSendCmd(e.nativeEvent)) {
-      e.preventDefault()
-      await triggerSend()
-      return
-    }
-
-    // Trigger line break.
-    if (checkIsNewLineCmd(e.nativeEvent)) {
-      e.preventDefault()
-
-      const content = addNewLine(value, inputRef.current)
-
-      setValue(content.value)
-      caret.setPosition(content.caretPosition)
-      return
-    }
-
-    // Trigger to use prev message.
-    if (checkIsPrevMessageCmd(e.nativeEvent, inputRef.current)) {
-      e.preventDefault()
-
-      const prevValue = buffer.switchPrev()
-      setValue(prevValue)
-      return
-    }
-
-    // Trigger to use next message.
-    if (checkIsNextMessageCmd(e.nativeEvent, inputRef.current)) {
-      e.preventDefault()
-
-      const nextValue = buffer.switchNext()
-      setValue(nextValue)
-      return
-    }
-
-    // Skip other keyboard events to fill input.
-  }
-
-  // Autofocus and clear on thread changed
-  useEffect(() => {
-    if (!inputRef.current) return
-    if (window.innerWidth > theme.breakpoints.values.md) inputRef.current.focus()
-    if (!location.state?.skipReloading) setValue('')
-  }, [threadId, loading]);
-
-  return (
-    <div className={classes.root}>
-      <TextField
-        autoFocus={window.innerWidth > theme.breakpoints.values.sm}
-        multiline
-        className={classes.field}
-        onKeyDown={handleKeyDown}
-        onChange={handleChange}
-        onBlur={handleBlur}
-        onFocus={handleFocus}
-        InputProps={{
-          inputRef,
-          classes: {
-            input: classes.fieldInput,
-          },
-        }}
-        value={value}
-        placeholder="Message..."
-      />
-      <IconButton
-        onClick={triggerSend}
-        className={classes.iconButton}
-        disabled={sendDisabled || value.length === 0}
-      >
-        <SendRoundedIcon />
-      </IconButton>
-    </div>
-  )
-})
diff --git a/ui/packages/platform/src/pages/Bot/Command/useBuffer.ts b/ui/packages/platform/src/pages/Bot/Command/useBuffer.ts
deleted file mode 100644
index 0368bc44..00000000
--- a/ui/packages/platform/src/pages/Bot/Command/useBuffer.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { useRef } from 'react'
-
-type Buffer = {
-  values: string[]
-  position: number
-}
-
-const NEW_EMPTY_VALUE = ''
-
-const INITIAL_BUFFER: Buffer = {
-  values: [NEW_EMPTY_VALUE],
-  position: 0,
-}
-
-export const useBuffer = () => {
-  const { current: buffer } = useRef<Buffer>(INITIAL_BUFFER)
-
-  const getCurrent = () => buffer.values[buffer.position]
-
-  const setToCurrent = (value: string) => buffer.values[buffer.position] = value
-
-  const switchNext = () => {
-    const newPosition = buffer.position + 1
-    if (newPosition in buffer.values) buffer.position = newPosition
-    return getCurrent()
-  }
-
-  const switchPrev = () => {
-    const newPosition = buffer.position - 1
-    if (newPosition in buffer.values) buffer.position = newPosition
-    return getCurrent()
-  }
-
-  const switchToLast = () => {
-    const lastIndex = buffer.values.length - 1
-    buffer.position = lastIndex
-    return getCurrent()
-  }
-
-  const addNew = () => {
-    buffer.values.push(NEW_EMPTY_VALUE)
-    return switchToLast()
-  }
-
-  return {
-    switchNext,
-    switchPrev,
-    switchToLast,
-    addNew,
-    getCurrent,
-    setToCurrent,
-  }
-}
diff --git a/ui/packages/platform/src/pages/Bot/Command/useCaret.ts b/ui/packages/platform/src/pages/Bot/Command/useCaret.ts
deleted file mode 100644
index 8de590a5..00000000
--- a/ui/packages/platform/src/pages/Bot/Command/useCaret.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useState, useEffect, MutableRefObject } from 'react'
-
-export const useCaret = (
-  elementRef: MutableRefObject<HTMLInputElement | HTMLTextAreaElement | undefined>,
-) => {
-  // Keep caret position after making new line, but only after react update.
-  const [nextPosition, setNextPosition] = useState<number | null>(null)
-
-  useEffect(() => {
-    if (nextPosition === null) return
-    if (!elementRef.current) return
-
-    elementRef.current.selectionStart = nextPosition
-    elementRef.current.selectionEnd = nextPosition
-
-    setNextPosition(null)
-  }, [elementRef, nextPosition])
-
-  return {
-    setPosition: setNextPosition,
-  }
-}
diff --git a/ui/packages/platform/src/pages/Bot/Command/utils.ts b/ui/packages/platform/src/pages/Bot/Command/utils.ts
deleted file mode 100644
index ba14b587..00000000
--- a/ui/packages/platform/src/pages/Bot/Command/utils.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { isMobileDevice } from "../../../utils/utils";
-
-export const checkIsSendCmd = (e: KeyboardEvent): boolean => {
-  if (isMobileDevice()) {
-    return false; // On mobile devices, Enter should not send.
-  }
-  return e.code === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey;
-};
-
-export const checkIsNewLineCmd = (e: KeyboardEvent): boolean => {
-  if (isMobileDevice()) {
-    return e.code === 'Enter'; // On mobile devices, Enter should create a new line.
-  }
-  return e.code === 'Enter' && (e.shiftKey || e.ctrlKey || e.metaKey);
-};
-
-export const addNewLine = (
-  value: string,
-  element: HTMLInputElement | HTMLTextAreaElement,
-) => {
-  const NEW_LINE_STR = '\n'
-
-  const firstLineLength = element.selectionStart ?? value.length
-  const secondLineLength = element.selectionEnd ?? value.length
-
-  const firstLine = value.substring(0, firstLineLength)
-  const secondLine = value.substring(secondLineLength)
-
-  return {
-    value: `${firstLine}${NEW_LINE_STR}${secondLine}`,
-    caretPosition: firstLineLength + NEW_LINE_STR.length,
-  }
-}
-
-export const checkIsPrevMessageCmd = (
-  e: KeyboardEvent,
-  element: HTMLInputElement | HTMLTextAreaElement,
-) => {
-  const isRightKey =
-    e.code === 'ArrowUp' && !e.ctrlKey && !e.metaKey && !e.shiftKey
-
-  // Use prev message only if the caret is in the start of the input.
-  const targetCaretPosition = 0
-
-  const isRightCaretPosition =
-    element.selectionStart === targetCaretPosition &&
-    element.selectionEnd === targetCaretPosition
-
-  return isRightKey && isRightCaretPosition
-}
-
-export const checkIsNextMessageCmd = (
-  e: KeyboardEvent,
-  element: HTMLInputElement | HTMLTextAreaElement,
-) => {
-  const isRightKey =
-    e.code === 'ArrowDown' && !e.ctrlKey && !e.metaKey && !e.shiftKey
-
-  // Use next message only if the caret is in the end of the input.
-  const targetCaretPosition = element.value.length
-
-  const isRightCaretPosition =
-    element.selectionStart === targetCaretPosition &&
-    element.selectionEnd === targetCaretPosition
-
-  return isRightKey && isRightCaretPosition
-}
diff --git a/ui/packages/platform/src/pages/Bot/DebugConsole/DebugConsole.tsx b/ui/packages/platform/src/pages/Bot/DebugConsole/DebugConsole.tsx
deleted file mode 100644
index 21a3bb57..00000000
--- a/ui/packages/platform/src/pages/Bot/DebugConsole/DebugConsole.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React, { useEffect, useRef } from "react";
-import Dialog from "@material-ui/core/Dialog";
-import { DialogContent, DialogTitle, makeStyles } from "@material-ui/core";
-import { useAiBot } from "../hooks";
-import IconButton from "@material-ui/core/IconButton";
-import CloseIcon from "@material-ui/icons/Close";
-import { DebugLogs } from "../DebugLogs/DebugLogs";
-import { createMessageFragment } from "../utils";
-
-const useStyles = makeStyles(
-  (theme) => ({
-    dialogStyle: {
-      top: '5%!important',
-      right: '10%!important',
-      left: 'unset!important',
-      height: '80vh',
-      width: '80vw',
-      [theme.breakpoints.down('sm')]: {
-        right: 'unset!important',
-        top: '0!important',
-        height: '100vh',
-        width: '100vw',
-      }
-    },
-    paper: {
-      width: '80vw',
-      height: '80vh',
-      opacity: '.5',
-      transition: '.2s ease',
-      '&:hover': {
-        opacity: 1
-      },
-      [theme.breakpoints.down('sm')]: {
-        opacity: 1,
-        width: '100vw',
-        height: '100vh',
-        margin: '0'
-      }
-    },
-    dialogTitle: {
-      padding: '0.5rem',
-      '& h2': {
-        fontSize: '1rem',
-        display: 'flex',
-        justifyContent: 'space-between',
-        alignItems: 'center'
-      }
-    },
-    dialogContent: {
-      padding: 0,
-      margin: 0,
-    }
-  }))
-
-type DebugConsoleProps = {
-  isOpen: boolean
-  onClose: () => void
-  threadId?: string
-}
-
-export const DebugConsole = (props: DebugConsoleProps) => {
-  const { isOpen, onClose, threadId } = props;
-  const { debugMessages, debugMessagesLoading } = useAiBot();
-  const classes = useStyles();
-  const containerRef = useRef<HTMLDivElement>(null);
-
-  useEffect(() => {
-    if (!containerRef.current || !debugMessages?.length || debugMessagesLoading || !isOpen) return;
-
-    let code = containerRef.current.getElementsByTagName('code')?.[0];
-    if (!code) {
-      code = document.createElement('code');
-      containerRef.current.appendChild(code);
-    }
-
-    const fragment = createMessageFragment(
-      code.hasChildNodes() ? [debugMessages[debugMessages.length - 1]] : debugMessages
-    );
-    code.appendChild(fragment);
-
-  }, [debugMessages, isOpen, debugMessagesLoading]);
-
-  return (
-    <Dialog
-      open={isOpen}
-      disableEnforceFocus
-      hideBackdrop
-      className={classes.dialogStyle}
-      classes={{paper: classes.paper}}
-      scroll="paper"
-      fullWidth
-      maxWidth="xl"
-    >
-      <DialogTitle className={classes.dialogTitle}>
-        Debug console
-        <IconButton
-          onClick={onClose}
-        >
-          <CloseIcon />
-        </IconButton>
-      </DialogTitle>
-      <DialogContent
-        classes={{root: classes.dialogContent}}
-        ref={containerRef}
-      >
-        <DebugLogs
-          isLoading={debugMessagesLoading}
-          isEmpty={debugMessages?.length === 0}
-          id={threadId!}
-        />
-      </DialogContent>
-    </Dialog>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/DebugDialog/DebugDialog.tsx b/ui/packages/platform/src/pages/Bot/DebugDialog/DebugDialog.tsx
deleted file mode 100644
index fb64b723..00000000
--- a/ui/packages/platform/src/pages/Bot/DebugDialog/DebugDialog.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React, { useEffect, useRef, useState } from 'react'
-import Dialog from "@material-ui/core/Dialog";
-import DialogTitle from '@material-ui/core/DialogTitle';
-import { DialogContent, IconButton, makeStyles } from "@material-ui/core";
-import { icons } from "@postgres.ai/shared/styles/icons";
-import { DebugMessage } from "../../../types/api/entities/bot";
-import { getDebugMessages } from "../../../api/bot/getDebugMessages";
-import { DebugLogs } from "../DebugLogs/DebugLogs";
-import { createMessageFragment } from "../utils";
-
-type DebugDialogProps = {
-  isOpen: boolean;
-  onClose: () => void;
-  messageId: string
-}
-
-const useStyles = makeStyles(
-  (theme) => ({
-    dialogTitle: {
-      fontSize: '1rem',
-      paddingBottom: '8px',
-      '& > *': {
-        fontSize: 'inherit'
-      }
-    },
-    closeButton: {
-      position: 'absolute',
-      right: theme.spacing(1),
-      top: theme.spacing(1),
-      color: theme.palette.grey[500],
-    },
-    dialogContent: {
-      backgroundColor: 'rgb(250, 250, 250)',
-      padding: '0.5rem 0',
-      '& pre': {
-        whiteSpace: 'pre-wrap!important'
-      }
-    }
-  }))
-
-export const DebugDialog = (props: DebugDialogProps) => {
-  const {isOpen, onClose, messageId} = props;
-  const classes = useStyles()
-
-  const [debugLoading, setDebugLoading] = useState(false);
-  const debugMessages = useRef<DebugMessage[] | null>(null)
-
-  const generateMessages = (messages: DebugMessage[]) => {
-    const container = document.getElementById(`logs-container-${messageId}`);
-    if (container) {
-      let code = container.getElementsByTagName('code')?.[0];
-      if (!code) {
-        code = document.createElement('code');
-        container.appendChild(code);
-      }
-
-      const fragment = createMessageFragment(messages);
-      code.appendChild(fragment);
-    }
-  };
-
-  const getDebugMessagesForMessage = async () => {
-    setDebugLoading(true)
-    if (messageId) {
-      const { response} = await getDebugMessages({ message_id: messageId })
-      if (response) {
-        debugMessages.current = response;
-        generateMessages(response)
-      }
-    }
-  }
-
-  useEffect(() => {
-    if (isOpen && !debugMessages.current) {
-      getDebugMessagesForMessage()
-        .then(() => setDebugLoading(false))
-    } else if (isOpen && debugMessages.current && debugMessages.current?.length > 0) {
-      setTimeout(() => generateMessages(debugMessages.current!), 0)
-    }
-  }, [isOpen]);
-
-
-  return (
-    <Dialog
-      open={isOpen}
-      onClose={onClose}
-      fullWidth
-      maxWidth="lg"
-    >
-      <DialogTitle classes={{root: classes.dialogTitle}}>
-        Debug
-        <IconButton
-          aria-label="close"
-          className={classes.closeButton}
-          onClick={onClose}
-        >
-          {icons.closeIcon}
-        </IconButton>
-      </DialogTitle>
-      <DialogContent
-        classes={{root: classes.dialogContent}}
-      >
-        <DebugLogs
-          id={messageId}
-          isEmpty={!debugMessages || debugMessages.current?.length === 0}
-          isLoading={debugLoading}
-        />
-      </DialogContent>
-    </Dialog>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/DebugLogs/DebugLogs.tsx b/ui/packages/platform/src/pages/Bot/DebugLogs/DebugLogs.tsx
deleted file mode 100644
index d556235a..00000000
--- a/ui/packages/platform/src/pages/Bot/DebugLogs/DebugLogs.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from "react";
-import { SyntaxHighlight } from "@postgres.ai/shared/components/SyntaxHighlight";
-
-type DebugLogsProps = {
-  isLoading: boolean
-  isEmpty: boolean
-  id: string
-}
-
-export const DebugLogs = (props: DebugLogsProps) => {
-  const { isLoading, isEmpty, id } = props;
-  return (
-    <SyntaxHighlight
-      id={`logs-container-${id}`}
-      style={{
-        overflowY: 'auto',
-        height: '100%',
-        overflowX: 'hidden',
-        backgroundColor: 'rgb(250, 250, 250)',
-        margin: 0,
-      }}
-      content={
-        isLoading ?
-          'Loading...'
-          : isEmpty
-            ? 'No debug information available.'
-            : ''
-      }
-    />
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HeaderButtons/HeaderButtons.tsx b/ui/packages/platform/src/pages/Bot/HeaderButtons/HeaderButtons.tsx
deleted file mode 100644
index 91de9438..00000000
--- a/ui/packages/platform/src/pages/Bot/HeaderButtons/HeaderButtons.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from "react";
-import { Button, makeStyles, useMediaQuery } from "@material-ui/core";
-import IconButton from "@material-ui/core/IconButton";
-import NavigateBeforeIcon from "@material-ui/icons/NavigateBefore";
-import NavigateNextIcon from '@material-ui/icons/NavigateNext';
-import AddCircleOutlineIcon from "@material-ui/icons/AddCircleOutline";
-import Box from "@mui/material/Box";
-import { theme } from "@postgres.ai/shared/styles/theme";
-import { SettingsPanel, SettingsPanelProps } from "../SettingsPanel/SettingsPanel";
-
-
-export type HeaderButtonsProps = {
-  isOpen: boolean;
-  onClose: () => void;
-  onCreateNewChat: () => void;
-  withChatVisibilityButton: boolean;
-  onSettingsClick: SettingsPanelProps["onSettingsClick"];
-  onConsoleClick: SettingsPanelProps["onConsoleClick"];
-}
-
-const useStyles = makeStyles((theme) => ({
-  container: {
-    display: 'flex',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    padding: '8px 5px',
-    flex: 1,
-    [theme.breakpoints.down('sm')]: {
-      justifyContent: 'flex-end',
-    }
-  },
-  hideChatButton: {
-    width: '2rem',
-    height: '2rem'
-  },
-  hideChatButtonIcon: {
-    width: '2rem',
-    height: '2rem',
-    fill: '#000'
-  },
-  createNewChatButton: {
-    [theme.breakpoints.down('sm')]: {
-      border: 'none',
-      minWidth: '2rem',
-      height: '2rem',
-      padding: 0,
-      marginRight: '0.25rem',
-      '& .MuiButton-startIcon': {
-        margin: 0
-      }
-    }
-  }
-}))
-
-export const HeaderButtons = (props: HeaderButtonsProps) => {
-  const {
-    onClose,
-    onCreateNewChat,
-    isOpen,
-    onSettingsClick,
-    withChatVisibilityButton,
-    onConsoleClick
-  } = props;
-  const matches = useMediaQuery(theme.breakpoints.down('sm'));
-  const classes = useStyles();
-
-
-  return (
-    <Box className={classes.container}>
-      {
-        withChatVisibilityButton &&
-          <SettingsPanel
-            onSettingsClick={onSettingsClick}
-            onConsoleClick={onConsoleClick}
-          />
-      }
-      <Button
-        variant='outlined'
-        aria-label="Create new chat"
-        startIcon={<AddCircleOutlineIcon />}
-        onClick={onCreateNewChat}
-        className={classes.createNewChatButton}
-      >
-        {!matches && 'New Chat'}
-      </Button>
-      <IconButton
-        onClick={onClose}
-        aria-label="Hide chats list"
-        title={isOpen ? "Hide chats list" : "Open chats list"}
-        classes={{root: classes.hideChatButton}}
-      >
-        {isOpen
-          ? <NavigateNextIcon className={classes.hideChatButtonIcon} />
-          : <NavigateBeforeIcon className={classes.hideChatButtonIcon} />
-        }
-      </IconButton>
-
-    </Box>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HintCards/ArrowGrowthIcon/ArrowGrowthIcon.tsx b/ui/packages/platform/src/pages/Bot/HintCards/ArrowGrowthIcon/ArrowGrowthIcon.tsx
deleted file mode 100644
index 6fcafec1..00000000
--- a/ui/packages/platform/src/pages/Bot/HintCards/ArrowGrowthIcon/ArrowGrowthIcon.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { SVGProps } from 'react'
-import React from 'react'
-
-export function ArrowGrowthIcon() {
-  return (
-    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 140 140">
-      <path
-        stroke="#AF52DE"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="4"
-        d="M134.167 35 78.7507 90.4167 49.584 61.25 5.83398 105M134.167 35H99.1673m34.9997 0v35"
-      />
-    </svg>)
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HintCards/CommonTypeIcon/CommonTypeIcon.tsx b/ui/packages/platform/src/pages/Bot/HintCards/CommonTypeIcon/CommonTypeIcon.tsx
deleted file mode 100644
index 061f9fdb..00000000
--- a/ui/packages/platform/src/pages/Bot/HintCards/CommonTypeIcon/CommonTypeIcon.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-
-export const CommonTypeIcon = () => {
-  return (
-    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 140 140">
-      <path
-        stroke="#AF52DE"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="4"
-        d="M69.9993 11.667v23.3333m0 69.9997v23.334M28.7577 28.7587 45.266 45.267m49.4667 49.4667 16.5083 16.5083M11.666 70.0003h23.3333m69.9997 0h23.334M28.7577 111.242 45.266 94.7337M94.7327 45.267l16.5083-16.5083" />
-    </svg>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HintCards/HintCard/HintCard.tsx b/ui/packages/platform/src/pages/Bot/HintCards/HintCard/HintCard.tsx
deleted file mode 100644
index 2af4818b..00000000
--- a/ui/packages/platform/src/pages/Bot/HintCards/HintCard/HintCard.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react'
-import { Hint } from 'pages/Bot/hints'
-import { matchHintTypeAndIcon } from "../../utils";
-import { makeStyles } from "@material-ui/core";
-import { useAiBot } from "../../hooks";
-
-const useStyles = makeStyles((theme) => ({
-  container: {
-    backgroundColor: 'transparent',
-    border: '1px solid rgba(0, 0, 0, 0.25)',
-    borderRadius: '0.5rem',
-    display: 'flex',
-    flexDirection: 'column',
-    alignItems: 'flex-start',
-    cursor: 'pointer',
-    width: '11rem',
-    height: '6rem',
-    padding: '0.5rem',
-    color: 'black',
-    textAlign: 'left',
-    fontSize: '0.938rem',
-    transition: '0.2s ease-in',
-
-    '& svg': {
-      width: '22px',
-      height: '22px',
-      marginBottom: '0.5rem',
-      '& path': {
-        stroke: 'black',
-      },
-      [theme.breakpoints.down('sm')]: {
-        width: '16px',
-        height: '16px'
-      }
-    },
-
-    '&:hover, &:focus-visible': {
-      border: '1px solid rgba(0, 0, 0, 0.8)',
-    },
-    [theme.breakpoints.down(1024)]: {
-      flex: '1 1 45%',
-    },
-    [theme.breakpoints.down(480)]: {
-      margin: '0 0.5rem',
-      fontSize: '0.813rem',
-      height: 'auto',
-    },
-  },
-}));
-
-export const HintCard = (props: Hint & {orgId: number}) => {
-  const { prompt, hint, type, orgId } = props;
-  const { sendMessage, chatVisibility } = useAiBot();
-
-  const classes = useStyles();
-
-  const handleSendMessage = async () => {
-    await sendMessage({
-      content: prompt,
-      org_id: orgId,
-      is_public: chatVisibility === 'public'
-    })
-  }
-
-  return (
-    <button
-      onClick={handleSendMessage}
-      className={classes.container}
-    >
-      {React.createElement(matchHintTypeAndIcon(type))}
-      <span>
-        {hint}
-      </span>
-    </button>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HintCards/HintCards.tsx b/ui/packages/platform/src/pages/Bot/HintCards/HintCards.tsx
deleted file mode 100644
index 650e522e..00000000
--- a/ui/packages/platform/src/pages/Bot/HintCards/HintCards.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react'
-import { hints } from '../hints'
-import { HintCard } from "./HintCard/HintCard";
-import { makeStyles } from "@material-ui/core";
-
-const useStyles = makeStyles((theme) => ({
-  container: {
-    display: 'flex',
-    flexWrap: 'wrap',
-    justifyContent: 'space-around',
-    gap: '.5rem',
-    marginTop: '2rem',
-    [theme.breakpoints.down(1200)]: {
-      justifyContent: 'center',
-    },
-    [theme.breakpoints.down(480)]: {
-      marginBottom: '1.5rem',
-    },
-    [theme.breakpoints.down(760)]: {
-      '& > *:nth-child(n+3)': {
-        display: 'none',
-      },
-    },
-  },
-}));
-
-export const HintCards = React.memo(({orgId}: {orgId: number}) => {
-  const classes = useStyles();
-  return (
-    <div className={classes.container}>
-      {
-        hints.map((hint) => <HintCard key={hint.hint} {...hint} orgId={orgId} />)
-      }
-    </div>
-  )
-})
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HintCards/TableIcon/TableIcon.tsx b/ui/packages/platform/src/pages/Bot/HintCards/TableIcon/TableIcon.tsx
deleted file mode 100644
index a4f2fb0e..00000000
--- a/ui/packages/platform/src/pages/Bot/HintCards/TableIcon/TableIcon.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react'
-
-export const TableIcon = () => {
-  return (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      fill="none"
-      viewBox="0 0 180 180"
-    >
-      <path
-        stroke="#AF52DE"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="4"
-        d="M67.5 22.5h-30c-3.9782 0-7.7936 1.5804-10.6066 4.3934C24.0804 29.7064 22.5 33.5218 22.5 37.5v30m45-45h75c3.978 0 7.794 1.5804 10.607 4.3934 2.813 2.813 4.393 6.6284 4.393 10.6066v30m-90-45v135m-45-90v75c0 3.978 1.5804 7.794 4.3934 10.607 2.813 2.813 6.6284 4.393 10.6066 4.393h30m-45-90h135m0 0v75c0 3.978-1.58 7.794-4.393 10.607-2.813 2.813-6.629 4.393-10.607 4.393h-75"
-      />
-    </svg>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/HintCards/WrenchIcon/WrenchIcon.tsx b/ui/packages/platform/src/pages/Bot/HintCards/WrenchIcon/WrenchIcon.tsx
deleted file mode 100644
index ad2fa49d..00000000
--- a/ui/packages/platform/src/pages/Bot/HintCards/WrenchIcon/WrenchIcon.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-
-export const WrenchIcon = () => {
-  return (
-    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 140 140">
-      <path
-        stroke="#AF52DE"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="4"
-        d="M85.7499 36.7501c-1.0688 1.0904-1.6675 2.5564-1.6675 4.0833 0 1.5269.5987 2.9929 1.6675 4.0834l9.3333 9.3333c1.0904 1.0688 2.5565 1.6675 4.0834 1.6675 1.5264 0 2.9924-.5987 4.0834-1.6675l21.992-21.9917c2.933 6.482 3.821 13.7039 2.546 20.7034-1.276 6.9995-4.654 13.444-9.685 18.4749-5.031 5.0309-11.475 8.4091-18.4748 9.6844-6.9994 1.2753-14.2214.3872-20.7033-2.546L38.6166 118.883c-2.3207 2.321-5.4682 3.625-8.75 3.625-3.2819 0-6.4294-1.304-8.75-3.625-2.3207-2.32-3.6244-5.468-3.6244-8.75 0-3.281 1.3037-6.429 3.6244-8.75l40.3083-40.3079c-2.9332-6.4819-3.8213-13.7039-2.546-20.7034 1.2753-6.9994 4.6535-13.444 9.6844-18.4749 5.0308-5.0309 11.4754-8.4091 18.4749-9.6844 6.9995-1.2753 14.2218-.3872 20.7038 2.546L85.7499 36.7501Z" />
-    </svg>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/Messages/Message/CodeBlock.tsx b/ui/packages/platform/src/pages/Bot/Messages/Message/CodeBlock.tsx
deleted file mode 100644
index ae7a21c3..00000000
--- a/ui/packages/platform/src/pages/Bot/Messages/Message/CodeBlock.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import React, { memo, useState } from 'react';
-import { Accordion, AccordionDetails, AccordionSummary, Typography, makeStyles, Button } from '@material-ui/core';
-import { Prism as SyntaxHighlighter, SyntaxHighlighterProps } from 'react-syntax-highlighter'
-import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
-import languages from 'react-syntax-highlighter/dist/esm/languages/prism/supported-languages';
-import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
-import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
-import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline';
-import CodeIcon from '@material-ui/icons/Code';
-import { formatLanguageName } from "../../utils";
-
-const useStyles = makeStyles((theme) => ({
-  container: {
-    marginTop: '.5em',
-    width: '100%'
-  },
-  header: {
-    background: 'rgb(240, 240, 240)',
-    borderTopLeftRadius: 8,
-    borderTopRightRadius: 8,
-    padding: '.2rem 1rem',
-    display: 'flex'
-  },
-  languageName: {
-    fontSize: '0.813rem',
-    color: theme.palette.text.primary
-  },
-  copyButton: {
-    marginLeft: 'auto',
-    color: theme.palette.text.primary,
-    padding: '0',
-    minHeight: 'auto',
-    fontSize: '0.813rem',
-    border: 0,
-    '&:hover': {
-      backgroundColor: 'transparent'
-    }
-  },
-  copyButtonIcon: {
-    width: '0.813rem',
-  },
-  summary: {
-    color: 'rgb(166, 38, 164)',
-    textDecoration: 'underline',
-    textDecorationStyle: 'dotted',
-    cursor: 'pointer',
-    backgroundColor: 'transparent',
-    boxShadow: 'none',
-    display: 'inline-flex',
-    minHeight: '2rem!important',
-    padding: 0,
-    '&:hover': {
-      textDecoration: 'none'
-    }
-  },
-  summaryText: {
-    display: 'inline-flex',
-    alignItems: 'center'
-  },
-  summaryTextIcon: {
-    marginRight: 8,
-    fontSize: '1rem'
-  },
-  details: {
-    padding: 0,
-    backgroundColor: 'transparent'
-  },
-  accordion: {
-    boxShadow: 'none',
-    backgroundColor: 'transparent',
-  },
-  pre: {
-    width: '100%',
-    marginTop: '0!important',
-
-  }
-}));
-
-type CodeBlockProps = { value: string, language?: string | null };
-
-export const CodeBlock = memo(({ value, language }: CodeBlockProps) => {
-  const classes = useStyles();
-  const [expanded, setExpanded] = useState(false);
-  const [copied, setCopied] = useState(false);
-
-  const codeLines = value.split('\n');
-  const handleToggle = () => setExpanded(!expanded);
-
-
-  const isValidLanguage = language && languages.includes(language);
-
-  const syntaxHighlighterProps: SyntaxHighlighterProps = {
-    showLineNumbers: true,
-    language: language || 'sql',
-    style: oneLight,
-    className: classes.pre,
-    children: value
-  }
-
-  const handleCopy = () => {
-    if ('clipboard' in navigator) {
-      navigator.clipboard.writeText(value).then(() => {
-        setCopied(true);
-        setTimeout(() => setCopied(false), 2000);
-      });
-    }
-  };
-  
-  const header = (
-    <div className={classes.header}>
-      {isValidLanguage && <Typography className={classes.languageName}>{language}</Typography>}
-      <Button
-        size="small"
-        variant="outlined"
-        className={classes.copyButton}
-        onClick={handleCopy}
-        startIcon={copied
-          ? <CheckCircleOutlineIcon className={classes.copyButtonIcon} />
-          : <FileCopyOutlinedIcon className={classes.copyButtonIcon} />
-        }
-      >
-        {copied ? 'Copied' : 'Copy'}
-      </Button>
-    </div>
-  )
-
-  if (codeLines.length > 20) {
-    return (
-      <Accordion expanded={expanded} onChange={handleToggle} className={classes.accordion}>
-        <AccordionSummary expandIcon={<ExpandMoreIcon />} className={classes.summary}>
-          <Typography
-            className={classes.summaryText}
-          >
-            <CodeIcon className={classes.summaryTextIcon} />
-            {expanded ? 'Hide' : 'Show'}{language ? ` ${formatLanguageName(language)}` : ''} code block ({codeLines.length} LOC)
-          </Typography>
-        </AccordionSummary>
-        <AccordionDetails className={classes.details}>
-          <div className={classes.container}>
-            {header}
-            <SyntaxHighlighter
-              {...syntaxHighlighterProps}
-              children={value}
-            />
-          </div>
-        </AccordionDetails>
-      </Accordion>
-    );
-  }
-
-  return (
-    <div className={classes.container}>
-      {header}
-      <SyntaxHighlighter
-        {...syntaxHighlighterProps}
-        children={value}
-      />
-    </div>
-  );
-})
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/Messages/Message/MermaidDiagram.tsx b/ui/packages/platform/src/pages/Bot/Messages/Message/MermaidDiagram.tsx
deleted file mode 100644
index a3100717..00000000
--- a/ui/packages/platform/src/pages/Bot/Messages/Message/MermaidDiagram.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import mermaid from 'mermaid';
-import { makeStyles } from "@material-ui/core";
-import { MermaidDiagramControls } from "./MermaidDiagramControls";
-import cn from "classnames";
-
-type MermaidDiagramProps = {
-  chart: string
-}
-
-type DiagramPosition = {
-  x: number,
-  y: number
-}
-
-type DiagramState = {
-  scale: number,
-  position: DiagramPosition,
-  startPosition: DiagramPosition,
-  dragging: boolean
-}
-
-const useStyles = makeStyles(
-  (theme) => ({
-    container: {
-      position: 'relative',
-      width: '100%',
-      overflow: 'hidden'
-    },
-    mermaid: {
-      [theme.breakpoints.up('sm')]: {
-        display: 'flex',
-        justifyContent: 'center',
-      }
-    },
-  }))
-
-mermaid.initialize({ startOnLoad: true, er: {  useMaxWidth: false } });
-
-export const MermaidDiagram = React.memo((props: MermaidDiagramProps) => {
-  const { chart } = props;
-
-  const classes = useStyles();
-
-  // Consolidated state management
-  const [diagramState, setDiagramState] = useState<DiagramState>({
-    scale: 1,
-    position: { x: 0, y: 0 },
-    dragging: false,
-    startPosition: { x: 0, y: 0 },
-  });
-
-  const [isDiagramValid, setDiagramValid] = useState<boolean | null>(null);
-  const [diagramError, setDiagramError] = useState<string | null>(null)
-
-  const diagramRef = useRef<HTMLDivElement>(null);
-
-  useEffect(() => {
-    let isMounted = true;
-    if (isDiagramValid === null || chart) {
-      mermaid.parse(chart)
-        .then(() => {
-          if (isMounted) {
-            setDiagramValid(true);
-            mermaid.contentLoaded();
-          }
-        })
-        .catch((e) => {
-          if (isMounted) {
-            setDiagramValid(false);
-            setDiagramError(e.message)
-            console.error('Diagram contains errors:', e.message);
-          }
-        });
-    }
-
-    return () => {
-      isMounted = false;
-    };
-  }, [chart, isDiagramValid]);
-
-  const handleZoomIn = useCallback(() => {
-    setDiagramState((prev) => ({
-      ...prev,
-      scale: Math.min(prev.scale + 0.1, 2),
-    }));
-  }, []);
-
-  const handleZoomOut = useCallback(() => {
-    setDiagramState((prev) => ({
-      ...prev,
-      scale: Math.max(prev.scale - 0.1, 0.8),
-    }));
-  }, []);
-
-  const handleMouseDown = useCallback((event: React.MouseEvent) => {
-    setDiagramState((prev) => ({
-      ...prev,
-      dragging: true,
-      startPosition: { x: event.clientX - prev.position.x, y: event.clientY - prev.position.y },
-    }));
-  }, []);
-
-  const handleMouseMove = useCallback((event: React.MouseEvent) => {
-    if (diagramState.dragging) {
-      setDiagramState((prev) => ({
-        ...prev,
-        position: { x: event.clientX - prev.startPosition.x, y: event.clientY - prev.startPosition.y },
-      }));
-    }
-  }, [diagramState.dragging]);
-
-  const handleMouseUp = useCallback(() => {
-    setDiagramState((prev) => ({ ...prev, dragging: false }));
-  }, []);
-
-  const handleTouchStart = useCallback((event: React.TouchEvent) => {
-    const touch = event.touches[0];
-    setDiagramState((prev) => ({
-      ...prev,
-      dragging: true,
-      startPosition: { x: touch.clientX - prev.position.x, y: touch.clientY - prev.position.y },
-    }));
-  }, []);
-
-  const handleTouchMove = useCallback((event: React.TouchEvent) => {
-    if (diagramState.dragging) {
-      const touch = event.touches[0];
-      setDiagramState((prev) => ({
-        ...prev,
-        position: { x: touch.clientX - prev.startPosition.x, y: touch.clientY - prev.startPosition.y },
-      }));
-    }
-  }, [diagramState.dragging]);
-
-  const handleTouchEnd = useCallback(() => {
-    setDiagramState((prev) => ({ ...prev, dragging: false }));
-  }, []);
-
-  if (isDiagramValid === null) {
-    return <p>Validating diagram...</p>;
-  }
-
-  if (isDiagramValid) {
-    return (
-      <div className={classes.container}>
-        <div
-          className={cn("mermaid", classes.mermaid)}
-          ref={diagramRef}
-          style={{
-            transform: `scale(${diagramState.scale}) translate(${diagramState.position.x}px, ${diagramState.position.y}px)`,
-            transformOrigin: '50% 50%',
-            cursor: diagramState.dragging ? 'grabbing' : 'grab',
-          }}
-          onMouseDown={handleMouseDown}
-          onMouseMove={handleMouseMove}
-          onMouseUp={handleMouseUp}
-          onMouseLeave={handleMouseUp}
-          onTouchStart={handleTouchStart}
-          onTouchMove={handleTouchMove}
-          onTouchEnd={handleTouchEnd}
-        >
-          {chart}
-        </div>
-        <MermaidDiagramControls
-          handleZoomIn={handleZoomIn}
-          handleZoomOut={handleZoomOut}
-          diagramRef={diagramRef}
-          sourceCode={chart}
-        />
-      </div>
-    );
-  } else {
-    return <p>{diagramError}</p>;
-  }
-});
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/Messages/Message/MermaidDiagramControls.tsx b/ui/packages/platform/src/pages/Bot/Messages/Message/MermaidDiagramControls.tsx
deleted file mode 100644
index d7d6837d..00000000
--- a/ui/packages/platform/src/pages/Bot/Messages/Message/MermaidDiagramControls.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import IconButton from "@material-ui/core/IconButton";
-import { ZoomInRounded, ZoomOutRounded, SaveAltRounded, FileCopyOutlined } from "@material-ui/icons";
-import {  makeStyles } from "@material-ui/core";
-import React, { useCallback } from "react";
-import Divider from "@material-ui/core/Divider";
-
-const useStyles = makeStyles(
-  () => ({
-    container: {
-      display: 'flex',
-      flexDirection: 'column',
-      alignItems: 'center',
-
-      position: 'absolute',
-      bottom: 20,
-      right: 10,
-      zIndex: 2,
-    },
-    controlButtons: {
-      display: 'flex',
-      flexDirection: 'column',
-      alignItems: 'center',
-
-      border: '1px solid rgba(0, 0, 0, 0.12)',
-      borderRadius: 8,
-
-      background: 'white',
-
-      "& .MuiIconButton-root": {
-        fontSize: '1.5rem',
-        color: 'rgba(0, 0, 0, 0.72)',
-        padding: 8,
-        '&:hover': {
-          color: 'rgba(0, 0, 0, 0.95)',
-        },
-        '&:first-child': {
-          borderRadius: '8px 8px 0 0',
-        },
-        '&:last-child': {
-          borderRadius: ' 0 0 8px 8px',
-        }
-      }
-    },
-    divider: {
-      width: 'calc(100% - 8px)',
-    },
-    actionButtonWrapper: {
-      border: '1px solid rgba(0, 0, 0, 0.12)',
-      borderRadius: 8,
-      background: 'white',
-      marginBottom: 8,
-      overflow: 'hidden'
-    },
-    actionButton: {
-      fontSize: '1.5rem',
-      color: 'rgba(0, 0, 0, 0.72)',
-      padding: 8,
-      borderRadius: 0,
-      '&:hover': {
-        color: 'rgba(0, 0, 0, 0.95)',
-      },
-    }
-  }))
-
-
-type MermaidDiagramControlsProps = {
-  handleZoomIn: () => void,
-  handleZoomOut: () => void,
-  diagramRef:  React.RefObject<HTMLDivElement>,
-  sourceCode: string
-}
-
-export const MermaidDiagramControls = (props: MermaidDiagramControlsProps) => {
-  const { sourceCode, handleZoomOut, handleZoomIn, diagramRef } = props;
-  const classes = useStyles();
-
-  const handleSaveClick = useCallback(() => {
-    if (diagramRef.current) {
-      const svgElement = diagramRef.current.querySelector('svg');
-      if (svgElement) {
-        const svgData = new XMLSerializer().serializeToString(svgElement);
-        const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
-        const url = URL.createObjectURL(svgBlob);
-
-        const link = document.createElement('a');
-        link.href = url;
-        link.download = 'er-diagram.svg';
-        document.body.appendChild(link);
-        link.click();
-        document.body.removeChild(link);
-
-        URL.revokeObjectURL(url);
-      }
-    }
-  }, []);
-
-  const handleCopyClick = async () => {
-    if ('clipboard' in navigator) {
-      await navigator.clipboard.writeText(sourceCode);
-    }
-  }
-
-  return (
-    <div className={classes.container}>
-      <div className={classes.actionButtonWrapper}>
-        <IconButton
-          title="Copy contents"
-          aria-label="Copy contents"
-          className={classes.actionButton}
-          onClick={handleCopyClick}
-        >
-          <FileCopyOutlined />
-        </IconButton>
-      </div>
-      <div className={classes.actionButtonWrapper}>
-        <IconButton
-          title="Download as SVG"
-          aria-label="Download diagram as SVG"
-          className={classes.actionButton}
-          onClick={handleSaveClick}
-        >
-          <SaveAltRounded />
-        </IconButton>
-      </div>
-      <div className={classes.controlButtons}>
-        <IconButton
-          onClick={handleZoomIn}
-          title="Zoom In"
-          aria-label="Zoom In"
-        >
-          <ZoomInRounded />
-        </IconButton>
-        <Divider className={classes.divider} />
-        <IconButton
-          onClick={handleZoomOut}
-          title="Zoom Out"
-          aria-label="Zoom Out"
-        >
-          <ZoomOutRounded />
-        </IconButton>
-      </div>
-    </div>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/Messages/Message/Message.tsx b/ui/packages/platform/src/pages/Bot/Messages/Message/Message.tsx
deleted file mode 100644
index 5aa894c4..00000000
--- a/ui/packages/platform/src/pages/Bot/Messages/Message/Message.tsx
+++ /dev/null
@@ -1,382 +0,0 @@
-import React, { useMemo, useState } from 'react'
-import cn from "classnames";
-import ReactMarkdown, { Components } from "react-markdown";
-import rehypeRaw from "rehype-raw";
-import remarkGfm from "remark-gfm";
-import { makeStyles } from "@material-ui/core";
-import { colors } from "@postgres.ai/shared/styles/colors";
-import { icons } from "@postgres.ai/shared/styles/icons";
-import { DebugDialog } from "../../DebugDialog/DebugDialog";
-import { CodeBlock } from "./CodeBlock";
-import { disallowedHtmlTagsForMarkdown, permalinkLinkBuilder } from "../../utils";
-import { StateMessage } from "../../../../types/api/entities/bot";
-import { MermaidDiagram } from "./MermaidDiagram";
-
-
-type BaseMessageProps = {
-  id: string | null;
-  created_at?: string;
-  content?: string;
-  name?: string;
-  isLoading?: boolean;
-  formattedTime?: string;
-  aiModel?: string
-  stateMessage?: StateMessage | null
-  isCurrentStreamMessage?: boolean
-  isPublic?: boolean;
-}
-
-type AiMessageProps = BaseMessageProps & {
-  isAi: true;
-  content: string;
-  aiModel: string
-  isCurrentStreamMessage?: boolean
-}
-
-type HumanMessageProps = BaseMessageProps & {
-  isAi: false;
-  name: string;
-  content: string;
-}
-
-type LoadingMessageProps = BaseMessageProps & {
-  isLoading: true;
-  isAi: true;
-  content?: undefined
-  stateMessage: StateMessage | null
-}
-
-type MessageProps = AiMessageProps | HumanMessageProps | LoadingMessageProps;
-
-const useStyles = makeStyles(
-  (theme) => ({
-    message: {
-      padding: 10,
-      paddingLeft: 60,
-      position: 'relative',
-      whiteSpace: 'normal',
-      [theme.breakpoints.down('xs')]: {
-        paddingLeft: 30
-      },
-      '& .markdown pre': {
-        [theme.breakpoints.down('sm')]: {
-          display: 'inline-block',
-          minWidth: '100%',
-          width: 'auto',
-        },
-        [theme.breakpoints.up('md')]: {
-          display: 'block',
-          maxWidth: 'auto',
-          width: 'auto',
-        },
-        [theme.breakpoints.up('lg')]: {
-          display: 'block',
-          maxWidth: 'auto',
-          width: 'auto',
-        },
-      },
-    },
-    messageAvatar: {
-      top: '10px',
-      left: '15px',
-      position: 'absolute',
-      width: 30,
-      height: 30,
-      [theme.breakpoints.down('xs')]: {
-        width: 24,
-        height: 24,
-        left: 0,
-        '& svg': {
-          width: 24,
-          height: 24,
-        }
-      }
-    },
-    messageAvatarImage: {
-      width: '100%',
-      borderRadius: '50%'
-    },
-    messageAuthor: {
-      fontSize: 14,
-      fontWeight: 'bold',
-    },
-    messageInfo: {
-      display: 'inline-block',
-      marginLeft: 10,
-      padding: 0,
-      fontSize: '0.75rem',
-      color: colors.pgaiDarkGray,
-      transition: '.2s ease',
-      background: "none",
-      border: "none",
-      textDecoration: "none",
-      '@media (max-width: 450px)': {
-        '&:nth-child(1)': {
-          display: 'none'
-        }
-      }
-    },
-    messageInfoActive: {
-      borderBottom: '1px solid currentcolor',
-      cursor: 'pointer',
-      '&:hover': {
-        color: '#404040'
-      }
-    },
-    messageHeader: {
-      height: '1.125rem',
-      display: 'flex',
-      flexWrap: 'wrap',
-      alignItems: 'baseline',
-      '@media (max-width: 450px)': {
-        height: 'auto',
-      }
-    },
-    additionalInfo: {
-      '@media (max-width: 450px)': {
-        width: '100%',
-        marginTop: 4,
-        marginLeft: -10,
-
-      }
-    },
-    badge: {
-      fontSize: '0.5rem',
-      minWidth: '0.714rem',
-      height: '0.714rem',
-      padding: '0 0.25rem'
-    },
-    messagesSpinner: {
-      display: 'flex',
-      justifyContent: 'center',
-      padding: 10
-    },
-    markdown: {
-      margin: '5px 5px 5px 0',
-      fontSize: 14,
-      '& h1': {
-        marginTop: 5
-      },
-      '& table': {
-        borderCollapse: 'collapse',
-        borderSpacing: 0
-      },
-      '& tr': {
-        borderTop: '1px solid #c6cbd1',
-        background: '#fff'
-      },
-      '& th, & td': {
-        padding: '10px 13px',
-        border: '1px solid #dfe2e5'
-      },
-      '& table tr:nth-child(2n)': {
-        background: '#f6f8fa'
-      },
-
-      '& blockquote': {
-        color: '#666',
-        margin: 0,
-        paddingLeft: '3em',
-        borderLeft: '0.5em #eee solid'
-      },
-      '& img.emoji': {
-        marginTop: 5
-      },
-      '& code': {
-        border: '1px dotted silver',
-        display: 'inline-block',
-        borderRadius: 3,
-        padding: 2,
-        backgroundColor: '#f6f8fa',
-        marginBottom: 3,
-        fontSize: '13px !important',
-        fontFamily: "'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New'," +
-          " 'andale mono', 'lucida console', monospace",
-      },
-      '& pre code': {
-        background: 'none',
-        border: 0,
-        margin: 0,
-        borderRadius: 0,
-        display: 'inline',
-        padding: 0,
-      },
-      '& div:not([class]):not([role])': {
-        display: 'block',
-        marginBlockStart: '1em',
-        marginBlockEnd: '1em',
-        marginInlineStart: 0,
-        marginInlineEnd: 0,
-        //animation: `$typing 0.5s steps(30, end), $blinkCaret 0.75s step-end infinite`,
-      },
-      '& .MuiExpansionPanel-root div': {
-        marginBlockStart: 0,
-        marginBlockEnd: 0,
-      },
-    },
-    loading: {
-      display: 'block',
-      marginBlockStart: '1em',
-      marginBlockEnd: '1em',
-      marginInlineStart: 0,
-      marginInlineEnd: 0,
-      fontSize: 14,
-      color: colors.pgaiDarkGray,
-      '&:after': {
-        overflow: 'hidden',
-        display: 'inline-block',
-        verticalAlign: 'bottom',
-        animation: '$ellipsis steps(4,end) 1.2s infinite',
-        content: "'\\2026'",
-        width: 0,
-      }
-    },
-    '@keyframes ellipsis': {
-      'to': {
-        width: '0.9em'
-      },
-    },
-    '@keyframes typing': {
-      from: { width: 0 },
-      to: { width: '100%' },
-    },
-    '@keyframes blinkCaret': {
-      from: { borderRightColor: 'transparent' },
-      to: { borderRightColor: 'transparent' },
-      '50%': { borderRightColor: 'black' },
-    },
-  }),
-
-)
-
-export const Message = React.memo((props: MessageProps) => {
-  const {
-    id,
-    isAi,
-    formattedTime,
-    content,
-    name,
-    created_at,
-    isLoading,
-    aiModel,
-    stateMessage,
-    isCurrentStreamMessage,
-    isPublic
-  } = props;
-
-  const [isDebugVisible, setDebugVisible] = useState(false);
-
-
-  const classes = useStyles();
-
-  const contentToRender: string = content?.replace(/\n/g, '  \n') || ''
-
-  const toggleDebugDialog = () => {
-    setDebugVisible(prevState => !prevState)
-  }
-
-  const renderers = useMemo<Components>(() => ({
-    p: ({ node, ...props }) => <div {...props} />,
-    img: ({ node, ...props }) => <img style={{ maxWidth: '60%' }} {...props} />,
-    code: ({ node, inline, className, children, ...props }) => {
-      const match = /language-(\w+)/.exec(className || '');
-      const matchMermaid = /language-mermaid/.test(className || '');
-      if (!inline) {
-        return (
-          <>
-            {matchMermaid && !isCurrentStreamMessage && <MermaidDiagram chart={String(children).replace(/\n$/, '')} />}
-            <CodeBlock value={String(children).replace(/\n$/, '')} language={match?.[1]} />
-          </>
-        )
-      } else {
-        return <code {...props}>{children}</code>
-      }
-    },
-  }), []);
-
-  return (
-    <>
-    {id && <DebugDialog
-        isOpen={isDebugVisible}
-        onClose={toggleDebugDialog}
-        messageId={id}
-      />}
-      <div className={classes.message}>
-        <div className={classes.messageAvatar}>
-          {isAi
-            ? <img
-              src="/images/bot_avatar.png"
-              alt="Postgres.AI Assistant avatar"
-              className={classes.messageAvatarImage}
-            />
-            : icons.userChatIcon}
-        </div>
-        <div className={classes.messageHeader}>
-          <span className={classes.messageAuthor}>
-            {isAi ? 'Postgres.AI' : name}
-          </span>
-          {created_at && formattedTime &&
-            <span
-              className={cn(classes.messageInfo)}
-              title={created_at}
-            >
-              {formattedTime}
-            </span>}
-          <div className={classes.additionalInfo}>
-            {id && isPublic && <>
-              <span className={classes.messageInfo}>|</span>
-              <a
-                className={cn(classes.messageInfo, classes.messageInfoActive)}
-                href={permalinkLinkBuilder(id)}
-                target="_blank"
-                rel="noreferrer"
-              >
-                permalink
-              </a>
-            </>}
-            {!isLoading && isAi && id && <>
-              <span className={classes.messageInfo}>|</span>
-              <button
-                className={cn(classes.messageInfo, classes.messageInfoActive)}
-                onClick={toggleDebugDialog}
-              >
-                debug info
-              </button>
-            </>}
-            {
-              aiModel && isAi && <>
-                <span className={classes.messageInfo}>|</span>
-                <span
-                  className={cn(classes.messageInfo)}
-                  title={aiModel}
-                >
-                  {aiModel}
-                </span>
-              </>
-            }
-          </div>
-        </div>
-        <div>
-          {isLoading
-            ?
-            <div className={classes.markdown}>
-              <div className={classes.loading}>
-                {stateMessage && stateMessage.state ? stateMessage.state : 'Thinking'}
-              </div>
-            </div>
-            : <ReactMarkdown
-                className={classes.markdown}
-                children={contentToRender || ''}
-                rehypePlugins={isAi ? [rehypeRaw] : []}
-                remarkPlugins={[remarkGfm]}
-                linkTarget='_blank'
-                components={renderers}
-                disallowedElements={disallowedHtmlTagsForMarkdown}
-                unwrapDisallowed
-              />
-          }
-        </div>
-      </div>
-    </>
-  )
-})
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/Messages/Messages.tsx b/ui/packages/platform/src/pages/Bot/Messages/Messages.tsx
deleted file mode 100644
index d8d12ce0..00000000
--- a/ui/packages/platform/src/pages/Bot/Messages/Messages.tsx
+++ /dev/null
@@ -1,298 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { useRef, useEffect, useState } from 'react';
-import { makeStyles, Typography } from "@material-ui/core";
-import cn from "classnames";
-import { ResizeObserver } from '@juggle/resize-observer';
-import {colors} from "@postgres.ai/shared/styles/colors";
-import {PageSpinner} from "@postgres.ai/shared/components/PageSpinner";
-import { usePrev } from 'hooks/usePrev';
-import { getMaxScrollTop, getUserMessagesCount } from './utils';
-import Format from "../../../utils/format";
-import { BotMessage } from "../../../types/api/entities/bot";
-import { Message } from "./Message/Message";
-import { useAiBot } from "../hooks";
-import { HintCards } from "../HintCards/HintCards";
-
-const useStyles = makeStyles(
-  (theme) => ({
-    root: {
-      borderRadius: 4,
-      overflow: 'hidden',
-      flex: '1 0 160px',
-      display: 'flex',
-      flexDirection: 'column',
-    },
-    emptyChat: {
-      justifyContent: 'center',
-      alignItems: 'center',
-      textAlign: 'center'
-    },
-    emptyChatMessage: {
-      maxWidth: '80%',
-      fontSize: '0.875rem'
-    },
-    messages: {
-      overflowY: 'auto',
-      flex: '1 1 100%'
-    },
-    message: {
-      padding: 10,
-      paddingLeft: 60,
-      position: 'relative',
-      whiteSpace: 'normal',
-      [theme.breakpoints.down('xs')]: {
-        paddingLeft: 30
-      },
-      '& .markdown pre': {
-        [theme.breakpoints.down('sm')]: {
-          display: 'inline-block',
-          minWidth: '100%',
-          width: 'auto',
-        },
-        [theme.breakpoints.up('md')]: {
-          display: 'block',
-          maxWidth: 'auto',
-          width: 'auto',
-        },
-        [theme.breakpoints.up('lg')]: {
-          display: 'block',
-          maxWidth: 'auto',
-          width: 'auto',
-        },
-      },
-    },
-    messageAvatar: {
-      top: '10px',
-      left: '15px',
-      position: 'absolute',
-      width: 30,
-      height: 30,
-      [theme.breakpoints.down('xs')]: {
-        width: 24,
-        height: 24,
-        left: 0,
-        '& svg': {
-          width: 24,
-          height: 24,
-        }
-      }
-    },
-    messageAvatarImage: {
-      width: '100%',
-      borderRadius: '50%'
-    },
-    messageAuthor: {
-      fontSize: 14,
-      fontWeight: 'bold',
-    },
-    messageInfo: {
-      display: 'inline-block',
-      marginLeft: 10,
-      fontSize: '0.75rem',
-      color: colors.pgaiDarkGray,
-      transition: '.2s ease'
-    },
-    messageInfoActive: {
-      '&:hover': {
-        color: '#404040'
-      }
-    },
-    messageHeader: {
-      height: '1.125rem',
-    },
-    messagesSpinner: {
-      display: 'flex',
-      justifyContent: 'center',
-      padding: 10
-    }
-  }),
-)
-
-type Time = string
-
-type FormattedTime = {
-  [id: string]: Time
-}
-
-export const Messages = React.memo(({orgId}: {orgId: number}) => {
-  const {
-    messages,
-    loading: isLoading,
-    wsLoading: isWaitingForAnswer,
-    stateMessage,
-    currentStreamMessage,
-    isStreamingInProcess
-  } = useAiBot();
-
-  const rootRef = useRef<HTMLDivElement>(null);
-  const wrapperRef = useRef<HTMLDivElement>(null);
-  const atBottomRef = useRef(true);
-  const shouldSkipScrollCalcRef = useRef(false);
-  const classes = useStyles();
-  const [formattedTimes, setFormattedTimes] = useState<FormattedTime>({});
-
-  // Scroll handlers.
-  const scrollBottom = () => {
-    shouldSkipScrollCalcRef.current = true;
-    if (rootRef.current) {
-      rootRef.current.scrollTop = getMaxScrollTop(rootRef.current);
-    }
-    atBottomRef.current = true;
-  };
-
-  const scrollBottomIfNeed = () => {
-    if (!atBottomRef.current) {
-      return;
-    }
-
-    scrollBottom();
-  };
-
-  // Listening resizing of wrapper.
-  useEffect(() => {
-    const observedElement = wrapperRef.current;
-    if (!observedElement) return;
-
-    const resizeObserver = new ResizeObserver(scrollBottomIfNeed);
-    resizeObserver.observe(observedElement);
-
-    return () => resizeObserver.unobserve(observedElement);
-  }, [wrapperRef.current]);
-
-  // Scroll to bottom if user sent new message.
-  const userMessagesCount = getUserMessagesCount(messages || [] as BotMessage[]);
-  const prevUserMessagesCount = usePrev(userMessagesCount);
-
-  useEffect(() => {
-    if ((userMessagesCount > (prevUserMessagesCount || 0)) && rootRef.current) {
-      scrollBottom();
-    }
-  }, [prevUserMessagesCount, userMessagesCount]);
-
-  useEffect(() => {
-    if (!isLoading && !isStreamingInProcess) {
-      scrollBottomIfNeed();
-    }
-  }, [isLoading, scrollBottomIfNeed, isStreamingInProcess]);
-
-  useEffect(() => {
-    const updateTimes = () => {
-      if (messages && messages.length > 0) {
-        const newFormattedTimes: FormattedTime = {};
-        messages.forEach(message => {
-          newFormattedTimes[message.id] = Format.timeAgo(message.created_at) || '';
-        });
-        setFormattedTimes(newFormattedTimes);
-      }
-    };
-
-    updateTimes();
-
-    const intervalId = setInterval(updateTimes, 60000);
-
-    return () => clearInterval(intervalId);
-  }, [messages]);
-
-  // Check auto-scroll condition.
-  const calcIsAtBottom = () => {
-    if (shouldSkipScrollCalcRef.current) {
-      shouldSkipScrollCalcRef.current = false;
-      return;
-    }
-    if (rootRef.current) {
-      atBottomRef.current = rootRef.current.scrollTop >= getMaxScrollTop(rootRef.current);
-    }
-  };
-
-  if (isLoading) {
-    return (
-      <div className={classes.root}>
-        <PageSpinner/>
-      </div>
-    )
-  }
-
-  if (!messages || messages.length === 0) {
-    return (
-      <div className={cn(classes.root, classes.emptyChat)}>
-        <Typography className={classes.emptyChatMessage}>
-          Postgres.AI Assistant can make mistakes. <br />
-          Consider checking important information. <br />
-          Depending on settings, LLM service provider such as GCP or OpenAI is used.
-        </Typography>
-        <HintCards orgId={orgId} />
-      </div>
-    )
-  }
-
-  return (
-    <div className={classes.root}>
-      <div className={classes.messages} ref={rootRef} onScroll={calcIsAtBottom}>
-        <div ref={wrapperRef}>
-          {messages &&
-            messages.map((message) => {
-              const {
-                id,
-                is_ai,
-                last_name,
-                first_name,
-                display_name,
-                slack_profile,
-                created_at,
-                content,
-                ai_model,
-                is_public
-              } = message;
-              let name = 'You';
-
-              if (first_name || last_name) {
-                name = `${first_name || ''} ${last_name || ''}`.trim();
-              } else if (display_name) {
-                name = display_name;
-              } else if (slack_profile) {
-                name = slack_profile;
-              }
-
-              let formattedTime = '';
-
-              if (formattedTimes) {
-                formattedTime = formattedTimes[id]
-              }
-
-              return (
-                <Message
-                  key={id || content}
-                  id={id}
-                  isAi={is_ai}
-                  name={name}
-                  created_at={created_at}
-                  content={content}
-                  formattedTime={formattedTime}
-                  aiModel={ai_model}
-                  isPublic={is_public}
-                />
-              )
-            })}
-          {
-            currentStreamMessage && <Message
-              id={null}
-              isAi
-              content={currentStreamMessage.content}
-              aiModel={currentStreamMessage.ai_model}
-              isCurrentStreamMessage
-            />
-          }
-          {isWaitingForAnswer &&
-            <Message id={null} isLoading isAi={true} stateMessage={stateMessage} />
-          }
-        </div>
-      </div>
-    </div>
-  );
-});
diff --git a/ui/packages/platform/src/pages/Bot/Messages/utils.ts b/ui/packages/platform/src/pages/Bot/Messages/utils.ts
deleted file mode 100644
index c10dc2e1..00000000
--- a/ui/packages/platform/src/pages/Bot/Messages/utils.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {BotMessage} from "../../../types/api/entities/bot";
-
-export const getMaxScrollTop = (element: HTMLElement) =>
-  element.scrollHeight - element.clientHeight
-
-
-export const getUserMessagesCount = (messages: BotMessage[]) => {
-  if (!messages) {
-    return 0
-  }
-
-  const keys = Object.keys(messages)
-
-  return keys.reduce((count, key) => {
-    const idx = Number(key)
-    return !messages[idx].is_ai ? count + 1 : count
-  }, 0)
-}
diff --git a/ui/packages/platform/src/pages/Bot/SettingsDialog/SettingsDialog.tsx b/ui/packages/platform/src/pages/Bot/SettingsDialog/SettingsDialog.tsx
deleted file mode 100644
index 7ddad90a..00000000
--- a/ui/packages/platform/src/pages/Bot/SettingsDialog/SettingsDialog.tsx
+++ /dev/null
@@ -1,342 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { useEffect, useState } from 'react'
-import {
-  IconButton,
-  TextField,
-  Dialog,
-  Typography,
-  Radio,
-  RadioGroup,
-  FormControlLabel,
-  Button,
-  makeStyles,
-} from '@material-ui/core'
-import MuiDialogTitle from '@material-ui/core/DialogTitle'
-import MuiDialogContent from '@material-ui/core/DialogContent'
-import MuiDialogActions from '@material-ui/core/DialogActions'
-import FormLabel from '@mui/material/FormLabel'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import { icons } from '@postgres.ai/shared/styles/icons'
-import { Spinner } from '@postgres.ai/shared/components/Spinner'
-import { colors } from "@postgres.ai/shared/styles/colors";
-import { useAiBot } from "../hooks";
-import { AiModel } from "../../../types/api/entities/bot";
-
-export type Visibility = 'public' | 'private';
-
-type DialogTitleProps = {
-  id: string
-  children: React.ReactNode
-  onClose: () => void
-}
-
-type PublicChatDialogProps = {
-  isOpen: boolean
-  onClose: () => void
-  threadId: string | null
-}
-
-const useDialogTitleStyles = makeStyles(
-  (theme) => ({
-    root: {
-      margin: 0,
-      padding: theme.spacing(1),
-    },
-    dialogTitle: {
-      fontSize: 16,
-      lineHeight: '19px',
-      fontWeight: 600,
-    },
-    closeButton: {
-      position: 'absolute',
-      right: theme.spacing(1),
-      top: 4,
-      color: theme.palette.grey[500],
-    },
-  }),
-  { index: 1 },
-)
-
-const DialogTitle = (props: DialogTitleProps) => {
-  const classes = useDialogTitleStyles()
-  const { children, onClose, ...other } = props
-  return (
-    <MuiDialogTitle disableTypography className={classes.root} {...other}>
-      <Typography className={classes.dialogTitle}>{children}</Typography>
-      {onClose ? (
-        <IconButton
-          aria-label="close"
-          className={classes.closeButton}
-          onClick={onClose}
-        >
-          {icons.closeIcon}
-        </IconButton>
-      ) : null}
-    </MuiDialogTitle>
-  )
-}
-
-const useDialogContentStyles = makeStyles(
-  (theme) => ({
-    dialogContent: {
-      paddingTop: 10,
-      padding: theme.spacing(2),
-    },
-  }),
-  { index: 1 },
-)
-
-const DialogContent = (props: { children: React.ReactNode }) => {
-  const classes = useDialogContentStyles()
-  return (
-    <MuiDialogContent dividers className={classes.dialogContent}>
-      {props.children}
-    </MuiDialogContent>
-  )
-}
-
-const useDialogActionsStyles = makeStyles(
-  (theme) => ({
-    root: {
-      margin: 0,
-      padding: theme.spacing(1),
-    },
-  }),
-  { index: 1 },
-)
-
-const DialogActions = (props: { children: React.ReactNode }) => {
-  const classes = useDialogActionsStyles()
-  return (
-    <MuiDialogActions className={classes.root}>
-      {props.children}
-    </MuiDialogActions>
-  )
-}
-
-const useDialogStyles = makeStyles(
-  () => ({
-    textField: {
-      ...styles.inputField,
-      marginTop: '0px',
-      width: 480,
-    },
-    copyButton: {
-      marginTop: '-3px',
-      fontSize: '20px',
-    },
-    dialog: {},
-    remark: {
-      fontSize: 12,
-      lineHeight: '12px',
-      color: colors.state.warning,
-      paddingLeft: 20,
-    },
-    remarkIcon: {
-      display: 'block',
-      height: '20px',
-      width: '22px',
-      float: 'left',
-      paddingTop: '5px',
-    },
-    urlContainer: {
-      marginTop: 10,
-      paddingLeft: 22,
-    },
-    radioLabel: {
-      fontSize: 12,
-    },
-    dialogContent: {
-      paddingTop: 10,
-    },
-  }),
-  { index: 1 },
-)
-
-export const SettingsDialog = (props: PublicChatDialogProps) => {
-  const {
-    onClose,
-    isOpen,
-    threadId,
-  } = props;
-
-
-  const {
-    chatVisibility,
-    changeChatVisibility,
-    isChangeVisibilityLoading,
-    getChatsList,
-    aiModels,
-    aiModel: activeModel,
-    setAiModel: setActiveModel,
-  } = useAiBot();
-
-  const [model, setModel] = useState<AiModel | null>(activeModel)
-  const [visibility, setVisibility] = useState<string>(chatVisibility);
-
-  const classes = useDialogStyles();
-
-  const publicUrl = `https://postgres.ai/chats/${threadId}`;
-
-  const handleCopyUrl = () => {
-    if ('clipboard' in navigator) {
-      navigator.clipboard.writeText(publicUrl);
-    }
-  }
-
-  const handleSaveChanges = () => {
-    if (model && model !== activeModel) {
-      setActiveModel(model)
-    }
-    if (visibility !== chatVisibility && threadId) {
-      changeChatVisibility(threadId, visibility === 'public')
-      getChatsList();
-    }
-
-    onClose()
-  }
-
-  useEffect(() => {
-    if (isOpen) {
-      if (visibility !== chatVisibility) {
-        setVisibility(chatVisibility)
-      }
-      if (model?.name !== activeModel?.name) {
-        setModel(activeModel)
-      }
-    }
-  }, [isOpen]);
-
-  const urlField = (
-    <div>
-      <TextField
-        id="token"
-        className={classes.textField}
-        margin="normal"
-        value={publicUrl}
-        variant="outlined"
-        style={{ width: 500 }}
-        onFocus={(event) => event.target.select()}
-        InputProps={{
-          readOnly: true,
-          id: 'sharedUrl',
-        }}
-        InputLabelProps={{
-          shrink: true,
-          style: styles.inputFieldLabel,
-        }}
-        FormHelperTextProps={{
-          style: styles.inputFieldHelper,
-        }}
-      />
-
-      <IconButton
-        className={classes.copyButton}
-        aria-label="Copy"
-        onClick={handleCopyUrl}
-      >
-        {icons.copyIcon}
-      </IconButton>
-    </div>
-  )
-
-  return (
-    <Dialog
-      onClose={onClose}
-      aria-labelledby="customized-dialog-title"
-      open={isOpen}
-      className={classes.dialog}
-      fullWidth
-    >
-      <DialogTitle
-        id="customized-dialog-title"
-        onClose={onClose}
-      >
-        Chat Settings
-      </DialogTitle>
-      <DialogContent>
-        {threadId && <>
-          <FormLabel component="legend">Visibility</FormLabel>
-          <RadioGroup
-            aria-label="shareUrl"
-            name="shareUrl"
-            value={visibility}
-            onChange={(event) => {
-              setVisibility(event.target.value as Visibility)
-            }}
-            className={classes.radioLabel}
-          >
-            <FormControlLabel
-              value="private"
-              control={<Radio />}
-              label="Only members of the organization can view"
-            />
-
-            <FormControlLabel
-              value="public"
-              control={<Radio />}
-              label="Anyone with a special link and members of the organization can view"
-            />
-          </RadioGroup>
-          {visibility && (
-              <div className={classes.urlContainer}>{urlField}</div>
-            )}
-        </>}
-        {aiModels && <>
-          <FormLabel component="legend">Model</FormLabel>
-          <RadioGroup
-            aria-label="model"
-            name="model"
-            value={`${model?.vendor}/${model?.name}`}
-            onChange={(event) => {
-              const selectedModel = aiModels?.find((model) => `${model.vendor}/${model.name}` === event.target.value)
-              setModel(selectedModel!)
-            }}
-            className={classes.radioLabel}
-          >
-            {aiModels.map((model) =>
-                <FormControlLabel
-                  key={`${model.vendor}/${model.name}`}
-                  value={`${model.vendor}/${model.name}`}
-                  control={<Radio />}
-                  label={model.name}
-                />
-              )
-            }
-          </RadioGroup>
-        </>}
-      </DialogContent>
-
-      <DialogActions>
-        <Button
-          autoFocus
-          variant="contained"
-          disabled={isChangeVisibilityLoading}
-          onClick={handleSaveChanges}
-          color="primary"
-        >
-          Save changes
-          {isChangeVisibilityLoading && (
-              <span>
-                &nbsp;
-                <Spinner size="sm" />
-              </span>
-            )}
-        </Button>
-        <Button
-          onClick={() => onClose()}
-          variant="outlined"
-          color="secondary"
-        >
-          Close
-        </Button>
-      </DialogActions>
-    </Dialog>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/SettingsPanel/SettingsPanel.tsx b/ui/packages/platform/src/pages/Bot/SettingsPanel/SettingsPanel.tsx
deleted file mode 100644
index b457841b..00000000
--- a/ui/packages/platform/src/pages/Bot/SettingsPanel/SettingsPanel.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import React, { useMemo } from 'react';
-import cn from "classnames";
-import { Button, makeStyles, useMediaQuery } from "@material-ui/core";
-import SettingsOutlinedIcon from "@material-ui/icons/SettingsOutlined";
-import { colors } from "@postgres.ai/shared/styles/colors";
-import { theme } from "@postgres.ai/shared/styles/theme";
-import { permalinkLinkBuilder } from "../utils";
-import { useAiBot } from "../hooks";
-import DeveloperModeIcon from "@material-ui/icons/DeveloperMode";
-
-export type SettingsPanelProps = {
-  onSettingsClick: () => void;
-  onConsoleClick: () => void;
-}
-
-const useStyles = makeStyles((theme) => ({
-    label: {
-      backgroundColor: colors.primary.main,
-      color: colors.primary.contrastText,
-      display: 'inline-block',
-      borderRadius: 3,
-      fontSize: 10,
-      lineHeight: '12px',
-      padding: 2,
-      paddingLeft: 3,
-      paddingRight: 3,
-      verticalAlign: 'text-top',
-      textDecoration: 'none',
-      '& > span': {
-        textTransform: 'capitalize'
-      }
-    },
-    labelVisibility: {
-      marginLeft: '0.5rem',
-      [theme.breakpoints.down('sm')]: {
-        marginLeft: '0.25rem'
-      },
-      '&:hover': {
-        backgroundColor: colors.secondary1.main
-      }
-    },
-    labelModel: {
-      background: colors.secondary1.main,
-    },
-    labelModelInvalid: {
-      background: colors.state.error,
-      border: "none",
-      cursor: 'pointer',
-      '&:hover': {
-        backgroundColor: colors.primary.dark
-      }
-    },
-    labelPrivate: {
-      backgroundColor: colors.pgaiDarkGray,
-    },
-    disabled: {
-      pointerEvents: "none"
-    },
-    button: {
-      marginLeft: '0.5rem',
-      [theme.breakpoints.down('sm')]: {
-        border: 'none',
-        minWidth: '2rem',
-        height: '2rem',
-        padding: 0,
-        marginLeft: '0.25rem',
-        '& .MuiButton-startIcon': {
-          margin: 0
-        }
-      }
-    }
-  }),
-)
-
-export const SettingsPanel = (props: SettingsPanelProps) => {
-  const { onSettingsClick, onConsoleClick } = props;
-  const classes = useStyles();
-  const matches = useMediaQuery(theme.breakpoints.down('sm'));
-  const { messages, chatVisibility, aiModel, aiModelsLoading } = useAiBot();
-  const permalinkId = useMemo(() => messages?.[0]?.id, [messages]);
-
-  let modelLabel;
-
-  if (aiModel) {
-    modelLabel = (
-      <span
-        className={cn(classes.label, classes.labelModel)}
-      >
-        {aiModel.name}
-      </span>
-    )
-  } else {
-    modelLabel = (
-      <button
-        className={cn(classes.label, classes.labelModelInvalid)}
-        onClick={onSettingsClick}
-      >
-        Model not set
-      </button>
-    )
-  }
-
-  return (
-    <>
-      {!aiModelsLoading && modelLabel}
-      {permalinkId && <a
-        href={permalinkId && chatVisibility === 'public' ? permalinkLinkBuilder(permalinkId) : ''}
-        className={cn(classes.label, classes.labelVisibility,
-          {
-            [classes.labelPrivate]: chatVisibility === 'private',
-            [classes.disabled]: chatVisibility === 'private' || !permalinkId
-          }
-        )}
-        target="_blank"
-        aria-disabled={chatVisibility === 'private' || !permalinkId}
-      >
-        <span>{chatVisibility}</span> thread
-      </a>}
-      <Button
-        variant="outlined"
-        onClick={onSettingsClick}
-        startIcon={<SettingsOutlinedIcon />}
-        className={classes.button}
-      >
-        {!matches && 'Settings'}
-      </Button>
-      <Button
-        variant="outlined"
-        onClick={onConsoleClick}
-        startIcon={<DeveloperModeIcon />}
-        className={classes.button}
-      >
-        {!matches && 'Console'}
-      </Button>
-    </>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/hints.ts b/ui/packages/platform/src/pages/Bot/hints.ts
deleted file mode 100644
index 6ff3bcd1..00000000
--- a/ui/packages/platform/src/pages/Bot/hints.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-export type HintType = 'design' | 'settings' | 'performance' | 'common'
-
-export type Hint = {
-  hint: string,
-  prompt: string,
-  type: HintType
-}
-
-export const hints: Hint[] = [
-  {
-    hint: 'Help me design an IoT system DB schema',
-    prompt: 'Help me design an IoT system DB schema',
-    type: 'design'
-  },
-  {
-    hint: 'Should I enable wal_compression?',
-    prompt: 'Should I enable wal_compression?',
-    type: 'settings',
-  },
-  {
-    hint: 'Demonstrate benefits of Index-Only Scans',
-    prompt: 'Show the benefits of Index-Only Scans. Invent a test case, create two types of queries, run them on Postgres 16, and show the plans. Then explain the difference.',
-    type: 'performance',
-  },
-  {
-    hint: 'What do people say about subtransactions?',
-    prompt: 'What do people say about subtransactions?',
-    type: 'common'
-  },
-]
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/hooks.tsx b/ui/packages/platform/src/pages/Bot/hooks.tsx
deleted file mode 100644
index edda9b44..00000000
--- a/ui/packages/platform/src/pages/Bot/hooks.tsx
+++ /dev/null
@@ -1,595 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
-import useWebSocket, {ReadyState} from "react-use-websocket";
-import { useLocation } from "react-router-dom";
-import { BotMessage, DebugMessage, AiModel, StateMessage, StreamMessage } from "../../types/api/entities/bot";
-import {getChatsWithWholeThreads} from "../../api/bot/getChatsWithWholeThreads";
-import {getChats} from "api/bot/getChats";
-import {useAlertSnackbar} from "@postgres.ai/shared/components/AlertSnackbar/useAlertSnackbar";
-import {localStorage} from "../../helpers/localStorage";
-import { updateChatVisibility } from "../../api/bot/updateChatVisibility";
-import { getAiModels } from "../../api/bot/getAiModels";
-import { getDebugMessages } from "../../api/bot/getDebugMessages";
-
-
-const WS_URL = process.env.REACT_APP_WS_URL || '';
-
-const DEFAULT_MODEL_NAME = 'gpt-4o-mini'
-
-type ErrorType = {
-  code?: number;
-  message: string;
-  type?: 'connection' | 'chatNotFound';
-}
-
-type SendMessageType = {
-  content: string;
-  thread_id?: string | null;
-  org_id?: number | null;
-  is_public?: boolean;
-}
-
-type UseAiBotReturnType = {
-  messages: BotMessage[] | null;
-  error: ErrorType | null;
-  loading: boolean;
-  sendMessage: (args: SendMessageType) => Promise<void>;
-  clearChat: () => void;
-  wsLoading: boolean;
-  wsReadyState: ReadyState;
-  changeChatVisibility: (threadId: string, isPublic: boolean) => void;
-  isChangeVisibilityLoading: boolean;
-  unsubscribe: (threadId: string) => void;
-  chatVisibility: 'public' | 'private';
-  debugMessages: DebugMessage[] | null;
-  getDebugMessagesForWholeThread: () => void;
-  chatsList: UseBotChatsListHook['chatsList'];
-  chatsListLoading: UseBotChatsListHook['loading'];
-  getChatsList: UseBotChatsListHook['getChatsList'];
-  aiModel: UseAiModelsList['aiModel'];
-  setAiModel: UseAiModelsList['setAiModel'];
-  aiModels: UseAiModelsList['aiModels'];
-  aiModelsLoading: UseAiModelsList['loading'];
-  debugMessagesLoading: boolean;
-  stateMessage: StateMessage | null;
-  isStreamingInProcess: boolean
-  currentStreamMessage: StreamMessage | null
-}
-
-type UseAiBotArgs = {
-  threadId?: string;
-  orgId?: number
-}
-
-export const useAiBotProviderValue = (args: UseAiBotArgs): UseAiBotReturnType => {
-  const { threadId, orgId } = args;
-  const { showMessage, closeSnackbar } = useAlertSnackbar();
-  const {
-    aiModels,
-    aiModel,
-    setAiModel,
-    loading: aiModelsLoading
-  } = useAiModelsList();
-  let location = useLocation<{skipReloading?: boolean}>();
-
-  const {
-    chatsList,
-    loading: chatsListLoading,
-    getChatsList,
-  } = useBotChatsList(orgId);
-
-  const [messages, setMessages] = useState<BotMessage[] | null>(null);
-  const [debugMessages, setDebugMessages] = useState<DebugMessage[] | null>(null);
-  const [debugMessagesLoading, setDebugMessagesLoading] = useState<boolean>(false);
-  const [isLoading, setLoading] = useState<boolean>(false);
-  const [error, setError] = useState<ErrorType | null>(null);
-  const [wsLoading, setWsLoading] = useState<boolean>(false);
-  const [chatVisibility, setChatVisibility] = useState<UseAiBotReturnType['chatVisibility']>('public');
-  const [stateMessage, setStateMessage] = useState<StateMessage | null>(null)
-  const [currentStreamMessage, setCurrentStreamMessage] = useState<StreamMessage | null>(null)
-  const [isStreamingInProcess, setStreamingInProcess] = useState<boolean>(false)
-
-  const [isChangeVisibilityLoading, setIsChangeVisibilityLoading] = useState<boolean>(false);
-  
-  const token = localStorage.getAuthToken()
-
-  const onWebSocketError = (error: WebSocketEventMap['error']) => {
-    console.error('WebSocket error:', error);
-    showMessage('WebSocket connection error: attempting to reconnect');
-  }
-
-  const onWebSocketMessage = (event: WebSocketEventMap['message']) => {
-    if (event.data) {
-      const messageData: BotMessage | DebugMessage | StateMessage | StreamMessage = JSON.parse(event.data);
-      if (messageData) {
-        const isThreadMatching = threadId && threadId === messageData.thread_id;
-        const isParentMatching = !threadId && 'parent_id' in messageData && messageData.parent_id && messages;
-        const isDebugMessage = messageData.type === 'debug';
-        const isStateMessage = messageData.type === 'state';
-        const isStreamMessage = messageData.type === 'stream';
-
-        if (isThreadMatching || isParentMatching || isDebugMessage || isStateMessage || isStreamMessage) {
-          switch (messageData.type) {
-            case 'debug':
-              handleDebugMessage(messageData)
-              break;
-            case 'state':
-              handleStateMessage(messageData, Boolean(isThreadMatching))
-              break;
-            case 'stream':
-              handleStreamMessage(messageData, Boolean(isThreadMatching))
-              break;
-            case 'message':
-              handleBotMessage(messageData)
-              break;
-          }
-        } else if (threadId !== messageData.thread_id) {
-          const threadInList = chatsList?.find((item) => item.thread_id === messageData.thread_id)
-          if (!threadInList) getChatsList()
-          if (currentStreamMessage) setCurrentStreamMessage(null)
-          if (wsLoading) setWsLoading(false);
-          if (isStreamingInProcess) setStreamingInProcess(false)
-        }
-      } else {
-        showMessage('An error occurred. Please try again')
-      }
-    } else {
-      showMessage('An error occurred. Please try again')
-    }
-
-    setLoading(false);
-  }
-
-  const handleDebugMessage = (message: DebugMessage) => {
-    let currentDebugMessages = [...(debugMessages || [])];
-    currentDebugMessages.push(message)
-    setDebugMessages(currentDebugMessages)
-  }
-
-  const handleStateMessage = (message: StateMessage, isThreadMatching?: boolean) => {
-    if (isThreadMatching || !threadId) {
-      if (message.state) {
-        setStateMessage(message)
-      } else {
-        setStateMessage(null)
-      }
-    }
-  }
-
-  const handleStreamMessage = (message: StreamMessage, isThreadMatching?: boolean) => {
-    if (isThreadMatching || !threadId) {
-      if (!isStreamingInProcess) setStreamingInProcess(true)
-      setCurrentStreamMessage(message)
-      setWsLoading(false);
-    }
-  }
-
-  const handleBotMessage = (message: BotMessage) => {
-    if (messages && messages.length > 0) {
-      let currentMessages = [...messages];
-      const lastMessage = currentMessages[currentMessages.length - 1];
-      if (lastMessage && !lastMessage.id && message.parent_id) {
-        lastMessage.id = message.parent_id;
-        lastMessage.created_at = message.created_at;
-        lastMessage.is_public = message.is_public;
-      }
-
-      currentMessages.push(message);
-      if (currentStreamMessage) setCurrentStreamMessage(null)
-      setMessages(currentMessages);
-      setWsLoading(false);
-      setStreamingInProcess(false);
-      if (document.visibilityState === "hidden") {
-        if (Notification.permission === "granted") {
-          new Notification("New message", {
-            body: 'New message from Postgres.AI Assistant',
-            icon: '/images/bot_avatar.png'
-          });
-        }
-      }
-    }
-  }
-
-  const onWebSocketOpen = () => {
-    console.log('WebSocket connection established');
-    if (threadId) {
-      subscribe(threadId)
-    }
-    setWsLoading(false);
-    closeSnackbar();
-  }
-  const onWebSocketClose = (event: WebSocketEventMap['close']) => {
-    console.log('WebSocket connection closed', event);
-    showMessage('WebSocket connection error: attempting to reconnect');
-  }
-
-  const { sendMessage: wsSendMessage, readyState, } = useWebSocket(WS_URL, {
-    protocols: ['Authorization', token || ''],
-    shouldReconnect: () => true,
-    reconnectAttempts: 50,
-    reconnectInterval: 5000, // ms
-    onError: onWebSocketError,
-    onMessage: onWebSocketMessage,
-    onClose: onWebSocketClose,
-    onOpen: onWebSocketOpen
-  })
-
-  const getChatMessages = useCallback(async (threadId: string) => {
-    setError(null);
-    setDebugMessages(null)
-    if (threadId) {
-      setLoading(true);
-      try {
-        const { response } = await getChatsWithWholeThreads({id: threadId});
-        subscribe(threadId)
-        if (response && response.length > 0) {
-          setMessages(response);
-        } else {
-          setError({
-            code: 404,
-            message: 'Specified chat not found or you have no access.',
-            type: 'chatNotFound'
-          })
-        }
-      } catch (e) {
-        setError(e as unknown as ErrorType)
-        showMessage('Connection error')
-      } finally {
-        setLoading(false);
-      }
-    }
-  }, []);
-
-  useEffect(() => {
-    let isCancelled = false;
-    setError(null);
-    setWsLoading(false);
-
-    if (threadId && !location.state?.skipReloading) {
-      getChatMessages(threadId)
-        .catch((e) => {
-          if (!isCancelled) {
-            setError(e);
-          }
-        });
-    } else if (threadId) {
-      subscribe(threadId)
-    }
-    return () => {
-      isCancelled = true;
-    };
-  }, [getChatMessages, location.state?.skipReloading, threadId]);
-
-  useEffect(() => {
-    const fetchData = async () => {
-      if (threadId) {
-        const { response } = await getChatsWithWholeThreads({id: threadId});
-        if (response && response.length > 0) {
-          setMessages(response);
-        }
-      }
-    };
-
-    let intervalId: NodeJS.Timeout | null = null;
-
-    if (readyState === ReadyState.CLOSED) {
-      intervalId = setInterval(fetchData, 20000);
-    }
-
-    return () => {
-      if (intervalId) {
-        clearInterval(intervalId);
-      }
-    };
-  }, [readyState, threadId]);
-
-  const sendMessage = async ({content, thread_id, org_id, is_public}: SendMessageType) => {
-    setWsLoading(true)
-    if (!thread_id) {
-      setLoading(true)
-    }
-    try {
-      //TODO: fix it
-      if (messages && messages.length > 0) {
-        setMessages((prevState) => [...prevState as BotMessage[], { content, is_ai: false, created_at: new Date().toISOString() } as BotMessage])
-      } else {
-        setMessages([{ content, is_ai: false, created_at: new Date().toISOString() } as BotMessage])
-      }
-      wsSendMessage(JSON.stringify({
-        action: 'send',
-        payload: {
-          content,
-          thread_id,
-          org_id,
-          is_public,
-          ai_model: `${aiModel?.vendor}/${aiModel?.name}`
-        }
-      }))
-      setError(error)
-
-    } catch (e) {
-      setError(e as unknown as ErrorType)
-    } finally {
-      setLoading(false)
-    }
-  }
-
-  const clearChat = () => {
-    setMessages(null);
-    setDebugMessages(null);
-    setWsLoading(false);
-  }
-
-  const changeChatVisibility = async (threadId: string, isPublic: boolean) => {
-    setIsChangeVisibilityLoading(true)
-    try {
-      const { error } = await updateChatVisibility({
-        thread_id: threadId,
-        is_public: isPublic
-      })
-      if (error) {
-        showMessage('Failed to change chat visibility. Please try again later.')
-      } else if (messages?.length) {
-        const newMessages: BotMessage[] = messages.map((message) => ({
-          ...message,
-          is_public: isPublic
-        }))
-        setMessages(newMessages)
-      }
-    } catch (e) {
-      showMessage('Failed to change chat visibility. Please try again later.')
-    } finally {
-      setIsChangeVisibilityLoading(false)
-    }
-  }
-
-  const subscribe = (threadId: string) => {
-    wsSendMessage(JSON.stringify({
-      action: 'subscribe',
-      payload: {
-        thread_id: threadId,
-      }
-    }))
-  }
-
-  const unsubscribe = (threadId: string) => {
-    wsSendMessage(JSON.stringify({
-      action: 'unsubscribe',
-      payload: {
-        thread_id: threadId,
-      }
-    }))
-  }
-
-  const getDebugMessagesForWholeThread = async () => {
-    setDebugMessagesLoading(true)
-    if (threadId) {
-      const { response } = await getDebugMessages({thread_id: threadId})
-      if (response) {
-        setDebugMessages(response)
-      }
-    }
-    setDebugMessagesLoading(false)
-  }
-
-  useEffect(() => {
-    if ('Notification' in window) {
-      Notification.requestPermission().then(permission => {
-        if (permission === "granted") {
-          console.log("Permission for notifications granted");
-        } else {
-          console.log("Permission for notifications denied");
-        }
-      });
-    }
-  }, [])
-
-  useEffect(() => {
-    if (messages && messages.length > 0 && threadId) {
-      setChatVisibility(messages[0].is_public ? 'public' : 'private')
-    }
-  }, [messages]);
-
-  return {
-    error: error,
-    wsLoading: wsLoading,
-    wsReadyState: readyState,
-    loading: isLoading,
-    changeChatVisibility,
-    isChangeVisibilityLoading,
-    sendMessage,
-    clearChat,
-    messages,
-    getDebugMessagesForWholeThread,
-    unsubscribe,
-    chatsList,
-    chatsListLoading,
-    getChatsList,
-    aiModel,
-    setAiModel,
-    aiModels,
-    aiModelsLoading,
-    chatVisibility,
-    debugMessages,
-    debugMessagesLoading,
-    stateMessage,
-    isStreamingInProcess,
-    currentStreamMessage
-  }
-}
-
-type AiBotContextType = UseAiBotReturnType;
-
-const AiBotContext = createContext<AiBotContextType | undefined>(undefined);
-
-type AiBotProviderProps = {
-  children: React.ReactNode;
-  args: UseAiBotArgs;
-};
-
-export const AiBotProvider = ({ children, args }: AiBotProviderProps) => {
-  const aiBot = useAiBotProviderValue(args);
-  return (
-    <AiBotContext.Provider value={aiBot}>
-      {children}
-      </AiBotContext.Provider>
-  );
-};
-
-export const useAiBot = (): AiBotContextType => {
-  const context = useContext(AiBotContext);
-  if (context === undefined) {
-    throw new Error('useAiBotContext must be used within an AiBotProvider');
-  }
-  return context;
-};
-
-type UseBotChatsListHook = {
-  chatsList: BotMessage[] | null;
-  error: Response | null;
-  loading: boolean;
-  getChatsList: () => void;
-};
-
-export const useBotChatsList = (orgId?: number): UseBotChatsListHook => {
-  const [chatsList, setChatsList] = useState<BotMessage[] | null>(null);
-  const [isLoading, setLoading] = useState<boolean>(false);
-  const [error, setError] = useState<Response | null>(null)
-
-  const getChatsList = useCallback(async () => {
-    setLoading(true);
-    try {
-      const queryString = `?parent_id=is.null&org_id=eq.${orgId}`;
-      const { response, error } = await getChats({ query: queryString });
-
-      setChatsList(response);
-      setError(error)
-
-    } catch (e) {
-      setError(e as unknown as Response)
-    } finally {
-      setLoading(false)
-    }
-  }, []);
-
-  useEffect(() => {
-    let isCancelled = false;
-
-    getChatsList()
-      .catch((e) => {
-        if (!isCancelled) {
-          setError(e);
-        }
-      });
-
-    return () => {
-      isCancelled = true;
-    };
-  }, [getChatsList]);
-
-  return {
-    chatsList,
-    error,
-    getChatsList,
-    loading: isLoading
-  }
-}
-
-type UseAiModelsList = {
-  aiModels: AiModel[] | null
-  error: Response | null
-  aiModel: AiModel | null
-  loading: boolean
-  setAiModel: (model: AiModel) => void
-}
-
-const useAiModelsList = (): UseAiModelsList => {
-  const [llmModels, setLLMModels] = useState<UseAiModelsList['aiModels']>(null);
-  const [error, setError] = useState<Response | null>(null);
-  const [userModel, setUserModel] = useState<AiModel | null>(null);
-  const [loading, setLoading] = useState(false)
-
-  const getModels = useCallback(async () => {
-    let models = null;
-    setLoading(true);
-    try {
-      const { response } = await getAiModels();
-      setLLMModels(response);
-      const currentModel = window.localStorage.getItem('bot.ai_model');
-      const parsedModel: AiModel = currentModel ? JSON.parse(currentModel) : null;
-
-      if (currentModel && parsedModel.name !== userModel?.name && response) {
-        // Check if the parsedModel exists in the response models
-        const modelInResponse = response.find(
-          (model) =>
-            model.name.includes(parsedModel.name)
-        );
-
-        if (modelInResponse) {
-          setUserModel(modelInResponse);
-          window.localStorage.setItem('bot.ai_model', JSON.stringify(modelInResponse));
-        } else {
-          // Model from localStorage does not exist in response
-          // Find a default model
-          const defaultModel = response.find((model) =>
-            model.name.includes(DEFAULT_MODEL_NAME)
-          );
-
-          if (defaultModel) {
-            setUserModel(defaultModel);
-            window.localStorage.setItem('bot.ai_model', JSON.stringify(defaultModel));
-          }
-        }
-      } else if (response) {
-        // Find a model where the model name includes the DEFAULT_MODEL_NAME
-        const matchingModel = response.find((model) =>
-          model.name.includes(DEFAULT_MODEL_NAME)
-        );
-        if (matchingModel) {
-          setModel(matchingModel);
-          window.localStorage.setItem('bot.ai_model', JSON.stringify(matchingModel));
-        }
-      }
-    } catch (e) {
-      setError(e as unknown as Response);
-    }
-    setLoading(false);
-    return models;
-  }, []);
-
-
-  useEffect(() => {
-    let isCancelled = false;
-
-    getModels()
-      .catch((e) => {
-        if (!isCancelled) {
-          setError(e);
-        }
-      });
-    return () => {
-      isCancelled = true;
-    };
-  }, [getModels]);
-
-  const setModel = (model: AiModel) => {
-    if (model !== userModel) {
-      setUserModel(model);
-      window.localStorage.setItem('bot.ai_model', JSON.stringify(model))
-    }
-  }
-
-  return {
-    aiModels: llmModels,
-    error,
-    setAiModel: setModel,
-    loading,
-    aiModel: userModel,
-  }
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/index.tsx b/ui/packages/platform/src/pages/Bot/index.tsx
deleted file mode 100644
index 056883fb..00000000
--- a/ui/packages/platform/src/pages/Bot/index.tsx
+++ /dev/null
@@ -1,237 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { useEffect, useState } from 'react';
-import cn from "classnames";
-import Box from '@mui/material/Box/Box';
-import { makeStyles, useMediaQuery } from "@material-ui/core";
-import { useHistory, useRouteMatch } from "react-router-dom";
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper';
-import { ErrorWrapper } from "../../components/Error/ErrorWrapper";
-import { Messages } from './Messages/Messages';
-import { Command } from './Command/Command';
-import { ChatsList } from "./ChatsList/ChatsList";
-import { BotWrapperProps } from "./BotWrapper";
-import { useAiBot } from "./hooks";
-import { usePrev } from "../../hooks/usePrev";
-import {HeaderButtons} from "./HeaderButtons/HeaderButtons";
-import settings from "../../utils/settings";
-import { SettingsDialog } from "./SettingsDialog/SettingsDialog";
-import { theme } from "@postgres.ai/shared/styles/theme";
-import { colors } from "@postgres.ai/shared/styles/colors";
-import { SettingsPanel } from "./SettingsPanel/SettingsPanel";
-import { DebugConsole } from "./DebugConsole/DebugConsole";
-
-type BotPageProps = BotWrapperProps;
-
-const useStyles = makeStyles(
-  (theme) => ({
-    actions: {
-      display: 'flex',
-      alignItems: 'center',
-      alignSelf: 'flex-end',
-      marginTop: -20,
-      [theme.breakpoints.down('sm')]: {
-        marginTop: -22
-      }
-    },
-    hiddenButtons: {
-      width: 192,
-      marginLeft: 52,
-      [theme.breakpoints.down('sm')]: {
-        width: 'min(100%, 320px)',
-        marginLeft: 'auto'
-      }
-    },
-    toggleListButton: {
-      flex: '0 0 auto',
-    },
-    contentContainer: {
-      height: '100%',
-      display: 'flex',
-      flexDirection: 'column',
-      flexGrow: 1,
-      transition: theme.transitions.create('margin', {
-        easing: theme.transitions.easing.sharp,
-        duration: theme.transitions.duration.leavingScreen,
-      }),
-      marginRight: 4,
-    },
-    isChatsListVisible: {
-      transition: theme.transitions.create('margin', {
-        easing: theme.transitions.easing.easeOut,
-        duration: theme.transitions.duration.enteringScreen,
-      }),
-      marginRight: 244,
-      [theme.breakpoints.down('sm')]: {
-        marginRight: 0,
-      }
-    },
-    label: {
-      backgroundColor: colors.primary.main,
-      color: colors.primary.contrastText,
-      display: 'inline-block',
-      borderRadius: 3,
-      fontSize: 10,
-      lineHeight: '12px',
-      padding: 2,
-      paddingLeft: 3,
-      paddingRight: 3,
-      verticalAlign: 'text-top',
-      marginRight: 8
-    },
-    labelPrivate: {
-      backgroundColor: colors.pgaiDarkGray,
-    }
-  }),
-  { index: 1 },
-)
-
-export const BotPage = (props: BotPageProps) => {
-  const { match, project, orgData } = props;
-
-  const {
-    messages,
-    error,
-    clearChat,
-    unsubscribe,
-    getDebugMessagesForWholeThread,
-    getChatsList,
-  } = useAiBot();
-
-  const matches = useMediaQuery(theme.breakpoints.down('sm'));
-
-  const [isChatsListVisible, setChatsListVisible] = useState(window?.innerWidth > 640);
-  const [isSettingsDialogVisible, setSettingsDialogVisible] = useState(false);
-  const [isDebugConsoleVisible, setDebugConsoleVisible] = useState(false);
-
-  const history = useHistory();
-
-  const prevThreadId = usePrev(match.params.threadId);
-
-  const isDemoOrg = useRouteMatch(`/${settings.demoOrgAlias}`);
-
-  const classes = useStyles();
-
-  const breadcrumbs = (
-    <ConsoleBreadcrumbsWrapper
-      org={match.params.org}
-      project={project}
-      breadcrumbs={[
-        { name: 'Assistant', url: 'assistant' },
-      ]}
-    />
-  );
-
-  const toggleChatsList = () => {
-    setChatsListVisible((prevState) => !prevState)
-  }
-
-  const toggleSettingsDialog = () => {
-    setSettingsDialogVisible((prevState) => !prevState)
-  }
-
-  const handleOpenDebugConsole = () => {
-    setDebugConsoleVisible(true);
-    getDebugMessagesForWholeThread()
-  }
-
-  const toggleDebugConsole = () => {
-    setDebugConsoleVisible((prevState) => !prevState)
-  }
-
-  const handleCreateNewChat = () => {
-    clearChat();
-    history.push(`/${match.params.org}/assistant`);
-  }
-
-  const handleChatListLinkClick = (targetThreadId: string) => {
-    if (match.params.threadId && match.params.threadId !== targetThreadId) {
-      unsubscribe(match.params.threadId)
-    }
-  }
-
-  useEffect(() => {
-    if (!match.params.threadId && !prevThreadId && messages && messages.length > 0 && messages[0].id) {
-      // hack that skip additional loading chats_ancestors_and_descendants
-      history.replace(`/${match.params.org}/assistant/${messages[0].id}`, { skipReloading: true })
-      getChatsList();
-    } else if (prevThreadId && !match.params.threadId) {
-      clearChat()
-    }
-  }, [match.params.threadId, match.params.org, messages, prevThreadId]);
-
-  useEffect(() => {
-    // fixes hack with skipping additional loading chats_ancestors_and_descendants
-    history.replace({ state: {} })
-  }, []);
-
-  useEffect(() => {
-    if (isDebugConsoleVisible) setDebugConsoleVisible(false)
-  }, [match.params.threadId]);
-
-  if (error && error.code === 404) {
-    return (
-      <>
-        {breadcrumbs}
-        <ErrorWrapper
-          message={error.message}
-          code={error.code}
-        />
-      </>
-    )
-  }
-
-  return (
-    <>
-      <DebugConsole
-        onClose={toggleDebugConsole}
-        isOpen={isDebugConsoleVisible}
-        threadId={match.params.threadId}
-      />
-      <SettingsDialog
-        isOpen={isSettingsDialogVisible}
-        onClose={toggleSettingsDialog}
-        threadId={match.params.threadId || null}
-      />
-      <ChatsList
-        isOpen={isChatsListVisible}
-        onCreateNewChat={handleCreateNewChat}
-        onClose={toggleChatsList}
-        isDemoOrg={Boolean(isDemoOrg)}
-        withChatVisibilityButton={matches && Boolean(match.params.threadId)}
-        onSettingsClick={toggleSettingsDialog}
-        onLinkClick={handleChatListLinkClick}
-        onConsoleClick={handleOpenDebugConsole}
-      />
-      <Box className={classes.actions}>
-        {!matches &&
-          <SettingsPanel
-            onSettingsClick={toggleSettingsDialog}
-            onConsoleClick={handleOpenDebugConsole}
-          />}
-        <Box className={classes.hiddenButtons}>
-          <HeaderButtons
-            isOpen={isChatsListVisible}
-            onClose={toggleChatsList}
-            onCreateNewChat={handleCreateNewChat}
-            withChatVisibilityButton={matches && Boolean(match.params.threadId)}
-            onSettingsClick={toggleSettingsDialog}
-            onConsoleClick={handleOpenDebugConsole}
-          />
-        </Box>
-      </Box>
-      <Box className={cn(classes.contentContainer, {[classes.isChatsListVisible]: isChatsListVisible})}>
-        <Messages orgId={orgData.id} />
-        <Command
-          threadId={match.params.threadId}
-          orgId={orgData.id}
-        />
-      </Box>
-    </>
-  )
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Bot/utils.ts b/ui/packages/platform/src/pages/Bot/utils.ts
deleted file mode 100644
index 440c8ee2..00000000
--- a/ui/packages/platform/src/pages/Bot/utils.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { API_URL_PREFIX } from "../../config/env";
-import { DebugMessage } from "../../types/api/entities/bot";
-import { HintType } from "./hints";
-import { FunctionComponent } from "react";
-import { ArrowGrowthIcon } from "./HintCards/ArrowGrowthIcon/ArrowGrowthIcon";
-import { WrenchIcon } from "./HintCards/WrenchIcon/WrenchIcon";
-import { TableIcon } from "./HintCards/TableIcon/TableIcon";
-import { CommonTypeIcon } from "./HintCards/CommonTypeIcon/CommonTypeIcon";
-
-export const permalinkLinkBuilder = (id: string): string => {
-  const apiUrl = process.env.REACT_APP_API_URL_PREFIX || API_URL_PREFIX;
-  const isV2API = /https?:\/\/.*v2\.postgres\.ai\b/.test(apiUrl);
-  return `https://${isV2API ? 'v2.' : ''}postgres.ai/chats/${id}`;
-};
-
-export const disallowedHtmlTagsForMarkdown= [
-  'script',
-  'style',
-  'iframe',
-  'form',
-  'input',
-  'link',
-  'meta',
-  'embed',
-  'object',
-  'applet',
-  'base',
-  'frame',
-  'frameset',
-  'audio',
-  'video',
-  'button',
-  'select',
-  'option',
-  'textarea'
-];
-
-export const createMessageFragment = (messages: DebugMessage[]): DocumentFragment => {
-  const fragment = document.createDocumentFragment();
-
-  messages.forEach((item) => {
-    const textBeforeLink = `[${item.created_at}]: `;
-    const parts = item.content.split(/(https?:\/\/[^\s)"']+)/g);
-
-    const messageContent = parts.map((part) => {
-      if(/https?:\/\/[^\s)"']+/.test(part)) {
-        const link = document.createElement('a');
-        link.href = part;
-        link.target = '_blank';
-        link.textContent = part;
-        return link;
-      } else {
-        return document.createTextNode(part);
-      }
-    });
-
-    fragment.appendChild(document.createTextNode(textBeforeLink));
-    messageContent.forEach((node) => fragment.appendChild(node));
-    fragment.appendChild(document.createTextNode('\n'));
-  });
-
-  return fragment;
-};
-
-export const formatLanguageName = (language: string): string => {
-  const specificCases: { [key: string]: string } = {
-    "sql": "SQL",
-    "pl/pgsql": "PL/pgSQL",
-    "pl/python": "PL/Python",
-    "json": "JSON",
-    "yaml": "YAML",
-    "html": "HTML",
-    "xml": "XML",
-    "css": "CSS",
-    "csv": "CSV",
-    "toml": "TOML",
-    "ini": "INI",
-    "r": "R",
-    "php": "PHP",
-    "sqlalchemy": "SQLAlchemy",
-    "xslt": "XSLT",
-    "xsd": "XSD",
-    "ajax": "AJAX",
-    "tsql": "TSQL",
-    "pl/sql": "PL/SQL",
-    "dax": "DAX",
-    "sparql": "SPARQL"
-  };
-
-  const normalizedLanguage = language.toLowerCase();
-
-  if (specificCases[normalizedLanguage]) {
-    return specificCases[normalizedLanguage];
-  } else {
-    return language.charAt(0).toUpperCase() + language.slice(1).toLowerCase();
-  }
-}
-
-export function matchHintTypeAndIcon(hintType: HintType): FunctionComponent<SVGElement> {
-  switch (hintType) {
-    case 'performance':
-      return ArrowGrowthIcon
-    case 'settings':
-      return WrenchIcon
-    case 'design':
-      return TableIcon
-    default:
-      return CommonTypeIcon
-  }
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/pages/Clone/index.tsx b/ui/packages/platform/src/pages/Clone/index.tsx
deleted file mode 100644
index f2423000..00000000
--- a/ui/packages/platform/src/pages/Clone/index.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useParams } from 'react-router-dom'
-
-import { Clone as ClonePage } from '@postgres.ai/shared/pages/Clone'
-
-import { getSnapshots } from 'api/snapshots/getSnapshots'
-import { getInstance } from 'api/instances/getInstance'
-import { getClone } from 'api/clones/getClone'
-import { resetClone } from 'api/clones/resetClone'
-import { destroyClone } from 'api/clones/destroyClone'
-import { updateClone } from 'api/clones/updateClone'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-import { ROUTES } from 'config/routes'
-
-type Params = {
-  org: string
-  project?: string
-  instanceId: string
-  cloneId: string
-}
-
-export const Clone = () => {
-  const params = useParams<Params>()
-
-  const routes = {
-    instance: () =>
-      params.project
-        ? ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.createPath({
-            org: params.org,
-            project: params.project,
-            instanceId: params.instanceId,
-          })
-        : ROUTES.ORG.INSTANCES.INSTANCE.createPath(params),
-  }
-
-  const api = {
-    getSnapshots,
-    getInstance,
-    getClone,
-    resetClone,
-    destroyClone,
-    updateClone,
-  }
-
-  const elements = {
-    breadcrumbs: (
-      <ConsoleBreadcrumbsWrapper
-        org={params.org}
-        project={params.project}
-        hasDivider
-        breadcrumbs={[
-          { name: 'Database Lab Instances', url: 'instances' },
-          { name: 'Instance #' + params.instanceId, url: params.instanceId },
-          {
-            name: 'Clone ' + params.cloneId,
-            url: null,
-          },
-        ]}
-      />
-    ),
-  }
-
-  return (
-    <ClonePage
-      instanceId={params.instanceId}
-      cloneId={params.cloneId}
-      routes={routes}
-      api={api}
-      elements={elements}
-    />
-  )
-}
diff --git a/ui/packages/platform/src/pages/CreateClone/index.tsx b/ui/packages/platform/src/pages/CreateClone/index.tsx
deleted file mode 100644
index 2ee905ef..00000000
--- a/ui/packages/platform/src/pages/CreateClone/index.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { useParams } from 'react-router-dom'
-
-import { CreateClone as CreateClonePage } from '@postgres.ai/shared/pages/CreateClone'
-
-import { ROUTES } from 'config/routes'
-import { getInstance } from 'api/instances/getInstance'
-import { getSnapshots } from 'api/snapshots/getSnapshots'
-import { createClone } from 'api/clones/createClone'
-import { getClone } from 'api/clones/getClone'
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-
-type Params = {
-  org: string
-  project?: string
-  instanceId: string
-}
-
-export const CreateClone = () => {
-  const params = useParams<Params>()
-
-  const routes = {
-    clone: (cloneId: string) =>
-      params.project
-        ? ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.CLONES.CLONE.createPath({
-            org: params.org,
-            project: params.project,
-            instanceId: params.instanceId,
-            cloneId,
-          })
-        : ROUTES.ORG.INSTANCES.INSTANCE.CLONES.CLONE.createPath({
-            ...params,
-            cloneId,
-          }),
-  }
-
-  const api = {
-    getSnapshots,
-    getInstance,
-    createClone,
-    getClone,
-  }
-
-  const elements = {
-    breadcrumbs: (
-      <ConsoleBreadcrumbsWrapper
-        hasDivider
-        org={params.org}
-        project={params.project}
-        breadcrumbs={[
-          { name: 'Database Lab Instances', url: 'instances' },
-          { name: 'Instance #' + params.instanceId, url: params.instanceId },
-          { name: 'Create clone', url: null },
-        ]}
-      />
-    ),
-  }
-
-  return (
-    <CreateClonePage
-      instanceId={params.instanceId}
-      routes={routes}
-      // navRoutes={[ROUTES, ROUTES.INSTANCE, ROUTES.INSTANCE.CLONES.CREATE]}
-      api={api}
-      elements={elements}
-    />
-  )
-}
diff --git a/ui/packages/platform/src/pages/Instance/index.tsx b/ui/packages/platform/src/pages/Instance/index.tsx
deleted file mode 100644
index 4bef5512..00000000
--- a/ui/packages/platform/src/pages/Instance/index.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { useState } from 'react'
-import { useParams } from 'react-router-dom'
-
-import { Instance as InstancePage } from '@postgres.ai/shared/pages/Instance'
-
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper'
-import { ROUTES } from 'config/routes'
-import { getInstance } from 'api/instances/getInstance'
-import { refreshInstance } from 'api/instances/refreshInstance'
-import { getSnapshots } from 'api/snapshots/getSnapshots'
-import { destroyClone } from 'api/clones/destroyClone'
-import { resetClone } from 'api/clones/resetClone'
-import { bannersStore } from 'stores/banners'
-import { getWSToken } from 'api/instances/getWSToken'
-import { getConfig } from 'api/configs/getConfig'
-import { getFullConfig } from 'api/configs/getFullConfig'
-import { getSeImages } from 'api/configs/getSeImages'
-import { testDbSource } from 'api/configs/testDbSource'
-import { updateConfig } from 'api/configs/updateConfig'
-import { getEngine } from 'api/engine/getEngine'
-import { initWS } from 'api/engine/initWS'
-
-type Params = {
-  org: string
-  project?: string
-  instanceId: string
-}
-
-export const Instance = () => {
-  const params = useParams<Params>()
-  const [projectAlias, setProjectAlias] = useState<string>('')
-
-  const routes = {
-    createClone: () =>
-      params.project
-        ? ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.CLONES.ADD.createPath({
-            org: params.org,
-            project: params.project,
-            instanceId: params.instanceId,
-          })
-        : ROUTES.ORG.INSTANCES.INSTANCE.CLONES.ADD.createPath(params),
-
-    clone: (cloneId: string) =>
-      params.project
-        ? ROUTES.ORG.PROJECT.INSTANCES.INSTANCE.CLONES.CLONE.createPath({
-            org: params.org,
-            project: params.project,
-            instanceId: params.instanceId,
-            cloneId,
-          })
-        : ROUTES.ORG.INSTANCES.INSTANCE.CLONES.CLONE.createPath({
-            ...params,
-            cloneId,
-          }),
-  }
-
-  const api = {
-    getInstance,
-    getSnapshots,
-    destroyClone,
-    refreshInstance,
-    resetClone,
-    getWSToken,
-    getConfig,
-    getFullConfig,
-    getSeImages,
-    updateConfig,
-    testDbSource,
-    getEngine,
-    initWS,
-  }
-
-  const callbacks = {
-    showDeprecatedApiBanner: bannersStore.showDeprecatedApi,
-    hideDeprecatedApiBanner: bannersStore.hideDeprecatedApi,
-  }
-
-  const instanceTitle = `#${params.instanceId} ${
-    projectAlias
-      ? `(${projectAlias})`
-      : params.project
-      ? `(${params.project})`
-      : ''
-  }`
-
-  const elements = {
-    breadcrumbs: (
-      <ConsoleBreadcrumbsWrapper
-        hasDivider
-        org={params.org}
-        project={params.project}
-        breadcrumbs={[
-          { name: 'Database Lab Instances', url: 'instances' },
-          {
-            name: `Instance ${instanceTitle}`,
-            url: null,
-          },
-        ]}
-      />
-    ),
-  }
-
-  return (
-    <InstancePage
-      isPlatform
-      setProjectAlias={setProjectAlias}
-      title={`Database Lab instance ${instanceTitle}`}
-      instanceId={params.instanceId}
-      routes={routes}
-      api={api}
-      callbacks={callbacks}
-      elements={elements}
-    />
-  )
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/Command/index.tsx b/ui/packages/platform/src/pages/JoeInstance/Command/index.tsx
deleted file mode 100644
index 55b0b649..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Command/index.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import React, { useState, useRef, useEffect } from 'react'
-import { makeStyles } from '@material-ui/core'
-
-import { Button } from '@postgres.ai/shared/components/Button'
-import { TextField } from '@postgres.ai/shared/components/TextField'
-
-
-import {
-  checkIsSendCmd,
-  checkIsNewLineCmd,
-  addNewLine,
-  checkIsPrevMessageCmd,
-  checkIsNextMessageCmd,
-} from './utils'
-
-import { useBuffer } from './useBuffer'
-import { useCaret } from './useCaret'
-
-type Props = {
-  isDisabled: boolean
-  onSend: (value: string) => void
-}
-
-const LABEL_FONT_SIZE = '14px'
-
-const useStyles = makeStyles(
-  {
-    root: {
-      display: 'flex',
-      alignItems: 'flex-end',
-      marginTop: '20px',
-    },
-    field: {
-      margin: '0 8px 0 0',
-      flex: '1 1 100%',
-      fontSize: LABEL_FONT_SIZE,
-    },
-    fieldInput: {
-      fontSize: '14px',
-      lineHeight: 'normal',
-      height: 'auto',
-      padding: '12px',
-    },
-    fieldLabel: {
-      fontSize: LABEL_FONT_SIZE,
-    },
-    button: {
-      flex: '0 0 auto',
-      height: '40px',
-    },
-  },
-  { index: 1 },
-)
-
-export const Command = React.memo((props: Props) => {
-  const { isDisabled, onSend } = props
-
-  const classes = useStyles()
-
-  // Handle value.
-  const [value, setValue] = useState('')
-
-  // Input DOM Element reference.
-  const inputRef = useRef<HTMLTextAreaElement | HTMLInputElement>()
-
-  // Messages buffer.
-  const buffer = useBuffer()
-
-  // Input caret.
-  const caret = useCaret(inputRef)
-
-  const triggerSend = () => {
-    if (!value.trim().length) return
-
-    onSend(value)
-    buffer.addNew()
-    setValue(buffer.getCurrent())
-  }
-
-  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-    setValue(e.target.value)
-    buffer.switchToLast()
-    buffer.setToCurrent(e.target.value)
-  }
-
-  const handleKeyDown = (e: React.KeyboardEvent) => {
-    if (!inputRef.current) return
-
-    // Trigger to send.
-    if (checkIsSendCmd(e.nativeEvent)) {
-      e.preventDefault()
-      triggerSend()
-      return
-    }
-
-    // Trigger line break.
-    if (checkIsNewLineCmd(e.nativeEvent)) {
-      e.preventDefault()
-
-      const content = addNewLine(value, inputRef.current)
-
-      setValue(content.value)
-      caret.setPosition(content.caretPosition)
-      return
-    }
-
-    // Trigger to use prev message.
-    if (checkIsPrevMessageCmd(e.nativeEvent, inputRef.current)) {
-      e.preventDefault()
-
-      const prevValue = buffer.switchPrev()
-      setValue(prevValue)
-      return
-    }
-
-    // Trigger to use next message.
-    if (checkIsNextMessageCmd(e.nativeEvent, inputRef.current)) {
-      e.preventDefault()
-
-      const nextValue = buffer.switchNext()
-      setValue(nextValue)
-      return
-    }
-
-    // Skip other keyboard events to fill input.
-  }
-
-  // Autofocus.
-  useEffect(() => {
-    if (!inputRef.current) return
-
-    inputRef.current.focus()
-  }, [])
-
-  return (
-    <div className={classes.root}>
-      <TextField
-        autoFocus={true}
-        label="Command"
-        multiline
-        className={classes.field}
-        onKeyDown={handleKeyDown}
-        disabled={isDisabled}
-        InputProps={{
-          inputRef,
-          classes: {
-            input: classes.fieldInput,
-          },
-        }}
-        InputLabelProps={{
-          className: classes.fieldLabel,
-        }}
-        value={value}
-        onChange={handleChange}
-      />
-      <Button
-        className={classes.button}
-        onClick={triggerSend}
-      >
-        Send
-      </Button>
-    </div>
-  )
-})
diff --git a/ui/packages/platform/src/pages/JoeInstance/Command/useBuffer.ts b/ui/packages/platform/src/pages/JoeInstance/Command/useBuffer.ts
deleted file mode 100644
index 0368bc44..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Command/useBuffer.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { useRef } from 'react'
-
-type Buffer = {
-  values: string[]
-  position: number
-}
-
-const NEW_EMPTY_VALUE = ''
-
-const INITIAL_BUFFER: Buffer = {
-  values: [NEW_EMPTY_VALUE],
-  position: 0,
-}
-
-export const useBuffer = () => {
-  const { current: buffer } = useRef<Buffer>(INITIAL_BUFFER)
-
-  const getCurrent = () => buffer.values[buffer.position]
-
-  const setToCurrent = (value: string) => buffer.values[buffer.position] = value
-
-  const switchNext = () => {
-    const newPosition = buffer.position + 1
-    if (newPosition in buffer.values) buffer.position = newPosition
-    return getCurrent()
-  }
-
-  const switchPrev = () => {
-    const newPosition = buffer.position - 1
-    if (newPosition in buffer.values) buffer.position = newPosition
-    return getCurrent()
-  }
-
-  const switchToLast = () => {
-    const lastIndex = buffer.values.length - 1
-    buffer.position = lastIndex
-    return getCurrent()
-  }
-
-  const addNew = () => {
-    buffer.values.push(NEW_EMPTY_VALUE)
-    return switchToLast()
-  }
-
-  return {
-    switchNext,
-    switchPrev,
-    switchToLast,
-    addNew,
-    getCurrent,
-    setToCurrent,
-  }
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/Command/useCaret.ts b/ui/packages/platform/src/pages/JoeInstance/Command/useCaret.ts
deleted file mode 100644
index 8de590a5..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Command/useCaret.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useState, useEffect, MutableRefObject } from 'react'
-
-export const useCaret = (
-  elementRef: MutableRefObject<HTMLInputElement | HTMLTextAreaElement | undefined>,
-) => {
-  // Keep caret position after making new line, but only after react update.
-  const [nextPosition, setNextPosition] = useState<number | null>(null)
-
-  useEffect(() => {
-    if (nextPosition === null) return
-    if (!elementRef.current) return
-
-    elementRef.current.selectionStart = nextPosition
-    elementRef.current.selectionEnd = nextPosition
-
-    setNextPosition(null)
-  }, [elementRef, nextPosition])
-
-  return {
-    setPosition: setNextPosition,
-  }
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/Command/utils.ts b/ui/packages/platform/src/pages/JoeInstance/Command/utils.ts
deleted file mode 100644
index 47b68f46..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Command/utils.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-export const checkIsSendCmd = (e: KeyboardEvent) =>
-  e.code === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey
-
-export const checkIsNewLineCmd = (e: KeyboardEvent) =>
-  e.code === 'Enter' && (e.shiftKey || e.ctrlKey || e.metaKey)
-
-export const addNewLine = (
-  value: string,
-  element: HTMLInputElement | HTMLTextAreaElement,
-) => {
-  const NEW_LINE_STR = '\n'
-
-  const firstLineLength = element.selectionStart ?? value.length
-  const secondLineLength = element.selectionEnd ?? value.length
-
-  const firstLine = value.substring(0, firstLineLength)
-  const secondLine = value.substring(secondLineLength)
-
-  return {
-    value: `${firstLine}${NEW_LINE_STR}${secondLine}`,
-    caretPosition: firstLineLength + NEW_LINE_STR.length,
-  }
-}
-
-export const checkIsPrevMessageCmd = (
-  e: KeyboardEvent,
-  element: HTMLInputElement | HTMLTextAreaElement,
-) => {
-  const isRightKey =
-    e.code === 'ArrowUp' && !e.ctrlKey && !e.metaKey && !e.shiftKey
-
-  // Use prev message only if the caret is in the start of the input.
-  const targetCaretPosition = 0
-
-  const isRightCaretPosition =
-    element.selectionStart === targetCaretPosition &&
-    element.selectionEnd === targetCaretPosition
-
-  return isRightKey && isRightCaretPosition
-}
-
-export const checkIsNextMessageCmd = (
-  e: KeyboardEvent,
-  element: HTMLInputElement | HTMLTextAreaElement,
-) => {
-  const isRightKey =
-    e.code === 'ArrowDown' && !e.ctrlKey && !e.metaKey && !e.shiftKey
-
-  // Use next message only if the caret is in the end of the input.
-  const targetCaretPosition = element.value.length
-
-  const isRightCaretPosition =
-    element.selectionStart === targetCaretPosition &&
-    element.selectionEnd === targetCaretPosition
-
-  return isRightKey && isRightCaretPosition
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/JoeInstanceWrapper.jsx b/ui/packages/platform/src/pages/JoeInstance/JoeInstanceWrapper.jsx
deleted file mode 100644
index c9984d10..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/JoeInstanceWrapper.jsx
+++ /dev/null
@@ -1,268 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import { colors } from '@postgres.ai/shared/styles/colors'
-import { styles } from '@postgres.ai/shared/styles/styles'
-import JoeInstance from 'pages/JoeInstance'
-
-export const JoeInstanceWrapper = (props) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      messageArtifacts: {
-        'box-shadow': 'none',
-        'min-height': 20,
-        'font-size': '14px',
-        'margin-top': '15px!important',
-        '& > .MuiExpansionPanelSummary-root': {
-          minHeight: 20,
-          backgroundColor: '#FFFFFF',
-          border: '1px solid',
-          borderColor: colors.secondary2.main,
-          width: 'auto',
-          color: colors.secondary2.main,
-          borderRadius: 3,
-          overflow: 'hidden',
-        },
-        '& > .MuiExpansionPanelSummary-root.Mui-expanded': {
-          minHeight: 20,
-        },
-        '& .MuiCollapse-hidden': {
-          height: '0px!important',
-        },
-      },
-      advancedExpansionPanelSummary: {
-        'justify-content': 'left',
-        padding: '0px',
-        'background-color': '#FFFFFF',
-        width: 'auto',
-        color: colors.secondary2.main,
-        'border-radius': '3px',
-        'padding-left': '15px',
-        display: 'inline-block',
-
-        '& div.MuiExpansionPanelSummary-content': {
-          'flex-grow': 'none',
-          margin: 0,
-          '& > p.MuiTypography-root.MuiTypography-body1': {
-            'font-size': '12px!important',
-          },
-          display: 'inline-block',
-        },
-        '& > .MuiExpansionPanelSummary-expandIcon': {
-          marginRight: 0,
-          padding: '5px!important',
-          color: colors.secondary2.main,
-        },
-      },
-      advancedExpansionPanelDetails: {
-        padding: 5,
-        'padding-left': 0,
-        'padding-right': 0,
-        'font-size': '14px!important',
-        '& > p': {
-          'font-size': '14px!important',
-        },
-      },
-      messageArtifactLink: {
-        cursor: 'pointer',
-        color: '#0000ee',
-      },
-      messageArtifactTitle: {
-        fontWeight: 'normal',
-        marginBottom: 0,
-      },
-      messageArtifact: {
-        'box-shadow': 'none',
-        'min-height': 20,
-        'font-size': '14px',
-        '&.MuiExpansionPanel-root.Mui-expanded': {
-          marginBottom: 0,
-          marginTop: 0,
-        },
-        '& > .MuiExpansionPanelSummary-root': {
-          minHeight: 20,
-          backgroundColor: '#FFFFFF',
-          border: 'none',
-          width: 'auto',
-          color: colors.secondary2.darkDark,
-          borderRadius: 3,
-          overflow: 'hidden',
-          marginBottom: 0,
-        },
-        '& > .MuiExpansionPanelSummary-root.Mui-expanded': {
-          minHeight: 20,
-        },
-      },
-      messageArtifactExpansionPanelSummary: {
-        'justify-content': 'left',
-        padding: '5px',
-        'background-color': '#FFFFFF',
-        width: 'auto',
-        color: colors.secondary2.darkDark,
-        'border-radius': '3px',
-        'padding-left': '0px',
-        display: 'inline-block',
-
-        '& div.MuiExpansionPanelSummary-content': {
-          'flex-grow': 'none',
-          margin: 0,
-          '& > p.MuiTypography-root.MuiTypography-body1': {
-            'font-size': '12px!important',
-          },
-          display: 'inline-block',
-        },
-        '& > .MuiExpansionPanelSummary-expandIcon': {
-          marginRight: 0,
-          padding: '5px!important',
-          color: colors.secondary2.darkDark,
-        },
-      },
-      messageArtifactExpansionPanelDetails: {
-        padding: 5,
-        'padding-right': 0,
-        'padding-left': 0,
-        'font-size': '14px!important',
-        '& > p': {
-          'font-size': '14px!important',
-        },
-      },
-      code: {
-        width: '100%',
-        'margin-top': 0,
-        '& > div': {
-          paddingTop: 8,
-          padding: 8,
-        },
-        'background-color': 'rgb(246, 248, 250)',
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: '13px',
-        },
-      },
-      messageArtifactsContainer: {
-        width: '100%',
-      },
-      heading: {
-        fontWeight: 'normal',
-        fontSize: 12,
-        color: colors.secondary2.main,
-      },
-      message: {
-        padding: 10,
-        'padding-left': 60,
-        position: 'relative',
-
-        '& .markdown pre': {
-          [theme.breakpoints.down('sm')]: {
-            display: 'inline-block',
-            minWidth: '100%',
-            width: 'auto',
-          },
-          [theme.breakpoints.up('md')]: {
-            display: 'block',
-            maxWidth: 'auto',
-            width: 'auto',
-          },
-          [theme.breakpoints.up('lg')]: {
-            display: 'block',
-            maxWidth: 'auto',
-            width: 'auto',
-          },
-        },
-        '&:hover $repeatCmdButton': {
-          display: 'inline-block',
-        },
-      },
-      messageAvatar: {
-        top: '10px',
-        left: '15px',
-        position: 'absolute',
-      },
-      messageAuthor: {
-        fontSize: 14,
-        fontWeight: 'bold',
-      },
-      messageTime: {
-        display: 'inline-block',
-        marginLeft: 10,
-        fontSize: '14px',
-        color: colors.pgaiDarkGray,
-      },
-      sqlCode: {
-        padding: 0,
-        border: 'none!important',
-        'margin-top': 0,
-        'margin-bottom': 0,
-        '& > .MuiInputBase-fullWidth > fieldset': {
-          border: 'none!important',
-        },
-        '& > .MuiInputBase-fullWidth': {
-          padding: 5,
-        },
-        '& > div > textarea': {
-          fontFamily:
-            '"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",' +
-            ' "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace',
-          color: 'black',
-          fontSize: '14px',
-        },
-      },
-      messageStatusContainer: {
-        marginTop: 15,
-      },
-      messageStatus: {
-        border: '1px solid #CCCCCC',
-        borderColor: colors.pgaiLightGray,
-        borderRadius: 3,
-        display: 'inline-block',
-        padding: 3,
-        fontSize: '12px',
-        lineHeight: '12px',
-        paddingLeft: 6,
-        paddingRight: 6,
-      },
-      messageStatusIcon: {
-        '& svg': {
-          marginBottom: -2,
-        },
-        'margin-right': 3,
-      },
-      messageProgress: {
-        '& svg': {
-          marginBottom: -2,
-          display: 'inline-block',
-        },
-      },
-      actions: {
-        display: 'flex',
-        justifyContent: 'space-between',
-      },
-      channelsList: {
-        ...styles.inputField,
-        marginBottom: 0,
-        marginRight: '8px',
-        flex: '0 1 240px',
-      },
-      clearChatButton: {
-        flex: '0 0 auto',
-      },
-      repeatCmdButton: {
-        fontSize: '12px',
-        padding: '2px 5px',
-        marginLeft: 10,
-        display: 'none',
-        lineHeight: '14px',
-        marginTop: '-4px',
-      },
-      messageHeader: {
-        height: '18px',
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <JoeInstance {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/Messages/Banner/index.tsx b/ui/packages/platform/src/pages/JoeInstance/Messages/Banner/index.tsx
deleted file mode 100644
index e8af480a..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Messages/Banner/index.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Status, Props as StatusProps } from '@postgres.ai/shared/components/Status'
-
-import styles from './styles.module.scss'
-
-type Props = {
-  messages: {
-    type: StatusProps['type']
-    value: string
-  }[]
-}
-
-export const Banner = (props: Props) => {
-  const { messages } = props
-
-  return (
-    <div className={styles.root}>
-      {messages.map((message) => {
-        return (
-          <Status
-            key={`${message.type}-${message.value}`}
-            className={styles.content}
-            type={message.type}
-          >
-            {message.value}
-          </Status>
-        )
-      })}
-    </div>
-  )
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/Messages/index.jsx b/ui/packages/platform/src/pages/JoeInstance/Messages/index.jsx
deleted file mode 100644
index f4e304c2..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Messages/index.jsx
+++ /dev/null
@@ -1,284 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { useRef, useEffect } from 'react';
-import {
-  Button,
-  TextField,
-  ExpansionPanel,
-  ExpansionPanelSummary,
-  ExpansionPanelDetails,
-  Typography
-} from '@material-ui/core';
-import { ExpandMore as ExpandMoreIcon } from '@material-ui/icons';
-import ReactMarkdown from 'react-markdown';
-import rehypeRaw from 'rehype-raw';
-import remarkGfm from 'remark-gfm';
-import { ResizeObserver } from '@juggle/resize-observer';
-
-import { icons } from '@postgres.ai/shared/styles/icons';
-import { Spinner } from '@postgres.ai/shared/components/Spinner';
-
-import { usePrev } from 'hooks/usePrev';
-
-import { getMaxScrollTop, getMessageArtifactIds, getUserMessagesCount } from './utils';
-import { Banner } from './Banner';
-
-import styles from './styles.module.scss';
-
-export const Messages = (props) => {
-  const {
-    classes,
-    messages,
-    markdownLink,
-    sendCommand,
-    loadMessageArtifacts,
-    preformatJoeMessageStatus,
-    systemMessages
-  } = props;
-
-  const rootRef = useRef();
-  const wrapperRef = useRef();
-  const atBottomRef = useRef(true);
-  const shouldSkipScrollCalcRef = useRef(false);
-
-  // Scroll handlers.
-  const scrollBottom = () => {
-    shouldSkipScrollCalcRef.current = true;
-    rootRef.current.scrollTop = getMaxScrollTop(rootRef.current);
-    atBottomRef.current = true;
-  };
-
-  const scrollBottomIfNeed = () => {
-    if (!atBottomRef.current) {
-      return;
-    }
-
-    scrollBottom();
-  };
-
-  // Listening resizing of wrapper.
-  useEffect(() => {
-    const observedElement = wrapperRef.current;
-    if (!observedElement) return;
-
-    const resizeObserver = new ResizeObserver(scrollBottomIfNeed);
-    resizeObserver.observe(observedElement);
-
-    return () => resizeObserver.unobserve(observedElement);
-  }, [wrapperRef.current]);
-
-  // Scroll to bottom if user sent new message.
-  const userMessagesCount = getUserMessagesCount(messages);
-  const prevUserMessagesCount = usePrev(userMessagesCount);
-
-  if (userMessagesCount > prevUserMessagesCount) {
-    scrollBottom();
-  }
-
-  // Check auto-scroll condition.
-  const calcIsAtBottom = () => {
-    if (shouldSkipScrollCalcRef.current) {
-      shouldSkipScrollCalcRef.current = false;
-      return;
-    }
-
-    atBottomRef.current = rootRef.current.scrollTop >= getMaxScrollTop(rootRef.current);
-  };
-
-  return (
-    <div className={styles.root}>
-      <div className={styles.messages} ref={rootRef} onScroll={calcIsAtBottom}>
-        <div ref={wrapperRef}>
-          {messages &&
-            // TODO(Anton): Objects doesn't guarantee keys order.
-            Object.keys(messages).map((m) => {
-              const msgArtifacts = getMessageArtifactIds(messages[m].message);
-              const otherArtifactsExists =
-                messages[m].message.indexOf('Other artifacts are provided in the thread') !== -1;
-
-              return (
-                <div key={m} className={classes.message} messageid={messages[m].id}>
-                  <div className={classes.messageAvatar}>
-                    {messages[m].author_id ? icons.userChatIcon : icons.joeChatIcon}
-                  </div>
-                  <div className={classes.messageHeader}>
-                    <span className={classes.messageAuthor}>
-                      {messages[m].author_id ? 'You' : 'Joe Bot'}
-                    </span>
-                    <span className={classes.messageTime}>{messages[m].formattedTime}</span>
-                    {(!messages[m].parent_id ||
-                      (messages[m].parent_id && messages[messages[m].parent_id])) && (
-                      <Button
-                        variant='outlined'
-                        className={classes.repeatCmdButton}
-                        key={m}
-                        onClick={() =>
-                          sendCommand(
-                            messages[m].parent_id
-                              ? messages[messages[m].parent_id].message
-                              : messages[m].message
-                          )
-                        }
-                      >
-                        Repeat
-                      </Button>
-                    )}
-                  </div>
-                  {messages[m].parent_id ? (
-                    <div>
-                      {
-                        <ReactMarkdown
-                          className='markdown'
-                          children={messages[m].formattedMessage}
-                          rehypePlugins={[rehypeRaw]}
-                          remarkPlugins={[remarkGfm]}
-                          linkTarget='_blank'
-                          components={{
-                            a: (properties) => {
-                              return markdownLink(properties, m);
-                            },
-                            p: 'div'
-                          }}
-                        />
-                      }
-                    </div>
-                  ) : (
-                    <div>
-                      <TextField
-                        variant='outlined'
-                        id={messages[m].id + 'cmd'}
-                        multiline
-                        fullWidth
-                        value={messages[m].message}
-                        className={classes.sqlCode}
-                        margin='normal'
-                        InputProps={{
-                          readOnly: true
-                        }}
-                      />
-                    </div>
-                  )}
-                  {messages[m].delivery_status === 'artifact_attached' && otherArtifactsExists ? (
-                    <div>
-                      <ExpansionPanel
-                        className={classes.messageArtifacts}
-                        TransitionProps={{
-                          enter: false,
-                          exit: false,
-                          appear: false,
-                          mountOnEnter: false,
-                          timeout: 0
-                        }}
-                      >
-                        <ExpansionPanelSummary
-                          expandIcon={<ExpandMoreIcon />}
-                          onClick={(event) => {
-                            loadMessageArtifacts(event, messages[m].id);
-                          }}
-                          aria-controls={'message-' + messages[m].id + '-artifacts-content'}
-                          id={'message-' + messages[m].id + '-artifacts-header'}
-                          className={classes.advancedExpansionPanelSummary}
-                        >
-                          <Typography className={classes.heading}>
-                            <nobr>Other artifacts</nobr>
-                          </Typography>
-                        </ExpansionPanelSummary>
-                        <ExpansionPanelDetails
-                          className={classes.messageArtifactExpansionPanelDetails}
-                        >
-                          {!messages[m].artifacts ||
-                          (messages[m].artifacts && messages[m].artifacts.isProcessing) ? (
-                              <Spinner />
-                            ) : null}
-
-                          {messages[m].artifacts && messages[m].artifacts.files ? (
-                            <div className={classes.messageArtifactsContainer}>
-                              {Object.keys(messages[m].artifacts.files).map((f) => {
-                                let artifactAlreadyExists =
-                                  msgArtifacts.indexOf(messages[m].artifacts.files[f].id) !== -1;
-                                return !artifactAlreadyExists ? (
-                                  <ExpansionPanel
-                                    key={messages[m].artifacts.files[f].id}
-                                    className={classes.messageArtifact}
-                                    TransitionProps={{
-                                      enter: false,
-                                      exit: false,
-                                      appear: false,
-                                      mountOnEnter: false,
-                                      timeout: 0
-                                    }}
-                                  >
-                                    <ExpansionPanelSummary
-                                      expandIcon={<ExpandMoreIcon />}
-                                      className={
-                                        classes.messageArtifactExpansionPanelSummary
-                                      }
-                                      aria-controls={
-                                        'artifact-' +
-                                        messages[m].artifacts.files[f].id +
-                                        '-content'
-                                      }
-                                      id={
-                                        'artifact-' +
-                                        messages[m].artifacts.files[f].id +
-                                        '-header'
-                                      }
-                                    >
-                                      <Typography
-                                        className={classes.messageArtifactTitle}
-                                      >
-                                        {messages[m].artifacts.files[f].title}
-                                      </Typography>
-                                    </ExpansionPanelSummary>
-                                    <ExpansionPanelDetails
-                                      className={
-                                        classes.advancedExpansionPanelDetails
-                                      }
-                                    >
-                                      <TextField
-                                        variant='outlined'
-                                        id={
-                                          'artifact' +
-                                          messages[m].artifacts.files[f].id
-                                        }
-                                        multiline
-                                        fullWidth
-                                        value={
-                                          messages[m].artifacts.files[f].content
-                                        }
-                                        className={classes.code}
-                                        margin='normal'
-                                        InputProps={{
-                                          readOnly: true
-                                        }}
-                                      />
-                                    </ExpansionPanelDetails>
-                                  </ExpansionPanel>
-                                ) : null;
-                              })}
-                            </div>
-                          ) : null}
-                        </ExpansionPanelDetails>
-                      </ExpansionPanel>
-                    </div>
-                  ) : null}
-                  {!messages[m].author_id ? (
-                    <div className={classes.messageStatusContainer}>
-                      {preformatJoeMessageStatus(messages[m].status)}
-                    </div>
-                  ) : null}
-                </div>
-              );
-            })}
-        </div>
-      </div>
-
-      { Boolean(systemMessages.length) && <Banner messages={systemMessages} /> }
-    </div>
-  );
-};
diff --git a/ui/packages/platform/src/pages/JoeInstance/Messages/styles.module.scss b/ui/packages/platform/src/pages/JoeInstance/Messages/styles.module.scss
deleted file mode 100644
index 55701107..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Messages/styles.module.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-@import '@postgres.ai/shared/styles/vars';
-
-.root {
-  margin-top: 20px;
-  border: 1px $color-gray solid;
-  border-radius: 4px;
-  overflow: hidden;
-  flex: 1 0 160px;
-  display: flex;
-  flex-direction: column;
-}
-
-.messages {
-  overflow-y: scroll;
-  flex: 1 1 100%;
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/Messages/utils.ts b/ui/packages/platform/src/pages/JoeInstance/Messages/utils.ts
deleted file mode 100644
index e1f34c0a..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/Messages/utils.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const getMaxScrollTop = (element: HTMLElement) =>
-  element.scrollHeight - element.clientHeight
-
-export const getMessageArtifactIds = (message: string) => {
-  const ids: number[] = []
-
-  if (!message) {
-    return ids
-  }
-
-  const match = message.match(/\/artifact\/(\d*)/)
-  if (!match || (match && match.length < 3)) {
-    return ids
-  }
-
-  for (let i = 1; i < match.length; i = i + 2) {
-    ids.push(parseInt(match[i], 10))
-  }
-
-  return ids
-}
-
-export const getUserMessagesCount = (messages: {
-  [x: string]: { author_id: string }
-}) => {
-  if (!messages) {
-    return 0
-  }
-
-  const keys = Object.keys(messages)
-
-  return keys.reduce((count, key) => {
-    return messages[key].author_id ? count + 1 : count
-  }, 0)
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/index.jsx b/ui/packages/platform/src/pages/JoeInstance/index.jsx
deleted file mode 100644
index 35f7b748..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/index.jsx
+++ /dev/null
@@ -1,453 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { Component } from 'react';
-import PropTypes from 'prop-types';
-import {
-  Button,
-  TextField,
-  MenuItem,
-  ExpansionPanel,
-  ExpansionPanelSummary,
-  ExpansionPanelDetails,
-  Typography
-} from '@material-ui/core';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner';
-import { Spinner } from '@postgres.ai/shared/components/Spinner';
-import { colors } from '@postgres.ai/shared/styles/colors';
-import { styles } from '@postgres.ai/shared/styles/styles';
-import { icons } from '@postgres.ai/shared/styles/icons';
-
-import Store from 'stores/store';
-import Actions from 'actions/actions';
-import { ErrorWrapper } from 'components/Error/ErrorWrapper';
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper';
-import ConsolePageTitle from 'components/ConsolePageTitle';
-
-import { getSystemMessages } from './utils';
-import { Messages } from './Messages';
-import { Command } from './Command';
-
-import './styles.scss';
-
-const VERIFY_MESSAGES_TIMEOUT = 60 * 1000;
-class JoeInstance extends Component {
-  state = {
-    command: '',
-    channelId: null,
-    artifacts: {}
-  };
-
-  componentDidMount() {
-    const that = this;
-    const instanceId = this.props.match.params.instanceId;
-    const orgId = this.props.orgId;
-
-
-    this.unsubscribe = Store.listen(function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null;
-      const joeInstance = this.data && this.data.joeInstance &&
-        this.getJoeInstance(instanceId) ? this.getJoeInstance(instanceId) : null;
-
-      if (auth && auth.token && !joeInstance.isProcessing &&
-        !that.state.data) {
-        Actions.getJoeInstance(auth.token, orgId, 0, instanceId);
-      }
-
-      if (auth && auth.token && !joeInstance.isChannelsProcessing &&
-        !joeInstance.isChannelsProcessed && joeInstance.isProcessed &&
-        !joeInstance.channelsError && joeInstance.data && joeInstance.data.id) {
-        Actions.getJoeInstanceChannels(auth.token, instanceId);
-      }
-
-      that.setState({
-        data: this.data
-      });
-
-      if (!that.state.channelId && Object.keys(joeInstance.channels).length > 0) {
-        that.setState({
-          channelId: joeInstance.channels[Object.keys(joeInstance.channels)[0]].channelId
-        }, () => {
-          that.sendHelpCommand();
-        });
-      }
-    });
-
-    Actions.refresh();
-  }
-
-  sendHelpCommand = () => {
-    const channelId = this.state.channelId;
-    const instanceId = this.props.match.params.instanceId;
-    const instance = this.state.data && this.state.data.joeInstance &&
-      this.state.data.joeInstance.instances[instanceId] ?
-      this.state.data.joeInstance.instances[instanceId] : null;
-    const messages = instance && instance.messages &&
-      instance.messages[channelId] ?
-      instance.messages[channelId] : null;
-
-    if (messages && Object.keys(messages).length > 0) {
-      return;
-    }
-
-    this.sendCommand('help');
-  };
-
-  sendCommand = (cmd) => {
-    const instanceId = this.props.match.params.instanceId;
-    const auth = this.state.data && this.state.data.auth ?
-      this.state.data.auth : null;
-    const instance = this.state.data && this.state.data.joeInstance &&
-      this.state.data.joeInstance.instances[instanceId] ?
-      this.state.data.joeInstance.instances[instanceId] : null;
-
-    // Send command.
-    Actions.sendJoeInstanceCommand(
-      auth.token, instanceId,
-      this.state.channelId,
-      cmd,
-      instance.channels[this.state.channelId].sessionId
-    );
-  };
-
-  handleChangeChannel = (event) => {
-    this.setState({ channelId: event.target.value }, () => {
-      this.sendHelpCommand();
-    });
-  };
-
-  handleChangeCommand = (event) => {
-    this.setState({
-      command: event.target.value
-    });
-  };
-
-  componentWillUnmount() {
-    const instanceId = this.props.match.params.instanceId;
-
-    this.unsubscribe();
-    if (this.updateInterval) {
-      clearInterval(this.updateInterval);
-    }
-
-    if (this.domObserver) {
-      this.domObserver.disconnect();
-    }
-
-    Actions.closeJoeWebSocketConnection(instanceId);
-  }
-
-  onSend = (value) => {
-    const instanceId = this.props.match.params.instanceId;
-    const auth = this.state.data && this.state.data.auth ?
-      this.state.data.auth : null;
-    const instance = this.state.data && this.state.data.joeInstance &&
-      this.state.data.joeInstance.instances[instanceId] ?
-      this.state.data.joeInstance.instances[instanceId] : null;
-
-    this.sendCommand(value);
-
-    if (!this.updateInterval) {
-      this.updateInterval = setInterval(() => {
-        Actions.getJoeInstanceMessages(
-          auth.token,
-          instanceId,
-          this.state.channelId,
-          instance.channels[this.state.channelId].sessionId
-        );
-      }, VERIFY_MESSAGES_TIMEOUT);
-    }
-  };
-
-  loadMessageArtifacts = (event, messageId) => {
-    const auth = this.state.data && this.state.data.auth ?
-      this.state.data.auth : null;
-    const channelId = this.state.channelId;
-    const instanceId = this.props.match.params.instanceId;
-    const instance = this.state.data && this.state.data.joeInstance &&
-      this.state.data.joeInstance.instances[instanceId] ?
-      this.state.data.joeInstance.instances[instanceId] : null;
-    const messages = instance && instance.messages &&
-      instance.messages[channelId] ?
-      instance.messages[channelId] : null;
-
-    if (messages && messages[messageId].artifacts &&
-      messages[messageId].artifacts.files) {
-      // Artifacts already loaded.
-      return true;
-    }
-
-    Actions.getJoeMessageArtifacts(
-      auth.token,
-      instanceId,
-      channelId,
-      messageId
-    );
-
-    return true;
-  };
-
-  preformatJoeMessageStatus = (status) => {
-    const { classes } = this.props;
-    let icon;
-    let text;
-    let color;
-
-    switch (status) {
-    case 'error':
-      icon = icons.failedIcon;
-      text = 'Failed';
-      color = colors.state.error;
-      break;
-    case 'ok':
-      icon = icons.okIcon;
-      text = 'Completed';
-      color = colors.state.ok;
-      break;
-    case 'running':
-      icon = icons.hourGlassIcon;
-      text = 'Processing';
-      color = colors.state.processing;
-      break;
-    default:
-      return null;
-    }
-
-    return (
-      <div className={classes.messageStatus} style={{ color: color }}>
-        <span className={classes.messageStatusIcon}>{icon}</span> {text}
-      </div>
-    );
-  };
-
-  markdownLink = (linkProps, messageId) => {
-    const { classes } = this.props;
-    const { href, target, children } = linkProps;
-    const instanceId = this.props.match.params.instanceId;
-    const channelId = this.state.channelId;
-    const instance = this.state.data && this.state.data.joeInstance &&
-      this.state.data.joeInstance.instances[instanceId] ?
-      this.state.data.joeInstance.instances[instanceId] : null;
-    const messages = instance && instance.messages &&
-      instance.messages[channelId] ?
-      instance.messages[channelId] : null;
-    let artifactId = false;
-    let artifacts = messages[messageId] && messages[messageId].artifacts ?
-      messages[messageId].artifacts : null;
-
-
-    let match = href.match(/\/artifact\/(\d*)/);
-    if (match && match.length > 1) {
-      artifactId = match[1];
-    }
-
-    if (href.startsWith('/artifact/') && artifactId) {
-      return (
-        <div className={'artifact-container'}>
-          <ExpansionPanel
-            className={classes.messageArtifacts}
-            expanded={this.state.artifacts[artifactId]}
-            TransitionProps={{
-              enter: false,
-              exit: false,
-              appear: false,
-              mountOnEnter: false,
-              timeout: 0
-            }}
-          >
-            <ExpansionPanelSummary
-              expandIcon={<ExpandMoreIcon/>}
-              onClick={
-                event => {
-                  this.setState({ artifacts: { [artifactId]: !this.state.artifacts[artifactId] } });
-                  this.loadMessageArtifacts(event, messages[messageId].id);
-                }
-              }
-              aria-controls={'message-' + messageId + '-artifacts-content'}
-              id={'message-' + messageId + '-artifacts-header'}
-              className={classes.advancedExpansionPanelSummary}
-            >
-              <Typography className={classes.heading}>
-                <nobr>{children}</nobr>
-              </Typography>
-            </ExpansionPanelSummary>
-            <ExpansionPanelDetails
-              className={classes.advancedExpansionPanelDetails}
-            >
-              {(!artifacts || (artifacts && artifacts.isProcessing)) ? (
-                <Spinner />) : ''}
-              {artifacts && artifacts.files ? (
-                <div className={classes.messageArtifactsContainer}>
-                  <div
-                    key={artifacts.files[artifactId].id}
-                    className={'artifact'}
-                  >
-                    <TextField
-                      variant='outlined'
-                      id={'artifact' + artifacts.files[artifactId].id}
-                      multiline
-                      fullWidth
-                      value={artifacts.files[artifactId].content}
-                      className={classes.code}
-                      margin='normal'
-                      InputProps={{
-                        readOnly: true
-                      }}
-                    />
-                  </div>
-                </div>
-              ) : null}
-            </ExpansionPanelDetails>
-          </ExpansionPanel>
-        </div>
-      );
-    }
-
-    return (
-      <a className={classes.link} href={href} target={target}>{children}</a>
-    );
-  };
-
-  render() {
-    const { classes } = this.props;
-    const instanceId = this.props.match.params.instanceId;
-    const channelId = this.state.channelId;
-    const instance = this.state.data && this.state.data.joeInstance &&
-      this.state.data.joeInstance.instances[instanceId] ?
-      this.state.data.joeInstance.instances[instanceId] : null;
-    const messages = instance && instance.messages &&
-      instance.messages[channelId] ?
-      instance.messages[channelId] : null;
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        org={this.props.org}
-        project={this.props.project}
-        breadcrumbs={[
-          { name: 'SQL Optimization', url: null },
-          { name: 'Joe Instances', url: 'joe-instances' },
-          { name: 'Instance #' + instanceId, url: null }
-        ]}
-      />
-    );
-
-    if (!this.state.data ||
-      (instance && (instance.isChannelsProcessing || instance.isProcessing))) {
-      return (
-        <>
-          {breadcrumbs}
-
-          <PageSpinner />
-        </>
-      );
-    }
-
-    if (instance.error) {
-      return (
-        <>
-          {breadcrumbs}
-
-          <ErrorWrapper
-            message={instance.errorMessage}
-            code={instance.errorCode}
-          />
-        </>
-      );
-    }
-
-    if (instance.channelsError) {
-      return (
-        <>
-          {breadcrumbs}
-
-          <ErrorWrapper
-            message={instance.channelsErrorMessage}
-          />
-        </>
-      );
-    }
-
-    return (
-      <>
-        {breadcrumbs}
-
-        <ConsolePageTitle title='Ask Joe'/>
-
-        { instance && instance.channelsErrorMessage ? (
-          <div>
-            <span className={classes.errorMsg}>{instance.channelsErrorMessage}</span>
-          </div>
-        ) : null }
-
-        <div className={classes.actions}>
-          <TextField
-            value={channelId}
-            onChange={event => this.handleChangeChannel(event)}
-            select
-            label='Project and database'
-            inputProps={{
-              name: 'channel',
-              id: 'channel'
-            }}
-            InputLabelProps={{
-              shrink: true,
-              style: styles.inputFieldLabel
-            }}
-            FormHelperTextProps={{
-              style: styles.inputFieldHelper
-            }}
-            variant='outlined'
-            className={classes.channelsList}
-          >
-            {Object.keys(instance.channels).map(c => {
-              return (
-                <MenuItem
-                  value={instance.channels[c].channelId}
-                  key={instance.channels[c].channelId}
-                >
-                  {instance.channels[c].channelId}
-                </MenuItem>
-              );
-            })}
-          </TextField>
-          <Button
-            className={classes.clearChatButton}
-            variant='outlined'
-            disabled={!messages || (messages && messages.length === 0)}
-            onClick={() => Actions.clearJoeInstanceChannelMessages(instanceId, channelId)}
-          >
-            <nobr>Clear chat</nobr>
-          </Button>
-        </div>
-
-        <Messages
-          classes={classes}
-          messages={messages}
-          markdownLink={this.markdownLink}
-          sendCommand={this.sendCommand}
-          loadMessageArtifacts={this.loadMessageArtifacts}
-          preformatJoeMessageStatus={this.preformatJoeMessageStatus}
-          systemMessages={getSystemMessages(instance, channelId)}
-        />
-
-        <Command
-          isDisabled={instance && instance.channelsError}
-          onSend={this.onSend}
-        />
-      </>
-    );
-  }
-}
-
-JoeInstance.propTypes = {
-  classes: PropTypes.object.isRequired,
-  theme: PropTypes.object.isRequired
-};
-
-export default JoeInstance
diff --git a/ui/packages/platform/src/pages/JoeInstance/styles.scss b/ui/packages/platform/src/pages/JoeInstance/styles.scss
deleted file mode 100644
index 13cede3c..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/styles.scss
+++ /dev/null
@@ -1,101 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-.markdown {
-  margin: 5px;
-  margin-left: 0;
-  font-size: 14px;
-}
-
-.markdown h1 {
-  margin-top: 5px;
-}
-
-.markdown table {
-  border-collapse: collapse;
-  border-spacing: 0;
-}
-
-.markdown pre {
-  border: 1px solid #ccc;
-  background: #f6f8fa;
-  padding: 5px;
-  font-size: 13px !important;
-  margin: 0;
-  border-radius: 3px;
-}
-
-.markdown blockquote {
-  color: #666;
-  margin: 0;
-  padding-left: 3em;
-  border-left: 0.5em #eee solid;
-}
-
-.markdown tr {
-  border-top: 1px solid #c6cbd1;
-  background: #fff;
-}
-
-.markdown th,
-.markdown td {
-  padding: 10px 13px;
-  border: 1px solid #dfe2e5;
-}
-
-.markdown table tr:nth-child(2n) {
-  background: #f6f8fa;
-}
-
-/* stylelint-disable-next-line selector-no-qualifying-type */
-.markdown img.emoji {
-  margin-top: 5px;
-}
-
-.markdown code {
-  /* stylelint-disable-next-line color-named */
-  border: 1px dotted silver;
-  display: inline-block;
-  border-radius: 3px;
-  padding: 2px;
-  background-color: #f6f8fa;
-  margin-bottom: 3px;
-  font-size: 13px !important;
-  font-family: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
-    'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-}
-
-.markdown pre {
-  overflow-wrap: break-word;
-  white-space: pre-wrap;
-}
-
-.markdown pre code {
-  background: none;
-  border: 0;
-  margin: 0;
-  border-radius: 0;
-  display: inline;
-  padding: 0;
-  font-size: 13px !important;
-  font-family: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
-    'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-}
-
-.markdown div:not([class]) {
-  display: block;
-  margin-block-start: 1em;
-  margin-block-end: 1em;
-  margin-inline-start: 0;
-  margin-inline-end: 0;
-}
-
-/* stylelint-disable-next-line selector-class-pattern */
-.markdown .MuiExpansionPanel-root div {
-  margin-block-start: 0;
-  margin-block-end: 0;
-}
diff --git a/ui/packages/platform/src/pages/JoeInstance/utils.ts b/ui/packages/platform/src/pages/JoeInstance/utils.ts
deleted file mode 100644
index befec871..00000000
--- a/ui/packages/platform/src/pages/JoeInstance/utils.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-type Instance = {
-  channels?: {
-    [key in string]?: {
-      commandErrorMessage?: string
-      wsErrorMessage?: string
-    }
-  }
-}
-
-export const getSystemMessages = (instance: Instance, channelId: string) => {
-  const channels = instance?.channels
-  if (!channels) return []
-
-  const channel = channels[channelId]
-  if (!channel) return []
-
-  const { commandErrorMessage, wsErrorMessage } = channel
-
-  const systemMessages = []
-
-  if (commandErrorMessage) {
-    systemMessages.push({
-      type: 'error' as const,
-      value: commandErrorMessage,
-    })
-  }
-
-  if (wsErrorMessage) {
-    systemMessages.push({
-      type: 'warning' as const,
-      value: wsErrorMessage,
-    })
-  }
-
-  return systemMessages
-}
diff --git a/ui/packages/platform/src/pages/JoeSessionCommand/JoeSessionCommandWrapper.jsx b/ui/packages/platform/src/pages/JoeSessionCommand/JoeSessionCommandWrapper.jsx
deleted file mode 100644
index 364e41b1..00000000
--- a/ui/packages/platform/src/pages/JoeSessionCommand/JoeSessionCommandWrapper.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import JoeSessionCommand from 'pages/JoeSessionCommand'
-import { styles } from '@postgres.ai/shared/styles/styles'
-
-export const JoeSessionCommandWrapper = (props) => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        display: 'flex',
-        'flex-direction': 'column',
-        flex: '1 1 100%',
-
-        '& h4': {
-          marginTop: '2em',
-          marginBottom: '0.7em',
-        },
-      },
-      appBar: {
-        position: 'relative',
-      },
-      title: {
-        marginLeft: theme.spacing(2),
-        flex: 1,
-        fontSize: '16px',
-      },
-      actions: {
-        display: 'flex',
-      },
-      visFrame: {
-        height: '100%',
-      },
-      nextButton: {
-        marginLeft: '10px',
-      },
-      flameGraphContainer: {
-        padding: '20px',
-      },
-      bottomSpace: {
-        ...styles.bottomSpace,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <JoeSessionCommand {...props} classes={classes} />
-}
diff --git a/ui/packages/platform/src/pages/JoeSessionCommand/TabPanel/index.tsx b/ui/packages/platform/src/pages/JoeSessionCommand/TabPanel/index.tsx
deleted file mode 100644
index 65790bf9..00000000
--- a/ui/packages/platform/src/pages/JoeSessionCommand/TabPanel/index.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-import Box from '@mui/material/Box'
-import { Typography, makeStyles } from '@material-ui/core'
-import { TabPanelProps } from '@postgres.ai/platform/src/components/types'
-
-const useStyles = makeStyles(
-  {
-    root: {
-      marginTop: 0,
-    },
-    content: {
-      padding: '10px 0 0 0',
-    },
-  },
-  { index: 1 },
-)
-
-export const TabPanel = (props: TabPanelProps) => {
-  const { children, value, index } = props
-
-  const classes = useStyles()
-
-  return (
-    <Typography
-      component="div"
-      role="tabpanel"
-      hidden={value !== index}
-      id={`plan-tabpanel-${index}`}
-      aria-labelledby={`plan-tab-${index}`}
-      className={classes.root}
-    >
-      <Box p={3} className={classes.content}>
-        {children}
-      </Box>
-    </Typography>
-  )
-}
diff --git a/ui/packages/platform/src/pages/JoeSessionCommand/index.js b/ui/packages/platform/src/pages/JoeSessionCommand/index.js
deleted file mode 100644
index d416cc97..00000000
--- a/ui/packages/platform/src/pages/JoeSessionCommand/index.js
+++ /dev/null
@@ -1,488 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import Dialog from '@material-ui/core/Dialog';
-import Button from '@material-ui/core/Button';
-import AppBar from '@material-ui/core/AppBar';
-import Toolbar from '@material-ui/core/Toolbar';
-import IconButton from '@material-ui/core/IconButton';
-import Typography from '@material-ui/core/Typography';
-import CloseIcon from '@material-ui/icons/Close';
-import Slide from '@material-ui/core/Slide';
-import Tabs from '@material-ui/core/Tabs';
-import Tab from '@material-ui/core/Tab';
-import { formatDistanceToNowStrict } from 'date-fns';
-
-import { FormattedText } from '@postgres.ai/shared/components/FormattedText';
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner';
-import { Spinner } from '@postgres.ai/shared/components/Spinner';
-
-import Store from 'stores/store';
-import Actions from 'actions/actions';
-import { ErrorWrapper } from 'components/Error/ErrorWrapper';
-import { ConsoleBreadcrumbsWrapper } from 'components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper';
-import ConsolePageTitle from 'components/ConsolePageTitle';
-import FlameGraph from 'components/FlameGraph';
-import { visualizeTypes } from 'assets/visualizeTypes';
-import Urls from 'utils/urls';
-import Permissions from 'utils/permissions';
-import format from 'utils/format';
-
-import { TabPanel } from './TabPanel';
-
-const hashLinkVisualizePrefix = 'visualize-';
-
-function a11yProps(index) {
-  return {
-    'id': `plan-tab-${index}`,
-    'aria-controls': `plan-tabpanel-${index}`
-  };
-}
-
-const FullScreenDialogTransition = React.forwardRef(function Transition(props, ref) {
-  return <Slide direction='up' ref={ref} {...props} />;
-});
-
-class JoeSessionCommand extends Component {
-  componentDidMount() {
-    const that = this;
-    const commandId = this.getCommandId();
-    const orgId = this.props.orgId ? this.props.orgId : null;
-
-    this.handleChangeTab(null, 0);
-
-    this.unsubscribe = Store.listen(function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null;
-      const command = this.data && this.data.command ?
-        this.data.command : null;
-
-      that.setState({ data: this.data });
-      if (auth && auth.token && !command.isProcessing && !command.error &&
-        !that.state && orgId) {
-        Actions.getJoeSessionCommand(auth.token, orgId, commandId);
-      }
-
-      if (!Urls.isSharedUrl() && !this.data.shareUrl.isProcessing &&
-        !this.data.sharedUrls['command'][commandId]) {
-        Actions.getSharedUrl(this.data.auth.token, orgId, 'command', commandId);
-      }
-
-      setTimeout(()=> {
-        const commandData = command ? command.data : null;
-        if (!!commandData && !this.hashLocationProcessed) {
-          this.hashLocationProcessed = true;
-          that.handleHashLocation();
-        }
-      }, 10);
-    });
-
-    Actions.refresh();
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe();
-  }
-
-  handleHashLocation() {
-    const hash = this.props.history.location.hash;
-    if (!hash || !hash.includes(hashLinkVisualizePrefix)) {
-      return;
-    }
-
-    const type = hash.replace('#', '').replace(hashLinkVisualizePrefix, '');
-
-    if (type === visualizeTypes.flame) {
-      this.showFlameGraphVisualization();
-      return;
-    }
-
-    this.showExternalVisualization(type);
-  }
-
-  setHashUrl(hashUrl) {
-    this.props.history.replace(this.props.history.location.pathname + hashUrl);
-  }
-
-  removeHashUrl() {
-    this.props.history.replace(this.props.history.location.pathname);
-  }
-
-  getCommandId = () => {
-    if (this.props.match && this.props.match.params && this.props.match.params.commandId) {
-      return parseInt(this.props.match.params.commandId, 10);
-    }
-
-    return parseInt(this.props.commandId, 10);
-  };
-
-  getCommandData = () => {
-    const commandId = this.getCommandId();
-    const data = this.state && this.state.data && this.state.data.command &&
-      this.state.data.command.data ? this.state.data.command.data : null;
-
-    return data && data.commandId === commandId ? data : null;
-  };
-
-  getExternalVisualization = () => {
-    return this.state && this.state.data &&
-      this.state.data.externalVisualization ? this.state.data.externalVisualization : null;
-  };
-
-  isExplain = () => {
-    const data = this.getCommandData();
-    return !!data && data.command === 'explain';
-  };
-
-  showExternalVisualization = (type) => {
-    const data = this.getCommandData();
-
-    if (!this.isExplain() || data.planExecJson.length === 0) {
-      return;
-    }
-
-    this.setHashUrl('#' + hashLinkVisualizePrefix + type);
-
-    Actions.getExternalVisualizationData(
-      type,
-      {
-        json: data.planExecJson,
-        text: data.planExecText
-      },
-      data.query
-    );
-  };
-
-  closeExternalVisualization = () => {
-    this.props.history.replace(this.getCommandId());
-    Actions.closeExternalVisualization();
-    this.setState({
-      showFlameGraph: false
-    });
-  };
-
-  handleExternalVisualizationClick = (type) => {
-    return () => {
-      this.showExternalVisualization(type);
-    };
-  };
-
-  showFlameGraphVisualization = () => {
-    this.setHashUrl('#' + hashLinkVisualizePrefix + visualizeTypes.flame);
-    this.setState({
-      showFlameGraph: true
-    });
-  };
-
-  handleChangeTab = (event, tabValue) => {
-    this.setState({ tab: tabValue });
-  };
-
-  showShareDialog = () => {
-    const commandId = this.getCommandId();
-    const orgId = this.props.orgId ? this.props.orgId : null;
-
-    if (!orgId) {
-      return;
-    }
-
-    Actions.showShareUrlDialog(
-      parseInt(orgId, 10),
-      'command',
-      commandId,
-      'Anyone on the internet with the special link can view query, plan and ' +
-      'all parameters. Check that there is no sensitive data.'
-    );
-  };
-
-  render() {
-    const { classes } = this.props;
-    const data = this.getCommandData();
-    const commandId = this.getCommandId();
-    const sessionId = this.props.sessionId || this.props.match.params.sessionId;
-    const orgId = this.props.orgId ? this.props.orgId : null;
-    let allowShare = false;
-
-    if (this.props.orgData && orgId) {
-      let permissions = Permissions.getPermissions(this.props.orgData);
-      allowShare = permissions.shareUrl;
-    }
-
-    const breadcrumbs = (
-      <ConsoleBreadcrumbsWrapper
-        {...this.props}
-        breadcrumbs={[
-          { name: 'SQL Optimization', url: null },
-          { name: 'History', url: 'sessions' },
-          { name: 'Command #' + commandId },
-        ]}
-      />
-    )
-
-    if (this.state && this.state.data && this.state.data.command.error) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-          <ErrorWrapper
-            message={this.state.data.command.errorMessage}
-            code={this.state.data.command.errorCode}
-          />
-        </div>
-      );
-    }
-
-    if (!data) {
-      return (
-        <div className={classes.root}>
-          {breadcrumbs}
-
-          <PageSpinner />
-        </div>
-      );
-    }
-
-    let username = 'Unknown';
-    if (data.slackUsername && data.slackUid) {
-      username = `${data.slackUsername} (${data.slackUid})`;
-    } else if (data.username) {
-      username = data.username;
-    } else if (data.useremail) {
-      username = data.useremail;
-    }
-
-    const externalVisualization = this.getExternalVisualization();
-
-    const showFlameGraph = this.state.showFlameGraph;
-    const disableVizButtons = showFlameGraph ||
-      (externalVisualization && externalVisualization.isProcessing);
-    const openVizDialog = showFlameGraph || (externalVisualization &&
-      externalVisualization.url && externalVisualization.url.length > 0);
-    const title = `Command #${commandId} (${data.command}) from session #${sessionId}`;
-
-    let shareUrlButton = (
-      <Button
-        variant='contained'
-        color='primary'
-        key='add_dblab_instance'
-        onClick={ this.showShareDialog }
-      >
-        Share
-        {(this.state.data.shareUrl && this.state.data.shareUrl.isProcessing) &&
-          <span>
-            &nbsp;
-            <Spinner size='sm' />
-          </span>
-        }
-      </Button>
-    );
-
-    let titleActions = [];
-    if (!Urls.isSharedUrl() && allowShare) {
-      titleActions.push(shareUrlButton);
-    }
-
-    let isShared = this.state.data.sharedUrls && this.state.data.sharedUrls['command'] &&
-      this.state.data.sharedUrls['command'][commandId] &&
-      this.state.data.sharedUrls['command'][commandId].uuid;
-
-    return (
-      <div className={classes.root}>
-        { breadcrumbs }
-
-        <ConsolePageTitle
-          title={ title }
-          label={ isShared ? 'Shared via link' : null}
-          actions={ titleActions }
-        />
-
-        { this.isExplain() &&
-          <div className={classes.actions}>
-            <Button
-              variant='contained'
-              color='primary'
-              onClick={this.handleExternalVisualizationClick(visualizeTypes.depesz)}
-              disabled={ disableVizButtons }
-            >
-              Explain Depesz
-
-              {
-                disableVizButtons &&
-                externalVisualization.type === visualizeTypes.depesz &&
-                <span>
-                  &nbsp;
-                  <Spinner size='sm' />
-                </span>
-              }
-            </Button>
-
-            <Button
-              className={classes.nextButton}
-              variant='contained'
-              color='primary'
-              onClick={this.handleExternalVisualizationClick(visualizeTypes.pev2)}
-              disabled={ disableVizButtons}
-            >
-              Explain PEV2
-
-              {
-                disableVizButtons &&
-                externalVisualization.type === visualizeTypes.pev2 &&
-                <span>
-                  &nbsp;
-                  <Spinner size='sm' />
-                </span>
-              }
-            </Button>
-
-            <Button
-              className={ classes.nextButton }
-              variant='contained'
-              color='primary'
-              onClick={ this.showFlameGraphVisualization }
-              disabled={ disableVizButtons }
-            >
-              Explain FlameGraph
-            </Button>
-          </div>
-        }
-
-        <div>
-          <h4>Author:</h4>
-          <p>
-            { username }
-          </p>
-        </div>
-
-        <h4>Command:</h4>
-        <FormattedText value={data.command} />
-
-        { data.query && (
-          <React.Fragment>
-            <h4>Query:</h4>
-            <FormattedText value={data.query} />
-          </React.Fragment>
-        )}
-
-        {this.isExplain() ? (
-          <div>
-            <h4>Plan:</h4>
-            <Tabs
-              value={this.state.tab}
-              onChange={this.handleChangeTab}
-              aria-label='Plan'
-              inputProps={{
-                name: 'tab',
-                id: 'tab'
-              }}
-            >
-              <Tab label='with execution' {...a11yProps(0)} />
-              <Tab label='with execution (JSON)' {...a11yProps(0)} />
-              <Tab label='w/o execution' {...a11yProps(1)} />
-            </Tabs>
-
-            <TabPanel value={this.state.tab} index={0}>
-              <FormattedText value={data.planExecText} />
-            </TabPanel>
-
-            <TabPanel value={this.state.tab} index={1}>
-              <FormattedText value={data.planExecJson} />
-            </TabPanel>
-
-            <TabPanel value={this.state.tab} index={2}>
-              <FormattedText value={data.planText} />
-            </TabPanel>
-
-            <h4>Statistics:</h4>
-            <FormattedText value={data.stats.trim()} />
-
-            <h4>Query locks:</h4>
-            <FormattedText value={data.queryLocks} />
-
-            <h4>Details:</h4>
-            <Typography component='p'>
-              <span>Uploaded</span>:&nbsp;{
-                formatDistanceToNowStrict(new Date(data.createdAt), { addSuffix: true })
-              }&nbsp;
-              ({ format.formatTimestampUtc(data.createdAt) })
-            </Typography>
-          </div>
-        ) : (
-          <div>
-            <h4>Response:</h4>
-            <FormattedText value={data.response} />
-          </div>
-        )}
-
-        { data.error && data.error.length > 0 &&
-          <FormattedText value={data.error} />
-        }
-
-        <div>
-          <Dialog
-            fullScreen
-            open={ openVizDialog }
-            onClose={this.handleDialogClose}
-            TransitionComponent={FullScreenDialogTransition}
-          >
-            <AppBar className={classes.appBar}>
-              <Toolbar>
-                <Typography variant='h2' className={classes.title}>
-                  Visualization
-                </Typography>
-                <IconButton
-                  edge='end'
-                  color='inherit'
-                  onClick={this.closeExternalVisualization}
-                  aria-label='close'
-                >
-                  <CloseIcon />
-                </IconButton>
-              </Toolbar>
-            </AppBar>
-
-            { showFlameGraph &&
-              <div className={classes.flameGraphContainer}>
-                <h4>Flame Graph (buffers):</h4>
-                <FlameGraph
-                  data={data.planExecJson}
-                  type='buffers'
-                  name='chart-b'
-                />
-
-                <h4>Flame Graph (timing):</h4>
-                <FlameGraph
-                  data={data.planExecJson}
-                  type='time'
-                  name='chart-t'
-                />
-              </div>
-            }
-
-            { externalVisualization.url &&
-              <iframe
-                id='iframeVisualization'
-                title='Visualization'
-                src={externalVisualization.url}
-                className={classes.visFrame}
-              />
-            }
-          </Dialog>
-        </div>
-
-        <div className={classes.bottomSpace}/>
-      </div>
-    );
-  }
-}
-
-JoeSessionCommand.propTypes = {
-  classes: PropTypes.object.isRequired,
-  theme: PropTypes.object.isRequired
-};
-
-export default JoeSessionCommand
diff --git a/ui/packages/platform/src/pages/Profile/ProfileWrapper.tsx b/ui/packages/platform/src/pages/Profile/ProfileWrapper.tsx
deleted file mode 100644
index ce42478b..00000000
--- a/ui/packages/platform/src/pages/Profile/ProfileWrapper.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import Profile from 'pages/Profile'
-
-export const ProfileWrapper = () => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        // To be consistent with parent layout.
-        // TODO(anton): rewrite parent layout.
-        paddingBottom: 'inherit',
-      },
-      container: {
-        display: 'flex',
-        flexWrap: 'wrap',
-      },
-      textField: {
-        marginLeft: theme.spacing(1),
-        marginRight: theme.spacing(1),
-      },
-      dense: {
-        marginTop: 16,
-      },
-      menu: {
-        width: 2100,
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <Profile classes={classes} />
-}
diff --git a/ui/packages/platform/src/pages/Profile/index.jsx b/ui/packages/platform/src/pages/Profile/index.jsx
deleted file mode 100644
index 045996bf..00000000
--- a/ui/packages/platform/src/pages/Profile/index.jsx
+++ /dev/null
@@ -1,129 +0,0 @@
-/*--------------------------------------------------------------------------
-* Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-* All Rights Reserved. Proprietary and confidential.
-* Unauthorized copying of this file, via any medium is strictly prohibited
-*--------------------------------------------------------------------------
-*/
-
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import Grid from '@material-ui/core/Grid';
-
-import { TextField } from '@postgres.ai/shared/components/TextField';
-import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner';
-
-import Store from 'stores/store';
-import Actions from 'actions/actions';
-import { ErrorWrapper } from 'components/Error/ErrorWrapper';
-import ConsolePageTitle from 'components/ConsolePageTitle';
-import { Head, createTitle } from 'components/Head';
-
-const PAGE_NAME = 'Profile';
-
-class Profile extends Component {
-  componentDidMount() {
-    const that = this;
-
-    this.unsubscribe = Store.listen(function () {
-      const auth = this.data && this.data.auth ? this.data.auth : null;
-      const userProfile = this.data && this.data.userProfile ?
-        this.data.userProfile : null;
-
-      that.setState({ data: this.data });
-
-      if (auth && auth.token && !userProfile.isProcessed && !userProfile.isProcessing &&
-        !userProfile.error) {
-        Actions.getUserProfile(auth.token);
-      }
-    });
-
-    Actions.refresh();
-  }
-
-  componentWillUnmount() {
-    this.unsubscribe();
-  }
-
-  render() {
-    const { classes } = this.props;
-    const data = this.state && this.state.data ? this.state.data.userProfile : null;
-
-    const headRendered = (
-      <Head title={createTitle([PAGE_NAME])} />
-    );
-
-    const pageTitle = (
-      <ConsolePageTitle top title={PAGE_NAME}/>
-    );
-
-    if (this.state && this.state.data && this.state.data.userProfile.error) {
-      return (
-        <div>
-          { headRendered }
-
-          {pageTitle}
-
-          <ErrorWrapper/>
-        </div>
-      );
-    }
-
-    if (!data || !data.data) {
-      return (
-        <div>
-          { headRendered }
-
-          {pageTitle}
-
-          <PageSpinner className={classes.progress} />
-        </div>
-      );
-    }
-
-    return (
-      <div className={classes.root}>
-        { headRendered }
-
-        {pageTitle}
-
-        <Grid
-          item
-          xs={12}
-          sm={6}
-          md={4}
-          lg={3}
-          xl={2}
-          className={classes.container}
-        >
-          <TextField
-            disabled
-            label='Email'
-            fullWidth
-            defaultValue={data.data.info.email}
-            className={classes.textField}
-          />
-          <TextField
-            disabled
-            label='First name'
-            fullWidth
-            defaultValue={data.data.info.first_name}
-            className={classes.textField}
-          />
-          <TextField
-            disabled
-            label='Last name'
-            fullWidth
-            defaultValue={data.data.info.last_name}
-            className={classes.textField}
-          />
-        </Grid>
-      </div>
-    );
-  }
-}
-
-Profile.propTypes = {
-  classes: PropTypes.object.isRequired,
-};
-
-export default Profile
diff --git a/ui/packages/platform/src/pages/SignIn/SignInWrapper.jsx b/ui/packages/platform/src/pages/SignIn/SignInWrapper.jsx
deleted file mode 100644
index e8d6f930..00000000
--- a/ui/packages/platform/src/pages/SignIn/SignInWrapper.jsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { makeStyles } from '@material-ui/core'
-import SignIn from 'pages/SignIn'
-
-export const SignInWrapper = () => {
-  const useStyles = makeStyles(
-    (theme) => ({
-      root: {
-        fontFamily: 'dinpro-regular,sans-serif',
-        backgroundColor: '#fbfbfb',
-        overflow: 'auto',
-        display: 'flex',
-        alignItems: 'center',
-        justifyContent: 'center',
-        padding: 40,
-        [theme.breakpoints.down('xs')]: {
-          padding: '40px 24px',
-        },
-      },
-
-      form: {
-        flex: '0 1 400px',
-        background: '#fff',
-        border: '1px solid #c9d8db',
-        margin: 'auto',
-        borderRadius: '3px',
-        padding: 40,
-        [theme.breakpoints.down('xs')]: {
-          padding: 24,
-        },
-      },
-
-      titleLink: {
-        textDecoration: 'none',
-        cursor: 'pointer',
-      },
-
-      title: {
-        fontFamily: 'dinpro-light,sans-serif',
-        color: '#1a1a1a',
-        fontSize: '3rem',
-        fontWeight: 300,
-        marginBottom: 32,
-        marginTop: 0,
-        textAlign: 'center',
-        [theme.breakpoints.down('xs')]: {
-          fontSize: '2rem',
-          marginBottom: 24,
-        },
-      },
-
-      button: {
-        fontFamily: 'roboto,sans-serif',
-        display: 'flex',
-        height: 40,
-        alignItems: 'center',
-        justifyContent: 'center',
-        fontSize: 14,
-        borderRadius: 5,
-        backgroundColor: '#fff',
-        color: 'rgba(0,0,0,.54)',
-        border: '1px solid #cecece',
-        fontWeight: 500,
-        marginBottom: 16,
-        textDecoration: 'none',
-        padding: 12,
-      },
-
-      buttonText: {
-        marginLeft: 24,
-        whiteSpace: 'nowrap',
-        flex: '0 0 131px',
-
-        [theme.breakpoints.down('xs')]: {
-          marginLeft: 16,
-        },
-      },
-
-      terms: {
-        fontFamily: 'inherit',
-        paddingTop: 10,
-        fontSize: 12,
-        textAlign: 'center',
-        lineHeight: '16px',
-      },
-    }),
-    { index: 1 },
-  )
-
-  const classes = useStyles()
-
-  return <SignIn classes={classes} />
-}
diff --git a/ui/packages/platform/src/pages/SignIn/index.jsx b/ui/packages/platform/src/pages/SignIn/index.jsx
deleted file mode 100644
index 5ff19e44..00000000
--- a/ui/packages/platform/src/pages/SignIn/index.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-/*--------------------------------------------------------------------------
-* Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
-* All Rights Reserved. Proprietary and confidential.
-* Unauthorized copying of this file, via any medium is strictly prohibited
-*--------------------------------------------------------------------------
-*/
-
-import PropTypes from 'prop-types';
-import Div100vh from 'react-div-100vh';
-import { GatewayLink } from '@postgres.ai/shared/components/GatewayLink';
-
-import settings from 'utils/settings';
-
-const getAuthUrlFor = provider => `${settings.authUrl}/auth?provider=${provider}`;
-
-
-const SignIn = (props) => {
-  const { classes } = props;
-
-  return (
-    // See https://stackoverflow.com/questions/37112218/css3-100vh-not-constant-in-mobile-browser.
-    <Div100vh className={ classes.root }>
-      <div className={ classes.form }>
-        <a className={ classes.titleLink } href={settings.rootUrl}>
-          <h1 className={ classes.title }>Postgres.ai</h1>
-        </a>
-
-        <div>
-          <a className={ classes.button } href={getAuthUrlFor('google')}>
-            <img width='18' src='/images/oauth-google-logo.png' alt="Google sign in" />
-            <span className={ classes.buttonText }>Sign in with Google</span>
-          </a>
-          <a className={ classes.button } href={getAuthUrlFor('linkedin')}>
-            <img width='18' src='/images/oauth-linkedin-logo.png' alt="LinkedIn sign in" />
-            <span className={ classes.buttonText }>Sign in with LinkedIn</span>
-          </a>
-          <a className={ classes.button } href={getAuthUrlFor('github')}>
-            <img width='18' src='/images/oauth-github-logo.png' alt="Github sign in" />
-            <span className={ classes.buttonText }>Sign in with GitHub</span>
-          </a>
-          <a className={ classes.button } href={getAuthUrlFor('gitlab')}>
-            <img width='18' src='/images/oauth-gitlab-logo.png' alt="Gitlab sign in" />
-            <span className={ classes.buttonText }>Sign in with GitLab</span>
-          </a>
-        </div>
-
-        <div className={ classes.terms }>
-          <small>
-            Signing in signifies that you have read and agree to our
-            <br />
-            <GatewayLink href={settings.rootUrl + '/tos'} target='_blank'>
-              Terms&nbsp;of&nbsp;Service
-            </GatewayLink>
-            &nbsp;and&nbsp;
-            <GatewayLink href={settings.rootUrl + '/privacy'} target='_blank'>
-              Privacy&nbsp;Policy
-            </GatewayLink>.
-          </small>
-        </div>
-      </div>
-    </Div100vh>
-  );
-};
-
-SignIn.propTypes = {
-  classes: PropTypes.object.isRequired,
-};
-
-export default SignIn;
diff --git a/ui/packages/platform/src/react-app-env.d.ts b/ui/packages/platform/src/react-app-env.d.ts
deleted file mode 100644
index 02edd50c..00000000
--- a/ui/packages/platform/src/react-app-env.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-// eslint-disable-next-line
-/// <reference types="react-scripts" />
-
-// Env types.
-declare namespace NodeJS {
-  interface ProcessEnv {
-    readonly REACT_APP_SENTRY_DSN: string | undefined
-    readonly REACT_APP_API_SERVER: string | undefined
-    readonly BUILD_TIMESTAMP: number
-  }
-}
diff --git a/ui/packages/platform/src/react-syntax-highlighter.d.ts b/ui/packages/platform/src/react-syntax-highlighter.d.ts
deleted file mode 100644
index 5de75d31..00000000
--- a/ui/packages/platform/src/react-syntax-highlighter.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-declare module 'react-syntax-highlighter/dist/esm/languages/prism/supported-languages' {
-  const languages: string[];
-  export default languages;
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/registerServiceWorker.js b/ui/packages/platform/src/registerServiceWorker.js
deleted file mode 100644
index e9ab1230..00000000
--- a/ui/packages/platform/src/registerServiceWorker.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-// In production, we register a service worker to serve assets from local cache.
-
-// This lets the app load faster on subsequent visits in production, and gives
-// it offline capabilities. However, it also means that developers (and users)
-// will only see deployed updates on the "N+1" visit to a page, since previously
-// cached resources are updated in the background.
-
-// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
-// This link also includes instructions on opting out of this behavior.
-
-const isLocalhost = Boolean(
-  window.location.hostname === 'localhost' ||
-    // [::1] is the IPv6 localhost address.
-    window.location.hostname === '[::1]' ||
-    // 127.0.0.1/8 is considered localhost for IPv4.
-    window.location.hostname.match(
-      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
-    ),
-)
-
-export default function register() {
-  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
-    // The URL constructor is available in all browsers that support SW.
-    const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
-    if (publicUrl.origin !== window.location.origin) {
-      // Our service worker won't work if PUBLIC_URL is on a different origin
-      // from what our page is served on. This might happen if a CDN is used to
-      // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
-      return
-    }
-
-    window.addEventListener('load', () => {
-      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
-
-      if (isLocalhost) {
-        // This is running on localhost. Lets check if a service worker still exists or not.
-        checkValidServiceWorker(swUrl)
-
-        // Add some additional logging to localhost, pointing developers to the
-        // service worker/PWA documentation.
-        navigator.serviceWorker.ready.then(() => {
-          console.log(
-            'This web app is being served cache-first by a service ' +
-              'worker. To learn more, visit https://goo.gl/SC7cgQ',
-          )
-        })
-      } else {
-        // Is not local host. Just register service worker
-        registerValidSW(swUrl)
-      }
-    })
-  }
-}
-
-function registerValidSW(swUrl) {
-  navigator.serviceWorker
-    .register(swUrl)
-    .then((registration) => {
-      registration.onupdatefound = () => {
-        const installingWorker = registration.installing
-        if (installingWorker) {
-          installingWorker.onstatechange = () => {
-            if (installingWorker.state === 'installed') {
-              if (navigator.serviceWorker.controller) {
-                // At this point, the old content will have been purged and
-                // the fresh content will have been added to the cache.
-                // It's the perfect time to display a "New content is
-                // available; please refresh." message in your web app.
-                console.log('New content is available; please refresh.')
-              } else {
-                // At this point, everything has been precached.
-                // It's the perfect time to display a
-                // "Content is cached for offline use." message.
-                console.log('Content is cached for offline use.')
-              }
-            }
-          }
-        }
-      }
-    })
-    .catch((error) => {
-      console.error('Error during service worker registration:', error)
-    })
-}
-
-function checkValidServiceWorker(swUrl) {
-  // Check if the service worker can be found. If it can't reload the page.
-  fetch(swUrl)
-    .then((response) => {
-      // Ensure service worker exists, and that we really are getting a JS file.
-      if (
-        response.status === 404 ||
-        response.headers.get('content-type')?.indexOf('javascript') === -1
-      ) {
-        // No service worker found. Probably a different app. Reload the page.
-        navigator.serviceWorker.ready.then((registration) => {
-          registration.unregister().then(() => {
-            window.location.reload()
-          })
-        })
-      } else {
-        // Service worker found. Proceed as normal.
-        registerValidSW(swUrl)
-      }
-    })
-    .catch(() => {
-      console.log(
-        'No internet connection found. App is running in offline mode.',
-      )
-    })
-}
-
-export function unregister() {
-  if ('serviceWorker' in navigator) {
-    navigator.serviceWorker.ready.then((registration) => {
-      registration.unregister()
-    })
-  }
-}
diff --git a/ui/packages/platform/src/stores/app.ts b/ui/packages/platform/src/stores/app.ts
deleted file mode 100644
index 8fc169fb..00000000
--- a/ui/packages/platform/src/stores/app.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { makeAutoObservable } from 'mobx'
-
-import { getMeta } from 'api/getMeta'
-import { BUILD_TIMESTAMP } from 'config/env'
-
-// 60 sec.
-const META_INFO_POLLING_PERIOD = 60 * 1000
-
-class AppStore {
-  isOutdatedVersion = false
-
-  constructor() {
-    makeAutoObservable(this)
-
-    this.checkMetaInfo()
-  }
-
-  private loadMetaInfo = async () => {
-    const { response } = await getMeta()
-    if (!response) return
-
-    this.isOutdatedVersion = response.buildTimestamp > BUILD_TIMESTAMP
-  }
-
-  private checkMetaInfo = async () => {
-    await this.loadMetaInfo()
-    if (this.isOutdatedVersion) return
-    setTimeout(this.checkMetaInfo, META_INFO_POLLING_PERIOD)
-  }
-}
-
-export const appStore = new AppStore()
diff --git a/ui/packages/platform/src/stores/banners.ts b/ui/packages/platform/src/stores/banners.ts
deleted file mode 100644
index df059a33..00000000
--- a/ui/packages/platform/src/stores/banners.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { makeAutoObservable } from 'mobx'
-
-class BannersStore {
-  isOpenDeprecatedApi = false
-
-  constructor() {
-    makeAutoObservable(this)
-  }
-
-  showDeprecatedApi = () => {
-    this.isOpenDeprecatedApi = true
-  }
-
-  hideDeprecatedApi = () => {
-    this.isOpenDeprecatedApi = false
-  }
-}
-
-export const bannersStore = new BannersStore()
diff --git a/ui/packages/platform/src/stores/preformatJoeMessage.ts b/ui/packages/platform/src/stores/preformatJoeMessage.ts
deleted file mode 100644
index fa216bf4..00000000
--- a/ui/packages/platform/src/stores/preformatJoeMessage.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { emoji } from 'config/emoji'
-
-export const preformatJoeMessage = (text: string) => {
-  let result = text
-
-  // Preprocess emoji.
-  const emojiEntries = Object.entries(emoji)
-
-  for (const [emojiKey, emojiValue] of emojiEntries) {
-    result = result.split(`:${emojiKey}:`).join(emojiValue)
-  }
-
-  // Remove artifacts links.
-  result = result.replace('_(The text in the preview above has been cut)_', '')
-  result = result.replace(
-    'Other artifacts are provided in the thread',
-    'Other artifacts are provided below',
-  )
-
-  // Prepare links.
-  result = result.replace(/<(.*)\|(.*)>/gm, '[$2]($1)')
-
-  // Preprocess  bold.
-  // Matched example: 'Hello *username*!' -> 'Hello **username**!'.
-  // Unmatched example: 'explain select * from user where username ~*' -> 'explain select * from user where username ~*'.
-  // TODO(Anton): Tests like Jest.
-  result = result.replace(/\*(\S+)\*/g, '**$1**')
-
-  // Preprocess code blocks.
-  result = result.replace(/```(.*)```/g, '`$1`')
-  result = result.split(/^```/m).join('```\n')
-  result = result.split(/```$/m).join('\n```')
-  result = result.split('\n').join('  \n')
-
-  return result
-}
diff --git a/ui/packages/platform/src/stores/store.js b/ui/packages/platform/src/stores/store.js
deleted file mode 100644
index 8785cda6..00000000
--- a/ui/packages/platform/src/stores/store.js
+++ /dev/null
@@ -1,2965 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import Reflux from 'reflux';
-import jwtDecode from 'jwt-decode';
-import md5 from 'md5';
-import { v4 as uuidv4 } from 'uuid';
-
-import { emoji } from 'config/emoji';
-import { localStorage } from 'helpers/localStorage';
-
-import settings from '../utils/settings';
-import Actions from '../actions/actions';
-import format from '../utils/format';
-
-import { preformatJoeMessage } from './preformatJoeMessage';
-import { createWebSocket } from 'utils/webSockets';
-
-const WS_MAX_RETRY_CONNECTION_COUNT = 5;
-
-const joeInstance = {
-  channels: {},
-  currentChannelId: null,
-  channelsError: null,
-  isChannelsProcessing: false,
-  isChannelsProcessed: false,
-
-  data: [],
-  isProcessing: false,
-  isProcessed: false,
-  error: null,
-  errorMessage: null,
-
-  sessionId: null,
-  commandError: null,
-  isCommandProcessing: false,
-  isCommandProcessed: false,
-
-  messages: {},
-  messageError: null,
-  isMessagesProcessing: false,
-  isMessagesProcessed: false
-};
-
-const storeItem = {
-  isProcessing: false,
-  isProcessed: false,
-  data: null,
-  error: null
-};
-
-const initialState = {
-  app: {
-    isProcessing: false,
-    data: null,
-    error: null,
-    needUpdate: false
-  },
-  auth: {
-    isProcessing: false,
-    userId: null,
-    token: null,
-    error: null
-  },
-  dashboard: {
-    profileUpdateInitAfterDemo: false
-  },
-  userProfile: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  inviteUser: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  useDemoData: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  orgProfile: {
-    isProcessing: false,
-    isProcessed: false,
-    orgDomains: {},
-    data: null,
-    error: null,
-    isUpdating: false
-  },
-  orgUsers: storeItem,
-  userTokens: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  tokenRequest: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  reports: {
-    projectId: 0,
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  report: {
-    reportId: null,
-    isProcessing: false,
-    isProcessed: false,
-    isDownloading: false,
-    isDeleting: false,
-    isDeleted: false,
-    data: null,
-    error: null
-  },
-  reportFile: {
-    files: {},
-    fileId: null,
-    isProcessing: false,
-    error: null
-  },
-  sessions: {
-    projectId: 0,
-    isProcessing: false,
-    isProcessed: false,
-    isDeleting: false,
-    isDeleted: false,
-    data: null,
-    error: null
-  },
-  commands: {
-    projectId: 0,
-    isProcessing: false,
-    isProcessed: false,
-    isHistoryExists: [],
-    data: null,
-    error: null
-  },
-  command: {
-    projectId: 0,
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  projects: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  dbLabInstances: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  newDbLabInstance: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  dbLabInstanceSnapshots: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  dbLabInstanceStatus: {
-    instances: {},
-    instanceId: null,
-    isProcessing: false,
-    error: null
-  },
-  dbLabCloneStatus: {
-    isProcessing: false,
-    clones: {},
-    cloneId: null,
-    instanceId: null,
-    error: null
-  },
-  notification: {
-    message: null,
-    type: null,
-    duration: null
-  },
-  joeInstances: {
-    isProcessing: false,
-    isProcessed: false,
-    data: null,
-    error: null
-  },
-  joeInstance: {
-    instances: {}
-  },
-  externalVisualization: {
-    isProcessing: false,
-    isProcessed: false,
-    error: null,
-    url: null,
-    plan: null,
-    type: null
-  },
-  newJoeInstance: storeItem,
-  sharedUrlData: storeItem,
-  shareUrl: storeItem,
-  sharedUrls: {
-    command: {}
-  },
-  billing: {
-    ...storeItem,
-    isSubscriptionProcessing: false,
-    isSubscriptionProcessed: false,
-    paymentMethodId: ''
-  },
-  dbLabInstance: storeItem,
-  dbLabSessions: {
-    ...storeItem,
-    isSessionExists: []
-  },
-  dbLabSession: {
-    ...storeItem,
-    isLogDownloading: false,
-    logs: {}
-  },
-  auditLog: storeItem
-};
-
-const Store = Reflux.createStore({
-  listenables: [Actions],
-
-  data: Object.assign({}, initialState),
-
-  getInitialState: function () {
-    return Object.assign({}, initialState);
-  },
-
-  getError: function (data) {
-    let result = '';
-
-    if (data.message === 'JWT expired' ||
-      data.details === 'Deprecated JWT value') {
-      Actions.signOut();
-    }
-
-    if (data.code || data.details || data.hint) {
-      if (data.details) {
-        result = data.details;
-      }
-
-      if (data.hint) {
-        result = result + ' ' + data.hint;
-      }
-
-      if (result !== '') {
-        return result;
-      }
-
-      if (data.message) {
-        return data.message;
-      }
-
-      return true;
-    }
-
-    return null;
-  },
-
-
-  onShowNotification(message, type, duration = 3000) {
-    this.data.notification.message = message;
-    this.data.notification.type = type;
-    this.data.notification.duration = duration;
-    this.trigger(this.data);
-  },
-
-  onHideNotification() {
-    this.data.notification.message = null;
-    this.data.notification.type = null;
-    this.data.notification.duration = null;
-    this.trigger(this.data);
-  },
-
-  onRefresh() {
-    this.data.inviteUser = {
-      isProcessing: false,
-      isProcessed: false,
-      data: null,
-      error: null
-    };
-
-    this.trigger(this.data);
-  },
-
-  onSignOut() {
-    this.data.auth.userId = null;
-    this.data.auth.token = null;
-    this.data.auth.error = null;
-    this.data.auth.errorMessage = null;
-    localStorage.removeAuthToken();
-    window.location = settings.signinUrl;
-  },
-
-
-  onDoAuthFailed: function (error) {
-    this.data.auth.isProcessing = false;
-    this.data.auth.isProcessed = true;
-    this.data.auth.error = true;
-    this.data.auth.errorMessage = error.message;
-    localStorage.removeAuthToken();
-    this.trigger(this.data);
-  },
-
-  onDoAuthProgressed: function () {
-    this.data.auth.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onDoAuthCompleted: function (data) {
-    this.data.auth.isProcessing = false;
-    this.data.auth.isProcessed = true;
-    this.data.auth.errorMessage = this.getError(data);
-    this.data.auth.error = !!this.data.auth.errorMessage;
-
-    if (typeof data.token !== 'undefined') {
-      let tokenData = null;
-
-      try {
-        tokenData = jwtDecode(data.token);
-      } catch (e) {
-        console.log(e);
-      }
-
-      if (tokenData && typeof tokenData.id !== 'undefined') {
-        this.data = this.getInitialState();
-        this.data.auth.token = data.token;
-        localStorage.setAuthToken(data.token);
-        this.data.auth.userId = parseInt(tokenData.id, 10);
-        Actions.getUserProfile(this.data.auth.token);
-      } else {
-        localStorage.removeAuthToken();
-        this.data.auth.token = null;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetUserProfileFailed: function (error) {
-    this.data.userProfile.isProcessing = false;
-    this.data.userProfile.error = true;
-    this.data.userProfile.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetUserProfileProgressed: function () {
-    this.data.userProfile.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetUserProfileCompleted: function (data) {
-    this.data.userProfile.isProcessing = false;
-    this.data.userProfile.errorMessage = this.getError(data);
-    this.data.userProfile.error = !!this.data.userProfile.errorMessage;
-
-    if (!this.data.userProfile.error && data.length > 0) {
-      this.data.userProfile.data = data[0];
-      this.data.userProfile.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetOrgsFailed: function (error) {
-    this.data.orgProfile.isProcessing = false;
-    this.data.orgProfile.error = true;
-    this.data.orgProfile.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetOrgsProgressed: function (data) {
-    this.data.orgProfile.isProcessing = true;
-    this.data.orgProfile.orgId = data.orgId;
-
-    this.trigger(this.data);
-  },
-
-  onGetOrgsCompleted: function (data) {
-    this.data.orgProfile.isProcessing = false;
-    this.data.orgProfile.errorMessage = this.getError(data.data);
-    this.data.orgProfile.error = this.data.orgProfile.errorMessage;
-
-    if (!this.data.orgProfile.error) {
-      if (data.data.length > 0) {
-        let users = [];
-
-        this.data.orgProfile.prevAlias = null;
-        this.data.orgProfile.data = data.data[0];
-        for (let i in data.data[0].users) {
-          if (data.data[0].users.hasOwnProperty(i)) {
-            users.push(data.data[0].users[i]);
-          }
-        }
-
-        this.data.orgProfile.data.users = users;
-        this.data.orgProfile.orgId = data.orgId;
-        this.data.orgProfile.isProcessed = true;
-      } else {
-        this.data.orgProfile.error = true;
-        this.data.orgProfile.errorMessage =
-          'You do not have permission to view this page.';
-        this.data.orgProfile.errorCode = 403;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetOrgUsersFailed: function (error) {
-    this.data.orgUsers.isProcessing = false;
-    this.data.orgUsers.error = true;
-    this.data.orgUsers.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetOrgUsersProgressed: function (orgId) {
-    this.data.orgUsers.isProcessing = true;
-    this.data.orgUsers.isRefreshed = false;
-    this.data.orgUsers.isRefresh = false;
-    if (this.data.orgUsers.orgId === orgId) {
-      this.data.orgUsers.isRefresh = true;
-    }
-    this.data.orgUsers.orgId = orgId;
-    this.data.orgUsers.isUpdating = false;
-    this.data.orgUsers.isUpdated = false;
-    this.data.orgUsers.updateUserId = null;
-    this.data.orgUsers.isDeleting = false;
-    this.data.orgUsers.isDeleted = false;
-    this.data.orgUsers.deleteUserId = null;
-
-    this.trigger(this.data);
-  },
-
-  onGetOrgUsersCompleted: function (orgId, data) {
-    this.data.orgUsers.isProcessing = false;
-    this.data.orgUsers.errorMessage = this.getError(data);
-    this.data.orgUsers.error = !!this.data.orgUsers.errorMessage;
-
-    if (this.data.orgUsers.isRefresh) {
-      this.data.orgUsers.isRefreshed = true;
-    }
-
-    if (!this.data.orgUsers.error) {
-      if (data.length > 0) {
-        let users = [];
-        let roles = [];
-
-        this.data.orgUsers.data = data[0];
-        for (let i in data[0].users) {
-          if (data[0].users.hasOwnProperty(i)) {
-            users.push(data[0].users[i]);
-          }
-        }
-
-        for (let i in data[0].roles) {
-          if (data[0].roles.hasOwnProperty(i)) {
-            roles.push(data[0].roles[i]);
-          }
-        }
-
-        this.data.orgUsers.data.users = users;
-        this.data.orgUsers.data.roles = roles;
-        this.data.orgUsers.orgId = orgId;
-        this.data.orgUsers.isProcessed = true;
-      } else {
-        this.data.orgUsers.error = true;
-        this.data.orgUsers.errorMessage =
-          'You do not have permission to view this page.';
-        this.data.orgUsers.errorCode = 403;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onUpdateOrgFailed: function (error) {
-    this.data.orgProfile.isUpdating = false;
-    this.data.orgProfile.updateError = true;
-    this.data.orgProfile.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onUpdateOrgProgressed: function (data) {
-    this.data.orgProfile.updateErrorFields = null;
-    this.data.orgProfile.isUpdating = true;
-
-    if (data && data.name) {
-      this.data.orgProfile.data.updateName = data.name;
-    }
-
-    if (data && data.alias) {
-      this.data.orgProfile.data.updateAlias = data.alias;
-    }
-
-    if (data && data.users_autojoin) {
-      this.data.orgProfile.data.updateUsersAutojoin = data['users_autojoin'];
-    }
-
-    this.trigger(this.data);
-  },
-
-  onUpdateOrgCompleted: function (data) {
-    this.data.orgProfile.isUpdating = false;
-    this.data.orgProfile.updateErrorMessage = this.getError(data);
-    this.data.orgProfile.updateError = !!this.data.orgProfile.updateErrorMessage;
-
-    if (!this.data.orgProfile.updateError && data.length > 0) {
-      this.data.orgProfile.prevAlias = this.data.orgProfile.data?.alias;
-      this.data.orgProfile.data = data[0];
-      Actions.getUserProfile(this.data.auth.token);
-      Actions.getOrgs(this.data.auth.token, this.data.orgProfile.orgId);
-      this.data.orgProfile.updateErrorFields = null;
-      Actions.showNotification('Organizations settings successfully saved.', 'success');
-    } else {
-      this.data.orgProfile.updateErrorFields = [];
-
-      if (this.data.orgProfile.updateErrorMessage.indexOf('u_orgs_name') !==
-        -1) {
-        this.data.orgProfile.updateErrorMessage =
-          'Organization with same name already exists.';
-        this.data.orgProfile.updateErrorFields.push('name');
-        this.data.orgProfile.updateError = true;
-      }
-
-      if (this.data.orgProfile.updateErrorMessage.indexOf('u_orgs_alias') !==
-        -1) {
-        this.data.orgProfile.updateErrorMessage =
-          'Organization with the same alias already exists.';
-        this.data.orgProfile.updateErrorFields.push('alias');
-        this.data.orgProfile.updateError = true;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-  //bot settings
-
-  onUpdateAiBotSettingsFailed: function (error) {
-    this.data.orgProfile.isUpdating = false;
-    this.data.orgProfile.updateError = true;
-    this.data.orgProfile.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onUpdateAiBotSettingsProgressed: function (data) {
-    this.data.orgProfile.updateErrorFields = null;
-    this.data.orgProfile.isUpdating = true;
-
-    this.trigger(this.data);
-  },
-
-  onUpdateAiBotSettingsCompleted: function (data) {
-    this.data.orgProfile.isUpdating = false;
-    this.data.orgProfile.updateErrorMessage = this.getError(data);
-    this.data.orgProfile.updateError = !!this.data.orgProfile.updateErrorMessage;
-
-    if (!this.data.orgProfile.updateError && data.length > 0) {
-      this.data.orgProfile.updateErrorFields = null;
-      Actions.showNotification('AI Bot settings successfully saved.', 'success');
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onCreateOrgFailed: function (error) {
-    this.data.orgProfile.isUpdating = false;
-    this.data.orgProfile.updateError = true;
-    this.data.orgProfile.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onCreateOrgProgressed: function (data) {
-    this.data.orgProfile.updateErrorFields = null;
-    this.data.orgProfile.isUpdating = true;
-    this.data.orgProfile.data = {};
-
-    if (data && data.name) {
-      this.data.orgProfile.data.updateName = data.name;
-    }
-
-    if (data && data.alias) {
-      this.data.orgProfile.data.updateAlias = data.alias;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onCreateOrgCompleted: function (data) {
-    this.data.orgProfile.isUpdating = false;
-    this.data.orgProfile.updateErrorMessage = this.getError(data);
-    this.data.orgProfile.updateError = !!this.data.orgProfile.updateErrorMessage;
-
-    if (!this.data.orgProfile.updateError && data) {
-      this.data.orgProfile.prevAlias = this.data.orgProfile.data.alias;
-      this.data.orgProfile.data = data;
-      Actions.getUserProfile(this.data.auth.token);
-      Actions.getOrgs(this.data.auth.token, this.data.orgProfile.orgId);
-      this.data.orgProfile.updateErrorFields = null;
-      window.location.pathname = this.data.orgProfile.data.alias
-    } else {
-      this.data.orgProfile.updateErrorFields = [];
-
-      if (this.data.orgProfile.updateErrorMessage.indexOf('u_orgs_name') !==
-        -1) {
-        this.data.orgProfile.updateErrorMessage =
-          'Organization with same name already exists.';
-        this.data.orgProfile.updateErrorFields.push('name');
-      }
-
-      if (this.data.orgProfile.updateErrorMessage.indexOf('u_orgs_alias') !==
-        -1) {
-        this.data.orgProfile.updateErrorMessage =
-          'Organization with same alias already exists.';
-        this.data.orgProfile.updateErrorFields.push('alias');
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onInviteUserFailed: function (error) {
-    this.data.inviteUser.isUpdating = false;
-    this.data.inviteUser.isProcessed = false;
-    this.data.inviteUser.updateError = true;
-    this.data.inviteUser.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onInviteUserProgressed: function (data) {
-    this.data.inviteUser.updateErrorFields = null;
-    this.data.inviteUser.isUpdating = true;
-    this.data.inviteUser.data = {};
-
-    if (data && data.email) {
-      this.data.inviteUser.data.email = data.email;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onInviteUserCompleted: function (data) {
-    this.data.inviteUser.isUpdating = false;
-    this.data.inviteUser.errorMessage = this.getError(data);
-    this.data.inviteUser.error = !!this.data.inviteUser.errorMessage;
-
-    if (!this.data.inviteUser.updateError && data) {
-      this.data.inviteUser.prevEmail = this.data.inviteUser.data.alias;
-      this.data.inviteUser.data = data;
-      this.data.inviteUser.isProcessed = true;
-      Actions.getOrgUsers(this.data.auth.token, this.data.orgProfile.orgId);
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onUseDemoDataFailed: function (error) {
-    this.data.useDemoData.isProcessing = false;
-    this.data.useDemoData.isProcessed = false;
-    this.data.useDemoData.error = true;
-    this.data.useDemoData.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onUseDemoDataProgressed: function () {
-    this.data.useDemoData.updateErrorFields = null;
-    this.data.useDemoData.isProcessing = true;
-    this.data.useDemoData.data = {};
-    this.trigger(this.data);
-  },
-
-  onUseDemoDataCompleted: function (data) {
-    this.data.useDemoData.isProcessing = false;
-    this.data.useDemoData.errorMessage = this.getError(data);
-    this.data.useDemoData.error = !!this.data.useDemoData.errorMessage;
-
-    if (this.data.useDemoData.error) {
-      Actions.showNotification(this.data.useDemoData.errorMessage, 'error');
-    } else {
-      this.data.useDemoData.data = data;
-      this.data.useDemoData.isProcessed = true;
-
-      Actions.showNotification('Successfully joined the Demo organization.', 'success');
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetAccessTokensFailed: function (error) {
-    this.data.userTokens.isProcessing = false;
-    this.data.userTokens.error = true;
-    this.data.userTokens.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetAccessTokensProgressed: function () {
-    this.data.userTokens.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetAccessTokensCompleted: function (data) {
-    this.data.userTokens.isProcessing = false;
-    this.data.userTokens.errorMessage = this.getError(data.data);
-    this.data.userTokens.error = !!this.data.userTokens.errorMessage;
-
-    if (!this.data.userTokens.error) {
-      this.data.userTokens.data = data.data;
-      this.data.userTokens.orgId = data.orgId;
-
-      this.data.userTokens.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetAccessTokenFailed: function (error) {
-    this.data.tokenRequest.isProcessing = false;
-    this.data.tokenRequest.error = true;
-    this.data.tokenRequest.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetAccessTokenProgressed: function () {
-    this.data.tokenRequest = {
-      isProcessing: true,
-      isProcessed: false,
-      data: null,
-      error: null
-    };
-    this.trigger(this.data);
-  },
-
-  onGetAccessTokenCompleted: function (data) {
-    this.data.tokenRequest.isProcessing = false;
-    this.data.tokenRequest.errorMessage = this.getError(data.data);
-    this.data.tokenRequest.error = !!this.data.tokenRequest.errorMessage;
-
-    if (!this.data.tokenRequest.error && data.data && data.orgId) {
-      this.data.tokenRequest.data = data.data;
-      this.data.tokenRequest.isProcessed = true;
-      Actions.getAccessTokens(this.data.auth.token, data.orgId);
-    } else if (this.data.tokenRequest.errorMessage.indexOf('timestamp') !==
-      -1) {
-      this.data.tokenRequest.errorMessage = 'Check the expiration date.';
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onHideGeneratedAccessToken: function () {
-    this.data.tokenRequest.isProcessed = false;
-    this.data.tokenRequest.data = null;
-    this.trigger(this.data);
-  },
-
-
-  onRevokeAccessTokenFailed: function (error) {
-    console.log('Revoking token', error);
-  },
-
-  onRevokeAccessTokenProgressed: function (id) {
-    for (let i in this.data.userTokens.data) {
-      if (this.data.userTokens.data[i].id === id) {
-        this.data.userTokens.data[i].revoking = true;
-      }
-    }
-  },
-
-  onRevokeAccessTokenCompleted: function (data) {
-    this.data.userTokens.revokeErrorMessage = this.getError(data.data);
-    this.data.userTokens.revokeError =
-      !!this.data.userTokens.revokeErrorMessage;
-
-    Actions.hideGeneratedAccessToken();
-    if (data && data.orgId) {
-      Actions.getAccessTokens(this.data.auth.token, data.orgId);
-    }
-
-    if (this.data.userTokens.revokeError) {
-      Actions.showNotification(this.data.userTokens.revokeErrorMessage, 'error');
-    } else {
-      Actions.showNotification('Token revoked.', 'success');
-    }
-  },
-
-
-  onGetCheckupReportsFailed: function (error) {
-    this.data.reports.isProcessing = false;
-    this.data.reports.error = true;
-    this.data.reports.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetCheckupReportsProgressed: function () {
-    this.data.reports.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetCheckupReportsCompleted: function (data) {
-    this.data.reports.isProcessing = false;
-    this.data.reports.errorMessage = this.getError(data);
-    this.data.reports.error = !!this.data.reports.errorMessage;
-
-    if (!this.data.reports.error) {
-      this.data.reports.data = data.data;
-      this.data.reports.orgId = data.orgId;
-      this.data.reports.projectId = data.projectId;
-      this.data.reports.reportId = data.reportId;
-      this.data.reports.isProcessed = true;
-      if (!data.data.length && data.reportId) {
-        this.data.reports.error = true;
-        this.data.reports.errorMessage =
-          'Specified report not found or you have no access.';
-        this.data.reports.errorCode = 404;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetCheckupReportFilesFailed: function (error) {
-    this.data.report.isProcessing = false;
-    this.data.report.error = true;
-    this.data.report.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetCheckupReportFilesProgressed: function () {
-    this.data.report.isProcessing = true;
-    this.data.report.data = null;
-    this.data.report.reportId = null;
-    this.trigger(this.data);
-  },
-
-  onGetCheckupReportFilesCompleted: function (data) {
-    this.data.report.isProcessing = false;
-    this.data.report.errorMessage = this.getError(data);
-    this.data.report.error = !!this.data.report.errorMessage;
-
-    if (!this.data.report.error) {
-      this.data.report.reportId = data.reportId;
-      this.data.report.type = data.type;
-      this.data.report.data = data.data;
-      this.data.report.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetCheckupReportFileFailed: function (error) {
-    this.data.reportFile.isProcessing = false;
-    this.data.reportFile.error = true;
-    this.data.reportFile.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetCheckupReportFileProgressed: function () {
-    this.data.reportFile.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetCheckupReportFileCompleted: function (id, data) {
-    this.data.reportFile.isProcessing = false;
-    this.data.reportFile.errorMessage = this.getError(data);
-    this.data.reportFile.error = !!this.data.reportFile.errorMessage;
-
-
-    if (!this.data.reportFile.error && data.length > 0) {
-      let fileId = data[0].type + '_' + id;
-
-      this.data.reportFile.files[fileId] = data[0];
-
-      if (data[0].type === 'md') {
-        let mdData = this.data.reportFile.files[fileId].data;
-        // Replace [ not in links.
-        mdData = mdData.split(/\[(?!.*\]\(.*\))/).join('\\[');
-        // Replace ] not in links.
-        mdData = mdData.split(/\](?!\(.*\))/).join('\\]');
-        // Replace icons.
-        mdData = mdData.split(':warning:').join(emoji.warning);
-        mdData = mdData.split(':information_source:').join(emoji.large_blue_diamond);
-        this.data.reportFile.files[fileId].data = mdData;
-        this.data.reportFile.files[fileId].isProcessed = true;
-      }
-    } else {
-      this.data.reportFile.error = true;
-      this.data.reportFile.errorMessage = 'Specified file not found or you have no access.';
-      this.data.reportFile.errorCode = 404;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetJoeSessionsFailed: function (error) {
-    this.data.sessions.isProcessing = false;
-    this.data.sessions.error = true;
-    this.data.sessions.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetJoeSessionsProgressed: function () {
-    this.data.sessions.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetJoeSessionsCompleted: function (data) {
-    this.data.sessions.isProcessing = false;
-    this.data.sessions.errorMessage = this.getError(data);
-    this.data.sessions.error = !!this.data.sessions.errorMessage;
-
-    if (!this.data.sessions.error) {
-      this.data.sessions.data = data.data;
-      this.data.sessions.orgId = data.orgId;
-      this.data.sessions.projectId = data.projectId;
-      this.data.sessions.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetJoeSessionCommandsFailed: function (error) {
-    this.data.commands.isProcessing = false;
-    this.data.commands.error = true;
-    this.data.commands.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetJoeSessionCommandsProgressed: function (params) {
-    if (!params.lastId) {
-      this.data.commands.data = [];
-    }
-    this.data.commands.isProcessing = true;
-    this.data.commands.isProcessed = false;
-    this.trigger(this.data);
-  },
-
-  onGetJoeSessionCommandsCompleted: function (data, params) {
-    this.data.commands.isProcessing = false;
-    this.data.commands.errorMessage = this.getError(data);
-    this.data.commands.error = !!this.data.commands.errorMessage;
-    this.data.commands.isProcessed = true;
-
-    if (!this.data.commands.error) {
-      if (params.limit) {
-        if (data.length < params.limit) {
-          this.data.commands.isComplete = true;
-        } else {
-          this.data.commands.isComplete = false;
-        }
-      }
-
-      if (params.lastId && this.data.commands.data && params.limit) {
-        this.data.commands.data = [...this.data.commands.data, ...data];
-      } else {
-        this.data.commands.data = data;
-      }
-
-      this.data.commands.isHistoryExists[params.orgId] =
-        this.data.commands.isHistoryExists[params.orgId] || data.length > 0;
-      this.data.commands.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetJoeSessionCommandFailed: function (error) {
-    this.data.command.isProcessing = false;
-    this.data.command.error = true;
-    this.data.command.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetJoeSessionCommandProgressed: function () {
-    this.data.command.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetJoeSessionCommandCompleted: function (data) {
-    this.data.command.isProcessing = false;
-    this.data.command.errorMessage = this.getError(data);
-    this.data.command.error = !!this.data.command.errorMessage;
-
-    if (!this.data.command.error && data && data.id) {
-      const command = {
-        projectId: data.project_id,
-        sessionId: data.joe_session_id,
-        projectName: data.project_name,
-        commandId: data.id,
-        command: data.command,
-        query: data.query,
-        response: data.response,
-        planText: data.plan_text,
-        planExecText: data.plan_execution_text,
-        planExecJson: data.plan_execution_json,
-        stats: data.stats,
-        error: data.error,
-        queryLocks: data.query_locks || '',
-        slackUid: data.slack_uid,
-        slackUsername: data.slack_username,
-        slackChannel: data.slack_channel,
-        slackTs: data.slack_ts,
-        createdAt: data.created_at,
-        createdFormatted: data.created_formatted,
-        useremail: data.useremail,
-        username: data.username
-      };
-
-      this.data.command.data = command;
-      this.data.command.isProcessed = true;
-    } else {
-      this.data.command.error = true;
-      this.data.command.errorMessage = 'Specified command not found or you have no access.';
-      this.data.command.errorCode = 404;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetProjectsFailed: function (error) {
-    this.data.projects.isProcessing = false;
-    this.data.projects.error = true;
-    this.data.projects.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetProjectsProgressed: function () {
-    this.data.projects.isProcessing = true;
-    this.data.projects.orgId = null;
-    this.trigger(this.data);
-  },
-
-  onGetProjectsCompleted: function (data) {
-    this.data.projects.isProcessing = false;
-    this.data.projects.errorMessage = this.getError(data.data);
-    this.data.projects.error = !!this.data.projects.errorMessage;
-
-    if (!this.data.projects.error) {
-      if (data.orgId) {
-        this.data.projects.orgId = data.orgId;
-      }
-
-      this.data.projects.data = data.data;
-      this.data.projects.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onSetReportsProject(orgId, projectId) {
-    this.data.reports.orgId = orgId;
-    this.data.reports.projectId = projectId;
-    Actions.getCheckupReports(this.data.auth.token, orgId, projectId);
-    this.trigger(this.data);
-  },
-
-  onSetSessionsProject(orgId, projectId) {
-    this.data.sessions.orgId = orgId;
-    this.data.sessions.projectId = projectId;
-    Actions.getJoeSessions(this.data.auth.token, orgId, projectId);
-    this.trigger(this.data);
-  },
-
-  onSetDbLabInstancesProject(orgId, projectId) {
-    this.data.dbLabInstances.orgId = orgId;
-    this.data.dbLabInstances.projectId = projectId;
-    Actions.getDbLabInstances(this.data.auth.token, orgId, projectId);
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabInstancesFailed: function (error) {
-    this.data.dbLabInstances.isProcessing = false;
-    this.data.dbLabInstances.error = true;
-    this.data.dbLabInstances.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetDbLabInstancesProgressed: function () {
-    this.data.dbLabInstances.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetDbLabInstancesCompleted: function (data) {
-    this.data.dbLabInstances.isProcessing = false;
-    this.data.dbLabInstances.errorMessage = this.getError(data);
-    this.data.dbLabInstances.error = !!this.data.dbLabInstances.errorMessage;
-
-    if (!this.data.dbLabInstances.error) {
-      let dblabs = {};
-
-      for (let i in data.data) {
-        if (data.data.hasOwnProperty(i)) {
-          dblabs[data.data[i].id] = data.data[i];
-        }
-      }
-
-      this.data.dbLabInstances.data = dblabs;
-      this.data.dbLabInstances.orgId = data.orgId;
-      this.data.dbLabInstances.projectId = data.projectId;
-      this.data.dbLabInstances.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onAddDbLabInstanceFailed: function (error) {
-    this.data.newDbLabInstance.isUpdating = false;
-    this.data.newDbLabInstance.isProcessed = false;
-    this.data.newDbLabInstance.updateError = true;
-    this.data.newDbLabInstance.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onAddDbLabInstanceProgressed: function (data) {
-    this.data.newDbLabInstance.updateErrorFields = null;
-    this.data.newDbLabInstance.isUpdating = true;
-    this.data.newDbLabInstance.data = {};
-
-    if (data && data.email) {
-      this.data.newDbLabInstance.data.email = data.email;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onAddDbLabInstanceCompleted: function (data) {
-    this.data.newDbLabInstance.isUpdating = false;
-    this.data.newDbLabInstance.errorMessage = this.getError(data.data);
-    this.data.newDbLabInstance.error = !!this.data.newDbLabInstance.errorMessage;
-
-    if (!this.data.newDbLabInstance.error && data.data) {
-      this.data.newDbLabInstance.data = data.data;
-      this.data.newDbLabInstance.isProcessed = true;
-      // Update orgs and projects.
-      Actions.getUserProfile(this.data.auth.token);
-      Actions.getDbLabInstances(this.data.auth.token, data.orgId, data.data
-        .project_id);
-    }
-
-    this.trigger(this.data);
-  },
-
-  onEditDbLabInstanceFailed: function (error) {
-    this.data.newDbLabInstance.isUpdating = false;
-    this.data.newDbLabInstance.isProcessed = false;
-    this.data.newDbLabInstance.updateError = true;
-    this.data.newDbLabInstance.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onEditDbLabInstanceProgressed: function (data) {
-    this.data.newDbLabInstance.updateErrorFields = null
-    this.data.newDbLabInstance.isUpdating = true
-    this.data.newDbLabInstance.data = {}
-
-    if (data && data.email) {
-      this.data.newDbLabInstance.data.email = data.email
-    }
-      
-    this.trigger(this.data)
-  },
-
-  onEditDbLabInstanceCompleted: function (data) {
-    this.data.newDbLabInstance.isUpdating = false;
-    this.data.newDbLabInstance.errorMessage = this.getError(data.data);
-    this.data.newDbLabInstance.error = !!this.data.newDbLabInstance.errorMessage;
-
-    if (!this.data.newDbLabInstance.error && data.data) {
-      this.data.newDbLabInstance.data = data.data;
-      this.data.newDbLabInstance.isProcessed = true;
-      // Update orgs and projects.
-      Actions.getUserProfile(this.data.auth.token);
-      Actions.getDbLabInstances(this.data.auth.token, data.orgId, data.data
-        .project_id);
-      
-      if (window.location.href.indexOf('edit') > -1) {
-        let url = window.location.href.split('/edit')[0]
-        window.location.href = url
-      }
-    }
-    this.trigger(this.data);
-  },
-
-
-  onCheckDbLabInstanceUrlFailed: function (error) {
-    this.data.newDbLabInstance.isChecked = false;
-    this.data.newDbLabInstance.isCheckProcessed = true;
-    this.data.newDbLabInstance.isChecking = false;
-    this.data.newDbLabInstance.checkingError = true;
-    this.data.newDbLabInstance.checkingErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onCheckDbLabInstanceUrlProgressed: function () {
-    this.data.newDbLabInstance.isChecked = false;
-    this.data.newDbLabInstance.isCheckProcessed = false;
-    this.data.newDbLabInstance.isChecking = true;
-    this.trigger(this.data);
-  },
-
-  onCheckDbLabInstanceUrlCompleted: function (data) {
-    this.data.newDbLabInstance.isChecking = false;
-    this.data.newDbLabInstance.checkingErrorMessage = this.getError(data);
-    this.data.newDbLabInstance.checkingError = !!this.data.newDbLabInstance
-      .checkingErrorMessage;
-    this.data.newDbLabInstance.isCheckProcessed = true;
-
-    if (!this.data.newDbLabInstance.checkingError && data) {
-      this.data.newDbLabInstance.isChecked = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onDestroyDbLabInstanceFailed: function (error) {
-    for (let i in this.data.dbLabInstances.data) {
-      if (this.data.dbLabInstances.data.hasOwnProperty(i)) {
-        this.data.dbLabInstances.data[i].isProcessing = false;
-      }
-    }
-
-    this.data.dbLabInstances.isProcessingItem = false;
-    this.data.dbLabInstances.itemError = true;
-    this.data.dbLabInstances.itemErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onDestroyDbLabInstanceProgressed: function (data) {
-    this.data.dbLabInstances.data[data.instanceId].isProcessing = true;
-    this.data.newDbLabInstance.isProcessingItem = true;
-    this.data.dbLabInstances.itemError = false;
-    this.data.dbLabInstances.itemErrorMessage = null;
-    this.trigger(this.data);
-  },
-
-  onDestroyDbLabInstanceCompleted: function (data) {
-    this.data.dbLabInstances.isProcessingItem = false;
-    this.data.dbLabInstances.itemErrorMessage = this.getError(data.data);
-    this.data.dbLabInstances.itemError = !!this.data.dbLabInstances.itemErrorMessage;
-
-    if (!this.data.dbLabInstances.itemError && data) {
-      this.data.dbLabInstances.data[data.instanceId].isProcessing = false;
-      Actions.getDbLabInstances(this.data.auth.token,
-        this.data.dbLabInstances.orgId, this.data.dbLabInstances.projectId
-      );
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onResetNewDbLabInstance: function () {
-    this.data.newDbLabInstance = {
-      isProcessing: false,
-      isProcessed: false,
-      isChecked: null,
-      isCheckProcessed: false,
-      data: null,
-      error: null
-    };
-  },
-
-  onGetDbLabInstanceStatusFailed: function (data, error) {
-    this.data.dbLabInstanceStatus.isProcessing = false;
-    this.data.dbLabInstanceStatus.isProcessed = true;
-    this.data.dbLabInstanceStatus.error = true;
-    this.data.dbLabInstanceStatus.errorMessage = error.message;
-
-    if (data && data.instanceId) {
-      this.data.dbLabInstanceStatus.instanceId = data.instanceId;
-
-      if (!this.data.dbLabInstanceStatus.instances[data.instanceId]) {
-        this.data.dbLabInstanceStatus.instances[data.instanceId] = {};
-      }
-
-      this.data.dbLabInstanceStatus.instances[data.instanceId]._isError =
-        true;
-      this.data.dbLabInstanceStatus.instances[data.instanceId]._isProcessed =
-        false;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabInstanceStatusProgressed: function (data) {
-    this.data.dbLabInstanceStatus.isProcessing = true;
-    this.data.dbLabInstanceStatus.isProcessed = false;
-    this.data.dbLabInstanceStatus.error = false;
-    this.data.dbLabInstanceStatus.data = {};
-
-    if (data && data.instanceId) {
-      this.data.dbLabInstanceStatus.instanceId = data.instanceId;
-
-      if (!this.data.dbLabInstanceStatus.instances[data.instanceId]) {
-        this.data.dbLabInstanceStatus.instances[data.instanceId] = {};
-      }
-
-      this.data.dbLabInstanceStatus.instances[data.instanceId]._isError =
-        false;
-      this.data.dbLabInstanceStatus.instances[data.instanceId]._isProcessed =
-        false;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabInstanceStatusCompleted: function (data) {
-    this.data.dbLabInstanceStatus.isProcessing = false;
-    this.data.dbLabInstanceStatus.errorMessage = this.getError(data.data);
-    this.data.dbLabInstanceStatus.error = !!this.data.dbLabInstanceStatus
-      .errorMessage;
-
-    if (!this.data.dbLabInstanceStatus.error && data.data && data.instanceId) {
-      this.data.dbLabInstanceStatus.instanceId = data.instanceId;
-
-      if (!this.data.dbLabInstanceStatus.instances[data.instanceId]) {
-        this.data.dbLabInstanceStatus.instances[data.instanceId] = {};
-      }
-
-      this.data.dbLabInstanceStatus.instances[data.instanceId] = data.data;
-
-      if (this.data.dbLabInstances.data &&
-        this.data.dbLabInstances.data[data.instanceId]) {
-        this.data.dbLabInstances.data[data.instanceId].state = data.data.state;
-      }
-
-      this.data.dbLabInstanceStatus.isProcessed = true;
-      this.data.dbLabInstanceStatus.instances[data.instanceId]._isProcessed =
-        true;
-      this.data.dbLabInstanceStatus.instances[data.instanceId]._isError =
-        false;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onDownloadReportJsonFilesProgressed: function () {
-    this.data.report.isDownloading = true;
-    this.trigger(this.data);
-  },
-
-  onDownloadReportJsonFilesCompleted: function () {
-    this.data.report.isDownloading = false;
-    this.trigger(this.data);
-  },
-
-
-  onAddOrgDomainFailed: function (error) {
-    this.data.orgProfile.orgDomains.isProcessing = false;
-    this.data.orgProfile.orgDomains.isProcessed = false;
-    this.data.orgProfile.orgDomains.error = true;
-    this.data.orgProfile.orgDomains.errorMessage = error.message;
-    Actions.showNotification(this.data.orgProfile.orgDomains.errorMessage, 'error');
-    this.trigger(this.data);
-  },
-
-  onAddOrgDomainProgressed: function () {
-    this.data.orgProfile.orgDomains.isProcessing = true;
-    this.data.orgProfile.orgDomains.isProcessed = false;
-    this.trigger(this.data);
-  },
-
-  onAddOrgDomainCompleted: function (data) {
-    this.data.orgProfile.orgDomains.isProcessing = false;
-    this.data.orgProfile.orgDomains.isProcessed = true;
-    this.data.orgProfile.orgDomains.errorMessage = this.getError(data);
-    this.data.orgProfile.orgDomains.error =
-      !!this.data.orgProfile.orgDomains.errorMessage;
-    Actions.showNotification('Domain successfully added. To confirm it, ' +
-      'please contact the Support team.', 'success');
-    Actions.getOrgs(this.data.auth.token, data.orgId);
-  },
-
-
-  onDeleteOrgDomainFailed: function (error) {
-    this.data.orgProfile.orgDomains.isDeleting = false;
-    this.data.orgProfile.orgDomains.isDeleted = false;
-    this.data.orgProfile.orgDomains.error = true;
-    this.data.orgProfile.orgDomains.errorMessage = error.message;
-    Actions.showNotification(this.data.orgProfile.orgDomains.errorMessage, 'error');
-    this.trigger(this.data);
-  },
-
-  onDeleteOrgDomainProgressed: function (data) {
-    this.data.orgProfile.orgDomains.domainId = data.domainId;
-    this.data.orgProfile.orgDomains.isDeleting = true;
-    this.data.orgProfile.orgDomains.isDeleted = false;
-    this.trigger(this.data);
-  },
-
-  onDeleteOrgDomainCompleted: function (data) {
-    this.data.orgProfile.orgDomains.isDeleting = false;
-    this.data.orgProfile.orgDomains.isDeleted = true;
-    this.data.orgProfile.orgDomains.errorMessage = this.getError(data);
-    this.data.orgProfile.orgDomains.error =
-      !!this.data.orgProfile.orgDomains.errorMessage;
-    this.data.orgProfile.orgDomains.domainId = null;
-    Actions.getOrgs(this.data.auth.token, data.orgId);
-    Actions.showNotification('Domain removed.', 'success');
-  },
-
-
-  onSetJoeInstancesProject(orgId, projectId) {
-    this.data.joeInstances.orgId = orgId;
-    this.data.joeInstances.projectId = projectId;
-    Actions.getJoeInstances(this.data.auth.token, orgId, projectId);
-    this.trigger(this.data);
-  },
-
-
-  onGetJoeInstancesFailed: function (error) {
-    this.data.joeInstances.isProcessing = false;
-    this.data.joeInstances.error = true;
-    this.data.joeInstances.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstancesProgressed: function () {
-    this.data.joeInstances.isProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstancesCompleted: function (orgId, projectId, data) {
-    this.data.joeInstances.isProcessing = false;
-    this.data.joeInstances.errorMessage = this.getError(data);
-    this.data.joeInstances.error = !!this.data.joeInstances.errorMessage;
-
-    if (!this.data.joeInstances.error) {
-      let instances = {};
-
-      for (let i in data) {
-        if (data.hasOwnProperty(i)) {
-          instances[data[i].id] = data[i];
-        }
-      }
-
-      this.data.joeInstances.data = instances;
-      this.data.joeInstances.orgId = orgId;
-      this.data.joeInstances.projectId = projectId;
-      this.data.joeInstances.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  getJoeInstance(instanceId) {
-    if (!this.data.joeInstance.instances[instanceId]) {
-      this.data.joeInstance.instances[instanceId] =
-      Object.assign({}, joeInstance);
-    }
-
-    return this.data.joeInstance.instances[instanceId];
-  },
-
-  getJoeInstanceChannelMessages(instanceId, channelId) {
-    const instance = this.getJoeInstance(instanceId);
-    const messages = instance.messages;
-
-    if (!messages[channelId]) {
-      messages[channelId] = {};
-    }
-
-    return messages[channelId];
-  },
-
-
-  getJoeMessageArtifacts(instanceId, channelId, messageId) {
-    const messages = this.getJoeInstanceChannelMessages(instanceId, channelId);
-
-    if (messages.hasOwnProperty(messageId)) {
-      if (!messages[messageId].hasOwnProperty('artifacts')) {
-        messages[messageId].artifacts = {};
-      }
-    } else {
-      return null;
-    }
-
-    return messages[messageId].artifacts;
-  },
-
-
-  onGetJoeInstanceChannelsFailed: function (instanceId, error) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isChannelsProcessing = false;
-    instance.channelsError = true;
-    instance.channelsErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstanceChannelsProgressed: function (instanceId) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isChannelsProcessing = true;
-    instance.channelsErrorMessage = null;
-    instance.channelsError = false;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstanceChannelsCompleted: function (instanceId, data) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isChannelsProcessing = false;
-    instance.channelsErrorMessage = this.getError(data);
-    instance.channelsError = !!instance.channelsErrorMessage;
-
-    if (!instance.channelsError) {
-      instance.channels = {};
-
-      for (let i = data.length - 1; i >= 0; i--) {
-        instance.channels[data[i].channel_id] = {
-          channelId: data[i].channel_id
-        };
-      }
-
-      instance.isChannelsProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onSendJoeInstanceCommandFailed: function (instanceId, error) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isCommandProcessing = false;
-    instance.commandError = true;
-    instance.commandErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onSendJoeInstanceCommandProgressed: function (instanceId) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isCommandProcessing = true;
-    instance.commandErrorMessage = null;
-    instance.commandError = false;
-    this.trigger(this.data);
-  },
-
-  setJoeInstanceMessage(messages, updatedMessage) {
-    const messageId = updatedMessage.id ? updatedMessage.id : updatedMessage.message_id;
-
-    const message = messages[messageId];
-
-    // TODO(Anton): React should process this. Messages update flow refactor needed.
-    const isMessageChanged =
-      !message ||
-      message.status !== updatedMessage.status ||
-      message.delivery_status !== updatedMessage.delivery_status ||
-      message.message !== updatedMessage.message ||
-      message.created_at !== updatedMessage.created_at;
-
-    messages[messageId] = updatedMessage;
-
-    if (isMessageChanged) {
-      const actualMessage = messages[messageId];
-      // Update message only if status changed. New one added or artifacts added.
-      actualMessage.formattedMessage = preformatJoeMessage(actualMessage.message);
-      actualMessage.formattedTime = format.formatTimestamp(actualMessage.created_at);
-    }
-
-    return isMessageChanged;
-  },
-
-  onSendJoeInstanceCommandCompleted: function (instanceId, data) {
-    const instance = this.getJoeInstance(instanceId);
-    let changed = false;
-    instance.isCommandProcessing = false;
-    instance.commandErrorMessage = this.getError(data);
-    instance.commandError = !!instance.commandErrorMessage;
-
-    if (data && data.session_id && data.message_id && data.channel_id) {
-      instance.channels[data.channel_id].sessionId = data.session_id;
-      const messages = this.getJoeInstanceChannelMessages(instanceId, data.channel_id);
-      changed = this.setJoeInstanceMessage(messages, data);
-
-      if (!instance.channels[data.channel_id].wsOpen) {
-        Actions.getJoeInstanceMessages(this.data.auth.token, instanceId,
-          data.channel_id, data.session_id);
-        try {
-          Actions.initJoeWebSocketConnection(
-            instanceId,
-            data.channel_id,
-            'joe-' + data.session_id + '-' + md5(data.channel_id));
-        } catch (e) {
-          instance.wsErrorMessage =
-            'Command send, but cannot start persistent network connection. ' +
-            'Execute any command to try to start the connection again.';
-        }
-      }
-
-      instance.isCommandProcessed = true;
-    }
-
-    if (changed) {
-      this.trigger(this.data);
-    }
-  },
-
-  onJoeWebSocketMessage: function (instanceId, channelId, event) {
-    const messages = this.getJoeInstanceChannelMessages(instanceId, channelId);
-
-    if (event.data) {
-      let data;
-      try {
-        data = JSON.parse(event.data);
-      } catch (e) {
-        console.log('Cannot parse WS message');
-      }
-
-      if (data && data.payload && data.payload.id) {
-        console.log('WebSocket message payload', data);
-        if (this.setJoeInstanceMessage(messages, data.payload)) {
-          this.trigger(this.data);
-        }
-      }
-    }
-  },
-
-  onCloseJoeWebSocketConnection: function (instanceId) {
-    const instance = this.getJoeInstance(instanceId);
-
-    instance.wsClose = true;
-    if (instance.ws) {
-      instance.ws.close();
-    }
-  },
-
-  restoreJoeWebSocketConnection: function (instanceId, channelId, wsChannelId) {
-    const instance = this.getJoeInstance(instanceId);
-
-    if (!instance.wsClose && instance.channels[channelId].wsOpen) {
-      instance.channels[channelId].wsRetryConnectionCount =
-        instance.channels[channelId].wsRetryConnectionCount ?
-          instance.channels[channelId].wsRetryConnectionCount + 1 : 1;
-
-      console.log('Try restore connection', instance.wsRetryConnectionCount ?
-        instance.wsRetryConnectionCount : 1);
-
-      if (instance.channels[channelId].wsRetryConnectionCount >
-          WS_MAX_RETRY_CONNECTION_COUNT) {
-        instance.channels[channelId].wsErrorMessage =
-          'There are issues with a persistent network connection. ' +
-          'Cannot restore connection. ' +
-          'Execute any command to try to restore the connection again.';
-        instance.channels[channelId].wsOpen = false;
-        instance.channels[channelId].wsFailed = true;
-        return;
-      }
-
-      Actions.initJoeWebSocketConnection(instanceId, channelId, wsChannelId);
-    } else {
-      instance.channels[channelId].wsOpen = false;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onInitJoeWebSocketConnection: function (instanceId, channelId, wsChannelId) {
-    const that = this;
-    const instance = this.getJoeInstance(instanceId);
-    let ws = createWebSocket(settings.wsServer,
-      wsChannelId + '/' + this.data.auth.token);
-
-    instance.ws = ws;
-    ws.onopen = function () {
-      console.log('WebSocket connection opened.', wsChannelId);
-      instance.channels[channelId].wsOpen = true;
-      instance.channels[channelId].wsError = null;
-      instance.channels[channelId].wsErrorMessage = null;
-      instance.channels[channelId].wsRetryConnectionCount = null;
-      instance.channels[channelId].wsFailed = null;
-      ws.send('?');
-      that.trigger(that.data);
-    };
-
-    ws.onmessage = function (event) {
-      console.log('WebSocket message payload', event);
-      that.onJoeWebSocketMessage(instanceId, channelId, event);
-    };
-
-    ws.onerror = function (error) {
-      console.log('WebSocket error.', error);
-
-      if (instance.channels[channelId].wsOpen) {
-        that.restoreJoeWebSocketConnection(instanceId, channelId, wsChannelId);
-      } else if (!instance.channels[channelId].wsFailed) {
-        instance.channels[channelId].wsError = error;
-        instance.channels[channelId].wsErrorMessage =
-          'There are issues with a persistent network connection. ' +
-          'Cannot start connection. ' +
-          'Execute any command to try to start the connection again.';
-      }
-
-      that.trigger(that.data);
-    };
-
-    ws.onclose = function (event) {
-      console.log('WebSocket closed.', event);
-      console.log('WebSocket connection closed with code: ' + event.code +
-        ' reason: ' + event.reason);
-
-      if (event.wasClean) {
-        console.log('WebSocket connection closed clear.');
-      } else {
-        console.log('WebSocket connection failed.');
-      }
-
-      that.restoreJoeWebSocketConnection(instanceId, channelId, wsChannelId);
-    };
-  },
-
-
-  onGetJoeInstanceMessagesFailed: function (instanceId, channelId, error) {
-    let instance = this.getJoeInstance(instanceId);
-    instance.isMessagesProcessing = false;
-    instance.commandError = true;
-    instance.commandErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstanceMessagesProgressed: function (instanceId) {
-    this.getJoeInstance(instanceId).isMessagesProcessing = true;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstanceMessagesCompleted: function (instanceId, channelId, data) {
-    let instance = this.getJoeInstance(instanceId);
-    const messages = this.getJoeInstanceChannelMessages(instanceId, channelId);
-    let lastId = instance.clear && instance.clear[channelId] ?
-      instance.clear[channelId] : 0;
-    let changed = false;
-
-    instance.isMessagesProcessing = false;
-    instance.messagesErrorMessage = this.getError(data);
-    instance.messagesError = !!instance.messagesErrorMessage;
-
-    if (data) {
-      for (let i in data) {
-        if (data.hasOwnProperty(i)) {
-          // Update only if message updated in database.
-          if (data[i].id > lastId && (!messages[data[i].id] ||
-            messages[data[i].id].updated_at !== data[i].updated_at)) {
-            changed = this.setJoeInstanceMessage(messages, data[i]) || changed;
-          }
-        }
-      }
-
-      instance.isMessagesProcessed = false;
-    }
-
-    if (changed) {
-      this.trigger(this.data);
-    }
-  },
-
-  onGetJoeMessageArtifactsFailed: function (instanceId, channelId, error) {
-    let instance = this.getJoeInstance(instanceId);
-    instance.isProcessingMessageArtifactsId = false;
-    instance.isProcessingMessageArtifactsError = error;
-    if (instance.isProcessingMessageArtifactsId) {
-      let artifacts = this.getJoeMessageArtifacts(
-        instanceId,
-        channelId,
-        instance.isProcessingMessageArtifactsId);
-      if (!artifacts) {
-        return;
-      }
-      artifacts.isProcessing = false;
-      artifacts.error = true;
-    }
-    this.trigger(this.data);
-  },
-
-  onGetJoeMessageArtifactsProgressed: function (instanceId, channelId, messageId) {
-    let instance = this.getJoeInstance(instanceId);
-    instance.isProcessingMessageArtifactsId = messageId;
-    instance.isProcessingMessageArtifactsError = null;
-    let artifacts = this.getJoeMessageArtifacts(instanceId, channelId, messageId);
-
-    if (artifacts) {
-      artifacts.isProcessing = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onGetJoeMessageArtifactsCompleted: function (instanceId, channelId, messageId, data) {
-    let instance = this.getJoeInstance(instanceId);
-    instance.isProcessingMessageArtifactsErrorMessage =
-      this.getError(data);
-    instance.isProcessingMessageArtifactsError =
-      !!instance.isProcessingMessageArtifactsErrorMessage;
-
-    let artifacts = this.getJoeMessageArtifacts(
-      instanceId,
-      channelId,
-      instance.isProcessingMessageArtifactsId
-    );
-
-    if (data && artifacts) {
-      artifacts['files'] = {};
-
-      for (let i = data.length - 1; i >= 0; i--) {
-        artifacts['files'][data[i].id] = data[i];
-      }
-
-      artifacts.isProcessing = false;
-    }
-
-    instance.isProcessingMessageArtifactsId = null;
-    instance.isMessagesProcessed = false;
-    this.trigger(this.data);
-  },
-
-
-  onResetNewJoeInstance: function () {
-    this.data.newJoeInstance = Object.assign({}, storeItem);
-    this.data.newJoeInstance.isChecked = null;
-    this.data.newJoeInstance.isCheckProcessed = false;
-  },
-
-  onCheckJoeInstanceUrlFailed: function (error) {
-    this.data.newJoeInstance.isChecked = false;
-    this.data.newJoeInstance.isCheckProcessed = true;
-    this.data.newJoeInstance.isChecking = false;
-    this.data.newJoeInstance.checkingError = true;
-    this.data.newJoeInstance.checkingErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onCheckJoeInstanceUrlProgressed: function () {
-    this.data.newJoeInstance.isChecked = false;
-    this.data.newJoeInstance.isCheckProcessed = false;
-    this.data.newJoeInstance.isChecking = true;
-    this.trigger(this.data);
-  },
-
-  onCheckJoeInstanceUrlCompleted: function (data) {
-    this.data.newJoeInstance.isChecking = false;
-    this.data.newJoeInstance.checkingErrorMessage = this.getError(data);
-    this.data.newJoeInstance.checkingError = !!this.data.newJoeInstance
-      .checkingErrorMessage;
-    this.data.newJoeInstance.isCheckProcessed = true;
-
-    if (!this.data.newJoeInstance.checkingError && data) {
-      this.data.newJoeInstance.isChecked = data.verified;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onAddJoeInstanceFailed: function (error) {
-    this.data.newJoeInstance.isUpdating = false;
-    this.data.newJoeInstance.isProcessed = false;
-    this.data.newJoeInstance.updateError = true;
-    this.data.newJoeInstance.updateErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onAddJoeInstanceProgressed: function (data) {
-    this.data.newJoeInstance.updateErrorFields = null;
-    this.data.newJoeInstance.isUpdating = true;
-    this.data.newJoeInstance.data = {};
-
-    if (data && data.email) {
-      this.data.newJoeInstance.data.email = data.email;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onAddJoeInstanceCompleted: function (orgId, data) {
-    this.data.newJoeInstance.isUpdating = false;
-    this.data.newJoeInstance.errorMessage = data && data.details ?
-      data.details : null;
-    this.data.newJoeInstance.error = !!this.data.newJoeInstance.errorMessage;
-
-    if (!this.data.newJoeInstance.error && data && orgId) {
-      this.data.newJoeInstance.data = data;
-      this.data.newJoeInstance.isProcessed = true;
-
-      // Update orgs and projects.
-      Actions.getUserProfile(this.data.auth.token);
-      Actions.getJoeInstances(this.data.auth.token, orgId, data.project_id);
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onDestroyJoeInstanceFailed: function (error) {
-    for (let i in this.data.joeInstances.data) {
-      if (this.data.joeInstances.data.hasOwnProperty(i)) {
-        this.data.joeInstances.data[i].isProcessing = false;
-      }
-    }
-
-    this.data.joeInstances.isProcessingItem = false;
-    this.data.joeInstances.itemError = true;
-    this.data.joeInstances.itemErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onDestroyJoeInstanceProgressed: function (instanceId) {
-    this.data.joeInstances.data[instanceId].isProcessing = true;
-    this.data.newDbLabInstance.isProcessingItem = true;
-    this.data.joeInstances.itemError = false;
-    this.data.joeInstances.itemErrorMessage = null;
-    this.trigger(this.data);
-  },
-
-  onDestroyJoeInstanceCompleted: function (instanceId, data) {
-    this.data.joeInstances.isProcessingItem = false;
-    this.data.joeInstances.itemErrorMessage = this.getError(data);
-    this.data.joeInstances.itemError = !!this.data.joeInstances.itemErrorMessage;
-
-    if (!this.data.joeInstances.itemError && data && instanceId) {
-      this.data.joeInstances.data[instanceId].isProcessing = false;
-      Actions.getJoeInstances(this.data.auth.token,
-        this.data.joeInstances.orgId, this.data.joeInstances.projectId
-      );
-    }
-  },
-
-
-  onGetExternalVisualizationDataFailed: function (error) {
-    this.data.externalVisualization.isProcessing = false;
-    this.data.externalVisualization.isProcessed = false;
-    this.data.externalVisualization.error = error;
-    this.trigger(this.data);
-  },
-
-  onGetExternalVisualizationDataProgressed: function (type, plan, query) {
-    this.data.externalVisualization.isProcessing = true;
-    this.data.externalVisualization.isProcessed = false;
-    this.data.externalVisualization.error = null;
-    this.data.externalVisualization.type = type;
-    this.data.externalVisualization.plan = plan;
-    this.data.externalVisualization.query = query;
-    this.trigger(this.data);
-  },
-
-  onGetExternalVisualizationDataCompleted: function (type, plan, query, url) {
-    this.data.externalVisualization.isProcessing = false;
-    this.data.externalVisualization.isProcessed = true;
-    this.data.externalVisualization.error = null;
-    this.data.externalVisualization.type = type;
-    this.data.externalVisualization.plan = plan;
-    this.data.externalVisualization.query = query;
-    this.data.externalVisualization.url = url;
-    this.trigger(this.data);
-  },
-
-  onCloseExternalVisualization: function () {
-    this.data.externalVisualization.isProcessing = false;
-    this.data.externalVisualization.isProcessed = true;
-    this.data.externalVisualization.error = null;
-    this.data.externalVisualization.type = null;
-    this.data.externalVisualization.plan = null;
-    this.data.externalVisualization.query = null;
-    this.data.externalVisualization.url = null;
-    this.trigger(this.data);
-  },
-
-
-  onDeleteJoeSessionsFailed: function (error) {
-    this.data.sessions.isDeleting = false;
-    this.data.sessions.isDeleted = false;
-    this.data.sessions.deleteError = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onDeleteJoeSessionsProgressed: function () {
-    this.data.sessions.isDeleting = true;
-    this.data.sessions.isDeleted = false;
-    this.data.sessions.deleteError = null;
-    this.trigger(this.data);
-  },
-
-  onDeleteJoeSessionsCompleted: function (ids, data) {
-    this.data.sessions.isDeleting = false;
-    this.data.sessions.isDeleted = (ids.length === data.sessions);
-    this.data.sessions.deleteError = null;
-
-    if (this.data.sessions.isDeleted) {
-      Actions.showNotification(
-        `${data.sessions} session(s), ${data.commands} ` +
-        ` command(s), and ${data.messages} message(s) with artifacts successfully removed.`,
-        'success',
-        5000
-      );
-
-      for (let i in ids) {
-        if (!ids.hasOwnProperty(i)) {
-          continue;
-        }
-
-        for (let j in this.data.sessions.data) {
-          if (this.data.sessions.data[j].id === ids[i]) {
-            delete this.data.sessions.data[j];
-          }
-        }
-      }
-    } else {
-      Actions.getJoeSessions(this.data.auth.token, this.data.sessions.orgId,
-        this.data.sessions.projectId);
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onDeleteJoeCommandsFailed: function (error) {
-    this.data.commands.isDeleting = false;
-    this.data.commands.isDeleted = false;
-    this.data.commands.deleteError = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onDeleteJoeCommandsProgressed: function () {
-    this.data.commands.isDeleting = true;
-    this.data.commands.isDeleted = false;
-    this.data.commands.deleteError = null;
-    this.trigger(this.data);
-  },
-
-  onDeleteJoeCommandsCompleted: function (ids, data) {
-    this.data.commands.isDeleting = false;
-    this.data.commands.isDeleted = (ids.length === data.commands);
-    this.data.commands.deleteError = null;
-
-    if (this.data.commands.isDeleted) {
-      Actions.showNotification(
-        `${data.commands} command(s) removed.`,
-        'success',
-        5000
-      );
-
-      // Delete from list successfully removed items.
-      for (let i in ids) {
-        if (!ids.hasOwnProperty(i)) {
-          continue;
-        }
-
-        for (let j in this.data.commands.data) {
-          if (this.data.commands.data[j].id === ids[i]) {
-            delete this.data.commands.data[j];
-          }
-        }
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onDeleteCheckupReportsFailed: function (error) {
-    this.data.reports.isDeleting = false;
-    this.data.reports.isDeleted = false;
-    this.data.reports.deleteError = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onDeleteCheckupReportsProgressed: function () {
-    this.data.reports.isDeleting = true;
-    this.data.reports.isDeleted = false;
-    this.data.reports.deleteError = null;
-    this.trigger(this.data);
-  },
-
-  onDeleteCheckupReportsCompleted: function (ids, data) {
-    this.data.reports.isDeleting = false;
-    this.data.reports.isDeleted = (ids.length === data.reports);
-    this.data.reports.deleteError = null;
-
-    if (this.data.reports.isDeleted) {
-      Actions.showNotification(
-        `${data.reports} report(s) and ` +
-        `${data.jsons + data.texts} file(s) successfully removed.`,
-        'success',
-        5000
-      );
-
-      for (let i in ids) {
-        if (!ids.hasOwnProperty(i)) {
-          continue;
-        }
-
-        for (let j in this.data.reports.data) {
-          if (this.data.reports.data[j].id === ids[i]) {
-            delete this.data.reports.data[j];
-          }
-        }
-      }
-    } else {
-      Actions.getCheckupReports(this.data.auth.token, this.data.reports.orgId,
-        this.data.reports.projectId);
-    }
-
-    this.trigger(this.data);
-  },
-
-  onJoeCommandFavoriteCompleted: function (data, commandId, favorite) {
-    let errorMessage = this.getError(data);
-    if (errorMessage) {
-      Actions.showNotification(errorMessage, 'error');
-      return;
-    }
-
-    for (let c in this.data.commands.data) {
-      if (this.data.commands.data[c].id === commandId) {
-        this.data.commands.data[c].is_favorite = favorite;
-      }
-    }
-
-    if (favorite) {
-      Actions.showNotification(
-        'Command added to your favorites.',
-        'success',
-        5000
-      );
-    } else {
-      Actions.showNotification(
-        'Command removed from your favorites.',
-        'success',
-        5000
-      );
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onClearJoeInstanceChannelMessages: function (instanceId, channelId) {
-    let instance = this.getJoeInstance(instanceId);
-    const messages = instance.messages;
-    const msgIds = Object.keys(messages[channelId] ? messages[channelId] : {});
-    let maxMessageId = Math.max(...msgIds);
-
-    instance.clear = {};
-    instance.clear[channelId] = maxMessageId;
-    messages[channelId] = {};
-    this.trigger(this.data);
-  },
-
-  onGetSharedUrlDataFailed: function (error) {
-    this.data.sharedUrlData.isProcessing = false;
-    this.data.sharedUrlData.isProcessed = false;
-    this.data.sharedUrlData.error = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onGetSharedUrlDataProgressed: function () {
-    this.data.sharedUrlData.isProcessing = true;
-    this.data.sharedUrlData.isProcessed = false;
-    this.data.sharedUrlData.error = null;
-    this.trigger(this.data);
-  },
-
-  onGetSharedUrlDataCompleted: function (uuid, data) {
-    this.data.sharedUrlData.isProcessing = false;
-    this.data.sharedUrlData.isProcessed = true;
-    this.data.sharedUrlData.errorMessage = this.getError(data);
-    this.data.sharedUrlData.error = !!this.data.sharedUrlData.errorMessage;
-
-    if (!this.data.sharedUrlData.error) {
-      this.data.sharedUrlData.data = data;
-
-      if (data.url && data.url.object_type) {
-        switch (data.url.object_type) {
-        case 'command':
-          this.onGetJoeSessionCommandCompleted(data.url_data);
-          this.data.command.projectId = data.url_data.project_id;
-          break;
-        default:
-          this.data.sharedUrlData.errorMessage = 'Unknown page type.';
-          this.data.sharedUrlData.error = true;
-          break;
-        }
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-  onAddSharedUrlProgressed() {
-    this.data.shareUrl.isAdding = true;
-
-    this.trigger(this.data);
-  },
-
-  onAddSharedUrlCompleted(params, data) {
-    this.data.shareUrl.isAdding = false;
-    this.data.shareUrl.data = data;
-
-    if (!this.data.sharedUrls[params.objectType]) {
-      this.data.sharedUrls[params.objectType] = {};
-    }
-
-    this.data.sharedUrls[params.objectType][params.objectId] = data;
-
-    this.trigger(this.data);
-  },
-
-  onRemoveSharedUrlProgressed() {
-    this.data.shareUrl.isRemoving = true;
-
-    this.trigger(this.data);
-  },
-
-  onRemoveSharedUrlCompleted(orgId, objectType, objectId) {
-    this.data.shareUrl.isRemoving = false;
-
-    if (this.data.sharedUrls[objectType] &&
-      this.data.sharedUrls[objectType][objectId]) {
-      this.data.sharedUrls[objectType][objectId] = {
-        object_type: objectType,
-        object_id: objectId
-      };
-    }
-
-    this.trigger(this.data);
-  },
-
-  onShowShareUrlDialog: function (orgId, objectType, objectId, remark) {
-    this.data.shareUrl.open = true;
-    this.data.shareUrl.objectType = objectType;
-    this.data.shareUrl.objectId = objectId;
-    this.data.shareUrl.uuid = uuidv4();
-    this.data.shareUrl.remark = remark;
-    this.data.shareUrl.orgId = orgId;
-
-    Actions.getSharedUrl(this.data.auth.token, orgId, objectType, objectId);
-
-    this.trigger(this.data);
-  },
-
-  onCloseShareUrlDialog: function (close, save, share) {
-    if (save) {
-      if (this.data.shareUrl.data && this.data.shareUrl.data.uuid && !share) {
-        Actions.removeSharedUrl(this.data.auth.token, this.data.shareUrl.orgId,
-          this.data.shareUrl.objectType, this.data.shareUrl.objectId,
-          this.data.shareUrl.data.uuid);
-        this.data.shareUrl.data = null;
-        this.data.shareUrl.uuid = uuidv4();
-      }
-
-      if ((!this.data.shareUrl.data ||
-        (this.data.shareUrl.data && !this.data.shareUrl.data.uuid))
-         && share) {
-        Actions.addSharedUrl(
-          this.data.auth.token,
-          {
-            orgId: this.data.shareUrl.orgId,
-            url: window.location.href,
-            objectType: this.data.shareUrl.objectType,
-            objectId: this.data.shareUrl.objectId,
-            uuid: this.data.shareUrl.uuid
-          }
-        );
-      }
-    }
-
-    if (close) {
-      this.data.shareUrl.open = false;
-      this.data.shareUrl.objectType = null;
-      this.data.shareUrl.objectId = null;
-      this.data.shareUrl.orgId = null;
-      this.data.shareUrl.data = null;
-      this.data.shareUrl.uuid = uuidv4();
-    }
-
-    this.trigger(this.data);
-  },
-
-  onGetSharedUrlFailed: function (error) {
-    this.data.shareUrl.isProcessing = false;
-    this.data.shareUrl.isProcessed = false;
-    this.data.shareUrl.error = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onGetSharedUrlProgressed: function () {
-    this.data.shareUrl.isProcessing = true;
-    this.data.shareUrl.isProcessed = false;
-    this.data.shareUrl.error = null;
-    this.trigger(this.data);
-  },
-
-  onGetSharedUrlCompleted: function (orgId, objectType, objectId, data) {
-    this.data.shareUrl.isProcessing = false;
-    this.data.shareUrl.isProcessed = true;
-    this.data.shareUrl.errorMessage = this.getError(data);
-    this.data.shareUrl.error = !!this.data.shareUrl.errorMessage;
-
-    if (!this.data.sharedUrls[objectType]) {
-      this.data.sharedUrls[objectType] = {};
-    }
-
-    if (!this.data.shareUrl.error) {
-      this.data.shareUrl.data = data;
-      this.data.sharedUrls[objectType][objectId] = data;
-    } else {
-      this.data.sharedUrls[objectType][objectId] = {
-        object_type: objectType,
-        object_id: objectId
-      };
-    }
-
-    this.trigger(this.data);
-  },
-
-  onSubscribeBillingFailed: function (error) {
-    this.data.shareUrl.isSubscriptionProcessing = false;
-    this.data.shareUrl.isSubscriptionProcessed = false;
-    this.data.shareUrl.error = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onSubscribeBillingProgressed: function () {
-    this.data.billing.isSubscriptionProcessing = true;
-    this.data.billing.isSubscriptionProcessed = false;
-    this.data.billing.error = null;
-    this.trigger(this.data);
-  },
-
-  onSubscribeBillingCompleted: function (orgId, paymentMethodId, data) {
-    this.data.billing.isSubscriptionProcessing = false;
-    this.data.billing.isSubscriptionProcessed = true;
-    this.data.billing.subscriptionErrorMessage = this.getError(data);
-    this.data.billing.subscriptionError = !!this.data.billing.subscriptionErrorMessage;
-
-    if (!this.data.billing.subscriptionError && data.result && data.result !== 'fail') {
-      Actions.getUserProfile(this.data.auth.token);
-      setTimeout(() => {
-        Actions.getUserProfile(this.data.auth.token);
-      }, 5000);
-    } 
-    this.trigger(this.data);
-  },
-
-
-  onGetBillingDataUsageFailed: function (error) {
-    this.data.billing.isProcessing = false;
-    this.data.billing.isProcessed = false;
-    this.data.billing.error = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onGetBillingDataUsageProgressed: function (orgId) {
-    this.data.billing.isProcessing = true;
-    this.data.billing.isProcessed = false;
-    this.data.billing.error = null;
-    this.data.billing.orgId = orgId;
-    this.trigger(this.data);
-  },
-
-  onGetBillingDataUsageCompleted: function (orgId, data) {
-    this.data.billing.isProcessing = false;
-    this.data.billing.isProcessed = true;
-    this.data.billing.errorMessage = this.getError(data);
-    this.data.billing.error = !!this.data.billing.errorMessage;
-    this.data.billing.orgId = orgId;
-
-    if (!this.data.billing.error && data.length === 1) {
-      this.data.billing.data = data[0];
-    }
-
-    this.trigger(this.data);
-  },
-
-  onSetSubscriptionError: function (message) {
-    this.data.billing.subscriptionErrorMessage = message;
-    this.data.billing.subscriptionError = !!this.data.billing.subscriptionErrorMessage;
-
-    this.trigger(this.data);
-  },
-
-
-  onGetJoeInstanceFailed: function (orgId, projectId, instanceId, error) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isProcessing = false;
-    instance.isProcessed = true;
-    instance.channelsError = true;
-    instance.channelsErrorMessage = this.getError(error);
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstanceProgressed: function (orgId, projectId, instanceId) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isProcessing = true;
-    instance.isProcessed = false;
-    instance.errorMessage = null;
-    instance.error = false;
-    this.trigger(this.data);
-  },
-
-  onGetJoeInstanceCompleted: function (orgId, projectId, instanceId, data) {
-    const instance = this.getJoeInstance(instanceId);
-    instance.isProcessing = false;
-    instance.errorMessage = this.getError(data);
-    instance.error = !!instance.errorMessage;
-
-    if (!instance.error) {
-      if (data.length) {
-        instance.data = data[0];
-        instance.isProcessed = true;
-      } else {
-        instance.error = true;
-        instance.errorMessage = 'Specified instance not found or you have no access.';
-        instance.errorCode = 404;
-      }
-    }
-
-    instance.isChannelsProcessed = false;
-
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabInstanceFailed: function (orgId, projectId, instanceId, error) {
-    this.data.dbLabInstance.isProcessing = false;
-    this.data.dbLabInstance.isProcessed = true;
-    this.data.dbLabInstance.error = true;
-    this.data.dbLabInstance.errorMessage = this.getError(error);
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabInstanceProgressed: function () {
-    this.data.dbLabInstance.isProcessing = true;
-    this.data.dbLabInstance.isProcessed = false;
-    this.data.dbLabInstance.error = false;
-    this.data.dbLabInstanceStatus.isProcessing = false;
-    this.data.dbLabInstanceStatus.isProcessed = false;
-    this.data.dbLabInstanceStatus.error = false;
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabInstanceCompleted: function (orgId, projectId, instanceId, data) {
-    this.data.dbLabInstance.isProcessing = false;
-    this.data.dbLabInstance.errorMessage = this.getError(data);
-    this.data.dbLabInstance.error = !!this.data.dbLabInstance.errorMessage;
-
-    if (!this.data.dbLabInstance.error) {
-      if (data.length) {
-        this.data.dbLabInstance.data = data[0];
-        this.data.dbLabInstance.isProcessed = true;
-      } else {
-        this.data.dbLabInstance.error = true;
-        this.data.dbLabInstance.errorMessage =
-          'Specified instance not found or you have no access.';
-        this.data.dbLabInstance.errorCode = 404;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabSessionsFailed: function (error) {
-    this.data.dbLabSessions.isProcessing = false;
-    this.data.dbLabSessions.error = true;
-    this.data.dbLabSessions.errorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionsProgressed: function (params) {
-    if (!params.lastId) {
-      this.data.dbLabSessions.data = [];
-    }
-    this.data.dbLabSessions.isProcessing = true;
-    this.data.dbLabSessions.isProcessed = false;
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionsCompleted: function (params, data, count) {
-    this.data.dbLabSessions.isProcessing = false;
-    this.data.dbLabSessions.errorMessage = this.getError(data);
-    this.data.dbLabSessions.error = !!this.data.dbLabSessions.errorMessage;
-    this.data.dbLabSessions.isProcessed = true;
-
-    if (!this.data.dbLabSessions.error) {
-      if (params.lastId && this.data.dbLabSessions.data && params.limit) {
-        this.data.dbLabSessions.data = [...this.data.dbLabSessions.data, ...data];
-      } else {
-        this.data.dbLabSessions.data = data;
-      }
-
-      if (!isNaN(count) && typeof count !== 'undefined') {
-        this.data.dbLabSessions.isComplete = data.length >= count;
-      }
-
-      this.data.dbLabSessions.isProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabSessionFailed: function (sessionId, error) {
-    this.data.dbLabSession.isProcessing = false;
-    this.data.dbLabSession.isProcessed = true;
-    this.data.dbLabSession.error = true;
-    this.data.dbLabSession.errorMessage = this.getError(error);
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionProgressed: function () {
-    this.data.dbLabSession.isProcessing = true;
-    this.data.dbLabSession.isProcessed = false;
-    this.data.dbLabSession.error = false;
-    this.data.dbLabSession.artifacts = null;
-    this.data.dbLabSession.artifactData = null;
-    this.data.dbLabSession.logs = null;
-
-    this.data.dbLabSession.isLogsProcessing = false;
-    this.data.dbLabSession.isLogsProcessed = false;
-    this.data.dbLabSession.logsError = false;
-    this.data.dbLabSession.logsErrorMessage = false;
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionCompleted: function (sessionId, data) {
-    this.data.dbLabSession.isProcessing = false;
-    this.data.dbLabSession.errorMessage = this.getError(data);
-    this.data.dbLabSession.error = !!this.data.dbLabSession.errorMessage;
-
-    if (!this.data.dbLabSession.error) {
-      if (data.length) {
-        this.data.dbLabSession.data = data[0];
-        this.data.dbLabSession.isProcessed = true;
-      } else {
-        this.data.dbLabSession.error = true;
-        this.data.dbLabSession.errorMessage =
-          'Specified session not found or you have no access.';
-        this.data.dbLabSession.errorCode = 404;
-      }
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabSessionLogsFailed: function (params, error) {
-    this.data.dbLabSession.isLogsProcessing = false;
-    this.data.dbLabSession.isLogsProcessed = true;
-    this.data.dbLabSession.logsError = true;
-    this.data.dbLabSession.logsErrorMessage = this.getError(error);
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionLogsProgressed: function (params) {
-    if (!params.lastId) {
-      this.data.dbLabSessions.data = [];
-    }
-
-    this.data.dbLabSession.isLogsProcessing = true;
-    this.data.dbLabSession.isLogsProcessed = false;
-    this.data.dbLabSession.error = false;
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionLogsCompleted: function (params, data, count) {
-    this.data.dbLabSession.isLogsProcessing = false;
-    this.data.dbLabSession.logsErrorMessage = this.getError(data);
-    this.data.dbLabSession.logsError = !!this.data.dbLabSession.errorMessage;
-
-    if (!this.data.dbLabSession.logsError) {
-      if (params.lastId && this.data.dbLabSession.logs && params.limit) {
-        this.data.dbLabSession.logs = [...this.data.dbLabSession.logs, ...data];
-      } else {
-        this.data.dbLabSession.logs = data;
-      }
-
-      if (!isNaN(count) && typeof count !== 'undefined') {
-        this.data.dbLabSession.isLogsComplete = data.length >= count;
-      }
-
-      this.data.dbLabSession.isLogsProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabSessionArtifactsFailed: function (sessionId, error) {
-    this.data.dbLabSession.isArtifactsProcessing = false;
-    this.data.dbLabSession.isArtifactsProcessed = true;
-    this.data.dbLabSession.artifactsError = true;
-    this.data.dbLabSession.artifactsErrorMessage = this.getError(error);
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionArtifactsProgressed: function (sessionId) {
-    if (!sessionId) {
-      this.data.dbLabSessions.data = [];
-    }
-
-    this.data.dbLabSession.isArtifactsProcessing = true;
-    this.data.dbLabSession.isArtifactsProcessed = false;
-    this.data.dbLabSession.error = false;
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionArtifactsCompleted: function (sessionId, data) {
-    this.data.dbLabSession.isArtifactsProcessing = false;
-    this.data.dbLabSession.artifactsErrorMessage = this.getError(data);
-    this.data.dbLabSession.artifactsError =
-      !!this.data.dbLabSession.artifactsErrorMessage;
-
-    if (!this.data.dbLabSession.artifactsError) {
-      this.data.dbLabSession.artifacts = data;
-
-      this.data.dbLabSession.isArtifactsProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetDbLabSessionArtifactFailed: function (sessionId, artifactType, error) {
-    if (!this.data.dbLabSession.artifactData) {
-      this.data.dbLabSession.artifactData = {};
-    }
-
-    if (!this.data.dbLabSession.artifactData[artifactType]) {
-      this.data.dbLabSession.artifactData[artifactType] = {};
-    }
-
-    this.data.dbLabSession.artifactData[artifactType].isProcessing = false;
-    this.data.dbLabSession.artifactData[artifactType].isProcessed = true;
-    this.data.dbLabSession.artifactData[artifactType].error = true;
-    this.data.dbLabSession.artifactData[artifactType].errorMessage = this.getError(error);
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionArtifactProgressed: function (sessionId, artifactType) {
-    if (!this.data.dbLabSession.artifactData) {
-      this.data.dbLabSession.artifactData = {};
-    }
-
-    if (!this.data.dbLabSession.artifactData[artifactType]) {
-      this.data.dbLabSession.artifactData[artifactType] = {};
-    }
-
-    this.data.dbLabSession.artifactData[artifactType].isProcessing = true;
-    this.data.dbLabSession.artifactData[artifactType].isProcessed = false;
-    this.data.dbLabSession.artifactData[artifactType].error = false;
-    this.data.dbLabSession.artifactData[artifactType].errorMessage =
-      this.getError(this.data.dbLabSession.artifactData[artifactType].error);
-
-    this.trigger(this.data);
-  },
-
-  onGetDbLabSessionArtifactCompleted: function (sessionId, artifactType, data) {
-    if (!this.data.dbLabSession.artifactData) {
-      this.data.dbLabSession.artifactData = {};
-    }
-
-    if (!this.data.dbLabSession.artifactData[artifactType]) {
-      this.data.dbLabSession.artifactData[artifactType] = {};
-    }
-
-    this.data.dbLabSession.artifactData[artifactType].isProcessing = false;
-    this.data.dbLabSession.artifactData[artifactType].isProcessed = true;
-    this.data.dbLabSession.artifactData[artifactType].errorMessage = this.getError(data);
-    this.data.dbLabSession.artifactData[artifactType].error =
-      !!this.data.dbLabSession.artifactData[artifactType].errorMessage;
-
-    if (!this.data.dbLabSession.artifactData[artifactType].error && data.length) {
-      this.data.dbLabSession.artifactData[artifactType].data =
-        JSON.stringify(data[0].artifact_data);
-    }
-
-    console.log(this.data.dbLabSession.artifactData[artifactType]);
-
-    this.trigger(this.data);
-  },
-
-
-  onUpdateOrgUserFailed: function (orgId, userId, role, error) {
-    this.data.orgUsers.isUpdating = false;
-    this.data.orgUsers.updateError = true;
-    this.data.orgUsers.updateErrorMessage = error.message;
-    this.data.orgUsers.updateUserId = null;
-    this.trigger(this.data);
-  },
-
-  onUpdateOrgUserProgressed: function (orgId, userId) {
-    this.data.orgUsers.updateErrorFields = null;
-    this.data.orgUsers.isUpdating = true;
-    this.data.orgUsers.updateUserId = userId;
-
-    this.trigger(this.data);
-  },
-
-  onUpdateOrgUserCompleted: function (orgId, userId, role, data) {
-    this.data.orgUsers.isUpdating = false;
-    this.data.orgUsers.updateErrorMessage = this.getError(data);
-    this.data.orgUsers.updateError = !!this.data.orgUsers.updateErrorMessage;
-
-    if (!this.data.orgUsers.updateError) {
-      Actions.showNotification('User role successfully updated.', 'success');
-    } else {
-      Actions.showNotification(this.data.orgUsers.updateErrorMessage, 'error');
-    }
-
-    this.data.orgUsers.isUpdated = this.data.orgUsers.updateUserId;
-    this.data.orgUsers.updateUserId = null;
-
-    this.trigger(this.data);
-  },
-
-  onDeleteOrgUserFailed: function (orgId, userId, error) {
-    this.data.orgUsers.isDeleting = false;
-    this.data.orgUsers.deleteError = true;
-    this.data.orgUsers.deleteErrorMessage = error.message;
-    this.data.orgUsers.deleteUserId = null;
-    this.trigger(this.data);
-  },
-
-  onDeleteOrgUserProgressed: function (orgId, userId) {
-    this.data.orgUsers.deleteErrorFields = null;
-    this.data.orgUsers.isDeleting = true;
-    this.data.orgUsers.deleteUserId = userId;
-
-    this.trigger(this.data);
-  },
-
-  onDeleteOrgUserCompleted: function (orgId, userId, data) {
-    this.data.orgUsers.isDeleting = false;
-    this.data.orgUsers.deleteErrorMessage = this.getError(data);
-    this.data.orgUsers.deleteError = !!this.data.orgUsers.deleteErrorMessage;
-
-    if (!this.data.orgUsers.deleteError) {
-      Actions.showNotification('User successfully deleted from the organization.', 'success');
-    } else {
-      Actions.showNotification(this.data.orgUsers.deleteErrorMessage, 'error');
-    }
-
-    this.data.orgUsers.isDeleted = this.data.orgUsers.deleteUserId;
-    this.data.orgUsers.deleteUserId = null;
-
-    if (!this.data.orgUsers.deleteError && this.data.userProfile.data.info.id === userId) {
-      window.location = process.env.PUBLIC_URL + '/';
-    }
-
-    this.trigger(this.data);
-  },
-
-
-  onGetAuditLogFailed: function (params, error) {
-    this.data.auditLog.isProcessing = false;
-    this.data.auditLog.isProcessed = true;
-    this.data.auditLog.error = true;
-    this.data.auditLog.errorMessage = this.getError(error);
-
-    this.trigger(this.data);
-  },
-
-  onGetAuditLogProgressed: function (params) {
-    if (!params.lastId) {
-      this.data.auditLog.data = null;
-    }
-
-    this.data.auditLog.isProcessing = true;
-    this.data.auditLog.isProcessed = false;
-    this.data.auditLog.error = false;
-
-    this.trigger(this.data);
-  },
-
-  onGetAuditLogCompleted: function (params, data, count) {
-    this.data.auditLog.isProcessing = false;
-    this.data.auditLog.errorMessage = this.getError(data);
-    this.data.auditLog.error = !!this.data.auditLog.errorMessage;
-
-    this.data.auditLog.orgId = params.orgId;
-    if (!this.data.auditLog.error) {
-      if (params.lastId && this.data.auditLog.data && params.limit) {
-        this.data.auditLog.data = [...this.data.auditLog.data, ...data];
-      } else {
-        this.data.auditLog.data = data;
-      }
-
-      if (!isNaN(count) && typeof count !== 'undefined') {
-        this.data.auditLog.isComplete = data.length >= count;
-      }
-
-      this.data.auditLog.isLogsProcessed = true;
-    }
-
-    this.trigger(this.data);
-  },
-
-  onDownloadDblabSessionLogFailed: function (error) {
-    if (error.details) {
-      Actions.showNotification(error.details, 'error');
-    } else {
-      Actions.showNotification('Unknown error occurred', 'error');
-    }
-    this.data.dbLabSession.isLogDownloading = false;
-    this.trigger(this.data);
-  },
-
-  onDownloadDblabSessionLogProgressed: function () {
-    this.data.dbLabSession.isLogDownloading = true;
-    this.trigger(this.data);
-  },
-
-  onDownloadDblabSessionLogCompleted: function () {
-    this.data.dbLabSession.isLogDownloading = false;
-    this.trigger(this.data);
-  },
-
-
-  onDownloadDblabSessionArtifactFailed: function (error) {
-    if (error.details) {
-      Actions.showNotification(error.details, 'error');
-    } else {
-      Actions.showNotification('Unknown error occurred', 'error');
-    }
-    this.data.dbLabSession.isArtifactDownloading = false;
-    this.data.dbLabSession.downloadingArtifactType = null;
-    this.trigger(this.data);
-  },
-
-  onDownloadDblabSessionArtifactProgressed: function (params) {
-    this.data.dbLabSession.isArtifactDownloading = true;
-    this.data.dbLabSession.downloadingArtifactType = params.artifactType;
-    this.trigger(this.data);
-  },
-
-  onDownloadDblabSessionArtifactCompleted: function () {
-    this.data.dbLabSession.isArtifactDownloading = false;
-    this.data.dbLabSession.downloadingArtifactType = null;
-    this.trigger(this.data);
-  },
-
-
-  onConfirmUserEmailFailed: function (error) {
-    this.data.userProfile.isConfirmProcessing = false;
-    this.data.userProfile.confirmError = true;
-    this.data.userProfile.confirmErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onConfirmUserEmailProgressed: function () {
-    this.data.userProfile.isConfirmProcessing = true;
-    this.data.userProfile.isConfirmProcessed = false;
-    this.trigger(this.data);
-  },
-
-  onConfirmUserEmailCompleted: function (data) {
-    this.data.userProfile.isConfirmProcessing = false;
-    this.data.userProfile.confirmErrorMessage = this.getError(data);
-    this.data.userProfile.confirmError = !!this.data.userProfile.confirmErrorMessage;
-    this.data.userProfile.isConfirmProcessed = true;
-
-    if (Array.isArray(data)) {
-      this.onGetUserProfileCompleted(data);
-
-      Actions.showNotification('Email successfully confirmed', 'success');
-    } else {
-      Actions.showNotification(
-        'Email not confirmed. Check verification code or resend code.',
-        'error',
-        10000
-      );
-    }
-
-    this.trigger(this.data);
-  },
-
-  onSendUserCodeCompleted: function (data) {
-    if (data && data.result === 'ok') {
-      Actions.showNotification('A verification code was sent.', 'success', 10000);
-    } else {
-      Actions.showNotification('Error happened on sending code.', 'error', 10000);
-    }
-  },
-
-  onConfirmTosAgreementFailed: function (error) {
-    this.data.userProfile.isTosAgreementConfirmProcessing = false;
-    this.data.userProfile.tosConfirmError = true;
-    this.data.userProfile.tosConfirmErrorMessage = error.message;
-    this.trigger(this.data);
-  },
-
-  onConfirmTosAgreementProgressed: function () {
-    this.data.userProfile.isTosAgreementConfirmProcessing = true;
-    this.data.userProfile.isTosAgreementConfirmProcessed = false;
-    this.trigger(this.data);
-  },
-
-  onConfirmTosAgreementCompleted: function (data) {
-    this.data.userProfile.isTosAgreementConfirmProcessing = false;
-    this.data.userProfile.tosConfirmErrorMessage = this.getError(data);
-    this.data.userProfile.tosConfirmError = !!this.data.userProfile.tosConfirmErrorMessage;
-    this.data.userProfile.isTosAgreementConfirmProcessed = true;
-
-    if (Array.isArray(data)) {
-      this.onGetUserProfileCompleted(data);
-
-      Actions.showNotification('Agreement successfully confirmed', 'success', 5000);
-    } else {
-      Actions.showNotification('Agreement not confirmed', 'error', 10000);
-    }
-
-    this.trigger(this.data);
-  }
-});
-
-
-export default Store;
diff --git a/ui/packages/platform/src/types/api/entities/bot.ts b/ui/packages/platform/src/types/api/entities/bot.ts
deleted file mode 100644
index 0447ce58..00000000
--- a/ui/packages/platform/src/types/api/entities/bot.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export type DebugMessage = {
-  type: 'debug'
-  message_id: string | null
-  org_id: string
-  thread_id: string
-  content: string
-  created_at: string
-}
-
-export type BotMessage = {
-  id: string
-  created_at: string
-  parent_id: string | null
-  content: string
-  is_ai: boolean
-  is_public: boolean
-  first_name: string | null
-  last_name: string | null
-  display_name: string | null
-  slack_profile: string | null
-  user_id: string
-  org_id: string
-  thread_id: string
-  type: 'message' | undefined
-  ai_model: string
-}
-
-export type BotMessageWithDebugInfo = BotMessage & {
-  debugMessages?: DebugMessage[]
-}
-
-export type AiModel = {
-  name: string;
-  vendor: string;
-  isThirdParty: boolean;
-  freeUseAvailable: boolean;
-};
-
-export type StateMessage = {
-  type: 'state'
-  state: string | null
-  thread_id: string
-}
-
-export type StreamMessage = {
-  type: 'stream'
-  content: string
-  ai_model: string
-  thread_id: string
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/types/api/entities/meta.ts b/ui/packages/platform/src/types/api/entities/meta.ts
deleted file mode 100644
index 9a1f4475..00000000
--- a/ui/packages/platform/src/types/api/entities/meta.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type MetaDto = {
-  buildTimestamp: number
-}
-
-export const formatMetaDto = (dto: MetaDto) => dto
-
-export type Meta = ReturnType<typeof formatMetaDto>
diff --git a/ui/packages/platform/src/utils/aliases.ts b/ui/packages/platform/src/utils/aliases.ts
deleted file mode 100644
index 9e18c41f..00000000
--- a/ui/packages/platform/src/utils/aliases.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export interface ProjectDataType {
-  [data: string]: {
-    id?: number
-    projects: {
-      [project: string]: {
-        id: number
-        name?: string
-      }
-    }
-  }
-}
-
-// Find project alias by project id from userProfile.orgs data.
-export const getProjectAliasById = function (
-  data: ProjectDataType,
-  projectId: number | string,
-) {
-  if (!data || !projectId) {
-    return null
-  }
-
-  for (const org in data) {
-    if (Object.prototype.hasOwnProperty.call(data, org)) {
-      for (const proj in data[org].projects) {
-        if (Object.prototype.hasOwnProperty.call(data[org].projects, proj)) {
-          if (data[org].projects[proj].id === projectId) {
-            return proj
-          }
-        }
-      }
-    }
-  }
-
-  return null
-}
-
-// Find project alias by project id from userProfile.orgs data.
-export const getProjectNameById = function (
-  data: ProjectDataType,
-  projectId: number | string,
-) {
-  if (!data || !projectId) {
-    return null
-  }
-
-  for (const org in data) {
-    if (Object.prototype.hasOwnProperty.call(data, org)) {
-      for (const proj in data[org].projects) {
-        if (Object.prototype.hasOwnProperty.call(data[org].projects, proj)) {
-          if (data[org].projects[proj].id === projectId) {
-            return data[org].projects[proj].name
-          }
-        }
-      }
-    }
-  }
-
-  return null
-}
-
-// Find org alias by org id from userProfile.orgs data.
-export const getOrgAliasById = function (
-  data?: ProjectDataType,
-  orgId?: number,
-) {
-  if (!data || !orgId) {
-    return null
-  }
-
-  for (const org in data) {
-    if (data[org].id === orgId) {
-      return org
-    }
-  }
-
-  return null
-}
-
-// Prepare alias.
-export const formatAlias = function (alias: string) {
-  let newAlias = alias.replace(/[ :/?#[\]@!$&()*+,;=.~"'']/g, '-')
-  newAlias = newAlias.toLowerCase()
-  return newAlias
-}
-
-export const getBasePath = function (
-  props: {
-    org: string
-    project: string
-    orgId: number
-    projectId: number | string
-  },
-  data: ProjectDataType,
-) {
-  let org = props.org ? props.org : null
-  let project = props.project ? props.project : null
-
-  if (!org && props.orgId && data) {
-    org = getOrgAliasById(data, props.orgId)
-  }
-
-  if (!project && props.projectId && data) {
-    project = getProjectAliasById(data, props.projectId)
-  }
-
-  if (org && project) {
-    return '/' + org + '/' + project
-  }
-
-  if (org && !project) {
-    return '/' + org
-  }
-
-  return ''
-}
diff --git a/ui/packages/platform/src/utils/cfggen.ts b/ui/packages/platform/src/utils/cfggen.ts
deleted file mode 100644
index 33f9e3af..00000000
--- a/ui/packages/platform/src/utils/cfggen.ts
+++ /dev/null
@@ -1,332 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import settings from './settings'
-
-export interface DataType {
-  port: number | null
-  pgPort: number | null | string
-  sshPort: number | null | string
-  statementTimeout: number | null | string
-  ssDatabaseName: string
-  pgSocketDir: string
-  psqlBinary: string
-  sshKeysPath: string
-  collectPeriod: number | string
-  hosts: string
-  projectName: string
-  databaseName: string
-  databaseUserName: string
-  apiToken: string
-  password: string
-  connectionType: string
-}
-
-const cfgGen = {
-  requiredParamsFilled: function (data: DataType) {
-    if (
-      data.hosts.trim() === '' ||
-      data.projectName === '' ||
-      data.databaseName === '' ||
-      data.databaseUserName === '' ||
-      data.apiToken === '' ||
-      data.password === '' ||
-      data.connectionType === ''
-    ) {
-      return false
-    }
-    return true
-  },
-
-  uniqueHosts: function (hosts: string) {
-    const hostsArray = hosts.split(/[;,(\s)(\n)(\r)(\t)(\r\n)]/)
-    const newHostsArray = []
-
-    for (const i in hostsArray) {
-      if (hostsArray[i] !== '' && newHostsArray.indexOf(hostsArray[i]) === -1) {
-        newHostsArray.push(hostsArray[i])
-      }
-    }
-
-    return newHostsArray.join(';')
-  },
-
-  generateFromSourcesInstruction: function (data: {
-    connectionType: string
-    hosts: string
-    projectName: string
-    password: string
-    databaseUserName: string
-    collectPeriod: number | string
-    databaseName: string
-    apiToken: string
-  }) {
-    let hostsType = 'CHECKUP_HOSTS'
-
-    if (!this.requiredParamsFilled(data)) {
-      return '# Not enough data'
-    }
-
-    if (data.connectionType === 'ssh') {
-      hostsType = 'SSH_CHECKUP_HOSTS'
-    }
-
-    if (data.connectionType === 'pg') {
-      hostsType = 'PG_CHECKUP_HOSTS'
-    }
-
-    const hosts = this.uniqueHosts(data.hosts).split(';').join(' ')
-    let result = `# Create config file
-cat <<EOF > ${data.projectName}.yml
-${this.generateCheckupConfig(data)}
-EOF
-
-# Start check health of Postgres databases
-`
-
-    if (data.password === 'inputpassword') {
-      result =
-        result +
-        `echo -e "\\n\\nEnter the password for DB user "${data.databaseUserName}": " \\
-&& read -s -p "" DB_PWD \\
-&& PGPASSWORD="$\{DB_PWD}" \\
-`
-    }
-
-    result =
-      result +
-      `CHECKUP_CONFIG_PATH=./${data.projectName}.yml \\
-  CHECKUP_SNAPSHOT_DISTANCE_SECONDS=${data.collectPeriod} \\
-  ${hostsType}="${hosts}" \\
-  ./run_checkup.sh
-`
-
-    return result
-  },
-
-  generateDockerInstruction: function (data: DataType) {
-    let hostsType = 'CHECKUP_HOSTS'
-
-    if (!this.requiredParamsFilled(data)) {
-      return '# Not enough data'
-    }
-
-    if (data.connectionType === 'ssh') {
-      hostsType = 'SSH_CHECKUP_HOSTS'
-    }
-
-    if (data.connectionType === 'pg') {
-      hostsType = 'PG_CHECKUP_HOSTS'
-    }
-
-    const hosts = this.uniqueHosts(data.hosts).split(';').join(' ')
-    let result = `# Create config file
-cat <<EOF > ${data.projectName}.yml
-${this.generateCheckupConfig(data)}
-EOF
-
-`
-
-    if (data.password === 'inputpassword') {
-      result =
-        result +
-        `echo -e "\\n\\nIMPORTANT: First, ensure you did 'docker login' for gitlab.registry.com, then proceed.\\n\\n" \\
-        echo -e "\\n\\nEnter the password for DB user "${data.databaseUserName}": " \\
-&& read -s -p "" DB_PWD \\
-&& PGPASSWORD="$\{DB_PWD}" \\
-&& `
-    } else {
-      result =
-        result +
-        `# Specify Postgres password
-export DB_PWD=****
-
-`
-    }
-
-    result =
-      result +
-      `# Start check health of Postgres databases
-docker run \\
-  -v $(pwd)/${data.projectName}.yml:/${data.projectName}.yml \\
-  -v $(pwd)/artifacts:/checkup/artifacts \\`
-
-    if (data.sshKeysPath !== '') {
-      result =
-        result +
-        `
-  -v ${data.sshKeysPath}:/root/.ssh \\`
-    }
-
-    result =
-      result +
-      `
-  -e CHECKUP_CONFIG_PATH="/${data.projectName}.yml" \\
-  -e ${hostsType}="${hosts}" \\
-  -e CHECKUP_SNAPSHOT_DISTANCE_SECONDS=${data.collectPeriod} \\
-  -e PGPASSWORD="$\{DB_PWD}" \\
-  registry.gitlab.com/postgres-ai/postgres-checkup:1-5-3 bash run_checkup.sh
-`
-
-    return result
-  },
-
-  generateCheckupConfig: function (data: DataType) {
-    let result = `- project: ${data.projectName}
-  dbname: ${data.databaseName}
-  username: ${data.databaseUserName}
-  epoch: 1
-  upload-api-url: ${settings.apiServer}`
-
-    if (data.apiToken && data.apiToken !== '') {
-      result =
-        result +
-        `
-  upload-api-token: ${data.apiToken}`
-    }
-
-    if (data.pgPort && data.pgPort !== 5432) {
-      result =
-        result +
-        `
-  pg-port ${data.pgPort}`
-    }
-
-    if (data.sshPort && data.sshPort !== 22) {
-      result =
-        result +
-        `
-  ssh-port ${data.sshPort}`
-    }
-
-    if (data.statementTimeout && data.statementTimeout !== 30) {
-      result =
-        result +
-        `
-  statement-timeout ${data.statementTimeout}`
-    }
-
-    if (data.ssDatabaseName && data.ssDatabaseName !== '') {
-      result =
-        result +
-        `
-  ss-dbname: ${data.ssDatabaseName}`
-    }
-
-    if (data.pgSocketDir && data.pgSocketDir !== '') {
-      result =
-        result +
-        `
-  pg-socket-dir: ${data.pgSocketDir}`
-    }
-
-    if (data.psqlBinary && data.psqlBinary !== '') {
-      result =
-        result +
-        `
-  psql-binary: ${data.psqlBinary}`
-    }
-
-    return result
-  },
-
-  generateRunCheckupSh: function (data: DataType) {
-    let result = `#!/bin/bash
-#run_checkup.sh
-
-`
-    const hosts = this.uniqueHosts(data.hosts).split(';').join(' ')
-
-    // Collect command
-    let collectCmd = `  ./checkup collect \\
-    --hostname "$\{host}" \\
-    --project "${data.projectName}" \\
-    -U "${data.databaseUserName}" \\
-    -e "1" \\
-    -d ${data.databaseName}`
-
-    if (data.port && data.port !== 5432) {
-      collectCmd =
-        collectCmd +
-        ` \\
-    --port ${data.port}`
-    }
-
-    if (data.statementTimeout && data.statementTimeout !== 30) {
-      collectCmd =
-        collectCmd +
-        ` \\
-    -S ${data.statementTimeout}`
-    }
-
-    if (data.ssDatabaseName && data.ssDatabaseName !== '') {
-      collectCmd =
-        collectCmd +
-        ` \\
-    --ss-dbname ${data.ssDatabaseName}`
-    }
-
-    if (data.pgSocketDir && data.pgSocketDir !== '') {
-      collectCmd =
-        collectCmd +
-        ` \\
-    --pg-socket-dir "${data.pgSocketDir}"`
-    }
-
-    if (data.psqlBinary && data.psqlBinary !== '') {
-      collectCmd =
-        collectCmd +
-        ` \\
-    --psql-binary "${data.psqlBinary}"`
-    }
-
-    result =
-      result +
-      `echo "Collect the first set of snapshots (only for K*** reports)..."
-for host in ${hosts} ; do
-  ${collectCmd} \\
-  --file resources/checks/K000_query_analysis.sh
-done
-
-echo "The first set of snapshots has been created. Wait ${data.collectPeriod} seconds..."
-sleep "${data.collectPeriod}"
-# the distance ^^^ should be large enough to get good data, at least 10 minutes
-
-echo "Collect the second set of snapshots and build reports..."
-for host in ${hosts} ; do
-  ${collectCmd}
-done
-
-`
-
-    result =
-      result +
-      `echo "Generate human readable reports..."
-./checkup process --project ${data.projectName} --html --pdf
-
-`
-
-    if (data.apiToken && data.apiToken !== '') {
-      result =
-        result +
-        `echo "Upload report files to platform..."
-./checkup upload --project ${data.projectName} --upload-api-token "${data.apiToken}" --upload-api-url "${settings.apiServer}"
-`
-    } else {
-      result =
-        result +
-        `echo "Upload report files to platform..."
-./checkup upload --project ${data.projectName} --upload-api-token "%TOKEN%" --upload-api-url "${settings.apiServer}"
-`
-    }
-    /* eslint-enable max-len */
-
-    return result
-  },
-}
-
-export default cfgGen
diff --git a/ui/packages/platform/src/utils/dblabutils.ts b/ui/packages/platform/src/utils/dblabutils.ts
deleted file mode 100644
index 303c18fc..00000000
--- a/ui/packages/platform/src/utils/dblabutils.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-const dblalbutils = {
-  getArtifactDescription(artifactType: string) {
-    const descriptions = {
-      log: 'database logs',
-      pg_stat_statements:
-        'tracking planning and execution statistics of all ' +
-        'SQL statements executed during migration run',
-      pg_stat_kcache:
-        'statistics about real reads and writes done by the filesystem layer',
-      pg_stat_all_indexes: 'statistics about accesses to specific index',
-      pg_stat_all_tables: 'statistics about accesses to specific table',
-      pg_stat_database: 'database-wide statistics',
-      pg_statio_all_indexes: 'statistics about I/O on specific index',
-      pg_statio_all_tables: 'statistics about I/O on specific table',
-      pg_statio_all_sequences: 'statistics about I/O on specific sequence',
-      pg_stat_bgwriter:
-        "statistics about the background writer process's activity",
-      pg_stat_user_functions: 'statistics about executions of each function',
-      pg_stat_slru:
-        'contain one row for each tracked SLRU cache, showing ' +
-        'statistics about access to cached pages',
-      pg_current_wal_lsn: 'calculate number of bytes WAL written',
-      pg_wal_lsn_diff: 'calculate number of bytes WAL written',
-      pg_stat_activity:
-        'dynamic view, no pont compare start and stop snapshots',
-      pg_stat_progress_create_index:
-        'Dynamic view, no pont compare start and stop snapshots',
-      pg_stat_progress_cluster:
-        'dynamic view, no pont compare start and stop snapshots',
-      pg_stat_progress_basebackup:
-        'dynamic view, no pont compare start and stop snapshots',
-      pg_stat_progress_analyze:
-        'dynamic view, no pont compare start and stop snapshots',
-      pg_stat_user_tables:
-        'statistics about accesses to each non-system table ' +
-        'in the current database',
-    }
-
-    return descriptions[artifactType as keyof typeof descriptions]
-  },
-}
-
-export default dblalbutils
diff --git a/ui/packages/platform/src/utils/format.ts b/ui/packages/platform/src/utils/format.ts
deleted file mode 100644
index c49de470..00000000
--- a/ui/packages/platform/src/utils/format.ts
+++ /dev/null
@@ -1,291 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import moment from 'moment'
-import { pluralize } from "./time";
-
-const Format = {
-  formatSeconds: function (seconds: number, decimal: number, separator = ' ') {
-    const result = []
-    const hours = Math.floor(seconds / 3600)
-    const minutes = Math.floor((seconds - hours * 3600) / 60)
-    const secs = seconds - hours * 3600 - minutes * 60
-
-    if (hours) {
-      result.push(hours + separator + 'h')
-    }
-    if (minutes) {
-      result.push(minutes + separator + 'm')
-    }
-    if (secs) {
-      result.push(secs.toFixed(decimal) + separator + 's')
-    }
-
-    return result.join(', ')
-  },
-
-  formatBytes: function (bytes: number, decimals: number, dec: boolean) {
-    const k = 1024
-    const dm = decimals || 2
-    const sizes = [
-      'bytes',
-      'KiB',
-      'MiB',
-      'GiB',
-      'TiB',
-      'PiB',
-      'EiB',
-      'ZiB',
-      'YiB',
-    ]
-
-    if (isNaN(bytes)) {
-      return ''
-    }
-
-    if (bytes === 0) {
-      return '0 Bytes'
-    }
-
-    let i = Math.floor(Math.log(bytes) / Math.log(k))
-    let value = parseFloat((bytes / Math.pow(k, i)).toFixed(dm))
-
-    if (value < 10 && !dec) {
-      value = value * k
-      i--
-    }
-
-    return value + ' ' + sizes[i]
-  },
-
-  formatGiB: function (bytes: number, decimals: number) {
-    const GiB = 1024 * 1024 * 1024
-    const dm = decimals || 3
-
-    if (isNaN(bytes)) {
-      return ''
-    }
-
-    if (bytes === 0) {
-      return '0 Bytes'
-    }
-
-    if (bytes < 10 * 1024 * 1024 * 1024) {
-      return this.formatBytes(bytes, decimals, true)
-    }
-
-    const num = parseFloat((bytes / GiB).toString()).toFixed(dm)
-
-    return num + ' GiB'
-  },
-
-  formatNumber: function (num: number, decimals: number) {
-    const k = 1000
-    const dm = decimals || 2
-    const sizes = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
-
-    if (num === 0) {
-      return '0'
-    }
-
-    if (k > num) {
-      return num
-    }
-
-    let i = Math.floor(Math.log(num) / Math.log(k))
-    let value = parseFloat((num / Math.pow(k, i)).toFixed(dm))
-
-    if (value < 10) {
-      value = value * k
-      i--
-    }
-
-    return Math.round(value) + ' ' + sizes[i]
-  },
-
-  formatStatus: function (status: string) {
-    const statusText = status.split('_').join(' ')
-
-    return statusText[0].toUpperCase() + statusText.substring(1)
-  },
-
-  formatTimestampUtc: function (timestamp: moment.MomentInput) {
-    if (!timestamp) {
-      return null
-    }
-
-    return moment(timestamp).utc().format('YYYY-MM-DD HH:mm:ss UTC')
-  },
-
-  formatTimestamp: function (timestamp: moment.MomentInput) {
-    if (!timestamp) {
-      return null
-    }
-
-    return moment(timestamp).format('YYYY-MM-DD HH:mm:ss')
-  },
-
-  formatDate: function (timestamp: moment.MomentInput) {
-    if (!timestamp) {
-      return null
-    }
-
-    return moment(timestamp).format('YYYY-MM-DD')
-  },
-
-  formatUnixDate: function (timestamp: number) {
-    if (!timestamp) {
-      return null
-    }
-    return moment.unix(timestamp).format('YYYY-MM-DD')
-  },
-
-  formatSql: function (sql: string) {
-    let query = sql
-    const keywords = [
-      'ADD CONSTRAINT',
-      'ALTER COLUMN',
-      'ALTER TABLE',
-      'ALTER',
-      'BACKUP DATABASE',
-      'BETWEEN',
-      'CASE',
-      'CHECK',
-      'COLUMN',
-      'CONSTRAINT',
-      'CREATE DATABASE',
-      'CREATE INDEX',
-      'CREATE OR REPLACE VIEW',
-      'CREATE TABLE',
-      'CREATE PROCEDURE',
-      'CREATE UNIQUE INDEX',
-      'CREATE VIEW',
-      'CREATE',
-      'DATABASE',
-      'DEFAULT',
-      'DELETE',
-      'DESC',
-      'DISTINCT',
-      'DROP COLUMN',
-      'DROP CONSTRAINT',
-      'DROP DATABASE',
-      'DROP DEFAULT',
-      'DROP INDEX',
-      'DROP TABLE',
-      'DROP VIEW',
-      'DROP',
-      'EXEC',
-      'EXISTS',
-      'FOREIGN KEY',
-      'FROM',
-      'FULL OUTER JOIN',
-      'GROUP BY',
-      'HAVING',
-      'INDEX',
-      'INNER JOIN',
-      'INSERT INTO',
-      'INSERT INTO SELECT',
-      'IN',
-      'IS NULL',
-      'IS NOT NULL',
-      'JOIN',
-      'LEFT JOIN',
-      'LIKE',
-      'LIMIT',
-      'ORDER BY',
-      'OUTER JOIN',
-      'PRIMARY KEY',
-      'PROCEDURE',
-      'RIGHT JOIN',
-      'ROWNUM',
-      'SELECT',
-      'SELECT DISTINCT',
-      'SELECT INTO',
-      'SELECT TOP',
-      'OFFSET',
-      'TABLE',
-      'TRUNCATE TABLE',
-      'UNION ALL',
-      'UNIQUE',
-      'UPDATE',
-      'VALUES',
-      'VIEW',
-      'WHERE',
-      'UNION',
-      'WITH',
-      'NOT',
-      'TOP',
-      'OR',
-      'ALL',
-      'AND',
-      'ANY',
-      'ADD',
-      'AS',
-      'ASC',
-      'SET',
-      'ON',
-    ]
-
-    for (let i = 0; i < keywords.length; i++) {
-      const regex = new RegExp('\\b' + keywords[i] + '\\b', 'gi')
-
-      query = (query as string).replace(regex, '<b>' + keywords[i] + '</b>')
-    }
-
-    return query
-  },
-
-  quoteStr: function (text: boolean | string | string[]) {
-    if (typeof text !== 'boolean' && text.indexOf(' ') !== -1) {
-      return '"' + text + '"'
-    }
-
-    return text
-  },
-
-  formatDbLabSessionCheck: function (check: unknown) {
-    switch (check) {
-      case 'session-duration-acceptable':
-      case 'session_duration_acceptable':
-        return 'Session duration is within allowed interval'
-      case 'no-long-dangerous-locks':
-      case 'no_long_dangerous_locks':
-        return 'Dangerous locks are not observed during the session'
-      default:
-        return check
-    }
-  },
-
-  limitStr: function (str: string, limit: number) {
-    return str.length > limit ? str.substr(0, limit).concat('…') : str
-  },
-
-  timeAgo: function (date: string | Date): string | null {
-    if (!date) return null
-
-
-    const now = new Date();
-    const past = new Date(date);
-    const diff = Math.abs(now.getTime() - past.getTime());
-    const seconds = Math.floor(diff / 1000);
-    const minutes = Math.floor(seconds / 60);
-    const hours = Math.floor(minutes / 60);
-    const days = Math.floor(hours / 24);
-
-    if (seconds < 60) {
-      return `${seconds} ${pluralize('second', 'seconds')(seconds)} ago`;
-    } else if (minutes < 60) {
-      return `${minutes} ${pluralize('minute', 'minutes')(minutes)} ago`;
-    } else if (hours < 24) {
-      return `${hours} ${pluralize('hour', 'hours')(hours)} ago`;
-    } else {
-      return `${days} ${pluralize('day', 'days')(seconds)} ago`;
-    }
-  }
-}
-
-export default Format
diff --git a/ui/packages/platform/src/utils/permissions.ts b/ui/packages/platform/src/utils/permissions.ts
deleted file mode 100644
index e3c32123..00000000
--- a/ui/packages/platform/src/utils/permissions.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-/* eslint-disable import/no-anonymous-default-export */
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import { OrgPermissions } from '@postgres.ai/platform/src/components/types'
-import { snakeToCamel } from './utils'
-
-const adminRoleId = 3
-
-export default {
-  userIsOwner: function (
-    userId: number,
-    orgId: number,
-    data: {
-      orgs?: {
-        [org: string]: { owner_user_id: number; id: number }
-      }
-    },
-  ) {
-    for (const i in data.orgs) {
-      if (
-        Object.prototype.hasOwnProperty.call(data.orgs, i) &&
-        data.orgs[i].id === orgId &&
-        data.orgs[i].owner_user_id === userId
-      ) {
-        return true
-      }
-    }
-
-    return false
-  },
-
-  isAdmin: function (orgData: { role: { id: number } }) {
-    return orgData && orgData.role && orgData.role.id === adminRoleId
-  },
-
-  /*
-  Permissions defined on the server and converted to camel case from snake case programmatically.
-
-  Available permissions listed for convenience (may be outdated):
-    Guest:
-    - settings_organization_list
-    - settings_token_create_personal
-    - settings_token_list_owned
-    - settings_token_revoke_owned
-    - settings_profile_view
-    - settings_projects_view
-    - dblab_instance_list
-    - dblab_instance_view
-    - dblab_clone_create
-    - dblab_clone_status
-    - dblab_clone_update
-    - dblab_clone_reset
-    - dblab_clone_destroy
-    - dblab_snapshot_list
-    - joe_assistant_post_command
-    - joe_history_view
-    - checkup_report_view
-
-    User:
-    - Guest
-    - settings_member_list
-
-    Owner: (TODO: Rename to Admin)
-    - User
-    - settings_organization_create
-    - settings_organization_update
-    - settings_token_create_impersonal
-    - settings_token_list_any
-    - settings_token_revoke_any
-    - settings_member_add
-    - settings_member_delete
-    - dblab_instance_create
-    - dblab_instance_delete
-    - joe_instance_create
-    - joe_instance_delete
-    - joe_assistant_create_session
-    - joe_assistant_post_message
-    - joe_assistant_post_artifact
-    - joe_history_delete
-    - checkup_report_configure
-    - checkup_report_delete
-  */
-  getPermissions: function (orgData: { role: { permissions: string[] } }) {
-    const permissions: OrgPermissions = {}
-
-    if (!orgData || !orgData.role || !orgData.role.permissions) {
-      return permissions
-    }
-
-    orgData.role.permissions.forEach((p: string) => {
-      const pCamel = snakeToCamel(p)
-      if (!pCamel) {
-        return
-      }
-
-      permissions[pCamel] = true
-    })
-
-    return permissions
-  },
-}
diff --git a/ui/packages/platform/src/utils/settings.ts b/ui/packages/platform/src/utils/settings.ts
deleted file mode 100644
index 77d6e2b1..00000000
--- a/ui/packages/platform/src/utils/settings.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-const API_SERVER = process.env.REACT_APP_API_SERVER;
-const WS_SERVER = process.env.REACT_APP_WS_SERVER;
-const SIGNIN_URL = process.env.REACT_APP_SIGNIN_URL;
-const AUTH_URL = process.env.REACT_APP_AUTH_URL;
-const ROOT_URL = process.env.REACT_APP_ROOT_URL;
-const EXPLAIN_DEPESZ_SERVER = process.env.REACT_APP_EXPLAIN_DEPESZ_SERVER;
-const EXPLAIN_PEV2_SERVER = process.env.REACT_APP_EXPLAIN_PEV2_SERVER;
-const STRIPE_API_KEY = process.env.REACT_APP_STRIPE_API_KEY
-
-const settings = {
-  server: window.location.protocol + '//' + window.location.host,
-  apiServer: API_SERVER,
-  wsServer: WS_SERVER,
-  signinUrl: SIGNIN_URL,
-  authUrl: AUTH_URL,
-  rootUrl: ROOT_URL,
-  explainDepeszServer: EXPLAIN_DEPESZ_SERVER,
-  explainPev2Server: EXPLAIN_PEV2_SERVER,
-  stripeApiKey: STRIPE_API_KEY,
-  demoOrgAlias: 'demo',
-  init: function (cb: () => void) {
-    this.server = window.location.protocol + '//' + window.location.host;
-    this.apiServer = API_SERVER;
-    this.wsServer = WS_SERVER;
-    this.signinUrl = SIGNIN_URL;
-    this.authUrl = AUTH_URL;
-    this.explainDepeszServer = EXPLAIN_DEPESZ_SERVER;
-    this.stripeApiKey = STRIPE_API_KEY;
-    if (cb) {
-      cb();
-    }
-  }
-};
-
-export default settings;
diff --git a/ui/packages/platform/src/utils/time.ts b/ui/packages/platform/src/utils/time.ts
deleted file mode 100644
index 5417b995..00000000
--- a/ui/packages/platform/src/utils/time.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-export const pluralize = (single: string, multiple: string) => (val: number) =>
-  val === 1 ? single : multiple
-
-const MS_IN_SECOND = 1000
-const MS_IN_MINUTE = MS_IN_SECOND * 60
-const MS_IN_HOUR = MS_IN_MINUTE * 60
-const MS_IN_DAY = MS_IN_HOUR * 24
-
-const INTERVALS = [
-  { duration: MS_IN_DAY, getName: pluralize('day', 'days') },
-  { duration: MS_IN_HOUR, getName: pluralize('hour', 'hours') },
-  { duration: MS_IN_MINUTE, getName: pluralize('minute', 'minutes') },
-  { duration: MS_IN_SECOND, getName: pluralize('second', 'seconds') },
-]
-
-const splitDuration = (durationMS: number) => {
-  let rest = durationMS
-
-  return INTERVALS.map((interval) => {
-    const value = Math.floor(rest / interval.duration)
-    rest = rest % interval.duration
-    return {
-      value,
-      name: interval.getName(value),
-    }
-  })
-}
-
-export const formatDuration = (durationMS: number) => {
-  const parts = splitDuration(durationMS)
-
-  let result = ''
-
-  parts.forEach((part, i) => {
-    const isLast = i === parts.length - 1
-
-    const shouldDisplay = part.value || (!result.length && isLast)
-    if (shouldDisplay) result += ` ${part.value} ${part.name}`
-  })
-
-  return result
-}
diff --git a/ui/packages/platform/src/utils/urls.ts b/ui/packages/platform/src/utils/urls.ts
deleted file mode 100644
index 0527092f..00000000
--- a/ui/packages/platform/src/utils/urls.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-/* eslint-disable import/no-anonymous-default-export */
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-import {
-  ProjectDataType,
-  getOrgAliasById,
-  getProjectAliasById,
-} from './aliases'
-
-export interface PropsType {
-  org?: string | number | null
-  orgId?: number | null
-  project?: string | null
-  projectId?: string | number | null
-}
-
-export default {
-  isRequestedPath: function (path: string) {
-    return window.location.pathname.startsWith('/' + path)
-  },
-
-  getRequestParam: function (paramName: string) {
-    const params = window.location.search.substring(1).split('&')
-
-    for (let i = 0; i < params.length; i++) {
-      const pair = params[i].split('=')
-      if (pair[0] === paramName) {
-        return pair[1]
-      }
-    }
-
-    return false
-  },
-
-  getBasePath: function (props: PropsType, data: ProjectDataType) {
-    let org = props.org ? props.org : null
-    let project = props.project ? props.project : null
-
-    if (!org && props.orgId && data) {
-      org = getOrgAliasById(data, props.orgId)
-    }
-
-    if (!project && props.projectId && data) {
-      project = getProjectAliasById(data, props.projectId)
-    }
-
-    if (org && project) {
-      return '/' + org + '/' + project
-    }
-
-    if (org && !project) {
-      return '/' + org
-    }
-
-    return ''
-  },
-
-  linkDbLabInstances: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/instances'
-  },
-
-  linkClusters: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/pg'
-  },
-
-  linkDbLabInstance: function (props: PropsType, instanceId: string) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/instances/' + instanceId
-  },
-
-  linkDbLabInstanceAdd: function (props: PropsType, creationType?: string) {
-    const basePath = this.getBasePath(props)
-
-    return (
-      basePath + '/instances' + (creationType ? '/' + creationType : '')
-    )
-  },
-
-  linkClusterInstanceAdd: function (props: PropsType, creationType?: string) {
-    const basePath = this.getBasePath(props)
-
-    return (
-      basePath + '/pg' + (creationType ? '/' + creationType : '')
-    )
-  },
-
-  linkDbLabClone: function (
-    props: PropsType,
-    instanceId: string,
-    cloneId: string | number | boolean,
-  ) {
-    const basePath = this.getBasePath(props)
-
-    return (
-      basePath +
-      '/instances/' +
-      instanceId +
-      '/clones/' +
-      encodeURIComponent(cloneId)
-    )
-  },
-
-  linkDbLabCloneAdd: function (props: PropsType, instanceId: string) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/instances/' + instanceId + '/clones/add'
-  },
-
-  linkDbLabInstanceEditProject: function (props: PropsType, instanceId: string) {
-    const basePath = this.getBasePath(props)
-
-    return `${basePath}/instances/edit/${instanceId}`
-  },
-
-
-  linkReport: function (props: PropsType, reportId: string, type?: string) {
-    const basePath = this.getBasePath(props)
-    let url = basePath + '/reports/' + reportId
-
-    if (type) {
-      url = url + '/' + type
-    }
-
-    return url
-  },
-
-  linkReports: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-    return basePath + '/reports'
-  },
-
-  linkReportFile: function (
-    props: PropsType,
-    reportId: string,
-    fileId: number,
-    type: string,
-  ) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/reports/' + reportId + '/files/' + fileId + '/' + type
-  },
-
-  linkCheckupAgentAdd: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-    return basePath + '/checkup-config'
-  },
-
-  linkJoeInstances: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/joe-instances'
-  },
-
-  linkJoeInstance: function (props: PropsType, instanceId: string | number) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/joe-instances/' + instanceId
-  },
-
-  linkJoeInstanceAdd: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/joe-instances/add'
-  },
-
-  linkBilling: function (props: PropsType) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/billing'
-  },
-
-  linkReportQuery: function (
-    props: PropsType,
-    reportId: string,
-    fileName: string,
-  ) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/reports/' + reportId + '/files/' + fileName + '/sql?raw'
-  },
-
-  linkReportQueryFull: function (
-    props: PropsType,
-    reportId: string,
-    fileName: string,
-  ) {
-    return (
-      process.env.PUBLIC_URL + this.linkReportQuery(props, reportId, fileName)
-    )
-  },
-
-  linkAccessTokens: function (props: { org: string }) {
-    const org = props.org ? props.org : null
-
-    return '/' + org + '/tokens'
-  },
-
-  isSharedUrl() {
-    return window.location.href.indexOf('/shared/') !== -1
-  },
-
-  linkShared(uuid: string | null) {
-    return (
-      window.location.protocol +
-      '//' +
-      window.location.host +
-      process.env.PUBLIC_URL +
-      '/shared/' +
-      uuid
-    )
-  },
-
-  linkBotChat: function (props: PropsType, msgId: string) {
-    const basePath = this.getBasePath(props)
-
-    return basePath + '/assistant/' + msgId
-  },
-}
diff --git a/ui/packages/platform/src/utils/utils.ts b/ui/packages/platform/src/utils/utils.ts
deleted file mode 100644
index 8707bef6..00000000
--- a/ui/packages/platform/src/utils/utils.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const generateToken = function () {
-  const a =
-    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'.split('')
-  const b = []
-
-  for (let i = 0; i < 32; i++) {
-    const j = (Math.random() * (a.length - 1)).toFixed(0)
-    b[i] = a[j as keyof typeof a]
-  }
-
-  return b.join('')
-}
-
-export const isHttps = function (url: string | string[]) {
-  return url && url.length > 5 && url.indexOf('https') === 0
-}
-
-export const snakeToCamel = (str: string) =>
-  str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase())
-
-export const validateDLEName = (name: string) => {
-  return (
-    name.length > 0 &&
-    !name.match(/^([a-z](?:[-a-z0-9]{0,61}[a-z0-9])?|[1-9][0-9]{0,19})$/)
-  )
-}
-
-export const isMobileDevice = (): boolean => {
-  let hasTouchScreen = false;
-
-  // Check for modern touch screen devices using maxTouchPoints
-  if ("maxTouchPoints" in navigator) {
-    hasTouchScreen = navigator.maxTouchPoints > 0;
-  }
-  // Check for older versions of IE with msMaxTouchPoints
-  else if ("msMaxTouchPoints" in navigator) {
-    hasTouchScreen = (navigator as unknown as { msMaxTouchPoints: number }).msMaxTouchPoints > 0;
-  }
-  // Use matchMedia to check for coarse pointer devices
-  else {
-    const mQ = window.matchMedia("(pointer:coarse)");
-    if (mQ && mQ.media === "(pointer:coarse)") {
-      hasTouchScreen = mQ.matches;
-    }
-    // Check for the presence of the orientation property as a fallback (deprecated in modern browsers)
-    else if ('orientation' in window) {
-      hasTouchScreen = true;
-    }
-    // Last resort: fallback with user agent sniffing
-    else {
-      const UA = navigator.userAgent;
-      hasTouchScreen = (
-        /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
-        /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
-      );
-    }
-  }
-
-  // Check for mobile screen width, 1366 because of iPad Pro in Landscape mode
-  // If this is not necessary, may reduce value to 1024 or 768
-  const isMobileScreen = window.innerWidth <= 1366;
-
-  return hasTouchScreen && isMobileScreen;
-}
\ No newline at end of file
diff --git a/ui/packages/platform/src/utils/webSockets.ts b/ui/packages/platform/src/utils/webSockets.ts
deleted file mode 100644
index bbfcbbcd..00000000
--- a/ui/packages/platform/src/utils/webSockets.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*--------------------------------------------------------------------------
- * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
- * All Rights Reserved. Proprietary and confidential.
- * Unauthorized copying of this file, via any medium is strictly prohibited
- *--------------------------------------------------------------------------
- */
-
-export const createWebSocket = function (url: string, path: string) {
-  // Here expected that url started with ws://
-  const wsUri = url + '/' + path
-
-  return new WebSocket(wsUri)
-}
diff --git a/ui/packages/platform/tsconfig.json b/ui/packages/platform/tsconfig.json
deleted file mode 100644
index 685b60da..00000000
--- a/ui/packages/platform/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "extends": "@tsconfig/recommended/tsconfig.json",
-  "compilerOptions": {
-    "target": "es5",
-    "lib": ["dom", "dom.iterable", "esnext"],
-    "allowJs": true,
-    "skipLibCheck": true,
-    "allowSyntheticDefaultImports": true,
-    "module": "esnext",
-    "esModuleInterop": true,
-    "moduleResolution": "node",
-    "resolveJsonModule": true,
-    "isolatedModules": true,
-    "noEmit": true,
-    "jsx": "react-jsx",
-    "baseUrl": "src",
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitAny": true,
-    "noImplicitThis": false,
-    "typeRoots": ["./node_modules/@types"],
-    "strictPropertyInitialization": false,
-    "downlevelIteration": true,
-  },
-  "include": ["src", "../shared"],
-  "exclude": ["node_modules", "**/node_modules/*"]
-}
diff --git a/ui/packages/shared/.gitlab-ci.yml b/ui/packages/shared/.gitlab-ci.yml
new file mode 100644
index 00000000..15baf4c9
--- /dev/null
+++ b/ui/packages/shared/.gitlab-ci.yml
@@ -0,0 +1,77 @@
+.only_ui_feature: &only_ui_feature
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+.only_ui_tag_release: &only_ui_tag_release
+  rules:
+    - if: $CI_COMMIT_TAG =~ /^v[a-zA-Z0-9_.-]*/
+
+.shared_base: &shared_base
+  image:
+    name: node:lts-alpine
+    pull_policy: if-not-present
+  allow_failure: true
+  cache:
+    key: "$CI_COMMIT_REF_SLUG"
+    paths:
+      - .pnpm-store
+  before_script:
+    - apk add --no-cache rsync jq
+    - corepack enable
+    - corepack prepare pnpm@8.9.2 --activate
+    - pnpm config set store-dir .pnpm-store
+    - pnpm --dir ui/packages/shared install --frozen-lockfile
+
+publish-shared-preview:
+  <<: [*shared_base, *only_ui_feature]
+  stage: build
+  script:
+    - cd ui/packages/shared
+    - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
+
+    # Get base version from package.json (strip any suffix)
+    - BASE_VERSION=$(jq -r .version package.json)
+    - BASE_VERSION=${BASE_VERSION%%-*}
+    - export PREID="pr-${CI_MERGE_REQUEST_IID:-preview}"
+
+    # Get all existing versions
+    - EXISTING_TAGS=$(npm view @postgres.ai/shared versions --json | jq -r '.[]' || echo "")
+
+    # Find next available version suffix
+    - VERSION=""
+    - for i in $(seq 0 20); do
+        CANDIDATE="${BASE_VERSION}-${PREID}";
+        [ "$i" -gt 0 ] && CANDIDATE="${CANDIDATE}.${i}";
+        if ! echo "$EXISTING_TAGS" | grep -qx "$CANDIDATE"; then
+          VERSION="$CANDIDATE";
+          break;
+        fi;
+      done
+    - if [ -z "$VERSION" ]; then echo "❌ Failed to determine unique preview version"; exit 1; fi
+    - echo "Publishing version $VERSION"
+    - npm version "$VERSION" --no-git-tag-version
+
+    # Build and pack
+    - pnpm run pack
+
+    # Publish .tgz archive
+    - TARBALL=$(ls postgres.ai-shared-*.tgz)
+    - pnpm publish "$TARBALL" --no-git-checks --tag "$PREID"
+
+publish-shared-release:
+  <<: [*shared_base, *only_ui_tag_release]
+  stage: build
+  script:
+    - cd ui/packages/shared
+    - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
+
+    # Extract version from tag (remove leading "v")
+    - export VERSION=${CI_COMMIT_TAG#"v"}
+
+    # Build and pack
+    - npm version "$VERSION" --no-git-tag-version
+    - pnpm run pack
+
+    # Publish
+    - TARBALL=$(ls postgres.ai-shared-*.tgz)
+    - pnpm publish "$TARBALL" --no-git-checks
\ No newline at end of file
diff --git a/ui/packages/shared/components/DestroyCloneModal/index.tsx b/ui/packages/shared/components/DestroyCloneModal/index.tsx
index ecfff306..cfe014b6 100644
--- a/ui/packages/shared/components/DestroyCloneModal/index.tsx
+++ b/ui/packages/shared/components/DestroyCloneModal/index.tsx
@@ -28,16 +28,12 @@ export const DestroyCloneModal = (props: Props) => {
   }
 
   return (
-    <Modal
-      title={`Destroy clone ${cloneId}`}
-      onClose={onClose}
-      isOpen={isOpen}
-    >
+    <Modal title={`Delete clone ${cloneId}`} onClose={onClose} isOpen={isOpen}>
       <Text>
-        Are you sure you want to destroy clone{' '}
-        <ImportantText>{cloneId}</ImportantText>?
+        Are you sure you want to delete clone{' '}
+        <ImportantText>{cloneId}</ImportantText>? This action cannot be undone.
       </Text>
-      
+
       <SimpleModalControls
         items={[
           {
@@ -45,10 +41,10 @@ export const DestroyCloneModal = (props: Props) => {
             onClick: onClose,
           },
           {
-            text: 'Destroy clone',
+            text: 'Delete clone',
             variant: 'primary',
             onClick: handleClickDestroy,
-          }
+          },
         ]}
       />
     </Modal>
diff --git a/ui/packages/shared/components/DestroyCloneRestrictionModal/index.tsx b/ui/packages/shared/components/DestroyCloneRestrictionModal/index.tsx
index 55f18137..608bbe7e 100644
--- a/ui/packages/shared/components/DestroyCloneRestrictionModal/index.tsx
+++ b/ui/packages/shared/components/DestroyCloneRestrictionModal/index.tsx
@@ -25,9 +25,9 @@ export const DestroyCloneRestrictionModal = observer((props: Props) => {
   const { isOpen, onClose, cloneId, clonePagePath } = props
 
   return (
-    <Modal title="Cannot destroy clone" isOpen={isOpen} onClose={onClose}>
+    <Modal title="Cannot delete clone" isOpen={isOpen} onClose={onClose}>
       <Text>
-        Cannot destroy clone <ImportantText>{cloneId}</ImportantText> because
+        Cannot delete clone <ImportantText>{cloneId}</ImportantText> because
         deletion protection is enabled. You can disable deletion protection on{' '}
         {clonePagePath ? (
           <Link to={clonePagePath}>the clone page</Link>
diff --git a/ui/packages/shared/components/Link2/index.tsx b/ui/packages/shared/components/Link2/index.tsx
index 4b9069fc..37df1201 100644
--- a/ui/packages/shared/components/Link2/index.tsx
+++ b/ui/packages/shared/components/Link2/index.tsx
@@ -10,10 +10,22 @@ import cn from 'classnames'
 
 import styles from './styles.module.scss'
 
-type Props = LinkProps
+type Props = LinkProps & { external?: boolean }
 
 export const Link = (props: Props) => {
-  const { className, ...otherProps } = props
+  const { className, external, to, ...otherProps } = props
 
-  return <LinkBase {...otherProps} className={cn(styles.root, className)} />
+  if (external) {
+    return (
+      <a
+        href={to as string}
+        {...otherProps}
+        className={cn(styles.root, className)}
+        target="_blank"
+        rel="noopener noreferrer"
+      />
+    )
+  }
+
+  return <LinkBase to={to} {...otherProps} className={cn(styles.root, className)} />
 }
diff --git a/ui/packages/shared/components/MenuButton/index.tsx b/ui/packages/shared/components/MenuButton/index.tsx
index 94b0e611..68606974 100644
--- a/ui/packages/shared/components/MenuButton/index.tsx
+++ b/ui/packages/shared/components/MenuButton/index.tsx
@@ -10,6 +10,8 @@ type BaseProps = {
   className?: string
   icon?: React.ReactNode
   isCollapsed?: boolean
+  onMouseEnter?: React.MouseEventHandler<HTMLButtonElement>
+  onMouseLeave?: React.MouseEventHandler<HTMLButtonElement>
 }
 
 type ButtonProps = BaseProps & {
@@ -53,7 +55,13 @@ export const Button = (props: Props) => {
 
   if (!props.type || props.type === 'button' || props.type === 'submit')
     return (
-      <button className={className} onClick={props.onClick} disabled={props.disabled}>
+      <button
+        className={className}
+        onClick={props.onClick}
+        disabled={props.disabled}
+        onMouseEnter={props.onMouseEnter}
+        onMouseLeave={props.onMouseLeave}
+      >
         {children}
       </button>
     )
diff --git a/ui/packages/shared/components/ResetCloneModal/index.tsx b/ui/packages/shared/components/ResetCloneModal/index.tsx
index b9de9f32..4b278137 100644
--- a/ui/packages/shared/components/ResetCloneModal/index.tsx
+++ b/ui/packages/shared/components/ResetCloneModal/index.tsx
@@ -18,6 +18,7 @@ import { ImportantText } from '@postgres.ai/shared/components/ImportantText'
 import { Spinner } from '@postgres.ai/shared/components/Spinner'
 import { SimpleModalControls } from '@postgres.ai/shared/components/SimpleModalControls'
 import { compareSnapshotsDesc } from '@postgres.ai/shared/utils/snapshot'
+import { isValidDate } from '@postgres.ai/shared/utils/date'
 
 type Props = {
   isOpen: boolean
@@ -111,9 +112,10 @@ export const ResetCloneModal = (props: Props) => {
               children: (
                 <>
                   {snapshot.dataStateAt} (
-                  {formatDistanceToNowStrict(snapshot.dataStateAtDate, {
-                    addSuffix: true,
-                  })}
+                  {isValidDate(snapshot.dataStateAtDate) && 
+                    formatDistanceToNowStrict(snapshot.dataStateAtDate, {
+                      addSuffix: true,
+                    })}
                   )
                   {isLatest && (
                     <span className={classes.snapshotTag}>Latest</span>
diff --git a/ui/packages/shared/components/TextField/index.tsx b/ui/packages/shared/components/TextField/index.tsx
index 9500eb3f..ab9af54e 100644
--- a/ui/packages/shared/components/TextField/index.tsx
+++ b/ui/packages/shared/components/TextField/index.tsx
@@ -35,6 +35,8 @@ export type TextFieldProps = {
   placeholder?: string
   onBlur?: TextFieldPropsBase['onBlur']
   onFocus?: TextFieldPropsBase['onFocus']
+  name?: TextFieldPropsBase['name']
+  helperText?: TextFieldPropsBase['helperText']
 }
 
 const useStyles = makeStyles(
@@ -51,6 +53,9 @@ const useStyles = makeStyles(
     input: {
       padding: '8px',
     },
+    helperText: {
+      fontSize: 12
+    }
   },
   { index: 1 },
 )
@@ -72,7 +77,7 @@ export const TextField = (props: TextFieldProps) => {
       value={props.value}
       margin="normal"
       fullWidth={props.fullWidth}
-      classes={{}}
+      classes={{ }}
       InputProps={{
         ...props.InputProps,
 
@@ -92,6 +97,11 @@ export const TextField = (props: TextFieldProps) => {
 
         ...props.InputLabelProps,
       }}
+      FormHelperTextProps={{
+        classes: {
+          root: classes.helperText
+        }
+      }}
       onChange={props.onChange}
       children={props.children}
       select={props.select}
@@ -100,6 +110,8 @@ export const TextField = (props: TextFieldProps) => {
       placeholder={props.placeholder}
       onBlur={props.onBlur}
       onFocus={props.onFocus}
+      name={props.name}
+      helperText={props.helperText}
     />
   )
 }
diff --git a/ui/packages/shared/config/index.ts b/ui/packages/shared/config/index.ts
index 3f9d7432..e1439b24 100644
--- a/ui/packages/shared/config/index.ts
+++ b/ui/packages/shared/config/index.ts
@@ -9,7 +9,7 @@ type Config = {
 
 export const config: Config = {
   dateFnsLocale: enUS,
-  appName: 'Postgres.ai',
+  appName: 'Postgres.AI',
 }
 
 const loadDateFnsLocale = async () => {
diff --git a/ui/packages/shared/icons/ArrowDropDown/index.tsx b/ui/packages/shared/icons/ArrowDropDown/index.tsx
index 8a03cd9f..b601b520 100644
--- a/ui/packages/shared/icons/ArrowDropDown/index.tsx
+++ b/ui/packages/shared/icons/ArrowDropDown/index.tsx
@@ -9,6 +9,7 @@ import React from 'react'
 
 type Props = {
   className?: string
+  onClick?: () => void
 }
 
 export const ArrowDropDownIcon = (props: Props) => {
@@ -18,6 +19,7 @@ export const ArrowDropDownIcon = (props: Props) => {
       viewBox="0 0 8 6"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
+      onClick={props.onClick}
     >
       <path
         // eslint-disable-next-line max-len
diff --git a/ui/packages/shared/icons/PostgresSQL/index.tsx b/ui/packages/shared/icons/PostgresSQL/index.tsx
new file mode 100644
index 00000000..9dd1c0a4
--- /dev/null
+++ b/ui/packages/shared/icons/PostgresSQL/index.tsx
@@ -0,0 +1,88 @@
+export const PostgresSQLIcon = () => (
+  <svg
+    className="postgres-logo"
+    width="432.071pt"
+    height="445.383pt"
+    viewBox="0 0 432.071 445.383"
+    xmlSpace="preserve"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <g
+      id="orginal"
+      style={{
+        fillRule: 'nonzero',
+        clipRule: 'nonzero',
+        stroke: '#000000',
+        strokeMiterlimit: 4,
+      }}
+    ></g>
+    <g
+      id="Layer_x0020_3"
+      style={{
+        fillRule: 'nonzero',
+        clipRule: 'nonzero',
+        stroke: '#FFFFFF',
+        strokeWidth: 12.4651,
+        strokeLinecap: 'round',
+        strokeLinejoin: 'round',
+        strokeMiterlimit: 4,
+      }}
+    >
+      <path
+        style={{
+          fill: '#000000',
+          stroke: '#000000',
+          strokeWidth: 37.3953,
+          strokeLinecap: 'butt',
+          strokeLinejoin: 'miter',
+          strokeMiterlimit: 4,
+        }}
+        d="M323.205,324.227c2.833-23.601,1.984-27.062,19.563-23.239l4.463,0.392c13.517,0.615,31.199-2.174,41.587-7c22.362-10.376,35.622-27.7,13.572-23.148c-50.297,10.376-53.755-6.655-53.755-6.655c53.111-78.803,75.313-178.836,56.149-203.322    C352.514-5.534,262.036,26.049,260.522,26.869l-0.482,0.089c-9.938-2.062-21.06-3.294-33.554-3.496c-22.761-0.374-40.032,5.967-53.133,15.904c0,0-161.408-66.498-153.899,83.628c1.597,31.936,45.777,241.655,98.47,178.31    c19.259-23.163,37.871-42.748,37.871-42.748c9.242,6.14,20.307,9.272,31.912,8.147l0.897-0.765c-0.281,2.876-0.157,5.689,0.359,9.019c-13.572,15.167-9.584,17.83-36.723,23.416c-27.457,5.659-11.326,15.734-0.797,18.367c12.768,3.193,42.305,7.716,62.268-20.224    l-0.795,3.188c5.325,4.26,4.965,30.619,5.72,49.452c0.756,18.834,2.017,36.409,5.856,46.771c3.839,10.36,8.369,37.05,44.036,29.406c29.809-6.388,52.6-15.582,54.677-101.107"
+      />
+      <path
+        style={{
+          fill: '#336791',
+          stroke: 'none',
+        }}
+        d="M402.395,271.23c-50.302,10.376-53.76-6.655-53.76-6.655c53.111-78.808,75.313-178.843,56.153-203.326c-52.27-66.785-142.752-35.2-144.262-34.38l-0.486,0.087c-9.938-2.063-21.06-3.292-33.56-3.496c-22.761-0.373-40.026,5.967-53.127,15.902    c0,0-161.411-66.495-153.904,83.63c1.597,31.938,45.776,241.657,98.471,178.312c19.26-23.163,37.869-42.748,37.869-42.748c9.243,6.14,20.308,9.272,31.908,8.147l0.901-0.765c-0.28,2.876-0.152,5.689,0.361,9.019c-13.575,15.167-9.586,17.83-36.723,23.416    c-27.459,5.659-11.328,15.734-0.796,18.367c12.768,3.193,42.307,7.716,62.266-20.224l-0.796,3.188c5.319,4.26,9.054,27.711,8.428,48.969c-0.626,21.259-1.044,35.854,3.147,47.254c4.191,11.4,8.368,37.05,44.042,29.406c29.809-6.388,45.256-22.942,47.405-50.555    c1.525-19.631,4.976-16.729,5.194-34.28l2.768-8.309c3.192-26.611,0.507-35.196,18.872-31.203l4.463,0.392c13.517,0.615,31.208-2.174,41.591-7c22.358-10.376,35.618-27.7,13.573-23.148z"
+      />
+      <path d="M215.866,286.484c-1.385,49.516,0.348,99.377,5.193,111.495c4.848,12.118,15.223,35.688,50.9,28.045c29.806-6.39,40.651-18.756,45.357-46.051c3.466-20.082,10.148-75.854,11.005-87.281" />
+      <path d="M173.104,38.256c0,0-161.521-66.016-154.012,84.109c1.597,31.938,45.779,241.664,98.473,178.316c19.256-23.166,36.671-41.335,36.671-41.335" />
+      <path d="M260.349,26.207c-5.591,1.753,89.848-34.889,144.087,34.417c19.159,24.484-3.043,124.519-56.153,203.329" />
+      <path
+        style={{
+          strokeLinejoin: 'bevel',
+        }}
+        d="M348.282,263.953c0,0,3.461,17.036,53.764,6.653c22.04-4.552,8.776,12.774-13.577,23.155c-18.345,8.514-59.474,10.696-60.146-1.069c-1.729-30.355,21.647-21.133,19.96-28.739c-1.525-6.85-11.979-13.573-18.894-30.338    c-6.037-14.633-82.796-126.849,21.287-110.183c3.813-0.789-27.146-99.002-124.553-100.599c-97.385-1.597-94.19,119.762-94.19,119.762"
+      />
+      <path d="M188.604,274.334c-13.577,15.166-9.584,17.829-36.723,23.417c-27.459,5.66-11.326,15.733-0.797,18.365c12.768,3.195,42.307,7.718,62.266-20.229c6.078-8.509-0.036-22.086-8.385-25.547c-4.034-1.671-9.428-3.765-16.361,3.994z" />
+      <path d="M187.715,274.069c-1.368-8.917,2.93-19.528,7.536-31.942c6.922-18.626,22.893-37.255,10.117-96.339c-9.523-44.029-73.396-9.163-73.436-3.193c-0.039,5.968,2.889,30.26-1.067,58.548c-5.162,36.913,23.488,68.132,56.479,64.938" />
+      <path
+        style={{
+          fill: '#FFFFFF',
+          stroke: '#FFFFFF',
+          strokeWidth: 4.155,
+          strokeLinecap: 'butt',
+          strokeLinejoin: 'miter',
+        }}
+        d="M172.517,141.7c-0.288,2.039,3.733,7.48,8.976,8.207c5.234,0.73,9.714-3.522,9.998-5.559c0.284-2.039-3.732-4.285-8.977-5.015c-5.237-0.731-9.719,0.333-9.996,2.367z"
+      />
+      <path
+        style={{
+          fill: '#FFFFFF',
+          strokeWidth: 2.0775,
+          strokeLinecap: 'butt',
+          strokeLinejoin: 'miter',
+        }}
+        d="M331.941,137.543c0.284,2.039-3.732,7.48-8.976,8.207c-5.238,0.73-9.718-3.522-10.005-5.559c-0.277-2.039,3.74-4.285,8.979-5.015c5.239-0.73,9.718,0.333,10.002,2.368z"
+      />
+      <path d="M350.676,123.432c0.863,15.994-3.445,26.888-3.988,43.914c-0.804,24.748,11.799,53.074-7.191,81.435" />
+      <path
+        style={{
+          strokeWidth: 3,
+        }}
+        d="M0,60.232"
+      />
+    </g>
+  </svg>
+)
diff --git a/ui/packages/shared/package.json b/ui/packages/shared/package.json
index 954a1638..a5fd0980 100644
--- a/ui/packages/shared/package.json
+++ b/ui/packages/shared/package.json
@@ -1,7 +1,13 @@
 {
   "name": "@postgres.ai/shared",
-  "version": "1.0.0",
-  "private": true,
+  "version": "4.0.0",
+  "scripts": {
+    "build": "tsc -p tsconfig.build.json && node scripts/copy-assets.js",
+    "pack": "node scripts/pack.js"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
   "dependencies": {
     "@babel/core": "^7.13.0",
     "@craco/craco": "^7.0.0-alpha.7",
@@ -13,8 +19,6 @@
     "@material-ui/styles": "^4.11.4",
     "@monaco-editor/react": "^4.4.5",
     "@mui/material": "^5.10.12",
-    "@postgres.ai/shared": "link:../shared",
-    "@postgres.ai/ce": "link:../ce",
     "@types/node": "^12.20.33",
     "@types/react": "^17.0.30",
     "@types/react-dom": "^17.0.10",
@@ -41,5 +45,11 @@
     "typescript": "^4.8.3",
     "use-timer": "^2.0.1",
     "yup": "^0.32.11"
+  },
+  "peerDependencies": {
+    "react": "^17.0.0 || ^18.0.0"
+  },
+  "devDependencies": {
+    "glob": "^11.0.2"
   }
 }
diff --git a/ui/packages/shared/pages/Branches/Branch/context.ts b/ui/packages/shared/pages/Branches/Branch/context.ts
new file mode 100644
index 00000000..fdd15d8a
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/Branch/context.ts
@@ -0,0 +1,25 @@
+import { createStrictContext } from '@postgres.ai/shared/utils/react'
+
+import { Api } from './stores/Main'
+import { Stores } from './useCreatedStores'
+
+export type Host = {
+  branchId: string
+  instanceId: string
+  routes: {
+    branch: () => string
+    branches: () => string
+    snapshot: (snapshotId: string) => string
+    createClone: (branchId: string) => string
+  }
+  api: Api
+  elements: {
+    breadcrumbs: React.ReactNode
+  }
+}
+
+export const { useStrictContext: useHost, Provider: HostProvider } =
+  createStrictContext<Host>()
+
+export const { useStrictContext: useStores, Provider: StoresProvider } =
+  createStrictContext<Stores>()
diff --git a/ui/packages/shared/pages/Branches/Branch/index.tsx b/ui/packages/shared/pages/Branches/Branch/index.tsx
new file mode 100644
index 00000000..58e9282e
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/Branch/index.tsx
@@ -0,0 +1,434 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import React, { useEffect, useState } from 'react'
+import { useHistory } from 'react-router'
+import { observer } from 'mobx-react-lite'
+import copyToClipboard from 'copy-to-clipboard'
+import {
+  makeStyles,
+  Button,
+  TextField,
+  IconButton,
+  Table,
+  TableHead,
+  TableRow,
+  TableBody,
+} from '@material-ui/core'
+
+import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
+import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
+import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { Spinner } from '@postgres.ai/shared/components/Spinner'
+import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
+import { icons } from '@postgres.ai/shared/styles/icons'
+import { styles } from '@postgres.ai/shared/styles/styles'
+import { DeleteBranchModal } from '@postgres.ai/shared/pages/Branches/components/Modals/DeleteBranchModal'
+import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
+import { generateSnapshotPageId } from '@postgres.ai/shared/pages/Instance/Snapshots/utils'
+import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
+import { getCliBranchListCommand } from '@postgres.ai/shared/pages/CreateBranch/utils'
+import {
+  TableBodyCell,
+  TableBodyCellMenu,
+  TableHeaderCell,
+} from '@postgres.ai/shared/components/Table'
+
+import { useCreatedStores } from './useCreatedStores'
+import { Host } from './context'
+import { DeleteBranch } from '@postgres.ai/shared/types/api/endpoints/deleteBranch'
+import { InstanceTabs, TABS_INDEX } from "../../Instance/Tabs";
+
+type Props = Host & { isPlatform?: boolean, hideBranchingFeatures?: boolean }
+
+const useStyles = makeStyles(
+  () => ({
+    wrapper: {
+      display: 'flex',
+      gap: '60px',
+      maxWidth: '100%',
+      fontSize: '14px',
+      marginTop: '20px',
+
+      '@media (max-width: 1300px)': {
+        flexDirection: 'column',
+        gap: '20px',
+      },
+    },
+    marginTop: {
+      marginTop: '16px',
+    },
+    title: {
+      marginTop: '8px',
+      lineHeight: '26px'
+    },
+    container: {
+      maxWidth: '100%',
+      flex: '1 1 0',
+      minWidth: 0,
+
+      '&  p,span': {
+        fontSize: 14,
+      },
+    },
+    snippetContainer: {
+      flex: '1 1 0',
+      minWidth: 0,
+      boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 12px',
+      padding: '10px 20px 10px 20px',
+      height: 'max-content',
+      borderRadius: '4px',
+    },
+    actions: {
+      display: 'flex',
+      marginRight: '-16px',
+    },
+    spinner: {
+      marginLeft: '8px',
+    },
+    actionButton: {
+      marginRight: '16px',
+    },
+    summary: {
+      marginTop: 20,
+    },
+    text: {
+      marginTop: '4px',
+    },
+    cliText: {
+      marginTop: '8px',
+    },
+    paramTitle: {
+      display: 'inline-block',
+      width: 200,
+    },
+    copyFieldContainer: {
+      position: 'relative',
+      display: 'block',
+      maxWidth: 525,
+      width: '100%',
+    },
+    textField: {
+      ...styles.inputField,
+      'max-width': 525,
+      display: 'inline-block',
+      '& .MuiOutlinedInput-input': {
+        paddingRight: '32px!important',
+      },
+    },
+    tableContainer: {
+      position: 'relative',
+      maxWidth: 525,
+      width: '100%',
+    },
+    copyButton: {
+      position: 'absolute',
+      top: 16,
+      right: 0,
+      zIndex: 100,
+      width: 32,
+      height: 32,
+      padding: 8,
+    },
+    pointerCursor: {
+      cursor: 'pointer',
+    },
+  }),
+  { index: 1 },
+)
+
+export const BranchesPage = observer((props: Props) => {
+  const classes = useStyles()
+  const history = useHistory()
+  const stores = useCreatedStores(props)
+
+  const [isOpenDestroyModal, setIsOpenDestroyModal] = useState(false)
+
+  const {
+    branch,
+    snapshotList,
+    deleteBranch,
+    reload,
+    load,
+    isReloading,
+    isBranchesLoading,
+    getBranchesError,
+    snapshotListError,
+    getBranchError,
+  } = stores.main
+
+  const hasBranchError = getBranchesError || getBranchError || snapshotListError
+
+  const branchLogLength = snapshotList?.reduce((acc, snapshot) => {
+    if (snapshot?.branch !== null) {
+      return acc + snapshot.branch?.length
+    } else {
+      return acc
+    }
+  }, 0)
+
+  const headRendered = (
+    <>
+      <style>{'p { margin: 0;}'}</style>
+
+      {props.elements.breadcrumbs}
+
+      <SectionTitle
+        className={classes.title}
+        tag="h1"
+        level={1}
+        text={`Branch ${props.branchId}`}
+      >
+        <InstanceTabs
+          tab={TABS_INDEX.BRANCHES}
+          isPlatform={props.isPlatform}
+          instanceId={props.instanceId}
+          hasLogs={props.api.initWS !== undefined}
+          hideInstanceTabs={props.hideBranchingFeatures}
+        />
+      </SectionTitle>
+    </>
+  )
+
+  useEffect(() => {
+    load(props.branchId, props.instanceId)
+  }, [])
+
+  if (isBranchesLoading) return <PageSpinner />
+
+  if (hasBranchError) {
+    return (
+      <>
+        {headRendered}
+        <ErrorStub
+          title={
+            getBranchesError?.title ||
+            getBranchError?.title ||
+            snapshotListError?.title
+          }
+          message={
+            getBranchesError?.message ||
+            getBranchError?.message ||
+            snapshotListError?.message
+          }
+          className={classes.marginTop}
+        />
+      </>
+    )
+  }
+
+  return (
+    <>
+      {headRendered}
+      <div className={classes.wrapper}>
+        <div className={classes.container}>
+          <div className={classes.actions}>
+            <Button
+              variant="contained"
+              color="primary"
+              onClick={() => history.push(props.routes.createClone(props.branchId))}
+              disabled={isReloading}
+              title={'Create clone'}
+              className={classes.actionButton}
+            >
+              Create clone
+            </Button>
+            <Button
+              variant="contained"
+              color="primary"
+              onClick={() => setIsOpenDestroyModal(true)}
+              disabled={isReloading}
+              title={'Delete this branch'}
+              className={classes.actionButton}
+            >
+              Delete branch
+            </Button>
+            <Button
+              variant="outlined"
+              color="secondary"
+              onClick={() => reload(props.branchId, props.instanceId)}
+              disabled={isReloading}
+              title={'Refresh branch information'}
+              className={classes.actionButton}
+            >
+              Reload info
+              {isReloading && <Spinner size="sm" className={classes.spinner} />}
+            </Button>
+          </div>
+          <br />
+          <div>
+            <div>
+              <p>
+                <strong>Data state at</strong>&nbsp;
+                <Tooltip
+                  content={
+                    <>
+                      <strong>Data state time</strong> is a time at which data
+                      is&nbsp; recovered for this branch.
+                    </>
+                  }
+                >
+                  {icons.infoIcon}
+                </Tooltip>
+              </p>
+              <p className={classes.text}>{branch?.dataStateAt || '-'}</p>
+            </div>
+            <div className={classes.summary}>
+              <p>
+                <strong>Summary</strong>&nbsp;
+              </p>
+              <p className={classes.text}>
+                <span className={classes.paramTitle}>Branch name:</span>
+                {branch?.name}
+              </p>
+              <p className={classes.text}>
+                <span className={classes.paramTitle}>Parent branch:</span>
+                {branch?.parent}
+              </p>
+            </div>
+            <br />
+            <p>
+              <strong>Snapshot info</strong>
+            </p>
+            <div className={classes.copyFieldContainer}>
+              <TextField
+                variant="outlined"
+                label="Snapshot ID"
+                value={branch?.snapshotID}
+                className={classes.textField}
+                margin="normal"
+                fullWidth
+                InputLabelProps={{
+                  shrink: true,
+                  style: styles.inputFieldLabel,
+                }}
+                FormHelperTextProps={{
+                  style: styles.inputFieldHelper,
+                }}
+              />
+              <IconButton
+                className={classes.copyButton}
+                aria-label="Copy"
+                onClick={() => copyToClipboard(String(branch?.snapshotID))}
+              >
+                {icons.copyIcon}
+              </IconButton>
+            </div>
+            <br />
+            {Number(branchLogLength) > 0 && (
+              <>
+                <strong>Branch log ({branchLogLength})</strong>
+                <HorizontalScrollContainer>
+                  <Table className={classes.tableContainer}>
+                    <TableHead>
+                      <TableRow>
+                        <TableHeaderCell />
+                        <TableHeaderCell>Name</TableHeaderCell>
+                        <TableHeaderCell>Snapshot ID</TableHeaderCell>
+                        <TableHeaderCell>Data state at</TableHeaderCell>
+                        <TableHeaderCell>Message</TableHeaderCell>
+                      </TableRow>
+                    </TableHead>
+                    {snapshotList?.map((snapshot, id) => (
+                      <TableBody key={id}>
+                        {snapshot?.branch?.map((item, id) => (
+                          <TableRow
+                            key={id}
+                            hover
+                            className={classes.pointerCursor}
+                            onClick={() =>
+                              generateSnapshotPageId(snapshot.id) &&
+                              history.push(
+                                props.routes.snapshot(
+                                  generateSnapshotPageId(snapshot.id) || '',
+                                ),
+                              )
+                            }
+                          >
+                            <TableBodyCellMenu
+                              actions={[
+                                {
+                                  name: 'Copy branch name',
+                                  onClick: () => copyToClipboard(item),
+                                },
+                                {
+                                  name: 'Copy snapshot ID',
+                                  onClick: () =>
+                                    copyToClipboard(snapshot.id || ''),
+                                },
+                              ]}
+                            />
+                            <TableBodyCell>{item}</TableBodyCell>
+                            <TableBodyCell>{snapshot.id || '-'}</TableBodyCell>
+                            <TableBodyCell>
+                              {snapshot.dataStateAt || '-'}
+                            </TableBodyCell>
+                            <TableBodyCell>
+                              {snapshot.message ?? '-'}
+                            </TableBodyCell>
+                          </TableRow>
+                        ))}
+                      </TableBody>
+                    ))}
+                  </Table>
+                </HorizontalScrollContainer>
+              </>
+            )}
+          </div>
+        </div>
+        <div className={classes.snippetContainer}>
+          <SectionTitle
+            className={classes.marginTop}
+            tag="h2"
+            level={2}
+            text={'Delete branch using CLI'}
+          />
+          <p className={classes.cliText}>
+            You can delete this branch using CLI. To do this, run the command
+            below:
+          </p>
+          <SyntaxHighlight content={`dblab branch delete ${props.branchId}`} />
+
+          <SectionTitle
+            className={classes.marginTop}
+            tag="h2"
+            level={2}
+            text={'Get branches using CLI'}
+          />
+          <p className={classes.marginTop}>
+            To list all branches using CLI, copy and paste it into your
+            terminal.
+          </p>
+          <SyntaxHighlight content={getCliBranchListCommand()} />
+
+          <SectionTitle
+            className={classes.marginTop}
+            tag="h2"
+            level={2}
+            text={'Get snapshots for this branch using CLI'}
+          />
+          <p className={classes.cliText}>
+            You can get a list of snapshots for this branch using CLI. To do
+            this, run the command below:
+          </p>
+          <SyntaxHighlight content={`dblab branch log ${props.branchId}`} />
+        </div>
+        <DeleteBranchModal
+          isOpen={isOpenDestroyModal}
+          onClose={() => setIsOpenDestroyModal(false)}
+          deleteBranch={deleteBranch as DeleteBranch}
+          branchName={props.branchId}
+          instanceId={props.instanceId}
+          afterSubmitClick={() => {
+            stores.main.reload(props.branchId, props.instanceId)
+            history.push(props.routes.branches())
+          }}
+        />
+      </div>
+    </>
+  )
+})
diff --git a/ui/packages/shared/pages/Branches/Branch/stores/Main.ts b/ui/packages/shared/pages/Branches/Branch/stores/Main.ts
new file mode 100644
index 00000000..6c6f1735
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/Branch/stores/Main.ts
@@ -0,0 +1,138 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { makeAutoObservable } from 'mobx'
+
+import { GetBranches } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { Branch } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { DeleteBranch } from '@postgres.ai/shared/types/api/endpoints/deleteBranch'
+import {
+  SnapshotList,
+  GetSnapshotList,
+} from '@postgres.ai/shared/types/api/endpoints/getSnapshotList'
+import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
+
+type Error = {
+  title?: string
+  message: string
+}
+
+export type Api = {
+  getBranches: GetBranches
+  deleteBranch: DeleteBranch
+  getSnapshotList: GetSnapshotList
+  initWS?: InitWS
+}
+
+export class MainStore {
+  getBranchError: Error | null = null
+  snapshotListError: Error | null = null
+  getBranchesError: Error | null = null
+
+  isReloading = false
+  isBranchesLoading = false
+
+  branches: Branch[] | null = null
+  branch: Branch | null = null
+  snapshotList: SnapshotList[] | null = null
+
+  private readonly api: Api
+
+  constructor(api: Api) {
+    this.api = api
+    makeAutoObservable(this)
+  }
+
+  load = async (branchId: string, instanceId: string) => {
+    if (!branchId) return
+
+    this.isBranchesLoading = true
+
+    await this.getBranches(branchId, instanceId)
+  }
+
+  reload = async (branchId: string, instanceId: string) => {
+    if (!branchId) return
+
+    this.isReloading = true
+    await this.getBranches(branchId, instanceId)
+    this.isReloading = false
+  }
+
+  getBranches = async (branchId: string, instanceId: string) => {
+    if (!this.api.getBranches) return
+    const { response, error } = await this.api.getBranches(instanceId)
+
+    if (error) {
+      this.isBranchesLoading = false
+      this.getBranchesError = await error.json().then((err) => err)
+    }
+
+    if (response) {
+      this.branches = response
+      this.getBranch(branchId, instanceId)
+    }
+
+    return response
+  }
+
+  getBranch = async (branchId: string, instanceId: string) => {
+    const currentBranch = this.branches?.filter((s) => {
+      return s.name === branchId
+    })
+
+    if (currentBranch && currentBranch?.length > 0) {
+      this.branch = currentBranch[0]
+      this.getSnapshotList(currentBranch[0].name, instanceId)
+    } else {
+      this.getBranchError = {
+        title: 'Error',
+        message: `Branch "${branchId}" not found`,
+      }
+      this.isBranchesLoading = false
+    }
+
+    return !!currentBranch
+  }
+
+  deleteBranch = async (branchName: string, instanceId: string) => {
+    if (!branchName) return
+
+    const { response, error } = await this.api.deleteBranch(
+      branchName,
+      instanceId,
+    )
+
+    if (response) {
+      this.branches =
+        this.branches?.filter((branch) => branch.name !== branchName) || []
+    }
+
+    return { response, error }
+  }
+
+  getSnapshotList = async (branchName: string, instanceId: string) => {
+    if (!this.api.getSnapshotList) return
+
+    const { response, error } = await this.api.getSnapshotList(
+      branchName,
+      instanceId,
+    )
+
+    this.isBranchesLoading = false
+
+    if (error) {
+      this.snapshotListError = await error.json().then((err) => err)
+    }
+
+    if (response) {
+      this.snapshotList = response
+    }
+
+    return response
+  }
+}
diff --git a/ui/packages/shared/pages/Branches/Branch/useCreatedStores.ts b/ui/packages/shared/pages/Branches/Branch/useCreatedStores.ts
new file mode 100644
index 00000000..7164757e
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/Branch/useCreatedStores.ts
@@ -0,0 +1,10 @@
+import { useMemo } from 'react'
+
+import { MainStore } from './stores/Main'
+import { Host } from './context'
+
+export const useCreatedStores = (host: Host) => ({
+  main: useMemo(() => new MainStore(host.api), []),
+})
+
+export type Stores = ReturnType<typeof useCreatedStores>
diff --git a/ui/packages/shared/pages/Branches/components/BranchesTable/index.tsx b/ui/packages/shared/pages/Branches/components/BranchesTable/index.tsx
new file mode 100644
index 00000000..a8406df2
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/components/BranchesTable/index.tsx
@@ -0,0 +1,202 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import cn from 'classnames'
+import { useEffect, useState } from 'react'
+import copy from 'copy-to-clipboard'
+import { makeStyles } from '@material-ui/core'
+import { useHistory } from 'react-router-dom'
+import { formatDistanceToNowStrict } from 'date-fns'
+
+import { isValidDate } from '@postgres.ai/shared/utils/date'
+import { ArrowDropDownIcon } from '@postgres.ai/shared/icons/ArrowDropDown'
+import { Branch } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
+import { useHost } from '@postgres.ai/shared/pages/Instance/context'
+import {
+  Table,
+  TableHead,
+  TableRow,
+  TableBody,
+  TableHeaderCell,
+  TableBodyCell,
+  TableBodyCellMenu,
+} from '@postgres.ai/shared/components/Table'
+
+import { DeleteBranchModal } from '../Modals/DeleteBranchModal'
+import { DeleteBranch } from '@postgres.ai/shared/types/api/endpoints/deleteBranch'
+
+const useStyles = makeStyles(
+  {
+    cellContentCentered: {
+      display: 'flex',
+      alignItems: 'center',
+    },
+    pointerCursor: {
+      cursor: 'pointer',
+    },
+    sortIcon: {
+      marginLeft: '8px',
+      width: '10px',
+    },
+    interactiveRow: {
+      cursor: 'pointer',
+    },
+    verticalCentered: {
+      display: 'flex',
+      alignItems: 'center',
+    },
+    marginTop: {
+      marginTop: '16px',
+    },
+    sortIconUp: {
+      transform: 'rotate(180deg)',
+    },
+  },
+  { index: 1 },
+)
+
+export const BranchesTable = ({
+  branches,
+  emptyTableText,
+  deleteBranch,
+  branchesRoute,
+  reloadBranches,
+}: {
+  branches: Branch[]
+  emptyTableText: string
+  deleteBranch: DeleteBranch
+  branchesRoute: string
+  reloadBranches: () => void
+}) => {
+  const host = useHost()
+  const history = useHistory()
+  const classes = useStyles()
+
+  const [state, setState] = useState({
+    sortByParent: 'desc',
+    branches: [] as Branch[],
+  })
+  const [branchId, setBranchId] = useState('')
+  const [isOpenDestroyModal, setIsOpenDestroyModal] = useState(false)
+
+  const handlesortByParent = () => {
+    const sortByParent = state.sortByParent === 'desc' ? 'asc' : 'desc'
+
+    const sortedBranches = [...state.branches].sort((a, b) => {
+      if (sortByParent === 'asc') {
+        return a.parent.localeCompare(b.parent)
+      } else {
+        return b.parent.localeCompare(a.parent)
+      }
+    })
+
+    setState({
+      sortByParent,
+      branches: sortedBranches,
+    })
+  }
+
+  useEffect(() => {
+    setState({
+      sortByParent: 'desc',
+      branches: branches ?? [],
+    })
+  }, [branches])
+
+  if (!state.branches.length) {
+    return <p className={classes.marginTop}>{emptyTableText}</p>
+  }
+
+  return (
+    <HorizontalScrollContainer>
+      <Table>
+        <TableHead>
+          <TableRow>
+            <TableHeaderCell />
+            <TableHeaderCell>Branch</TableHeaderCell>
+            <TableHeaderCell>
+              <div
+                onClick={handlesortByParent}
+                className={cn(classes.interactiveRow, classes.verticalCentered)}
+              >
+                Parent
+                <ArrowDropDownIcon
+                  className={cn(
+                    state.sortByParent === 'asc' && classes.sortIconUp,
+                    classes.sortIcon,
+                  )}
+                />
+              </div>
+            </TableHeaderCell>
+            <TableHeaderCell>Data state time</TableHeaderCell>
+            <TableHeaderCell>Latest Snapshot ID</TableHeaderCell>
+            <TableHeaderCell>Number of snapshots</TableHeaderCell>
+          </TableRow>
+        </TableHead>
+        <TableBody>
+          {state.branches?.map((branch) => {
+            const branchPagePath = host.routes.branch(branch.name)
+
+            return (
+              <TableRow
+                key={branch.name}
+                hover
+                onClick={() => history.push(branchPagePath)}
+                className={classes.pointerCursor}
+              >
+                <TableBodyCellMenu
+                  actions={[
+                    {
+                      name: 'Copy snapshot ID',
+                      onClick: () => copy(branch.snapshotID),
+                    },
+                    {
+                      name: 'Delete branch',
+                      onClick: () => {
+                        setBranchId(branch.name)
+                        setIsOpenDestroyModal(true)
+                      },
+                    },
+                  ]}
+                />
+
+                <TableBodyCell>{branch.name}</TableBodyCell>
+                <TableBodyCell>{branch.parent}</TableBodyCell>
+                <TableBodyCell>
+                  {branch.dataStateAt} (
+                  {isValidDate(new Date(branch.dataStateAt))
+                    ? formatDistanceToNowStrict(new Date(branch.dataStateAt), {
+                        addSuffix: true,
+                      })
+                    : '-'}
+                  )
+                </TableBodyCell>
+                <TableBodyCell>{branch.snapshotID}</TableBodyCell>
+                <TableBodyCell>{branch.numSnapshots}</TableBodyCell>
+              </TableRow>
+            )
+          })}
+        </TableBody>
+        <DeleteBranchModal
+          isOpen={isOpenDestroyModal}
+          onClose={() => {
+            setIsOpenDestroyModal(false)
+            setBranchId('')
+          }}
+          deleteBranch={deleteBranch}
+          branchName={branchId}
+          instanceId={host.instanceId}
+          afterSubmitClick={() => {
+            reloadBranches()
+            history.push(branchesRoute)
+          }}
+        />
+      </Table>
+    </HorizontalScrollContainer>
+  )
+}
diff --git a/ui/packages/shared/pages/Branches/components/Modals/DeleteBranchModal/index.tsx b/ui/packages/shared/pages/Branches/components/Modals/DeleteBranchModal/index.tsx
new file mode 100644
index 00000000..12f09320
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/components/Modals/DeleteBranchModal/index.tsx
@@ -0,0 +1,84 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { useState } from 'react'
+import { makeStyles } from '@material-ui/core'
+
+import { Modal } from '@postgres.ai/shared/components/Modal'
+import { ModalProps } from '@postgres.ai/shared/pages/Branches/components/Modals/types'
+import { SimpleModalControls } from '@postgres.ai/shared/components/SimpleModalControls'
+import { ImportantText } from '@postgres.ai/shared/components/ImportantText'
+import { Text } from '@postgres.ai/shared/components/Text'
+import { DeleteBranch } from '@postgres.ai/shared/types/api/endpoints/deleteBranch'
+interface DeleteBranchModalProps extends ModalProps {
+  deleteBranch: DeleteBranch
+  branchName: string
+  instanceId: string
+  afterSubmitClick: () => void
+}
+
+const useStyles = makeStyles(
+  {
+    errorMessage: {
+      color: 'red',
+      marginTop: '10px',
+    },
+  },
+  { index: 1 },
+)
+
+export const DeleteBranchModal = ({
+  isOpen,
+  onClose,
+  deleteBranch,
+  branchName,
+  instanceId,
+  afterSubmitClick,
+}: DeleteBranchModalProps) => {
+  const classes = useStyles()
+  const [deleteError, setDeleteError] = useState<string | null>(null)
+
+  const handleDelete = async () => {
+    const deleteRes = await deleteBranch(branchName, instanceId)
+
+    if (deleteRes?.error) {
+      setDeleteError(deleteRes.error?.message)
+    } else {
+      afterSubmitClick()
+    }
+  }
+
+  const handleClose = () => {
+    setDeleteError(null)
+    onClose()
+  }
+
+  return (
+    <Modal title="Confirmation" onClose={handleClose} isOpen={isOpen} size="xs">
+      <Text>
+        Are you sure you want to delete branch{' '}
+        <ImportantText>{branchName}</ImportantText>? This action cannot be
+        undone.
+      </Text>
+      {deleteError && <p className={classes.errorMessage}>{deleteError}</p>}
+      <SimpleModalControls
+        items={[
+          {
+            text: 'Cancel',
+            onClick: handleClose,
+          },
+          {
+            text: 'Delete branch',
+            variant: 'primary',
+            onClick: handleDelete,
+            isDisabled: branchName === 'main',
+          },
+        ]}
+      />
+    </Modal>
+  )
+}
diff --git a/ui/packages/shared/pages/Branches/components/Modals/types.ts b/ui/packages/shared/pages/Branches/components/Modals/types.ts
new file mode 100644
index 00000000..c7226170
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/components/Modals/types.ts
@@ -0,0 +1,4 @@
+export interface ModalProps {
+  isOpen: boolean
+  onClose: () => void
+}
diff --git a/ui/packages/shared/pages/Branches/index.tsx b/ui/packages/shared/pages/Branches/index.tsx
new file mode 100644
index 00000000..c7cfbdf2
--- /dev/null
+++ b/ui/packages/shared/pages/Branches/index.tsx
@@ -0,0 +1,128 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { observer } from 'mobx-react-lite'
+import { useHistory } from 'react-router'
+import { makeStyles } from '@material-ui/core'
+import React, { useEffect, useState } from 'react'
+
+import { useStores, useHost } from '@postgres.ai/shared/pages/Instance/context'
+import { Button } from '@postgres.ai/shared/components/Button2'
+import { Branch } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { Spinner } from '@postgres.ai/shared/components/Spinner'
+import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
+import { BranchesTable } from '@postgres.ai/shared/pages/Branches/components/BranchesTable'
+import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
+import { InfoIcon } from '@postgres.ai/shared/icons/Info'
+import { DeleteBranch } from '@postgres.ai/shared/types/api/endpoints/deleteBranch'
+
+const useStyles = makeStyles(
+  {
+    container: {
+      marginTop: '16px',
+    },
+    infoIcon: {
+      height: '12px',
+      width: '12px',
+      marginLeft: '8px',
+      color: '#808080',
+    },
+    spinner: {
+      position: 'absolute',
+      right: '50%',
+      transform: 'translate(-50%, -50%)',
+    },
+  },
+  { index: 1 },
+)
+
+interface BranchesProps {
+  instanceId: string
+}
+
+export const Branches: React.FC<BranchesProps> = observer(({ instanceId }) => {
+  const host = useHost()
+  const stores = useStores()
+  const classes = useStyles()
+  const history = useHistory()
+  const [branches, setBranches] = useState<Branch[]>([])
+  const [isLoading, setIsLoading] = useState(true)
+  const {
+    instance,
+    getBranches,
+    isBranchesLoading,
+    getBranchesError,
+    deleteBranch,
+  } = stores.main
+
+  const goToBranchAddPage = () => history.push(host.routes.createBranch())
+
+  const loadBranches = () => {
+    getBranches(instanceId)
+      .then((response) => {
+        response && setBranches(response)
+      })
+      .finally(() => setIsLoading(false))
+  }
+
+  useEffect(() => {
+    loadBranches()
+  }, [])
+
+  if (!instance && !isBranchesLoading) return <></>
+
+  if (getBranchesError)
+    return (
+      <ErrorStub
+        title={getBranchesError?.title}
+        message={getBranchesError?.message}
+      />
+    )
+
+  return (
+    <div className={classes.container}>
+      {isBranchesLoading || isLoading ? (
+        <Spinner size="lg" className={classes.spinner} />
+      ) : (
+        <>
+          <SectionTitle
+            level={2}
+            tag="h2"
+            text={`Branches (${branches?.length || 0})`}
+            rightContent={
+              <>
+                <Button
+                  theme="primary"
+                  isDisabled={!branches.length}
+                  onClick={goToBranchAddPage}
+                >
+                  Create branch
+                </Button>
+
+                {!branches.length && (
+                  <Tooltip content="No existing branch">
+                    <div style={{ display: 'flex' }}>
+                      <InfoIcon className={classes.infoIcon} />
+                    </div>
+                  </Tooltip>
+                )}
+              </>
+            }
+          />
+          <BranchesTable
+            branches={branches}
+            branchesRoute={host.routes.branches()}
+            reloadBranches={loadBranches}
+            deleteBranch={deleteBranch as DeleteBranch}
+            emptyTableText="This instance has no active branches."
+          />
+        </>
+      )}
+    </div>
+  )
+})
diff --git a/ui/packages/shared/pages/Clone/context.ts b/ui/packages/shared/pages/Clone/context.ts
index 552ef934..165993a2 100644
--- a/ui/packages/shared/pages/Clone/context.ts
+++ b/ui/packages/shared/pages/Clone/context.ts
@@ -8,6 +8,8 @@ export type Host = {
   cloneId: string
   routes: {
     instance: () => string
+    snapshot: (snapshotId: string) => string
+    createSnapshot: (cloneId: string) => string
   }
   api: Api
   elements: {
diff --git a/ui/packages/shared/pages/Clone/index.tsx b/ui/packages/shared/pages/Clone/index.tsx
index 7f75e398..ccab4325 100644
--- a/ui/packages/shared/pages/Clone/index.tsx
+++ b/ui/packages/shared/pages/Clone/index.tsx
@@ -5,16 +5,16 @@
  *--------------------------------------------------------------------------
  */
 
-import { useEffect, useState } from 'react'
-import copyToClipboard from 'copy-to-clipboard'
+import React, { useEffect, useState } from 'react'
 import { observer } from 'mobx-react-lite'
 import { useHistory } from 'react-router-dom'
+import copyToClipboard from 'copy-to-clipboard'
 import {
   makeStyles,
-  TextField,
   Button,
   FormControlLabel,
   Checkbox,
+  TextField,
   IconButton,
 } from '@material-ui/core'
 
@@ -35,33 +35,66 @@ import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
 import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
 import { icons } from '@postgres.ai/shared/styles/icons'
 import { styles } from '@postgres.ai/shared/styles/styles'
+import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
 
 import { Status } from './Status'
 import { useCreatedStores } from './useCreatedStores'
 import { Host } from './context'
+import {
+  getCliDestroyCloneCommand,
+  getCliProtectedCloneCommand,
+  getCliResetCloneCommand,
+  getCreateSnapshotCommand,
+} from './utils'
+import { InstanceTabs, TABS_INDEX } from "../Instance/Tabs";
 
-const textFieldWidth = 400
+const textFieldWidth = 525
 
 const useStyles = makeStyles(
   (theme) => ({
+    wrapper: {
+      display: 'flex',
+      gap: '40px',
+      maxWidth: '1200px',
+      fontSize: '14px !important',
+      marginTop: '20px',
+
+      '@media (max-width: 1300px)': {
+        flexDirection: 'column',
+        gap: '20px',
+      },
+    },
     title: {
-      marginTop: '16px',
+      marginTop: '8px',
+      lineHeight: '26px'
+    },
+    tooltip: {
+      marginTop: '8px',
     },
     container: {
-      maxWidth: '425px',
+      maxWidth: textFieldWidth + 25,
       marginTop: '16px',
     },
     text: {
       marginTop: '4px',
     },
     errorStub: {
-      marginTop: '24px',
+      margin: '24px 0',
     },
     spinner: {
       marginLeft: '8px',
     },
     summary: {
-      marginTop: 20,
+      flex: '1.5 1 0',
+      minWidth: 0,
+    },
+    snippetContainer: {
+      flex: '1 1 0',
+      minWidth: 0,
+      boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 12px',
+      padding: '10px 20px 10px 20px',
+      height: 'max-content',
+      borderRadius: '4px',
     },
     paramTitle: {
       display: 'inline-block',
@@ -88,10 +121,12 @@ const useStyles = makeStyles(
     },
     actions: {
       display: 'flex',
-      marginRight: '-16px',
+      flexWrap: 'wrap',
+      rowGap: '16px',
+      marginBottom: '20px',
     },
     actionButton: {
-      marginRight: '16px',
+      marginRight: '10px',
     },
     remark: {
       fontSize: '12px',
@@ -137,6 +172,9 @@ const useStyles = makeStyles(
       maxWidth: textFieldWidth,
       width: '100%',
     },
+    status: {
+      maxWidth: `${textFieldWidth}px`,
+    },
     copyButton: {
       position: 'absolute',
       top: 16,
@@ -146,14 +184,11 @@ const useStyles = makeStyles(
       height: 32,
       padding: 8,
     },
-    status: {
-      maxWidth: `${textFieldWidth}px`,
-    },
   }),
   { index: 1 },
 )
 
-type Props = Host
+type Props = Host & { isPlatform?: boolean, hideBranchingFeatures?: boolean }
 
 export const Clone = observer((props: Props) => {
   const classes = useStyles()
@@ -172,6 +207,7 @@ export const Clone = observer((props: Props) => {
 
   const {
     instance,
+    snapshots,
     clone,
     isResettingClone,
     isDestroyingClone,
@@ -192,30 +228,29 @@ export const Clone = observer((props: Props) => {
         tag="h1"
         level={1}
         text={`Clone ${props.cloneId}`}
-      />
+      >
+        <InstanceTabs
+          tab={TABS_INDEX.CLONES}
+          isPlatform={props.isPlatform}
+          instanceId={props.instanceId}
+          hasLogs={props.api.initWS !== undefined}
+          hideInstanceTabs={props.hideBranchingFeatures}
+        />
+      </SectionTitle>
     </>
   )
 
-  // Getting instance error.
-  if (stores.main.instanceError)
-    return (
-      <>
-        {headRendered}
-
-        <ErrorStub
-          title={stores.main.instanceError.title}
-          message={stores.main.instanceError.message}
-        />
-      </>
-    )
+  const cloneErrorMessage =
+    stores.main.instanceError?.message || stores.main.cloneError?.message
+  const cloneErrorTitle =
+    stores.main.instanceError?.title || stores.main.cloneError?.title
 
-  // Getting clone error.
-  if (stores.main.cloneError)
+  if (cloneErrorMessage && cloneErrorTitle)
     return (
       <>
         {headRendered}
 
-        <ErrorStub {...stores.main.cloneError} />
+        <ErrorStub title={cloneErrorTitle} message={cloneErrorMessage} />
       </>
     )
 
@@ -249,6 +284,10 @@ export const Clone = observer((props: Props) => {
     if (isSuccess) history.push(props.routes.instance())
   }
 
+  const createSnapshot = async () => {
+    history.push(props.routes.createSnapshot(props.cloneId))
+  }
+
   // Clone reload.
   const reloadClone = () => stores.main.reload()
 
@@ -274,73 +313,82 @@ export const Clone = observer((props: Props) => {
   return (
     <>
       {headRendered}
-      <div className={classes.container}>
-        <div className={classes.actions}>
-          <Button
-            variant="contained"
-            color="primary"
-            onClick={requestResetClone}
-            disabled={isDisabledControls}
-            title={'Reset clone'}
-            className={classes.actionButton}
-          >
-            Reset clone
-            {isResettingClone && (
-              <Spinner size="sm" className={classes.spinner} />
-            )}
-          </Button>
-          <Button
-            variant="contained"
-            color="primary"
-            onClick={requestDestroyClone}
-            disabled={isDisabledControls}
-            title={'Destroy this clone'}
-            className={classes.actionButton}
-          >
-            Destroy clone
-            {isDestroyingClone && (
-              <Spinner size="sm" className={classes.spinner} />
-            )}
-          </Button>
-          <Button
-            variant="outlined"
-            color="secondary"
-            onClick={reloadClone}
-            disabled={isDisabledControls || isReloading}
-            title={'Refresh clone information'}
-            className={classes.actionButton}
-          >
-            Reload info
-            {isReloading && <Spinner size="sm" className={classes.spinner} />}
-          </Button>
-        </div>
-
-        {stores.main.resetCloneError && (
-          <ErrorStub
-            title="Resetting error"
-            message={stores.main.resetCloneError}
-            className={classes.errorStub}
-          />
-        )}
-
-        {stores.main.destroyCloneError && (
-          <ErrorStub
-            title="Destroying error"
-            message={stores.main.destroyCloneError}
-            className={classes.errorStub}
-          />
-        )}
-
+      <div className={classes.wrapper}>
         <div className={classes.summary}>
-          <div>
+          <div className={classes.actions}>
+            <Button
+              variant="contained"
+              color="primary"
+              onClick={requestResetClone}
+              disabled={isDisabledControls}
+              title={'Reset clone'}
+              className={classes.actionButton}
+            >
+              Reset clone
+              {isResettingClone && (
+                <Spinner size="sm" className={classes.spinner} />
+              )}
+            </Button>
+            <Button
+              variant="contained"
+              color="primary"
+              onClick={requestDestroyClone}
+              disabled={isDisabledControls}
+              title={'Delete this clone'}
+              className={classes.actionButton}
+            >
+              Delete clone
+              {isDestroyingClone && (
+                <Spinner size="sm" className={classes.spinner} />
+              )}
+            </Button>
+            {!props.hideBranchingFeatures && <Button
+              variant="contained"
+              color="primary"
+              onClick={createSnapshot}
+              disabled={isDisabledControls}
+              title={'Create snapshot'}
+              className={classes.actionButton}
+            >
+              Create snapshot
+              {snapshots.snapshotDataLoading && (
+                <Spinner size="sm" className={classes.spinner} />
+              )}
+            </Button>}
+            <Button
+              variant="outlined"
+              color="secondary"
+              onClick={reloadClone}
+              disabled={isDisabledControls}
+              title={'Refresh clone information'}
+              className={classes.actionButton}
+            >
+              Reload info
+              {isReloading && <Spinner size="sm" className={classes.spinner} />}
+            </Button>
+          </div>
+          {stores.main.destroyCloneError ||
+            (stores.main.resetCloneError && (
+              <ErrorStub
+                title={'Resetting error'}
+                message={stores.main.resetCloneError}
+                className={classes.errorStub}
+              />
+            ))}
+          {!props.hideBranchingFeatures && <div>
+            <p>
+              <strong>Branch</strong>
+            </p>
+            <p className={classes.text}>{clone.branch}</p>
+          </div>}
+
+          <div className={classes.title}>
             <p>
               <strong>Created</strong>
             </p>
             <p className={classes.text}>{clone.createdAt}</p>
           </div>
-
           <br />
-
           <div>
             <p>
               <strong>Data state at</strong>&nbsp;
@@ -357,9 +405,7 @@ export const Clone = observer((props: Props) => {
             </p>
             <p className={classes.text}>{clone.snapshot?.dataStateAt}</p>
           </div>
-
           <br />
-
           <div>
             <p>
               <strong>Status</strong>
@@ -367,9 +413,7 @@ export const Clone = observer((props: Props) => {
 
             <Status rawClone={clone} className={classes.status} />
           </div>
-
           <br />
-
           <div>
             <p>
               <strong>Summary</strong>&nbsp;
@@ -494,10 +538,10 @@ export const Clone = observer((props: Props) => {
                   <Tooltip
                     content={
                       <>
-                        Used to connect to Postgres using psql. Change DBNAME
-                        to&nbsp; name of the database you want to connect. Use
-                        PGPASSWORD&nbsp; environment variable to set database
-                        password or type&nbsp; it when prompted.
+                        Used to connect to PostgreSQL using psql. Change DBNAME
+                        to the name of the database you want to connect to. Use
+                        the PGPASSWORD environment variable to set the database
+                        password or type it when prompted.
                       </>
                     }
                   >
@@ -540,10 +584,10 @@ export const Clone = observer((props: Props) => {
                   <Tooltip
                     content={
                       <>
-                        Used to connect to Postgres using JDBC. Change DBNAME
-                        to&nbsp; name of the database you want to connect,
-                        change DBPASSWORD&nbsp; to the password you’ve used on
-                        clone creation.
+                        Used to connect to PostgreSQL using JDBC. Change DBNAME
+                        to the name of the database you want to connect to, and
+                        change DBPASSWORD to the password you used when creating
+                        the clone.
                       </>
                     }
                   >
@@ -555,9 +599,7 @@ export const Clone = observer((props: Props) => {
               )}
             </>
           )}
-
           <br />
-
           <div className={classes.fieldBlock}>
             <span className={classes.remark}>
               Password was set during clone creation. It’s not being stored.
@@ -565,13 +607,10 @@ export const Clone = observer((props: Props) => {
               You would need to recreate a clone if the password is lost.
             </span>
           </div>
-
           <br />
-
           <p>
             <strong>Protection</strong>
           </p>
-
           <p>
             <FormControlLabel
               className={classes.checkboxLabel}
@@ -587,15 +626,14 @@ export const Clone = observer((props: Props) => {
             />
             <br />
             <span className={classes.remark}>
-              When enabled no one can delete this clone and automated deletion
+              When enabled, no one can delete this clone and automated deletion
               is also disabled.
               <br />
               Please be careful: abandoned clones with this checkbox enabled may
-              cause out-of-disk-space events. Check disk space on daily basis
-              and delete this clone once the work is done.
+              cause out-of-disk-space events. Check disk space on a daily basis
+              and delete this clone once your work is done.
             </span>
           </p>
-
           {stores.main.updateCloneError && (
             <ErrorStub
               title="Updating error"
@@ -605,6 +643,51 @@ export const Clone = observer((props: Props) => {
           )}
         </div>
 
+        <div className={classes.snippetContainer}>
+          <SectionTitle tag="h2" level={2} text={'Reset clone using CLI'} />
+          <p className={classes.tooltip}>
+            You can reset the clone using CLI using the following command:
+          </p>
+          <SyntaxHighlight content={getCliResetCloneCommand(props.cloneId)} />
+
+          <SectionTitle
+            className={classes.title}
+            tag="h2"
+            level={2}
+            text={'Delete clone using CLI'}
+          />
+          <p className={classes.tooltip}>
+            You can delete the clone using CLI using the following command:
+          </p>
+          <SyntaxHighlight content={getCliDestroyCloneCommand(props.cloneId)} />
+
+          <SectionTitle
+            className={classes.title}
+            tag="h2"
+            level={2}
+            text={'Toggle deletion protection using CLI'}
+          />
+          <p className={classes.tooltip}>
+            You can toggle deletion protection using CLI for this clone using
+            the following command:
+          </p>
+          <SyntaxHighlight content={getCliProtectedCloneCommand(true)} />
+
+          <SyntaxHighlight content={getCliProtectedCloneCommand(false)} />
+
+          <SectionTitle
+            className={classes.title}
+            tag="h2"
+            level={2}
+            text={'Create snapshot for this clone using CLI'}
+          />
+          <p className={classes.tooltip}>
+            You can create a snapshot for this clone using CLI using the
+            following command:
+          </p>
+          <SyntaxHighlight content={getCreateSnapshotCommand(props.cloneId)} />
+        </div>
+
         <>
           <DestroyCloneRestrictionModal
             isOpen={isOpenRestrictionModal}
@@ -623,7 +706,7 @@ export const Clone = observer((props: Props) => {
             isOpen={isOpenResetModal}
             onClose={() => setIsOpenResetModal(false)}
             clone={clone}
-            snapshots={stores.main.snapshots.data}
+            snapshots={snapshots.data}
             onResetClone={resetClone}
             version={instance.state?.engine.version}
           />
diff --git a/ui/packages/shared/pages/Clone/stores/Main.ts b/ui/packages/shared/pages/Clone/stores/Main.ts
index 81dd934e..41f5069e 100644
--- a/ui/packages/shared/pages/Clone/stores/Main.ts
+++ b/ui/packages/shared/pages/Clone/stores/Main.ts
@@ -20,6 +20,7 @@ import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
 import { Instance } from '@postgres.ai/shared/types/api/entities/instance'
 import { checkIsCloneStable } from '@postgres.ai/shared/utils/clone'
 import { getTextFromUnknownApiError } from '@postgres.ai/shared/utils/api'
+import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
 
 const UNSTABLE_CLONE_UPDATE_TIMEOUT = 1000
 
@@ -29,6 +30,7 @@ export type Api = SnapshotsApi & {
   resetClone: ResetClone
   destroyClone: DestroyClone
   updateClone: UpdateClone
+  initWS?: InitWS
 }
 
 type Error = {
diff --git a/ui/packages/shared/pages/Clone/utils/index.ts b/ui/packages/shared/pages/Clone/utils/index.ts
new file mode 100644
index 00000000..f99ade66
--- /dev/null
+++ b/ui/packages/shared/pages/Clone/utils/index.ts
@@ -0,0 +1,15 @@
+export const getCliResetCloneCommand = (cloneId: string) => {
+  return `dblab clone reset ${cloneId ? cloneId : `<CLONE_ID>`}`
+}
+
+export const getCliDestroyCloneCommand = (cloneId: string) => {
+  return `dblab clone destroy ${cloneId ? cloneId : `<CLONE_ID>`}`
+}
+
+export const getCliProtectedCloneCommand = (enabled: boolean) => {
+  return `dblab clone update --protected ${enabled ? '' : 'false'}`
+}
+
+export const getCreateSnapshotCommand = (cloneId: string) => {
+  return `dblab branch snapshot --clone-id ${cloneId}`
+}
diff --git a/ui/packages/shared/pages/Configuration/tooltipText.tsx b/ui/packages/shared/pages/Configuration/tooltipText.tsx
deleted file mode 100644
index 81dcc1a8..00000000
--- a/ui/packages/shared/pages/Configuration/tooltipText.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import styles from './styles.module.scss'
-
-export const tooltipText = {
-  dockerTag: () => (
-    <div>
-      Docker image version - latest ones listed first. If unsure, pick the top
-      one
-    </div>
-  ),
-  dockerImage: () => (
-    <div>
-      Major PostgreSQL version (e.g., "9.6", "15"). For logical provisioning
-      mode, the version used by DBLab doesn't need to match the version used on
-      the source (although, it's recommended). <br />
-      If you need a version that is not listed here, contact support.
-    </div>
-  ),
-  dockerImageType: () => (
-    <div>
-      Docker image used to run all database containers – clones, snapshot
-      preparation containers, sync containers. Although such images are based on
-      traditional Docker images for Postgres, DBLab expects slightly different
-      behavior: e.g., Postgres is not the first process used to start container,
-      so Postgres restarts are possible, they do not trigger container state
-      change. For details, see{' '}
-      <a
-        target={'_blank'}
-        href={'https://postgres.ai/docs/database-lab/supported-databases'}
-        className={styles.externalLink}
-      >
-        the docs
-      </a>
-      .
-    </div>
-  ),
-  sharedBuffers: () => (
-    <div>
-      Defines default buffer pool size of each Postgres instance managed by DBLab.
-      Note, that this amount of RAM is immediately allocated at Postgres startup
-      time. For example, if the machine running DBLab has 32 GiB of RAM, and the
-      value used here is '1GB', then the theoretical limit of the number of
-      clones is 32. Practically, this limit is even lower because some memory is
-      consumed by various other processes. If you need more clones, reduce the
-      value of{' '}
-      <span className={styles.firaCodeFont}>configs.shared_buffers</span>.
-    </div>
-  ),
-  sharedPreloadLibraries: () => (
-    <div>
-      Specifies one or more shared libraries (comma-separated list) to be
-      preloaded at Postgres server start (
-      <a
-        target={'_blank'}
-        href={'https://postgresqlco.nf/doc/en/param/shared_preload_libraries/'}
-        className={styles.externalLink}
-      >
-        details
-      </a>
-      ). If some libraries/extensions are missing, Postgres fails to start, so
-      make sure that <span className={styles.firaCodeFont}>dockerImage</span>{' '}
-      used above contains all the needed extensions.
-    </div>
-  ),
-  host: () => (
-    <div>
-      Hostname/IP of database that will be used as source for data retrieval
-      (full data refresh).
-    </div>
-  ),
-  port: () => (
-    <div>
-      Port of database that will be used as source for data retrieval (full data
-      refresh).
-    </div>
-  ),
-  username: () => (
-    <div>
-      Username used to connect to database that will be used as source for data
-      retrieval (full data refresh).
-    </div>
-  ),
-  password: () => (
-    <div>
-      Password used to connect to database that will be used as source for data
-      retrieval (full data refresh).
-    </div>
-  ),
-  dbname: () => (
-    <div>
-      Database name used to connect to the source to run diagnostics queries.
-      This database is not necesserarily to be copied (another field,{' '}
-      <span className={styles.firaCodeFont}>databases</span>, defines which
-      database to copy).
-    </div>
-  ),
-  databases: () => (
-    <div>
-      Specifies list of databases Postgres server to copy at data retrieval
-      (full data refresh). To specify multiple database names, provide each
-      value in a separte line or use space as a divider. To copy all available
-      databases, leave this value empty.
-    </div>
-  ),
-  dumpParallelJobs: () => (
-    <div>
-      Number of parallel workers used to dump the source databases to disk. If
-      the source is production server under load, it is not recommended to use
-      more than 50% of its number of vCPUs. The higher number, the faster
-      dumping is, but the higher risks of performance issues on the source
-      (e.g., due to CPU or disk IO saturation).
-    </div>
-  ),
-  pgDumpCustomOptions: () => (
-    <div>
-      pg_dump options to be used to create a database dump, for example:
-      '--exclude-schema=repack --exclude-schema="camelStyleSchemaName"'. Note
-      that due to security reasons, the current implementation supports only
-      letters, numbers, hyphen, underscore, equal sign, and double quotes.
-    </div>
-  ),
-  restoreParallelJobs: () => (
-    <div>
-      Number of parallel workers used to restore databases from dump to Postgres
-      managed by DBLab. For initial data retrieval (very first data refresh), it
-      is recommended to use the number of vCPUs available on machine running
-      DBLab. With this approach, we have faster restore time, but we need to keep
-      in mind that we can also have higher usage of CPU and disk IO on this
-      machine (up to temporary saturation of resources). For subsequent
-      refreshes, if DBLab is constantly used, it is recommended to reduce this
-      value by 50% to keep some room for normal use of DBLab (such as work with
-      clones).
-    </div>
-  ),
-  pgRestoreCustomOptions: () => (
-    <div>
-      pg_restore options to be used to restore from a database dump, for
-      example: '--exclude-schema=repack
-      --exclude-schema="camelStyleSchemaName"'. Note that due to security
-      reasons, the current implementation supports only letters, numbers,
-      hyphen, underscore, equal sign, and double quotes.
-    </div>
-  ),
-  timetable: () => (
-    <div>
-      Schedule for full data refreshes, in{' '}
-      <a
-        target={'_blank'}
-        href={'https://en.wikipedia.org/wiki/Cron#Overview'}
-        className={styles.externalLink}
-      >
-        crontab format
-      </a>
-      .
-    </div>
-  ),
-  tuningParams: () => <div>Test</div>,
-}
diff --git a/ui/packages/shared/pages/CreateBranch/index.tsx b/ui/packages/shared/pages/CreateBranch/index.tsx
new file mode 100644
index 00000000..0ae11354
--- /dev/null
+++ b/ui/packages/shared/pages/CreateBranch/index.tsx
@@ -0,0 +1,306 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import cn from 'classnames'
+import { useHistory } from 'react-router'
+import { observer } from 'mobx-react-lite'
+import React, { useEffect, useState } from 'react'
+import { TextField, makeStyles } from '@material-ui/core'
+
+import { Button } from '@postgres.ai/shared/components/Button'
+import { ResponseMessage } from '@postgres.ai/shared/pages/Instance/Configuration/ResponseMessage'
+import { Select } from '@postgres.ai/shared/components/Select'
+import { CreateBranchFormValues } from '@postgres.ai/shared/types/api/endpoints/createBranch'
+import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
+import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
+import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex'
+import { Spinner } from '@postgres.ai/shared/components/Spinner'
+
+import { useForm } from './useForm'
+import { MainStoreApi } from './stores/Main'
+import { useCreatedStores } from './useCreatedStores'
+import { getCliBranchListCommand, getCliCreateBranchCommand } from './utils'
+import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
+import { InstanceTabs, TABS_INDEX } from "../Instance/Tabs";
+
+interface CreateBranchProps {
+  instanceId: string
+  api: MainStoreApi
+  routes: {
+    branch: (branchName: string) => string
+  }
+  elements: {
+    breadcrumbs: React.ReactNode
+  }
+  isPlatform?: boolean
+  hideBranchingFeatures?: boolean
+}
+
+const useStyles = makeStyles(
+  {
+    wrapper: {
+      display: 'flex',
+      gap: '60px',
+      maxWidth: '1200px',
+      fontSize: '14px',
+      marginTop: '20px',
+
+      '@media (max-width: 1300px)': {
+        flexDirection: 'column',
+        gap: '20px',
+      },
+    },
+    container: {
+      maxWidth: '100%',
+      flex: '1 1 0',
+      minWidth: 0,
+
+      '&  p,span': {
+        fontSize: 14,
+      },
+    },
+    snippetContainer: {
+      flex: '1 1 0',
+      minWidth: 0,
+      boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 12px',
+      padding: '10px 20px 10px 20px',
+      height: 'max-content',
+      borderRadius: '4px',
+    },
+    marginBottom: {
+      marginBottom: '8px',
+    },
+    marginBottom2x: {
+      marginBottom: '16px',
+    },
+    marginTop: {
+      marginTop: '8px',
+    },
+    title: {
+      marginTop: '8px',
+      lineHeight: '26px'
+    },
+    form: {
+      marginTop: '16px',
+    },
+    spinner: {
+      marginLeft: '8px',
+      color: '#fff',
+    },
+    snapshotOverflow: {
+      width: '100%',
+      wordWrap: 'break-word',
+      whiteSpace: 'initial',
+    },
+  },
+  { index: 1 },
+)
+
+export const CreateBranchPage = observer(
+  ({ instanceId, api, elements, routes, isPlatform, hideBranchingFeatures }: CreateBranchProps) => {
+    const stores = useCreatedStores(api)
+    const classes = useStyles()
+    const history = useHistory()
+    const [branchSnapshots, setBranchSnapshots] = useState<Snapshot[]>([])
+
+    const {
+      load,
+      branchesList,
+      getBranchesError,
+      createBranch,
+      createBranchError,
+      isBranchesLoading,
+      isCreatingBranch,
+      getSnapshots,
+      snapshotsError,
+    } = stores.main
+
+    const handleSubmit = async (values: CreateBranchFormValues) => {
+      await createBranch({
+        ...values,
+        instanceId,
+      }).then((branch) => {
+        if (branch && branch?.name) {
+          history.push(routes.branch(branch.name))
+        }
+      })
+    }
+
+    const fetchSnapshots = async (branchName: string) => {
+      await getSnapshots(instanceId, branchName).then((response) => {
+        if (response) {
+          setBranchSnapshots(response)
+          formik.setFieldValue('snapshotID', response[0]?.id)
+        }
+      })
+    }
+
+    const handleParentBranchChange = async (
+      e: React.ChangeEvent<HTMLInputElement>,
+    ) => {
+      const branchName = e.target.value
+      formik.setFieldValue('baseBranch', branchName)
+      await fetchSnapshots(branchName)
+    }
+
+    const [{ formik }] = useForm(handleSubmit)
+
+    useEffect(() => {
+      load(instanceId)
+      fetchSnapshots(formik.values.baseBranch)
+    }, [formik.values.baseBranch])
+
+    if (isBranchesLoading) {
+      return <StubSpinner />
+    }
+
+    return (
+      <>
+        {elements.breadcrumbs}
+        <SectionTitle tag="h1" level={1} text="Create branch" className={classes.title}>
+          <InstanceTabs
+            tab={TABS_INDEX.BRANCHES}
+            isPlatform={isPlatform}
+            instanceId={instanceId}
+            hasLogs={api.initWS !== undefined}
+            hideInstanceTabs={hideBranchingFeatures}
+          />
+        </SectionTitle>
+        <div className={classes.wrapper}>
+          <div className={classes.container}>
+            {(snapshotsError || getBranchesError) && (
+              <div className={classes.marginTop}>
+                <ErrorStub
+                  message={snapshotsError?.message || getBranchesError?.message}
+                />
+              </div>
+            )}
+            <div className={classes.form}>
+              <TextField
+                label="Branch name"
+                variant="outlined"
+                required
+                fullWidth
+                size="small"
+                InputLabelProps={{
+                  shrink: true,
+                }}
+                value={formik.values.branchName}
+                error={Boolean(formik.errors.branchName)}
+                helperText={formik.errors.branchName}
+                className={classes.marginBottom}
+                onChange={(e) =>
+                  formik.setFieldValue('branchName', e.target.value)
+                }
+              />
+              <p className={cn(classes.marginTop, classes.marginBottom)}>
+                Choose an existing branch. The new branch will initially point
+                at the same snapshot as the parent branch but going further,
+                their evolution paths will be independent - new snapshots can be
+                created for both branches.
+              </p>
+              <Select
+                fullWidth
+                label="Parent branch"
+                value={formik.values.baseBranch}
+                disabled={!branchesList || formik.isSubmitting}
+                onChange={handleParentBranchChange}
+                error={Boolean(formik.errors.baseBranch)}
+                items={
+                  branchesList
+                    ? branchesList.map((branch) => {
+                        return {
+                          value: branch.name,
+                          children: branch.name,
+                        }
+                      })
+                    : []
+                }
+              />
+              <strong>Snapshot ID</strong>
+              <p className={cn(classes.marginTop, classes.marginBottom)}>
+                Choose an existing snapshot. This snapshot will be memorized as
+                a forking point for the new branch; it cannot be deleted while
+                the branch exists.
+              </p>
+              <Select
+                fullWidth
+                className={classes.marginBottom2x}
+                label="Snapshot ID"
+                value={formik.values.snapshotID}
+                disabled={!branchesList || formik.isSubmitting}
+                onChange={(e) =>
+                  formik.setFieldValue('snapshotID', e.target.value)
+                }
+                error={Boolean(formik.errors.baseBranch)}
+                items={
+                  branchSnapshots
+                    ? branchSnapshots.map((snapshot, i) => {
+                        const isLatest = i === 0
+                        return {
+                          value: snapshot.id,
+                          children: (
+                            <div>
+                              <strong className={classes.snapshotOverflow}>
+                                {snapshot?.id} {isLatest && <span>Latest</span>}
+                              </strong>
+                              {snapshot?.dataStateAt && (
+                                <p>Data state at: {snapshot?.dataStateAt}</p>
+                              )}
+                            </div>
+                          ),
+                        }
+                      })
+                    : []
+                }
+              />
+              <Button
+                variant="primary"
+                size="medium"
+                className={classes.marginTop}
+                onClick={formik.submitForm}
+              >
+                Create branch
+                {isCreatingBranch && (
+                  <Spinner size="sm" className={classes.spinner} />
+                )}
+              </Button>
+              {createBranchError && (
+                <ResponseMessage type={'error'} message={createBranchError} />
+              )}
+            </div>
+          </div>{' '}
+          <div className={classes.snippetContainer}>
+            <SectionTitle tag="h1" level={1} text="The same using CLI" />
+            <p className={classes.marginTop}>
+              Alternatively, you can create a new branch using CLI. Fill the
+              form, copy the command below and paste it into your terminal.
+            </p>
+            <SyntaxHighlight
+              content={getCliCreateBranchCommand(
+                formik.values.branchName,
+                formik.values.baseBranch,
+              )}
+            />
+            <SectionTitle
+              className={classes.marginTop}
+              tag="h2"
+              level={2}
+              text={'Get branches using CLI'}
+            />
+            <p className={classes.marginTop}>
+              You can get a list of all branches using CLI. Copy the command
+              below and paste it into your terminal.
+            </p>
+            <SyntaxHighlight content={getCliBranchListCommand()} />
+          </div>
+        </div>
+      </>
+    )
+  },
+)
diff --git a/ui/packages/shared/pages/CreateBranch/stores/Main.ts b/ui/packages/shared/pages/CreateBranch/stores/Main.ts
new file mode 100644
index 00000000..937a5526
--- /dev/null
+++ b/ui/packages/shared/pages/CreateBranch/stores/Main.ts
@@ -0,0 +1,98 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { makeAutoObservable } from 'mobx'
+
+import { GetBranches } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import {
+  CreateBranch,
+  CreateBranchFormValues,
+} from '@postgres.ai/shared/types/api/endpoints/createBranch'
+import { Branch } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { GetSnapshots } from '@postgres.ai/shared/types/api/endpoints/getSnapshots'
+import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
+
+type Error = {
+  title?: string
+  message: string
+}
+
+export type MainStoreApi = {
+  getBranches: GetBranches
+  createBranch: CreateBranch
+  getSnapshots: GetSnapshots
+  initWS?: InitWS
+}
+
+export class MainStore {
+  snapshotsError: Error | null = null
+  getBranchesError: Error | null = null
+  createBranchError: string | null = null
+
+  isBranchesLoading = false
+  isCreatingBranch = false
+
+  branchesList: Branch[] = []
+  private readonly api: MainStoreApi
+
+  constructor(api: MainStoreApi) {
+    this.api = api
+    makeAutoObservable(this)
+  }
+
+  load = async (instanceId: string) => {
+    await this.getBranches(instanceId).then((response) => {
+      if (response) {
+        this.branchesList = response
+      }
+    })
+  }
+
+  createBranch = async (values: CreateBranchFormValues) => {
+    if (!this.api.createBranch) return
+
+    this.isCreatingBranch = true
+    this.createBranchError = null
+
+    const { response, error } = await this.api.createBranch(values)
+
+    this.isCreatingBranch = false
+
+    if (error)
+      this.createBranchError = await error.json().then((err) => err.details)
+
+    return response
+  }
+
+  getBranches = async (instanceId: string) => {
+    if (!this.api.getBranches) return
+    this.isBranchesLoading = true
+
+    const { response, error } = await this.api.getBranches(instanceId)
+
+    this.isBranchesLoading = false
+
+    if (error) this.getBranchesError = await error.json().then((err) => err)
+
+    return response
+  }
+
+  getSnapshots = async (instanceId: string, branchName?: string) => {
+    if (!this.api.getSnapshots) return
+
+    const { response, error } = await this.api.getSnapshots({
+      instanceId,
+      branchName,
+    })
+
+    if (error) {
+      this.snapshotsError = await error.json().then((err) => err)
+    }
+
+    return response
+  }
+}
diff --git a/ui/packages/shared/pages/CreateBranch/useCreatedStores.ts b/ui/packages/shared/pages/CreateBranch/useCreatedStores.ts
new file mode 100644
index 00000000..304e97f7
--- /dev/null
+++ b/ui/packages/shared/pages/CreateBranch/useCreatedStores.ts
@@ -0,0 +1,11 @@
+import { useMemo } from 'react'
+
+import { MainStore, MainStoreApi } from './stores/Main'
+
+export const useCreatedStores = (api: MainStoreApi) => ({
+  main: useMemo(() => new MainStore(api), []),
+})
+
+export type Stores = ReturnType<typeof useCreatedStores>
+
+export type { MainStoreApi }
diff --git a/ui/packages/shared/pages/CreateBranch/useForm.ts b/ui/packages/shared/pages/CreateBranch/useForm.ts
new file mode 100644
index 00000000..ff14320c
--- /dev/null
+++ b/ui/packages/shared/pages/CreateBranch/useForm.ts
@@ -0,0 +1,60 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { useFormik } from 'formik'
+import { CreateBranchFormValues } from '@postgres.ai/shared/types/api/endpoints/createBranch'
+
+import * as Yup from 'yup'
+
+const Schema = Yup.object().shape({
+  branchName: Yup.string()
+    .required('Branch name is required')
+    .max(255, 'Branch name cannot exceed 255 characters')
+    .test(
+      'valid-zfs-name',
+      'Branch name can only contain letters, numbers, and these characters: _ -',
+      (value) => {
+        if (!value) return true;
+        // ZFS allows only alphanumeric characters and: _ -
+        const validChars = /^[a-zA-Z0-9_-]*$/;
+        return validChars.test(value);
+      }
+    )
+    .test(
+      'valid-start-char',
+      'Branch name must start with a letter, number, or underscore',
+      (value) => {
+        if (!value) return true;
+        // Dataset names can begin with alphanumeric character or underscore
+        const validStartChar = /^[a-zA-Z0-9_]/;
+        return validStartChar.test(value);
+      }
+    ),
+})
+
+export const useForm = (onSubmit: (values: CreateBranchFormValues) => void) => {
+  const formik = useFormik<CreateBranchFormValues>({
+    initialValues: {
+      instanceId: '',
+      branchName: '',
+      baseBranch: 'main',
+      snapshotID: '',
+      creationType: 'branch',
+    },
+    validationSchema: Schema,
+    onSubmit,
+    validateOnBlur: false,
+    validateOnChange: false,
+  })
+
+  const isFormDisabled =
+    formik.isSubmitting ||
+    !formik.values.branchName ||
+    (!formik.values.snapshotID && !formik.values.baseBranch)
+
+  return [{ formik, isFormDisabled }]
+}
diff --git a/ui/packages/shared/pages/CreateBranch/utils/index.ts b/ui/packages/shared/pages/CreateBranch/utils/index.ts
new file mode 100644
index 00000000..4b5c43aa
--- /dev/null
+++ b/ui/packages/shared/pages/CreateBranch/utils/index.ts
@@ -0,0 +1,18 @@
+export const getCliCreateBranchCommand = (
+  branchName: string,
+  parentBranchName: string,
+) => {
+  const branchArg = branchName || `<BRANCH_NAME>`;
+  const parentBranchArg =
+    parentBranchName && parentBranchName !== `main`
+      ? `--parent-branch ${parentBranchName}`
+      : ``;
+
+  return `dblab branch ${branchArg} ${parentBranchArg}`.trim();
+};
+
+
+
+export const getCliBranchListCommand = () => {
+  return `dblab branch`
+}
diff --git a/ui/packages/shared/pages/CreateClone/index.tsx b/ui/packages/shared/pages/CreateClone/index.tsx
index 35f57322..2629f5e0 100644
--- a/ui/packages/shared/pages/CreateClone/index.tsx
+++ b/ui/packages/shared/pages/CreateClone/index.tsx
@@ -1,5 +1,5 @@
 import cn from 'classnames'
-import { useEffect } from 'react'
+import React, { useEffect, useState } from 'react'
 import { useHistory } from 'react-router-dom'
 import { observer } from 'mobx-react-lite'
 import { useTimer } from 'use-timer'
@@ -12,20 +12,24 @@ import { Select } from '@postgres.ai/shared/components/Select'
 import { Button } from '@postgres.ai/shared/components/Button'
 import { Spinner } from '@postgres.ai/shared/components/Spinner'
 import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
-import { compareSnapshotsDesc } from '@postgres.ai/shared/utils/snapshot'
 import { round } from '@postgres.ai/shared/utils/numbers'
 import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
 import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
 import {
   MIN_ENTROPY,
   getEntropy,
   validatePassword,
 } from '@postgres.ai/shared/helpers/getEntropy'
 
+import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
 import { useCreatedStores, MainStoreApi } from './useCreatedStores'
 import { useForm, FormValues } from './useForm'
+import { getCliCloneStatus, getCliCreateCloneCommand } from './utils'
+import { compareSnapshotsDesc } from '@postgres.ai/shared/utils/snapshot'
 
 import styles from './styles.module.scss'
+import { InstanceTabs, TABS_INDEX } from "../Instance/Tabs";
 
 type Host = {
   instanceId: string
@@ -38,13 +42,15 @@ type Host = {
   }
 }
 
-type Props = Host
+type Props = Host & { isPlatform?: boolean, hideBranchingFeatures?: boolean }
 
 export const CreateClone = observer((props: Props) => {
   const history = useHistory()
   const stores = useCreatedStores(props.api)
-  const cloneError = stores.main.cloneError
   const timer = useTimer()
+  const [branchesList, setBranchesList] = useState<string[]>([])
+  const [snapshots, setSnapshots] = useState([] as Snapshot[])
+  const [isLoadingSnapshots, setIsLoadingSnapshots] = useState(false)
 
   // Form.
   const onSubmit = async (values: FormValues) => {
@@ -62,56 +68,103 @@ export const CreateClone = observer((props: Props) => {
 
     formik.setFieldError('dbPassword', '')
 
-    if (!isSuccess || cloneError) {
+    if (!isSuccess || stores.main.cloneError) {
       timer.pause()
       timer.reset()
     }
   }
 
+  const fetchBranchSnapshotsData = async (branchName: string, initialSnapshotId?: string) => {
+    const snapshotsRes = (await stores.main.getSnapshots(props.instanceId, branchName)) ?? []
+    setSnapshots(snapshotsRes)
+
+    const selectedSnapshot = snapshotsRes.find(s => s.id === initialSnapshotId) || snapshotsRes[0]
+
+    formik.setFieldValue('snapshotId', selectedSnapshot?.id)
+  }
+
+  const handleSelectBranch = async (
+    e: React.ChangeEvent<{ value: string }>,
+  ) => {
+    const selectedBranch = e.target.value
+    formik.setFieldValue('branch', selectedBranch)
+
+    if (props.api.getSnapshots) {
+      await fetchBranchSnapshotsData(selectedBranch)
+    }
+  }
+
   const formik = useForm(onSubmit)
 
+  const fetchData = async (initialBranch?: string, initialSnapshotId?: string) => {
+    try {
+      setIsLoadingSnapshots(true)
+      await stores.main.load(props.instanceId)
+
+      const branches = (await stores.main.getBranches(props.instanceId)) ?? []
+
+      let initiallySelectedBranch = branches[0]?.name;
+
+      if (initialBranch && branches.find((branch) => branch.name === initialBranch)) {
+        initiallySelectedBranch = initialBranch;
+      }
+
+      setBranchesList(branches.map((branch) => branch.name))
+      formik.setFieldValue('branch', initiallySelectedBranch)
+
+      if (props.api.getSnapshots) {
+        await fetchBranchSnapshotsData(initiallySelectedBranch, initialSnapshotId)
+      } else {
+        const allSnapshots = stores.main?.snapshots?.data ?? []
+        const sortedSnapshots = allSnapshots.slice().sort(compareSnapshotsDesc)
+        setSnapshots(sortedSnapshots)
+        let selectedSnapshot = allSnapshots.find(s => s.id === initialSnapshotId) || allSnapshots[0]
+        formik.setFieldValue('snapshotId', selectedSnapshot?.id)
+      }
+    } catch (error) {
+      console.error('Error fetching data:', error)
+    } finally {
+      setIsLoadingSnapshots(false)
+    }
+  }
+
   // Initial loading data.
   useEffect(() => {
-    stores.main.load(props.instanceId)
-  }, [])
+    const queryString = history.location.search.split('?')[1]
+    const params = new URLSearchParams(queryString)
+    const branchId = params.get('branch_id') ?? undefined
+    const snapshotId = params.get('snapshot_id') ?? undefined
+
+    fetchData(branchId, snapshotId)
+  }, [history.location.search, formik.initialValues])
 
   // Redirect when clone is created and stable.
   useEffect(() => {
-    if (!stores.main.clone) return
-    if (!stores.main.isCloneStable) return
+    if (!stores.main.clone || !stores.main.isCloneStable) return
 
     history.push(props.routes.clone(stores.main.clone.id))
   }, [stores.main.clone, stores.main.isCloneStable])
 
-  // Snapshots.
-  const sortedSnapshots = stores.main.snapshots.data
-    ?.slice()
-    .sort(compareSnapshotsDesc)
-
-  useEffect(() => {
-    const [firstSnapshot] = sortedSnapshots ?? []
-    if (!firstSnapshot) return
-
-    formik.setFieldValue('snapshotId', firstSnapshot.id)
-  }, [Boolean(sortedSnapshots)])
-
   const headRendered = (
     <>
       {/* //TODO: make global reset styles. */}
       <style>{'p { margin: 0; }'}</style>
 
       {props.elements.breadcrumbs}
-      <SectionTitle
-        className={styles.title}
-        tag="h1"
-        level={1}
-        text="Create clone"
-      />
+      <SectionTitle tag="h1" level={1} text="Create clone"  className={styles.pageTitle}>
+        <InstanceTabs
+          tab={TABS_INDEX.CLONES}
+          isPlatform={props.isPlatform}
+          instanceId={props.instanceId}
+          hasLogs={props.api.initWS !== undefined}
+          hideInstanceTabs={props.hideBranchingFeatures}
+        />
+      </SectionTitle>
     </>
   )
 
   // Initial loading spinner.
-  if (!stores.main.instance || !stores.main.snapshots.data)
+  if (!stores.main.instance || isLoadingSnapshots)
     return (
       <>
         {headRendered}
@@ -120,189 +173,257 @@ export const CreateClone = observer((props: Props) => {
       </>
     )
 
-  // Instance getting error.
-  if (stores.main.instanceError)
+  // Instance/branches getting error.
+  if (
+    stores.main.instanceError ||
+    stores.main.getBranchesError ||
+    stores.main.getSnapshotsError ||
+    stores.main?.snapshots?.error
+  )
     return (
       <>
         {headRendered}
 
-        <ErrorStub message={stores.main.instanceError} />
+        <ErrorStub
+          message={
+            stores.main.instanceError ||
+            stores.main.getBranchesError?.message ||
+            stores.main.getSnapshotsError?.message ||
+            (stores.main?.snapshots?.error as string)
+          }
+        />
       </>
     )
 
-  // Snapshots getting error.
-  if (stores.main.snapshots.error)
-    return <ErrorStub message={stores.main.snapshots.error} />
-
   const isCloneUnstable = Boolean(
     stores.main.clone && !stores.main.isCloneStable,
   )
-  const isCreatingClone = formik.isSubmitting || isCloneUnstable
+
+  const isCreatingClone =
+    (formik.isSubmitting || isCloneUnstable) && !stores.main.cloneError
 
   return (
     <>
       {headRendered}
-
-      <div className={styles.form}>
-        {stores.main.cloneError && (
+      <div className={styles.container}>
+        <div className={styles.form}>
           <div className={styles.section}>
-            <ErrorStub message={stores.main.cloneError} />
-          </div>
-        )}
-
-        <div className={styles.section}>
-          <TextField
-            fullWidth
-            label="Clone ID"
-            value={formik.values.cloneId}
-            onChange={(e) => formik.setFieldValue('cloneId', e.target.value)}
-            error={Boolean(formik.errors.cloneId)}
-            disabled={isCreatingClone}
-          />
-
-          <Select
-            fullWidth
-            label="Data state time *"
-            value={formik.values.snapshotId}
-            disabled={!sortedSnapshots || isCreatingClone}
-            onChange={(e) => formik.setFieldValue('snapshotId', e.target.value)}
-            error={Boolean(formik.errors.snapshotId)}
-            items={
-              sortedSnapshots?.map((snapshot, i) => {
-                const isLatest = i === 0
-                return {
-                  value: snapshot.id,
-                  children: (
-                    <>
-                      {snapshot.dataStateAt}
-                      {isLatest && (
-                        <span className={styles.snapshotTag}>Latest</span>
-                      )}
-                    </>
-                  ),
+            {branchesList && branchesList.length > 0 && (
+              <Select
+                fullWidth
+                label="Branch"
+                value={formik.values.branch}
+                disabled={!branchesList || isCreatingClone}
+                onChange={handleSelectBranch}
+                error={Boolean(formik.errors.branch)}
+                items={
+                  branchesList?.map((snapshot) => {
+                    return {
+                      value: snapshot,
+                      children: snapshot,
+                    }
+                  }) ?? []
                 }
-              }) ?? []
-            }
-          />
-
-          <p className={styles.remark}>
-            By default latest snapshot of database is used. You can select&nbsp;
-            different snapshots if earlier database state is needed
-          </p>
-        </div>
+              />
+            )}
 
-        <div className={styles.section}>
-          <h2 className={styles.title}>Database credentials *</h2>
-
-          <p className={styles.text}>
-            Set custom credentials for the new clone. Save the password in
-            reliable place, it can’t be read later.
-          </p>
-
-          <TextField
-            fullWidth
-            label="Database username *"
-            value={formik.values.dbUser}
-            onChange={(e) => formik.setFieldValue('dbUser', e.target.value)}
-            error={Boolean(formik.errors.dbUser)}
-            disabled={isCreatingClone}
-          />
-
-          <TextField
-            fullWidth
-            label="Database password *"
-            type="password"
-            value={formik.values.dbPassword}
-            onChange={(e) => {
-              formik.setFieldValue('dbPassword', e.target.value)
-              if (formik.errors.dbPassword) {
-                formik.setFieldError('dbPassword', '')
+            <TextField
+              fullWidth
+              label="Clone ID"
+              value={formik.values.cloneId}
+              onChange={(e) => {
+                const sanitizedCloneIdValue = e.target.value.replace(/\s/g, '')
+                formik.setFieldValue('cloneId', sanitizedCloneIdValue)
+              }}
+              error={Boolean(formik.errors.cloneId)}
+              helperText={formik.errors.cloneId}
+              disabled={isCreatingClone}
+            />
+
+            <Select
+              fullWidth
+              label="Snapshot *"
+              value={formik.values.snapshotId}
+              disabled={!snapshots || isCreatingClone}
+              onChange={(e) =>
+                formik.setFieldValue('snapshotId', e.target.value)
               }
-            }}
-            error={Boolean(formik.errors.dbPassword)}
-            disabled={isCreatingClone}
-          />
-          <p
-            className={cn(
-              formik.errors.dbPassword && styles.error,
-              styles.remark,
-            )}
-          >
-            {formik.errors.dbPassword}
-          </p>
-        </div>
+              error={Boolean(formik.errors.snapshotId)}
+              items={
+                snapshots.map((snapshot, i) => {
+                  const isLatest = i === 0
+                  return {
+                    value: snapshot.id,
+                    children: (
+                      <div className={styles.snapshotItem}>
+                        <strong className={styles.snapshotOverflow}>
+                          {snapshot?.id} {isLatest && <span>Latest</span>}
+                        </strong>
+                        {snapshot?.dataStateAt && (
+                          <p>Data state at: {snapshot?.dataStateAt}</p>
+                        )}
+                        {snapshot.message && (
+                          <span>Message: {snapshot.message}</span>
+                        )}
+                      </div>
+                    ),
+                  }
+                }) ?? []
+              }
+            />
 
-        <div className={styles.section}>
-          <Paper className={styles.summary}>
-            <InfoIcon className={styles.summaryIcon} />
-
-            <div className={styles.params}>
-              <p className={styles.param}>
-                <span>Data size:</span>
-                <strong>
-                  {stores.main.instance.state?.dataSize
-                    ? formatBytesIEC(stores.main.instance.state.dataSize)
-                    : '-'}
-                </strong>
-              </p>
-
-              <p className={styles.param}>
-                <span>Expected cloning time:</span>
-                <strong>
-                  {round(
-                    stores.main.instance.state?.cloning
-                      .expectedCloningTime as number,
-                    2,
-                  )}{' '}
-                  s
-                </strong>
-              </p>
-            </div>
-          </Paper>
-        </div>
+            <p className={styles.remark}>
+              By default latest snapshot of database is used. You can
+              select&nbsp; different snapshots if earlier database state is
+              needed.
+            </p>
+          </div>
 
-        <div className={styles.section}>
-          <FormControlLabel
-            label="Enable deletion protection"
-            control={
-              <Checkbox
-                checked={formik.values.isProtected}
-                onChange={(e) =>
-                  formik.setFieldValue('isProtected', e.target.checked)
+          <div className={styles.section}>
+            <h2 className={styles.title}>Database credentials *</h2>
+
+            <p className={styles.text}>
+              Set custom credentials for the new clone. Save the password in
+              reliable place, it can't be read later.
+            </p>
+
+            <TextField
+              fullWidth
+              label="Database username *"
+              value={formik.values.dbUser}
+              onChange={(e) => formik.setFieldValue('dbUser', e.target.value)}
+              error={Boolean(formik.errors.dbUser)}
+              disabled={isCreatingClone}
+            />
+
+            <TextField
+              fullWidth
+              label="Database password *"
+              type="password"
+              value={formik.values.dbPassword}
+              onChange={(e) => {
+                formik.setFieldValue('dbPassword', e.target.value)
+
+                if (formik.errors.dbPassword) {
+                  formik.setFieldError('dbPassword', '')
                 }
-                name="protected"
-                disabled={isCreatingClone}
-              />
-            }
-          />
-
-          <p className={styles.remark}>
-            When enabled no one can delete this clone and automated deletion is
-            also disabled.
-            <br />
-            Please be careful: abandoned clones with this checkbox enabled may
-            cause out-of-disk-space events. Check disk space on daily basis and
-            delete this clone once the work is done.
-          </p>
-        </div>
-
-        <div className={styles.section}>
-          <div className={styles.controls}>
-            <Button
-              onClick={formik.submitForm}
-              variant="primary"
-              size="medium"
-              isDisabled={isCreatingClone}
-            >
-              Create clone
-              {isCreatingClone && (
-                <Spinner size="sm" className={styles.spinner} />
+              }}
+              error={Boolean(formik.errors.dbPassword)}
+              disabled={isCreatingClone}
+            />
+            <p
+              className={cn(
+                formik.errors.dbPassword && styles.error,
+                styles.remark,
               )}
-            </Button>
+            >
+              {formik.errors.dbPassword}
+            </p>
+          </div>
 
-            {isCreatingClone && (
-              <p className={styles.elapsedTime}>Elapsed time: {timer.time} s</p>
+          <div className={styles.form}>
+            <FormControlLabel
+              label="Enable deletion protection"
+              control={
+                <Checkbox
+                  checked={formik.values.isProtected}
+                  onChange={(e) =>
+                    formik.setFieldValue('isProtected', e.target.checked)
+                  }
+                  name="protected"
+                  disabled={isCreatingClone}
+                />
+              }
+            />
+
+            <p className={styles.remark}>
+              When enabled, no one can delete this clone and automated deletion
+              is also disabled.
+              <br />
+              Please be careful: abandoned clones with this checkbox enabled may
+              cause out-of-disk-space events. Check disk space on a daily basis
+              and delete this clone once the work is done.
+            </p>
+          </div>
+
+          <div className={cn(styles.marginBottom, styles.section)}>
+            <Paper className={styles.summary}>
+              <InfoIcon className={styles.summaryIcon} />
+
+              <div className={styles.params}>
+                <p className={styles.param}>
+                  <span>Data size:</span>
+                  <strong>
+                    {stores.main.instance.state?.dataSize
+                      ? formatBytesIEC(stores.main.instance.state.dataSize)
+                      : '-'}
+                  </strong>
+                </p>
+
+                <p className={styles.param}>
+                  <span>Expected cloning time:</span>
+                  <strong>
+                    {round(
+                      stores.main.instance.state?.cloning
+                        .expectedCloningTime as number,
+                      2,
+                    )}{' '}
+                    s
+                  </strong>
+                </p>
+              </div>
+            </Paper>
+            {stores.main.cloneError && (
+              <div className={cn(styles.marginBottom, styles.section)}>
+                <ErrorStub message={stores.main.cloneError} />
+              </div>
             )}
+            <div className={styles.controls}>
+              <Button
+                onClick={formik.submitForm}
+                variant="primary"
+                size="medium"
+                isDisabled={isCreatingClone}
+              >
+                Create clone
+                {isCreatingClone && (
+                  <Spinner size="sm" className={styles.spinner} />
+                )}
+              </Button>
+            </div>
+          </div>
+        </div>
+
+        <div className={styles.form}>
+          <div className={styles.snippetContainer}>
+            <SectionTitle
+              className={styles.title}
+              tag="h1"
+              level={1}
+              text="The same using CLI"
+            />
+            <p className={styles.text}>
+              Alternatively, you can create a new clone using CLI. Fill the
+              form, copy the command below and paste it into your terminal.
+            </p>
+            <SyntaxHighlight
+              wrapLines
+              content={getCliCreateCloneCommand(formik.values)}
+            />
+
+            <SectionTitle
+              className={styles.title}
+              tag="h2"
+              level={2}
+              text="Check clone status"
+            />
+            <p className={styles.text}>
+              To check the status of your newly created clone, use the command
+              below.
+            </p>
+            <SyntaxHighlight
+              content={getCliCloneStatus(formik.values.cloneId)}
+            />
           </div>
         </div>
       </div>
diff --git a/ui/packages/shared/pages/CreateClone/stores/Main.ts b/ui/packages/shared/pages/CreateClone/stores/Main.ts
index b7012695..559aefb9 100644
--- a/ui/packages/shared/pages/CreateClone/stores/Main.ts
+++ b/ui/packages/shared/pages/CreateClone/stores/Main.ts
@@ -5,6 +5,8 @@ import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
 import { GetInstance } from '@postgres.ai/shared/types/api/endpoints/getInstance'
 import { CreateClone } from '@postgres.ai/shared/types/api/endpoints/createClone'
 import { GetClone } from '@postgres.ai/shared/types/api/endpoints/getClone'
+import { GetBranches } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { GetSnapshots } from '@postgres.ai/shared/types/api/endpoints/getSnapshots'
 import {
   SnapshotsStore,
   SnapshotsApi,
@@ -13,6 +15,7 @@ import { getTextFromUnknownApiError } from '@postgres.ai/shared/utils/api'
 import { checkIsCloneStable } from '@postgres.ai/shared/utils/clone'
 
 import { FormValues } from '../useForm'
+import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
 
 const UNSTABLE_CLONE_UPDATE_TIMEOUT = 1000
 
@@ -20,11 +23,16 @@ export type MainStoreApi = SnapshotsApi & {
   getInstance: GetInstance
   createClone: CreateClone
   getClone: GetClone
+  getBranches?: GetBranches
+  getSnapshots?: GetSnapshots
+  initWS?: InitWS
 }
 
 export class MainStore {
   instance: Instance | null = null
   instanceError: string | null = null
+  getBranchesError: Error | null = null
+  getSnapshotsError: Error | null = null
 
   clone: Clone | null = null
   cloneError: string | null = null
@@ -33,13 +41,15 @@ export class MainStore {
 
   private readonly api: MainStoreApi
 
-  readonly snapshots: SnapshotsStore
+  readonly snapshots?: SnapshotsStore
 
   constructor(api: MainStoreApi) {
     makeAutoObservable(this)
 
     this.api = api
-    this.snapshots = new SnapshotsStore(api)
+    if (!api.getSnapshots) {
+      this.snapshots = new SnapshotsStore(api)
+    }
   }
 
   get isCloneStable() {
@@ -50,7 +60,7 @@ export class MainStore {
   load = async (instanceId: string) => {
     const [instance, isLoadedSnapshots] = await Promise.all([
       this.api.getInstance({ instanceId }),
-      this.snapshots.load(instanceId),
+      this.snapshots?.load(instanceId) ?? true,
     ])
 
     if (instance.response) this.instance = instance.response
@@ -83,6 +93,27 @@ export class MainStore {
     return Boolean(response)
   }
 
+  getBranches = async (instanceId: string) => {
+    if (!this.api.getBranches) return
+    const { response, error } = await this.api.getBranches(instanceId)
+
+    if (error) this.getBranchesError = await error.json().then((err) => err)
+
+    return response
+  }
+
+  getSnapshots = async (instanceId: string, branchName?: string) => {
+    if (!this.api.getSnapshots) return
+    const { response, error } = await this.api.getSnapshots({
+      instanceId,
+      branchName,
+    })
+
+    if (error) this.getSnapshotsError = await error.json().then((err) => err)
+
+    return response
+  }
+
   private updateCloneUntilStable = async (args: {
     instanceId: string
     cloneId: string
diff --git a/ui/packages/shared/pages/CreateClone/styles.module.scss b/ui/packages/shared/pages/CreateClone/styles.module.scss
index 0af18996..9b4fed12 100644
--- a/ui/packages/shared/pages/CreateClone/styles.module.scss
+++ b/ui/packages/shared/pages/CreateClone/styles.module.scss
@@ -1,15 +1,41 @@
+.container {
+  display: flex;
+  gap: 60px;
+  margin-top: 20px;
+  max-width: 1200px;
+
+  @media (max-width: 1300px) {
+    flex-direction: column;
+    gap: 20px;
+  }
+}
+
 .title {
   margin-top: 16px;
 }
 
 .form {
-  max-width: 425px;
-  margin-top: 8px;
+  flex: 1 1 0;
+}
+
+.snippetContainer {
+  flex: 1 1 0;
+  min-width: 0;
+  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
+  padding: 0 20px 10px 20px;
+  height: max-content;
+  border-radius: 4px;
+}
+
+.snapshotItem {
+  display: flex;
+  flex-direction: column;
+  padding: 4px 0;
 }
 
 .section {
   & + .section {
-    margin-top: 32px;
+    margin-top: 24px;
   }
 }
 
@@ -22,6 +48,10 @@
   margin-top: 8px;
 }
 
+.marginBottom {
+  margin-bottom: 10px;
+}
+
 .remark {
   font-size: 12px;
 }
@@ -38,8 +68,10 @@
 .summary {
   display: flex;
   align-items: center;
-  background-color: rgba(#808080, 0.15) !important;
+  background-color: rgb(250, 250, 250) !important;
+  box-shadow: none !important;
   padding: 12px;
+  margin: 10px 0 15px 0;
 }
 
 .summaryIcon {
@@ -69,3 +101,14 @@
 .elapsedTime {
   margin-left: 24px;
 }
+
+.snapshotOverflow {
+  width: 100%;
+  word-wrap: break-word;
+  white-space: initial;
+}
+
+.pageTitle {
+  margin-top: 8px;
+  line-height: 26px;
+}
\ No newline at end of file
diff --git a/ui/packages/shared/pages/CreateClone/useForm.ts b/ui/packages/shared/pages/CreateClone/useForm.ts
index a7161e3d..1ab5381e 100644
--- a/ui/packages/shared/pages/CreateClone/useForm.ts
+++ b/ui/packages/shared/pages/CreateClone/useForm.ts
@@ -2,6 +2,7 @@ import { useFormik } from 'formik'
 import * as Yup from 'yup'
 
 export type FormValues = {
+  branch: string
   cloneId: string
   snapshotId: string
   dbUser: string
@@ -10,7 +11,18 @@ export type FormValues = {
 }
 
 const Schema = Yup.object().shape({
-  cloneId: Yup.string(),
+  cloneId: Yup.string()
+    .max(63, 'Clone ID cannot exceed 63 characters')
+    .test(
+      'valid-docker-name',
+      'Clone ID must start with a letter or number and can only contain letters, numbers, underscores, periods, and hyphens',
+      (value) => {
+        if (!value) return true;
+        // Docker container name requirements: start with letter/number, contain only ASCII [a-zA-Z0-9_.-]
+        const validDockerName = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
+        return validDockerName.test(value);
+      }
+    ),
   snapshotId: Yup.string().required('Date state time is required'),
   dbUser: Yup.string().required('Database username is required'),
   dbPassword: Yup.string().required('Database password is required'),
@@ -20,6 +32,7 @@ const Schema = Yup.object().shape({
 export const useForm = (onSubmit: (values: FormValues) => void) => {
   const formik = useFormik<FormValues>({
     initialValues: {
+      branch: '',
       cloneId: '',
       snapshotId: '',
       dbUser: '',
diff --git a/ui/packages/shared/pages/CreateClone/utils/index.ts b/ui/packages/shared/pages/CreateClone/utils/index.ts
new file mode 100644
index 00000000..2b06778f
--- /dev/null
+++ b/ui/packages/shared/pages/CreateClone/utils/index.ts
@@ -0,0 +1,21 @@
+import { FormValues } from '@postgres.ai/shared/pages/CreateClone/useForm'
+
+export const getCliCreateCloneCommand = (values: FormValues) => {
+  const { dbUser, dbPassword, branch, isProtected, cloneId } = values
+
+  return `dblab clone create \
+
+  --username ${dbUser ? dbUser : `<USERNAME>`} \
+
+  --password ${dbPassword ? dbPassword.replace(/./g, '*') : `<PASSWORD>`} \
+
+  ${branch ? `--branch ${branch}` : ``} \
+
+  ${isProtected ? `--protected` : ''} \ 
+
+  --id ${cloneId ? cloneId : `<CLONE_ID>`} \ `
+}
+
+export const getCliCloneStatus = (cloneId: string) => {
+  return `dblab clone status ${cloneId ? cloneId : `<CLONE_ID>`}`
+}
diff --git a/ui/packages/shared/pages/CreateSnapshot/index.tsx b/ui/packages/shared/pages/CreateSnapshot/index.tsx
new file mode 100644
index 00000000..a5f789e7
--- /dev/null
+++ b/ui/packages/shared/pages/CreateSnapshot/index.tsx
@@ -0,0 +1,253 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import React, { useEffect } from 'react'
+import { useHistory } from 'react-router'
+import { observer } from 'mobx-react-lite'
+import { makeStyles } from '@material-ui/core'
+
+import { Button } from '@postgres.ai/shared/components/Button'
+import { ResponseMessage } from '@postgres.ai/shared/pages/Instance/Configuration/ResponseMessage'
+import { Select } from '@postgres.ai/shared/components/Select'
+import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
+import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex'
+import { Spinner } from '@postgres.ai/shared/components/Spinner'
+import { generateSnapshotPageId } from '@postgres.ai/shared/pages/Instance/Snapshots/utils'
+import { TextField } from '@postgres.ai/shared/components/TextField'
+
+import { FormValues, useForm } from './useForm'
+import { MainStoreApi } from './stores/Main'
+import { useCreatedStores } from './useCreatedStores'
+import { getCliCreateSnapshotCommand } from './utils'
+import { InstanceTabs, TABS_INDEX } from "../Instance/Tabs";
+
+interface CreateSnapshotProps {
+  instanceId: string
+  api: MainStoreApi
+  routes: {
+    snapshot: (snapshotId: string) => string
+  }
+  elements: {
+    breadcrumbs: React.ReactNode
+  }
+  isPlatform?: boolean
+  hideBranchingFeatures?: boolean
+}
+
+const useStyles = makeStyles(
+  {
+    wrapper: {
+      display: 'flex',
+      gap: '60px',
+      maxWidth: '1200px',
+      fontSize: '14px',
+      marginTop: '20px',
+
+      '@media (max-width: 1300px)': {
+        flexDirection: 'column',
+        gap: '20px',
+      },
+    },
+    container: {
+      maxWidth: '100%',
+      flex: '1 1 0',
+      minWidth: 0,
+
+      '&  p,span': {
+        fontSize: 14,
+      },
+    },
+    snippetContainer: {
+      flex: '1 1 0',
+      minWidth: 0,
+      boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 12px',
+      padding: '10px 20px 10px 20px',
+      height: 'max-content',
+      borderRadius: '4px',
+    },
+    marginBottom: {
+      marginBottom: '8px',
+    },
+    marginBottom2x: {
+      marginBottom: '16px',
+    },
+    marginTop: {
+      marginTop: '8px',
+    },
+    marginTop2x: {
+      marginTop: '16px',
+    },
+    spinner: {
+      marginLeft: '8px',
+      color: '#fff',
+    },
+    title: {
+      marginTop: '8px',
+      lineHeight: '26px'
+    },
+  },
+  { index: 1 },
+)
+
+export const CreateSnapshotPage = observer(
+  ({ instanceId, api, elements, routes, isPlatform, hideBranchingFeatures }: CreateSnapshotProps) => {
+    const stores = useCreatedStores(api)
+    const classes = useStyles()
+    const history = useHistory()
+
+    const {
+      load,
+      instance,
+      createSnapshot,
+      isCreatingSnapshot,
+      snapshotError,
+    } = stores.main
+
+    const clonesList = instance?.instance?.state?.cloning.clones || []
+
+    const handleSubmit = async (values: FormValues) => {
+      await createSnapshot(values.cloneID, values.message, instanceId).then(
+        (snapshot) => {
+          if (snapshot && generateSnapshotPageId(snapshot.snapshotID)) {
+            history.push(
+              routes.snapshot(
+                generateSnapshotPageId(snapshot.snapshotID) as string,
+              ),
+            )
+          }
+        },
+      )
+    }
+
+    const [{ formik }] = useForm(handleSubmit)
+
+    if (!clonesList) {
+      return <StubSpinner />
+    }
+
+    useEffect(() => {
+      load(instanceId)
+    }, [])
+
+    useEffect(() => {
+      if (!history.location.search) return
+
+      const queryString = history.location.search.split('?')[1]
+
+      if (!queryString) return
+
+      const params = new URLSearchParams(queryString)
+
+      const cloneID = params.get('clone_id')
+
+      if (!cloneID) return
+
+      formik.setFieldValue('cloneID', cloneID)
+
+    }, [history.location.search, formik.initialValues])
+
+    return (
+      <>
+        {elements.breadcrumbs}
+        <SectionTitle tag="h1" level={1} text="Create snapshot" className={classes.title}>
+          <InstanceTabs
+            tab={TABS_INDEX.SNAPSHOTS}
+            isPlatform={isPlatform}
+            instanceId={instanceId}
+            hasLogs={api.initWS !== undefined}
+            hideInstanceTabs={hideBranchingFeatures}
+          />
+        </SectionTitle>
+        <div className={classes.wrapper}>
+          <div className={classes.container}>
+            <div className={classes.marginTop2x}>
+              <strong>Clone ID</strong>
+              <p className={classes.marginTop}>
+                Choose a clone ID from the dropdown below. This will be the
+                starting point for your new snapshot.
+              </p>
+              <Select
+                fullWidth
+                label="Clone ID *"
+                value={formik.values.cloneID}
+                disabled={formik.isSubmitting}
+                className={classes.marginBottom2x}
+                onChange={(e) =>
+                  formik.setFieldValue('cloneID', e.target.value)
+                }
+                error={Boolean(formik.errors.cloneID)}
+                items={
+                  clonesList
+                    ? clonesList.map((clone, i) => {
+                        const isLatest = i === 0
+                        return {
+                          value: clone.id,
+                          children: (
+                            <div>
+                              <strong>
+                                {clone.id} {isLatest && <span>Latest</span>}
+                              </strong>
+                              <p>Created: {clone?.snapshot?.createdAt}</p>
+                              <p>
+                                Data state at: {clone?.snapshot?.dataStateAt}
+                              </p>
+                            </div>
+                          ),
+                        }
+                      })
+                    : []
+                }
+              />
+              <strong>Message</strong>
+              <p className={classes.marginTop}>
+                Message to be added to the snapshot.
+              </p>
+              <TextField
+                label="Message *"
+                fullWidth
+                className={classes.marginBottom2x}
+                value={formik.values.message}
+                error={Boolean(formik.errors.message)}
+                onChange={(e) =>
+                  formik.setFieldValue('message', e.target.value)
+                }
+              />
+              <Button
+                variant="primary"
+                size="medium"
+                className={classes.marginTop}
+                onClick={formik.submitForm}
+              >
+                Create snapshot
+                {isCreatingSnapshot && (
+                  <Spinner size="sm" className={classes.spinner} />
+                )}
+              </Button>
+              {snapshotError && (
+                <ResponseMessage type={'error'} message={snapshotError} />
+              )}
+            </div>
+          </div>{' '}
+          <div className={classes.snippetContainer}>
+            <SectionTitle tag="h1" level={1} text="The same using CLI" />
+            <p className={classes.marginTop}>
+              Alternatively, you can create a new snapshot using CLI. Fill the
+              form, copy the command below and paste it into your terminal.
+            </p>
+            <SyntaxHighlight
+              content={getCliCreateSnapshotCommand(
+                formik.values.cloneID,
+                formik.values.message,
+              )}
+            />
+          </div>
+        </div>
+      </>
+    )
+  },
+)
diff --git a/ui/packages/shared/pages/CreateSnapshot/stores/Main.ts b/ui/packages/shared/pages/CreateSnapshot/stores/Main.ts
new file mode 100644
index 00000000..d24474fc
--- /dev/null
+++ b/ui/packages/shared/pages/CreateSnapshot/stores/Main.ts
@@ -0,0 +1,70 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { makeAutoObservable } from 'mobx'
+
+import { CreateSnapshot } from '@postgres.ai/shared/types/api/endpoints/createSnapshot'
+import {
+  MainStore as InstanceStore,
+  Api as InstanceStoreApi,
+} from '@postgres.ai/shared/pages/Instance/stores/Main'
+import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
+
+type Error = {
+  title?: string
+  message: string
+}
+
+export type MainStoreApi = InstanceStoreApi & {
+  createSnapshot: CreateSnapshot
+  initWS?: InitWS
+}
+
+export class MainStore {
+  snapshotError: Error | null = null
+
+  isCreatingSnapshot = false
+
+  readonly instance: InstanceStore
+
+  private readonly api: MainStoreApi
+
+  constructor(api: MainStoreApi) {
+    this.api = api
+    this.instance = new InstanceStore(api)
+
+    makeAutoObservable(this)
+  }
+
+  load = async (instanceId: string) => {
+    this.instance.load(instanceId)
+  }
+
+  createSnapshot = async (
+    cloneID: string,
+    message: string,
+    instanceId: string,
+  ) => {
+    if (!this.api.createSnapshot) return
+
+    this.snapshotError = null
+    this.isCreatingSnapshot = true
+
+    const { response, error } = await this.api.createSnapshot(
+      cloneID,
+      message,
+      instanceId,
+    )
+
+    this.isCreatingSnapshot = false
+
+    if (error)
+      this.snapshotError = await error.json().then((err) => err.message)
+
+    return response
+  }
+}
diff --git a/ui/packages/shared/pages/CreateSnapshot/useCreatedStores.ts b/ui/packages/shared/pages/CreateSnapshot/useCreatedStores.ts
new file mode 100644
index 00000000..304e97f7
--- /dev/null
+++ b/ui/packages/shared/pages/CreateSnapshot/useCreatedStores.ts
@@ -0,0 +1,11 @@
+import { useMemo } from 'react'
+
+import { MainStore, MainStoreApi } from './stores/Main'
+
+export const useCreatedStores = (api: MainStoreApi) => ({
+  main: useMemo(() => new MainStore(api), []),
+})
+
+export type Stores = ReturnType<typeof useCreatedStores>
+
+export type { MainStoreApi }
diff --git a/ui/packages/shared/pages/CreateSnapshot/useForm.ts b/ui/packages/shared/pages/CreateSnapshot/useForm.ts
new file mode 100644
index 00000000..25a99fcc
--- /dev/null
+++ b/ui/packages/shared/pages/CreateSnapshot/useForm.ts
@@ -0,0 +1,34 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { useFormik } from 'formik'
+import * as Yup from 'yup'
+
+export type FormValues = {
+  cloneID: string
+  message: string
+}
+
+const Schema = Yup.object().shape({
+  cloneID: Yup.string().required('Clone ID is required'),
+  message: Yup.string().required('Message is required'),
+})
+
+export const useForm = (onSubmit: (values: FormValues) => void) => {
+  const formik = useFormik<FormValues>({
+    initialValues: {
+      cloneID: '',
+      message: '',
+    },
+    validationSchema: Schema,
+    onSubmit,
+    validateOnBlur: false,
+    validateOnChange: false,
+  })
+
+  return [{ formik }]
+}
diff --git a/ui/packages/shared/pages/CreateSnapshot/utils/index.ts b/ui/packages/shared/pages/CreateSnapshot/utils/index.ts
new file mode 100644
index 00000000..ff091898
--- /dev/null
+++ b/ui/packages/shared/pages/CreateSnapshot/utils/index.ts
@@ -0,0 +1,8 @@
+export const getCliCreateSnapshotCommand = (
+  cloneID: string,
+  message: string,
+) => {
+  return `dblab commit --clone-id ${cloneID || '<CLONE_ID>'} --message ${
+    message || '<MESSAGE>'
+  }`
+}
diff --git a/ui/packages/shared/pages/Instance/components/ClonesList/ConnectionModal/index.tsx b/ui/packages/shared/pages/Instance/Clones/ClonesList/ConnectionModal/index.tsx
similarity index 88%
rename from ui/packages/shared/pages/Instance/components/ClonesList/ConnectionModal/index.tsx
rename to ui/packages/shared/pages/Instance/Clones/ClonesList/ConnectionModal/index.tsx
index b7912011..6e7d2497 100644
--- a/ui/packages/shared/pages/Instance/components/ClonesList/ConnectionModal/index.tsx
+++ b/ui/packages/shared/pages/Instance/Clones/ClonesList/ConnectionModal/index.tsx
@@ -115,7 +115,7 @@ export const ConnectionModal = observer((props: Props) => {
 
         {psqlConnectionStr && (
           <div className={classes.item}>
-            <p className={classes.fieldText}>Connect to Postgres using psql:</p>
+            <p className={classes.fieldText}>Connect to PostgreSQL using psql:</p>
             <div className={classes.fieldWrapper}>
               <TextField
                 label="psql connection string"
@@ -136,10 +136,10 @@ export const ConnectionModal = observer((props: Props) => {
               <Tooltip
                 content={
                   <>
-                    Used to connect to Postgres using psql. Change DBNAME
-                    to&nbsp; name of the database you want to connect. Use
-                    PGPASSWORD&nbsp; environment variable to set database
-                    password or type&nbsp; it when prompted.
+                    Used to connect to PostgreSQL using psql. Change DBNAME
+                    to the name of the database you want to connect to. Use
+                    the PGPASSWORD environment variable to set the database
+                    password or type it when prompted.
                   </>
                 }
               >
@@ -151,7 +151,7 @@ export const ConnectionModal = observer((props: Props) => {
 
         {jdbcConnectionStr && (
           <div className={classes.item}>
-            <p className={classes.fieldText}>Connect to Postgres using JDBC:</p>
+            <p className={classes.fieldText}>Connect to PostgreSQL using JDBC:</p>
             <div className={classes.fieldWrapper}>
               <TextField
                 label="JDBC connection string"
@@ -172,10 +172,10 @@ export const ConnectionModal = observer((props: Props) => {
               <Tooltip
                 content={
                   <>
-                    Used to connect to Postgres using JDBC. Change DBNAME
-                    to&nbsp; name of the database you want to connect, change
-                    DBPASSWORD&nbsp; to the password you’ve used on clone
-                    creation.
+                    Used to connect to PostgreSQL using JDBC. Change DBNAME
+                    to the name of the database you want to connect to, and
+                    change DBPASSWORD to the password you used when creating
+                    the clone.
                   </>
                 }
               >
diff --git a/ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/index.tsx b/ui/packages/shared/pages/Instance/Clones/ClonesList/MenuCell/index.tsx
similarity index 98%
rename from ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/index.tsx
rename to ui/packages/shared/pages/Instance/Clones/ClonesList/MenuCell/index.tsx
index ed0578ac..bb8fced4 100644
--- a/ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/index.tsx
+++ b/ui/packages/shared/pages/Instance/Clones/ClonesList/MenuCell/index.tsx
@@ -61,7 +61,7 @@ export const MenuCell = observer((props: Props) => {
           onClick: () => onConnect(clone.id),
         },
         {
-          name: 'Destroy clone',
+          name: 'Delete clone',
           onClick: handleClickDestroyClone,
         },
         {
diff --git a/ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/utils.ts b/ui/packages/shared/pages/Instance/Clones/ClonesList/MenuCell/utils.ts
similarity index 84%
rename from ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/utils.ts
rename to ui/packages/shared/pages/Instance/Clones/ClonesList/MenuCell/utils.ts
index 610619af..8cd1ec75 100644
--- a/ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/utils.ts
+++ b/ui/packages/shared/pages/Instance/Clones/ClonesList/MenuCell/utils.ts
@@ -6,7 +6,7 @@
  */
 
 export const destroyRestriction = (cloneId: string) => {
-  const message = `The clone "${cloneId}" is marked as protected. To destroy it, disable the destroy protection first.`
+  const message = `The clone "${cloneId}" is marked as protected. To delete it, disable the delete protection first.`
   window.alert(message)
 }
 
@@ -16,6 +16,6 @@ export const getResetApprove = (cloneId: string) => {
 }
 
 export const getDestroyApprove = (cloneId: string) => {
-  const message = `Are you sure you want to destroy the Database Lab clone: "${cloneId}"?`
+  const message = `Are you sure you want to delete the Database Lab clone: "${cloneId}"?`
   return window.confirm(message)
 }
diff --git a/ui/packages/shared/pages/Instance/components/ClonesList/index.tsx b/ui/packages/shared/pages/Instance/Clones/ClonesList/index.tsx
similarity index 68%
rename from ui/packages/shared/pages/Instance/components/ClonesList/index.tsx
rename to ui/packages/shared/pages/Instance/Clones/ClonesList/index.tsx
index e93270a7..4415aaa2 100644
--- a/ui/packages/shared/pages/Instance/components/ClonesList/index.tsx
+++ b/ui/packages/shared/pages/Instance/Clones/ClonesList/index.tsx
@@ -32,6 +32,7 @@ import {
   getCloneStatusText,
 } from '@postgres.ai/shared/utils/clone'
 import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
+import { isValidDate } from '@postgres.ai/shared/utils/date'
 
 import { MenuCell } from './MenuCell'
 import { ConnectionModal } from './ConnectionModal'
@@ -42,12 +43,19 @@ type Props = {
   clones?: Clone[]
   isDisabled: boolean
   emptyStubText: string
+  hideBranchingFeatures?: boolean
 }
 
 export const ClonesList = (props: Props) => {
   const host = useHost()
   const history = useHistory()
 
+  const [state, setState] = useState({
+    sortByBranch: 'desc',
+    sortByCreated: 'desc',
+    clones: props.clones ?? [],
+  })
+
   const [cloneIdForConnect, setCloneIdForConnect] = useState<null | string>(
     null,
   )
@@ -62,7 +70,49 @@ export const ClonesList = (props: Props) => {
     setIsOpenConnectionModal(false)
   }
 
-  if (!props.clones?.length)
+  const handleSortByBranch = () => {
+    const sortByBranch = state.sortByBranch === 'desc' ? 'asc' : 'desc'
+
+    const sortedClones = [...state.clones].sort((a, b) => {
+      if (sortByBranch === 'asc') {
+        return a.branch.localeCompare(b.branch)
+      } else {
+        return b.branch.localeCompare(a.branch)
+      }
+    })
+
+    setState({
+      ...state,
+      sortByBranch,
+      clones: sortedClones,
+    })
+  }
+
+  const handleSortByCreated = () => {
+    const sortByCreated = state.sortByCreated === 'desc' ? 'asc' : 'desc'
+
+    const sortedClones = [...state.clones].sort((a, b) => {
+      if (sortByCreated === 'asc') {
+        return (
+          new Date(a.createdAtDate).getTime() -
+          new Date(b.createdAtDate).getTime()
+        )
+      } else {
+        return (
+          new Date(b.createdAtDate).getTime() -
+          new Date(a.createdAtDate).getTime()
+        )
+      }
+    })
+
+    setState({
+      ...state,
+      sortByCreated,
+      clones: sortedClones,
+    })
+  }
+
+  if (!state.clones?.length)
     return <p className={styles.emptyStub}>{props.emptyStubText}</p>
 
   return (
@@ -74,6 +124,21 @@ export const ClonesList = (props: Props) => {
               <TableHeaderCell />
               <TableHeaderCell>Status</TableHeaderCell>
               <TableHeaderCell>ID</TableHeaderCell>
+              {!props.hideBranchingFeatures && <TableHeaderCell>
+                <div
+                  onClick={handleSortByBranch}
+                  className={cn(styles.interactiveRow, styles.verticalCentered)}
+                >
+                  Branch
+                  <ArrowDropDownIcon
+                    className={cn(
+                      state.sortByCreated === 'asc' && styles.hideSortIcon,
+                      state.sortByBranch === 'asc' && styles.sortIconUp,
+                      styles.sortIcon,
+                    )}
+                  />
+                </div>
+              </TableHeaderCell>}
               <TableHeaderCell>
                 <Tooltip content="When enabled, neither manual nor automated deletion of this clone is possible. Note that abandoned protected clones may lead to out-of-disk-space events because they hold old data, blocking cleanup and refresh processes.">
                   <div className={styles.verticalCentered}>
@@ -83,9 +148,18 @@ export const ClonesList = (props: Props) => {
                 </Tooltip>
               </TableHeaderCell>
               <TableHeaderCell>
-                <div className={styles.verticalCentered}>
+                <div
+                  onClick={handleSortByCreated}
+                  className={cn(styles.interactiveRow, styles.verticalCentered)}
+                >
                   Created
-                  <ArrowDropDownIcon className={styles.sortIcon} />
+                  <ArrowDropDownIcon
+                    className={cn(
+                      state.sortByBranch === 'asc' && styles.hideSortIcon,
+                      state.sortByCreated === 'asc' && styles.sortIconUp,
+                      styles.sortIcon,
+                    )}
+                  />
                 </div>
               </TableHeaderCell>
               <TableHeaderCell>Port</TableHeaderCell>
@@ -103,7 +177,7 @@ export const ClonesList = (props: Props) => {
             </TableRow>
           </TableHead>
           <TableBody>
-            {props.clones.map((clone) => {
+            {state.clones.map((clone) => {
               const clonePagePath = host.routes.clone(clone.id)
 
               return (
@@ -130,6 +204,7 @@ export const ClonesList = (props: Props) => {
                     </Tooltip>
                   </TableBodyCell>
                   <TableBodyCell>{clone.id}</TableBodyCell>
+                  {!props.hideBranchingFeatures && <TableBodyCell>{clone.branch}</TableBodyCell>}
                   <TableBodyCell>
                     {clone.protected ? (
                       <Tooltip content="Clone is protected from manual and automated deletion. Note that abandoned protected clones may lead to out-of-disk-space events because they hold old data, blocking cleanup and refresh processes.">
@@ -143,9 +218,11 @@ export const ClonesList = (props: Props) => {
                   </TableBodyCell>
                   <TableBodyCell>
                     {clone.createdAt} (
-                    {formatDistanceToNowStrict(clone.createdAtDate, {
-                      addSuffix: true,
-                    })}
+                    {isValidDate(clone.createdAtDate)
+                      ? formatDistanceToNowStrict(clone.createdAtDate, {
+                          addSuffix: true,
+                        })
+                      : '-'}
                     )
                   </TableBodyCell>
                   <TableBodyCell>{clone.db.port}</TableBodyCell>
@@ -158,12 +235,14 @@ export const ClonesList = (props: Props) => {
                     {clone.snapshot ? (
                       <>
                         {clone.snapshot.dataStateAt} (
-                        {formatDistanceToNowStrict(
-                          clone.snapshot.dataStateAtDate,
-                          {
-                            addSuffix: true,
-                          },
-                        )}
+                        {isValidDate(clone.snapshot.dataStateAtDate)
+                          ? formatDistanceToNowStrict(
+                              clone.snapshot.dataStateAtDate,
+                              {
+                                addSuffix: true,
+                              },
+                            )
+                          : '-'}
                         )
                       </>
                     ) : (
diff --git a/ui/packages/shared/pages/Instance/components/ClonesList/styles.module.scss b/ui/packages/shared/pages/Instance/Clones/ClonesList/styles.module.scss
similarity index 74%
rename from ui/packages/shared/pages/Instance/components/ClonesList/styles.module.scss
rename to ui/packages/shared/pages/Instance/Clones/ClonesList/styles.module.scss
index 52cc53cc..f7bfa0e8 100644
--- a/ui/packages/shared/pages/Instance/components/ClonesList/styles.module.scss
+++ b/ui/packages/shared/pages/Instance/Clones/ClonesList/styles.module.scss
@@ -18,6 +18,16 @@
 .sortIcon {
   margin-left: 8px;
   width: 10px;
+  cursor: pointer;
+  transition: transform 0.15s ease-in-out;
+}
+
+.hideSortIcon {
+  opacity: 0;
+}
+
+.sortIconUp {
+  transform: rotate(180deg);
 }
 
 .protectionIcon {
diff --git a/ui/packages/shared/pages/Instance/ClonesModal/index.tsx b/ui/packages/shared/pages/Instance/Clones/ClonesModal/index.tsx
similarity index 91%
rename from ui/packages/shared/pages/Instance/ClonesModal/index.tsx
rename to ui/packages/shared/pages/Instance/Clones/ClonesModal/index.tsx
index 67931faf..49baa0e7 100644
--- a/ui/packages/shared/pages/Instance/ClonesModal/index.tsx
+++ b/ui/packages/shared/pages/Instance/Clones/ClonesModal/index.tsx
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'
 
 import { useStores } from '@postgres.ai/shared/pages/Instance/context'
 import { Modal } from '@postgres.ai/shared/components/Modal'
-import { ClonesList } from '@postgres.ai/shared/pages/Instance/components/ClonesList'
+import { ClonesList } from '@postgres.ai/shared/pages/Instance/Clones/ClonesList'
 import { Tags } from '@postgres.ai/shared/pages/Instance/components/Tags'
 import { ModalReloadButton } from '@postgres.ai/shared/pages/Instance/components/ModalReloadButton'
 
@@ -36,7 +36,7 @@ export const ClonesModal = observer(() => {
 
   return (
     <Modal
-      title={`Clones (${instance.state?.cloning.clones.length})`}
+      title={`Clones (${instance.state?.cloning.clones?.length})`}
       isOpen={isOpenModal}
       onClose={closeModal}
       size="md"
@@ -64,7 +64,7 @@ export const ClonesModal = observer(() => {
             !snapshotId || snapshotId === clone.snapshot?.id
           return isMatchedByPool && isMatchedBySnapshot
         })}
-        emptyStubText="No clones found"
+        emptyStubText="No clones found."
       />
     </Modal>
   )
diff --git a/ui/packages/shared/pages/Instance/ClonesModal/utils.ts b/ui/packages/shared/pages/Instance/Clones/ClonesModal/utils.ts
similarity index 100%
rename from ui/packages/shared/pages/Instance/ClonesModal/utils.ts
rename to ui/packages/shared/pages/Instance/Clones/ClonesModal/utils.ts
diff --git a/ui/packages/shared/pages/Instance/Clones/Header/styles.module.scss b/ui/packages/shared/pages/Instance/Clones/Header/styles.module.scss
index c0d3a84e..7acb0d01 100644
--- a/ui/packages/shared/pages/Instance/Clones/Header/styles.module.scss
+++ b/ui/packages/shared/pages/Instance/Clones/Header/styles.module.scss
@@ -4,8 +4,11 @@
   display: flex;
   justify-content: space-between;
   padding: 20px;
+  margin-bottom: 20px;
+  background-color: #f9f9f9;
 
   @include sm {
     padding: 20px 0;
+    margin-bottom: 10px;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/packages/shared/pages/Instance/Clones/index.tsx b/ui/packages/shared/pages/Instance/Clones/index.tsx
index 15faf881..de1cfc51 100644
--- a/ui/packages/shared/pages/Instance/Clones/index.tsx
+++ b/ui/packages/shared/pages/Instance/Clones/index.tsx
@@ -18,12 +18,16 @@ import { InfoIcon } from '@postgres.ai/shared/icons/Info'
 
 import { useStores, useHost } from '@postgres.ai/shared/pages/Instance/context'
 
-import { ClonesList } from '../components/ClonesList'
-
+import { ClonesList } from './ClonesList'
 import { Header } from './Header'
 
 const SHORT_LIST_SIZE = 3
 
+interface ClonesProps {
+  onlyRenderList?: boolean
+  hideBranchingFeatures?: boolean
+}
+
 const useStyles = makeStyles(
   (theme) => ({
     root: {
@@ -37,9 +41,6 @@ const useStyles = makeStyles(
         marginRight: 0,
       },
     },
-    tableTitle: {
-      marginTop: '5px',
-    },
     listSizeButton: {
       marginTop: '12px',
     },
@@ -53,7 +54,8 @@ const useStyles = makeStyles(
   { index: 1 },
 )
 
-export const Clones = observer(() => {
+export const Clones = observer((props: ClonesProps) => {
+  const onlyRenderList = props?.onlyRenderList
   const classes = useStyles()
   const history = useHistory()
   const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('sm'))
@@ -65,7 +67,7 @@ export const Clones = observer(() => {
   const { instance } = stores.main
   if (!instance || !instance.state) return null
 
-  const isShortList = isMobile && isShortListForMobile
+  const isShortList = isMobile && isShortListForMobile && !onlyRenderList
   const toggleListSize = () => setIsShortListForMobile(!isShortListForMobile)
 
   const goToCloneAddPage = () => history.push(host.routes.createClone())
@@ -79,19 +81,20 @@ export const Clones = observer(() => {
 
   return (
     <div className={classes.root}>
-      <SectionTitle level={2} tag="h2" text="Cloning summary" />
-
-      <Header
-        expectedCloningTimeS={round(
-          instance.state.cloning.expectedCloningTime,
-          2,
-        )}
-        logicalSize={instance.state.dataSize}
-        clonesCount={instance.state.cloning.clones.length}
-      />
-
+      {!onlyRenderList && (
+        <>
+          <SectionTitle level={2} tag="h2" text="Cloning summary" />
+          <Header
+            expectedCloningTimeS={round(
+              instance.state.cloning.expectedCloningTime,
+              2,
+            )}
+            logicalSize={instance.state.dataSize}
+            clonesCount={instance.state.cloning.clones.length}
+          />
+        </>
+      )}
       <SectionTitle
-        className={classes.tableTitle}
         level={2}
         tag="h3"
         text={`Clones (${instance.state.cloning.clones.length})`}
@@ -108,7 +111,9 @@ export const Clones = observer(() => {
 
             {!hasSnapshots && (
               <Tooltip content="No snapshots">
-                <InfoIcon className={classes.infoIcon} />
+                <div style={{ display: 'flex' }}>
+                  <InfoIcon className={classes.infoIcon} />
+                </div>
               </Tooltip>
             )}
           </>
@@ -122,10 +127,11 @@ export const Clones = observer(() => {
             : instance.state.cloning.clones
         }
         isDisabled={stores.main.isDisabledInstance}
-        emptyStubText="This instance has no active clones"
+        emptyStubText="This instance has no active clones."
+        hideBranchingFeatures={props.hideBranchingFeatures}
       />
 
-      {showListSizeButton && (
+      {showListSizeButton && !onlyRenderList && (
         <Button className={classes.listSizeButton} onClick={toggleListSize}>
           {isShortList ? 'Show more' : 'Show less'}
         </Button>
diff --git a/ui/packages/shared/pages/Configuration/Header/index.tsx b/ui/packages/shared/pages/Instance/Configuration/Header/index.tsx
similarity index 100%
rename from ui/packages/shared/pages/Configuration/Header/index.tsx
rename to ui/packages/shared/pages/Instance/Configuration/Header/index.tsx
diff --git a/ui/packages/shared/pages/Configuration/InputWithTooltip/index.tsx b/ui/packages/shared/pages/Instance/Configuration/InputWithTooltip/index.tsx
similarity index 89%
rename from ui/packages/shared/pages/Configuration/InputWithTooltip/index.tsx
rename to ui/packages/shared/pages/Instance/Configuration/InputWithTooltip/index.tsx
index fc906a03..fadd0f81 100644
--- a/ui/packages/shared/pages/Configuration/InputWithTooltip/index.tsx
+++ b/ui/packages/shared/pages/Instance/Configuration/InputWithTooltip/index.tsx
@@ -15,7 +15,7 @@ const useStyles = makeStyles(
   {
     textField: {
       '& .MuiOutlinedInput-notchedOutline': {
-        borderColor: '#000',
+        borderColor: '#000 !important',
       },
     },
     selectField: {
@@ -34,6 +34,9 @@ const useStyles = makeStyles(
     error: {
       color: '#f44336',
     },
+    infoIcon: {
+      position: 'relative',
+    },
     absoluteSpinner: {
       position: 'absolute',
       left: 'calc(50% - 40px)',
@@ -92,7 +95,9 @@ export const InputWithTooltip = ({
           spellCheck={false}
         />
         <Tooltip interactive content={<p>{tooltipText()}</p>}>
-          <InfoIcon className={styles.infoIcon} />
+          <div className={styles.infoIcon}>
+            <InfoIcon />
+          </div>
         </Tooltip>
       </Box>
     </Box>
@@ -123,17 +128,8 @@ export const InputWithChip = ({
   const classes = useStyles()
 
   return (
-    <Box
-      mt={2}
-      mb={1}
-      display="flex"
-      flexDirection="column"
-      justifyContent="flex-start"
-      alignItems="flex-start"
-      gap="5px"
-    >
-      <label className={classes.label}>{label}</label>
-      <Box display="flex" alignItems="center" width="100%">
+    <Box mt={2} mb={2}>
+      <Box display="flex" alignItems="center">
         <TextField
           className={classNames(
             !disabled && classes.textField,
@@ -144,6 +140,7 @@ export const InputWithChip = ({
           value={value}
           multiline
           disabled={disabled}
+          label={label}
           inputProps={{
             name: id,
             id: id,
@@ -153,12 +150,14 @@ export const InputWithChip = ({
           }}
         />
         <Tooltip interactive content={<p>{tooltipText()}</p>}>
-          <InfoIcon className={styles.infoIcon} />
+          <div className={styles.infoIcon}>
+            <InfoIcon />
+          </div>
         </Tooltip>
       </Box>
-      {value && (
-        <div className={styles.chipContainer}>
-          {uniqueChipValue(value)
+      <div className={styles.chipContainer}>
+        {value &&
+          uniqueChipValue(value)
             .split(' ')
             .map((uniqueValue, index) => {
               if (uniqueValue !== '') {
@@ -176,8 +175,7 @@ export const InputWithChip = ({
                 )
               }
             })}
-        </div>
-      )}
+      </div>
     </Box>
   )
 }
@@ -224,7 +222,7 @@ export const SelectWithTooltip = ({
             !disabled && classes.textField,
             styles.textField,
           )}
-          label=""
+          label={''}
           error={error}
           value={value}
           disabled={disabled}
@@ -232,7 +230,9 @@ export const SelectWithTooltip = ({
           items={items}
         />
         <Tooltip interactive content={<p>{tooltipText()}</p>}>
-          <InfoIcon className={styles.infoIcon} />
+          <div className={styles.infoIcon}>
+            <InfoIcon />
+          </div>
         </Tooltip>
       </Box>
     </Box>
diff --git a/ui/packages/shared/pages/Configuration/ResponseMessage/index.tsx b/ui/packages/shared/pages/Instance/Configuration/ResponseMessage/index.tsx
similarity index 100%
rename from ui/packages/shared/pages/Configuration/ResponseMessage/index.tsx
rename to ui/packages/shared/pages/Instance/Configuration/ResponseMessage/index.tsx
diff --git a/ui/packages/shared/pages/Configuration/configOptions.ts b/ui/packages/shared/pages/Instance/Configuration/configOptions.ts
similarity index 75%
rename from ui/packages/shared/pages/Configuration/configOptions.ts
rename to ui/packages/shared/pages/Instance/Configuration/configOptions.ts
index d155872c..f5dddb73 100644
--- a/ui/packages/shared/pages/Configuration/configOptions.ts
+++ b/ui/packages/shared/pages/Instance/Configuration/configOptions.ts
@@ -1,13 +1,13 @@
 export const dockerImageOptions = [
   {
-    name: 'Generic Postgres (postgresai/extended-postgres)',
+    name: 'Generic PostgreSQL (postgresai/extended-postgres)',
     type: 'Generic Postgres',
   },
-  { name: 'Generic Postgres with PostGIS', type: 'postgis' },
-  { name: 'Amazon RDS for Postgres', type: 'rds' },
-  { name: 'Amazon RDS Aurora for Postgres', type: 'aurora' },
-  { name: 'Heroku Postgres', type: 'heroku' },
-  { name: 'Supabase Postgres', type: 'supabase' },
+  { name: 'Generic PostgreSQL with PostGIS', type: 'postgis' },
+  { name: 'Amazon RDS for PostgreSQL', type: 'rds' },
+  { name: 'Amazon RDS Aurora for PostgreSQL', type: 'aurora' },
+  { name: 'Heroku PostgreSQL', type: 'heroku' },
+  { name: 'Supabase PostgreSQL', type: 'supabase' },
   { name: 'Google Cloud SQL for PostgreSQL', type: 'google-cloud-sql' },
   {
     name: 'Timescale Cloud',
diff --git a/ui/packages/shared/pages/Configuration/index.tsx b/ui/packages/shared/pages/Instance/Configuration/index.tsx
similarity index 89%
rename from ui/packages/shared/pages/Configuration/index.tsx
rename to ui/packages/shared/pages/Instance/Configuration/index.tsx
index dc704ec7..b9969eec 100644
--- a/ui/packages/shared/pages/Configuration/index.tsx
+++ b/ui/packages/shared/pages/Instance/Configuration/index.tsx
@@ -5,7 +5,7 @@
  *--------------------------------------------------------------------------
  */
 
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useMemo } from 'react'
 import { observer } from 'mobx-react-lite'
 import Editor from '@monaco-editor/react'
 import {
@@ -35,6 +35,9 @@ import {
   uniqueChipValue,
   customOrGenericImage,
   genericDockerImages,
+  getImageMajorVersion,
+  createFallbackDockerImage,
+  createEnhancedDockerImages,
 } from './utils'
 import {
   SelectWithTooltip,
@@ -74,11 +77,13 @@ const useStyles = makeStyles(
 
 export const Configuration = observer(
   ({
+    instanceId,
     switchActiveTab,
     reload,
     isConfigurationActive,
     disableConfigModification,
   }: {
+    instanceId: string
     switchActiveTab: (_: null, activeTab: number) => void
     reload: () => void
     isConfigurationActive: boolean
@@ -164,12 +169,15 @@ export const Configuration = observer(
         ...submitState,
         response: '',
       })
-      await updateConfig({
-        ...values,
-        tuningParams: formatTuningParamsToObj(
-          values.tuningParams,
-        ) as unknown as string,
-      }).then((response) => {
+      await updateConfig(
+        {
+          ...values,
+          tuningParams: formatTuningParamsToObj(
+            values.tuningParams,
+          ) as unknown as string,
+        },
+        instanceId,
+      ).then((response) => {
         if (response?.ok) {
           setSubmitState({
             status: 'success',
@@ -189,6 +197,32 @@ export const Configuration = observer(
     const [{ formik, connectionData, isConnectionDataValid }] =
       useForm(onSubmit)
 
+    // Memoized enhanced Docker images to avoid recreation on every render
+    // This combines predefined images with any custom image from configuration
+    const enhancedDockerImages = useMemo(() => {
+      return createEnhancedDockerImages(
+        configData?.dockerImageType === 'Generic Postgres' ? configData?.dockerPath : undefined,
+        configData?.dockerImageType === 'Generic Postgres' ? configData?.dockerTag : undefined
+      )
+    }, [configData?.dockerPath, configData?.dockerTag, configData?.dockerImageType])
+
+    // Memoized computed values from enhanced images
+    const dockerImageVersions = useMemo(() => {
+      return enhancedDockerImages
+        .map((image) => image.pg_major_version)
+        .filter((value, index, self) => self.indexOf(value) === index)
+        .sort((a, b) => Number(a) - Number(b))
+    }, [enhancedDockerImages])
+
+    // Memoized tags and locations for performance
+    const dockerTags = useMemo(() => {
+      return enhancedDockerImages.map((image) => image.tag)
+    }, [enhancedDockerImages])
+
+    const dockerLocations = useMemo(() => {
+      return enhancedDockerImages.map((image) => image.location)
+    }, [enhancedDockerImages])
+
     const scrollToField = () => {
       const errorElement = document.querySelector('.Mui-error')
       if (errorElement) {
@@ -227,7 +261,10 @@ export const Configuration = observer(
             },
           },
         })
-        testDbSource(connectionData)
+        testDbSource({
+          ...connectionData,
+          instanceId,
+        })
           .then((res) => {
             if (res?.response) {
               setTestConnectionState({
@@ -305,7 +342,7 @@ export const Configuration = observer(
     }
 
     const handleModalClick = async () => {
-      await getFullConfig()
+      await getFullConfig(instanceId)
       setIsModalOpen(true)
     }
 
@@ -449,30 +486,23 @@ export const Configuration = observer(
       e: React.ChangeEvent<HTMLInputElement>,
     ) => {
       if (e.target.value === 'Generic Postgres') {
-        const genericImageVersions = genericDockerImages
-          .map((image) => image.pg_major_version)
-          .filter((value, index, self) => self.indexOf(value) === index)
-          .sort((a, b) => Number(a) - Number(b))
-        const currentDockerImage = genericImageVersions.slice(-1)[0]
+        // Use memoized enhanced list for better performance
+        const currentDockerImage = dockerImageVersions.slice(-1)[0]
 
         setDockerState({
           ...dockerState,
-          tags: genericDockerImages
-            .map((image) => image.tag)
-            .filter((tag) => tag.startsWith(currentDockerImage)),
-          locations: genericDockerImages
-            .map((image) => image.location)
-            .filter((location) => location?.includes(currentDockerImage)),
-          images: genericImageVersions,
-          data: genericDockerImages,
+          tags: dockerTags.filter((tag) => tag.startsWith(currentDockerImage)),
+          locations: dockerLocations.filter((location) => location?.includes(currentDockerImage)),
+          images: dockerImageVersions,
+          data: enhancedDockerImages,
         })
 
         formik.setValues({
           ...formik.values,
           dockerImage: currentDockerImage,
           dockerImageType: e.target.value,
-          dockerTag: genericDockerImages.map((image) => image.tag)[0],
-          dockerPath: genericDockerImages.map((image) => image.location)[0],
+          dockerTag: dockerTags[0],
+          dockerPath: dockerLocations[0],
           sharedPreloadLibraries:
             'pg_stat_statements,pg_stat_kcache,pg_cron,pgaudit,anon',
         })
@@ -512,16 +542,28 @@ export const Configuration = observer(
           tags: updatedDockerTags,
         })
 
-        const currentLocation = dockerState.data.find(
-          (image) => image.tag === updatedDockerTags[0],
-        )?.location as string
+        // Add safety check for empty array
+        const firstTag = updatedDockerTags[0]
+        if (firstTag) {
+          const currentLocation = dockerState.data.find(
+            (image) => image.tag === firstTag,
+          )?.location
 
-        formik.setValues({
-          ...formik.values,
-          dockerTag: updatedDockerTags[0],
-          dockerImage: e.target.value,
-          dockerPath: currentLocation,
-        })
+          formik.setValues({
+            ...formik.values,
+            dockerTag: firstTag,
+            dockerImage: e.target.value,
+            dockerPath: currentLocation || '',
+          })
+        } else {
+          // Fallback when no matching tags found
+          formik.setValues({
+            ...formik.values,
+            dockerImage: e.target.value,
+            dockerTag: '',
+            dockerPath: '',
+          })
+        }
       } else {
         formik.setValues({
           ...formik.values,
@@ -545,44 +587,49 @@ export const Configuration = observer(
 
           if (customOrGenericImage(configData?.dockerImageType)) {
             if (configData?.dockerImageType === 'Generic Postgres') {
-              const genericImageVersions = genericDockerImages
-                .map((image) => image.pg_major_version)
-                .filter((value, index, self) => self.indexOf(value) === index)
-                .sort((a, b) => Number(a) - Number(b))
-              const currentDockerImage =
-                genericDockerImages.filter(
-                  (image) => image.location === configData?.dockerPath,
-                )[0] ||
-                genericDockerImages.filter((image) =>
-                  configData?.dockerPath?.includes(image.pg_major_version),
-                )[0]
+              // Use memoized enhanced list for better performance
+              const currentDockerImage = enhancedDockerImages.find(
+                (image) => image.location === configData?.dockerPath || image.tag === configData?.dockerTag
+              )
 
-              setDockerState({
-                ...dockerState,
-                tags: genericDockerImages
-                  .map((image) => image.tag)
-                  .filter((tag) =>
+              if (currentDockerImage) {
+                setDockerState({
+                  ...dockerState,
+                  tags: dockerTags.filter((tag) =>
                     tag.startsWith(currentDockerImage.pg_major_version),
                   ),
-                images: genericImageVersions,
-                data: genericDockerImages,
-              })
+                  images: dockerImageVersions,
+                  data: enhancedDockerImages,
+                })
 
-              formik.setFieldValue('dockerTag', currentDockerImage?.tag)
-              formik.setFieldValue(
-                'dockerImage',
-                currentDockerImage.pg_major_version,
-              )
+                formik.setFieldValue('dockerTag', currentDockerImage.tag)
+                formik.setFieldValue('dockerImage', currentDockerImage.pg_major_version)
+                formik.setFieldValue('dockerPath', currentDockerImage.location)
+              } else {
+                // Fallback: shouldn't happen with enhancedDockerImages, but keep for safety
+                const fallbackVersion = dockerImageVersions.slice(-1)[0]
+                
+                setDockerState({
+                  ...dockerState,
+                  tags: dockerTags.filter((tag) => tag.startsWith(fallbackVersion)),
+                  images: dockerImageVersions,
+                  data: enhancedDockerImages,
+                })
+
+                formik.setFieldValue('dockerTag', configData?.dockerTag || '')
+                formik.setFieldValue('dockerImage', fallbackVersion)
+                formik.setFieldValue('dockerPath', configData?.dockerPath || '')
+              }
             } else {
               formik.setFieldValue('dockerImage', configData?.dockerPath)
             }
           }
         }
       }
-    }, [config])
+    }, [config, configData?.dockerPath, configData?.dockerTag, configData?.dockerImageType])
 
     useEffect(() => {
-      getEngine().then((res) => {
+      getEngine(instanceId).then((res) => {
         setDledition(String(res?.edition))
       })
     }, [])
@@ -594,7 +641,7 @@ export const Configuration = observer(
           !isCeEdition &&
           !customOrGenericImage(configData?.dockerImageType)
         ) {
-          await getFullConfig().then(async (data) => {
+          await getFullConfig(instanceId).then(async (data) => {
             if (data) {
               await fetchSeImages({
                 packageGroup: configData?.dockerImageType as string,
@@ -963,7 +1010,7 @@ export const Configuration = observer(
                   className={classes.grayText}
                   style={{ marginTop: '0.5rem', display: 'block' }}
                 >
-                  Default Postgres configuration used for all Postgres instances
+                  Default PostgreSQL configuration used for all PostgreSQL instances
                   running in containers managed by DBLab.
                 </span>
                 <InputWithTooltip
@@ -1148,7 +1195,7 @@ export const Configuration = observer(
                 <Button
                   variant="outlined"
                   color="secondary"
-                  onClick={() => switchActiveTab(null, 0)}
+                  onClick={switchTab}
                 >
                   Cancel
                 </Button>
diff --git a/ui/packages/shared/pages/Configuration/styles.module.scss b/ui/packages/shared/pages/Instance/Configuration/styles.module.scss
similarity index 100%
rename from ui/packages/shared/pages/Configuration/styles.module.scss
rename to ui/packages/shared/pages/Instance/Configuration/styles.module.scss
diff --git a/ui/packages/shared/pages/Instance/Configuration/tooltipText.tsx b/ui/packages/shared/pages/Instance/Configuration/tooltipText.tsx
new file mode 100644
index 00000000..c715c345
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Configuration/tooltipText.tsx
@@ -0,0 +1,170 @@
+import styles from './styles.module.scss'
+
+export const tooltipText = {
+  dockerTag: () => (
+    <div>
+      Docker image version — the latest ones are listed first. If you are unsure,
+      pick the first one.
+    </div>
+  ),
+  dockerImage: () => (
+    <div>
+      Major PostgreSQL version (e.g., "9.6", "15"). For logical provisioning
+      mode, the version used by DBLab does not need to match the version on the
+      source, although matching versions is recommended. <br />
+      If you need a version that is not listed here, contact support.
+    </div>
+  ),
+  dockerImageType: () => (
+    <div>
+      Docker image used to run all database containers — clones, snapshot
+      preparation containers, and sync containers. Although such images are based on
+      traditional Docker images for PostgreSQL, DBLab expects slightly different
+      behavior: for example, PostgreSQL is not the first process used to start the
+      container, so PostgreSQL restarts do not trigger a container state change.
+      For details, see{' '}
+      <a
+        target={'_blank'}
+        href={'https://postgres.ai/docs/database-lab/supported-databases'}
+        className={styles.externalLink}
+      >
+        the docs
+      </a>
+      .
+    </div>
+  ),
+  sharedBuffers: () => (
+    <div>
+      Defines the default buffer pool size for each PostgreSQL instance managed by
+      DBLab. Note that this amount of RAM is immediately allocated at PostgreSQL
+      startup time. For example, if the machine running DBLab has 32 GiB of RAM and
+      the value used here is '1GB', then the theoretical limit of clones is 32.
+      Practically, this limit is even lower because some memory is consumed by
+      other processes. If you need more clones, reduce the value of{' '}
+      <span className={styles.firaCodeFont}>configs.shared_buffers</span>.
+    </div>
+  ),
+  sharedPreloadLibraries: () => (
+    <div>
+      Specifies one or more shared libraries (comma-separated list) to be
+      preloaded at PostgreSQL server start (
+      <a
+        target={'_blank'}
+        href={'https://postgresqlco.nf/doc/en/param/shared_preload_libraries/'}
+        className={styles.externalLink}
+      >
+        details
+      </a>
+      ). If some libraries or extensions are missing, PostgreSQL fails to start,
+      so make sure that <span className={styles.firaCodeFont}>dockerImage</span>{' '}
+      used above contains all required extensions.
+    </div>
+  ),
+  host: () => (
+    <div>
+      Hostname or IP of the database that will be used as the source for data
+      retrieval (full data refresh).
+    </div>
+  ),
+  port: () => (
+    <div>
+      Port of the database that will be used as the source for data retrieval
+      (full data refresh).
+    </div>
+  ),
+  username: () => (
+    <div>
+      Username used to connect to the database that will be used as the source
+      for data retrieval (full data refresh).
+    </div>
+  ),
+  password: () => (
+    <div>
+      Password used to connect to the database that will be used as the source
+      for data retrieval (full data refresh).
+    </div>
+  ),
+  dbname: () => (
+    <div>
+      Database name used to connect to the source to run diagnostic queries.
+      This database is not necessarily copied (another field,{' '}
+      <span className={styles.firaCodeFont}>databases</span>, defines which
+      database to copy).
+    </div>
+  ),
+  databases: () => (
+    <div>
+      Specifies the list of databases the PostgreSQL server will copy during data
+      retrieval (full data refresh). To specify multiple database names, provide
+      each value on a separate line or use spaces as dividers. To copy all
+      available databases, leave this value empty.
+    </div>
+  ),
+  dumpParallelJobs: () => (
+    <div>
+      Number of parallel workers used to dump the source databases to disk. If
+      the source is a production server under load, it is not recommended to use
+      more than 50% of its available vCPUs. Increasing this number speeds up
+      dumping but increases the risk of performance issues on the source (e.g.,
+      due to CPU or disk I/O saturation).
+    </div>
+  ),
+  pgDumpCustomOptions: () => (
+    <div>
+      pg_dump options to be used to create a database dump, for example:
+      '--exclude-schema=repack --exclude-schema="camelStyleSchemaName"'. Note
+      that due to security reasons, the current implementation supports only
+      letters, numbers, hyphen, underscore, equal sign, and double quotes.
+    </div>
+  ),
+  restoreParallelJobs: () => (
+    <div>
+      Number of parallel workers used to restore databases from dump to
+      PostgreSQL managed by DBLab. For initial data retrieval (the first data
+      refresh), it is recommended to match the number of available vCPUs on the
+      machine running DBLab. This yields faster restore times but can increase
+      CPU and disk I/O usage on that machine (up to temporary resource
+      saturation). For subsequent refreshes, if DBLab is in continuous use, it is
+      recommended to reduce this value by 50% to reserve capacity for normal
+      DBLab operations (such as working with clones).
+    </div>
+  ),
+  pgRestoreCustomOptions: () => (
+    <div>
+      pg_restore options to be used to restore from a database dump, for
+      example: '--exclude-schema=repack
+      --exclude-schema="camelStyleSchemaName"'. Note that due to security
+      reasons, the current implementation supports only letters, numbers,
+      hyphen, underscore, equal sign, and double quotes.
+    </div>
+  ),
+  timetable: () => (
+    <div>
+      Schedule for full data refreshes, in{' '}
+      <a
+        target={'_blank'}
+        href={'https://en.wikipedia.org/wiki/Cron#Overview'}
+        className={styles.externalLink}
+      >
+        crontab format
+      </a>
+      .
+    </div>
+  ),
+  tuningParams: () => (
+    <div>
+      Query tuning parameters. These are essential to ensure that cloned
+      PostgreSQL instances generate the same plans as the source (specifically,
+      they are crucial for query performance troubleshooting and optimization,
+      including working with EXPLAIN plans). For details, see the{' '}
+      <a
+        target={'_blank'}
+        href={'https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones'}
+        className={styles.externalLink}
+      >
+        docs
+      </a>
+      .
+    </div>
+  ),
+}
diff --git a/ui/packages/shared/pages/Configuration/useForm.ts b/ui/packages/shared/pages/Instance/Configuration/useForm.ts
similarity index 100%
rename from ui/packages/shared/pages/Configuration/useForm.ts
rename to ui/packages/shared/pages/Instance/Configuration/useForm.ts
diff --git a/ui/packages/shared/pages/Configuration/utils/index.ts b/ui/packages/shared/pages/Instance/Configuration/utils/index.ts
similarity index 53%
rename from ui/packages/shared/pages/Configuration/utils/index.ts
rename to ui/packages/shared/pages/Instance/Configuration/utils/index.ts
index 22076810..51dd9c6b 100644
--- a/ui/packages/shared/pages/Configuration/utils/index.ts
+++ b/ui/packages/shared/pages/Instance/Configuration/utils/index.ts
@@ -5,17 +5,20 @@ import { FormValues } from '../useForm'
 
 const seContainerRegistry = 'se-images'
 const genericImagePrefix = 'postgresai/extended-postgres'
-// since some tags are rc, we need to specify the exact tags to use
+// Predefined list of Docker images for UI display
+// This list is shown to users for convenient selection
+// IMPORTANT: if user specified an image in config that's not in this list,
+// it will be automatically added via createEnhancedDockerImages()
 const dockerImagesConfig = {
-  '9.6': ['0.5.2', '0.5.1', '0.4.6'],
-  '10': ['0.5.2', '0.5.1', '0.4.6'],
-  '11': ['0.5.2', '0.5.1', '0.4.6'],
-  '12': ['0.5.2', '0.5.1', '0.4.6'],
-  '13': ['0.5.2', '0.5.1', '0.4.6'],
-  '14': ['0.5.2', '0.5.1', '0.4.6'],
-  '15': ['0.5.2', '0.5.1', '0.4.6'],
-  '16': ['0.5.2', '0.5.1', '0.4.6'],
-  '17': ['0.5.2', '0.5.1'],
+  '9.6': ['0.5.3', '0.5.2', '0.5.1'],
+  '10': ['0.5.3', '0.5.2', '0.5.1'],
+  '11': ['0.5.3', '0.5.2', '0.5.1'],
+  '12': ['0.5.3', '0.5.2', '0.5.1'],
+  '13': ['0.5.3', '0.5.2', '0.5.1'],
+  '14': ['0.5.3', '0.5.2', '0.5.1'],
+  '15': ['0.5.3', '0.5.2', '0.5.1'],
+  '16': ['0.5.3', '0.5.2', '0.5.1'],
+  '17': ['0.5.3', '0.5.2', '0.5.1'],
 }
 
 export type FormValuesKey = keyof FormValues
@@ -110,11 +113,22 @@ export const getImageType = (imageUrl: string) => {
 }
 
 export const getImageMajorVersion = (pgImage: string | undefined) => {
-  const pgImageVersion = pgImage?.split(':')[1]
-  const pgServerVersion = pgImageVersion?.split('-')[0]
-  return pgServerVersion?.includes('.')
-    ? pgServerVersion?.split('.')[0]
-    : pgServerVersion
+  if (!pgImage) return undefined
+  
+  try {
+    const pgImageVersion = pgImage.split(':')[1]
+    if (!pgImageVersion) return undefined
+    
+    const pgServerVersion = pgImageVersion.split('-')[0]
+    if (!pgServerVersion) return undefined
+    
+    return pgServerVersion.includes('.')
+      ? pgServerVersion.split('.')[0]
+      : pgServerVersion
+  } catch (error) {
+    // Return undefined for malformed image strings
+    return undefined
+  }
 }
 
 export const formatDatabases = (databases: DatabaseType | null) => {
@@ -151,3 +165,71 @@ export const postUniqueCustomOptions = (options: string) => {
 
 export const customOrGenericImage = (dockerImage: string | undefined) =>
   dockerImage === 'Generic Postgres' || dockerImage === 'custom'
+
+export const createFallbackDockerImage = (
+  dockerPath: string,
+  dockerTag: string,
+): DockerImage => {
+  const majorVersion = getImageMajorVersion(dockerPath) || '17' // Default to 17 if version can't be extracted
+  
+  return {
+    package_group: 'postgresai',
+    pg_major_version: majorVersion,
+    tag: dockerTag || `${majorVersion}-custom`,
+    location: dockerPath,
+  }
+}
+
+// Creates enhanced list of Docker images, including image from configuration
+export const createEnhancedDockerImages = (
+  configDockerPath?: string,
+  configDockerTag?: string,
+): DockerImage[] => {
+  let enhancedImages = [...genericDockerImages]
+
+  // If there's an image in config, check if we need to add it
+  if (configDockerPath && configDockerTag) {
+    const existingImage = genericDockerImages.find(
+      (image) => image.location === configDockerPath || image.tag === configDockerTag
+    )
+
+    // If image not found in predefined list, add it
+    if (!existingImage) {
+      // Check if this is a Generic Postgres image
+      if (configDockerPath.includes(genericImagePrefix)) {
+        // For Generic Postgres images create proper structure
+        const majorVersion = getImageMajorVersion(configDockerPath)
+        if (majorVersion) {
+          const configImage: DockerImage = {
+            package_group: 'postgresai',
+            pg_major_version: majorVersion,
+            tag: configDockerTag,
+            location: configDockerPath,
+          }
+          enhancedImages.push(configImage)
+        } else {
+          // Fallback if version extraction failed
+          const configImage = createFallbackDockerImage(configDockerPath, configDockerTag)
+          enhancedImages.push(configImage)
+        }
+      } else {
+        // For custom images use fallback
+        const configImage = createFallbackDockerImage(configDockerPath, configDockerTag)
+        enhancedImages.push(configImage)
+      }
+    }
+  }
+
+  return enhancedImages
+}
+
+// Checks if image is loaded from configuration (not from predefined list)
+export const isConfigLoadedImage = (
+  dockerPath: string,
+  dockerTag: string,
+): boolean => {
+  const existingImage = genericDockerImages.find(
+    (image) => image.location === dockerPath || image.tag === dockerTag
+  )
+  return !existingImage
+}
diff --git a/ui/packages/shared/pages/Instance/Info/Disks/Disk/index.tsx b/ui/packages/shared/pages/Instance/Info/Disks/Disk/index.tsx
index 435c36f5..4a61fb01 100644
--- a/ui/packages/shared/pages/Instance/Info/Disks/Disk/index.tsx
+++ b/ui/packages/shared/pages/Instance/Info/Disks/Disk/index.tsx
@@ -4,7 +4,7 @@ import { formatDistanceToNowStrict } from 'date-fns'
 import { colors } from '@postgres.ai/shared/styles/colors'
 import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
 import { Status as PerformanceStatus } from '@postgres.ai/shared/components/Status'
-import { formatUTC } from '@postgres.ai/shared/utils/date'
+import { formatUTC, isValidDate } from '@postgres.ai/shared/utils/date'
 
 import { Property } from '../../components/Property'
 import { ActionsMenu } from './ActionsMenu'
@@ -113,9 +113,11 @@ export const Disk = (props: Props) => {
         <div className={classes.content}>
           <Property name="Refreshing started at">
             {formatUTC(props.refreshingStartDate, 'yyyy-MM-dd HH:mm:ss')} UTC (
-            {formatDistanceToNowStrict(props.refreshingStartDate, {
-              addSuffix: true,
-            })}
+            {isValidDate(props.refreshingStartDate)
+              ? formatDistanceToNowStrict(props.refreshingStartDate, {
+                  addSuffix: true,
+                })
+              : '-'}
             )
           </Property>
         </div>
diff --git a/ui/packages/shared/pages/Instance/Info/Retrieval/ConfirmFullRefreshModal/index.tsx b/ui/packages/shared/pages/Instance/Info/Retrieval/ConfirmFullRefreshModal/index.tsx
new file mode 100644
index 00000000..4ce21350
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Info/Retrieval/ConfirmFullRefreshModal/index.tsx
@@ -0,0 +1,109 @@
+import { useState } from 'react'
+import { makeStyles } from '@material-ui/core'
+
+import { Modal } from '@postgres.ai/shared/components/Modal'
+import { Text } from '@postgres.ai/shared/components/Text'
+import { SimpleModalControls } from '@postgres.ai/shared/components/SimpleModalControls'
+import { useStores } from '@postgres.ai/shared/pages/Instance/context'
+
+type Props = {
+  isOpen: boolean
+  onClose: () => void
+  instanceId: string
+}
+
+interface ErrorResponse {
+  error?: {
+    message?: string
+    details?: string
+  }
+}
+
+const useStyles = makeStyles(
+  {
+    errorMessage: {
+      color: 'red',
+      marginTop: '10px',
+      wordBreak: 'break-all',
+    },
+    checkboxRoot: {
+      padding: '9px 10px',
+    },
+    grayText: {
+      color: '#8a8a8a',
+      fontSize: '12px',
+      wordBreak: 'break-word',
+    },
+    marginTop: {
+      marginTop: '6px',
+    },
+  },
+  { index: 1 },
+)
+
+export const ConfirmFullRefreshModal = ({
+  isOpen,
+  onClose,
+  instanceId,
+}: Props) => {
+  const classes = useStyles()
+  const stores = useStores()
+  
+  const { fullRefresh } = stores.main
+
+  const [fullRefreshError, setFullRefreshError] = useState<ErrorResponse | null>(null)
+
+  const handleClose = () => {
+    onClose()
+  }
+
+  const handleConfirm = async () => {
+    const result = await fullRefresh(instanceId);
+    if (!result) {
+      setFullRefreshError({ error: { message: 'Unexpected error occurred.' } });
+      return;
+    }
+    const { response, error } = result;
+    if (error) {
+      setFullRefreshError({
+        error: {
+          message: error.message,
+        },
+      })
+      return
+    }
+    if (response) {
+      onClose()
+    }
+  }
+
+
+  return (
+    <Modal
+      title={'Confirmation'}
+      onClose={handleClose}
+      isOpen={isOpen}
+      size="sm"
+    >
+      <Text>
+        Are you sure you want to perform a full refresh of the instance? 
+        This action cannot be undone.
+      </Text>
+      {fullRefreshError && <p className={classes.errorMessage}>{fullRefreshError.error?.message}</p>}
+      <SimpleModalControls
+        items={[
+          {
+            text: 'Cancel',
+            onClick: handleClose,
+          },
+          {
+            text: 'Confirm',
+            variant: 'primary',
+            onClick: handleConfirm,
+            isDisabled: fullRefreshError !== null,
+          },
+        ]}
+      />
+    </Modal>
+  )
+}
diff --git a/ui/packages/shared/pages/Instance/Info/Retrieval/RetrievalModal/index.tsx b/ui/packages/shared/pages/Instance/Info/Retrieval/RetrievalModal/index.tsx
index 1c22d653..bf56e6f6 100644
--- a/ui/packages/shared/pages/Instance/Info/Retrieval/RetrievalModal/index.tsx
+++ b/ui/packages/shared/pages/Instance/Info/Retrieval/RetrievalModal/index.tsx
@@ -1,5 +1,3 @@
-import { useEffect } from 'react'
-
 import { Modal } from '@postgres.ai/shared/components/Modal'
 import { useStores } from '@postgres.ai/shared/pages/Instance/context'
 import { ModalReloadButton } from '@postgres.ai/shared/pages/Instance/components/ModalReloadButton'
@@ -23,10 +21,6 @@ export const RetrievalModal = ({
   const stores = useStores()
   const { isReloadingInstanceRetrieval, reloadInstanceRetrieval } = stores.main
 
-  useEffect(() => {
-    reloadInstanceRetrieval()
-  },[])
-
   return (
     <Modal
       title="Retrieval activity details"
diff --git a/ui/packages/shared/pages/Instance/Info/Retrieval/index.tsx b/ui/packages/shared/pages/Instance/Info/Retrieval/index.tsx
index ace8a726..791dea91 100644
--- a/ui/packages/shared/pages/Instance/Info/Retrieval/index.tsx
+++ b/ui/packages/shared/pages/Instance/Info/Retrieval/index.tsx
@@ -7,8 +7,6 @@ import { Status } from '@postgres.ai/shared/components/Status'
 import { capitalize } from '@postgres.ai/shared/utils/strings'
 import { formatDateStd } from '@postgres.ai/shared/utils/date'
 import { Button } from '@postgres.ai/shared/components/Button2'
-import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
-import { InfoIcon } from '@postgres.ai/shared/icons/Info'
 
 import { Section } from '../components/Section'
 import { Property } from '../components/Property'
@@ -17,6 +15,7 @@ import { RefreshFailedAlert } from './RefreshFailedAlert'
 
 import { getTypeByStatus, isRetrievalUnknown } from './utils'
 import { RetrievalModal } from './RetrievalModal'
+import { ConfirmFullRefreshModal } from './ConfirmFullRefreshModal'
 
 const useStyles = makeStyles(
   () => ({
@@ -28,26 +27,37 @@ const useStyles = makeStyles(
     },
     detailsButton: {
       marginLeft: '8px',
+      '@media (max-width: 600px)': {
+        marginTop: '4px',
+      },
     },
   }),
   { index: 1 },
 )
 
-export const Retrieval = observer(() => {
+type RetrievalProps = {
+  hideBranchingFeatures?: boolean
+}
+
+export const Retrieval = observer((props: RetrievalProps) => {
   const stores = useStores()
   const classes = useStyles()
   const [isModalOpen, setIsModalOpen] = useState<boolean>(false)
+  const [isFullRefreshModalOpen, setIsFullRefreshModalOpen] = useState<boolean>(false)
 
   const { instance, instanceRetrieval } = stores.main
   if (!instance) return null
 
-  const retrieving  = instance.state?.retrieving
+  const retrieving = instance.state?.retrieving
   if (!retrieving) return null
 
   if (!instanceRetrieval) return null
+
   const { mode, status, activity } = instanceRetrieval
+
   const isVisible = mode !== 'physical' && !isRetrievalUnknown(mode)
   const isActive = mode === 'logical' && status === 'refreshing'
+  const canCallFullRefresh = retrieving.status === 'finished' || retrieving.status === 'failed'
 
   return (
     <Section title="Retrieval">
@@ -64,13 +74,16 @@ export const Retrieval = observer(() => {
               >
                 Show details
               </Button>
-              {!isActive && (
-                <Tooltip content="No retrieval activity details">
-                  <InfoIcon className={classes.infoIcon} />
-                </Tooltip>
-              )}
             </>
           )}
+          {!props.hideBranchingFeatures && <Button
+            theme="secondary"
+            onClick={() => setIsFullRefreshModalOpen(true)}
+            isDisabled={!canCallFullRefresh}
+            className={classes.detailsButton}
+          >
+            Full refresh
+          </Button>}
         </Status>
       </Property>
       <Property name="Mode">{retrieving.mode}</Property>
@@ -90,6 +103,11 @@ export const Retrieval = observer(() => {
         isOpen={isModalOpen}
         onClose={() => setIsModalOpen(false)}
       />
+      <ConfirmFullRefreshModal
+        isOpen={isFullRefreshModalOpen}
+        onClose={() => setIsFullRefreshModalOpen(false)}
+        instanceId={instance.id}
+      />
     </Section>
   )
 })
diff --git a/ui/packages/shared/pages/Instance/Info/Snapshots/index.tsx b/ui/packages/shared/pages/Instance/Info/Snapshots/index.tsx
index 6b6dd4aa..bc0c1e60 100644
--- a/ui/packages/shared/pages/Instance/Info/Snapshots/index.tsx
+++ b/ui/packages/shared/pages/Instance/Info/Snapshots/index.tsx
@@ -13,6 +13,7 @@ import { Button } from '@postgres.ai/shared/components/Button2'
 import { Spinner } from '@postgres.ai/shared/components/Spinner'
 import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
 import { useStores } from '@postgres.ai/shared/pages/Instance/context'
+import { isValidDate } from '@postgres.ai/shared/utils/date'
 
 import { Section } from '../components/Section'
 import { Property } from '../components/Property'
@@ -57,9 +58,11 @@ export const Snapshots = observer(() => {
             {lastSnapshot && (
               <>
                 {lastSnapshot.dataStateAt} (
-                {formatDistanceToNowStrict(lastSnapshot.dataStateAtDate, {
-                  addSuffix: true,
-                })}
+                {isValidDate(lastSnapshot.dataStateAtDate)
+                  ? formatDistanceToNowStrict(lastSnapshot.dataStateAtDate, {
+                      addSuffix: true,
+                    })
+                  : '-'}
                 )
               </>
             )}
@@ -70,9 +73,11 @@ export const Snapshots = observer(() => {
             {firstSnapshot && (
               <>
                 {firstSnapshot.dataStateAt} (
-                {formatDistanceToNowStrict(firstSnapshot.dataStateAtDate, {
-                  addSuffix: true,
-                })}
+                {isValidDate(firstSnapshot.dataStateAtDate)
+                  ? formatDistanceToNowStrict(firstSnapshot.dataStateAtDate, {
+                      addSuffix: true,
+                    })
+                  : '-'}
                 )
               </>
             )}
diff --git a/ui/packages/shared/pages/Instance/Info/Snapshots/utils.ts b/ui/packages/shared/pages/Instance/Info/Snapshots/utils.ts
index 45703b1f..616ada2c 100644
--- a/ui/packages/shared/pages/Instance/Info/Snapshots/utils.ts
+++ b/ui/packages/shared/pages/Instance/Info/Snapshots/utils.ts
@@ -8,11 +8,24 @@
 import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
 
 export const getEdgeSnapshots = (snapshots: Snapshot[]) => {
-  const list = [...snapshots]
-  const [first] = list
-  const [last] = list.reverse()
+  if (!snapshots.length) {
+    return {
+      firstSnapshot: null,
+      lastSnapshot: null
+    }
+  }
+  
+  const sortedList = [...snapshots].sort((a, b) => {
+    const aTime = a.dataStateAtDate?.getTime() ?? 0
+    const bTime = b.dataStateAtDate?.getTime() ?? 0
+    return bTime - aTime
+  })
+
+  const [first] = sortedList
+  const [last] = sortedList.slice(-1)
+
   return {
-    firstSnapshot: (first as Snapshot | undefined) ?? null,
-    lastSnapshot: (last as Snapshot | undefined) ?? null
+    firstSnapshot: first ?? null, // newest
+    lastSnapshot: last ?? null    // oldest
   }
 }
diff --git a/ui/packages/shared/pages/Instance/Info/Status/index.tsx b/ui/packages/shared/pages/Instance/Info/Status/index.tsx
index 8da349bb..a95ef1f3 100644
--- a/ui/packages/shared/pages/Instance/Info/Status/index.tsx
+++ b/ui/packages/shared/pages/Instance/Info/Status/index.tsx
@@ -29,7 +29,7 @@ export const Status = observer(() => {
     useState(false)
   const stores = useStores()
 
-  const { instance } = stores.main
+  const { instance, uiVersion, config } = stores.main
   if (!instance || !instance.state) return null
 
   const { code, message } = instance.state.status
@@ -60,7 +60,11 @@ export const Status = observer(() => {
           {formatDateStd(instance.createdAt, { withDistance: true })}
         </Property>
       )}
-      {version && <Property name="Version">{version}</Property>}
+      {uiVersion && <Property name="UI version">{`v${uiVersion}`}</Property>}
+      {version && <Property name="Engine version">{version}</Property>}
+      {config?.dockerPath && (
+        <Property name="Docker image">{config?.dockerPath}</Property>
+      )}
 
       {!isStatusOk && (
         <div className={styles.controls}>
diff --git a/ui/packages/shared/pages/Instance/Info/index.tsx b/ui/packages/shared/pages/Instance/Info/index.tsx
index 2a4da3e1..d33fbccb 100644
--- a/ui/packages/shared/pages/Instance/Info/index.tsx
+++ b/ui/packages/shared/pages/Instance/Info/index.tsx
@@ -76,9 +76,14 @@ const useStyles = makeStyles(
   { index: 1 },
 )
 
-export const Info = () => {
+type InfoProps = {
+  hideBranchingFeatures?: boolean
+}
+
+export const Info = (props: InfoProps) => {
   const classes = useStyles()
   const width = useWindowDimensions()
+  const [onHover, setOnHover] = useState(false)
   const isMobileScreen = width <= SMALL_BREAKPOINT_PX
 
   const [isCollapsed, setIsCollapsed] = useState(
@@ -100,9 +105,12 @@ export const Info = () => {
     >
       {!isMobileScreen && (
         <Button
+          onMouseEnter={() => setOnHover(true)}
+          onMouseLeave={() => setOnHover(false)}
           className={classes.collapseBtn}
           onClick={handleClick}
           isCollapsed={isCollapsed}
+          type="button"
           icon={
             isCollapsed ? (
               <ArrowLeft className={classes.arrowImage} />
@@ -111,14 +119,14 @@ export const Info = () => {
             )
           }
         >
-          Collapse
+          {onHover && 'Collapse'}
         </Button>
       )}
 
       {!isCollapsed && (
         <div>
           <Status />
-          <Retrieval />
+          <Retrieval hideBranchingFeatures={props.hideBranchingFeatures} />
           <Connection />
           <Disks />
           <Snapshots />
diff --git a/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotHeader/index.tsx b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotHeader/index.tsx
new file mode 100644
index 00000000..e38ec9c3
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotHeader/index.tsx
@@ -0,0 +1,77 @@
+import { observer } from 'mobx-react-lite'
+import { makeStyles, TextField } from '@material-ui/core'
+import { Select } from '@postgres.ai/shared/components/Select'
+
+interface SnapshotHeaderProps {
+  branches: string[] | null
+  selectedBranch: string
+  setMessageFilter: (value: string) => void
+  setSelectedBranch: (value: string) => void
+}
+
+const useStyles = makeStyles(
+  {
+    outerContainer: {
+      display: 'flex',
+      justifyContent: 'space-between',
+      alignItems: 'center',
+      paddingBottom: '6px',
+    },
+    select: {
+      width: '200px',
+    },
+    inputContainer: {
+      width: '300px',
+
+      '& input': {
+        padding: '8px',
+      },
+    },
+  },
+  { index: 1 },
+)
+
+export const SnapshotHeader = observer(
+  ({
+    branches,
+    selectedBranch,
+    setMessageFilter,
+    setSelectedBranch,
+  }: SnapshotHeaderProps) => {
+    const classes = useStyles()
+
+    return (
+      <div className={classes.outerContainer}>
+        <Select
+          fullWidth
+          label="Branch"
+          className={classes.select}
+          value={selectedBranch}
+          disabled={!branches}
+          onChange={(e) => {
+            setSelectedBranch(e.target.value)
+          }}
+          items={
+            branches
+              ? branches.map((branch) => {
+                  return {
+                    value: branch,
+                    children: <div>{branch}</div>,
+                  }
+                })
+              : []
+          }
+        />
+        <TextField
+          variant="outlined"
+          className={classes.inputContainer}
+          onChange={(e) => setMessageFilter(e.target.value)}
+          label={'Filter by message'}
+          InputLabelProps={{
+            shrink: true,
+          }}
+        />
+      </div>
+    )
+  },
+)
diff --git a/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsList/index.tsx b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsList/index.tsx
new file mode 100644
index 00000000..8fc0146f
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsList/index.tsx
@@ -0,0 +1,284 @@
+import React from 'react'
+import { observer } from 'mobx-react-lite'
+import { makeStyles } from '@material-ui/core'
+import copy from 'copy-to-clipboard'
+
+import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
+import { DestroySnapshotModal } from '@postgres.ai/shared/pages/Snapshots/Snapshot/DestorySnapshotModal'
+import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
+import { useHost, useStores } from '@postgres.ai/shared/pages/Instance/context'
+import { IconButton } from '@mui/material'
+import { icons } from '@postgres.ai/shared/styles/icons'
+import { RowMenu } from '@postgres.ai/shared/components/Table/RowMenu'
+import {
+  generateSnapshotPageId,
+  groupSnapshotsByCreatedAtDate,
+} from '@postgres.ai/shared/pages/Instance/Snapshots/utils'
+import { format, formatDistanceToNowStrict } from 'date-fns'
+import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
+import { useHistory } from 'react-router'
+import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
+
+const useStyles = makeStyles(
+  {
+    pointerCursor: {
+      cursor: 'pointer',
+      padding: '0 8px',
+
+      '&:hover': {
+        backgroundColor: '#f9f9f9',
+      },
+    },
+    commitItem: {
+      display: 'flex',
+      justifyContent: 'space-between',
+      padding: '14px 0',
+      borderBottom: '1px solid #ddd',
+
+      '&:last-child': {
+        borderBottom: 'none',
+        marginBottom: '12px',
+      },
+    },
+    infoBlock: {
+      display: 'flex',
+      flexDirection: 'column',
+      justifyContent: 'center',
+      overflow: 'hidden',
+    },
+    header: {
+      fontWeight: 500,
+    },
+    infoContent: {
+      fontSize: '12px',
+      color: '#808080',
+      display: 'flex',
+      alignItems: 'center',
+      gap: '5px',
+    },
+    snapshotId: {
+      fontSize: '12px',
+      overflow: 'hidden',
+      textOverflow: 'ellipsis',
+      whiteSpace: 'nowrap',
+      padding: '0 10px',
+    },
+    actionsContainer: {
+      overflow: 'hidden',
+      display: 'flex',
+      height: 'max-content',
+      alignItems: 'center',
+      justifyContent: 'flex-end',
+      border: '1px solid #ddd',
+      borderRadius: '5px',
+      padding: '0 14px',
+      justifySelf: 'end',
+      maxWidth: '100%',
+
+      '& .MuiButtonBase-root, .MuiTableCell-root': {
+        padding: '0!important',
+        borderBottom: 'none',
+      },
+    },
+    dateGroup: {
+      fontSize: '14px',
+      fontWeight: 'bold',
+      padding: '14px 0 5px 0',
+      borderBottom: '1px solid #ddd',
+    },
+    rowMenuContainer: {
+      paddingLeft: '10px',
+      '& .MuiButtonBase-root': {
+        width: '20px',
+        height: '20px',
+        padding: '8px',
+      },
+    },
+    copyButtonContainer: {
+      '& .MuiButtonBase-root': {
+        borderLeft: '1px solid #ddd',
+        borderRadius: '0',
+        borderRight: '1px solid #ddd',
+      },
+    },
+    copyButton: {
+      width: '32px',
+      height: '32px',
+      padding: '8px',
+    },
+    tooltipInfo: {
+      fontSize: '12px',
+    },
+    gridContainer: {
+      width: '100%',
+      display: 'grid',
+      columnGap: '20px',
+      gridTemplateColumns: 'repeat(5, 0.75fr) 1fr',
+    },
+  },
+  { index: 1 },
+)
+
+const SnapshotListItem = ({
+  snapshot,
+  setSnapshotModal,
+  openClonesModal,
+}: {
+  snapshot: Snapshot
+  openClonesModal: () => void
+  setSnapshotModal: (modal: { isOpen: boolean; snapshotId: string }) => void
+}) => {
+  const classes = useStyles()
+  const timeAgo = formatDistanceToNowStrict(snapshot.createdAtDate)
+
+  const history = useHistory()
+  const host = useHost()
+
+  return (
+    <div className={classes.commitItem}>
+      <div className={classes.gridContainer}>
+        <div className={classes.infoBlock}>
+          <div className={classes.header}>{snapshot.message || '-'}</div>
+          <div className={classes.infoContent} title={snapshot.dataStateAt}>
+            {timeAgo} ago
+          </div>
+        </div>
+        <div className={classes.infoBlock}>
+          <div className={classes.header}>Pool</div>
+          <div className={classes.infoContent}>{snapshot.pool ?? '-'}</div>
+        </div>
+        <div className={classes.infoBlock}>
+          <div className={classes.header}>Number of clones</div>
+          <div className={classes.infoContent}>{snapshot.numClones ?? '-'}</div>
+        </div>
+        <div className={classes.infoBlock}>
+          <div className={classes.header}>Logical Size</div>
+          <div className={classes.infoContent}>
+            {snapshot.logicalSize ? formatBytesIEC(snapshot.logicalSize) : '-'}
+          </div>
+        </div>
+        <div className={classes.infoBlock}>
+          <div className={classes.header}>Physical Size</div>
+          <div className={classes.infoContent}>
+            {snapshot.physicalSize
+              ? formatBytesIEC(snapshot.physicalSize)
+              : '-'}
+          </div>
+        </div>
+        <div
+          className={classes.actionsContainer}
+          onClick={(e) => e.stopPropagation()}
+        >
+          <div className={classes.snapshotId}>{snapshot.id}</div>
+          <div className={classes.copyButtonContainer} title="Copy snapshot ID">
+            <IconButton
+              className={classes.copyButton}
+              onClick={(e) => {
+                e.stopPropagation()
+                copy(snapshot.id)
+              }}
+            >
+              {icons.copyIcon}
+            </IconButton>
+          </div>
+          <div className={classes.rowMenuContainer} title="Actions">
+            <RowMenu
+              actions={[
+                {
+                  name: 'Show related clones',
+                  onClick: () => openClonesModal(),
+                },
+                {
+                  name: 'Create new clone',
+                  onClick: () => history.push(host.routes.createClone()),
+                },
+                {
+                  name: 'Delete snapshot',
+                  onClick: () =>
+                    setSnapshotModal({
+                      isOpen: true,
+                      snapshotId: snapshot.id,
+                    }),
+                }
+              ]}
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export const SnapshotsList = observer(
+  ({
+    routes,
+    filteredSnapshots,
+    instanceId,
+  }: {
+    routes: {
+      snapshot: (snapshotId: string) => string
+    }
+    filteredSnapshots: Snapshot[]
+    instanceId: string
+  }) => {
+    const classes = useStyles()
+    const stores = useStores()
+    const history = useHistory()
+    const groupedSnapshots = groupSnapshotsByCreatedAtDate(filteredSnapshots)
+
+    const [snapshotModal, setSnapshotModal] = React.useState({
+      isOpen: false,
+      snapshotId: '',
+    })
+
+    return (
+      <HorizontalScrollContainer>
+        {groupedSnapshots.map((group, index) => {
+          const groupDateFormatted = format(
+            group[0].createdAtDate,
+            'MMM dd, yyyy',
+          )
+
+          return (
+            <div key={index}>
+              <div className={classes.dateGroup}>{groupDateFormatted}</div>
+              {group.map((snapshot) => {
+                const snapshotPageId = generateSnapshotPageId(snapshot.id)
+                return (
+                  <div
+                    key={snapshot.id}
+                    className={classes.pointerCursor}
+                    onClick={() =>
+                      snapshotPageId &&
+                      history.push(routes.snapshot(snapshotPageId))
+                    }
+                  >
+                    <SnapshotListItem
+                      snapshot={snapshot}
+                      setSnapshotModal={setSnapshotModal}
+                      openClonesModal={() =>
+                        stores.clonesModal.openModal({
+                          snapshotId: snapshot.id,
+                        })
+                      }
+                    />
+                  </div>
+                )
+              })}
+            </div>
+          )
+        })}
+        {snapshotModal.isOpen && snapshotModal.snapshotId && (
+          <DestroySnapshotModal
+            isOpen={snapshotModal.isOpen}
+            onClose={() => setSnapshotModal({ isOpen: false, snapshotId: '' })}
+            snapshotId={snapshotModal.snapshotId}
+            instanceId={instanceId}
+            afterSubmitClick={() => stores.main.load(instanceId)}
+            destroySnapshot={stores.main.destroySnapshot as DestroySnapshot}
+          />
+        )}
+      </HorizontalScrollContainer>
+    )
+  },
+)
diff --git a/ui/packages/shared/pages/Instance/SnapshotsModal/utils.ts b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsModal/utils.ts
similarity index 100%
rename from ui/packages/shared/pages/Instance/SnapshotsModal/utils.ts
rename to ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsModal/utils.ts
diff --git a/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsTable/index.tsx b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsTable/index.tsx
new file mode 100644
index 00000000..4c9c2164
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Snapshots/components/SnapshotsTable/index.tsx
@@ -0,0 +1,244 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import React from 'react'
+import cn from 'classnames'
+import { observer } from 'mobx-react-lite'
+import { makeStyles } from '@material-ui/core'
+import { formatDistanceToNowStrict } from 'date-fns'
+import copy from 'copy-to-clipboard'
+import { useHistory } from 'react-router-dom'
+
+import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
+import { generateSnapshotPageId } from '@postgres.ai/shared/pages/Instance/Snapshots/utils'
+import { DestroySnapshotModal } from '@postgres.ai/shared/pages/Snapshots/Snapshot/DestorySnapshotModal'
+import { useStores } from '@postgres.ai/shared/pages/Instance/context'
+import { ArrowDropDownIcon } from '@postgres.ai/shared/icons/ArrowDropDown'
+import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
+import { isSameDayUTC, isValidDate } from '@postgres.ai/shared/utils/date'
+import {
+  Table,
+  TableHead,
+  TableRow,
+  TableBody,
+  TableHeaderCell,
+  TableBodyCell,
+  TableBodyCellMenu,
+} from '@postgres.ai/shared/components/Table'
+import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
+
+const useStyles = makeStyles(
+  {
+    cellContentCentered: {
+      display: 'flex',
+      alignItems: 'center',
+    },
+    pointerCursor: {
+      cursor: 'pointer',
+    },
+    sortIcon: {
+      marginLeft: '8px',
+      width: '10px',
+      cursor: 'pointer',
+      transition: 'transform 0.15s ease-in-out',
+    },
+
+    sortIconUp: {
+      transform: 'rotate(180deg)',
+    },
+
+    hideSortIcon: {
+      opacity: 0,
+    },
+
+    verticalCentered: {
+      display: 'flex',
+      alignItems: 'center',
+    },
+  },
+  { index: 1 },
+)
+
+interface SnapshotsTableProps {
+  instanceId: string
+}
+
+export const SnapshotsTable: React.FC<SnapshotsTableProps> = observer(
+  ({ instanceId }) => {
+    const history = useHistory()
+    const classes = useStyles()
+    const stores = useStores()
+    const { snapshots } = stores.main
+
+    const [snapshotModal, setSnapshotModal] = React.useState({
+      isOpen: false,
+      snapshotId: '',
+    })
+
+    const filteredSnapshots = snapshots?.data?.filter((snapshot) => {
+      const isMatchedByDate =
+        !stores.snapshotsModal.date ||
+        isSameDayUTC(snapshot.dataStateAtDate, stores.snapshotsModal.date)
+
+      const isMatchedByPool =
+        !stores.snapshotsModal.pool ||
+        snapshot.pool === stores.snapshotsModal.pool
+
+      return isMatchedByDate && isMatchedByPool
+    })
+
+    const [state, setState] = React.useState({
+      sortByCreatedDate: 'desc',
+      snapshots: filteredSnapshots ?? [],
+    })
+
+    const handleSortByCreatedDate = () => {
+      const sortByCreatedDate =
+        state.sortByCreatedDate === 'desc' ? 'asc' : 'desc'
+
+      const sortedSnapshots = [...state.snapshots].sort((a, b) => {
+        if (sortByCreatedDate === 'asc') {
+          return (
+            new Date(a.createdAtDate).getTime() -
+            new Date(b.createdAtDate).getTime()
+          )
+        } else {
+          return (
+            new Date(b.createdAtDate).getTime() -
+            new Date(a.createdAtDate).getTime()
+          )
+        }
+      })
+
+      setState({
+        ...state,
+        sortByCreatedDate,
+        snapshots: sortedSnapshots,
+      })
+    }
+
+    if (!snapshots.data) return null
+
+    return (
+      <HorizontalScrollContainer>
+        <Table>
+          <TableHead>
+            <TableRow>
+              <TableHeaderCell />
+              <TableHeaderCell>Data state time</TableHeaderCell>
+              <TableHeaderCell>
+                <div
+                  className={cn(
+                    classes.pointerCursor,
+                    classes.verticalCentered,
+                  )}
+                  onClick={handleSortByCreatedDate}
+                >
+                  Created
+                  <ArrowDropDownIcon
+                    className={cn(
+                      state.sortByCreatedDate === 'asc' && classes.sortIconUp,
+                      classes.sortIcon,
+                    )}
+                  />
+                </div>
+              </TableHeaderCell>
+              <TableHeaderCell>Pool</TableHeaderCell>
+              <TableHeaderCell>Number of clones</TableHeaderCell>
+              <TableHeaderCell>Logical Size</TableHeaderCell>
+              <TableHeaderCell>Physical Size</TableHeaderCell>
+            </TableRow>
+          </TableHead>
+          <TableBody>
+            {state.snapshots?.map((snapshot) => {
+              const snapshotPageId = generateSnapshotPageId(snapshot.id)
+              return (
+                <TableRow
+                  key={snapshot.id}
+                  hover
+                  onClick={() =>
+                    snapshotPageId &&
+                    history.push(`/instance/snapshots/${snapshotPageId}`)
+                  }
+                  className={classes.pointerCursor}
+                >
+                  <TableBodyCellMenu
+                    actions={[
+                      {
+                        name: 'Copy snapshot ID',
+                        onClick: () => copy(snapshot.id),
+                      },
+                      {
+                        name: 'Show related clones',
+                        onClick: () =>
+                          stores.clonesModal.openModal({
+                            snapshotId: snapshot.id,
+                          }),
+                      },
+                      {
+                        name: 'Delete snapshot',
+                        onClick: () =>
+                          setSnapshotModal({
+                            isOpen: true,
+                            snapshotId: snapshot.id,
+                          }),
+                      },
+                    ]}
+                  />
+                  <TableBodyCell>
+                    {snapshot.dataStateAt} (
+                    {isValidDate(snapshot.dataStateAtDate)
+                      ? formatDistanceToNowStrict(snapshot.dataStateAtDate, {
+                          addSuffix: true,
+                        })
+                      : '-'}
+                    )
+                  </TableBodyCell>
+                  <TableBodyCell>
+                    {snapshot.createdAt} (
+                    {isValidDate(snapshot.createdAtDate)
+                      ? formatDistanceToNowStrict(snapshot.createdAtDate, {
+                          addSuffix: true,
+                        })
+                      : '-'}
+                    )
+                  </TableBodyCell>
+                  <TableBodyCell>{snapshot.pool ?? '-'}</TableBodyCell>
+                  <TableBodyCell>{snapshot.numClones ?? '-'}</TableBodyCell>
+                  <TableBodyCell>
+                    {snapshot.logicalSize
+                      ? formatBytesIEC(snapshot.logicalSize)
+                      : '-'}
+                  </TableBodyCell>
+                  <TableBodyCell>
+                    {snapshot.physicalSize
+                      ? formatBytesIEC(snapshot.physicalSize)
+                      : '-'}
+                  </TableBodyCell>
+                </TableRow>
+              )
+            })}
+          </TableBody>
+          {snapshotModal.isOpen && snapshotModal.snapshotId && (
+            <DestroySnapshotModal
+              isOpen={snapshotModal.isOpen}
+              onClose={() =>
+                setSnapshotModal({ isOpen: false, snapshotId: '' })
+              }
+              snapshotId={snapshotModal.snapshotId}
+              instanceId={instanceId}
+              afterSubmitClick={() =>
+                stores.main?.reload(stores.main.instance?.id ?? '')
+              }
+              destroySnapshot={stores.main.destroySnapshot as DestroySnapshot}
+            />
+          )}
+        </Table>
+      </HorizontalScrollContainer>
+    )
+  },
+)
diff --git a/ui/packages/shared/pages/Instance/Snapshots/index.tsx b/ui/packages/shared/pages/Instance/Snapshots/index.tsx
new file mode 100644
index 00000000..f50b06f6
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Snapshots/index.tsx
@@ -0,0 +1,171 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { useHistory } from 'react-router'
+import { observer } from 'mobx-react-lite'
+import { makeStyles } from '@material-ui/core'
+
+import { useStores, useHost } from '@postgres.ai/shared/pages/Instance/context'
+import { SnapshotsList } from '@postgres.ai/shared/pages/Instance/Snapshots/components/SnapshotsList'
+import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { Spinner } from '@postgres.ai/shared/components/Spinner'
+import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
+import { Button } from '@postgres.ai/shared/components/Button2'
+import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
+import { InfoIcon } from '@postgres.ai/shared/icons/Info'
+import { useEffect, useMemo, useState } from 'react'
+import { Branch } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { SnapshotHeader } from './components/SnapshotHeader'
+
+const useStyles = makeStyles(
+  {
+    sectionTitle: {
+      borderBottom: 0,
+    },
+    marginTop: {
+      marginTop: '16px',
+    },
+    infoIcon: {
+      height: '12px',
+      width: '12px',
+      marginLeft: '8px',
+      color: '#808080',
+    },
+    spinner: {
+      position: 'absolute',
+      right: '50%',
+      transform: 'translate(-50%, -50%)',
+    },
+  },
+  { index: 1 },
+)
+
+interface SnapshotsProps {
+  instanceId: string
+}
+
+export const Snapshots: React.FC<SnapshotsProps> = observer(
+  ({ instanceId }) => {
+    const host = useHost()
+    const stores = useStores()
+    const classes = useStyles()
+    const history = useHistory()
+    const { getBranches, instance, snapshots } = stores.main
+    const [messageFilter, setMessageFilter] = useState('')
+    const [branches, setBranches] = useState<string[] | null>(null)
+    const [selectedBranch, setSelectedBranch] = useState<string>('All branches')
+    const [isLoadingBranches, setIsLoadingBranches] = useState(true)
+
+    const filteredSnapshots = useMemo(() => {
+      if (!snapshots.data) return []
+
+      if (!messageFilter.trim()) {
+        return snapshots.data
+      }
+
+      return snapshots.data.filter((snapshot) =>
+        snapshot?.message?.toLowerCase()?.includes(messageFilter.toLowerCase()),
+      )
+    }, [snapshots.data, messageFilter])
+
+    const clonesList = instance?.state?.cloning.clones || []
+    const isEmpty = !filteredSnapshots?.length
+    const hasClones = Boolean(clonesList?.length)
+    const goToSnapshotAddPage = () => history.push(host.routes.createSnapshot())
+    const snapshotPagePath = (snapshotId: string) =>
+      host.routes.snapshot(snapshotId)
+
+    useEffect(() => {
+      const fetchInitialData = async () => {
+        try {
+          setIsLoadingBranches(true)
+          const branches = await getBranches(instanceId)
+          const branchNames = branches?.map(({ name }: Branch) => name) ?? []
+          setBranches(['All branches', ...branchNames])
+        } catch (error) {
+          console.error('Error fetching initial data:', error)
+        } finally {
+          setIsLoadingBranches(false)
+        }
+      }
+
+      fetchInitialData()
+    }, [])
+
+    useEffect(() => {
+      if (selectedBranch) {
+        stores.main.reloadSnapshots(
+          selectedBranch === 'All branches' ? '' : selectedBranch,
+        )
+      }
+    }, [selectedBranch])
+
+    if (!instance && !snapshots.isLoading) return <></>
+
+    if (snapshots?.error) return <ErrorStub message={snapshots?.error} />
+
+    return (
+      <div className={classes.marginTop}>
+        {snapshots.isLoading || isLoadingBranches ? (
+          <Spinner size="lg" className={classes.spinner} />
+        ) : (
+          <>
+            <SectionTitle
+              level={2}
+              tag="h2"
+              text={`Snapshots (${filteredSnapshots?.length || 0})`}
+              className={classes.sectionTitle}
+              rightContent={
+                <>
+                  <Button
+                    theme="primary"
+                    onClick={goToSnapshotAddPage}
+                    isDisabled={!hasClones}
+                  >
+                    Create snapshot
+                  </Button>
+
+                  {!hasClones && (
+                    <Tooltip content="No clones">
+                      <div style={{ display: 'flex' }}>
+                        <InfoIcon className={classes.infoIcon} />
+                      </div>
+                    </Tooltip>
+                  )}
+                </>
+              }
+            />
+            <SnapshotHeader
+              branches={branches}
+              selectedBranch={selectedBranch}
+              setMessageFilter={setMessageFilter}
+              setSelectedBranch={setSelectedBranch}
+            />
+            {!isEmpty ? (
+              <SnapshotsList
+                routes={{ snapshot: snapshotPagePath }}
+                instanceId={instanceId}
+                filteredSnapshots={filteredSnapshots}
+              />
+            ) : (
+              <p className={classes.marginTop}>
+                {messageFilter.length || selectedBranch ? (
+                  <span>
+                    No active snapshots found. Try removing the filter and
+                    checking again
+                  </span>
+                ) : (
+                  <span> This instance has no active snapshots</span>
+                )}
+              </p>
+            )}
+          </>
+        )}
+      </div>
+    )
+  },
+)
diff --git a/ui/packages/shared/pages/Instance/Snapshots/utils/index.ts b/ui/packages/shared/pages/Instance/Snapshots/utils/index.ts
new file mode 100644
index 00000000..a57bfe3e
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Snapshots/utils/index.ts
@@ -0,0 +1,39 @@
+import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
+import { isSameDayUTC } from '@postgres.ai/shared/utils/date'
+
+export const generateSnapshotPageId = (id: string) => {
+  if (!id.includes('@')) return null
+
+  const snapshotIdPart = id.split('@')[1]
+  return snapshotIdPart.startsWith('snapshot_')
+    ? snapshotIdPart.substring(9)
+    : snapshotIdPart
+}
+
+export const groupSnapshotsByCreatedAtDate = (snapshots: Snapshot[]) => {
+  const groups: Snapshot[][] = []
+
+  snapshots.forEach((snapshot) => {
+    let grouped = false
+
+    for (const group of groups) {
+      const groupDate = new Date(group[0].createdAtDate)
+
+      if (isSameDayUTC(snapshot.createdAtDate, groupDate)) {
+        group.push(snapshot)
+        grouped = true
+        break
+      }
+    }
+
+    if (!grouped) {
+      groups.push([snapshot])
+    }
+  })
+
+  groups.sort((a, b) => {
+    return b[0].createdAtDate.getTime() - a[0].createdAtDate.getTime()
+  })
+
+  return groups
+}
diff --git a/ui/packages/shared/pages/Instance/SnapshotsModal/index.tsx b/ui/packages/shared/pages/Instance/SnapshotsModal/index.tsx
index 0d58e2c4..5d87cdfc 100644
--- a/ui/packages/shared/pages/Instance/SnapshotsModal/index.tsx
+++ b/ui/packages/shared/pages/Instance/SnapshotsModal/index.tsx
@@ -28,8 +28,7 @@ import { isSameDayUTC } from '@postgres.ai/shared/utils/date'
 
 import { Tags } from '@postgres.ai/shared/pages/Instance/components/Tags'
 import { ModalReloadButton } from '@postgres.ai/shared/pages/Instance/components/ModalReloadButton'
-
-import { getTags } from './utils'
+import { getTags } from '../Snapshots/components/SnapshotsModal/utils'
 
 const useStyles = makeStyles(
   {
diff --git a/ui/packages/shared/pages/Instance/Tabs/PlatformTabs.tsx b/ui/packages/shared/pages/Instance/Tabs/PlatformTabs.tsx
new file mode 100644
index 00000000..aaca23c2
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Tabs/PlatformTabs.tsx
@@ -0,0 +1,83 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import React from 'react'
+import { Link, useParams } from 'react-router-dom'
+import { Tab as TabComponent, Tabs as TabsComponent } from '@material-ui/core'
+
+import { TABS_INDEX } from '.'
+import { useTabsStyles } from './styles'
+import { PostgresSQLIcon } from '@postgres.ai/shared/icons/PostgresSQL'
+
+type Props = {
+  value: number
+  handleChange: (event: React.ChangeEvent<{}>, newValue: number) => void
+  hasLogs: boolean
+  isPlatform?: boolean
+  hideInstanceTabs?: boolean
+}
+
+export const PlatformTabs = ({
+  value,
+  handleChange,
+  hasLogs,
+  hideInstanceTabs,
+}: Props) => {
+  const classes = useTabsStyles()
+  const { org, instanceId } = useParams<{ org: string; instanceId: string }>()
+
+  const tabs = [
+    {
+      label: '👁️ Overview',
+      to: 'overview',
+      value: TABS_INDEX.OVERVIEW,
+    },
+    {
+      label: '🖖 Branches',
+      to: 'branches',
+      value: TABS_INDEX.BRANCHES,
+      hide: hideInstanceTabs,
+    },
+    {
+      label: '⚡ Snapshots',
+      to: 'snapshots',
+      value: TABS_INDEX.SNAPSHOTS,
+      hide: hideInstanceTabs,
+    },
+    {
+      label: (
+        <div className={classes.flexRow}>
+          <PostgresSQLIcon /> Clones
+        </div>
+      ),
+      to: 'clones',
+      value: TABS_INDEX.CLONES,
+      hide: hideInstanceTabs,
+    }
+  ]
+
+  return (
+    <TabsComponent
+      value={value}
+      onChange={handleChange}
+      classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}
+    >
+      {tabs.map(({ label, to, value, hide }) => (
+        <Link key={value} to={`/${org}/instances/${instanceId}/${to}`}>
+          <TabComponent
+            label={label}
+            value={value}
+            classes={{
+              root: hide ? classes.tabHidden : classes.tabRoot,
+            }}
+            onClick={(event) => handleChange(event, value)}
+          />
+        </Link>
+      ))}
+    </TabsComponent>
+  )
+}
diff --git a/ui/packages/shared/pages/Instance/Tabs/index.tsx b/ui/packages/shared/pages/Instance/Tabs/index.tsx
index 9d4c0f7f..2c1906b7 100644
--- a/ui/packages/shared/pages/Instance/Tabs/index.tsx
+++ b/ui/packages/shared/pages/Instance/Tabs/index.tsx
@@ -6,56 +6,77 @@
  */
 
 import React from 'react'
-import {
-  makeStyles,
-  Tab as TabComponent,
-  Tabs as TabsComponent,
-} from '@material-ui/core'
-import { colors } from '@postgres.ai/shared/styles/colors'
+import { Link } from 'react-router-dom'
+import { Tab as TabComponent, Tabs as TabsComponent } from '@material-ui/core'
 
-const useStyles = makeStyles(
-  {
-    tabsRoot: {
-      minHeight: 0,
-      marginTop: '-8px',
-    },
-    tabsIndicator: {
-      height: '3px',
-    },
-    tabRoot: {
-      fontWeight: 400,
-      minWidth: 0,
-      minHeight: 0,
-      padding: '6px 16px',
-      borderBottom: `3px solid ${colors.consoleStroke}`,
-
-      '& + $tabRoot': {
-        marginLeft: '10px',
-      },
+import { PostgresSQLIcon } from '@postgres.ai/shared/icons/PostgresSQL'
+import { useTabsStyles } from './styles'
+import { PlatformTabs } from "./PlatformTabs";
+import { useCreatedStores } from "../useCreatedStores";
+import { Host } from '../context'
 
-      '&.Mui-disabled': {
-        opacity: 1,
-        color: colors.pgaiDarkGray,
-      },
-    },
-    tabHidden: {
-      display: 'none',
-    },
-  },
-  { index: 1 },
-)
 
-type Props = {
+export const TABS_INDEX = {
+  OVERVIEW: 0,
+  BRANCHES: 1,
+  SNAPSHOTS: 2,
+  CLONES: 3,
+  LOGS: 4,
+  CONFIGURATION: 5,
+}
+export interface TabsProps {
   value: number
   handleChange: (event: React.ChangeEvent<{}>, newValue: number) => void
   hasLogs: boolean
   isPlatform?: boolean
+  hideInstanceTabs?: boolean
 }
 
-export const Tabs = (props: Props) => {
-  const classes = useStyles()
+export const Tabs = ({
+  value,
+  handleChange,
+  hasLogs,
+  hideInstanceTabs,
+}: TabsProps) => {
+  const classes = useTabsStyles()
 
-  const { value, handleChange, hasLogs } = props
+  const tabData = [
+    { label: '👁️ Overview', to: '/instance', value: TABS_INDEX.OVERVIEW },
+    {
+      label: '🖖 Branches',
+      to: '/instance/branches',
+      value: TABS_INDEX.BRANCHES,
+      hide: hideInstanceTabs,
+    },
+    {
+      label: '⚡ Snapshots',
+      to: '/instance/snapshots',
+      value: TABS_INDEX.SNAPSHOTS,
+      hide: hideInstanceTabs,
+    },
+    {
+      label: (
+        <div className={classes.flexRow}>
+          <PostgresSQLIcon /> Clones
+        </div>
+      ),
+      to: '/instance/clones',
+      value: TABS_INDEX.CLONES,
+      hide: hideInstanceTabs,
+    },
+    {
+      label: '📓 Logs',
+      to: '/instance/logs',
+      value: TABS_INDEX.LOGS,
+      disabled: !hasLogs,
+    },
+    {
+      label: '🛠️ Configuration',
+      to: '/instance/configuration',
+      value: TABS_INDEX.CONFIGURATION,
+      hide: hideInstanceTabs,
+    },
+  ]
 
   return (
     <TabsComponent
@@ -63,36 +84,57 @@ export const Tabs = (props: Props) => {
       onChange={handleChange}
       classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}
     >
-      <TabComponent
-        label="Overview"
-        classes={{
-          root: classes.tabRoot,
-        }}
-        value={0}
-      />
-      <TabComponent
-        label="Logs"
-        disabled={!hasLogs}
-        classes={{
-          root: props.isPlatform ? classes.tabHidden : classes.tabRoot,
-        }}
-        value={1}
-      />
-      <TabComponent
-        label="Configuration"
-        classes={{
-          root: props.isPlatform ? classes.tabHidden : classes.tabRoot,
-        }}
-        value={2}
-      />
-      {/* // TODO(Anton): Probably will be later. */}
-      {/* <TabComponent
-        label='Snapshots'
-        disabled
-        classes={{
-          root: classes.tabRoot
-        }}
-      /> */}
+      {tabData.map(({ label, to, value, hide, disabled }) => (
+        <Link key={value} to={to}>
+          <TabComponent
+            label={label}
+            value={value}
+            disabled={disabled}
+            classes={{
+              root: hide ? classes.tabHidden : classes.tabRoot,
+            }}
+          />
+        </Link>
+      ))}
     </TabsComponent>
   )
 }
+
+type InstanceTabProps = {
+  tab: number
+  isPlatform?: boolean
+  onTabChange?: (tabID: number) => void
+  instanceId: string
+  hasLogs: boolean
+  hideInstanceTabs?: boolean
+}
+
+export const InstanceTabs = (props: InstanceTabProps) => {
+  const { instanceId, onTabChange, tab, hasLogs, hideInstanceTabs = false } = props;
+  const stores = useCreatedStores({} as unknown as Host)
+  const {
+    load,
+  } = stores.main
+
+  const switchTab = (_: React.ChangeEvent<{}> | null, tabID: number) => {
+    const contentElement = document.getElementById('content-container')
+    if (onTabChange) {
+      onTabChange(tabID)
+    }
+
+    if (tabID === 0) {
+      load(instanceId)
+    }
+
+    contentElement?.scrollTo(0, 0)
+  }
+
+  const tabProps = {
+    value: tab,
+    handleChange: switchTab,
+    hasLogs: Boolean(hasLogs),
+    hideInstanceTabs: hideInstanceTabs,
+  }
+
+  return props.isPlatform ? <PlatformTabs {...tabProps} /> : <Tabs {...tabProps} />
+}
diff --git a/ui/packages/shared/pages/Instance/Tabs/styles.ts b/ui/packages/shared/pages/Instance/Tabs/styles.ts
new file mode 100644
index 00000000..f39e79aa
--- /dev/null
+++ b/ui/packages/shared/pages/Instance/Tabs/styles.ts
@@ -0,0 +1,68 @@
+import { makeStyles } from '@material-ui/core'
+import { colors } from '@postgres.ai/shared/styles/colors'
+
+export const useTabsStyles = makeStyles(
+  {
+    tabsRoot: {
+      minHeight: 0,
+      marginTop: '-8px',
+      '& .MuiTabs-fixed': {
+        overflowX: 'auto!important',
+        scrollbarWidth: 'none',
+        '&::-webkit-scrollbar': {
+          display: 'none',
+        },
+
+        '& div:first-child': {
+          '@media (max-width: 700px)': {
+            display: 'flex',
+          },
+        },
+      },
+      '& .postgres-logo': {
+        width: '18px',
+        height: '18px',
+      },
+      '& a': {
+        color: colors.black,
+        textDecoration: 'none',
+
+        '@media (max-width: 700px)': {
+          display: 'flex',
+          width: '100%',
+        },
+      },
+    },
+    flexRow: {
+      display: 'flex',
+      flexDirection: 'row',
+      gap: '5px',
+      width: '100%',
+    },
+    tabsIndicator: {
+      height: '3px',
+    },
+    tabRoot: {
+      fontWeight: 400,
+      minWidth: 0,
+      minHeight: 0,
+      width: '100%',
+      padding: '6px 16px',
+      borderBottom: `3px solid ${colors.consoleStroke}`,
+      '& + $tabRoot': {
+        marginLeft: '10px',
+      },
+      '&.Mui-disabled': {
+        opacity: 1,
+        color: colors.pgaiDarkGray,
+      },
+      '@media (max-width: 700px)': {
+        width: 'max-content',
+      },
+    },
+    tabHidden: {
+      display: 'none',
+    },
+  },
+  { index: 1 },
+)
diff --git a/ui/packages/shared/pages/Instance/components/ModalReloadButton/index.tsx b/ui/packages/shared/pages/Instance/components/ModalReloadButton/index.tsx
index d57a0534..b2662af1 100644
--- a/ui/packages/shared/pages/Instance/components/ModalReloadButton/index.tsx
+++ b/ui/packages/shared/pages/Instance/components/ModalReloadButton/index.tsx
@@ -35,7 +35,6 @@ export const ModalReloadButton = (props: Props) => {
       onClick={props.onReload}
       className={classes.content}
       isLoading={props.isReloading}
-      isDisabled={props.isReloading}
     >
       Reload info
     </Button>
diff --git a/ui/packages/shared/pages/Instance/context.ts b/ui/packages/shared/pages/Instance/context.ts
index 225b12e3..7cf7927b 100644
--- a/ui/packages/shared/pages/Instance/context.ts
+++ b/ui/packages/shared/pages/Instance/context.ts
@@ -14,7 +14,12 @@ export type Host = {
   instanceId: string
   routes: {
     createClone: () => string
+    createBranch: () => string
+    createSnapshot: () => string
     clone: (cloneId: string) => string
+    branch: (branchId: string) => string
+    branches: () => string
+    snapshot: (snapshotId: string) => string
   }
   api: Api
   title: string
@@ -26,6 +31,8 @@ export type Host = {
     breadcrumbs: React.ReactNode
   }
   wsHost?: string
+  hideBranchingFeatures?: boolean
+  renderCurrentTab?: number
   isPlatform?: boolean
   setProjectAlias?: (alias: string) => void
 }
diff --git a/ui/packages/shared/pages/Instance/index.tsx b/ui/packages/shared/pages/Instance/index.tsx
index facd6c0f..2147e594 100644
--- a/ui/packages/shared/pages/Instance/index.tsx
+++ b/ui/packages/shared/pages/Instance/index.tsx
@@ -14,13 +14,15 @@ import { StubSpinner } from '@postgres.ai/shared/components/StubSpinner'
 import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
 import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
 
-import { Tabs } from './Tabs'
+import { TABS_INDEX, InstanceTabs } from './Tabs'
 import { Logs } from '../Logs'
 import { Clones } from './Clones'
 import { Info } from './Info'
-import { Configuration } from '../Configuration'
-import { ClonesModal } from './ClonesModal'
+import { Snapshots } from './Snapshots'
+import { Branches } from '../Branches'
+import { Configuration } from './Configuration'
 import { SnapshotsModal } from './SnapshotsModal'
+import { ClonesModal } from './Clones/ClonesModal'
 import { InactiveInstance } from './InactiveInstance'
 import { Host, HostProvider, StoresProvider } from './context'
 
@@ -31,7 +33,7 @@ import { useCreatedStores } from './useCreatedStores'
 
 import './styles.scss'
 
-type Props = Host
+type Props = Host & { hideBranchingFeatures?: boolean }
 
 const useStyles = makeStyles(
   (theme) => ({
@@ -63,9 +65,11 @@ const useStyles = makeStyles(
 export const Instance = observer((props: Props) => {
   const classes = useStyles()
 
-  const { instanceId, api } = props
-  const [activeTab, setActiveTab] = React.useState(0)
-  const [hasBeenRedirected, setHasBeenRedirected] = React.useState(false);
+  const { instanceId, api, isPlatform } = props
+  const [activeTab, setActiveTab] = React.useState(
+    props?.renderCurrentTab || TABS_INDEX.OVERVIEW,
+  )
+  const [hasBeenRedirected, setHasBeenRedirected] = React.useState(false)
 
   const stores = useCreatedStores(props)
   const {
@@ -73,8 +77,8 @@ export const Instance = observer((props: Props) => {
     instanceError,
     instanceRetrieval,
     isLoadingInstance,
+    isLoadingInstanceRetrieval,
     load,
-    reload,
   } = stores.main
 
   const switchTab = (_: React.ChangeEvent<{}> | null, tabID: number) => {
@@ -84,32 +88,33 @@ export const Instance = observer((props: Props) => {
     if (tabID === 0) {
       load(props.instanceId)
     }
-    contentElement?.scroll(0, 0)
+
+    contentElement?.scrollTo(0, 0)
   }
 
   const isInstanceIntegrated =
     instanceRetrieval ||
-    (!isLoadingInstance && instance && instance?.url && !instanceError)
+    (!isLoadingInstance && !isLoadingInstanceRetrieval && instance && instance?.url && !instanceError)
 
   const isConfigurationActive = instanceRetrieval?.mode !== 'physical'
 
   useEffect(() => {
-    load(instanceId)
+    load(instanceId, isPlatform)
   }, [instanceId])
 
   useEffect(() => {
-    if (props.setProjectAlias) props.setProjectAlias(instance?.projectAlias || '')
+    if (props.setProjectAlias)
+      props.setProjectAlias(instance?.projectAlias || '')
   }, [instance])
 
   useEffect(() => {
     if (
       instance &&
-      instance?.state?.retrieving?.status === 'pending' &&
+      instance.state?.retrieving?.status === 'pending' &&
       isConfigurationActive &&
-      !props.isPlatform &&
       !hasBeenRedirected
     ) {
-      setActiveTab(2)
+      setActiveTab(TABS_INDEX.CONFIGURATION)
       setHasBeenRedirected(true)
     }
   }, [instance, hasBeenRedirected])
@@ -125,7 +130,7 @@ export const Instance = observer((props: Props) => {
           className={classes.title}
           rightContent={
             <Button
-              onClick={() => reload(props.instanceId)}
+              onClick={() => load(props.instanceId, isPlatform)}
               isDisabled={!instance && !instanceError}
               className={classes.reloadButton}
             >
@@ -134,11 +139,13 @@ export const Instance = observer((props: Props) => {
           }
         >
           {isInstanceIntegrated && (
-            <Tabs
-              isPlatform={props.isPlatform}
-              value={activeTab}
-              handleChange={switchTab}
-              hasLogs={api.initWS != undefined}
+            <InstanceTabs
+              instanceId={props.instanceId}
+              tab={activeTab}
+              onTabChange={(tabID) =>  setActiveTab(tabID)}
+              isPlatform={isPlatform!}
+              hasLogs={api.initWS !== undefined}
+              hideInstanceTabs={props.hideBranchingFeatures}
             />
           )}
         </SectionTitle>
@@ -149,16 +156,13 @@ export const Instance = observer((props: Props) => {
 
         {isInstanceIntegrated ? (
           <>
-            <TabPanel value={activeTab} index={0}>
+            <TabPanel value={activeTab} index={TABS_INDEX.OVERVIEW}>
               {!instanceError && (
                 <div className={classes.content}>
-                  {!instance ||
-                    (!instance?.state?.retrieving?.status && <StubSpinner />)}
-
-                  {instance ? (
+                  {instance && instance.state?.retrieving?.status ? (
                     <>
                       <Clones />
-                      <Info />
+                      <Info hideBranchingFeatures={props.hideBranchingFeatures} />
                     </>
                   ) : (
                     <StubSpinner />
@@ -171,26 +175,44 @@ export const Instance = observer((props: Props) => {
               <SnapshotsModal />
             </TabPanel>
 
-            {!props.isPlatform && (
-              <>
-                <TabPanel value={activeTab} index={1}>
-                  {activeTab === 1 && <Logs api={api} />}
-                </TabPanel>
-
-                <TabPanel value={activeTab} index={2}>
-                  <Configuration
-                    isConfigurationActive={isConfigurationActive}
-                    disableConfigModification={
-                      instance?.state?.engine.disableConfigModification
-                    }
-                    switchActiveTab={switchTab}
-                    reload={() => load(props.instanceId)}
-                  />
-                </TabPanel>
-              </>
-            )}
+            <TabPanel value={activeTab} index={TABS_INDEX.CLONES}>
+              {activeTab === TABS_INDEX.CLONES && (
+                <div className={classes.content}>
+                  {!instanceError &&
+                    (instance ? <Clones onlyRenderList /> : <StubSpinner />)}
+                </div>
+              )}
+            </TabPanel>
+            <TabPanel value={activeTab} index={TABS_INDEX.LOGS}>
+              {activeTab === TABS_INDEX.LOGS && (
+                <Logs api={api} instanceId={props.instanceId} />
+              )}
+            </TabPanel>
+            <TabPanel value={activeTab} index={TABS_INDEX.CONFIGURATION}>
+              {activeTab === TABS_INDEX.CONFIGURATION && (
+                <Configuration
+                  instanceId={instanceId}
+                  switchActiveTab={switchTab}
+                  isConfigurationActive={isConfigurationActive}
+                  reload={() => load(props.instanceId)}
+                  disableConfigModification={
+                    instance?.state?.engine.disableConfigModification
+                  }
+                />
+              )}
+            </TabPanel>
+            <TabPanel value={activeTab} index={TABS_INDEX.SNAPSHOTS}>
+              {activeTab === TABS_INDEX.SNAPSHOTS && (
+                <Snapshots instanceId={instanceId} />
+              )}
+            </TabPanel>
+            <TabPanel value={activeTab} index={TABS_INDEX.BRANCHES}>
+              {activeTab === TABS_INDEX.BRANCHES && (
+                <Branches instanceId={instanceId} />
+              )}
+            </TabPanel>
           </>
-        ) : !isLoadingInstance && !instanceError ? (
+        ) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (
           <TabPanel value={activeTab} index={activeTab}>
             <InactiveInstance
               instance={instance}
@@ -199,7 +221,7 @@ export const Instance = observer((props: Props) => {
           </TabPanel>
         ) : (
           !instanceError && (
-            <TabPanel value={activeTab} index={0}>
+            <TabPanel value={activeTab} index={activeTab}>
               <div className={classes.content}>
                 <StubSpinner />
               </div>
diff --git a/ui/packages/shared/pages/Instance/stores/Main.ts b/ui/packages/shared/pages/Instance/stores/Main.ts
index a247bd34..572f13de 100644
--- a/ui/packages/shared/pages/Instance/stores/Main.ts
+++ b/ui/packages/shared/pages/Instance/stores/Main.ts
@@ -7,6 +7,7 @@
 import { makeAutoObservable } from 'mobx'
 
 import { GetSnapshots } from '@postgres.ai/shared/types/api/endpoints/getSnapshots'
+import { CreateSnapshot } from '@postgres.ai/shared/types/api/endpoints/createSnapshot'
 import { GetInstance } from '@postgres.ai/shared/types/api/endpoints/getInstance'
 import { Config } from '@postgres.ai/shared/types/api/entities/config'
 import { GetConfig } from '@postgres.ai/shared/types/api/endpoints/getConfig'
@@ -25,17 +26,23 @@ import { GetFullConfig } from '@postgres.ai/shared/types/api/endpoints/getFullCo
 import { GetInstanceRetrieval } from '@postgres.ai/shared/types/api/endpoints/getInstanceRetrieval'
 import { InstanceRetrievalType } from '@postgres.ai/shared/types/api/entities/instanceRetrieval'
 import { GetEngine } from '@postgres.ai/shared/types/api/endpoints/getEngine'
+import { GetSnapshotList } from '@postgres.ai/shared/types/api/endpoints/getSnapshotList'
+import { GetBranches } from '@postgres.ai/shared/types/api/endpoints/getBranches'
+import { DeleteBranch } from '@postgres.ai/shared/types/api/endpoints/deleteBranch'
 import { GetSeImages } from '@postgres.ai/shared/types/api/endpoints/getSeImages'
+import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
+import { FullRefresh } from "../../../types/api/endpoints/fullRefresh";
 
 const UNSTABLE_CLONE_STATUS_CODES = ['CREATING', 'RESETTING', 'DELETING']
 
 export type Api = {
   getInstance: GetInstance
-  getSnapshots: GetSnapshots
+  getSnapshots?: GetSnapshots
+  createSnapshot?: CreateSnapshot
   refreshInstance?: RefreshInstance
-  destroyClone: DestroyClone
-  resetClone: ResetClone
-  getWSToken: GetWSToken
+  destroyClone?: DestroyClone
+  resetClone?: ResetClone
+  getWSToken?: GetWSToken
   initWS?: InitWS
   getConfig?: GetConfig
   updateConfig?: UpdateConfig
@@ -44,6 +51,11 @@ export type Api = {
   getSeImages?: GetSeImages
   getEngine?: GetEngine
   getInstanceRetrieval?: GetInstanceRetrieval
+  getBranches?: GetBranches
+  getSnapshotList?: GetSnapshotList
+  deleteBranch?: DeleteBranch
+  destroySnapshot?: DestroySnapshot
+  fullRefresh?: FullRefresh
 }
 
 type Error = {
@@ -56,10 +68,14 @@ export class MainStore {
   instanceRetrieval: InstanceRetrievalType | null = null
   config: Config | null = null
   fullConfig?: string
+  dleEdition?: string
   platformUrl?: string
+  uiVersion?: string
   instanceError: Error | null = null
   configError: string | null = null
   getFullConfigError: string | null = null
+  getBranchesError: Error | null = null
+  snapshotListError: string | null = null
   seImagesError: string | undefined | null = null
 
   unstableClones = new Set<string>()
@@ -70,7 +86,10 @@ export class MainStore {
   isConfigurationLoading = false
   isReloadingInstance = false
   isReloadingInstanceRetrieval = false
+  isBranchesLoading = false
+  isConfigLoading = false
   isLoadingInstance = false
+  isLoadingInstanceRetrieval = false
 
   private readonly api: Api
 
@@ -86,44 +105,73 @@ export class MainStore {
     return this.instance.state?.status.code === 'NO_RESPONSE'
   }
 
-  load = (instanceId: string) => {
+  load = (instanceId: string, isPlatform: boolean = false) => {
     this.instance = null
+    this.instanceRetrieval = null
     this.isReloadingInstance = true
+    this.isLoadingInstanceRetrieval = false
+
+    if (!isPlatform) {
+      this.getBranches(instanceId)
+    }
+
+    const runRetrieval = () => {
+      this.loadInstanceRetrieval(instanceId).then(() => {
+        if (this.instanceRetrieval) {
+          this.getConfig(instanceId)
+          this.getFullConfig(instanceId)
+        }
+      })
+    }
+
     this.loadInstance(instanceId, false).then(() => {
-      if (this.instance?.createdAt && this.instance?.url || !this.instance?.createdAt) {
+      if (
+        (this.instance?.createdAt && this.instance?.url) ||
+        !this.instance?.createdAt
+      ) {
         this.snapshots.load(instanceId)
       }
-    })
-    this.loadInstanceRetrieval(instanceId).then(() => {
-      if (this.instanceRetrieval) {
-        this.getConfig()
-        this.getFullConfig()
+
+      if (isPlatform && this.instance?.url) {
+        this.getBranches(instanceId)
+        runRetrieval()
       }
     })
+
+    if (!isPlatform) {
+      runRetrieval()
+    }
   }
 
   reload = (instanceId: string) => {
     this.instance = null
+    this.instanceRetrieval = null
     this.isReloadingInstance = true
+    this.isLoadingInstanceRetrieval = false
+    
     this.loadInstance(instanceId, false).then(() => {
       if (this.api.refreshInstance)
         this.api.refreshInstance({ instanceId: instanceId })
 
-        if (this.instance?.createdAt && this.instance?.url || !this.instance?.createdAt) {
+      if (
+        (this.instance?.createdAt && this.instance?.url) ||
+        !this.instance?.createdAt
+      ) {
         this.snapshots.load(instanceId)
       }
     })
+    
     this.loadInstanceRetrieval(instanceId).then(() => {
       if (this.instanceRetrieval) {
-        this.getConfig()
-        this.getFullConfig()
+        this.getConfig(instanceId)
+        this.getFullConfig(instanceId)
       }
     })
   }
 
-  reloadSnapshots = async () => {
+  reloadSnapshots = async (branchName?: string) => {
     if (!this.instance) return
-    await this.snapshots.reload(this.instance.id)
+    await this.snapshots.reload(this.instance.id, branchName)
   }
 
   reloadInstanceRetrieval = async () => {
@@ -134,12 +182,19 @@ export class MainStore {
   }
 
   private loadInstanceRetrieval = async (instanceId: string) => {
-    if (!this.api.getInstanceRetrieval) return
+    if (!this.api.getInstanceRetrieval) {
+      this.isLoadingInstanceRetrieval = false
+      return
+    }
+
+    this.isLoadingInstanceRetrieval = true
 
     const { response, error } = await this.api.getInstanceRetrieval({
       instanceId: instanceId,
     })
 
+    this.isLoadingInstanceRetrieval = false
+
     if (response) this.instanceRetrieval = response
 
     if (error)
@@ -191,14 +246,16 @@ export class MainStore {
     return !!response
   }
 
-  getConfig = async () => {
+  getConfig = async (instanceId: string) => {
     if (!this.api.getConfig) return
 
     this.isConfigurationLoading = true
+    this.isConfigLoading = true
 
-    const { response, error } = await this.api.getConfig()
+    const { response, error } = await this.api.getConfig(instanceId)
 
     this.isConfigurationLoading = false
+    this.isConfigLoading = false
 
     if (response) {
       this.config = response
@@ -212,25 +269,33 @@ export class MainStore {
     return response
   }
 
-  updateConfig = async (values: Config) => {
+  updateConfig = async (values: Config, instanceId: string) => {
     if (!this.api.updateConfig) return
 
-    const { response, error } = await this.api.updateConfig({ ...values })
+    const { response, error } = await this.api.updateConfig(
+      { ...values },
+      instanceId,
+    )
 
     if (error) this.configError = await error.json().then((err) => err.message)
 
     return response
   }
 
-  getFullConfig = async () => {
+  getFullConfig = async (instanceId: string) => {
     if (!this.api.getFullConfig) return
 
-    const { response, error } = await this.api.getFullConfig()
+    const { response, error } = await this.api.getFullConfig(instanceId)
+
     if (response) {
       this.fullConfig = response
 
       const splitYML = this.fullConfig.split('---')
       this.platformUrl = splitYML[0]?.split('url: ')[1]?.split('\n')[0]
+      this.uiVersion = splitYML[0]
+        ?.split('dockerImage: "postgresai/ce-ui:')[2]
+        ?.split('\n')[0]
+        ?.replace(/['"]+/g, '')
     }
 
     if (error)
@@ -260,12 +325,16 @@ export class MainStore {
     return response
   }
 
-  getEngine = async () => {
+  getEngine = async (instanceId: string) => {
     if (!this.api.getEngine) return
 
     this.configError = null
 
-    const { response, error } = await this.api.getEngine()
+    const { response, error } = await this.api.getEngine(instanceId)
+
+    if (response) {
+      this.dleEdition = response.edition
+    }
 
     if (error) await getTextFromUnknownApiError(error)
     return response
@@ -283,7 +352,7 @@ export class MainStore {
   }
 
   resetClone = async (cloneId: string, snapshotId: string) => {
-    if (!this.instance) return
+    if (!this.instance || !this.api.resetClone) return
 
     this.unstableClones.add(cloneId)
 
@@ -300,7 +369,7 @@ export class MainStore {
   }
 
   destroyClone = async (cloneId: string) => {
-    if (!this.instance) return
+    if (!this.instance || !this.api.destroyClone) return
 
     this.unstableClones.add(cloneId)
 
@@ -332,4 +401,84 @@ export class MainStore {
     await this.loadInstanceRetrieval(this.instance.id)
     this.isReloadingClones = false
   }
+
+  getBranches = async (instanceId: string) => {
+    if (!this.api.getBranches) return
+    this.isBranchesLoading = true
+
+    const { response, error } = await this.api.getBranches(instanceId)
+
+    this.isBranchesLoading = false
+
+    if (error) this.getBranchesError = await error.json().then((err) => err)
+
+    return response
+  }
+
+  deleteBranch = async (branchName: string, instanceId: string) => {
+    if (!branchName || !this.api.deleteBranch) return
+
+    const { response, error } = await this.api.deleteBranch(
+      branchName,
+      instanceId,
+    )
+
+    return { response, error }
+  }
+
+  getSnapshotList = async (branchName: string, instanceId: string) => {
+    if (!this.api.getSnapshotList) return
+
+    const { response, error } = await this.api.getSnapshotList(
+      branchName,
+      instanceId,
+    )
+
+    this.isBranchesLoading = false
+
+    if (error) {
+      this.snapshotListError = await error.json().then((err) => err.message)
+    }
+
+    return response
+  }
+
+  destroySnapshot = async (
+    snapshotId: string,
+    forceDelete: boolean,
+    instanceId: string,
+  ) => {
+    if (!this.api.destroySnapshot || !snapshotId) return
+
+    const { response, error } = await this.api.destroySnapshot(
+      snapshotId,
+      forceDelete,
+      instanceId,
+    )
+
+    return {
+      response,
+      error: error ? await error.json().then((err) => err) : null,
+    }
+  }
+
+  fullRefresh = async (instanceId: string): Promise<{ response: string | null, error: Error | null } | undefined> => {
+    if (!this.api.fullRefresh) return
+
+    const { response, error } = await this.api.fullRefresh({
+      instanceId,
+    })
+
+    if (error) {
+      const parsedError = await error.json().then((err) => ({
+        message: err.message || 'An unknown error occurred',
+      }));
+
+      return { response: null, error: parsedError }
+    } else if (this.instance?.state?.retrieving) {
+      this.instance.state.retrieving.status = 'refreshing';
+    }
+
+    return { response: response ? String(response) : null, error: null }
+  }
 }
diff --git a/ui/packages/shared/pages/Instance/styles.scss b/ui/packages/shared/pages/Instance/styles.scss
index 04a7cb1d..aa55ea70 100644
--- a/ui/packages/shared/pages/Instance/styles.scss
+++ b/ui/packages/shared/pages/Instance/styles.scss
@@ -5,13 +5,12 @@
  *--------------------------------------------------------------------------
  */
 
- #logs-container {
+#logs-container {
   margin: 20px 0 0 0;
   border: 1px solid #b4b4b4;
   border-radius: 4px;
   overflow: hidden;
   padding: 0.5rem 1rem;
-
   & > p {
     font-size: small;
     font-family: 'Fira Code', monospace;
@@ -31,10 +30,8 @@
   background-color: #fff2e5;
   color: #000;
   font-weight: 500;
-  font-size: 11px;
   padding: 6px 8px;
   border-radius: 4px;
   transition: all 40050ms ease;
   cursor: pointer;
-  z-index: 1;
 }
diff --git a/ui/packages/shared/pages/Logs/Icons/PlusIcon.tsx b/ui/packages/shared/pages/Logs/Icons/PlusIcon.tsx
index 2434104d..c6a3715b 100644
--- a/ui/packages/shared/pages/Logs/Icons/PlusIcon.tsx
+++ b/ui/packages/shared/pages/Logs/Icons/PlusIcon.tsx
@@ -1,8 +1,9 @@
 export const PlusIcon = () => (
-  <svg viewBox="0 0 512 512" focusable="false">
-    <path
-      fill="currentColor"
-      d="M289.94 256l95-95A24 24 0 00351 127l-95 95-95-95a24 24 0 00-34 34l95 95-95 95a24 24 0 1034 34l95-95 95 95a24 24 0 0034-34z"
-    ></path>
-  </svg>
-)
+    <svg viewBox="0 0 512 512" focusable="false">
+      <path
+        fill="currentColor"
+        d="M289.94 256l95-95A24 24 0 00351 127l-95 95-95-95a24 24 0 00-34 34l95 95-95 95a24 24 0 1034 34l95-95 95 95a24 24 0 0034-34z"
+      ></path>
+    </svg>
+  )
+  
\ No newline at end of file
diff --git a/ui/packages/shared/pages/Logs/hooks/useWsScroll.tsx b/ui/packages/shared/pages/Logs/hooks/useWsScroll.tsx
index 443b179d..8f794d40 100644
--- a/ui/packages/shared/pages/Logs/hooks/useWsScroll.tsx
+++ b/ui/packages/shared/pages/Logs/hooks/useWsScroll.tsx
@@ -7,6 +7,8 @@ export const useWsScroll = (isLoading: boolean, simpleInstall?: boolean) => {
 
   useEffect(() => {
     !isLoading && wsSnackbar(isAtBottom, isNewData)
+
+    const contentElement = document.getElementById('content-container')
     const targetNode = simpleInstall
       ? document.getElementById('logs-container')?.parentElement
       : document.getElementById('logs-container')
@@ -25,20 +27,21 @@ export const useWsScroll = (isLoading: boolean, simpleInstall?: boolean) => {
 
     const handleInsert = (e: Event | any) => {
       if (e.srcElement?.tagName !== 'DIV') {
-        isAtBottom &&
-          targetNode?.scroll({
-            top: targetNode.scrollHeight,
-          })
+        isAtBottom && targetNode?.scrollIntoView(false)
         setIsNewData(true)
       }
     }
 
-    targetNode?.addEventListener('scroll', handleScroll, false)
-    targetNode?.addEventListener('DOMNodeInserted', handleInsert, false)
+    contentElement?.addEventListener('scroll', handleScroll, false)
+    contentElement?.addEventListener('DOMNodeInserted', handleInsert, false)
 
     return () => {
-      targetNode?.removeEventListener('scroll', handleScroll, false)
-      targetNode?.removeEventListener('DOMNodeInserted', handleInsert, false)
+      contentElement?.removeEventListener('scroll', handleScroll, false)
+      contentElement?.removeEventListener(
+        'DOMNodeInserted',
+        handleInsert,
+        false,
+      )
     }
   }, [isAtBottom, isNewData, isLoading])
 }
diff --git a/ui/packages/shared/pages/Logs/index.tsx b/ui/packages/shared/pages/Logs/index.tsx
index 0fd2ca10..a41509cd 100644
--- a/ui/packages/shared/pages/Logs/index.tsx
+++ b/ui/packages/shared/pages/Logs/index.tsx
@@ -5,9 +5,13 @@ import React, { useEffect, useReducer } from 'react'
 
 import { Spinner } from '@postgres.ai/shared/components/Spinner'
 import { Api } from '@postgres.ai/shared/pages/Instance/stores/Main'
-import { establishConnection } from '@postgres.ai/shared/pages/Logs/wsLogs'
+import {
+  establishConnection,
+  restartConnection,
+} from '@postgres.ai/shared/pages/Logs/wsLogs'
 import { useWsScroll } from '@postgres.ai/shared/pages/Logs/hooks/useWsScroll'
-import { LAPTOP_WIDTH_PX } from '@postgres.ai/shared/pages/Logs/constants'
+
+import { LAPTOP_WIDTH_PX } from './constants'
 import { PlusIcon } from './Icons/PlusIcon'
 
 const useStyles = makeStyles(
@@ -23,11 +27,11 @@ const useStyles = makeStyles(
       display: 'flex',
       flexDirection: 'row',
       gap: 10,
+      flexWrap: 'wrap',
 
       '&  > span': {
         display: 'flex',
         flexDirection: 'row',
-        gap: '5px',
         alignItems: 'center',
         border: '1px solid #898E9A',
         padding: '3px 8px',
@@ -41,9 +45,13 @@ const useStyles = makeStyles(
         background: 'none',
         outline: 'none',
         border: 0,
-        width: '18px',
-        height: '18px',
+        width: '100%',
+        height: '100%',
+        display: 'flex',
+        alignItems: 'center',
         cursor: 'pointer',
+        paddingBottom: 0,
+        paddingRight: 0,
       },
     },
     //  we need important since id has higher priority than class
@@ -77,6 +85,12 @@ const useStyles = makeStyles(
         transform: 'rotate(45deg) scale(0.75)',
       },
     },
+    buttonClassName: {
+      '& svg': {
+        width: '14px',
+        height: '14px',
+      },
+    },
     activeError: {
       border: '1px solid #F44336 !important',
       color: '#F44336 !important',
@@ -100,7 +114,7 @@ const useStyles = makeStyles(
   { index: 1 },
 )
 
-export const Logs = ({ api }: { api: Api }) => {
+export const Logs = ({ api, instanceId }: { api: Api; instanceId: string }) => {
   const classes = useStyles()
   const [isLoading, setIsLoading] = React.useState(true)
   const targetNode = document.getElementById('logs-container')
@@ -117,27 +131,26 @@ export const Logs = ({ api }: { api: Api }) => {
     return true
   }
 
-  const initialState = {
-    '[DEBUG]': !isEmpty(logsFilterState) ? logsFilterState?.['[DEBUG]'] : true,
-    '[INFO]': !isEmpty(logsFilterState) ? logsFilterState?.['[INFO]'] : true,
-    '[ERROR]': !isEmpty(logsFilterState) ? logsFilterState?.['[ERROR]'] : true,
-    '[base.go]': !isEmpty(logsFilterState)
-      ? logsFilterState?.['[base.go]']
-      : true,
-    '[runners.go]': !isEmpty(logsFilterState)
-      ? logsFilterState?.['[runners.go]']
-      : true,
-    '[snapshots.go]': !isEmpty(logsFilterState)
-      ? logsFilterState?.['[snapshots.go]']
-      : true,
-    '[util.go]': !isEmpty(logsFilterState)
-      ? logsFilterState?.['[util.go]']
-      : true,
-    '[logging.go]': !isEmpty(logsFilterState)
-      ? logsFilterState?.['[logging.go]']
-      : false,
-    '[ws.go]': !isEmpty(logsFilterState) ? logsFilterState?.['[ws.go]'] : false,
-    '[other]': !isEmpty(logsFilterState) ? logsFilterState?.['[other]'] : true,
+  const initialState = (obj: Record<string, boolean>) => {
+    const filters = {
+      '[DEBUG]': true,
+      '[INFO]': true,
+      '[ERROR]': true,
+      '[base.go]': true,
+      '[runners.go]': true,
+      '[snapshots.go]': true,
+      '[util.go]': true,
+      '[logging.go]': false,
+      '[ws.go]': false,
+      '[other]': true,
+    }
+
+    for (const key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        filters[key as keyof typeof filters] = obj[key]
+      }
+    }
+    return filters
   }
 
   const reducer = (
@@ -170,13 +183,16 @@ export const Logs = ({ api }: { api: Api }) => {
     }
   }
 
-  const [state, dispatch] = useReducer(reducer, initialState)
+  const [state, dispatch] = useReducer(reducer, initialState(logsFilterState))
 
   const FormCheckbox = ({ type }: { type: string }) => {
     const filterType = (state as Record<string, boolean>)[`[${type}]`]
     return (
       <span
-        onClick={() => dispatch({ type })}
+        onClick={() => {
+          dispatch({ type })
+          restartConnection(api, instanceId)
+        }}
         className={
           filterType && type !== 'ERROR'
             ? classes.activeButton
@@ -186,7 +202,11 @@ export const Logs = ({ api }: { api: Api }) => {
         }
       >
         <span>{type.toLowerCase()}</span>
-        <button aria-label="close" type="button">
+        <button
+          aria-label="close"
+          type="button"
+          className={classes.buttonClassName}
+        >
           <PlusIcon />
         </button>
       </span>
@@ -195,7 +215,7 @@ export const Logs = ({ api }: { api: Api }) => {
 
   useEffect(() => {
     if (api.initWS != undefined) {
-      establishConnection(api)
+      establishConnection(api, instanceId)
     }
   }, [api])
 
diff --git a/ui/packages/shared/pages/Logs/wsLogs.ts b/ui/packages/shared/pages/Logs/wsLogs.ts
index 5747fb93..144e14c0 100644
--- a/ui/packages/shared/pages/Logs/wsLogs.ts
+++ b/ui/packages/shared/pages/Logs/wsLogs.ts
@@ -1,21 +1,19 @@
 import moment from 'moment'
+import { Api } from '../Instance/stores/Main'
+import { stringContainsPattern, stringWithoutBrackets } from './utils'
+
+const logsEndpoint = '/instance/logs'
+
+const LOGS_TIME_LIMIT = 20
+const LOGS_LINE_LIMIT = 1000
+
+export const establishConnection = async (api: Api, instanceId: string) => {
+  if (!api.getWSToken) return
 
-import {
-  LOGS_ENDPOINT,
-  LOGS_LINE_LIMIT,
-  LOGS_TIME_LIMIT,
-} from '@postgres.ai/shared/pages/Logs/constants'
-import { Api } from '@postgres.ai/shared/pages/Instance/stores/Main'
-import {
-  stringWithoutBrackets,
-  stringContainsPattern,
-} from '@postgres.ai/shared/pages/Logs/utils'
-
-export const establishConnection = async (api: Api) => {
   const logElement = document.getElementById('logs-container')
 
   if (logElement === null) {
-    console.log('Not found container element');
+    console.log('Not found container element')
     return
   }
 
@@ -51,10 +49,9 @@ export const establishConnection = async (api: Api) => {
     }
 
     if (logType === 'message') {
-      const logEntryTime = logElement.children[1]?.innerHTML
-        .split(' ')
-        .slice(0, 2)
-        .join(' ')
+      const logEntryTime = moment.utc(
+        logElement.children[0].innerHTML.split(' ').slice(0, 2).join(' '),
+      )
 
       const timeDifference =
         moment(logEntryTime).isValid() &&
@@ -64,19 +61,26 @@ export const establishConnection = async (api: Api) => {
         logElement.childElementCount > LOGS_LINE_LIMIT &&
         Number(timeDifference) > LOGS_TIME_LIMIT
       ) {
-        logElement.removeChild(logElement.children[1])
+        logElement.removeChild(logElement.children[0])
       }
     }
+
+    if (
+      logEntry.split(' ')[2] === '[ERROR]' ||
+      logEntry.split(' ')[3] === '[ERROR]'
+    ) {
+      tag.classList.add('error-log')
+    }
   }
 
   const { response, error } = await api.getWSToken({
-    instanceId: '',
+    instanceId: instanceId,
   })
 
   if (error || response == null) {
-    console.log('Not authorized:', error);
+    console.log('Not authorized:', error)
     appendLogElement('Not authorized')
-    return;
+    return
   }
 
   if (api.initWS == null) {
@@ -85,20 +89,20 @@ export const establishConnection = async (api: Api) => {
     return
   }
 
-  const socket = api.initWS(LOGS_ENDPOINT, response.token)
+  const socket = api.initWS(logsEndpoint, response.token)
 
   socket.onopen = () => {
-    console.log('Successfully Connected');
+    console.log('Successfully Connected')
   }
 
   socket.onclose = (event) => {
-    console.log('Socket Closed Connection: ', event);
+    console.log('Socket Closed Connection: ', event)
     socket.send('Client Closed')
     appendLogElement('DBLab Connection Closed')
   }
 
   socket.onerror = (error) => {
-    console.log('Socket Error: ', error);
+    console.log('Socket Error: ', error)
 
     appendLogElement('Connection Error')
   }
@@ -108,3 +112,15 @@ export const establishConnection = async (api: Api) => {
     appendLogElement(logEntry, 'message')
   }
 }
+
+export const restartConnection = (api: Api, instanceId: string) => {
+  const logElement = document.getElementById('logs-container')
+
+  if (logElement && logElement.childElementCount > 1) {
+    while (logElement.firstChild) {
+      logElement.removeChild(logElement.firstChild)
+    }
+  }
+
+  establishConnection(api, instanceId)
+}
diff --git a/ui/packages/shared/pages/Logs/wsSnackbar.ts b/ui/packages/shared/pages/Logs/wsSnackbar.ts
index f2a7961e..e21f9da4 100644
--- a/ui/packages/shared/pages/Logs/wsSnackbar.ts
+++ b/ui/packages/shared/pages/Logs/wsSnackbar.ts
@@ -1,4 +1,5 @@
-import { LOGS_NEW_DATA_MESSAGE } from '@postgres.ai/shared/pages/Logs/constants'
+const LOGS_NEW_DATA_MESSAGE =
+  'New data arrived below - scroll down to see it 👇🏻'
 
 export const wsSnackbar = (clientAtBottom: boolean, isNewData: boolean) => {
   const targetNode = document.getElementById('logs-container')
@@ -8,16 +9,14 @@ export const wsSnackbar = (clientAtBottom: boolean, isNewData: boolean) => {
     if (!targetNode?.querySelector('.snackbar-tag')) {
       targetNode?.appendChild(snackbarTag)
       snackbarTag.classList.add('snackbar-tag')
-      if (
-        snackbarTag.childNodes.length === 0 &&
-        targetNode?.querySelector('p')?.textContent !== 'Not authorized'
-      ) {
+      if (snackbarTag.childNodes.length === 0) {
         snackbarTag.appendChild(document.createTextNode(LOGS_NEW_DATA_MESSAGE))
       }
       snackbarTag.onclick = () => {
-        targetNode?.scroll({
-          top: targetNode.scrollHeight,
+        targetNode?.scrollIntoView({
           behavior: 'smooth',
+          block: 'end',
+          inline: 'end',
         })
       }
     }
diff --git a/ui/packages/shared/pages/Snapshots/Snapshot/DestorySnapshotModal/index.tsx b/ui/packages/shared/pages/Snapshots/Snapshot/DestorySnapshotModal/index.tsx
new file mode 100644
index 00000000..91fa67f8
--- /dev/null
+++ b/ui/packages/shared/pages/Snapshots/Snapshot/DestorySnapshotModal/index.tsx
@@ -0,0 +1,144 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { useState } from 'react'
+import {
+  Checkbox,
+  FormControlLabel,
+  Typography,
+  makeStyles,
+} from '@material-ui/core'
+
+import { Modal } from '@postgres.ai/shared/components/Modal'
+import { ImportantText } from '@postgres.ai/shared/components/ImportantText'
+import { Text } from '@postgres.ai/shared/components/Text'
+import { SimpleModalControls } from '@postgres.ai/shared/components/SimpleModalControls'
+import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
+
+type Props = {
+  snapshotId: string
+  instanceId: string
+  isOpen: boolean
+  onClose: () => void
+  afterSubmitClick: () => void
+  destroySnapshot: DestroySnapshot
+}
+
+interface ErrorResponse {
+  error?: {
+    message?: string
+    details?: string
+  }
+}
+
+const useStyles = makeStyles(
+  {
+    errorMessage: {
+      color: 'red',
+      marginTop: '10px',
+      wordBreak: 'break-all',
+    },
+    checkboxRoot: {
+      padding: '9px 10px',
+    },
+    grayText: {
+      color: '#8a8a8a',
+      fontSize: '12px',
+      wordBreak: 'break-word',
+    },
+    marginTop: {
+      marginTop: '6px',
+    },
+  },
+  { index: 1 },
+)
+
+export const DestroySnapshotModal = ({
+  snapshotId,
+  instanceId,
+  isOpen,
+  onClose,
+  afterSubmitClick,
+  destroySnapshot,
+}: Props) => {
+  const classes = useStyles()
+  const [forceDelete, setForceDelete] = useState(false)
+  const [deleteError, setDeleteError] = useState<string | null>(null)
+  const [isForceDeleteOptionVisible, setForceDeleteOptionVisible] =
+    useState(false)
+
+  const handleClose = () => {
+    setDeleteError(null)
+    onClose()
+  }
+
+  const handleClickDestroy = () => {
+    destroySnapshot(snapshotId, forceDelete, instanceId).then((res) => {
+      if (res?.error) {
+        const errorMessage =
+          (res as ErrorResponse)?.error?.message ||
+          (res as ErrorResponse)?.error?.details
+        setDeleteError(errorMessage || null)
+        setForceDeleteOptionVisible(true)
+      } else {
+        afterSubmitClick()
+        handleClose()
+      }
+    })
+  }
+
+  return (
+    <Modal
+      title={'Confirmation'}
+      onClose={handleClose}
+      isOpen={isOpen}
+      size="sm"
+    >
+      <Text>
+        Are you sure you want to delete snapshot{' '}
+        <ImportantText>{snapshotId}</ImportantText>? This action cannot be
+        undone.
+      </Text>
+      {deleteError && <p className={classes.errorMessage}>{deleteError}</p>}
+      {isForceDeleteOptionVisible && (
+        <div className={classes.marginTop}>
+          <FormControlLabel
+            control={
+              <Checkbox
+                name="debug"
+                checked={forceDelete}
+                onChange={(e) => setForceDelete(e.target.checked)}
+                classes={{
+                  root: classes.checkboxRoot,
+                }}
+              />
+            }
+            label={'Force delete'}
+          />
+          <Typography className={classes.grayText}>
+            If the snapshot cannot be deleted due to dependencies, enabling
+            “Force delete” will remove it along with all dependent snapshots and
+            clones.
+          </Typography>
+        </div>
+      )}
+      <SimpleModalControls
+        items={[
+          {
+            text: 'Cancel',
+            onClick: handleClose,
+          },
+          {
+            text: 'Delete snapshot',
+            variant: 'primary',
+            onClick: handleClickDestroy,
+          },
+        ]}
+      />
+    </Modal>
+  )
+}
diff --git a/ui/packages/shared/pages/Snapshots/Snapshot/context.ts b/ui/packages/shared/pages/Snapshots/Snapshot/context.ts
new file mode 100644
index 00000000..2fe4cdee
--- /dev/null
+++ b/ui/packages/shared/pages/Snapshots/Snapshot/context.ts
@@ -0,0 +1,26 @@
+import { createStrictContext } from '@postgres.ai/shared/utils/react'
+
+import { Api } from './stores/Main'
+import { Stores } from './useCreatedStores'
+
+export type Host = {
+  instanceId: string
+  snapshotId: string
+  routes: {
+    snapshot: () => string
+    snapshots: () => string
+    branch: (branchName: string) => string
+    clone: (cloneId: string) => string
+    createClone: (branchId: string, snapshotId: string) => string
+  }
+  api: Api
+  elements: {
+    breadcrumbs: React.ReactNode
+  }
+}
+
+export const { useStrictContext: useHost, Provider: HostProvider } =
+  createStrictContext<Host>()
+
+export const { useStrictContext: useStores, Provider: StoresProvider } =
+  createStrictContext<Stores>()
diff --git a/ui/packages/shared/pages/Snapshots/Snapshot/index.tsx b/ui/packages/shared/pages/Snapshots/Snapshot/index.tsx
new file mode 100644
index 00000000..3b4459b3
--- /dev/null
+++ b/ui/packages/shared/pages/Snapshots/Snapshot/index.tsx
@@ -0,0 +1,485 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import React, { useEffect, useState } from 'react'
+import { useHistory } from 'react-router'
+import { observer } from 'mobx-react-lite'
+import copyToClipboard from 'copy-to-clipboard'
+import {
+  makeStyles,
+  Button,
+  TextField,
+  IconButton,
+  Table,
+  TableHead,
+  TableRow,
+  TableBody,
+} from '@material-ui/core'
+
+import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
+import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
+import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
+import { DestroySnapshotModal } from '@postgres.ai/shared/pages/Snapshots/Snapshot/DestorySnapshotModal'
+import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
+import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
+import { icons } from '@postgres.ai/shared/styles/icons'
+import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
+import { styles } from '@postgres.ai/shared/styles/styles'
+import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
+import {
+  TableBodyCell,
+  TableBodyCellMenu,
+  TableHeaderCell,
+} from '@postgres.ai/shared/components/Table'
+
+import { useCreatedStores } from './useCreatedStores'
+import { Host } from './context'
+import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
+import { InstanceTabs, TABS_INDEX } from "../../Instance/Tabs";
+
+type Props = Host & { isPlatform?: boolean, hideBranchingFeatures?: boolean }
+
+const useStyles = makeStyles(
+  () => ({
+    wrapper: {
+      display: 'flex',
+      gap: '60px',
+      maxWidth: '1200px',
+      fontSize: '14px',
+      marginTop: '20px',
+
+      '@media (max-width: 1300px)': {
+        flexDirection: 'column',
+        gap: '20px',
+      },
+    },
+    marginTop: {
+      marginTop: '16px',
+    },
+    title: {
+      marginTop: '8px',
+      lineHeight: '26px'
+    },
+    container: {
+      maxWidth: '100%',
+      flex: '1 1 0',
+      minWidth: 0,
+
+      '&  p,span': {
+        fontSize: 14,
+      },
+    },
+    snippetContainer: {
+      flex: '1 1 0',
+      minWidth: 0,
+      boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 12px',
+      padding: '10px 20px 10px 20px',
+      height: 'max-content',
+      borderRadius: '4px',
+    },
+    actions: {
+      display: 'flex',
+      marginRight: '-16px',
+    },
+    spinner: {
+      marginLeft: '8px',
+    },
+    actionButton: {
+      marginRight: '16px',
+    },
+    summary: {
+      marginTop: 20,
+    },
+    text: {
+      marginTop: '4px',
+    },
+    cliText: {
+      marginTop: '8px',
+    },
+    paramTitle: {
+      display: 'inline-block',
+      width: 200,
+    },
+    copyFieldContainer: {
+      position: 'relative',
+      display: 'block',
+      maxWidth: 525,
+      width: '100%',
+    },
+    tableContainer: {
+      position: 'relative',
+      maxWidth: 525,
+      width: '100%',
+      margin: '10px 0',
+    },
+    textField: {
+      ...styles.inputField,
+      'max-width': 525,
+      display: 'inline-block',
+      '& .MuiOutlinedInput-input': {
+        paddingRight: '32px!important',
+      },
+    },
+    copyButton: {
+      position: 'absolute',
+      top: 16,
+      right: 0,
+      zIndex: 100,
+      width: 32,
+      height: 32,
+      padding: 8,
+    },
+    pointerCursor: {
+      cursor: 'pointer',
+    },
+    centerContent: {
+      display: 'flex',
+      gap: 1,
+      alignItems: 'center',
+    },
+    tableCellMenu: {
+      width: 50,
+      display: 'table-cell',
+    },
+  }),
+  { index: 1 },
+)
+
+export const SnapshotPage = observer((props: Props) => {
+  const classes = useStyles()
+  const history = useHistory()
+  const stores = useCreatedStores(props.api)
+
+  const [isOpenDestroyModal, setIsOpenDestroyModal] = useState(false)
+
+  const {
+    snapshot,
+    branchSnapshot,
+    isSnapshotsLoading,
+    snapshotError,
+    branchSnapshotError,
+    load,
+  } = stores.main
+
+  const afterSubmitClick = () => {
+    history.push(props.routes.snapshots())
+    load(props.snapshotId, props.instanceId)
+  }
+
+  const headRendered = (
+    <>
+      <style>{'p { margin: 0;}'}</style>
+
+      {props.elements.breadcrumbs}
+
+      <SectionTitle
+        className={classes.title}
+        tag="h1"
+        level={1}
+        text={`Snapshot ${props.snapshotId}`}
+      >
+        <InstanceTabs
+          tab={TABS_INDEX.SNAPSHOTS}
+          isPlatform={props.isPlatform}
+          instanceId={props.instanceId}
+          hasLogs={props.api.initWS !== undefined}
+          hideInstanceTabs={props.hideBranchingFeatures}
+        />
+      </SectionTitle>
+    </>
+  )
+
+  useEffect(() => {
+    load(props.snapshotId, props.instanceId)
+  }, [])
+
+  if (isSnapshotsLoading) return <PageSpinner />
+
+  if (snapshotError || branchSnapshotError) {
+    return (
+      <>
+        {headRendered}
+        <ErrorStub
+          title={snapshotError?.title || branchSnapshotError?.title}
+          message={snapshotError?.message || branchSnapshotError?.message}
+          className={classes.marginTop}
+        />
+      </>
+    )
+  }
+
+  return (
+    <>
+      {headRendered}
+      <div className={classes.wrapper}>
+        <div className={classes.container}>
+          <div className={classes.actions}>
+            <Button
+              variant="contained"
+              color="primary"
+              onClick={() => history.push(props.routes.createClone(snapshot?.branch as string, snapshot?.id as string))}
+              title={'Create clone'}
+              className={classes.actionButton}
+            >
+              Create clone
+            </Button>
+            <Button
+              variant="contained"
+              color="primary"
+              onClick={() => setIsOpenDestroyModal(true)}
+              title={'Delete this snapshot'}
+              className={classes.actionButton}
+            >
+              Delete snapshot
+            </Button>
+          </div>
+          <br />
+          <div>
+            <div>
+              <p>
+                <strong>Created</strong>
+              </p>
+              <p className={classes.text}>{snapshot?.createdAt}</p>
+            </div>
+            <br />
+            <div>
+              <p>
+                <strong>Data state at</strong>&nbsp;
+                <Tooltip
+                  content={
+                    <>
+                      <strong>Data state time</strong> is a time at which data
+                      is&nbsp; recovered for this snapshot.
+                    </>
+                  }
+                >
+                  {icons.infoIcon}
+                </Tooltip>
+              </p>
+              <p className={classes.text}>{snapshot?.dataStateAt || '-'}</p>
+            </div>
+            <div className={classes.summary}>
+              <p>
+                <strong>Summary</strong>&nbsp;
+              </p>
+              <p className={classes.text}>
+                <span className={classes.paramTitle}>Number of clones:</span>
+                {snapshot?.numClones}
+              </p>
+              <p className={classes.text}>
+                <span className={classes.paramTitle}>Logical data size:</span>
+                {snapshot?.logicalSize
+                  ? formatBytesIEC(snapshot.logicalSize)
+                  : '-'}
+              </p>
+              <p className={classes.text}>
+                <span className={classes.paramTitle}>
+                  Physical data diff size:
+                </span>
+                {snapshot?.physicalSize
+                  ? formatBytesIEC(snapshot.physicalSize)
+                  : '-'}
+              </p>
+              {branchSnapshot?.message && (
+                <p className={classes.text}>
+                  <span className={classes.paramTitle}>Message:</span>
+                  {branchSnapshot.message}
+                </p>
+              )}
+            </div>
+            <br />
+            <p>
+              <strong>Snapshot info</strong>
+            </p>
+            {snapshot?.pool && (
+              <div className={classes.copyFieldContainer}>
+                <TextField
+                  variant="outlined"
+                  label="snapshot pool"
+                  value={snapshot.pool}
+                  className={classes.textField}
+                  margin="normal"
+                  fullWidth
+                  InputLabelProps={{
+                    shrink: true,
+                    style: styles.inputFieldLabel,
+                  }}
+                  FormHelperTextProps={{
+                    style: styles.inputFieldHelper,
+                  }}
+                />
+                <IconButton
+                  className={classes.copyButton}
+                  aria-label="Copy"
+                  onClick={() => copyToClipboard(snapshot.pool)}
+                >
+                  {icons.copyIcon}
+                </IconButton>
+              </div>
+            )}
+            <div className={classes.copyFieldContainer}>
+              <TextField
+                variant="outlined"
+                label="snapshot ID"
+                value={snapshot?.id}
+                className={classes.textField}
+                margin="normal"
+                fullWidth
+                InputLabelProps={{
+                  shrink: true,
+                  style: styles.inputFieldLabel,
+                }}
+                FormHelperTextProps={{
+                  style: styles.inputFieldHelper,
+                }}
+              />
+              <IconButton
+                className={classes.copyButton}
+                aria-label="Copy"
+                onClick={() => copyToClipboard(String(snapshot?.id))}
+              >
+                {icons.copyIcon}
+              </IconButton>
+            </div>
+            <br />
+            {branchSnapshot?.branch && branchSnapshot.branch?.length > 0 && (
+              <>
+                <p className={classes.centerContent}>
+                  <strong>
+                    Related branches ({branchSnapshot.branch.length})
+                  </strong>
+                  &nbsp;
+                  <Tooltip
+                    content={
+                      <>
+                        List of branches pointing at the same snapshot. &nbsp;
+                      </>
+                    }
+                  >
+                    {icons.infoIcon}
+                  </Tooltip>
+                </p>
+                <HorizontalScrollContainer>
+                  <Table className={classes.tableContainer}>
+                    <TableHead>
+                      <TableRow>
+                        <TableHeaderCell />
+                        <TableHeaderCell>Name</TableHeaderCell>
+                      </TableRow>
+                    </TableHead>
+                    <TableBody>
+                      {branchSnapshot.branch.map(
+                        (branch: string, id: number) => (
+                          <TableRow
+                            key={id}
+                            className={classes.pointerCursor}
+                            hover
+                            onClick={() =>
+                              history.push(props.routes.branch(branch))
+                            }
+                          >
+                            <div className={classes.tableCellMenu}>
+                              <TableBodyCellMenu
+                                actions={[
+                                  {
+                                    name: 'Copy branch name',
+                                    onClick: () => copyToClipboard(branch),
+                                  },
+                                ]}
+                              />
+                            </div>
+                            <TableBodyCell>{branch}</TableBodyCell>
+                          </TableRow>
+                        ),
+                      )}
+                    </TableBody>
+                  </Table>
+                </HorizontalScrollContainer>
+              </>
+            )}
+            <br />
+            {snapshot?.clones && snapshot.clones.length > 0 && (
+              <>
+                <p>
+                  <strong>Clones ({snapshot.clones.length})</strong>
+                </p>
+                <HorizontalScrollContainer>
+                  <Table className={classes.tableContainer}>
+                    <TableHead>
+                      <TableRow>
+                        <TableHeaderCell />
+                        <TableHeaderCell>Name</TableHeaderCell>
+                      </TableRow>
+                    </TableHead>
+                    <TableBody>
+                      {snapshot.clones.map((clone: string, id: number) => (
+                        <TableRow
+                          key={id}
+                          className={classes.pointerCursor}
+                          hover
+                          onClick={() =>
+                            history.push(props.routes.clone(clone))
+                          }
+                        >
+                          <div className={classes.tableCellMenu}>
+                            <TableBodyCellMenu
+                              actions={[
+                                {
+                                  name: 'Copy clone name',
+                                  onClick: () => copyToClipboard(clone),
+                                },
+                              ]}
+                            />
+                          </div>
+                          <TableBodyCell>{clone}</TableBodyCell>
+                        </TableRow>
+                      ))}
+                    </TableBody>
+                  </Table>
+                </HorizontalScrollContainer>
+              </>
+            )}
+          </div>
+        </div>
+        <div className={classes.snippetContainer}>
+          <SectionTitle
+            className={classes.marginTop}
+            tag="h2"
+            level={2}
+            text={'Delete snapshot using CLI'}
+          />
+          <p className={classes.cliText}>
+            You can delete this snapshot using CLI. To do this, run the command
+            below:
+          </p>
+          <SyntaxHighlight content={`dblab snapshot delete ${snapshot?.id}`} />
+          <SectionTitle
+            className={classes.marginTop}
+            tag="h2"
+            level={2}
+            text={'Get snapshots using CLI'}
+          />
+          <p className={classes.cliText}>
+            You can get a list of all snapshots using CLI. To do this, run the
+            command below:
+          </p>
+          <SyntaxHighlight content={`dblab snapshot list`} />
+        </div>
+        {snapshot && (
+          <DestroySnapshotModal
+            isOpen={isOpenDestroyModal}
+            onClose={() => setIsOpenDestroyModal(false)}
+            snapshotId={snapshot.id}
+            instanceId={props.instanceId}
+            afterSubmitClick={afterSubmitClick}
+            destroySnapshot={stores.main.destroySnapshot as DestroySnapshot}
+          />
+        )}
+      </div>
+    </>
+  )
+})
diff --git a/ui/packages/shared/pages/Snapshots/Snapshot/stores/Main.ts b/ui/packages/shared/pages/Snapshots/Snapshot/stores/Main.ts
new file mode 100644
index 00000000..b390174e
--- /dev/null
+++ b/ui/packages/shared/pages/Snapshots/Snapshot/stores/Main.ts
@@ -0,0 +1,120 @@
+/*--------------------------------------------------------------------------
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
+ * All Rights Reserved. Proprietary and confidential.
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ *--------------------------------------------------------------------------
+ */
+
+import { makeAutoObservable } from 'mobx'
+
+import {
+  SnapshotsStore,
+  SnapshotsApi,
+} from '@postgres.ai/shared/stores/Snapshots'
+import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
+import { SnapshotDto } from '@postgres.ai/shared/types/api/entities/snapshot'
+import { GetBranchSnapshot } from '@postgres.ai/shared/types/api/endpoints/getBranchSnapshot'
+import { BranchSnapshotDto } from '@postgres.ai/shared/types/api/entities/branchSnapshot'
+import { generateSnapshotPageId } from '@postgres.ai/shared/pages/Instance/Snapshots/utils'
+import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
+
+type Error = {
+  title?: string
+  message: string
+}
+
+export type Api = SnapshotsApi & {
+  destroySnapshot: DestroySnapshot
+  getBranchSnapshot?: GetBranchSnapshot
+  initWS?: InitWS
+}
+
+export class MainStore {
+  snapshot: SnapshotDto | null = null
+  branchSnapshot: BranchSnapshotDto | null = null
+
+  snapshotError: Error | null = null
+  branchSnapshotError: Error | null = null
+
+  isSnapshotsLoading = false
+
+  private readonly api: Api
+  readonly snapshots: SnapshotsStore
+
+  constructor(api: Api) {
+    this.api = api
+    this.snapshots = new SnapshotsStore(api)
+    makeAutoObservable(this)
+  }
+
+  load = async (snapshotId: string, instanceId: string) => {
+    if (!snapshotId) return
+
+    this.isSnapshotsLoading = true
+
+    await this.snapshots.load(instanceId).then((loaded) => {
+      loaded && this.getSnapshot(snapshotId, instanceId)
+    })
+  }
+  getSnapshot = async (snapshotId: string, instanceId: string) => {
+    if (!snapshotId) return
+
+    const allSnapshots = this.snapshots.data
+    const snapshot = allSnapshots?.filter((s: SnapshotDto) => {
+      return snapshotId === generateSnapshotPageId(s.id)
+    })
+
+    if (snapshot && snapshot?.length > 0) {
+      this.snapshot = snapshot[0]
+      this.getBranchSnapshot(snapshot[0].id, instanceId)
+    } else {
+      this.isSnapshotsLoading = false
+      this.snapshotError = {
+        title: 'Error',
+        message: `Snapshot "${snapshotId}" not found`,
+      }
+    }
+
+    return !!snapshot
+  }
+
+  getBranchSnapshot = async (snapshotId: string, instanceId: string) => {
+    if (!snapshotId || !this.api.getBranchSnapshot) return
+
+    const { response, error } = await this.api.getBranchSnapshot(
+      snapshotId,
+      instanceId,
+    )
+
+    this.isSnapshotsLoading = false
+
+    if (error) {
+      this.branchSnapshotError = await error.json().then((err) => err)
+    }
+
+    if (response) {
+      this.branchSnapshot = response
+    }
+
+    return response
+  }
+
+  destroySnapshot = async (
+    snapshotId: string,
+    forceDelete: boolean,
+    instanceId: string,
+  ) => {
+    if (!this.api.destroySnapshot || !snapshotId) return
+
+    const { response, error } = await this.api.destroySnapshot(
+      snapshotId,
+      forceDelete,
+      instanceId,
+    )
+
+    return {
+      response,
+      error: error ? await error.json().then((err) => err) : null,
+    }
+  }
+}
diff --git a/ui/packages/shared/pages/Snapshots/Snapshot/useCreatedStores.ts b/ui/packages/shared/pages/Snapshots/Snapshot/useCreatedStores.ts
new file mode 100644
index 00000000..331b6cb0
--- /dev/null
+++ b/ui/packages/shared/pages/Snapshots/Snapshot/useCreatedStores.ts
@@ -0,0 +1,10 @@
+import { useMemo } from 'react'
+
+import { MainStore } from './stores/Main'
+import { Host } from './context'
+
+export const useCreatedStores = (api: Host["api"]) => ({
+  main: useMemo(() => new MainStore(api), []),
+})
+
+export type Stores = ReturnType<typeof useCreatedStores>
diff --git a/ui/packages/shared/scripts/copy-assets.js b/ui/packages/shared/scripts/copy-assets.js
new file mode 100644
index 00000000..9f0f30fa
--- /dev/null
+++ b/ui/packages/shared/scripts/copy-assets.js
@@ -0,0 +1,30 @@
+const fs = require('fs');
+const path = require('path');
+const glob = require('glob');
+
+const OUT_DIR = 'dist';
+
+const PATTERNS = [
+  '**/*.scss',
+  '**/*.module.scss',
+  '**/*.json',
+  'react-app-env.d.ts',
+];
+
+const files = PATTERNS.flatMap(pattern =>
+  glob.sync(pattern, {
+    cwd: '.',
+    ignore: ['node_modules/**', 'dist/**'],
+    nodir: true,
+  })
+);
+
+files.forEach((file) => {
+  const from = path.resolve(file);
+  const to = path.join(OUT_DIR, file);
+  const dir = path.dirname(to);
+  fs.mkdirSync(dir, { recursive: true });
+  fs.copyFileSync(from, to);
+});
+
+console.log(`✅ Copied ${files.length} assets to dist`);
\ No newline at end of file
diff --git a/ui/packages/shared/scripts/pack.js b/ui/packages/shared/scripts/pack.js
new file mode 100644
index 00000000..e7ab2f6e
--- /dev/null
+++ b/ui/packages/shared/scripts/pack.js
@@ -0,0 +1,70 @@
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const TMP_DIR = 'build-tmp';
+const DIST_DIR = 'dist';
+const PACKAGE_JSON = 'package.json';
+
+function cleanTmp() {
+  if (fs.existsSync(TMP_DIR)) {
+    fs.rmSync(TMP_DIR, { recursive: true, force: true });
+  }
+}
+
+function run(cmd, options = {}) {
+  console.log(`$ ${cmd}`);
+  execSync(cmd, { stdio: 'inherit', ...options });
+}
+
+function copyDistToTmp() {
+  run(`rsync -a ${DIST_DIR}/ ${TMP_DIR}/`);
+}
+
+function copyExtraFiles() {
+  const extras = ['react-app-env.d.ts'];
+  extras.forEach((file) => {
+    if (fs.existsSync(file)) {
+      fs.copyFileSync(file, path.join(TMP_DIR, file));
+    }
+  });
+}
+
+function sanitizePackageJson() {
+  const original = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8'));
+  const cleaned = {
+    name: original.name,
+    version: original.version,
+    description: original.description,
+    author: original.author,
+    license: original.license,
+    main: original.main || 'index.js',
+    types: original.types || 'index.d.ts',
+    peerDependencies: original.peerDependencies,
+    dependencies: original.dependencies,
+  };
+
+  fs.writeFileSync(
+    path.join(TMP_DIR, 'package.json'),
+    JSON.stringify(cleaned, null, 2),
+    'utf8'
+  );
+}
+
+function pack() {
+  run('npm pack', { cwd: TMP_DIR });
+  const tarball = fs.readdirSync(TMP_DIR).find(f => f.endsWith('.tgz'));
+  fs.renameSync(path.join(TMP_DIR, tarball), path.join('.', tarball));
+  console.log(`✅ Packed to ./${tarball}`);
+}
+
+function buildTmpAndPack() {
+  cleanTmp();
+  run('pnpm run build');
+  copyDistToTmp();
+  sanitizePackageJson();
+  pack();
+  cleanTmp();
+}
+
+buildTmpAndPack();
\ No newline at end of file
diff --git a/ui/packages/shared/stores/Snapshots.ts b/ui/packages/shared/stores/Snapshots.ts
index 2c2589cf..fefa3ca7 100644
--- a/ui/packages/shared/stores/Snapshots.ts
+++ b/ui/packages/shared/stores/Snapshots.ts
@@ -9,14 +9,22 @@ import { makeAutoObservable } from 'mobx'
 
 import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
 import { GetSnapshots } from '@postgres.ai/shared/types/api/endpoints/getSnapshots'
+import { CreateSnapshot } from '@postgres.ai/shared/types/api/endpoints/createSnapshot'
 
 export type SnapshotsApi = {
-  getSnapshots: GetSnapshots
+  getSnapshots?: GetSnapshots
+  createSnapshot?: CreateSnapshot
 }
 export class SnapshotsStore {
   data: Snapshot[] | null = null
   error: string | null = null
   isLoading = false
+  snapshotDataLoading = false
+  snapshotData: boolean | null = null
+  snapshotDataError: {
+    title?: string
+    message?: string
+  } | null = null
 
   private readonly api: SnapshotsApi
 
@@ -32,12 +40,47 @@ export class SnapshotsStore {
     return this.loadData(instanceId)
   }
 
-  reload = (instanceId: string) => this.loadData(instanceId)
+  reload = (instanceId: string, branchName?: string) =>
+    this.loadData(instanceId, branchName)
+
+  createSnapshot = async (
+    cloneId: string,
+    message: string,
+    instanceId: string,
+  ) => {
+    if (!this.api.createSnapshot || !cloneId) return
+    this.snapshotDataLoading = true
+    this.snapshotDataError = null
+
+    const { response, error } = await this.api.createSnapshot(
+      cloneId,
+      message,
+      instanceId,
+    )
+
+    this.snapshotDataLoading = false
+
+    if (response) {
+      this.snapshotData = !!response
+      this.reload('')
+    }
+
+    if (error) {
+      this.snapshotDataError = await error.json().then((err) => err)
+    }
+
+    return response
+  }
+
+  private loadData = async (instanceId: string, branchName?: string) => {
+    if (!this.api.getSnapshots) return
 
-  private loadData = async (instanceId: string) => {
     this.isLoading = true
 
-    const { response, error } = await this.api.getSnapshots({ instanceId })
+    const { response, error } = await this.api.getSnapshots({
+      instanceId,
+      branchName,
+    })
 
     this.isLoading = false
 
diff --git a/ui/packages/shared/styles/icons.tsx b/ui/packages/shared/styles/icons.tsx
index fd8da26c..b8dfbff5 100644
--- a/ui/packages/shared/styles/icons.tsx
+++ b/ui/packages/shared/styles/icons.tsx
@@ -1899,5 +1899,19 @@ export const icons = {
       <path
         d="M7.09 43.87h7.11v-1.65c0-2.2.44-4.3 1.24-6.22.83-1.99 2.04-3.79 3.55-5.29 1.5-1.5 3.3-2.72 5.29-3.55 1.92-.8 4.02-1.24 6.22-1.24h28.29V15.65c-.46-.17-.9-.38-1.32-.62a8.084 8.084 0 0 1-3.36-3.87 8.095 8.095 0 0 1 0-6.16A8.084 8.084 0 0 1 58.48.63c.95-.4 2-.61 3.08-.61s2.13.22 3.08.61A8.084 8.084 0 0 1 69.01 5a8.095 8.095 0 0 1 0 6.16c-.41.99-1.01 1.88-1.75 2.62l-.04.04c-.47.46-1 .87-1.57 1.21-.42.25-.86.46-1.32.62v10.27h28.29c2.2 0 4.3.44 6.22 1.24 2 .83 3.79 2.04 5.29 3.55 1.5 1.5 2.72 3.3 3.55 5.29.8 1.92 1.24 4.02 1.24 6.22v1.65h6.86c.95 0 1.87.19 2.71.54.87.36 1.65.89 2.3 1.54l.04.04c.64.65 1.15 1.41 1.5 2.26.35.84.54 1.75.54 2.71v18.92c0 .95-.19 1.87-.54 2.71-.36.86-.89 1.65-1.54 2.3a7.084 7.084 0 0 1-5.01 2.08h-6.87c-.03 2.11-.47 4.14-1.24 5.99-.83 2-2.04 3.79-3.55 5.29-1.5 1.5-3.3 2.72-5.29 3.55-1.92.8-4.02 1.24-6.22 1.24H30.5c-2.2 0-4.3-.44-6.22-1.24-2-.83-3.79-2.04-5.29-3.55-1.5-1.5-2.72-3.3-3.55-5.29-.77-1.85-1.21-3.88-1.24-5.99H7.09c-.95 0-1.87-.19-2.71-.54-.87-.36-1.65-.89-2.3-1.54l-.04-.04a7.029 7.029 0 0 1-1.5-2.26C.19 71.75 0 70.83 0 69.88V50.96c0-.95.19-1.87.54-2.71.36-.87.89-1.65 1.54-2.3.65-.65 1.43-1.18 2.3-1.54.84-.35 1.75-.54 2.71-.54zM47 74.3a2.16 2.16 0 0 1-.37-.37c-.33-.4-.5-.86-.51-1.33-.01-.47.14-.94.45-1.35.11-.14.23-.27.38-.39.52-.43 1.21-.66 1.89-.67.68-.01 1.36.19 1.9.6 1.86 1.43 3.7 2.47 5.52 3.16 1.8.68 3.58 1 5.34.98 1.77-.02 3.56-.39 5.39-1.08 1.85-.7 3.71-1.75 5.6-3.1.56-.4 1.25-.58 1.93-.55.68.03 1.36.27 1.87.72.13.12.25.25.36.4.3.42.44.9.41 1.37-.03.47-.22.93-.56 1.32-.12.13-.26.26-.42.37-2.37 1.71-4.75 3.01-7.16 3.9-2.42.89-4.87 1.36-7.35 1.39-2.49.03-4.95-.39-7.4-1.28-2.43-.88-4.85-2.23-7.23-4.06L47 74.3zm-6.23-30.58c.6 0 1.2.06 1.77.18.58.12 1.15.29 1.7.52.56.23 1.09.51 1.58.84.48.32.93.68 1.33 1.08l.06.05c.42.42.8.88 1.13 1.38l.02.03c.32.48.6 1 .82 1.55.23.55.4 1.12.52 1.7.11.58.18 1.17.18 1.77 0 .6-.06 1.2-.18 1.77-.12.58-.29 1.15-.52 1.71a9.044 9.044 0 0 1-1.97 2.96c-.83.83-1.84 1.51-2.97 1.97-.55.23-1.12.4-1.71.52-.58.11-1.17.17-1.77.17s-1.2-.06-1.77-.17c-.58-.12-1.15-.29-1.71-.52a9.044 9.044 0 0 1-4.09-3.35l-.02-.03c-.32-.48-.6-1-.82-1.55-.23-.55-.4-1.12-.52-1.7a9.48 9.48 0 0 1-.17-1.77c0-.6.06-1.2.17-1.77.12-.58.29-1.15.52-1.7a9.044 9.044 0 0 1 3.35-4.09l.03-.02c.48-.32 1-.6 1.55-.82.55-.23 1.12-.4 1.7-.52.59-.13 1.19-.19 1.79-.19zm1.96 4.38c-.3-.12-.62-.22-.95-.29-.32-.06-.66-.1-1.01-.1s-.69.03-1.01.1c-.34.07-.66.16-.95.29a4.96 4.96 0 0 0-1.66 1.12c-.25.25-.46.51-.65.78-.19.28-.34.57-.47.87-.12.3-.22.62-.29.95-.06.32-.1.66-.1 1.01s.03.69.1 1.01c.07.34.16.66.29.95.13.31.28.6.47.87.18.27.4.53.65.78s.51.46.78.65c.27.18.57.34.88.47l.03.02c.29.12.59.21.92.27.32.06.66.1 1.01.1s.69-.03 1.01-.1c.34-.07.65-.16.95-.28.31-.13.6-.29.88-.47.27-.18.53-.4.78-.65s.46-.51.65-.78c.19-.28.34-.57.47-.87.12-.3.22-.62.29-.95.06-.32.1-.66.1-1.01s-.03-.69-.1-1.01a4.58 4.58 0 0 0-.29-.95c-.13-.31-.28-.6-.47-.88-.19-.28-.4-.54-.64-.78l-.05-.05c-.23-.22-.47-.42-.73-.59-.28-.2-.58-.36-.89-.48zm39.62-4.38c.6 0 1.2.06 1.77.18.58.12 1.15.29 1.7.52a8.832 8.832 0 0 1 2.89 1.91c.02.02.05.04.07.06.42.42.8.88 1.13 1.38l.02.03c.32.48.6 1 .82 1.55.23.55.4 1.12.52 1.7.11.58.17 1.17.17 1.77 0 .6-.06 1.2-.17 1.77-.12.58-.29 1.15-.52 1.71a9.044 9.044 0 0 1-3.35 4.09l-.03.02c-.49.32-1 .6-1.55.82-.55.23-1.12.4-1.71.52-.58.11-1.17.17-1.77.17-.6 0-1.2-.06-1.77-.17-.58-.12-1.15-.29-1.71-.52-.57-.24-1.1-.52-1.58-.84l-.05-.04a9.42 9.42 0 0 1-1.33-1.09c-.42-.42-.8-.88-1.13-1.38l-.02-.03c-.32-.48-.6-1-.82-1.55-.23-.55-.4-1.12-.52-1.7-.11-.58-.18-1.17-.18-1.77 0-.6.06-1.2.18-1.77.12-.58.29-1.15.52-1.7a9.044 9.044 0 0 1 1.97-2.96c.41-.41.88-.79 1.38-1.13.49-.33 1.02-.61 1.58-.85a9.058 9.058 0 0 1 3.49-.7zm1.97 4.38c-.3-.12-.62-.22-.95-.29-.32-.06-.66-.1-1.01-.1s-.69.03-1.01.1c-.34.07-.66.16-.95.29a4.96 4.96 0 0 0-1.66 1.12c-.25.25-.46.51-.65.78-.19.28-.34.57-.47.87-.12.3-.22.62-.29.95-.06.32-.1.66-.1 1.01s.03.69.1 1.01c.07.34.16.66.29.95.13.31.28.6.47.87.18.27.4.53.65.78s.51.46.78.65c.27.18.57.34.88.47l.03.02c.29.12.59.21.92.27.32.06.66.1 1.01.1s.69-.03 1.01-.1c.34-.07.65-.16.95-.28.31-.13.6-.29.88-.47a5.312 5.312 0 0 0 1.43-1.42c.19-.28.34-.57.47-.87.12-.3.22-.62.29-.95.06-.32.1-.66.1-1.01s-.03-.69-.1-1.01a4.58 4.58 0 0 0-.29-.95c-.13-.31-.28-.6-.47-.88-.19-.28-.4-.54-.64-.78l-.05-.05c-.23-.22-.47-.42-.73-.59-.29-.21-.58-.37-.89-.49zm-66.13-2.23v30.87c0 1.66.33 3.24.93 4.69a12.5 12.5 0 0 0 2.68 4c1.14 1.14 2.5 2.06 4 2.68 1.44.6 3.03.93 4.69.93h62.13c1.66 0 3.24-.33 4.69-.93 1.5-.63 2.86-1.54 4-2.68s2.06-2.5 2.68-4c.6-1.44.93-3.03.93-4.69V42.22c0-1.66-.33-3.25-.93-4.69a12.5 12.5 0 0 0-2.68-4c-1.14-1.14-2.5-2.06-4-2.68-1.44-.6-3.03-.93-4.69-.93H30.5c-1.66 0-3.24.33-4.69.93a12.5 12.5 0 0 0-4 2.68c-1.14 1.14-2.06 2.5-2.68 4-.6 1.44-.93 3.03-.93 4.69v3.65h-.01zm-3.99 1.99H7.09c-.42 0-.82.08-1.18.23-.38.16-.72.39-1.01.68l-.04.03c-.27.28-.49.61-.64.97-.15.36-.23.76-.23 1.18v18.92c0 .42.08.82.23 1.18.16.38.39.72.68 1.01.56.56 1.33.91 2.18.91h7.11V47.86h.01zm101.59 0h-6.86v25.11h6.86c.42 0 .82-.08 1.18-.23.38-.16.72-.39 1.01-.68.29-.29.52-.63.68-1.01.15-.36.23-.76.23-1.18V50.96c0-.42-.08-.82-.23-1.18-.16-.38-.39-.72-.68-1.01-.29-.29-.63-.52-1.01-.68-.36-.14-.76-.23-1.18-.23z"/>
     </svg>
+  ),
+  auditLogIcon: (
+    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 512 512">
+      <path
+        fillRule="evenodd"
+        d="m384 85.3333337 85.333333 85.3333333v256H42.66666678L42.66525 193.996358c10.0983011 15.352321 24.2153849 33.106855 42.6673443 48.165701L85.3333334 384H426.666667V181.333334l-53.333334-53.3333337-39.735846.0017872c-5.439498-10.6533523-14.584184-26.4898523-27.734229-42.6683963zM384 320v21.333334H128.0000001V320zm0-64v21.333334H256l-.000063-20.370657c.541196-.318106 1.079687-.63898 1.615477-.962551zM181.333333 42.666667C278.4 42.666667 320 149.333334 320 149.333334S278.4 256 181.333333 256C84.2666668 256 42.66666678 149.333334 42.66666678 149.333334S84.2666668 42.666667 181.333333 42.666667zm0 26.6666667c-61.2906662 0-97.0666662 57.0666666-108.2986662 80.0000003 11.232 22.933333 47.008 80 108.2986662 80 61.290667 0 97.066667-57.066667 108.298667-80-11.232-22.9333337-47.008-80.0000003-108.298667-80.0000003zm0 33.3333333c26.80422 0 48.533334 20.8933783 48.533334 46.666667 0 25.773288-21.729114 46.666666-48.533334 46.666666-26.804219 0-48.5333329-20.893378-48.5333329-46.666666 0-25.7732887 21.7291139-46.666667 48.5333329-46.666667zm0 26.6666667c-11.487522 0-20.8 8.954305-20.8 20.0000003 0 11.045695 9.312478 20 20.8 20 11.487523 0 20.8-8.954305 20.8-20 0-11.0456953-9.312477-20.0000003-20.8-20.0000003z"
+      />
+    </svg>
+  ),
+  consultingIcon: (
+    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16">
+      <path
+        d="M13 3c-.538.515-1.185.92-1.902 1.178-.748.132-2.818-.828-3.838.152-.17.17-.38.34-.6.51-.48-.21-1.22-.53-1.76-.84S3 3 3 3L0 6.5s.74 1 1.2 1.66c.3.44.67 1.11.91 1.56l-.34.4c-.058.115-.093.25-.093.393 0 .235.092.449.243.607.138.103.311.165.5.165s.362-.062.502-.167c-.094.109-.149.249-.149.402 0 .193.088.365.226.479.144.085.317.135.501.135s.357-.05.505-.137c-.112.139-.177.313-.177.503s.065.364.174.502c.099.035.214.056.334.056.207 0 .399-.063.558-.17-.043.095-.065.203-.065.317 0 .234.096.445.252.595.13.059.283.093.443.093.226 0 .437-.068.611-.185l.516-.467c.472.47 1.123.761 1.842.761.02 0 .041 0 .061-.001.494-.042.908-.356 1.094-.791.146.056.312.094.488.094.236 0 .455-.068.64-.185.585-.387.445-.687.445-.687.125.055.27.087.423.087.321 0 .61-.142.806-.366.176-.181.283-.427.283-.697 0-.19-.053-.367-.145-.518.008.005.015.005.021.005.421 0 .787-.232.978-.574.068-.171.105-.363.105-.563 0-.342-.11-.659-.296-.917l.003.005c.82-.16.79-.57 1.19-1.17.384-.494.852-.902 1.387-1.208zm-.05 7.06c-.44.44-.78.25-1.53-.32S9.18 8.1 9.18 8.1c.061.305.202.57.401.781.319.359 1.269 1.179 1.719 1.599.28.26 1 .78.58 1.18s-.75 0-1.44-.56-2.23-1.94-2.23-1.94c-.001.018-.002.038-.002.059 0 .258.104.491.272.661.17.2 1.12 1.12 1.52 1.54s.75.67.41 1-1.03-.19-1.41-.58c-.59-.57-1.76-1.63-1.76-1.63-.001.016-.001.034-.001.053 0 .284.098.544.263.75.288.378.848.868 1.188 1.248s.54.7 0 1-1.34-.44-1.69-.8v-.002c0-.103-.038-.197-.1-.269-.159-.147-.374-.238-.609-.238-.104 0-.204.018-.297.05.128-.114.204-.274.204-.452s-.076-.338-.198-.45c-.126-.095-.284-.152-.455-.152s-.33.057-.457.153c.117-.113.189-.268.189-.441 0-.213-.109-.4-.274-.509-.153-.097-.336-.153-.532-.153-.244 0-.468.088-.642.233.095-.114.151-.26.151-.42 0-.195-.085-.37-.219-.491-.178-.165-.417-.266-.679-.266-.185 0-.358.05-.507.138L1.91 8.069c-.46-.73-1-1.49-1-1.49l2.28-2.77s.81.5 1.48.88c.33.19.9.44 1.33.64-.68.51-1.25 1-1.08 1.34.297.214.668.343 1.069.343.376 0 .726-.113 1.018-.307.373-.251.84-.403 1.343-.403.347 0 .677.072.976.203.554.374 1.574 1.294 2.504 1.874 1.17.85 1.4 1.4 1.12 1.68z"/>
+    </svg>
   )
 }
diff --git a/ui/packages/shared/styles/styles.ts b/ui/packages/shared/styles/styles.ts
index b2f01098..e26e30d4 100644
--- a/ui/packages/shared/styles/styles.ts
+++ b/ui/packages/shared/styles/styles.ts
@@ -8,11 +8,11 @@
 import { colors } from './colors'
 import { theme } from './theme'
 
-export const styles = {
+export const styles: Record<string, any> = {
   root: {
-    'min-height': '100%',
+    'minHeight': '100%',
     width: '100%',
-    'z-index': 1,
+    'zIndex': 1,
     position: 'relative',
     [theme.breakpoints.down('sm')]: {
       maxWidth: '100vw',
diff --git a/ui/packages/shared/tsconfig.build.json b/ui/packages/shared/tsconfig.build.json
new file mode 100644
index 00000000..ef287c42
--- /dev/null
+++ b/ui/packages/shared/tsconfig.build.json
@@ -0,0 +1,37 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+      "rootDir": ".",
+      "outDir": "dist",
+      "declaration": true,
+      "emitDeclarationOnly": false,
+      "noEmit": false,
+      "module": "esnext",
+      "target": "es2019",
+      "moduleResolution": "node",
+      "jsx": "react-jsx",
+      "resolveJsonModule": true,
+      "esModuleInterop": true
+    },
+    "include": [
+      "components/**/*",
+      "config/**/*",
+      "helpers/**/*",
+      "hooks/**/*",
+      "icons/**/*",
+      "pages/**/*",
+      "stores/**/*",
+      "styles/**/*",
+      "types/**/*",
+      "utils/**/*",
+      "react-app-env.d.ts",
+    ],
+    "exclude": [
+      "node_modules",
+      "dist",
+      "meta.json",
+      "craco.config.js",
+      "**/*.test.ts",
+      "**/*.test.tsx"
+    ]
+  }
\ No newline at end of file
diff --git a/ui/packages/shared/tsconfig.json b/ui/packages/shared/tsconfig.json
index 69995a95..e47a16e2 100644
--- a/ui/packages/shared/tsconfig.json
+++ b/ui/packages/shared/tsconfig.json
@@ -19,9 +19,12 @@
     "isolatedModules": true,
     "noEmit": true,
     "jsx": "react-jsx",
-    "baseUrl": "."
+    "baseUrl": ".",
+    "paths": {
+      "@postgres.ai/shared/*": ["./*"]
+    }
   },
   "include": [
-    ".",
+    "."
   ]
 }
diff --git a/ui/packages/shared/types/api/endpoints/createBranch.ts b/ui/packages/shared/types/api/endpoints/createBranch.ts
new file mode 100644
index 00000000..8ab64b7b
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/createBranch.ts
@@ -0,0 +1,14 @@
+import { CreateBranchResponse } from '@postgres.ai/shared/types/api/entities/createBranch'
+
+export type CreateBranchFormValues = {
+  branchName: string
+  instanceId: string
+  baseBranch: string
+  snapshotID: string
+  creationType?: 'branch' | 'snapshot'
+}
+
+export type CreateBranch = (values: CreateBranchFormValues) => Promise<{
+  response: CreateBranchResponse | null
+  error: Response | null
+}>
diff --git a/ui/packages/shared/types/api/endpoints/createClone.ts b/ui/packages/shared/types/api/endpoints/createClone.ts
index 5c4ce144..afa56f3e 100644
--- a/ui/packages/shared/types/api/endpoints/createClone.ts
+++ b/ui/packages/shared/types/api/endpoints/createClone.ts
@@ -7,4 +7,5 @@ export type CreateClone = (args: {
   dbUser: string
   dbPassword: string
   isProtected: boolean
+  branch?: string
 }) => Promise<{ response: Clone | null; error: Response | null }>
diff --git a/ui/packages/shared/types/api/endpoints/createSnapshot.ts b/ui/packages/shared/types/api/endpoints/createSnapshot.ts
new file mode 100644
index 00000000..752918a4
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/createSnapshot.ts
@@ -0,0 +1,10 @@
+import { CreateSnapshotResponse } from '@postgres.ai/shared/types/api/entities/createSnapshot'
+
+export type CreateSnapshot = (
+  cloneID: string,
+  message: string,
+  instanceId: string,
+) => Promise<{
+  response: CreateSnapshotResponse | null
+  error: Response | null
+}>
diff --git a/ui/packages/shared/types/api/endpoints/deleteBranch.ts b/ui/packages/shared/types/api/endpoints/deleteBranch.ts
new file mode 100644
index 00000000..81475020
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/deleteBranch.ts
@@ -0,0 +1,4 @@
+export type DeleteBranch = (
+  branchName: string,
+  instanceId: string,
+) => Promise<{ response: Response | null; error: Error | null }>
diff --git a/ui/packages/shared/types/api/endpoints/destroySnapshot.ts b/ui/packages/shared/types/api/endpoints/destroySnapshot.ts
new file mode 100644
index 00000000..17c201c1
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/destroySnapshot.ts
@@ -0,0 +1,8 @@
+export type DestroySnapshot = (
+  snapshotId: string,
+  forceDelete: boolean,
+  instanceId: string,
+) => Promise<{
+  response: boolean | null
+  error: Response | null
+}>
diff --git a/ui/packages/shared/types/api/endpoints/fullRefresh.ts b/ui/packages/shared/types/api/endpoints/fullRefresh.ts
new file mode 100644
index 00000000..268845c3
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/fullRefresh.ts
@@ -0,0 +1,6 @@
+export type FullRefresh = (args: {
+  instanceId: string
+}) => Promise<{
+  response: string | null
+  error: Response | null
+}>
diff --git a/ui/packages/shared/types/api/endpoints/getBranchSnapshot.ts b/ui/packages/shared/types/api/endpoints/getBranchSnapshot.ts
new file mode 100644
index 00000000..7b80b7d6
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/getBranchSnapshot.ts
@@ -0,0 +1,6 @@
+import { BranchSnapshotDto } from '@postgres.ai/shared/types/api/entities/branchSnapshot'
+
+export type GetBranchSnapshot = (
+  snapshotId: string,
+  instanceId: string,
+) => Promise<{ response: BranchSnapshotDto | null; error: Response | null }>
diff --git a/ui/packages/shared/types/api/endpoints/getBranches.ts b/ui/packages/shared/types/api/endpoints/getBranches.ts
new file mode 100644
index 00000000..199c0f4a
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/getBranches.ts
@@ -0,0 +1,20 @@
+import { formatDateToISO } from '@postgres.ai/shared/utils/date'
+
+export interface Branch {
+  name: string
+  parent: string
+  dataStateAt: string
+  snapshotID: string
+  numSnapshots: number
+}
+
+export const formatBranchesDto = (dto: Branch[]) =>
+  dto.map((item) => ({
+    ...item,
+    dataStateAt: formatDateToISO(item.dataStateAt),
+  }))
+
+export type GetBranches = (instanceId: string) => Promise<{
+  response: Branch[] | null
+  error: Response | null
+}>
diff --git a/ui/packages/shared/types/api/endpoints/getConfig.ts b/ui/packages/shared/types/api/endpoints/getConfig.ts
index ea9f7546..edab4150 100644
--- a/ui/packages/shared/types/api/endpoints/getConfig.ts
+++ b/ui/packages/shared/types/api/endpoints/getConfig.ts
@@ -1,6 +1,6 @@
-import { Config } from "../entities/config"
+import { Config } from '../entities/config'
 
-export type GetConfig = () => Promise<{
+export type GetConfig = (instanceId: string) => Promise<{
   response: Config | null
   error: Response | null
 }>
diff --git a/ui/packages/shared/types/api/endpoints/getEngine.ts b/ui/packages/shared/types/api/endpoints/getEngine.ts
index 22672da2..57ea3063 100644
--- a/ui/packages/shared/types/api/endpoints/getEngine.ts
+++ b/ui/packages/shared/types/api/endpoints/getEngine.ts
@@ -3,7 +3,7 @@ export type EngineDto = {
   edition?: string
 }
 
-export type GetEngine = () => Promise<{
+export type GetEngine = (instanceId: string) => Promise<{
   response: EngineType | null
   error: Response | null
 }>
diff --git a/ui/packages/shared/types/api/endpoints/getFullConfig.ts b/ui/packages/shared/types/api/endpoints/getFullConfig.ts
index 6d38ab13..96a3426e 100644
--- a/ui/packages/shared/types/api/endpoints/getFullConfig.ts
+++ b/ui/packages/shared/types/api/endpoints/getFullConfig.ts
@@ -1,4 +1,4 @@
-export type GetFullConfig = () => Promise<{
+export type GetFullConfig = (instanceId: string) => Promise<{
   response: string | null
   error: Response | any | null
 }>
diff --git a/ui/packages/shared/types/api/endpoints/getSnapshotList.ts b/ui/packages/shared/types/api/endpoints/getSnapshotList.ts
new file mode 100644
index 00000000..69c84c6b
--- /dev/null
+++ b/ui/packages/shared/types/api/endpoints/getSnapshotList.ts
@@ -0,0 +1,14 @@
+export interface SnapshotList {
+  branch: string[]
+  id: string
+  dataStateAt: string
+  message: string
+}
+
+export type GetSnapshotList = (
+  branchName: string,
+  instanceId: string,
+) => Promise<{
+  response: SnapshotList[] | null
+  error: Response | null
+}>
diff --git a/ui/packages/shared/types/api/endpoints/getSnapshots.ts b/ui/packages/shared/types/api/endpoints/getSnapshots.ts
index 4d739d56..3dbd2930 100644
--- a/ui/packages/shared/types/api/endpoints/getSnapshots.ts
+++ b/ui/packages/shared/types/api/endpoints/getSnapshots.ts
@@ -1,6 +1,6 @@
 import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
 
-export type GetSnapshots = (args: { instanceId: string }) => Promise<{
+export type GetSnapshots = (args: { instanceId: string, branchName?: string }) => Promise<{
   response: Snapshot[] | null
   error: Response | null
 }>
diff --git a/ui/packages/shared/types/api/endpoints/updateConfig.ts b/ui/packages/shared/types/api/endpoints/updateConfig.ts
index 375bee3d..4e859d78 100644
--- a/ui/packages/shared/types/api/endpoints/updateConfig.ts
+++ b/ui/packages/shared/types/api/endpoints/updateConfig.ts
@@ -1,6 +1,9 @@
 import { Config } from '@postgres.ai/shared/types/api/entities/config'
 
-export type UpdateConfig = (values: Config) => Promise<{
+export type UpdateConfig = (
+  values: Config,
+  instanceId: string,
+) => Promise<{
   response: Response | null
   error: Response | null
 }>
diff --git a/ui/packages/shared/types/api/entities/branchSnapshot.ts b/ui/packages/shared/types/api/entities/branchSnapshot.ts
new file mode 100644
index 00000000..69257425
--- /dev/null
+++ b/ui/packages/shared/types/api/entities/branchSnapshot.ts
@@ -0,0 +1,8 @@
+export type BranchSnapshotDTO = {
+  message: string
+  branch: string[]
+}
+
+export const formatBranchSnapshotDto = (dto: BranchSnapshotDTO) => dto
+
+export type BranchSnapshotDto = ReturnType<typeof formatBranchSnapshotDto>
diff --git a/ui/packages/shared/types/api/entities/branchSnapshots.ts b/ui/packages/shared/types/api/entities/branchSnapshots.ts
new file mode 100644
index 00000000..f721aa82
--- /dev/null
+++ b/ui/packages/shared/types/api/entities/branchSnapshots.ts
@@ -0,0 +1,10 @@
+import { parseDate } from '@postgres.ai/shared/utils/date'
+import { SnapshotDto } from './snapshot'
+
+export const formatBranchSnapshotDto = (dto: SnapshotDto[]) =>
+  dto.map((item) => ({
+    ...item,
+    numClones: item.numClones.toString(),
+    createdAtDate: parseDate(item.createdAt),
+    dataStateAtDate: parseDate(item.dataStateAt),
+  }))
diff --git a/ui/packages/shared/types/api/entities/clone.ts b/ui/packages/shared/types/api/entities/clone.ts
index 7ac24393..bf0dc169 100644
--- a/ui/packages/shared/types/api/entities/clone.ts
+++ b/ui/packages/shared/types/api/entities/clone.ts
@@ -14,6 +14,7 @@ import {
 export type CloneDto = {
   createdAt: string
   id: string
+  branch: string
   status: {
     code: 'OK' | 'CREATING' | 'DELETING' | 'RESETTING' | 'FATAL'
     message: string
diff --git a/ui/packages/shared/types/api/entities/config.ts b/ui/packages/shared/types/api/entities/config.ts
index e6cdbcca..1731c3b3 100644
--- a/ui/packages/shared/types/api/entities/config.ts
+++ b/ui/packages/shared/types/api/entities/config.ts
@@ -4,7 +4,7 @@ import {
   getImageMajorVersion,
   getImageType,
   isSeDockerImage,
-} from '@postgres.ai/shared/pages/Configuration/utils'
+} from '@postgres.ai/shared/pages/Instance/Configuration/utils'
 import { formatTuningParams } from '../endpoints/testDbSource'
 
 export interface DatabaseType {
@@ -63,12 +63,15 @@ export const formatConfig = (config: configTypes) => {
     debug: config.global?.debug,
     dockerImage: isSeDockerImage(dockerImage)
       ? getImageMajorVersion(dockerImage)
+      : dockerImage && getImageType(dockerImage) === 'Generic Postgres'
+      ? getImageMajorVersion(dockerImage) || dockerImage
       : dockerImage,
     ...(dockerImage && {
       dockerImageType: getImageType(dockerImage),
     }),
-    ...(isSeDockerImage(dockerImage) && {
-      dockerTag: dockerImage?.split(':')[1],
+    // Extract dockerTag for both SE images and Generic Postgres images
+    ...(dockerImage && dockerImage.includes(':') && {
+      dockerTag: dockerImage.split(':')[1],
     }),
     dockerPath: dockerImage,
     tuningParams: formatTuningParams(config.databaseConfigs?.configs),
diff --git a/ui/packages/shared/types/api/entities/createBranch.ts b/ui/packages/shared/types/api/entities/createBranch.ts
new file mode 100644
index 00000000..6b656fee
--- /dev/null
+++ b/ui/packages/shared/types/api/entities/createBranch.ts
@@ -0,0 +1,7 @@
+export type CreateBranchDTO = {
+  name: string
+}
+
+export const formatCreateBranchDto = (dto: CreateBranchDTO) => dto
+
+export type CreateBranchResponse = ReturnType<typeof formatCreateBranchDto>
diff --git a/ui/packages/shared/types/api/entities/createSnapshot.ts b/ui/packages/shared/types/api/entities/createSnapshot.ts
new file mode 100644
index 00000000..6ce75e6c
--- /dev/null
+++ b/ui/packages/shared/types/api/entities/createSnapshot.ts
@@ -0,0 +1,7 @@
+export type CreateSnapshotDTO = {
+  snapshotID: string
+}
+
+export const formatCreateSnapshotDto = (dto: CreateSnapshotDTO) => dto
+
+export type CreateSnapshotResponse = ReturnType<typeof formatCreateSnapshotDto>
diff --git a/ui/packages/shared/types/api/entities/dbSource.ts b/ui/packages/shared/types/api/entities/dbSource.ts
index a13d2654..85977248 100644
--- a/ui/packages/shared/types/api/entities/dbSource.ts
+++ b/ui/packages/shared/types/api/entities/dbSource.ts
@@ -4,10 +4,11 @@ export type dbSource = {
   dbname: string
   username: string
   password: string
+  instanceId: string
   db_list?: string[]
 }
 
 export type TestSourceDTO = {
   message: string
   status: string
-}
\ No newline at end of file
+}
diff --git a/ui/packages/shared/types/api/entities/snapshot.ts b/ui/packages/shared/types/api/entities/snapshot.ts
index 3d29912e..07f2c4a1 100644
--- a/ui/packages/shared/types/api/entities/snapshot.ts
+++ b/ui/packages/shared/types/api/entities/snapshot.ts
@@ -1,18 +1,22 @@
 import { parseDate } from '@postgres.ai/shared/utils/date'
 
 export type SnapshotDto = {
+  numClones: string | number
+  clones: string[]
   createdAt: string
   dataStateAt: string
   id: string
   pool: string
   physicalSize: number
   logicalSize: number
+  message: string
+  branch: string
 }
 
 export const formatSnapshotDto = (dto: SnapshotDto) => ({
   ...dto,
   createdAtDate: parseDate(dto.createdAt),
-  dataStateAtDate: parseDate(dto.dataStateAt)
+  dataStateAtDate: parseDate(dto.dataStateAt),
 })
 
 export type Snapshot = ReturnType<typeof formatSnapshotDto>
diff --git a/ui/packages/shared/utils/date.ts b/ui/packages/shared/utils/date.ts
index 934f4dab..0b024c58 100644
--- a/ui/packages/shared/utils/date.ts
+++ b/ui/packages/shared/utils/date.ts
@@ -21,6 +21,11 @@ import {
   formatDistanceToNowStrict,
 } from 'date-fns'
 
+export const formatDateToISO = (dateString: string) => {
+  const parsedDate = parse(dateString, 'yyyyMMddHHmmss', new Date())
+  return format(parsedDate, "yyyy-MM-dd'T'HH:mm:ssXXX")
+}
+
 // parseDate parses date of both format: '2006-01-02 15:04:05 UTC' and `2006-01-02T15:04:05Z` (RFC3339).
 export const parseDate = (dateStr: string) =>
   parse(
@@ -85,3 +90,7 @@ export const formatDateStd = (
   `${formatUTC(date, 'yyyy-MM-dd HH:mm:ss')} UTC ${
     options?.withDistance ? `(${formatDistanceStd(date)})` : ''
   }`
+
+export const isValidDate = (dateObject: Date) => {
+  return new Date(dateObject).toString() !== 'Invalid Date'
+}
diff --git a/ui/packages/shared/utils/snapshot.ts b/ui/packages/shared/utils/snapshot.ts
index 6e5c5d90..4410f2d4 100644
--- a/ui/packages/shared/utils/snapshot.ts
+++ b/ui/packages/shared/utils/snapshot.ts
@@ -1,4 +1,8 @@
-import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
-
-export const compareSnapshotsDesc = (a: Snapshot, b: Snapshot) =>
-  b.dataStateAtDate.getTime() - a.dataStateAtDate.getTime()
+export const compareSnapshotsDesc = (
+  a: { dataStateAtDate: Date },
+  b: { dataStateAtDate: Date },
+): number => {
+  const dataStateAtDateA = a.dataStateAtDate?.getTime() ?? 0
+  const dataStateAtDateB = b.dataStateAtDate?.getTime() ?? 0
+  return dataStateAtDateB - dataStateAtDateA
+}
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index db0ffb93..6bd47eca 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -28,7 +28,7 @@ overrides:
   semver@>=7.0.0 <7.5.2: '>=7.5.2'
   semver@<5.7.2: '>=5.7.2'
   semver@>=6.0.0 <6.3.1: '>=6.3.1'
-  minimatch@<3.0.5: '>=3.0.5'
+  minimatch: 3.1.2
   json5@<1.0.2: '>=1.0.2'
   json5@>=2.0.0 <2.2.2: '>=2.2.2'
   ip@<1.1.9: '>=1.1.9'
@@ -98,6 +98,9 @@ importers:
       '@types/react-router-dom':
         specifier: ^5.3.1
         version: 5.3.1
+      '@types/react-syntax-highlighter':
+        specifier: ^15.5.6
+        version: 15.5.6
       byte-size:
         specifier: ^8.1.0
         version: 8.1.0
@@ -158,6 +161,9 @@ importers:
       react-scripts:
         specifier: ^5.0.0
         version: 5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.57.0)(react@17.0.2)(sass@1.43.2)(typescript@4.8.3)
+      react-syntax-highlighter:
+        specifier: ^15.5.0
+        version: 15.5.0(react@17.0.2)
       stream-browserify:
         specifier: ^3.0.0
         version: 3.0.0
@@ -220,283 +226,6 @@ importers:
         specifier: ^2.0.0
         version: 2.0.0(prettier@2.4.1)(stylelint@14.0.1)
 
-  packages/platform:
-    dependencies:
-      '@craco/craco':
-        specifier: ^6.4.3
-        version: 6.4.5(@types/node@12.20.33)(react-scripts@5.0.1)(typescript@4.8.3)
-      '@emotion/cache':
-        specifier: ^11.10.5
-        version: 11.10.5
-      '@emotion/react':
-        specifier: ^11.10.5
-        version: 11.10.5(@babel/core@7.19.0)(@types/react@17.0.39)(react@17.0.2)
-      '@emotion/server':
-        specifier: ^11.10.0
-        version: 11.10.0
-      '@emotion/styled':
-        specifier: ^11.10.5
-        version: 11.10.5(@babel/core@7.19.0)(@emotion/react@11.10.5)(@types/react@17.0.39)(react@17.0.2)
-      '@juggle/resize-observer':
-        specifier: ^3.3.1
-        version: 3.3.1
-      '@material-ui/core':
-        specifier: ^4.12.3
-        version: 4.12.4(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@material-ui/icons':
-        specifier: ^4.11.2
-        version: 4.11.2(@material-ui/core@4.12.4)(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@material-ui/lab':
-        specifier: 4.0.0-alpha.61
-        version: 4.0.0-alpha.61(@material-ui/core@4.12.4)(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@material-ui/styles':
-        specifier: ^4.11.4
-        version: 4.11.5(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@material-ui/system':
-        specifier: ^4.12.2
-        version: 4.12.2(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@monaco-editor/react':
-        specifier: ^4.4.5
-        version: 4.4.5(monaco-editor@0.48.0)(react-dom@17.0.2)(react@17.0.2)
-      '@mui/material':
-        specifier: ^5.10.12
-        version: 5.10.12(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@postgres.ai/ce':
-        specifier: link:../ce
-        version: link:../ce
-      '@postgres.ai/platform':
-        specifier: link:./
-        version: 'link:'
-      '@postgres.ai/shared':
-        specifier: link:../shared
-        version: link:../shared
-      '@sentry/react':
-        specifier: ^6.11.0
-        version: 6.19.7(react@17.0.2)
-      '@sentry/tracing':
-        specifier: ^6.11.0
-        version: 6.19.7
-      '@stripe/react-stripe-js':
-        specifier: ^1.1.2
-        version: 1.6.0(@stripe/stripe-js@1.20.3)(react-dom@17.0.2)(react@17.0.2)
-      '@stripe/stripe-js':
-        specifier: ^1.9.0
-        version: 1.20.3
-      '@types/d3':
-        specifier: ^7.4.0
-        version: 7.4.0
-      '@types/dompurify':
-        specifier: ^2.3.4
-        version: 2.3.4
-      '@types/node':
-        specifier: ^12.20.33
-        version: 12.20.33
-      '@types/qs':
-        specifier: ^6.9.7
-        version: 6.9.7
-      '@types/react':
-        specifier: ^17.0.5
-        version: 17.0.39
-      '@types/react-dom':
-        specifier: ^17.0.3
-        version: 17.0.11
-      '@types/react-router':
-        specifier: ^5.1.17
-        version: 5.1.17
-      '@types/react-router-dom':
-        specifier: ^5.1.7
-        version: 5.3.1
-      '@types/react-syntax-highlighter':
-        specifier: ^15.5.6
-        version: 15.5.6
-      bootstrap:
-        specifier: '>=5.0.0'
-        version: 5.3.3(@popperjs/core@2.11.8)
-      byte-size:
-        specifier: ^7.0.1
-        version: 7.0.1
-      classnames:
-        specifier: ^2.3.1
-        version: 2.3.1
-      clsx:
-        specifier: ^1.1.1
-        version: 1.2.1
-      copy-to-clipboard:
-        specifier: ^3.3.1
-        version: 3.3.1
-      create-file-webpack:
-        specifier: ^1.0.2
-        version: 1.0.2
-      crypto-browserify:
-        specifier: ^3.12.0
-        version: 3.12.0
-      d3:
-        specifier: ^5.12.0
-        version: 5.16.0
-      d3-flame-graph:
-        specifier: ^2.1.3
-        version: 2.2.2
-      date-fns:
-        specifier: ^2.22.1
-        version: 2.25.0
-      dompurify:
-        specifier: '>=2.5.4'
-        version: 3.1.6
-      dotenv:
-        specifier: ^10.0.0
-        version: 10.0.0
-      es6-promise:
-        specifier: ^4.2.8
-        version: 4.2.8
-      formik:
-        specifier: ^2.2.9
-        version: 2.2.9(react@17.0.2)
-      get-user-locale:
-        specifier: ^1.4.0
-        version: 1.4.0
-      jwt-decode:
-        specifier: ^3.1.2
-        version: 3.1.2
-      jwt-encode:
-        specifier: ^1.0.1
-        version: 1.0.1
-      lodash:
-        specifier: ^4.17.15
-        version: 4.17.21
-      md5:
-        specifier: ^2.2.1
-        version: 2.3.0
-      mermaid:
-        specifier: ^11.0.2
-        version: 11.0.2
-      mobx:
-        specifier: ^6.3.2
-        version: 6.3.5
-      mobx-react-lite:
-        specifier: ^3.2.0
-        version: 3.2.1(mobx@6.3.5)(react-dom@17.0.2)(react@17.0.2)
-      moment:
-        specifier: '>=2.29.2'
-        version: 2.30.1
-      prop-types:
-        specifier: ^15.7.2
-        version: 15.8.1
-      qs:
-        specifier: ^6.11.0
-        version: 6.11.0
-      react:
-        specifier: ^17.0.2
-        version: 17.0.2
-      react-bootstrap:
-        specifier: ^0.32.4
-        version: 0.32.4(react-dom@17.0.2)(react@17.0.2)
-      react-countdown-hook:
-        specifier: ^1.1.0
-        version: 1.1.0(react@17.0.2)
-      react-div-100vh:
-        specifier: ^0.6.0
-        version: 0.6.0(react@17.0.2)
-      react-dom:
-        specifier: ^17.0.2
-        version: 17.0.2(react@17.0.2)
-      react-markdown:
-        specifier: ^8.0.1
-        version: 8.0.3(@types/react@17.0.39)(react@17.0.2)
-      react-router:
-        specifier: ^5.1.2
-        version: 5.2.1(react@17.0.2)
-      react-router-dom:
-        specifier: ^5.1.2
-        version: 5.3.0(react@17.0.2)
-      react-router-hash-link:
-        specifier: ^1.2.2
-        version: 1.2.2(react-router-dom@5.3.0)(react@17.0.2)
-      react-scripts:
-        specifier: ^5.0.0
-        version: 5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.57.0)(react@17.0.2)(sass@1.43.2)(typescript@4.8.3)
-      react-syntax-highlighter:
-        specifier: ^15.5.0
-        version: 15.5.0(react@17.0.2)
-      react-use-websocket:
-        specifier: 3.0.0
-        version: 3.0.0(react-dom@17.0.2)(react@17.0.2)
-      reflux:
-        specifier: ^6.4.1
-        version: 6.4.1(react@17.0.2)
-      rehype-raw:
-        specifier: ^6.1.1
-        version: 6.1.1
-      remark-gfm:
-        specifier: ^3.0.1
-        version: 3.0.1
-      stream-browserify:
-        specifier: ^3.0.0
-        version: 3.0.0
-      typeface-roboto:
-        specifier: 0.0.75
-        version: 0.0.75
-      typescript:
-        specifier: ^4.4.4
-        version: 4.8.3
-      use-interval:
-        specifier: ^1.3.0
-        version: 1.4.0(react@17.0.2)
-      use-timer:
-        specifier: ^2.0.1
-        version: 2.0.1(react-dom@17.0.2)(react@17.0.2)
-      uuid:
-        specifier: ^3.3.2
-        version: 3.4.0
-      whatwg-fetch:
-        specifier: ^3.6.2
-        version: 3.6.2
-      yup:
-        specifier: ^0.32.11
-        version: 0.32.11
-    devDependencies:
-      '@babel/core':
-        specifier: ^7.19.0
-        version: 7.19.0
-      '@babel/eslint-parser':
-        specifier: ^7.18.9
-        version: 7.18.9(@babel/core@7.19.0)(eslint@8.57.0)
-      '@babel/eslint-plugin':
-        specifier: ^7.18.10
-        version: 7.18.10(@babel/eslint-parser@7.18.9)(eslint@8.57.0)
-      '@babel/preset-react':
-        specifier: ^7.18.6
-        version: 7.18.6(@babel/core@7.19.0)
-      '@tsconfig/recommended':
-        specifier: ^1.0.1
-        version: 1.0.1
-      '@typescript-eslint/eslint-plugin':
-        specifier: ^5.6.0
-        version: 5.36.2(@typescript-eslint/parser@5.36.2)(eslint@8.57.0)(typescript@4.8.3)
-      '@typescript-eslint/parser':
-        specifier: ^5.6.0
-        version: 5.36.2(eslint@8.57.0)(typescript@4.8.3)
-      cspell:
-        specifier: ^5.6.6
-        version: 5.18.4
-      eslint:
-        specifier: ^8.23.0
-        version: 8.57.0
-      eslint-plugin-react:
-        specifier: ^7.18.0
-        version: 7.31.8(eslint@8.57.0)
-      eslint-plugin-react-hooks:
-        specifier: ^4.2.0
-        version: 4.6.0(eslint@8.57.0)
-      sass:
-        specifier: ^1.37.5
-        version: 1.43.2
-      stylelint:
-        specifier: ^13.13.1
-        version: 13.13.1
-      stylelint-config-sass-guidelines:
-        specifier: ^8.0.0
-        version: 8.0.0(stylelint@13.13.1)
-
   packages/shared:
     dependencies:
       '@babel/core':
@@ -529,12 +258,6 @@ importers:
       '@mui/material':
         specifier: ^5.10.12
         version: 5.10.12(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@17.0.39)(react-dom@17.0.2)(react@17.0.2)
-      '@postgres.ai/ce':
-        specifier: link:../ce
-        version: link:../ce
-      '@postgres.ai/shared':
-        specifier: link:../shared
-        version: 'link:'
       '@types/node':
         specifier: ^12.20.33
         version: 12.20.33
@@ -613,6 +336,10 @@ importers:
       yup:
         specifier: ^0.32.11
         version: 0.32.11
+    devDependencies:
+      glob:
+        specifier: ^11.0.2
+        version: 11.0.2
 
 packages:
 
@@ -683,18 +410,7 @@ packages:
       eslint-scope: 5.1.1
       eslint-visitor-keys: 2.1.0
       semver: 7.5.4
-
-  /@babel/eslint-plugin@7.18.10(@babel/eslint-parser@7.18.9)(eslint@8.57.0):
-    resolution: {integrity: sha512-iV1OZj/7eg4wZIcsVEkXS3MUWdhmpLsu2h+9Zr2ppywKWdCRs6VfjxbRzmHHYeurTizrrnaJ9ZkbO8KOv4lauQ==}
-    engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
-    peerDependencies:
-      '@babel/eslint-parser': '>=7.11.0'
-      eslint: '>=7.5.0'
-    dependencies:
-      '@babel/eslint-parser': 7.18.9(@babel/core@7.19.0)(eslint@8.57.0)
-      eslint: 8.57.0
-      eslint-rule-composer: 0.3.0
-    dev: true
+    dev: false
 
   /@babel/generator@7.24.4:
     resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==}
@@ -1667,6 +1383,7 @@ packages:
     dependencies:
       '@babel/core': 7.19.0
       '@babel/helper-plugin-utils': 7.24.0
+    dev: false
 
   /@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.19.0):
     resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==}
@@ -1676,6 +1393,7 @@ packages:
     dependencies:
       '@babel/core': 7.19.0
       '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.19.0)
+    dev: false
 
   /@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.19.0):
     resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==}
@@ -1699,6 +1417,7 @@ packages:
       '@babel/core': 7.19.0
       '@babel/helper-annotate-as-pure': 7.22.5
       '@babel/helper-plugin-utils': 7.24.0
+    dev: false
 
   /@babel/plugin-transform-regenerator@7.18.6(@babel/core@7.19.0):
     resolution: {integrity: sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==}
@@ -1936,6 +1655,7 @@ packages:
       '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.19.0)
       '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.19.0)
       '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.19.0)
+    dev: false
 
   /@babel/preset-typescript@7.18.6(@babel/core@7.19.0):
     resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==}
@@ -1951,14 +1671,6 @@ packages:
       - supports-color
     dev: false
 
-  /@babel/runtime-corejs2@7.15.4:
-    resolution: {integrity: sha512-TmuTI+n5HsMesW6Ah2WjvBwix9fBMXwbMxQV3c0ETLAzlmwN4OeRVbYMYwp9P4LEOlAxwGKdd9e8pMiLMAg/Mg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      core-js: 2.6.12
-      regenerator-runtime: 0.13.9
-    dev: false
-
   /@babel/runtime-corejs3@7.19.0:
     resolution: {integrity: sha512-JyXXoCu1N8GLuKc2ii8y5RGma5FMpFeO2nAQIe0Yzrbq+rQnN+sFj47auLblR5ka6aHNGPDgv8G/iI2Grb0ldQ==}
     engines: {node: '>=6.9.0'}
@@ -2011,37 +1723,6 @@ packages:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: false
 
-  /@braintree/sanitize-url@7.1.0:
-    resolution: {integrity: sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==}
-    dev: false
-
-  /@chevrotain/cst-dts-gen@11.0.3:
-    resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==}
-    dependencies:
-      '@chevrotain/gast': 11.0.3
-      '@chevrotain/types': 11.0.3
-      lodash-es: 4.17.21
-    dev: false
-
-  /@chevrotain/gast@11.0.3:
-    resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==}
-    dependencies:
-      '@chevrotain/types': 11.0.3
-      lodash-es: 4.17.21
-    dev: false
-
-  /@chevrotain/regexp-to-ast@11.0.3:
-    resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==}
-    dev: false
-
-  /@chevrotain/types@11.0.3:
-    resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==}
-    dev: false
-
-  /@chevrotain/utils@11.0.3:
-    resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
-    dev: false
-
   /@colors/colors@1.5.0:
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
     engines: {node: '>=0.1.90'}
@@ -2581,20 +2262,6 @@ packages:
       csstype: 3.1.1
     dev: false
 
-  /@emotion/server@11.10.0:
-    resolution: {integrity: sha512-MTvJ21JPo9aS02GdjFW4nhdwOi2tNNpMmAM/YED0pkxzjDNi5WbiTwXqaCnvLc2Lr8NFtjhT0az1vTJyLIHYcw==}
-    peerDependencies:
-      '@emotion/css': ^11.0.0-rc.0
-    peerDependenciesMeta:
-      '@emotion/css':
-        optional: true
-    dependencies:
-      '@emotion/utils': 1.2.0
-      html-tokenize: 2.0.1
-      multipipe: 1.0.2
-      through: 2.3.8
-    dev: false
-
   /@emotion/sheet@1.2.1:
     resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==}
     dev: false
@@ -2694,6 +2361,18 @@ packages:
   /@humanwhocodes/object-schema@2.0.3:
     resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
 
+  /@isaacs/cliui@8.0.2:
+    resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+    engines: {node: '>=12'}
+    dependencies:
+      string-width: 5.1.2
+      string-width-cjs: /string-width@4.2.3
+      strip-ansi: 7.0.1
+      strip-ansi-cjs: /strip-ansi@6.0.1
+      wrap-ansi: 8.1.0
+      wrap-ansi-cjs: /wrap-ansi@7.0.0
+    dev: true
+
   /@istanbuljs/load-nyc-config@1.1.0:
     resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
     engines: {node: '>=8'}
@@ -2981,10 +2660,6 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.14
     dev: false
 
-  /@juggle/resize-observer@3.3.1:
-    resolution: {integrity: sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==}
-    dev: false
-
   /@leichtgewicht/ip-codec@2.0.4:
     resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
     dev: false
@@ -3140,12 +2815,6 @@ packages:
       react-is: 17.0.2
     dev: false
 
-  /@mermaid-js/parser@0.2.0:
-    resolution: {integrity: sha512-33dyFdhwsX9n4+E8SRj1ulxwAgwCj9RyCMtoqXD5cDfS9F6y9xmvmjFjHoPaViH4H7I7BXD8yP/XEWig5XrHSQ==}
-    dependencies:
-      langium: 3.0.0
-    dev: false
-
   /@monaco-editor/loader@1.3.2(monaco-editor@0.48.0):
     resolution: {integrity: sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g==}
     peerDependencies:
@@ -3387,10 +3056,6 @@ packages:
     resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
     dev: false
 
-  /@popperjs/core@2.11.8:
-    resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
-    dev: false
-
   /@rollup/plugin-babel@5.3.1(@babel/core@7.19.0)(rollup@2.79.2):
     resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
     engines: {node: '>= 10.0.0'}
@@ -3449,173 +3114,49 @@ packages:
     resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==}
     dev: false
 
-  /@sentry/browser@6.19.7:
-    resolution: {integrity: sha512-oDbklp4O3MtAM4mtuwyZLrgO1qDVYIujzNJQzXmi9YzymJCuzMLSRDvhY83NNDCRxf0pds4DShgYeZdbSyKraA==}
-    engines: {node: '>=6'}
-    dependencies:
-      '@sentry/core': 6.19.7
-      '@sentry/types': 6.19.7
-      '@sentry/utils': 6.19.7
-      tslib: 1.14.1
+  /@sinclair/typebox@0.24.39:
+    resolution: {integrity: sha512-GqtkxoAjhTzoMwFg/JYRl+1+miOoyvp6mkLpbMSd2fIQak2KvY00ndlXxxkDBpuCPYkorZeEZf0LEQn9V9NRVQ==}
     dev: false
 
-  /@sentry/core@6.19.7:
-    resolution: {integrity: sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw==}
-    engines: {node: '>=6'}
+  /@sinonjs/commons@1.8.3:
+    resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==}
     dependencies:
-      '@sentry/hub': 6.19.7
-      '@sentry/minimal': 6.19.7
-      '@sentry/types': 6.19.7
-      '@sentry/utils': 6.19.7
-      tslib: 1.14.1
+      type-detect: 4.0.8
     dev: false
 
-  /@sentry/hub@6.19.7:
-    resolution: {integrity: sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA==}
-    engines: {node: '>=6'}
+  /@sinonjs/fake-timers@8.1.0:
+    resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
     dependencies:
-      '@sentry/types': 6.19.7
-      '@sentry/utils': 6.19.7
-      tslib: 1.14.1
+      '@sinonjs/commons': 1.8.3
     dev: false
 
-  /@sentry/minimal@6.19.7:
-    resolution: {integrity: sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ==}
-    engines: {node: '>=6'}
+  /@surma/rollup-plugin-off-main-thread@2.2.3:
+    resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
     dependencies:
-      '@sentry/hub': 6.19.7
-      '@sentry/types': 6.19.7
-      tslib: 1.14.1
+      ejs: 3.1.10
+      json5: 2.2.3
+      magic-string: 0.25.9
+      string.prototype.matchall: 4.0.7
     dev: false
 
-  /@sentry/react@6.19.7(react@17.0.2):
-    resolution: {integrity: sha512-VzJeBg/v41jfxUYPkH2WYrKjWc4YiMLzDX0f4Zf6WkJ4v3IlDDSkX6DfmWekjTKBho6wiMkSNy2hJ1dHfGZ9jA==}
-    engines: {node: '>=6'}
-    peerDependencies:
-      react: 15.x || 16.x || 17.x || 18.x
-    dependencies:
-      '@sentry/browser': 6.19.7
-      '@sentry/minimal': 6.19.7
-      '@sentry/types': 6.19.7
-      '@sentry/utils': 6.19.7
-      hoist-non-react-statics: 3.3.2
-      react: 17.0.2
-      tslib: 1.14.1
+  /@svgr/babel-plugin-add-jsx-attribute@5.4.0:
+    resolution: {integrity: sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==}
+    engines: {node: '>=10'}
     dev: false
 
-  /@sentry/tracing@6.19.7:
-    resolution: {integrity: sha512-ol4TupNnv9Zd+bZei7B6Ygnr9N3Gp1PUrNI761QSlHtPC25xXC5ssSD3GMhBgyQrcvpuRcCFHVNNM97tN5cZiA==}
-    engines: {node: '>=6'}
-    dependencies:
-      '@sentry/hub': 6.19.7
-      '@sentry/minimal': 6.19.7
-      '@sentry/types': 6.19.7
-      '@sentry/utils': 6.19.7
-      tslib: 1.14.1
+  /@svgr/babel-plugin-remove-jsx-attribute@5.4.0:
+    resolution: {integrity: sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==}
+    engines: {node: '>=10'}
     dev: false
 
-  /@sentry/types@6.19.7:
-    resolution: {integrity: sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg==}
-    engines: {node: '>=6'}
+  /@svgr/babel-plugin-remove-jsx-empty-expression@5.0.1:
+    resolution: {integrity: sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==}
+    engines: {node: '>=10'}
     dev: false
 
-  /@sentry/utils@6.19.7:
-    resolution: {integrity: sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA==}
-    engines: {node: '>=6'}
-    dependencies:
-      '@sentry/types': 6.19.7
-      tslib: 1.14.1
-    dev: false
-
-  /@sinclair/typebox@0.24.39:
-    resolution: {integrity: sha512-GqtkxoAjhTzoMwFg/JYRl+1+miOoyvp6mkLpbMSd2fIQak2KvY00ndlXxxkDBpuCPYkorZeEZf0LEQn9V9NRVQ==}
-    dev: false
-
-  /@sinonjs/commons@1.8.3:
-    resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==}
-    dependencies:
-      type-detect: 4.0.8
-    dev: false
-
-  /@sinonjs/fake-timers@8.1.0:
-    resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
-    dependencies:
-      '@sinonjs/commons': 1.8.3
-    dev: false
-
-  /@stripe/react-stripe-js@1.6.0(@stripe/stripe-js@1.20.3)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-tMmsPD+wkpiiVJZgQ1E06tklG5MZHG462s6OWja9abpxq76kerAxMFN+KdhUg0LIEY79THbzvH3s/WGHasnV3w==}
-    peerDependencies:
-      '@stripe/stripe-js': ^1.19.1
-      react: ^16.8.0 || ^17.0.0
-      react-dom: ^16.8.0 || ^17.0.0
-    dependencies:
-      '@stripe/stripe-js': 1.20.3
-      prop-types: 15.8.1
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-    dev: false
-
-  /@stripe/stripe-js@1.20.3:
-    resolution: {integrity: sha512-pFhPvajrZJUzoOZDC3/2YsOeL5VD63CVlbOXs+AriIchmy6Kb2CQqrcHPCuK72FM726lwo4neF0wPRBtsLYXuw==}
-    dev: false
-
-  /@stylelint/postcss-css-in-js@0.37.3(postcss-syntax@0.36.2)(postcss@8.4.38):
-    resolution: {integrity: sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg==}
-    deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
-    peerDependencies:
-      postcss: '>=8.4.38'
-      postcss-syntax: '>=0.36.2'
-    dependencies:
-      '@babel/core': 7.19.0
-      postcss: 8.4.38
-      postcss-syntax: 0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@4.0.9)(postcss@8.4.38)
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /@stylelint/postcss-markdown@0.36.2(postcss-syntax@0.36.2)(postcss@8.4.38):
-    resolution: {integrity: sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==}
-    deprecated: 'Use the original unforked package instead: postcss-markdown'
-    peerDependencies:
-      postcss: '>=8.4.38'
-      postcss-syntax: '>=0.36.2'
-    dependencies:
-      postcss: 8.4.38
-      postcss-syntax: 0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@4.0.9)(postcss@8.4.38)
-      remark: 13.0.0
-      unist-util-find-all-after: 3.0.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /@surma/rollup-plugin-off-main-thread@2.2.3:
-    resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
-    dependencies:
-      ejs: 3.1.10
-      json5: 2.2.3
-      magic-string: 0.25.9
-      string.prototype.matchall: 4.0.7
-    dev: false
-
-  /@svgr/babel-plugin-add-jsx-attribute@5.4.0:
-    resolution: {integrity: sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==}
-    engines: {node: '>=10'}
-    dev: false
-
-  /@svgr/babel-plugin-remove-jsx-attribute@5.4.0:
-    resolution: {integrity: sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==}
-    engines: {node: '>=10'}
-    dev: false
-
-  /@svgr/babel-plugin-remove-jsx-empty-expression@5.0.1:
-    resolution: {integrity: sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==}
-    engines: {node: '>=10'}
-    dev: false
-
-  /@svgr/babel-plugin-replace-jsx-attribute-value@5.0.1:
-    resolution: {integrity: sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==}
-    engines: {node: '>=10'}
+  /@svgr/babel-plugin-replace-jsx-attribute-value@5.0.1:
+    resolution: {integrity: sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==}
+    engines: {node: '>=10'}
     dev: false
 
   /@svgr/babel-plugin-svg-dynamic-title@5.4.0:
@@ -3733,10 +3274,6 @@ packages:
     resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
     dev: false
 
-  /@tsconfig/recommended@1.0.1:
-    resolution: {integrity: sha512-2xN+iGTbPBEzGSnVp/Hd64vKJCJWxsi9gfs88x4PPMyEjHJoA3o5BY9r5OLPHIZU2pAQxkSAsJFqn6itClP8mQ==}
-    dev: true
-
   /@types/babel__core@7.1.19:
     resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==}
     dependencies:
@@ -3796,197 +3333,6 @@ packages:
       '@types/node': 12.20.33
     dev: false
 
-  /@types/d3-array@3.0.3:
-    resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==}
-    dev: false
-
-  /@types/d3-axis@3.0.1:
-    resolution: {integrity: sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==}
-    dependencies:
-      '@types/d3-selection': 3.0.3
-    dev: false
-
-  /@types/d3-brush@3.0.1:
-    resolution: {integrity: sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==}
-    dependencies:
-      '@types/d3-selection': 3.0.3
-    dev: false
-
-  /@types/d3-chord@3.0.1:
-    resolution: {integrity: sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==}
-    dev: false
-
-  /@types/d3-color@3.1.0:
-    resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==}
-    dev: false
-
-  /@types/d3-contour@3.0.1:
-    resolution: {integrity: sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==}
-    dependencies:
-      '@types/d3-array': 3.0.3
-      '@types/geojson': 7946.0.10
-    dev: false
-
-  /@types/d3-delaunay@6.0.1:
-    resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==}
-    dev: false
-
-  /@types/d3-dispatch@3.0.1:
-    resolution: {integrity: sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==}
-    dev: false
-
-  /@types/d3-drag@3.0.1:
-    resolution: {integrity: sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==}
-    dependencies:
-      '@types/d3-selection': 3.0.3
-    dev: false
-
-  /@types/d3-dsv@3.0.0:
-    resolution: {integrity: sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==}
-    dev: false
-
-  /@types/d3-ease@3.0.0:
-    resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==}
-    dev: false
-
-  /@types/d3-fetch@3.0.1:
-    resolution: {integrity: sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==}
-    dependencies:
-      '@types/d3-dsv': 3.0.0
-    dev: false
-
-  /@types/d3-force@3.0.3:
-    resolution: {integrity: sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==}
-    dev: false
-
-  /@types/d3-format@3.0.1:
-    resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==}
-    dev: false
-
-  /@types/d3-geo@3.0.2:
-    resolution: {integrity: sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==}
-    dependencies:
-      '@types/geojson': 7946.0.10
-    dev: false
-
-  /@types/d3-hierarchy@3.1.0:
-    resolution: {integrity: sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==}
-    dev: false
-
-  /@types/d3-interpolate@3.0.1:
-    resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==}
-    dependencies:
-      '@types/d3-color': 3.1.0
-    dev: false
-
-  /@types/d3-path@3.0.0:
-    resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==}
-    dev: false
-
-  /@types/d3-polygon@3.0.0:
-    resolution: {integrity: sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==}
-    dev: false
-
-  /@types/d3-quadtree@3.0.2:
-    resolution: {integrity: sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==}
-    dev: false
-
-  /@types/d3-random@3.0.1:
-    resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==}
-    dev: false
-
-  /@types/d3-scale-chromatic@3.0.0:
-    resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==}
-    dev: false
-
-  /@types/d3-scale@4.0.2:
-    resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==}
-    dependencies:
-      '@types/d3-time': 3.0.0
-    dev: false
-
-  /@types/d3-selection@3.0.3:
-    resolution: {integrity: sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==}
-    dev: false
-
-  /@types/d3-shape@3.1.0:
-    resolution: {integrity: sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==}
-    dependencies:
-      '@types/d3-path': 3.0.0
-    dev: false
-
-  /@types/d3-time-format@4.0.0:
-    resolution: {integrity: sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==}
-    dev: false
-
-  /@types/d3-time@3.0.0:
-    resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==}
-    dev: false
-
-  /@types/d3-timer@3.0.0:
-    resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==}
-    dev: false
-
-  /@types/d3-transition@3.0.2:
-    resolution: {integrity: sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==}
-    dependencies:
-      '@types/d3-selection': 3.0.3
-    dev: false
-
-  /@types/d3-zoom@3.0.1:
-    resolution: {integrity: sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==}
-    dependencies:
-      '@types/d3-interpolate': 3.0.1
-      '@types/d3-selection': 3.0.3
-    dev: false
-
-  /@types/d3@7.4.0:
-    resolution: {integrity: sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==}
-    dependencies:
-      '@types/d3-array': 3.0.3
-      '@types/d3-axis': 3.0.1
-      '@types/d3-brush': 3.0.1
-      '@types/d3-chord': 3.0.1
-      '@types/d3-color': 3.1.0
-      '@types/d3-contour': 3.0.1
-      '@types/d3-delaunay': 6.0.1
-      '@types/d3-dispatch': 3.0.1
-      '@types/d3-drag': 3.0.1
-      '@types/d3-dsv': 3.0.0
-      '@types/d3-ease': 3.0.0
-      '@types/d3-fetch': 3.0.1
-      '@types/d3-force': 3.0.3
-      '@types/d3-format': 3.0.1
-      '@types/d3-geo': 3.0.2
-      '@types/d3-hierarchy': 3.1.0
-      '@types/d3-interpolate': 3.0.1
-      '@types/d3-path': 3.0.0
-      '@types/d3-polygon': 3.0.0
-      '@types/d3-quadtree': 3.0.2
-      '@types/d3-random': 3.0.1
-      '@types/d3-scale': 4.0.2
-      '@types/d3-scale-chromatic': 3.0.0
-      '@types/d3-selection': 3.0.3
-      '@types/d3-shape': 3.1.0
-      '@types/d3-time': 3.0.0
-      '@types/d3-time-format': 4.0.0
-      '@types/d3-timer': 3.0.0
-      '@types/d3-transition': 3.0.2
-      '@types/d3-zoom': 3.0.1
-    dev: false
-
-  /@types/debug@4.1.7:
-    resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
-    dependencies:
-      '@types/ms': 0.7.31
-    dev: false
-
-  /@types/dompurify@2.3.4:
-    resolution: {integrity: sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==}
-    dependencies:
-      '@types/trusted-types': 2.0.2
-    dev: false
-
   /@types/eslint@8.4.6:
     resolution: {integrity: sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==}
     dependencies:
@@ -4019,10 +3365,6 @@ packages:
       '@types/serve-static': 1.15.0
     dev: false
 
-  /@types/geojson@7946.0.10:
-    resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==}
-    dev: false
-
   /@types/graceful-fs@4.1.5:
     resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
     dependencies:
@@ -4076,15 +3418,6 @@ packages:
     resolution: {integrity: sha512-xZmuPTa3rlZoIbtDUyJKZQimJV3bxCmzMIO2c9Pz9afyDro6kr7R79GwcB6mRhuoPmV2p1Vb66WOJH7F886WKQ==}
     dev: false
 
-  /@types/mdast@3.0.10:
-    resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
-    dependencies:
-      '@types/unist': 2.0.6
-
-  /@types/mdurl@1.0.2:
-    resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
-    dev: false
-
   /@types/mime@3.0.1:
     resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
     dev: false
@@ -4093,10 +3426,6 @@ packages:
     resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
     dev: true
 
-  /@types/ms@0.7.31:
-    resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
-    dev: false
-
   /@types/node@12.20.33:
     resolution: {integrity: sha512-5XmYX2GECSa+CxMYaFsr2mrql71Q4EvHjKS+ox/SiwSdaASMoBIWE6UmZqFO+VX1jIcsYLStI4FFoB6V7FeIYw==}
     dev: false
@@ -4112,10 +3441,6 @@ packages:
   /@types/parse-json@4.0.0:
     resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
 
-  /@types/parse5@6.0.3:
-    resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
-    dev: false
-
   /@types/prettier@2.7.0:
     resolution: {integrity: sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==}
     dev: false
@@ -4234,6 +3559,7 @@ packages:
 
   /@types/unist@2.0.6:
     resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
+    dev: false
 
   /@types/ws@8.5.3:
     resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
@@ -4677,7 +4003,6 @@ packages:
   /ansi-regex@6.0.1:
     resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
     engines: {node: '>=12'}
-    dev: false
 
   /ansi-styles@3.2.1:
     resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
@@ -4696,6 +4021,11 @@ packages:
     engines: {node: '>=10'}
     dev: false
 
+  /ansi-styles@6.2.1:
+    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+    engines: {node: '>=12'}
+    dev: true
+
   /anymatch@3.1.2:
     resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
     engines: {node: '>= 8'}
@@ -4749,6 +4079,7 @@ packages:
       es-abstract: 1.20.2
       get-intrinsic: 1.1.2
       is-string: 1.0.7
+    dev: false
 
   /array-timsort@1.0.3:
     resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
@@ -4776,6 +4107,7 @@ packages:
       define-properties: 1.1.4
       es-abstract: 1.20.2
       es-shim-unscopables: 1.0.0
+    dev: false
 
   /array.prototype.reduce@1.0.4:
     resolution: {integrity: sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==}
@@ -4853,19 +4185,6 @@ packages:
       postcss-value-parser: 4.2.0
     dev: false
 
-  /autoprefixer@9.8.8:
-    resolution: {integrity: sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==}
-    hasBin: true
-    dependencies:
-      browserslist: 4.23.0
-      caniuse-lite: 1.0.30001612
-      normalize-range: 0.1.2
-      num2fraction: 1.2.2
-      picocolors: 0.2.1
-      postcss: 8.4.38
-      postcss-value-parser: 4.2.0
-    dev: true
-
   /aws-sign2@0.7.0:
     resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
     dev: false
@@ -5056,14 +4375,6 @@ packages:
       - supports-color
     dev: false
 
-  /bail@1.0.5:
-    resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==}
-    dev: true
-
-  /bail@2.0.2:
-    resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
-    dev: false
-
   /balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
@@ -5152,14 +4463,6 @@ packages:
     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
     dev: false
 
-  /bootstrap@5.3.3(@popperjs/core@2.11.8):
-    resolution: {integrity: sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==}
-    peerDependencies:
-      '@popperjs/core': ^2.11.8
-    dependencies:
-      '@popperjs/core': 2.11.8
-    dev: false
-
   /brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
     dependencies:
@@ -5170,7 +4473,6 @@ packages:
     resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
     dependencies:
       balanced-match: 1.0.2
-    dev: false
 
   /braces@3.0.3:
     resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -5257,10 +4559,6 @@ packages:
     resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
     dev: false
 
-  /buffer-from@0.1.2:
-    resolution: {integrity: sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==}
-    dev: false
-
   /buffer-from@1.1.2:
     resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
     dev: false
@@ -5281,11 +4579,6 @@ packages:
     engines: {node: '>=6'}
     dev: false
 
-  /byte-size@7.0.1:
-    resolution: {integrity: sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A==}
-    engines: {node: '>=10'}
-    dev: false
-
   /byte-size@8.1.0:
     resolution: {integrity: sha512-FkgMTAg44I0JtEaUAvuZTtU2a2YDmBRbQxdsQNSMtLCjhG0hMcF5b1IMN9UjSCJaU4nvlj/GER7B9sI4nKdCgA==}
     engines: {node: '>=12.17'}
@@ -5311,6 +4604,7 @@ packages:
     dependencies:
       function-bind: 1.1.1
       get-intrinsic: 1.1.2
+    dev: false
 
   /call-bind@1.0.7:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
@@ -5378,10 +4672,6 @@ packages:
     resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
     dev: false
 
-  /ccount@2.0.1:
-    resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
-    dev: false
-
   /chalk@2.4.2:
     resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
     engines: {node: '>=4'}
@@ -5409,19 +4699,14 @@ packages:
 
   /character-entities-legacy@1.1.4:
     resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}
+    dev: false
 
   /character-entities@1.2.4:
     resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==}
-
-  /character-entities@2.0.2:
-    resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
     dev: false
 
   /character-reference-invalid@1.1.4:
     resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
-
-  /charenc@0.0.2:
-    resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
     dev: false
 
   /check-more-types@2.24.0:
@@ -5433,26 +4718,6 @@ packages:
     resolution: {integrity: sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==}
     dev: false
 
-  /chevrotain-allstar@0.3.1(chevrotain@11.0.3):
-    resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==}
-    peerDependencies:
-      chevrotain: ^11.0.0
-    dependencies:
-      chevrotain: 11.0.3
-      lodash-es: 4.17.21
-    dev: false
-
-  /chevrotain@11.0.3:
-    resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==}
-    dependencies:
-      '@chevrotain/cst-dts-gen': 11.0.3
-      '@chevrotain/gast': 11.0.3
-      '@chevrotain/regexp-to-ast': 11.0.3
-      '@chevrotain/types': 11.0.3
-      '@chevrotain/utils': 11.0.3
-      lodash-es: 4.17.21
-    dev: false
-
   /chokidar@3.5.3:
     resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
     engines: {node: '>= 8.10.0'}
@@ -5619,10 +4884,6 @@ packages:
     resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
     dev: false
 
-  /comma-separated-tokens@2.0.2:
-    resolution: {integrity: sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==}
-    dev: false
-
   /commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
     dev: false
@@ -5756,12 +5017,6 @@ packages:
     requiresBuild: true
     dev: false
 
-  /core-js@2.6.12:
-    resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
-    deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
-    requiresBuild: true
-    dev: false
-
   /core-js@3.25.1:
     resolution: {integrity: sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ==}
     requiresBuild: true
@@ -5774,12 +5029,6 @@ packages:
   /core-util-is@1.0.3:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
 
-  /cose-base@1.0.3:
-    resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
-    dependencies:
-      layout-base: 1.0.2
-    dev: false
-
   /cosmiconfig-typescript-loader@1.0.9(@types/node@12.20.33)(cosmiconfig@7.0.1)(typescript@4.8.3):
     resolution: {integrity: sha512-tRuMRhxN4m1Y8hP9SNYfz7jRwt8lZdWxdjg/ohg5esKmsndJIn4yT96oJVcf5x0eA11taXl+sIp+ielu529k6g==}
     engines: {node: '>=12', npm: '>=6'}
@@ -5882,9 +5131,14 @@ packages:
       shebang-command: 2.0.0
       which: 2.0.2
 
-  /crypt@0.0.2:
-    resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
-    dev: false
+  /cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+    engines: {node: '>= 8'}
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+    dev: true
 
   /crypto-browserify@3.12.0:
     resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==}
@@ -6274,539 +5528,33 @@ packages:
       yauzl: 2.10.0
     dev: false
 
-  /cytoscape-cose-bilkent@4.1.0(cytoscape@3.30.2):
-    resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
-    peerDependencies:
-      cytoscape: ^3.2.0
-    dependencies:
-      cose-base: 1.0.3
-      cytoscape: 3.30.2
+  /damerau-levenshtein@1.0.8:
+    resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
     dev: false
 
-  /cytoscape@3.30.2:
-    resolution: {integrity: sha512-oICxQsjW8uSaRmn4UK/jkczKOqTrVqt5/1WL0POiJUT2EKNc9STM4hYFHv917yu55aTBMFNRzymlJhVAiWPCxw==}
+  /dashdash@1.14.1:
+    resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
     engines: {node: '>=0.10'}
-    dev: false
-
-  /d3-array@1.2.4:
-    resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==}
-    dev: false
-
-  /d3-array@3.2.4:
-    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
-    engines: {node: '>=12'}
     dependencies:
-      internmap: 2.0.3
+      assert-plus: 1.0.0
     dev: false
 
-  /d3-axis@1.0.12:
-    resolution: {integrity: sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==}
+  /data-urls@2.0.0:
+    resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==}
+    engines: {node: '>=10'}
+    dependencies:
+      abab: 2.0.6
+      whatwg-mimetype: 2.3.0
+      whatwg-url: 8.7.0
     dev: false
 
-  /d3-axis@3.0.0:
-    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
-    engines: {node: '>=12'}
+  /date-fns@2.25.0:
+    resolution: {integrity: sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==}
+    engines: {node: '>=0.11'}
     dev: false
 
-  /d3-brush@1.1.6:
-    resolution: {integrity: sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==}
-    dependencies:
-      d3-dispatch: 1.0.6
-      d3-drag: 1.2.5
-      d3-interpolate: 1.4.0
-      d3-selection: 1.4.2
-      d3-transition: 1.3.2
-    dev: false
-
-  /d3-brush@3.0.0:
-    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-dispatch: 3.0.1
-      d3-drag: 3.0.0
-      d3-interpolate: 3.0.1
-      d3-selection: 3.0.0
-      d3-transition: 3.0.1(d3-selection@3.0.0)
-    dev: false
-
-  /d3-chord@1.0.6:
-    resolution: {integrity: sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==}
-    dependencies:
-      d3-array: 1.2.4
-      d3-path: 1.0.9
-    dev: false
-
-  /d3-chord@3.0.1:
-    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-path: 3.1.0
-    dev: false
-
-  /d3-collection@1.0.7:
-    resolution: {integrity: sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==}
-    dev: false
-
-  /d3-color@3.1.0:
-    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-contour@1.3.2:
-    resolution: {integrity: sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==}
-    dependencies:
-      d3-array: 1.2.4
-    dev: false
-
-  /d3-contour@4.0.2:
-    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-array: 3.2.4
-    dev: false
-
-  /d3-delaunay@6.0.4:
-    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
-    engines: {node: '>=12'}
-    dependencies:
-      delaunator: 5.0.1
-    dev: false
-
-  /d3-dispatch@1.0.6:
-    resolution: {integrity: sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==}
-    dev: false
-
-  /d3-dispatch@3.0.1:
-    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-drag@1.2.5:
-    resolution: {integrity: sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==}
-    dependencies:
-      d3-dispatch: 1.0.6
-      d3-selection: 1.4.2
-    dev: false
-
-  /d3-drag@3.0.0:
-    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-dispatch: 3.0.1
-      d3-selection: 3.0.0
-    dev: false
-
-  /d3-dsv@1.2.0:
-    resolution: {integrity: sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==}
-    hasBin: true
-    dependencies:
-      commander: 2.20.3
-      iconv-lite: 0.4.24
-      rw: 1.3.3
-    dev: false
-
-  /d3-dsv@3.0.1:
-    resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
-    engines: {node: '>=12'}
-    hasBin: true
-    dependencies:
-      commander: 7.2.0
-      iconv-lite: 0.6.3
-      rw: 1.3.3
-    dev: false
-
-  /d3-ease@1.0.7:
-    resolution: {integrity: sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==}
-    dev: false
-
-  /d3-ease@3.0.1:
-    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-fetch@1.2.0:
-    resolution: {integrity: sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==}
-    dependencies:
-      d3-dsv: 1.2.0
-    dev: false
-
-  /d3-fetch@3.0.1:
-    resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-dsv: 3.0.1
-    dev: false
-
-  /d3-flame-graph@2.2.2:
-    resolution: {integrity: sha512-Vo5wqnYA2RsrnBsSYTQB72DhMcduE9dgkf22Fy0f/0qXUpflL3Yo8YKxmzPi6Fioy7/0fgygYYZrf/JbrE1jyQ==}
-    dependencies:
-      d3-array: 1.2.4
-      d3-dispatch: 1.0.6
-      d3-ease: 1.0.7
-      d3-format: 1.4.5
-      d3-hierarchy: 1.1.9
-      d3-scale: 2.2.2
-      d3-selection: 1.4.2
-      d3-transition: 1.3.2
-    dev: false
-
-  /d3-force@1.2.1:
-    resolution: {integrity: sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==}
-    dependencies:
-      d3-collection: 1.0.7
-      d3-dispatch: 1.0.6
-      d3-quadtree: 1.0.7
-      d3-timer: 1.0.10
-    dev: false
-
-  /d3-force@3.0.0:
-    resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-dispatch: 3.0.1
-      d3-quadtree: 3.0.1
-      d3-timer: 3.0.1
-    dev: false
-
-  /d3-format@1.4.5:
-    resolution: {integrity: sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==}
-    dev: false
-
-  /d3-format@3.1.0:
-    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-geo@1.12.1:
-    resolution: {integrity: sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==}
-    dependencies:
-      d3-array: 1.2.4
-    dev: false
-
-  /d3-geo@3.1.1:
-    resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-array: 3.2.4
-    dev: false
-
-  /d3-hierarchy@1.1.9:
-    resolution: {integrity: sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==}
-    dev: false
-
-  /d3-hierarchy@3.1.2:
-    resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-interpolate@1.4.0:
-    resolution: {integrity: sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==}
-    dependencies:
-      d3-color: 3.1.0
-    dev: false
-
-  /d3-interpolate@3.0.1:
-    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-color: 3.1.0
-    dev: false
-
-  /d3-path@1.0.9:
-    resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
-    dev: false
-
-  /d3-path@3.1.0:
-    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-polygon@1.0.6:
-    resolution: {integrity: sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==}
-    dev: false
-
-  /d3-polygon@3.0.1:
-    resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-quadtree@1.0.7:
-    resolution: {integrity: sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==}
-    dev: false
-
-  /d3-quadtree@3.0.1:
-    resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-random@1.1.2:
-    resolution: {integrity: sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==}
-    dev: false
-
-  /d3-random@3.0.1:
-    resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-sankey@0.12.3:
-    resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
-    dependencies:
-      d3-array: 1.2.4
-      d3-shape: 1.3.7
-    dev: false
-
-  /d3-scale-chromatic@1.5.0:
-    resolution: {integrity: sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==}
-    dependencies:
-      d3-color: 3.1.0
-      d3-interpolate: 1.4.0
-    dev: false
-
-  /d3-scale-chromatic@3.1.0:
-    resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-color: 3.1.0
-      d3-interpolate: 3.0.1
-    dev: false
-
-  /d3-scale@2.2.2:
-    resolution: {integrity: sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==}
-    dependencies:
-      d3-array: 1.2.4
-      d3-collection: 1.0.7
-      d3-format: 1.4.5
-      d3-interpolate: 1.4.0
-      d3-time: 1.1.0
-      d3-time-format: 2.3.0
-    dev: false
-
-  /d3-scale@4.0.2:
-    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-array: 3.2.4
-      d3-format: 3.1.0
-      d3-interpolate: 3.0.1
-      d3-time: 3.1.0
-      d3-time-format: 4.1.0
-    dev: false
-
-  /d3-selection@1.4.2:
-    resolution: {integrity: sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==}
-    dev: false
-
-  /d3-selection@3.0.0:
-    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-shape@1.3.7:
-    resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
-    dependencies:
-      d3-path: 1.0.9
-    dev: false
-
-  /d3-shape@3.2.0:
-    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-path: 3.1.0
-    dev: false
-
-  /d3-time-format@2.3.0:
-    resolution: {integrity: sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==}
-    dependencies:
-      d3-time: 1.1.0
-    dev: false
-
-  /d3-time-format@4.1.0:
-    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-time: 3.1.0
-    dev: false
-
-  /d3-time@1.1.0:
-    resolution: {integrity: sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==}
-    dev: false
-
-  /d3-time@3.1.0:
-    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-array: 3.2.4
-    dev: false
-
-  /d3-timer@1.0.10:
-    resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==}
-    dev: false
-
-  /d3-timer@3.0.1:
-    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /d3-transition@1.3.2:
-    resolution: {integrity: sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==}
-    dependencies:
-      d3-color: 3.1.0
-      d3-dispatch: 1.0.6
-      d3-ease: 1.0.7
-      d3-interpolate: 1.4.0
-      d3-selection: 1.4.2
-      d3-timer: 1.0.10
-    dev: false
-
-  /d3-transition@3.0.1(d3-selection@3.0.0):
-    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
-    engines: {node: '>=12'}
-    peerDependencies:
-      d3-selection: 2 - 3
-    dependencies:
-      d3-color: 3.1.0
-      d3-dispatch: 3.0.1
-      d3-ease: 3.0.1
-      d3-interpolate: 3.0.1
-      d3-selection: 3.0.0
-      d3-timer: 3.0.1
-    dev: false
-
-  /d3-voronoi@1.1.4:
-    resolution: {integrity: sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==}
-    dev: false
-
-  /d3-zoom@1.8.3:
-    resolution: {integrity: sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==}
-    dependencies:
-      d3-dispatch: 1.0.6
-      d3-drag: 1.2.5
-      d3-interpolate: 1.4.0
-      d3-selection: 1.4.2
-      d3-transition: 1.3.2
-    dev: false
-
-  /d3-zoom@3.0.0:
-    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-dispatch: 3.0.1
-      d3-drag: 3.0.0
-      d3-interpolate: 3.0.1
-      d3-selection: 3.0.0
-      d3-transition: 3.0.1(d3-selection@3.0.0)
-    dev: false
-
-  /d3@5.16.0:
-    resolution: {integrity: sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==}
-    dependencies:
-      d3-array: 1.2.4
-      d3-axis: 1.0.12
-      d3-brush: 1.1.6
-      d3-chord: 1.0.6
-      d3-collection: 1.0.7
-      d3-color: 3.1.0
-      d3-contour: 1.3.2
-      d3-dispatch: 1.0.6
-      d3-drag: 1.2.5
-      d3-dsv: 1.2.0
-      d3-ease: 1.0.7
-      d3-fetch: 1.2.0
-      d3-force: 1.2.1
-      d3-format: 1.4.5
-      d3-geo: 1.12.1
-      d3-hierarchy: 1.1.9
-      d3-interpolate: 1.4.0
-      d3-path: 1.0.9
-      d3-polygon: 1.0.6
-      d3-quadtree: 1.0.7
-      d3-random: 1.1.2
-      d3-scale: 2.2.2
-      d3-scale-chromatic: 1.5.0
-      d3-selection: 1.4.2
-      d3-shape: 1.3.7
-      d3-time: 1.1.0
-      d3-time-format: 2.3.0
-      d3-timer: 1.0.10
-      d3-transition: 1.3.2
-      d3-voronoi: 1.1.4
-      d3-zoom: 1.8.3
-    dev: false
-
-  /d3@7.9.0:
-    resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
-    engines: {node: '>=12'}
-    dependencies:
-      d3-array: 3.2.4
-      d3-axis: 3.0.0
-      d3-brush: 3.0.0
-      d3-chord: 3.0.1
-      d3-color: 3.1.0
-      d3-contour: 4.0.2
-      d3-delaunay: 6.0.4
-      d3-dispatch: 3.0.1
-      d3-drag: 3.0.0
-      d3-dsv: 3.0.1
-      d3-ease: 3.0.1
-      d3-fetch: 3.0.1
-      d3-force: 3.0.0
-      d3-format: 3.1.0
-      d3-geo: 3.1.1
-      d3-hierarchy: 3.1.2
-      d3-interpolate: 3.0.1
-      d3-path: 3.1.0
-      d3-polygon: 3.0.1
-      d3-quadtree: 3.0.1
-      d3-random: 3.0.1
-      d3-scale: 4.0.2
-      d3-scale-chromatic: 3.1.0
-      d3-selection: 3.0.0
-      d3-shape: 3.2.0
-      d3-time: 3.1.0
-      d3-time-format: 4.1.0
-      d3-timer: 3.0.1
-      d3-transition: 3.0.1(d3-selection@3.0.0)
-      d3-zoom: 3.0.0
-    dev: false
-
-  /dagre-d3-es@7.0.10:
-    resolution: {integrity: sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==}
-    dependencies:
-      d3: 7.9.0
-      lodash-es: 4.17.21
-    dev: false
-
-  /damerau-levenshtein@1.0.8:
-    resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
-    dev: false
-
-  /dashdash@1.14.1:
-    resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
-    engines: {node: '>=0.10'}
-    dependencies:
-      assert-plus: 1.0.0
-    dev: false
-
-  /data-urls@2.0.0:
-    resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==}
-    engines: {node: '>=10'}
-    dependencies:
-      abab: 2.0.6
-      whatwg-mimetype: 2.3.0
-      whatwg-url: 8.7.0
-    dev: false
-
-  /date-fns@2.25.0:
-    resolution: {integrity: sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==}
-    engines: {node: '>=0.11'}
-    dev: false
-
-  /dayjs@1.11.13:
-    resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
-    dev: false
-
-  /dayjs@1.11.9:
-    resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}
+  /dayjs@1.11.9:
+    resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}
     dev: false
 
   /debug@2.6.9:
@@ -6861,12 +5609,6 @@ packages:
     resolution: {integrity: sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==}
     dev: false
 
-  /decode-named-character-reference@1.0.2:
-    resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
-    dependencies:
-      character-entities: 2.0.2
-    dev: false
-
   /dedent@0.7.0:
     resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
     dev: false
@@ -6911,17 +5653,12 @@ packages:
     dependencies:
       has-property-descriptors: 1.0.0
       object-keys: 1.1.1
+    dev: false
 
   /defined@1.0.0:
     resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==}
     dev: false
 
-  /delaunator@5.0.1:
-    resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
-    dependencies:
-      robust-predicates: 3.0.2
-    dev: false
-
   /delayed-stream@1.0.0:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
     engines: {node: '>=0.4.0'}
@@ -6937,11 +5674,6 @@ packages:
     engines: {node: '>= 0.8'}
     dev: false
 
-  /dequal@2.0.2:
-    resolution: {integrity: sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==}
-    engines: {node: '>=6'}
-    dev: false
-
   /des.js@1.0.1:
     resolution: {integrity: sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==}
     dependencies:
@@ -6998,11 +5730,6 @@ packages:
     engines: {node: '>=0.3.1'}
     dev: false
 
-  /diff@5.1.0:
-    resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
-    engines: {node: '>=0.3.1'}
-    dev: false
-
   /diffie-hellman@5.0.3:
     resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
     dependencies:
@@ -7037,6 +5764,7 @@ packages:
     engines: {node: '>=0.10.0'}
     dependencies:
       esutils: 2.0.3
+    dev: false
 
   /doctrine@3.0.0:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
@@ -7050,12 +5778,6 @@ packages:
       utila: 0.4.0
     dev: false
 
-  /dom-helpers@3.4.0:
-    resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
-    dependencies:
-      '@babel/runtime': 7.19.0
-    dev: false
-
   /dom-helpers@5.2.1:
     resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
     dependencies:
@@ -7068,6 +5790,7 @@ packages:
     dependencies:
       domelementtype: 2.3.0
       entities: 2.2.0
+    dev: false
 
   /dom-serializer@1.4.1:
     resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
@@ -7079,9 +5802,11 @@ packages:
 
   /domelementtype@1.3.1:
     resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}
+    dev: false
 
   /domelementtype@2.3.0:
     resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+    dev: false
 
   /domexception@2.0.1:
     resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==}
@@ -7091,12 +5816,6 @@ packages:
       webidl-conversions: 5.0.0
     dev: false
 
-  /domhandler@2.4.2:
-    resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==}
-    dependencies:
-      domelementtype: 1.3.1
-    dev: true
-
   /domhandler@4.3.1:
     resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
     engines: {node: '>= 4'}
@@ -7104,15 +5823,12 @@ packages:
       domelementtype: 2.3.0
     dev: false
 
-  /dompurify@3.1.6:
-    resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==}
-    dev: false
-
   /domutils@1.7.0:
     resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
     dependencies:
       dom-serializer: 0.2.2
       domelementtype: 1.3.1
+    dev: false
 
   /domutils@2.8.0:
     resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@@ -7145,16 +5861,14 @@ packages:
     engines: {node: '>=10'}
     dev: false
 
-  /duplexer2@0.1.4:
-    resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
-    dependencies:
-      readable-stream: 2.3.8
-    dev: false
-
   /duplexer@0.1.2:
     resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
     dev: false
 
+  /eastasianwidth@0.2.0:
+    resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+    dev: true
+
   /ecc-jsbn@0.1.2:
     resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
     dependencies:
@@ -7204,7 +5918,6 @@ packages:
 
   /emoji-regex@9.2.2:
     resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
-    dev: false
 
   /emojis-list@3.0.0:
     resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
@@ -7243,12 +5956,9 @@ packages:
       strip-ansi: 6.0.1
     dev: false
 
-  /entities@1.1.2:
-    resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
-    dev: true
-
   /entities@2.2.0:
     resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+    dev: false
 
   /error-ex@1.3.2:
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@@ -7288,6 +5998,7 @@ packages:
       string.prototype.trimend: 1.0.5
       string.prototype.trimstart: 1.0.5
       unbox-primitive: 1.0.2
+    dev: false
 
   /es-array-method-boxes-properly@1.0.0:
     resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
@@ -7313,6 +6024,7 @@ packages:
     resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==}
     dependencies:
       has: 1.0.3
+    dev: false
 
   /es-to-primitive@1.2.1:
     resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
@@ -7321,9 +6033,6 @@ packages:
       is-callable: 1.2.4
       is-date-object: 1.0.5
       is-symbol: 1.0.4
-
-  /es6-promise@4.2.8:
-    resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
     dev: false
 
   /escalade@3.1.2:
@@ -7347,11 +6056,6 @@ packages:
     resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
     engines: {node: '>=10'}
 
-  /escape-string-regexp@5.0.0:
-    resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
-    engines: {node: '>=12'}
-    dev: false
-
   /escodegen@2.0.0:
     resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==}
     engines: {node: '>=6.0'}
@@ -7544,6 +6248,7 @@ packages:
       eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
     dependencies:
       eslint: 8.57.0
+    dev: false
 
   /eslint-plugin-react@7.31.8(eslint@8.57.0):
     resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==}
@@ -7566,6 +6271,7 @@ packages:
       resolve: 2.0.0-next.4
       semver: 7.5.4
       string.prototype.matchall: 4.0.7
+    dev: false
 
   /eslint-plugin-testing-library@5.6.3(eslint@8.57.0)(typescript@4.8.3):
     resolution: {integrity: sha512-//fhmCzopr8UDv5X2M3XMGxQ0j6KjKYZ+6PGqdV0woLiXTSTOAzuNsiTELGv883iCeUrYrnHhtObPXyiTMytVQ==}
@@ -7580,11 +6286,6 @@ packages:
       - typescript
     dev: false
 
-  /eslint-rule-composer@0.3.0:
-    resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==}
-    engines: {node: '>=4.0.0'}
-    dev: true
-
   /eslint-scope@5.1.1:
     resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
     engines: {node: '>=8.0.0'}
@@ -7728,10 +6429,6 @@ packages:
     resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==}
     dev: false
 
-  /eventemitter3@1.2.0:
-    resolution: {integrity: sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA==}
-    dev: false
-
   /eventemitter3@4.0.7:
     resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
     dev: false
@@ -7848,6 +6545,7 @@ packages:
 
   /extend@3.0.2:
     resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+    dev: false
 
   /extract-zip@2.0.1(supports-color@8.1.1):
     resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
@@ -7957,7 +6655,7 @@ packages:
   /filelist@1.0.4:
     resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
     dependencies:
-      minimatch: 5.1.0
+      minimatch: 3.1.2
     dev: false
 
   /filesize@8.0.7:
@@ -8047,6 +6745,14 @@ packages:
         optional: true
     dev: false
 
+  /foreground-child@3.3.1:
+    resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+    engines: {node: '>=14'}
+    dependencies:
+      cross-spawn: 7.0.6
+      signal-exit: 4.1.0
+    dev: true
+
   /forever-agent@0.6.1:
     resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==}
     dev: false
@@ -8075,7 +6781,7 @@ packages:
       fs-extra: 9.1.0
       glob: 7.2.3
       memfs: 3.4.7
-      minimatch: 5.1.0
+      minimatch: 3.1.2
       schema-utils: 2.7.0
       semver: 7.5.4
       tapable: 1.1.3
@@ -8182,12 +6888,14 @@ packages:
       define-properties: 1.1.4
       es-abstract: 1.20.2
       functions-have-names: 1.2.3
+    dev: false
 
   /functional-red-black-tree@1.0.1:
     resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
 
   /functions-have-names@1.2.3:
     resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+    dev: false
 
   /gensequence@3.1.1:
     resolution: {integrity: sha512-ys3h0hiteRwmY6BsvSttPmkhC0vEQHPJduANBRtH/dlDPZ0UBIb/dXy80IcckXyuQ6LKg+PloRqvGER9IS7F7g==}
@@ -8209,6 +6917,7 @@ packages:
       function-bind: 1.1.1
       has: 1.0.3
       has-symbols: 1.0.3
+    dev: false
 
   /get-intrinsic@1.2.4:
     resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
@@ -8253,6 +6962,7 @@ packages:
     dependencies:
       call-bind: 1.0.2
       get-intrinsic: 1.1.2
+    dev: false
 
   /get-user-locale@1.4.0:
     resolution: {integrity: sha512-gQo03lP1OArHLKlnoglqrGGl7b04u2EP9Xutmp72cMdtrrSD7ZgIsCsUKZynYWLDkVJW33Cj3pliP7uP0UonHQ==}
@@ -8288,8 +6998,22 @@ packages:
     resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
     dev: false
 
+  /glob@11.0.2:
+    resolution: {integrity: sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==}
+    engines: {node: 20 || >=22}
+    hasBin: true
+    dependencies:
+      foreground-child: 3.3.1
+      jackspeak: 4.1.0
+      minimatch: 10.0.1
+      minipass: 7.1.2
+      package-json-from-dist: 1.0.1
+      path-scurry: 2.0.0
+    dev: true
+
   /glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
     dependencies:
       fs.realpath: 1.0.0
       inflight: 1.0.6
@@ -8351,14 +7075,6 @@ packages:
     resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
     dev: true
 
-  /gonzales-pe@4.3.0:
-    resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==}
-    engines: {node: '>=0.6.0'}
-    hasBin: true
-    dependencies:
-      minimist: 1.2.8
-    dev: true
-
   /gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
     dependencies:
@@ -8378,10 +7094,6 @@ packages:
       duplexer: 0.1.2
     dev: false
 
-  /hachure-fill@0.5.2:
-    resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
-    dev: false
-
   /handle-thing@2.0.1:
     resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==}
     dev: false
@@ -8397,6 +7109,7 @@ packages:
 
   /has-bigints@1.0.2:
     resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+    dev: false
 
   /has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
@@ -8415,6 +7128,7 @@ packages:
     resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
     dependencies:
       get-intrinsic: 1.1.2
+    dev: false
 
   /has-property-descriptors@1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
@@ -8430,12 +7144,14 @@ packages:
   /has-symbols@1.0.3:
     resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
     engines: {node: '>= 0.4'}
+    dev: false
 
   /has-tostringtag@1.0.0:
     resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
     engines: {node: '>= 0.4'}
     dependencies:
       has-symbols: 1.0.3
+    dev: false
 
   /has@1.0.3:
     resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
@@ -8474,72 +7190,10 @@ packages:
       function-bind: 1.1.2
     dev: false
 
-  /hast-to-hyperscript@10.0.1:
-    resolution: {integrity: sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==}
-    dependencies:
-      '@types/unist': 2.0.6
-      comma-separated-tokens: 2.0.2
-      property-information: 6.1.1
-      space-separated-tokens: 2.0.1
-      style-to-object: 0.3.0
-      unist-util-is: 5.1.1
-      web-namespaces: 2.0.1
-    dev: false
-
-  /hast-util-from-parse5@7.1.0:
-    resolution: {integrity: sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==}
-    dependencies:
-      '@types/hast': 2.3.4
-      '@types/parse5': 6.0.3
-      '@types/unist': 2.0.6
-      hastscript: 7.0.2
-      property-information: 6.1.1
-      vfile: 5.3.0
-      vfile-location: 4.0.1
-      web-namespaces: 2.0.1
-    dev: false
-
   /hast-util-parse-selector@2.2.5:
     resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
     dev: false
 
-  /hast-util-parse-selector@3.1.0:
-    resolution: {integrity: sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==}
-    dependencies:
-      '@types/hast': 2.3.4
-    dev: false
-
-  /hast-util-raw@7.2.1:
-    resolution: {integrity: sha512-wgtppqXVdXzkDXDFclLLdAyVUJSKMYYi6LWIAbA8oFqEdwksYIcPGM3RkKV1Dfn5GElvxhaOCs0jmCOMayxd3A==}
-    dependencies:
-      '@types/hast': 2.3.4
-      '@types/parse5': 6.0.3
-      hast-util-from-parse5: 7.1.0
-      hast-util-to-parse5: 7.0.0
-      html-void-elements: 2.0.1
-      parse5: 6.0.1
-      unist-util-position: 4.0.1
-      unist-util-visit: 4.1.0
-      vfile: 5.3.0
-      web-namespaces: 2.0.1
-      zwitch: 2.0.2
-    dev: false
-
-  /hast-util-to-parse5@7.0.0:
-    resolution: {integrity: sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A==}
-    dependencies:
-      '@types/hast': 2.3.4
-      '@types/parse5': 6.0.3
-      hast-to-hyperscript: 10.0.1
-      property-information: 6.1.1
-      web-namespaces: 2.0.1
-      zwitch: 2.0.2
-    dev: false
-
-  /hast-util-whitespace@2.0.0:
-    resolution: {integrity: sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==}
-    dev: false
-
   /hastscript@6.0.0:
     resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==}
     dependencies:
@@ -8550,16 +7204,6 @@ packages:
       space-separated-tokens: 1.1.5
     dev: false
 
-  /hastscript@7.0.2:
-    resolution: {integrity: sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==}
-    dependencies:
-      '@types/hast': 2.3.4
-      comma-separated-tokens: 2.0.2
-      hast-util-parse-selector: 3.1.0
-      property-information: 6.1.1
-      space-separated-tokens: 2.0.1
-    dev: false
-
   /he@1.2.0:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
@@ -8651,22 +7295,7 @@ packages:
   /html-tags@3.2.0:
     resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==}
     engines: {node: '>=8'}
-    dev: true
-
-  /html-tokenize@2.0.1:
-    resolution: {integrity: sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w==}
-    hasBin: true
-    dependencies:
-      buffer-from: 0.1.2
-      inherits: 2.0.4
-      minimist: 1.2.8
-      readable-stream: 1.0.34
-      through2: 0.4.2
-    dev: false
-
-  /html-void-elements@2.0.1:
-    resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
-    dev: false
+    dev: true
 
   /html-webpack-plugin@5.5.0(webpack@5.95.0):
     resolution: {integrity: sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==}
@@ -8682,17 +7311,6 @@ packages:
       webpack: 5.95.0
     dev: false
 
-  /htmlparser2@3.10.1:
-    resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
-    dependencies:
-      domelementtype: 1.3.1
-      domhandler: 2.4.2
-      domutils: 1.7.0
-      entities: 1.1.2
-      inherits: 2.0.4
-      readable-stream: 3.6.0
-    dev: true
-
   /htmlparser2@6.1.0:
     resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
     dependencies:
@@ -8901,10 +7519,6 @@ packages:
     engines: {node: '>=10'}
     dev: false
 
-  /inline-style-parser@0.1.1:
-    resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
-    dev: false
-
   /internal-slot@1.0.3:
     resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
     engines: {node: '>= 0.4'}
@@ -8912,16 +7526,6 @@ packages:
       get-intrinsic: 1.1.2
       has: 1.0.3
       side-channel: 1.0.4
-
-  /internmap@2.0.3:
-    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
-    engines: {node: '>=12'}
-    dev: false
-
-  /invariant@2.2.4:
-    resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
-    dependencies:
-      loose-envify: 1.4.0
     dev: false
 
   /ipaddr.js@1.9.1:
@@ -8936,12 +7540,14 @@ packages:
 
   /is-alphabetical@1.0.4:
     resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
+    dev: false
 
   /is-alphanumerical@1.0.4:
     resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==}
     dependencies:
       is-alphabetical: 1.0.4
       is-decimal: 1.0.4
+    dev: false
 
   /is-arrayish@0.2.1:
     resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@@ -8950,6 +7556,7 @@ packages:
     resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
     dependencies:
       has-bigints: 1.0.2
+    dev: false
 
   /is-binary-path@2.1.0:
     resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
@@ -8963,18 +7570,12 @@ packages:
     dependencies:
       call-bind: 1.0.2
       has-tostringtag: 1.0.0
-
-  /is-buffer@1.1.6:
-    resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
     dev: false
 
-  /is-buffer@2.0.5:
-    resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
-    engines: {node: '>=4'}
-
   /is-callable@1.2.4:
     resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}
     engines: {node: '>= 0.4'}
+    dev: false
 
   /is-ci@3.0.1:
     resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
@@ -8993,9 +7594,11 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       has-tostringtag: 1.0.0
+    dev: false
 
   /is-decimal@1.0.4:
     resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
+    dev: false
 
   /is-docker@2.2.1:
     resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
@@ -9024,6 +7627,7 @@ packages:
 
   /is-hexadecimal@1.0.4:
     resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
+    dev: false
 
   /is-in-browser@1.1.3:
     resolution: {integrity: sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==}
@@ -9044,12 +7648,14 @@ packages:
   /is-negative-zero@2.0.2:
     resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
     engines: {node: '>= 0.4'}
+    dev: false
 
   /is-number-object@1.0.7:
     resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
     engines: {node: '>= 0.4'}
     dependencies:
       has-tostringtag: 1.0.0
+    dev: false
 
   /is-number@7.0.0:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
@@ -9074,21 +7680,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
-  /is-plain-obj@2.1.0:
-    resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
-    engines: {node: '>=8'}
-    dev: true
-
   /is-plain-obj@3.0.0:
     resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==}
     engines: {node: '>=10'}
     dev: false
 
-  /is-plain-obj@4.0.0:
-    resolution: {integrity: sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw==}
-    engines: {node: '>=12'}
-    dev: false
-
   /is-plain-object@2.0.4:
     resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
     engines: {node: '>=0.10.0'}
@@ -9111,6 +7707,7 @@ packages:
     dependencies:
       call-bind: 1.0.2
       has-tostringtag: 1.0.0
+    dev: false
 
   /is-regexp@1.0.0:
     resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
@@ -9131,6 +7728,7 @@ packages:
     resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
     dependencies:
       call-bind: 1.0.2
+    dev: false
 
   /is-stream@2.0.1:
     resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
@@ -9142,12 +7740,14 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       has-tostringtag: 1.0.0
+    dev: false
 
   /is-symbol@1.0.4:
     resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
     engines: {node: '>= 0.4'}
     dependencies:
       has-symbols: 1.0.3
+    dev: false
 
   /is-typedarray@1.0.0:
     resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
@@ -9155,11 +7755,13 @@ packages:
   /is-unicode-supported@0.1.0:
     resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
     engines: {node: '>=10'}
+    dev: false
 
   /is-weakref@1.0.2:
     resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
     dependencies:
       call-bind: 1.0.2
+    dev: false
 
   /is-wsl@2.2.0:
     resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
@@ -9234,6 +7836,13 @@ packages:
       istanbul-lib-report: 3.0.0
     dev: false
 
+  /jackspeak@4.1.0:
+    resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==}
+    engines: {node: 20 || >=22}
+    dependencies:
+      '@isaacs/cliui': 8.0.2
+    dev: true
+
   /jake@10.8.5:
     resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==}
     engines: {node: '>=10'}
@@ -9242,7 +7851,7 @@ packages:
       async: 3.2.4
       chalk: 4.1.2
       filelist: 1.0.4
-      minimatch: 5.1.0
+      minimatch: 3.1.2
     dev: false
 
   /jest-changed-files@27.5.1:
@@ -9979,30 +8588,6 @@ packages:
     dependencies:
       array-includes: 3.1.5
       object.assign: 4.1.4
-
-  /jwt-decode@3.1.2:
-    resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
-    dev: false
-
-  /jwt-encode@1.0.1:
-    resolution: {integrity: sha512-QrGiNhynbAYyFdbC1GbjborzenSFs5Ga+2nE0uBokGXsH11xrgd1AX55HR7P+wGQyyZOn6LUO5iKlh74dlhBkA==}
-    dependencies:
-      ts.cryptojs256: 1.0.1
-    dev: false
-
-  /katex@0.16.11:
-    resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==}
-    hasBin: true
-    dependencies:
-      commander: 8.3.0
-    dev: false
-
-  /keycode@2.2.0:
-    resolution: {integrity: sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A==}
-    dev: false
-
-  /khroma@2.1.0:
-    resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
     dev: false
 
   /kind-of@6.0.3:
@@ -10014,35 +8599,15 @@ packages:
     engines: {node: '>=6'}
     dev: false
 
-  /kleur@4.1.5:
-    resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
-    engines: {node: '>=6'}
-    dev: false
-
   /klona@2.0.5:
     resolution: {integrity: sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==}
     engines: {node: '>= 8'}
     dev: false
 
-  /known-css-properties@0.21.0:
-    resolution: {integrity: sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==}
-    dev: true
-
   /known-css-properties@0.23.0:
     resolution: {integrity: sha512-h9ivI88e1lFNmTT4HovBN33Ysn0OIJG7IPG2mkpx2uniQXFWqo35QdiX7w0TovlUFXfW8aPFblP5/q0jlOr2sA==}
     dev: true
 
-  /langium@3.0.0:
-    resolution: {integrity: sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==}
-    engines: {node: '>=16.0.0'}
-    dependencies:
-      chevrotain: 11.0.3
-      chevrotain-allstar: 0.3.1(chevrotain@11.0.3)
-      vscode-languageserver: 9.0.1
-      vscode-languageserver-textdocument: 1.0.12
-      vscode-uri: 3.0.8
-    dev: false
-
   /language-subtag-registry@0.3.22:
     resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
     dev: false
@@ -10053,10 +8618,6 @@ packages:
       language-subtag-registry: 0.3.22
     dev: false
 
-  /layout-base@1.0.2:
-    resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
-    dev: false
-
   /lazy-ass@1.6.0:
     resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==}
     engines: {node: '> 0.8'}
@@ -10196,6 +8757,7 @@ packages:
     dependencies:
       chalk: 4.1.2
       is-unicode-supported: 0.1.0
+    dev: false
 
   /log-update@4.0.0:
     resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
@@ -10207,19 +8769,12 @@ packages:
       wrap-ansi: 6.2.0
     dev: false
 
-  /longest-streak@2.0.4:
-    resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==}
-    dev: true
-
-  /longest-streak@3.0.1:
-    resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==}
-    dev: false
-
   /loose-envify@1.4.0:
     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
     hasBin: true
     dependencies:
       js-tokens: 4.0.0
+    dev: false
 
   /lower-case@2.0.2:
     resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
@@ -10234,6 +8789,11 @@ packages:
       highlight.js: 10.7.3
     dev: false
 
+  /lru-cache@11.1.0:
+    resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
+    engines: {node: 20 || >=22}
+    dev: true
+
   /lru-cache@6.0.0:
     resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
     engines: {node: '>=10'}
@@ -10272,16 +8832,6 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /markdown-table@3.0.2:
-    resolution: {integrity: sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==}
-    dev: false
-
-  /marked@13.0.3:
-    resolution: {integrity: sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==}
-    engines: {node: '>= 18'}
-    hasBin: true
-    dev: false
-
   /mathml-tag-names@2.1.3:
     resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
     dev: true
@@ -10294,162 +8844,6 @@ packages:
       safe-buffer: 5.2.1
     dev: false
 
-  /md5@2.3.0:
-    resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
-    dependencies:
-      charenc: 0.0.2
-      crypt: 0.0.2
-      is-buffer: 1.1.6
-    dev: false
-
-  /mdast-util-definitions@5.1.0:
-    resolution: {integrity: sha512-5hcR7FL2EuZ4q6lLMUK5w4lHT2H3vqL9quPvYZ/Ku5iifrirfMHiGdhxdXMUbUkDmz5I+TYMd7nbaxUhbQkfpQ==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      '@types/unist': 2.0.6
-      unist-util-visit: 3.1.0
-    dev: false
-
-  /mdast-util-find-and-replace@2.2.0:
-    resolution: {integrity: sha512-bz8hUWkMX7UcasORORcyBEsTKJ+dBiFwRPrm43hHC9NMRylIMLbfO5rwfeCN+UtY4AAi7s8WqXftb9eX6ZsqCg==}
-    dependencies:
-      escape-string-regexp: 5.0.0
-      unist-util-is: 5.1.1
-      unist-util-visit-parents: 5.1.0
-    dev: false
-
-  /mdast-util-from-markdown@0.8.5:
-    resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      mdast-util-to-string: 2.0.0
-      micromark: 2.11.4
-      parse-entities: 2.0.0
-      unist-util-stringify-position: 2.0.3
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /mdast-util-from-markdown@1.2.0:
-    resolution: {integrity: sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      '@types/unist': 2.0.6
-      decode-named-character-reference: 1.0.2
-      mdast-util-to-string: 3.1.0
-      micromark: 3.0.10
-      micromark-util-decode-numeric-character-reference: 1.0.0
-      micromark-util-decode-string: 1.0.2
-      micromark-util-normalize-identifier: 1.0.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      unist-util-stringify-position: 3.0.0
-      uvu: 0.5.6
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /mdast-util-gfm-autolink-literal@1.0.2:
-    resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      ccount: 2.0.1
-      mdast-util-find-and-replace: 2.2.0
-      micromark-util-character: 1.1.0
-    dev: false
-
-  /mdast-util-gfm-footnote@1.0.1:
-    resolution: {integrity: sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      mdast-util-to-markdown: 1.3.0
-      micromark-util-normalize-identifier: 1.0.0
-    dev: false
-
-  /mdast-util-gfm-strikethrough@1.0.1:
-    resolution: {integrity: sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      mdast-util-to-markdown: 1.3.0
-    dev: false
-
-  /mdast-util-gfm-table@1.0.4:
-    resolution: {integrity: sha512-aEuoPwZyP4iIMkf2cLWXxx3EQ6Bmh2yKy9MVCg4i6Sd3cX80dcLEfXO/V4ul3pGH9czBK4kp+FAl+ZHmSUt9/w==}
-    dependencies:
-      markdown-table: 3.0.2
-      mdast-util-from-markdown: 1.2.0
-      mdast-util-to-markdown: 1.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /mdast-util-gfm-task-list-item@1.0.1:
-    resolution: {integrity: sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      mdast-util-to-markdown: 1.3.0
-    dev: false
-
-  /mdast-util-gfm@2.0.1:
-    resolution: {integrity: sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==}
-    dependencies:
-      mdast-util-from-markdown: 1.2.0
-      mdast-util-gfm-autolink-literal: 1.0.2
-      mdast-util-gfm-footnote: 1.0.1
-      mdast-util-gfm-strikethrough: 1.0.1
-      mdast-util-gfm-table: 1.0.4
-      mdast-util-gfm-task-list-item: 1.0.1
-      mdast-util-to-markdown: 1.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /mdast-util-to-hast@12.1.1:
-    resolution: {integrity: sha512-qE09zD6ylVP14jV4mjLIhDBOrpFdShHZcEsYvvKGABlr9mGbV7mTlRWdoFxL/EYSTNDiC9GZXy7y8Shgb9Dtzw==}
-    dependencies:
-      '@types/hast': 2.3.4
-      '@types/mdast': 3.0.10
-      '@types/mdurl': 1.0.2
-      mdast-util-definitions: 5.1.0
-      mdurl: 1.0.1
-      micromark-util-sanitize-uri: 1.0.0
-      unist-builder: 3.0.0
-      unist-util-generated: 2.0.0
-      unist-util-position: 4.0.1
-      unist-util-visit: 4.1.0
-    dev: false
-
-  /mdast-util-to-markdown@0.6.5:
-    resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-      longest-streak: 2.0.4
-      mdast-util-to-string: 2.0.0
-      parse-entities: 2.0.0
-      repeat-string: 1.6.1
-      zwitch: 1.0.5
-    dev: true
-
-  /mdast-util-to-markdown@1.3.0:
-    resolution: {integrity: sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      '@types/unist': 2.0.6
-      longest-streak: 3.0.1
-      mdast-util-to-string: 3.1.0
-      micromark-util-decode-string: 1.0.2
-      unist-util-visit: 4.1.0
-      zwitch: 2.0.2
-    dev: false
-
-  /mdast-util-to-string@2.0.0:
-    resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==}
-    dev: true
-
-  /mdast-util-to-string@3.1.0:
-    resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==}
-    dev: false
-
   /mdn-data@2.0.14:
     resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
     dev: false
@@ -10458,10 +8852,6 @@ packages:
     resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==}
     dev: false
 
-  /mdurl@1.0.1:
-    resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
-    dev: false
-
   /media-typer@0.3.0:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
@@ -10474,326 +8864,46 @@ packages:
       fs-monkey: 1.0.3
     dev: false
 
-  /memfs@4.8.2:
-    resolution: {integrity: sha512-j4WKth315edViMBGkHW6NTF0QBjsTrcRDmYNcGsPq+ozMEyCCCIlX2d2mJ5wuh6iHvJ3FevUrr48v58YRqVdYg==}
-    engines: {node: '>= 4.0.0'}
-    dependencies:
-      tslib: 2.4.0
-    dev: false
-
-  /meow@9.0.0:
-    resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
-    engines: {node: '>=10'}
-    dependencies:
-      '@types/minimist': 1.2.2
-      camelcase-keys: 6.2.2
-      decamelize: 1.2.0
-      decamelize-keys: 1.1.0
-      hard-rejection: 2.1.0
-      minimist-options: 4.1.0
-      normalize-package-data: 3.0.3
-      read-pkg-up: 7.0.1
-      redent: 3.0.0
-      trim-newlines: 3.0.1
-      type-fest: 0.18.1
-      yargs-parser: 20.2.9
-    dev: true
-
-  /merge-descriptors@1.0.3:
-    resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
-    dev: false
-
-  /merge-stream@2.0.0:
-    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-    dev: false
-
-  /merge2@1.4.1:
-    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
-    engines: {node: '>= 8'}
-
-  /mermaid@11.0.2:
-    resolution: {integrity: sha512-KFM1o560odBHvXTTSx47ne/SE4aJKb2GbysHAVdQafIJtB6O3c0K4F+v3nC+zqS6CJhk7sXaagectNrTG+ARDw==}
-    dependencies:
-      '@braintree/sanitize-url': 7.1.0
-      '@mermaid-js/parser': 0.2.0
-      cytoscape: 3.30.2
-      cytoscape-cose-bilkent: 4.1.0(cytoscape@3.30.2)
-      d3: 7.9.0
-      d3-sankey: 0.12.3
-      dagre-d3-es: 7.0.10
-      dayjs: 1.11.13
-      dompurify: 3.1.6
-      katex: 0.16.11
-      khroma: 2.1.0
-      lodash-es: 4.17.21
-      marked: 13.0.3
-      roughjs: 4.6.6
-      stylis: 4.3.4
-      ts-dedent: 2.2.0
-      uuid: 9.0.1
-    dev: false
-
-  /methods@1.1.2:
-    resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
-    engines: {node: '>= 0.6'}
-    dev: false
-
-  /micromark-core-commonmark@1.0.6:
-    resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==}
-    dependencies:
-      decode-named-character-reference: 1.0.2
-      micromark-factory-destination: 1.0.0
-      micromark-factory-label: 1.0.2
-      micromark-factory-space: 1.0.0
-      micromark-factory-title: 1.0.2
-      micromark-factory-whitespace: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-chunked: 1.0.0
-      micromark-util-classify-character: 1.0.0
-      micromark-util-html-tag-name: 1.1.0
-      micromark-util-normalize-identifier: 1.0.0
-      micromark-util-resolve-all: 1.0.0
-      micromark-util-subtokenize: 1.0.2
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-extension-gfm-autolink-literal@1.0.3:
-    resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==}
-    dependencies:
-      micromark-util-character: 1.1.0
-      micromark-util-sanitize-uri: 1.0.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-extension-gfm-footnote@1.0.4:
-    resolution: {integrity: sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==}
-    dependencies:
-      micromark-core-commonmark: 1.0.6
-      micromark-factory-space: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-normalize-identifier: 1.0.0
-      micromark-util-sanitize-uri: 1.0.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-extension-gfm-strikethrough@1.0.4:
-    resolution: {integrity: sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==}
-    dependencies:
-      micromark-util-chunked: 1.0.0
-      micromark-util-classify-character: 1.0.0
-      micromark-util-resolve-all: 1.0.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-extension-gfm-table@1.0.5:
-    resolution: {integrity: sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==}
-    dependencies:
-      micromark-factory-space: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-extension-gfm-tagfilter@1.0.1:
-    resolution: {integrity: sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==}
-    dependencies:
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-extension-gfm-task-list-item@1.0.3:
-    resolution: {integrity: sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==}
-    dependencies:
-      micromark-factory-space: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-extension-gfm@2.0.1:
-    resolution: {integrity: sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==}
-    dependencies:
-      micromark-extension-gfm-autolink-literal: 1.0.3
-      micromark-extension-gfm-footnote: 1.0.4
-      micromark-extension-gfm-strikethrough: 1.0.4
-      micromark-extension-gfm-table: 1.0.5
-      micromark-extension-gfm-tagfilter: 1.0.1
-      micromark-extension-gfm-task-list-item: 1.0.3
-      micromark-util-combine-extensions: 1.0.0
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-factory-destination@1.0.0:
-    resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==}
-    dependencies:
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-factory-label@1.0.2:
-    resolution: {integrity: sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==}
-    dependencies:
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-factory-space@1.0.0:
-    resolution: {integrity: sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==}
-    dependencies:
-      micromark-util-character: 1.1.0
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-factory-title@1.0.2:
-    resolution: {integrity: sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==}
-    dependencies:
-      micromark-factory-space: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
-
-  /micromark-factory-whitespace@1.0.0:
-    resolution: {integrity: sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==}
-    dependencies:
-      micromark-factory-space: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-util-character@1.1.0:
-    resolution: {integrity: sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==}
-    dependencies:
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-util-chunked@1.0.0:
-    resolution: {integrity: sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==}
-    dependencies:
-      micromark-util-symbol: 1.0.1
-    dev: false
-
-  /micromark-util-classify-character@1.0.0:
-    resolution: {integrity: sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==}
-    dependencies:
-      micromark-util-character: 1.1.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-util-combine-extensions@1.0.0:
-    resolution: {integrity: sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==}
-    dependencies:
-      micromark-util-chunked: 1.0.0
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-util-decode-numeric-character-reference@1.0.0:
-    resolution: {integrity: sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==}
-    dependencies:
-      micromark-util-symbol: 1.0.1
-    dev: false
-
-  /micromark-util-decode-string@1.0.2:
-    resolution: {integrity: sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==}
-    dependencies:
-      decode-named-character-reference: 1.0.2
-      micromark-util-character: 1.1.0
-      micromark-util-decode-numeric-character-reference: 1.0.0
-      micromark-util-symbol: 1.0.1
-    dev: false
-
-  /micromark-util-encode@1.0.1:
-    resolution: {integrity: sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==}
-    dev: false
-
-  /micromark-util-html-tag-name@1.1.0:
-    resolution: {integrity: sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==}
-    dev: false
-
-  /micromark-util-normalize-identifier@1.0.0:
-    resolution: {integrity: sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==}
-    dependencies:
-      micromark-util-symbol: 1.0.1
-    dev: false
-
-  /micromark-util-resolve-all@1.0.0:
-    resolution: {integrity: sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==}
-    dependencies:
-      micromark-util-types: 1.0.2
-    dev: false
-
-  /micromark-util-sanitize-uri@1.0.0:
-    resolution: {integrity: sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==}
+  /memfs@4.8.2:
+    resolution: {integrity: sha512-j4WKth315edViMBGkHW6NTF0QBjsTrcRDmYNcGsPq+ozMEyCCCIlX2d2mJ5wuh6iHvJ3FevUrr48v58YRqVdYg==}
+    engines: {node: '>= 4.0.0'}
     dependencies:
-      micromark-util-character: 1.1.0
-      micromark-util-encode: 1.0.1
-      micromark-util-symbol: 1.0.1
+      tslib: 2.4.0
     dev: false
 
-  /micromark-util-subtokenize@1.0.2:
-    resolution: {integrity: sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==}
+  /meow@9.0.0:
+    resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
+    engines: {node: '>=10'}
     dependencies:
-      micromark-util-chunked: 1.0.0
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    dev: false
+      '@types/minimist': 1.2.2
+      camelcase-keys: 6.2.2
+      decamelize: 1.2.0
+      decamelize-keys: 1.1.0
+      hard-rejection: 2.1.0
+      minimist-options: 4.1.0
+      normalize-package-data: 3.0.3
+      read-pkg-up: 7.0.1
+      redent: 3.0.0
+      trim-newlines: 3.0.1
+      type-fest: 0.18.1
+      yargs-parser: 20.2.9
+    dev: true
 
-  /micromark-util-symbol@1.0.1:
-    resolution: {integrity: sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==}
+  /merge-descriptors@1.0.3:
+    resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
     dev: false
 
-  /micromark-util-types@1.0.2:
-    resolution: {integrity: sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==}
+  /merge-stream@2.0.0:
+    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
     dev: false
 
-  /micromark@2.11.4:
-    resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
-    dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
-      parse-entities: 2.0.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
+  /merge2@1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
 
-  /micromark@3.0.10:
-    resolution: {integrity: sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==}
-    dependencies:
-      '@types/debug': 4.1.7
-      debug: 4.3.4(supports-color@8.1.1)
-      decode-named-character-reference: 1.0.2
-      micromark-core-commonmark: 1.0.6
-      micromark-factory-space: 1.0.0
-      micromark-util-character: 1.1.0
-      micromark-util-chunked: 1.0.0
-      micromark-util-combine-extensions: 1.0.0
-      micromark-util-decode-numeric-character-reference: 1.0.0
-      micromark-util-encode: 1.0.1
-      micromark-util-normalize-identifier: 1.0.0
-      micromark-util-resolve-all: 1.0.0
-      micromark-util-sanitize-uri: 1.0.0
-      micromark-util-subtokenize: 1.0.2
-      micromark-util-symbol: 1.0.1
-      micromark-util-types: 1.0.2
-      uvu: 0.5.6
-    transitivePeerDependencies:
-      - supports-color
+  /methods@1.1.2:
+    resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+    engines: {node: '>= 0.6'}
     dev: false
 
   /micromatch@4.0.8:
@@ -10870,18 +8980,18 @@ packages:
     resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
     dev: false
 
+  /minimatch@10.0.1:
+    resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
+    engines: {node: 20 || >=22}
+    dependencies:
+      brace-expansion: 2.0.1
+    dev: true
+
   /minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
     dependencies:
       brace-expansion: 1.1.11
 
-  /minimatch@5.1.0:
-    resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
-    engines: {node: '>=10'}
-    dependencies:
-      brace-expansion: 2.0.1
-    dev: false
-
   /minimist-options@4.1.0:
     resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
     engines: {node: '>= 6'}
@@ -10893,6 +9003,12 @@ packages:
 
   /minimist@1.2.8:
     resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+    dev: false
+
+  /minipass@7.1.2:
+    resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    dev: true
 
   /mkdirp@0.5.6:
     resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
@@ -10930,11 +9046,6 @@ packages:
   /monaco-editor@0.48.0:
     resolution: {integrity: sha512-goSDElNqFfw7iDHMg8WDATkfcyeLTNpBHQpO8incK6p5qZt5G/1j41X0xdGzpIkGojGXM+QiRQyLjnfDVvrpwA==}
 
-  /mri@1.2.0:
-    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
-    engines: {node: '>=4'}
-    dev: false
-
   /ms@2.0.0:
     resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
     dev: false
@@ -10954,13 +9065,6 @@ packages:
       thunky: 1.1.0
     dev: false
 
-  /multipipe@1.0.2:
-    resolution: {integrity: sha512-6uiC9OvY71vzSGX8lZvSqscE7ft9nPupJ8fMjrCNRAUy2LREUW42UL+V/NTrogr6rFgRydUrCX4ZitfpSNkSCQ==}
-    dependencies:
-      duplexer2: 0.1.4
-      object-assign: 4.1.1
-    dev: false
-
   /nanoclone@0.2.1:
     resolution: {integrity: sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==}
     dev: false
@@ -11027,6 +9131,7 @@ packages:
   /normalize-range@0.1.2:
     resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
     engines: {node: '>=0.10.0'}
+    dev: false
 
   /normalize-selector@0.2.0:
     resolution: {integrity: sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==}
@@ -11050,10 +9155,6 @@ packages:
       boolbase: 1.0.0
     dev: false
 
-  /num2fraction@1.2.2:
-    resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==}
-    dev: true
-
   /nwsapi@2.2.2:
     resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==}
     dev: false
@@ -11061,6 +9162,7 @@ packages:
   /object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
+    dev: false
 
   /object-hash@3.0.0:
     resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
@@ -11069,19 +9171,17 @@ packages:
 
   /object-inspect@1.12.2:
     resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
+    dev: false
 
   /object-inspect@1.13.2:
     resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
     engines: {node: '>= 0.4'}
     dev: false
 
-  /object-keys@0.4.0:
-    resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==}
-    dev: false
-
   /object-keys@1.1.1:
     resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
     engines: {node: '>= 0.4'}
+    dev: false
 
   /object.assign@4.1.4:
     resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
@@ -11091,6 +9191,7 @@ packages:
       define-properties: 1.1.4
       has-symbols: 1.0.3
       object-keys: 1.1.1
+    dev: false
 
   /object.entries@1.1.5:
     resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==}
@@ -11099,6 +9200,7 @@ packages:
       call-bind: 1.0.2
       define-properties: 1.1.4
       es-abstract: 1.20.2
+    dev: false
 
   /object.fromentries@2.0.5:
     resolution: {integrity: sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==}
@@ -11107,6 +9209,7 @@ packages:
       call-bind: 1.0.2
       define-properties: 1.1.4
       es-abstract: 1.20.2
+    dev: false
 
   /object.getownpropertydescriptors@2.1.4:
     resolution: {integrity: sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==}
@@ -11123,6 +9226,7 @@ packages:
     dependencies:
       define-properties: 1.1.4
       es-abstract: 1.20.2
+    dev: false
 
   /object.values@1.1.5:
     resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==}
@@ -11131,6 +9235,7 @@ packages:
       call-bind: 1.0.2
       define-properties: 1.1.4
       es-abstract: 1.20.2
+    dev: false
 
   /obuf@1.1.2:
     resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
@@ -11260,6 +9365,10 @@ packages:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
     engines: {node: '>=6'}
 
+  /package-json-from-dist@1.0.1:
+    resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+    dev: true
+
   /param-case@3.0.4:
     resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
     dependencies:
@@ -11301,6 +9410,7 @@ packages:
       is-alphanumerical: 1.0.4
       is-decimal: 1.0.4
       is-hexadecimal: 1.0.4
+    dev: false
 
   /parse-json@5.2.0:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
@@ -11327,10 +9437,6 @@ packages:
       tslib: 2.4.0
     dev: false
 
-  /path-data-parser@0.1.0:
-    resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
-    dev: false
-
   /path-exists@3.0.0:
     resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
     engines: {node: '>=4'}
@@ -11356,6 +9462,14 @@ packages:
   /path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
+  /path-scurry@2.0.0:
+    resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
+    engines: {node: 20 || >=22}
+    dependencies:
+      lru-cache: 11.1.0
+      minipass: 7.1.2
+    dev: true
+
   /path-to-regexp@0.1.10:
     resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
     dev: false
@@ -11396,10 +9510,6 @@ packages:
     resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
     dev: false
 
-  /picocolors@0.2.1:
-    resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==}
-    dev: true
-
   /picocolors@1.0.0:
     resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
 
@@ -11438,17 +9548,6 @@ packages:
       find-up: 3.0.0
     dev: false
 
-  /points-on-curve@0.2.0:
-    resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
-    dev: false
-
-  /points-on-path@0.2.1:
-    resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
-    dependencies:
-      path-data-parser: 0.1.0
-      points-on-curve: 0.2.0
-    dev: false
-
   /popper.js@1.16.1-lts:
     resolution: {integrity: sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==}
     dev: false
@@ -11689,17 +9788,6 @@ packages:
       postcss: 8.4.38
     dev: false
 
-  /postcss-html@0.36.0(postcss-syntax@0.36.2)(postcss@8.4.38):
-    resolution: {integrity: sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==}
-    peerDependencies:
-      postcss: '>=8.4.38'
-      postcss-syntax: '>=0.36.0'
-    dependencies:
-      htmlparser2: 3.10.1
-      postcss: 8.4.38
-      postcss-syntax: 0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@4.0.9)(postcss@8.4.38)
-    dev: true
-
   /postcss-image-set-function@4.0.7(postcss@8.4.38):
     resolution: {integrity: sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==}
     engines: {node: ^12 || ^14 || >=16}
@@ -11751,13 +9839,6 @@ packages:
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-less@3.1.4:
-    resolution: {integrity: sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==}
-    engines: {node: '>=6.14.4'}
-    dependencies:
-      postcss: 8.4.38
-    dev: true
-
   /postcss-load-config@3.1.4(postcss@8.4.38):
     resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
     engines: {node: '>= 10'}
@@ -12191,13 +10272,6 @@ packages:
     resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==}
     dev: true
 
-  /postcss-safe-parser@4.0.2:
-    resolution: {integrity: sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==}
-    engines: {node: '>=6.0.0'}
-    dependencies:
-      postcss: 8.4.38
-    dev: true
-
   /postcss-safe-parser@6.0.0(postcss@8.4.38):
     resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==}
     engines: {node: '>=12.0'}
@@ -12207,13 +10281,6 @@ packages:
       postcss: 8.4.38
     dev: true
 
-  /postcss-sass@0.4.4:
-    resolution: {integrity: sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==}
-    dependencies:
-      gonzales-pe: 4.3.0
-      postcss: 8.4.38
-    dev: true
-
   /postcss-scss@4.0.9(postcss@8.4.38):
     resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==}
     engines: {node: '>=12.0'}
@@ -12240,14 +10307,6 @@ packages:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
-  /postcss-sorting@5.0.1:
-    resolution: {integrity: sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==}
-    engines: {node: '>=8.7.0'}
-    dependencies:
-      lodash: 4.17.21
-      postcss: 8.4.38
-    dev: true
-
   /postcss-svgo@5.0.4(postcss@8.4.38):
     resolution: {integrity: sha512-yDKHvULbnZtIrRqhZoA+rxreWpee28JSRH/gy9727u0UCgtpv1M/9WEWY3xySlFa0zQJcqf6oCBJPR5NwkmYpg==}
     engines: {node: ^10 || ^12 || >=14.0}
@@ -12259,33 +10318,6 @@ packages:
       svgo: 2.8.0
     dev: false
 
-  /postcss-syntax@0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@4.0.9)(postcss@8.4.38):
-    resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}
-    peerDependencies:
-      postcss: '>=8.4.38'
-      postcss-html: '*'
-      postcss-jsx: '*'
-      postcss-less: '*'
-      postcss-markdown: '*'
-      postcss-scss: '*'
-    peerDependenciesMeta:
-      postcss-html:
-        optional: true
-      postcss-jsx:
-        optional: true
-      postcss-less:
-        optional: true
-      postcss-markdown:
-        optional: true
-      postcss-scss:
-        optional: true
-    dependencies:
-      postcss: 8.4.38
-      postcss-html: 0.36.0(postcss-syntax@0.36.2)(postcss@8.4.38)
-      postcss-less: 3.1.4
-      postcss-scss: 4.0.9(postcss@8.4.38)
-    dev: true
-
   /postcss-unique-selectors@5.0.4(postcss@8.4.38):
     resolution: {integrity: sha512-5ampwoSDJCxDPoANBIlMgoBcYUHnhaiuLYJR5pj1DLnYQvMRVyFuTA5C3Bvt+aHtiqWpJkD/lXT50Vo1D0ZsAQ==}
     engines: {node: ^10 || ^12 || >=14.0}
@@ -12393,22 +10425,13 @@ packages:
       sisteransi: 1.0.5
     dev: false
 
-  /prop-types-extra@1.1.1(react@17.0.2):
-    resolution: {integrity: sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==}
-    peerDependencies:
-      react: '>=0.14.0'
-    dependencies:
-      react: 17.0.2
-      react-is: 16.13.1
-      warning: 4.0.3
-    dev: false
-
   /prop-types@15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
     dependencies:
       loose-envify: 1.4.0
       object-assign: 4.1.1
       react-is: 16.13.1
+    dev: false
 
   /property-expr@2.0.4:
     resolution: {integrity: sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==}
@@ -12420,10 +10443,6 @@ packages:
       xtend: 4.0.2
     dev: false
 
-  /property-information@6.1.1:
-    resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==}
-    dev: false
-
   /proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -12474,13 +10493,6 @@ packages:
       side-channel: 1.0.4
     dev: false
 
-  /qs@6.11.0:
-    resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
-    engines: {node: '>=0.6'}
-    dependencies:
-      side-channel: 1.0.4
-    dev: false
-
   /qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -12551,28 +10563,6 @@ packages:
       whatwg-fetch: 3.6.2
     dev: false
 
-  /react-bootstrap@0.32.4(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==}
-    peerDependencies:
-      react: ^0.14.9 || >=15.3.0
-      react-dom: ^0.14.9 || >=15.3.0
-    dependencies:
-      '@babel/runtime-corejs2': 7.15.4
-      classnames: 2.3.1
-      dom-helpers: 3.4.0
-      invariant: 2.2.4
-      keycode: 2.2.0
-      prop-types: 15.8.1
-      prop-types-extra: 1.1.1(react@17.0.2)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-overlays: 0.8.3(react-dom@17.0.2)(react@17.0.2)
-      react-prop-types: 0.4.0(react@17.0.2)
-      react-transition-group: 2.9.0(react-dom@17.0.2)(react@17.0.2)
-      uncontrollable: 5.1.0(react@17.0.2)
-      warning: 3.0.0
-    dev: false
-
   /react-countdown-hook@1.1.0(react@17.0.2):
     resolution: {integrity: sha512-qK9yIbJ+g0Gk/tAkXT0fXpfW2DiN/xyWePGtWtA3Bl19ZM7zsCOttXGMg+DTa0cNx4Yn/1WRwNuXng+QDs5ygw==}
     peerDependencies:
@@ -12623,14 +10613,6 @@ packages:
       - vue-template-compiler
     dev: false
 
-  /react-div-100vh@0.6.0(react@17.0.2):
-    resolution: {integrity: sha512-ErV0VTNXUd8jZqofC0ExZr5u+XDD2kN2te4SbwtqsyTm0UOjVYu53kP+FalGQrTe+DoMG8VYR2dITcAFu7c/5w==}
-    peerDependencies:
-      react: '>=16.8.0'
-    dependencies:
-      react: 17.0.2
-    dev: false
-
   /react-dom@17.0.2(react@17.0.2):
     resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==}
     peerDependencies:
@@ -12652,6 +10634,7 @@ packages:
 
   /react-is@16.13.1:
     resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+    dev: false
 
   /react-is@17.0.2:
     resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
@@ -12661,62 +10644,6 @@ packages:
     resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
     dev: false
 
-  /react-lifecycles-compat@3.0.4:
-    resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
-    dev: false
-
-  /react-markdown@8.0.3(@types/react@17.0.39)(react@17.0.2):
-    resolution: {integrity: sha512-We36SfqaKoVNpN1QqsZwWSv/OZt5J15LNgTLWynwAN5b265hrQrsjMtlRNwUvS+YyR3yDM8HpTNc4pK9H/Gc0A==}
-    peerDependencies:
-      '@types/react': '>=16'
-      react: '>=16'
-    dependencies:
-      '@types/hast': 2.3.4
-      '@types/prop-types': 15.7.5
-      '@types/react': 17.0.39
-      '@types/unist': 2.0.6
-      comma-separated-tokens: 2.0.2
-      hast-util-whitespace: 2.0.0
-      prop-types: 15.8.1
-      property-information: 6.1.1
-      react: 17.0.2
-      react-is: 18.2.0
-      remark-parse: 10.0.1
-      remark-rehype: 10.1.0
-      space-separated-tokens: 2.0.1
-      style-to-object: 0.3.0
-      unified: 10.1.1
-      unist-util-visit: 4.1.0
-      vfile: 5.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /react-overlays@0.8.3(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==}
-    peerDependencies:
-      react: ^0.14.9 || >=15.3.0
-      react-dom: ^0.14.9 || >=15.3.0
-    dependencies:
-      classnames: 2.3.1
-      dom-helpers: 3.4.0
-      prop-types: 15.8.1
-      prop-types-extra: 1.1.1(react@17.0.2)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-transition-group: 2.9.0(react-dom@17.0.2)(react@17.0.2)
-      warning: 3.0.0
-    dev: false
-
-  /react-prop-types@0.4.0(react@17.0.2):
-    resolution: {integrity: sha512-IyjsJhDX9JkoOV9wlmLaS7z+oxYoIWhfzDcFy7inwoAKTu+VcVNrVpPmLeioJ94y6GeDRsnwarG1py5qofFQMg==}
-    peerDependencies:
-      react: '>=0.14.0'
-    dependencies:
-      react: 17.0.2
-      warning: 3.0.0
-    dev: false
-
   /react-refresh@0.11.0:
     resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==}
     engines: {node: '>=0.10.0'}
@@ -12737,17 +10664,6 @@ packages:
       tiny-warning: 1.0.3
     dev: false
 
-  /react-router-hash-link@1.2.2(react-router-dom@5.3.0)(react@17.0.2):
-    resolution: {integrity: sha512-LBthLVHdqPeKDVt3+cFRhy15Z7veikOvdKRZRfyBR2vjqIE7rxn+tKLjb6DOmLm6JpoQVemVDnxQ35RVnEHdQA==}
-    peerDependencies:
-      react: '>=15'
-      react-router-dom: '>=4'
-    dependencies:
-      prop-types: 15.8.1
-      react: 17.0.2
-      react-router-dom: 5.3.0(react@17.0.2)
-    dev: false
-
   /react-router@5.2.1(react@17.0.2):
     resolution: {integrity: sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==}
     peerDependencies:
@@ -12874,20 +10790,6 @@ packages:
       refractor: 3.6.0
     dev: false
 
-  /react-transition-group@2.9.0(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==}
-    peerDependencies:
-      react: '>=15.0.0'
-      react-dom: '>=15.0.0'
-    dependencies:
-      dom-helpers: 3.4.0
-      loose-envify: 1.4.0
-      prop-types: 15.8.1
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-lifecycles-compat: 3.0.4
-    dev: false
-
   /react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2):
     resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
     peerDependencies:
@@ -12902,16 +10804,6 @@ packages:
       react-dom: 17.0.2(react@17.0.2)
     dev: false
 
-  /react-use-websocket@3.0.0(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-BInlbhXYrODBPKIplDAmI0J1VPM+1KhCLN09o+dzgQ8qMyrYs4t5kEYmCrTqyRuMTmpahylHFZWQXpfYyDkqOw==}
-    peerDependencies:
-      react: '>= 16.8.0'
-      react-dom: '>= 16.8.0'
-    dependencies:
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-    dev: false
-
   /react@17.0.2:
     resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
     engines: {node: '>=0.10.0'}
@@ -12945,15 +10837,6 @@ packages:
       type-fest: 0.6.0
     dev: true
 
-  /readable-stream@1.0.34:
-    resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==}
-    dependencies:
-      core-util-is: 1.0.3
-      inherits: 2.0.4
-      isarray: 0.0.1
-      string_decoder: 0.10.31
-    dev: false
-
   /readable-stream@2.3.8:
     resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
     dependencies:
@@ -12973,6 +10856,7 @@ packages:
       inherits: 2.0.4
       string_decoder: 1.3.0
       util-deprecate: 1.0.2
+    dev: false
 
   /readdirp@3.6.0:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
@@ -12984,7 +10868,7 @@ packages:
     resolution: {integrity: sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==}
     engines: {node: '>=0.10.0'}
     dependencies:
-      minimatch: 5.1.0
+      minimatch: 3.1.2
     dev: false
 
   /redent@3.0.0:
@@ -12995,22 +10879,6 @@ packages:
       strip-indent: 3.0.0
     dev: true
 
-  /reflux-core@1.0.0:
-    resolution: {integrity: sha512-K9NIxsvKJ94KTkLhJgtMHdxoFONyXDc/58Q2yfiPZ4Bta+qjDs2i2xRGS0rmHua4ehB9Aa9oMLASXeDZtIKWVQ==}
-    dependencies:
-      eventemitter3: 1.2.0
-    dev: false
-
-  /reflux@6.4.1(react@17.0.2):
-    resolution: {integrity: sha512-WH162Od4badj8eC/oXWbYF8vxWBFGu07ksZMmUUUD/psW5ijLHiA+7Vkg8xvkEvX9TKHp26hW7SwJ/9GT2HHmw==}
-    peerDependencies:
-      react: ^15.0.2
-    dependencies:
-      eventemitter3: 1.2.0
-      react: 17.0.2
-      reflux-core: 1.0.0
-    dev: false
-
   /refractor@3.6.0:
     resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==}
     dependencies:
@@ -13051,6 +10919,7 @@ packages:
       call-bind: 1.0.2
       define-properties: 1.1.4
       functions-have-names: 1.2.3
+    dev: false
 
   /regexpp@3.2.0:
     resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
@@ -13076,75 +10945,13 @@ packages:
     resolution: {integrity: sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==}
     hasBin: true
     dependencies:
-      jsesc: 0.5.0
-    dev: false
-
-  /rehype-raw@6.1.1:
-    resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==}
-    dependencies:
-      '@types/hast': 2.3.4
-      hast-util-raw: 7.2.1
-      unified: 10.1.1
-    dev: false
-
-  /relateurl@0.2.7:
-    resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
-    engines: {node: '>= 0.10'}
-    dev: false
-
-  /remark-gfm@3.0.1:
-    resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      mdast-util-gfm: 2.0.1
-      micromark-extension-gfm: 2.0.1
-      unified: 10.1.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /remark-parse@10.0.1:
-    resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==}
-    dependencies:
-      '@types/mdast': 3.0.10
-      mdast-util-from-markdown: 1.2.0
-      unified: 10.1.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /remark-parse@9.0.0:
-    resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==}
-    dependencies:
-      mdast-util-from-markdown: 0.8.5
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /remark-rehype@10.1.0:
-    resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==}
-    dependencies:
-      '@types/hast': 2.3.4
-      '@types/mdast': 3.0.10
-      mdast-util-to-hast: 12.1.1
-      unified: 10.1.1
-    dev: false
-
-  /remark-stringify@9.0.1:
-    resolution: {integrity: sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==}
-    dependencies:
-      mdast-util-to-markdown: 0.6.5
-    dev: true
-
-  /remark@13.0.0:
-    resolution: {integrity: sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==}
-    dependencies:
-      remark-parse: 9.0.0
-      remark-stringify: 9.0.1
-      unified: 9.2.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
+      jsesc: 0.5.0
+    dev: false
+
+  /relateurl@0.2.7:
+    resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
+    engines: {node: '>= 0.10'}
+    dev: false
 
   /renderkid@3.0.0:
     resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}
@@ -13237,6 +11044,7 @@ packages:
       is-core-module: 2.10.0
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
+    dev: false
 
   /restore-cursor@3.1.0:
     resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
@@ -13272,10 +11080,6 @@ packages:
       inherits: 2.0.4
     dev: false
 
-  /robust-predicates@3.0.2:
-    resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
-    dev: false
-
   /rollup-plugin-terser@7.0.2(rollup@2.79.2):
     resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
     deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
@@ -13297,42 +11101,23 @@ packages:
       fsevents: 2.3.3
     dev: false
 
-  /roughjs@4.6.6:
-    resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
-    dependencies:
-      hachure-fill: 0.5.2
-      path-data-parser: 0.1.0
-      points-on-curve: 0.2.0
-      points-on-path: 0.2.1
-    dev: false
-
   /run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
     dependencies:
       queue-microtask: 1.2.3
 
-  /rw@1.3.3:
-    resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
-    dev: false
-
   /rxjs@7.8.1:
     resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
     dependencies:
       tslib: 2.4.0
     dev: false
 
-  /sade@1.8.1:
-    resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
-    engines: {node: '>=6'}
-    dependencies:
-      mri: 1.2.0
-    dev: false
-
   /safe-buffer@5.1.2:
     resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
 
   /safe-buffer@5.2.1:
     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+    dev: false
 
   /safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -13553,6 +11338,7 @@ packages:
       call-bind: 1.0.2
       get-intrinsic: 1.1.2
       object-inspect: 1.12.2
+    dev: false
 
   /side-channel@1.0.6:
     resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
@@ -13567,6 +11353,11 @@ packages:
   /signal-exit@3.0.7:
     resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
 
+  /signal-exit@4.1.0:
+    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+    engines: {node: '>=14'}
+    dev: true
+
   /sisteransi@1.0.5:
     resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
     dev: false
@@ -13663,10 +11454,6 @@ packages:
     resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
     dev: false
 
-  /space-separated-tokens@2.0.1:
-    resolution: {integrity: sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==}
-    dev: false
-
   /spdx-correct@3.1.1:
     resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
     dependencies:
@@ -13805,6 +11592,15 @@ packages:
       is-fullwidth-code-point: 3.0.0
       strip-ansi: 6.0.1
 
+  /string-width@5.1.2:
+    resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+    engines: {node: '>=12'}
+    dependencies:
+      eastasianwidth: 0.2.0
+      emoji-regex: 9.2.2
+      strip-ansi: 7.0.1
+    dev: true
+
   /string.prototype.matchall@4.0.7:
     resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==}
     dependencies:
@@ -13816,6 +11612,7 @@ packages:
       internal-slot: 1.0.3
       regexp.prototype.flags: 1.4.3
       side-channel: 1.0.4
+    dev: false
 
   /string.prototype.trimend@1.0.5:
     resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==}
@@ -13823,6 +11620,7 @@ packages:
       call-bind: 1.0.2
       define-properties: 1.1.4
       es-abstract: 1.20.2
+    dev: false
 
   /string.prototype.trimstart@1.0.5:
     resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==}
@@ -13830,9 +11628,6 @@ packages:
       call-bind: 1.0.2
       define-properties: 1.1.4
       es-abstract: 1.20.2
-
-  /string_decoder@0.10.31:
-    resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
     dev: false
 
   /string_decoder@1.1.1:
@@ -13845,6 +11640,7 @@ packages:
     resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
     dependencies:
       safe-buffer: 5.2.1
+    dev: false
 
   /stringify-object@3.3.0:
     resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
@@ -13866,7 +11662,6 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       ansi-regex: 6.0.1
-    dev: false
 
   /strip-bom@3.0.0:
     resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
@@ -13912,12 +11707,6 @@ packages:
     resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
     dev: true
 
-  /style-to-object@0.3.0:
-    resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==}
-    dependencies:
-      inline-style-parser: 0.1.1
-    dev: false
-
   /stylehacks@5.0.3(postcss@8.4.38):
     resolution: {integrity: sha512-ENcUdpf4yO0E1rubu8rkxI+JGQk4CgjchynZ4bDBJDfqdy+uhTRSWb8/F3Jtu+Bw5MW45Po3/aQGeIyyxgQtxg==}
     engines: {node: ^10 || ^12 || >=14.0}
@@ -13950,17 +11739,6 @@ packages:
       stylelint: 14.0.1
     dev: true
 
-  /stylelint-config-sass-guidelines@8.0.0(stylelint@13.13.1):
-    resolution: {integrity: sha512-v21iDWtzpfhuKJlYKpoE1vjp+GT8Cr6ZBWwMx/jf+eCEblZgAIDVVjgAELoDLhVj17DcEFwlIKJBMfrdAmXg0Q==}
-    engines: {node: '>=10.0.0'}
-    peerDependencies:
-      stylelint: ^13.7.0
-    dependencies:
-      stylelint: 13.13.1
-      stylelint-order: 4.1.0(stylelint@13.13.1)
-      stylelint-scss: 3.21.0(stylelint@13.13.1)
-    dev: true
-
   /stylelint-config-standard-scss@2.0.1(postcss@8.4.38)(stylelint@14.0.1):
     resolution: {integrity: sha512-TW5NLquUSS0mg2N31zzaSbYRbV/CMifSVLdpgo6VdGvjysgYqJOcKM/5bmXucTOsdfqomcPXetFZ3adC7nD+cg==}
     peerDependencies:
@@ -13982,17 +11760,6 @@ packages:
       stylelint-config-recommended: 6.0.0(stylelint@14.0.1)
     dev: true
 
-  /stylelint-order@4.1.0(stylelint@13.13.1):
-    resolution: {integrity: sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw==}
-    peerDependencies:
-      stylelint: ^10.0.1 || ^11.0.0 || ^12.0.0 || ^13.0.0
-    dependencies:
-      lodash: 4.17.21
-      postcss: 8.4.38
-      postcss-sorting: 5.0.1
-      stylelint: 13.13.1
-    dev: true
-
   /stylelint-prettier@2.0.0(prettier@2.4.1)(stylelint@14.0.1):
     resolution: {integrity: sha512-jvT3G+9lopkeB0ARmDPszyfaOnvnIF+30QCjZxyt7E6fynI1T9mOKgYDNb9bXX17M7PXMZaX3j/26wqakjp1tw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -14005,20 +11772,6 @@ packages:
       stylelint: 14.0.1
     dev: true
 
-  /stylelint-scss@3.21.0(stylelint@13.13.1):
-    resolution: {integrity: sha512-CMI2wSHL+XVlNExpauy/+DbUcB/oUZLARDtMIXkpV/5yd8nthzylYd1cdHeDMJVBXeYHldsnebUX6MoV5zPW4A==}
-    engines: {node: '>=8'}
-    peerDependencies:
-      stylelint: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0
-    dependencies:
-      lodash: 4.17.21
-      postcss-media-query-parser: 0.2.3
-      postcss-resolve-nested-selector: 0.1.1
-      postcss-selector-parser: 6.0.10
-      postcss-value-parser: 4.2.0
-      stylelint: 13.13.1
-    dev: true
-
   /stylelint-scss@4.0.0(stylelint@14.0.1):
     resolution: {integrity: sha512-lIRhPqtI6I065EJ6aI4mWKsmQt8Krnu6aF9XSL9s8Nd2f/cDKImST0T9TfjnUul3ReKYWozkG9dlpNTZH2FB9w==}
     peerDependencies:
@@ -14032,65 +11785,6 @@ packages:
       stylelint: 14.0.1
     dev: true
 
-  /stylelint@13.13.1:
-    resolution: {integrity: sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==}
-    engines: {node: '>=10.13.0'}
-    hasBin: true
-    dependencies:
-      '@stylelint/postcss-css-in-js': 0.37.3(postcss-syntax@0.36.2)(postcss@8.4.38)
-      '@stylelint/postcss-markdown': 0.36.2(postcss-syntax@0.36.2)(postcss@8.4.38)
-      autoprefixer: 9.8.8
-      balanced-match: 2.0.0
-      chalk: 4.1.2
-      cosmiconfig: 7.0.1
-      debug: 4.3.4(supports-color@8.1.1)
-      execall: 2.0.0
-      fast-glob: 3.2.12
-      fastest-levenshtein: 1.0.16
-      file-entry-cache: 6.0.1
-      get-stdin: 8.0.0
-      global-modules: 2.0.0
-      globby: 11.1.0
-      globjoin: 0.1.4
-      html-tags: 3.2.0
-      ignore: 5.3.1
-      import-lazy: 4.0.0
-      imurmurhash: 0.1.4
-      known-css-properties: 0.21.0
-      lodash: 4.17.21
-      log-symbols: 4.1.0
-      mathml-tag-names: 2.1.3
-      meow: 9.0.0
-      micromatch: 4.0.8
-      normalize-selector: 0.2.0
-      postcss: 8.4.38
-      postcss-html: 0.36.0(postcss-syntax@0.36.2)(postcss@8.4.38)
-      postcss-less: 3.1.4
-      postcss-media-query-parser: 0.2.3
-      postcss-resolve-nested-selector: 0.1.1
-      postcss-safe-parser: 4.0.2
-      postcss-sass: 0.4.4
-      postcss-scss: 4.0.9(postcss@8.4.38)
-      postcss-selector-parser: 6.0.10
-      postcss-syntax: 0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@4.0.9)(postcss@8.4.38)
-      postcss-value-parser: 4.2.0
-      resolve-from: 5.0.0
-      slash: 3.0.0
-      specificity: 0.4.1
-      string-width: 4.2.3
-      strip-ansi: 6.0.1
-      style-search: 0.1.0
-      sugarss: 2.0.0
-      svg-tags: 1.0.0
-      table: 6.8.1
-      v8-compile-cache: 2.3.0
-      write-file-atomic: 3.0.3
-    transitivePeerDependencies:
-      - postcss-jsx
-      - postcss-markdown
-      - supports-color
-    dev: true
-
   /stylelint@14.0.1:
     resolution: {integrity: sha512-ZcAkmFLVCultmwkQUjxKzxW/o5+CzNmDk6TPJj/d4Y7ipTGGrewIWmNm+InjdSr04PR5/yynsAJeYJY/wisdMg==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -14142,16 +11836,6 @@ packages:
     resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==}
     dev: false
 
-  /stylis@4.3.4:
-    resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==}
-    dev: false
-
-  /sugarss@2.0.0:
-    resolution: {integrity: sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==}
-    dependencies:
-      postcss: 8.4.38
-    dev: true
-
   /supports-color@5.5.0:
     resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
     engines: {node: '>=4'}
@@ -14347,7 +12031,7 @@ packages:
     dependencies:
       '@istanbuljs/schema': 0.1.3
       glob: 7.2.3
-      minimatch: 5.1.0
+      minimatch: 3.1.2
     dev: false
 
   /text-table@0.2.0:
@@ -14361,13 +12045,6 @@ packages:
     resolution: {integrity: sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==}
     dev: false
 
-  /through2@0.4.2:
-    resolution: {integrity: sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==}
-    dependencies:
-      readable-stream: 1.0.34
-      xtend: 2.1.2
-    dev: false
-
   /through@2.3.8:
     resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
     dev: false
@@ -14450,23 +12127,10 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /trough@1.0.5:
-    resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==}
-    dev: true
-
-  /trough@2.0.2:
-    resolution: {integrity: sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w==}
-    dev: false
-
   /tryer@1.0.1:
     resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==}
     dev: false
 
-  /ts-dedent@2.2.0:
-    resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
-    engines: {node: '>=6.10'}
-    dev: false
-
   /ts-node@10.9.1(@types/node@12.20.33)(typescript@4.8.3):
     resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
     hasBin: true
@@ -14498,10 +12162,6 @@ packages:
       yn: 3.1.1
     dev: false
 
-  /ts.cryptojs256@1.0.1:
-    resolution: {integrity: sha512-9XtEgRVOZBCdpPcCEhfvv9I2AVXdvfI81I/KpFM0wEfbq5JVHlXH7bfIjGQmYrIHGiBEHKsIaStQ87k926J7dA==}
-    dev: false
-
   /tsconfig-paths@3.14.1:
     resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
     dependencies:
@@ -14597,10 +12257,6 @@ packages:
     dependencies:
       is-typedarray: 1.0.0
 
-  /typeface-roboto@0.0.75:
-    resolution: {integrity: sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==}
-    dev: false
-
   /typescript@4.8.3:
     resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==}
     engines: {node: '>=4.2.0'}
@@ -14613,14 +12269,6 @@ packages:
       has-bigints: 1.0.2
       has-symbols: 1.0.3
       which-boxed-primitive: 1.0.2
-
-  /uncontrollable@5.1.0(react@17.0.2):
-    resolution: {integrity: sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==}
-    peerDependencies:
-      react: '>=15.0.0'
-    dependencies:
-      invariant: 2.2.4
-      react: 17.0.2
     dev: false
 
   /unicode-canonical-property-names-ecmascript@2.0.0:
@@ -14646,106 +12294,12 @@ packages:
     engines: {node: '>=4'}
     dev: false
 
-  /unified@10.1.1:
-    resolution: {integrity: sha512-v4ky1+6BN9X3pQrOdkFIPWAaeDsHPE1svRDxq7YpTc2plkIqFMwukfqM+l0ewpP9EfwARlt9pPFAeWYhHm8X9w==}
-    dependencies:
-      '@types/unist': 2.0.6
-      bail: 2.0.2
-      extend: 3.0.2
-      is-buffer: 2.0.5
-      is-plain-obj: 4.0.0
-      trough: 2.0.2
-      vfile: 5.3.0
-    dev: false
-
-  /unified@9.2.2:
-    resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-      bail: 1.0.5
-      extend: 3.0.2
-      is-buffer: 2.0.5
-      is-plain-obj: 2.1.0
-      trough: 1.0.5
-      vfile: 4.2.1
-    dev: true
-
   /unique-string@2.0.0:
     resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
     engines: {node: '>=8'}
     dependencies:
       crypto-random-string: 2.0.0
 
-  /unist-builder@3.0.0:
-    resolution: {integrity: sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-    dev: false
-
-  /unist-util-find-all-after@3.0.2:
-    resolution: {integrity: sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==}
-    dependencies:
-      unist-util-is: 4.1.0
-    dev: true
-
-  /unist-util-generated@2.0.0:
-    resolution: {integrity: sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==}
-    dev: false
-
-  /unist-util-is@4.1.0:
-    resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==}
-    dev: true
-
-  /unist-util-is@5.1.1:
-    resolution: {integrity: sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==}
-    dev: false
-
-  /unist-util-position@4.0.1:
-    resolution: {integrity: sha512-mgy/zI9fQ2HlbOtTdr2w9lhVaiFUHWQnZrFF2EUoVOqtAUdzqMtNiD99qA5a1IcjWVR8O6aVYE9u7Z2z1v0SQA==}
-    dev: false
-
-  /unist-util-stringify-position@2.0.3:
-    resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==}
-    dependencies:
-      '@types/unist': 2.0.6
-    dev: true
-
-  /unist-util-stringify-position@3.0.0:
-    resolution: {integrity: sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==}
-    dependencies:
-      '@types/unist': 2.0.6
-    dev: false
-
-  /unist-util-visit-parents@4.1.1:
-    resolution: {integrity: sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==}
-    dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 5.1.1
-    dev: false
-
-  /unist-util-visit-parents@5.1.0:
-    resolution: {integrity: sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==}
-    dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 5.1.1
-    dev: false
-
-  /unist-util-visit@3.1.0:
-    resolution: {integrity: sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==}
-    dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 5.1.1
-      unist-util-visit-parents: 4.1.1
-    dev: false
-
-  /unist-util-visit@4.1.0:
-    resolution: {integrity: sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 5.1.1
-      unist-util-visit-parents: 5.1.0
-    dev: false
-
   /universalify@0.2.0:
     resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
     engines: {node: '>= 4.0.0'}
@@ -14796,15 +12350,6 @@ packages:
       requires-port: 1.0.0
     dev: false
 
-  /use-interval@1.4.0(react@17.0.2):
-    resolution: {integrity: sha512-1betIJun2rXKLxa30AFOBZCeZhsBJoJ/3+gkCeYbJ63lAR//EnAb1NjNeFqzgqeM7zQfR76rrCUaA8DvfgoOpA==}
-    engines: {node: '>=8', npm: '>=5'}
-    peerDependencies:
-      react: '>=16.8.0 || ^17'
-    dependencies:
-      react: 17.0.2
-    dev: false
-
   /use-timer@2.0.1(react-dom@17.0.2)(react@17.0.2):
     resolution: {integrity: sha512-ESFkDYZluJZ+G/CaoOBFxg0TjuG7kgNFNhIFZhqv14ho2WIZwuxkRnEURhyLm9I4vNz+ea9ZowNaOkExDAM+6Q==}
     peerDependencies:
@@ -14842,33 +12387,11 @@ packages:
     engines: {node: '>= 0.4.0'}
     dev: false
 
-  /uuid@3.4.0:
-    resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
-    deprecated: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
-    hasBin: true
-    dev: false
-
   /uuid@8.3.2:
     resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
     hasBin: true
     dev: false
 
-  /uuid@9.0.1:
-    resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
-    hasBin: true
-    dev: false
-
-  /uvu@0.5.6:
-    resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
-    engines: {node: '>=8'}
-    hasBin: true
-    dependencies:
-      dequal: 2.0.2
-      diff: 5.1.0
-      kleur: 4.1.5
-      sade: 1.8.1
-    dev: false
-
   /v8-compile-cache-lib@3.0.1:
     resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
     dev: false
@@ -14911,80 +12434,10 @@ packages:
       extsprintf: 1.3.0
     dev: false
 
-  /vfile-location@4.0.1:
-    resolution: {integrity: sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==}
-    dependencies:
-      '@types/unist': 2.0.6
-      vfile: 5.3.0
-    dev: false
-
-  /vfile-message@2.0.4:
-    resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-      unist-util-stringify-position: 2.0.3
-    dev: true
-
-  /vfile-message@3.1.0:
-    resolution: {integrity: sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g==}
-    dependencies:
-      '@types/unist': 2.0.6
-      unist-util-stringify-position: 3.0.0
-    dev: false
-
-  /vfile@4.2.1:
-    resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==}
-    dependencies:
-      '@types/unist': 2.0.6
-      is-buffer: 2.0.5
-      unist-util-stringify-position: 2.0.3
-      vfile-message: 2.0.4
-    dev: true
-
-  /vfile@5.3.0:
-    resolution: {integrity: sha512-Tj44nY/48OQvarrE4FAjUfrv7GZOYzPbl5OD65HxVKwLJKMPU7zmfV8cCgCnzKWnSfYG2f3pxu+ALqs7j22xQQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-      is-buffer: 2.0.5
-      unist-util-stringify-position: 3.0.0
-      vfile-message: 3.1.0
-    dev: false
-
-  /vscode-jsonrpc@8.2.0:
-    resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
-    engines: {node: '>=14.0.0'}
-    dev: false
-
-  /vscode-languageserver-protocol@3.17.5:
-    resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
-    dependencies:
-      vscode-jsonrpc: 8.2.0
-      vscode-languageserver-types: 3.17.5
-    dev: false
-
-  /vscode-languageserver-textdocument@1.0.12:
-    resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
-    dev: false
-
-  /vscode-languageserver-types@3.17.5:
-    resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
-    dev: false
-
-  /vscode-languageserver@9.0.1:
-    resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
-    hasBin: true
-    dependencies:
-      vscode-languageserver-protocol: 3.17.5
-    dev: false
-
   /vscode-uri@3.0.3:
     resolution: {integrity: sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==}
     dev: true
 
-  /vscode-uri@3.0.8:
-    resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
-    dev: false
-
   /w3c-hr-time@1.0.2:
     resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
     deprecated: Use your platform's native performance.now() and performance.timeOrigin.
@@ -15005,18 +12458,6 @@ packages:
       makeerror: 1.0.12
     dev: false
 
-  /warning@3.0.0:
-    resolution: {integrity: sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==}
-    dependencies:
-      loose-envify: 1.4.0
-    dev: false
-
-  /warning@4.0.3:
-    resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
-    dependencies:
-      loose-envify: 1.4.0
-    dev: false
-
   /watchpack@2.4.1:
     resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==}
     engines: {node: '>=10.13.0'}
@@ -15031,10 +12472,6 @@ packages:
       minimalistic-assert: 1.0.1
     dev: false
 
-  /web-namespaces@2.0.1:
-    resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
-    dev: false
-
   /webidl-conversions@4.0.2:
     resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
     dev: false
@@ -15252,6 +12689,7 @@ packages:
       is-number-object: 1.0.7
       is-string: 1.0.7
       is-symbol: 1.0.4
+    dev: false
 
   /which@1.3.1:
     resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
@@ -15454,7 +12892,15 @@ packages:
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
-    dev: false
+
+  /wrap-ansi@8.1.0:
+    resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      ansi-styles: 6.2.1
+      string-width: 5.1.2
+      strip-ansi: 7.0.1
+    dev: true
 
   /wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -15513,13 +12959,6 @@ packages:
     resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
     dev: false
 
-  /xtend@2.1.2:
-    resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==}
-    engines: {node: '>=0.4'}
-    dependencies:
-      object-keys: 0.4.0
-    dev: false
-
   /xtend@4.0.2:
     resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
     engines: {node: '>=0.4'}
@@ -15587,11 +13026,3 @@ packages:
       property-expr: 2.0.4
       toposort: 2.0.2
     dev: false
-
-  /zwitch@1.0.5:
-    resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
-    dev: true
-
-  /zwitch@2.0.2:
-    resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==}
-    dev: false
