Repository: PostHog/HouseWatch Branch: main Commit: 9b126c0d588c Files: 136 Total size: 272.5 KB Directory structure: gitextract_43962rdt/ ├── .github/ │ └── workflows/ │ ├── docker-api.yaml │ ├── docker-frontend.yaml │ └── release-chart.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── bin/ │ ├── celery │ ├── docker │ ├── migrate │ ├── serve │ └── start ├── charts/ │ └── housewatch/ │ ├── .helmignore │ ├── Chart.yaml │ ├── templates/ │ │ ├── _helpers.tpl │ │ ├── deployment-nginx.yaml │ │ ├── deployment-web.yaml │ │ ├── deployment-worker.yaml │ │ ├── nginx-configmap.yaml │ │ ├── redis.yaml │ │ └── service.yaml │ └── values.yaml ├── docker/ │ ├── Caddyfile │ └── clickhouse-server/ │ └── config.d/ │ └── config.xml ├── docker-compose.dev.yml ├── docker-compose.yml ├── frontend/ │ ├── .gitignore │ ├── .node-version │ ├── .prettierrc │ ├── Dockerfile │ ├── Dockerfile.dev │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public/ │ │ ├── fonts/ │ │ │ └── OFL.txt │ │ ├── index.html │ │ ├── manifest.json │ │ └── robots.txt │ ├── src/ │ │ ├── App.tsx │ │ ├── Layout.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── pages/ │ │ │ ├── AITools/ │ │ │ │ ├── AIToolsPage.tsx │ │ │ │ └── NaturalLanguageQueryEditor.tsx │ │ │ ├── Backups/ │ │ │ │ ├── Backups.tsx │ │ │ │ └── ScheduledBackups.tsx │ │ │ ├── Clusters/ │ │ │ │ └── Clusters.tsx │ │ │ ├── DiskUsage/ │ │ │ │ └── DiskUsage.tsx │ │ │ ├── Errors/ │ │ │ │ └── Errors.tsx │ │ │ ├── Logs/ │ │ │ │ └── Logs.tsx │ │ │ ├── Operations/ │ │ │ │ └── Operations.tsx │ │ │ ├── Overview/ │ │ │ │ ├── Overview.tsx │ │ │ │ └── tips.ts │ │ │ ├── QueryEditor/ │ │ │ │ ├── Benchmark.tsx │ │ │ │ ├── QueryEditor.tsx │ │ │ │ ├── QueryEditorPage.tsx │ │ │ │ ├── SavedQueries.tsx │ │ │ │ └── SavedQuery.tsx │ │ │ ├── RunningQueries/ │ │ │ │ └── RunningQueries.tsx │ │ │ ├── SchemaStats/ │ │ │ │ ├── SchemaStats.tsx │ │ │ │ └── SchemaTable.tsx │ │ │ └── SlowQueries/ │ │ │ ├── ExampleQueriesTab.tsx │ │ │ ├── ExplainTab.tsx │ │ │ ├── MetricsTab.tsx │ │ │ ├── NormalizedQueryTab.tsx │ │ │ ├── QueryDetail.tsx │ │ │ └── SlowQueries.tsx │ │ ├── react-app-env.d.ts │ │ └── utils/ │ │ ├── dateUtils.ts │ │ └── usePollingEffect.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── housewatch/ │ ├── __init__.py │ ├── admin/ │ │ └── __init__.py │ ├── admin.py │ ├── ai/ │ │ └── templates.py │ ├── api/ │ │ ├── __init__.py │ │ ├── analyze.py │ │ ├── async_migration.py │ │ ├── backups.py │ │ ├── cluster.py │ │ ├── instance.py │ │ └── saved_queries.py │ ├── apps.py │ ├── asgi.py │ ├── async_migrations/ │ │ ├── __init__.py │ │ ├── async_migration_utils.py │ │ └── runner.py │ ├── celery.py │ ├── clickhouse/ │ │ ├── __init__.py │ │ ├── backups.py │ │ ├── client.py │ │ ├── clusters.py │ │ ├── queries/ │ │ │ ├── __init__.py │ │ │ └── sql.py │ │ └── table.py │ ├── gunicorn.conf.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ ├── 0002_asyncmigration_asyncmigration_unique name.py │ │ ├── 0003_asyncmigration_operations_and_more.py │ │ ├── 0004_asyncmigration_last_error.py │ │ ├── 0005_asyncmigration_finished_at.py │ │ ├── 0006_savedquery.py │ │ ├── 0007_scheduledbackup_scheduledbackuprun.py │ │ ├── 0008_remove_scheduledbackup_aws_endpoint_url_and_more.py │ │ ├── 0009_scheduledbackup_cluster_alter_scheduledbackup_id.py │ │ ├── 0010_scheduledbackup_incremental_schedule_and_more.py │ │ ├── 0011_scheduledbackup_is_sharded_and_more.py │ │ ├── 0012_preferredreplica.py │ │ └── __init__.py │ ├── models/ │ │ ├── __init__.py │ │ ├── async_migration.py │ │ ├── backup.py │ │ ├── instance.py │ │ ├── preferred_replica.py │ │ └── saved_queries.py │ ├── settings/ │ │ ├── __init__.py │ │ └── utils.py │ ├── tasks/ │ │ └── __init__.py │ ├── tests/ │ │ └── test_backup_table_fixture.sql │ ├── urls.py │ ├── utils/ │ │ ├── __init__.py │ │ └── encrypted_fields/ │ │ ├── fields.py │ │ └── hkdf.py │ ├── views.py │ └── wsgi.py ├── manage.py ├── mypy.ini ├── pyproject.toml ├── pytest.ini ├── requirements-dev.in ├── requirements-dev.txt ├── requirements.in ├── requirements.txt └── runtime.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/docker-api.yaml ================================================ name: API Docker build on: push: jobs: docker: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: platforms: arm64 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Docker image metadata id: meta uses: docker/metadata-action@v4 with: images: ghcr.io/${{ github.repository }}/api tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} type=ref,event=pr type=sha - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/docker-frontend.yaml ================================================ name: Frontend Docker build on: push: jobs: docker: runs-on: ubuntu-latest defaults: run: working-directory: "frontend" permissions: contents: read packages: write steps: - uses: actions/checkout@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: platforms: arm64 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Docker image metadata id: meta uses: docker/metadata-action@v4 with: images: ghcr.io/${{ github.repository }}/frontend tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} type=ref,event=pr type=sha - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: pnpm/action-setup@v2 with: version: 8.6.12 - name: Install Node.js uses: actions/setup-node@v4 with: node-version-file: frontend/.node-version cache: "pnpm" cache-dependency-path: frontend/pnpm-lock.yaml - name: Install dependencies run: pnpm i --frozen-lockfile - name: Build run: pnpm build - name: Build and push uses: docker/build-push-action@v4 with: context: frontend/ platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/release-chart.yaml ================================================ name: Release Chart on: pull_request: paths: - charts/** - .github/workflows/release-chart.yaml push: branches: - main paths: - charts/** - .github/workflows/release-chart.yaml jobs: release: name: Release chart to repo runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout uses: actions/checkout@v3 - name: Install Helm uses: azure/setup-helm@v1 with: version: v3.12.2 - name: Add - to version in charts/housewatch/Chart.yaml and update Chart.lock if: github.ref != 'refs/heads/main' run: | sed -i 's/^version: \(.*\)$/version: \1-${{ github.sha }}/g' charts/housewatch/Chart.yaml - name: Configure Git run: | git config user.name "Max Hedgehog" git config user.email "127861667+max-hedgehog[bot]@users.noreply.github.com" git fetch origin gh-pages --depth=1 - name: Package run: | helm dependency update charts/housewatch/ mkdir -p .cr-release-packages cd .cr-release-packages helm package ../charts/housewatch cd - set -x - name: Run chart-releaser uses: helm/chart-releaser-action@4b85f2c82c80ff4284ff8520f47bbe69dd89b0aa if: github.ref == 'refs/heads/main' && github.repository == 'PostHog/HouseWatch' env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" with: skip_existing: true skip_packaging: true ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ env/ __pycache__/ node_modules housewatch.sqlite3 .DS_Store yarn.lock charts/housewatch/charts/ .pnpm/ ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.0.275 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black rev: 22.10.0 hooks: - id: black ================================================ FILE: Dockerfile ================================================ FROM python:3.10 ENV PYTHONUNBUFFERED 1 WORKDIR /code COPY requirements.txt ./ RUN apt-get update && \ apt-get install -y --no-install-recommends \ "build-essential" \ "git" \ "libpq-dev" \ "libxmlsec1" \ "libxmlsec1-dev" \ "libffi-dev" \ "pkg-config" \ && \ rm -rf /var/lib/apt/lists/* && \ pip install -r requirements.txt --compile --no-cache-dir USER root COPY manage.py manage.py COPY housewatch housewatch/ COPY bin bin/ RUN DEBUG=1 python manage.py collectstatic --noinput ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 PostHog Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

## 📈 Open source tool for monitoring and managing ClickHouse clusters - Get an overview of cluster load and performance - Drill down into your queries and understand the load they put on your cluster - Search through logs and errors - Monitor and kill running queries with the click of a button - Get stats on your disk usage per node, and understand how much disk space tables, columns, and parts take up - Run your own queries straight from the interface to further dig into performance and cluster issues - Setup operations to run in the background with automatic rollbacks for failures ## 💻 Deploy To deploy HouseWatch, clone this repo and then run the following, substituting the environment variables for the relevant values of one of your ClickHouse instances: ```bash SITE_ADDRESS= \ CLICKHOUSE_HOST=localhost \ CLICKHOUSE_CLUSTER=mycluster \ CLICKHOUSE_USER=default \ CLICKHOUSE_PASSWORD=xxxxxxxxxxx \ docker compose -f docker-compose.yml up ``` `SITE_ADDRESS` here is the address that the UI will be running on. It can be a domain name or simply a port like `:80`. After running the above, the UI will be running on the address you specified. This will be something like http://localhost if you used `:80` for your `SITE_ADDRESS` above. I would think twice about exposing this to the internet, as it is not currently secured in any way.
Read more
The following are the supported environment variables for configuring your HouseWatch deployment: - `CLICKHOUSE_HOST`: Required - hostname of the instance to connect to. - `CLICKHOUSE_USER`: Required - username to access ClickHouse. Can be a read-only user, but in that case not all features will work. - `CLICKHOUSE_PASSWORD`: Required - password for the specified user. - `CLICKHOUSE_DATABASE`: Optional - database to connect to by default. - `CLICKHOUSE_CLUSTER`: Optional - cluster name, to analyze data from the whole cluster. - `CLICKHOUSE_SECURE`: Optional - see [clickhouse-driver docs](https://clickhouse-driver.readthedocs.io/en/latest/index.html) for more information - `CLICKHOUSE_VERIFY`: Optional - see [clickhouse-driver docs](https://clickhouse-driver.readthedocs.io/en/latest/index.html) for more information - `CLICKHOUSE_CA`: Optional - see [clickhouse-driver docs](https://clickhouse-driver.readthedocs.io/en/latest/index.html) for more information - `OPENAI_API_KEY`: Optional - enables the experimental "AI Tools" page, which currently features a natural language query editor - `OPENAI_MODEL`: Optional - a valid OpenAI model (e.g. `gpt-3.5-turbo`, `gpt-4`) that you have access to with the key above to be used for the AI features
## 🏡 Running locally To run HouseWatch locally along with a local ClickHouse instance, execute: ```bash docker compose -f docker-compose.dev.yml up -d ``` then go to http://localhost:8080 ## 💡 Motivation At PostHog we manage a few large ClickHouse clusters and found ourselves in need of a tool to monitor and manage these more easily. ClickHouse is fantastic at introspection, providing a lot of metadata about the system in its system tables so that it can be easily queried. However, knowing exactly how to query and parse the available information can be a difficult task. Over the years at PostHog, we've developed great intuition for how to debug ClickHouse issues using ClickHouse, and HouseWatch is the compilation of this knowledge into a tool. Beyond monitoring, we also built internal systems and processes for managing the clusters that spanned various platforms. We would use Grafana to look at metrics, SSH into nodes for running operations and using specialized tooling, query via Metabase to dig deeper into the data in the system tables and create dashboards, and then a combination of tools baked into the PostHog product for further debugging and streamlined operations such as our [async migrations](https://posthog.com/blog/async-migrations) tool, and internal views for listing queries and analyzing their performance. As a result, we felt it was appropriate to have these tools live in one place. Ultimately, our vision for HouseWatch is that it can both serve the purpose of a pganalyze for the ClickHouse ecosystem, while also including tooling for taking action on insights derived from the analysis. ## 🏗️ Status of the project HouseWatch is in its early days and we have a lot more features in mind that we'd like to build into it going forward. The code could also use some cleaning up :) As of right now, it is considered Beta software and you should exercise caution when using it in production. One potential approach is to connect HouseWatch to ClickHouse using a read-only user. In this case, the cluster management features will not work (e.g. operations, query editor), but the analysis toolset will function normally. HouseWatch was created and is maintained by [PostHog](https://posthog.com) and [yakkomajuri](https://github.com/yakkomajuri). ## ℹ️ Contributing Contributions are certainly welcome! However, if you'd like to build a new feature, please open up an issue first. ## ⭐ Features

Query performance


Schema stats


Query benchmarking


Logs


Query editor


Disk usage


Errors


Operations

## 🗒️ To-do list A public list of things we intend to do with HouseWatch in the near future.
See list
Features - [ ] System issues tab - [ ] EXPLAIN visualizer - [ ] Multiple instance support - [ ] Stats on page cache hit percentage - [ ] Make operations resilient to Celery going down (as we do in PostHog with async migrations) - [ ] Read-only mode - [ ] Button to force refresh running queries list - [ ] Logs pagination - [ ] Allow copying example queries - [ ] Configurable time ranges - [ ] Whole cluster schema stats - [ ] More operation controls: view, delete, edit, re-run, display errors Developer experience - [ ] Configure instance from UI - [ ] Publish a Docker image - [ ] Development docker-compose.yml with baked in ClickHouse Cleanup - [ ] Extract README images out of repo - [ ] Make banner subtitle work on dark mode - [ ] Fetch data independently on the query analyzer - [ ] Breakpoint for logs search - [ ] Run Django "production server" - [ ] Write tests :) - [ ] Query editor pipe all errors to client - [ ] Abstraction to load data from API as JSON
================================================ FILE: bin/celery ================================================ #!/bin/bash set -e celery -A housewatch worker -B ================================================ FILE: bin/docker ================================================ #!/bin/bash set -e ./bin/migrate ./bin/serve ================================================ FILE: bin/migrate ================================================ #!/bin/bash set -e python manage.py migrate ================================================ FILE: bin/serve ================================================ #!/bin/bash exec gunicorn housewatch.wsgi -c housewatch/gunicorn.conf.py \ --worker-tmp-dir /dev/shm ================================================ FILE: bin/start ================================================ #!/bin/bash set -e export DEBUG=1 ./bin/celery & python manage.py runserver ================================================ FILE: charts/housewatch/.helmignore ================================================ # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *.orig *~ # Various IDEs .project .idea/ *.tmproj .vscode/ ================================================ FILE: charts/housewatch/Chart.yaml ================================================ apiVersion: v2 name: housewatch description: Open source tool for monitoring and managing ClickHouse clusters type: application version: 0.1.9 appVersion: "0.1.2" dependencies: - name: postgresql version: "12.10.0" repository: "oci://registry-1.docker.io/bitnamicharts" - name: rabbitmq version: "12.1.3" repository: "oci://registry-1.docker.io/bitnamicharts" ================================================ FILE: charts/housewatch/templates/_helpers.tpl ================================================ {{/* Expand the name of the chart. */}} {{- define "housewatch.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "housewatch.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* Create chart name and version as used by the chart label. */}} {{- define "housewatch.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} {{- define "housewatch.labels" -}} helm.sh/chart: {{ include "housewatch.chart" . }} {{ include "housewatch.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "housewatch.selectorLabels" -}} app.kubernetes.io/name: {{ include "housewatch.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} {{- define "housewatch.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "housewatch.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} ================================================ FILE: charts/housewatch/templates/deployment-nginx.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "housewatch.fullname" . }}-nginx labels: {{- include "housewatch.labels" . | nindent 4 }} spec: selector: matchLabels: {{- include "housewatch.selectorLabels" . | nindent 6 }} app.kubernetes.io/service: nginx template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "housewatch.selectorLabels" . | nindent 8 }} app.kubernetes.io/service: nginx spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} volumes: - name: frontend emptyDir: {} - name: nginx-config configMap: name: {{ include "housewatch.fullname" . }}-nginx initContainers: - name: frontend-copy image: "{{ .Values.image.frontendRepository }}:{{ .Values.image.tag }}" command: [sh, -cex] args: - cp -r /frontend/build/* /http/ volumeMounts: - mountPath: /http name: frontend containers: - name: nginx image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" ports: - name: http containerPort: 80 protocol: TCP volumeMounts: - mountPath: /http name: frontend - mountPath: /etc/nginx/nginx.conf name: nginx-config subPath: nginx.conf resources: {{- toYaml .Values.nginx.resources | nindent 12 }} ================================================ FILE: charts/housewatch/templates/deployment-web.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "housewatch.fullname" . }} labels: {{- include "housewatch.labels" . | nindent 4 }} spec: selector: matchLabels: {{- include "housewatch.selectorLabels" . | nindent 6 }} app.kubernetes.io/service: web template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "housewatch.selectorLabels" . | nindent 8 }} app.kubernetes.io/service: web spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} containers: - name: web image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" command: ["bash", "-c"] args: - | python manage.py migrate python manage.py runserver 0.0.0.0:8000 ports: - name: api containerPort: 8000 protocol: TCP env: - name: REDIS_URL value: redis://{{ include "housewatch.fullname" . }}-redis:6379 - name: CLICKHOUSE_HOST value: "{{ .Values.clickhouse.host }}" - name: CLICKHOUSE_DATABASE value: "{{ .Values.clickhouse.database }}" - name: CLICKHOUSE_USER value: "{{ .Values.clickhouse.user }}" - name: CLICKHOUSE_PASSWORD valueFrom: secretKeyRef: name: "{{ .Values.clickhouse.secretName }}" key: "{{ .Values.clickhouse.secretPasswordKey }}" - name: CLICKHOUSE_CLUSTER value: {{ .Values.clickhouse.cluster }} - name: CLICKHOUSE_SECURE value: "{{ .Values.clickhouse.secure }}" - name: CLICKHOUSE_VERIFY value: "{{ .Values.clickhouse.verify }}" - name: CLICKHOUSE_CA value: "{{ .Values.clickhouse.ca }}" - name: DATABASE_URL value: "{{ . | tpl .Values.database_url }}" - name: RABBITMQ_URL value: "amqp://{{ .Values.rabbitmq.auth.username }}:{{ .Values.rabbitmq.auth.password }}@{{ .Release.Name }}-rabbitmq:5672/" livenessProbe: httpGet: path: / port: api readinessProbe: httpGet: path: / port: api resources: {{- toYaml .Values.web.resources | nindent 12 }} ================================================ FILE: charts/housewatch/templates/deployment-worker.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "housewatch.fullname" . }}-worker labels: {{- include "housewatch.labels" . | nindent 4 }} spec: selector: matchLabels: {{- include "housewatch.selectorLabels" . | nindent 6 }} app.kubernetes.io/service: worker template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "housewatch.selectorLabels" . | nindent 8 }} app.kubernetes.io/service: worker spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} containers: - name: worker image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" command: ["./bin/celery"] env: - name: REDIS_URL value: redis://{{ include "housewatch.fullname" . }}-redis:6379 - name: CLICKHOUSE_HOST value: "{{ .Values.clickhouse.host }}" - name: CLICKHOUSE_DATABASE value: "{{ .Values.clickhouse.database }}" - name: CLICKHOUSE_USER value: "{{ .Values.clickhouse.user }}" - name: CLICKHOUSE_PASSWORD valueFrom: secretKeyRef: name: "{{ .Values.clickhouse.secretName }}" key: "{{ .Values.clickhouse.secretPasswordKey }}" - name: CLICKHOUSE_CLUSTER value: "{{ .Values.clickhouse.cluster }}" - name: CLICKHOUSE_SECURE value: "{{ .Values.clickhouse.secure }}" - name: CLICKHOUSE_VERIFY value: "{{ .Values.clickhouse.verify }}" - name: CLICKHOUSE_CA value: "{{ .Values.clickhouse.ca }}" - name: DATABASE_URL value: "{{ . | tpl .Values.database_url }}" - name: RABBITMQ_URL value: "amqp://{{ .Values.rabbitmq.auth.username }}:{{ .Values.rabbitmq.auth.password }}@{{ .Release.Name }}-rabbitmq:5672/" resources: {{- toYaml .Values.worker.resources | nindent 12 }} ================================================ FILE: charts/housewatch/templates/nginx-configmap.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: {{ include "housewatch.fullname" . }}-nginx data: nginx.conf: | events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; location / { root /http; try_files $uri $uri/ /index.html =404; } location /api { proxy_pass http://{{ include "housewatch.fullname" . }}-api:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; } location /admin { proxy_pass http://{{ include "housewatch.fullname" . }}-api:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; } location /healthz { proxy_pass http://{{ include "housewatch.fullname" . }}-api:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; } } } ================================================ FILE: charts/housewatch/templates/redis.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "housewatch.fullname" . }}-redis labels: {{- include "housewatch.labels" . | nindent 4 }} spec: replicas: 1 selector: matchLabels: {{- include "housewatch.selectorLabels" . | nindent 6 }} app.kubernetes.io/service: redis template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "housewatch.selectorLabels" . | nindent 8 }} app.kubernetes.io/service: redis spec: containers: - name: redis image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: redis containerPort: 6379 protocol: TCP resources: {{- toYaml .Values.redis.resources | nindent 12 }} ================================================ FILE: charts/housewatch/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: {{ include "housewatch.fullname" . }} annotations: {{- toYaml .Values.service.annotations | nindent 4 }} labels: {{- include "housewatch.labels" . | nindent 4 }} spec: type: ClusterIP ports: - port: 80 targetPort: http protocol: TCP name: http selector: {{- include "housewatch.selectorLabels" . | nindent 4 }} app.kubernetes.io/service: nginx --- apiVersion: v1 kind: Service metadata: name: {{ include "housewatch.fullname" . }}-api labels: {{- include "housewatch.labels" . | nindent 4 }} spec: type: ClusterIP ports: - port: 8000 targetPort: api protocol: TCP name: api selector: {{- include "housewatch.selectorLabels" . | nindent 4 }} app.kubernetes.io/service: web --- apiVersion: v1 kind: Service metadata: name: {{ include "housewatch.fullname" . }}-redis labels: {{- include "housewatch.labels" . | nindent 4 }} spec: type: ClusterIP ports: - port: 6379 targetPort: 6379 protocol: TCP name: redis selector: {{- include "housewatch.selectorLabels" . | nindent 4 }} app.kubernetes.io/service: redis ================================================ FILE: charts/housewatch/values.yaml ================================================ image: repository: ghcr.io/posthog/housewatch/api frontendRepository: ghcr.io/posthog/housewatch/frontend tag: main nginx: image: repository: nginx tag: stable clickhouse: user: default host: clickhouse database: default secure: "false" verify: "false" ca: "" service: annotations: {} web: resources: requests: cpu: 100m memory: 500Mi limits: memory: 500Mi frontend: resources: requests: cpu: 500m memory: 2Gi limits: memory: 2Gi worker: resources: requests: cpu: 100m memory: 1500Mi limits: memory: 1500Mi database_url: postgres://housewatch:housewatch@{{ include "postgresql.primary.fullname" .Subcharts.postgresql }}:5432/housewatch postgresql: auth: database: housewatch username: housewatch password: housewatch rabbitmq: auth: username: housewatch password: housewatch erlangCookie: housewatch redis: image: repository: redis tag: 6.2.7-alpine resources: requests: cpu: 100m memory: 1Gi limits: memory: 1Gi ================================================ FILE: docker/Caddyfile ================================================ { debug } {$SITE_ADDRESS} { reverse_proxy web:3000 reverse_proxy /api/* app:8000 reverse_proxy /logout app:8000 reverse_proxy /admin/ app:8000 } ================================================ FILE: docker/clickhouse-server/config.d/config.xml ================================================ :: 0.0.0.0 1 clickhouse 9000 ================================================ FILE: docker-compose.dev.yml ================================================ version: "3" services: app: build: . environment: &django_env DEBUG: 1 REDIS_URL: redis://redis:6379 RABBITMQ_URL: amqp://posthog:posthog@rabbitmq:5672 DATABASE_URL: postgres://housewatch:housewatch@db:5432/housewatch CLICKHOUSE_HOST: clickhouse CLICKHOUSE_DATABASE: default CLICKHOUSE_USER: default CLICKHOUSE_PASSWORD: "" CLICKHOUSE_CLUSTER: housewatch CLICKHOUSE_SECURE: false CLICKHOUSE_VERIFY: false CLICKHOUSE_CA: "" AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION command: - bash - -c - | python manage.py runserver 0.0.0.0:8000 ports: - "8000:8000" volumes: - .:/code depends_on: migrations: condition: service_completed_successfully clickhouse: condition: service_started db: condition: service_healthy redis: condition: service_started rabbitmq: condition: service_started migrations: build: . environment: *django_env command: python manage.py migrate depends_on: db: condition: service_healthy volumes: - .:/code web: build: context: ./frontend dockerfile: Dockerfile.dev caddy: image: caddy:2.6.1 restart: unless-stopped ports: - "8080:8080" - "443:443" environment: SITE_ADDRESS: ":8080" volumes: - ./docker/Caddyfile:/etc/caddy/Caddyfile depends_on: - web - app db: image: postgres:16-alpine restart: on-failure environment: POSTGRES_USER: housewatch POSTGRES_DB: housewatch POSTGRES_PASSWORD: housewatch healthcheck: test: ["CMD-SHELL", "pg_isready -U housewatch"] interval: 5s timeout: 5s ports: - "5432:5432" redis: image: redis:6.2.7-alpine restart: on-failure command: redis-server --maxmemory-policy allkeys-lru --maxmemory 200mb worker: build: . environment: <<: *django_env command: - ./bin/celery volumes: - .:/code depends_on: - clickhouse - db - redis - rabbitmq clickhouse: image: ${CLICKHOUSE_SERVER_IMAGE:-clickhouse/clickhouse-server:23.12.5} restart: on-failure depends_on: - zookeeper volumes: - ./docker/clickhouse-server/config.d:/etc/clickhouse-server/config.d ports: - "8123:8123" zookeeper: image: zookeeper:3.7.0 restart: on-failure rabbitmq: image: rabbitmq:3.12.2-management-alpine ports: - "15672:15672" # Web management UI - "5672:5672" # Default RabbitMQ broker port environment: RABBITMQ_DEFAULT_USER: posthog RABBITMQ_DEFAULT_PASS: posthog ================================================ FILE: docker-compose.yml ================================================ version: "3" services: app: build: . environment: &django_env DATABASE_URL: postgres://housewatch:housewatch@db:5432/housewatch RABBITMQ_URL: amqp://posthog:posthog@rabbitmq:5672 REDIS_URL: redis://redis:6379 CLICKHOUSE_HOST: $CLICKHOUSE_HOST CLICKHOUSE_DATABASE: $CLICKHOUSE_DATABASE CLICKHOUSE_USER: $CLICKHOUSE_USER CLICKHOUSE_PASSWORD: $CLICKHOUSE_PASSWORD CLICKHOUSE_CLUSTER: $CLICKHOUSE_CLUSTER CLICKHOUSE_SECURE: $CLICKHOUSE_SECURE CLICKHOUSE_VERIFY: $CLICKHOUSE_VERIFY CLICKHOUSE_CA: $CLICKHOUSE_CA AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION command: - bash - -c - | python manage.py migrate python manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - "8000:8000" depends_on: - db - redis - rabbitmq - clickhouse web: build: ./frontend ports: - "3000:3000" worker: build: . environment: <<: *django_env command: - ./bin/celery volumes: - .:/code depends_on: - db - redis - rabbitmq - clickhouse redis: image: redis:6.2.7-alpine restart: on-failure ports: - "6388:6379" command: redis-server --maxmemory-policy allkeys-lru --maxmemory 200mb db: image: postgres:14-alpine restart: on-failure environment: POSTGRES_USER: housewatch POSTGRES_DB: housewatch POSTGRES_PASSWORD: housewatch caddy: image: caddy:2.6.1 restart: unless-stopped ports: - "80:80" - "443:443" environment: SITE_ADDRESS: $SITE_ADDRESS volumes: - ./docker/Caddyfile:/etc/caddy/Caddyfile depends_on: - web - app rabbitmq: image: rabbitmq:3.12.2-management-alpine ports: - "15672:15672" # Web management UI - "5672:5672" # Default RabbitMQ broker port environment: RABBITMQ_DEFAULT_USER: posthog RABBITMQ_DEFAULT_PASS: posthog ================================================ FILE: frontend/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: frontend/.node-version ================================================ 20.4.0 ================================================ FILE: frontend/.prettierrc ================================================ { "trailingComma": "es5", "tabWidth": 4, "semi": false, "singleQuote": true, "printWidth": 120 } ================================================ FILE: frontend/Dockerfile ================================================ FROM alpine:latest WORKDIR /frontend COPY build/ build/ CMD ["echo", "Serve the files from /frontend/build, don't run this container directly"] ================================================ FILE: frontend/Dockerfile.dev ================================================ FROM node:20.4.0-alpine ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable WORKDIR /frontend COPY . . RUN pnpm i CMD ["pnpm", "vite", "--port", "3000", "--host"] ================================================ FILE: frontend/README.md ================================================ # Getting Started with Create React App This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). ## Available Scripts In the project directory, you can run: ### `npm start` Runs the app in the development mode.\ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. ### `npm test` Launches the test runner in the interactive watch mode.\ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. ### `npm run build` Builds the app for production to the `build` folder.\ It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.\ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. ### `npm run eject` **Note: this is a one-way operation. Once you `eject`, you can’t go back!** If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. ## Learn More You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). To learn React, check out the [React documentation](https://reactjs.org/). ================================================ FILE: frontend/index.html ================================================ HouseWatch
================================================ FILE: frontend/package.json ================================================ { "name": "housewatch", "version": "1.0.0-beta", "dependencies": { "@ant-design/charts": "^1.4.2", "@ant-design/icons": "^5.2.0", "@ant-design/plots": "^2.1.12", "antd": "^5.13.2", "prismjs": "^1.29.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "5.2.0", "react-scripts": "^2.1.3", "react-simple-code-editor": "^0.13.1", "sql-formatter-plus": "^1.3.6", "swr": "^2.2.4", "typescript": "^4.9.5", "uuid": "^9.0.0" }, "devDependencies": { "@types/jest": "^27.5.2", "@types/lodash": "^4.14.192", "@types/node": "^16.18.22", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/react-router-dom": "5.3.3", "@types/uuid": "^9.0.1", "@vitejs/plugin-react": "^4.2.1", "vite": "^5.0.12" }, "pnpm": { "overrides": { "node-forge": "1.3.2" } }, "scripts": { "dev": "vite", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "proxy": "http://localhost:8000", "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531" } ================================================ FILE: frontend/public/fonts/OFL.txt ================================================ Copyright (c) 2015 Indian Type Foundry (info@indiantypefoundry.com) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: frontend/public/index.html ================================================ HouseWatch
================================================ FILE: frontend/public/manifest.json ================================================ { "short_name": "HouseWatch", "name": "HouseWatch", "icons": [], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: frontend/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: frontend/src/App.tsx ================================================ import React from 'react' import { BrowserRouter as Router } from 'react-router-dom' import Layout from './Layout' function App() { return (
) } export default App ================================================ FILE: frontend/src/Layout.tsx ================================================ // @ts-nocheck import React, { useEffect, useState } from 'react' import { DiskUsage } from './pages/DiskUsage/DiskUsage' import SlowQueries from './pages/SlowQueries/SlowQueries' import Schema from './pages/SchemaStats/SchemaStats' import QueryDetail from './pages/SlowQueries/QueryDetail' import SchemaTable from './pages/SchemaStats/SchemaTable' import Overview from './pages/Overview/Overview' import Clusters from './pages/Clusters/Clusters' import Backups from './pages/Backups/Backups' import ScheduledBackups from './pages/Backups/ScheduledBackups' import Errors from './pages/Errors/Errors' import { Switch, Route, useHistory } from 'react-router-dom' import { Operations } from './pages/Operations/Operations' import RunningQueries from './pages/RunningQueries/RunningQueries' import Logs from './pages/Logs/Logs' import { ApartmentOutlined, CloudServerOutlined, CodeOutlined, DashboardOutlined, HddOutlined, HomeOutlined, WarningOutlined, ClockCircleOutlined, GithubFilled, BarsOutlined, FormOutlined, ToolOutlined, SaveOutlined, } from '@ant-design/icons' import { ConfigProvider, MenuProps } from 'antd' import { Layout, Menu } from 'antd' import QueryEditorPage from './pages/QueryEditor/QueryEditorPage' import AIToolsPage from './pages/AITools/AIToolsPage' const { Header, Content, Footer, Sider } = Layout type MenuItem = Required['items'][number] const items: MenuItem[] = [ { key: '', icon: , label: 'Overview' }, { key: 'clusters', label: 'Clusters', icon: }, { key: 'backup', label: 'Backup', icon: , children: [ { key: 'backups', label: 'Adhoc Backups' }, { key: 'scheduled_backups', label: 'Scheduled Backups' }, ], }, { key: 'query_performance', label: 'Query performance', icon: }, { key: 'running_queries', label: 'Running queries', icon: }, { key: 'schema', label: 'Schema stats', icon: }, { key: 'disk_usage', label: 'Disk usage', icon: }, { key: 'logs', label: 'Logs', icon: }, { key: 'errors', label: 'Errors', icon: }, { key: 'query_editor', label: 'Query editor', icon: }, { key: 'operations', label: 'Operations', icon: }, { key: 'ai_tools', label: 'AI Tools', icon: }, ] export default function AppLayout(): JSX.Element { const [hostname, setHostname] = useState('') const fetchHostname = async () => { const response = await fetch(`/api/analyze/hostname`) const responseJson = await response.json() setHostname(responseJson.hostname) } useEffect(() => { fetchHostname() }, []) const history = useHistory() const openPage = history.location.pathname.split('/')[1] return (
history.push('')}>

HouseWatch

history.push(`/${info.key}`)} />

{hostname}

) } ================================================ FILE: frontend/src/index.css ================================================ @font-face { font-family: 'Hind Siliguri'; src: url('fonts/HindSiliguri-Medium.ttf'); } html { overflow: hidden; height: 100%; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #262626; font-size: 0.875rem; line-height: 1.57; font-weight: 400; overflow: auto; height: 100%; } .ant-menu-item-selected { color: black !important; } h1 { font-size: 24px; } h1, h2, h3, h4, h5, h6 { font-family: sans-serif; } strong, b { font-weight: 600; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } .clickable:hover, .clickable div:hover, .code-editor:hover textarea { cursor: pointer !important; } .code-editor textarea { line-height: 2 !important; font-weight: 100 !important; } .code-editor:hover { background-color: #ededed; } .ant-tabs-tab-active .ant-tabs-tab-btn, .ant-tabs-tab-btn:hover { color: #1677ff !important; } .ant-tabs-ink-bar { background: #1677ff !important; } .run-async-migration-btn:hover { border-color: #64a0f4 !important; background: #13a5ff !important; } input, textarea, button, select, .ant-select-selector { box-shadow: none !important; } .sidebar .ant-layout-sider-children { position: fixed; } .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { background-color: initial !important; } ================================================ FILE: frontend/src/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import App from './App' const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) root.render() ================================================ FILE: frontend/src/pages/AITools/AIToolsPage.tsx ================================================ import React, { useEffect, useState } from 'react' import { Tabs } from 'antd' import { useHistory } from 'react-router-dom' import NaturalLanguageQueryEditor from './NaturalLanguageQueryEditor' export default function AIToolsPage() { const history = useHistory() const [error, setError] = useState(null) const loadData = async () => { const res = await fetch('/api/analyze/ai_tools_available') const resJson = await res.json() if ('error' in resJson) { setError(resJson['error']) } } useEffect(() => { loadData() }, []) return ( <>

AI Tools (Alpha)

{error ? (

{error}

) : ( <> , }, ]} defaultActiveKey="natural_language" onChange={(tab) => history.push(`/query_editor/${tab}`)} /> )} ) } ================================================ FILE: frontend/src/pages/AITools/NaturalLanguageQueryEditor.tsx ================================================ import React, { useEffect, useState } from 'react' import { Select, Checkbox, Button, Table, ConfigProvider } from 'antd' import TextArea from 'antd/es/input/TextArea' // @ts-ignore import { highlight, languages } from 'prismjs/components/prism-core' // @ts-ignore import 'prismjs/components/prism-sql' import 'prismjs/themes/prism.css' import Editor from 'react-simple-code-editor' // @ts-ignore import { format } from 'sql-formatter-plus' export interface TableData { table: string database: string } export default function NaturalLanguageQueryEditor() { const [query, setQuery] = useState('') const [tables, setTables] = useState(null) const [tablesToQuery, setTablesToQuery] = useState([]) const [readonly, setReadonly] = useState(true) const [data, setData] = useState([{}]) const [sql, setSql] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const columns = data.length > 0 ? Object.keys(data[0]).map((column) => ({ title: column, dataIndex: column })) : [] const runQuery = async () => { setLoading(true) setSql(null) const res = await fetch('/api/analyze/natural_language_query', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: query, tables_to_query: tablesToQuery, readonly: readonly || false, }), }) const resJson = await res.json() if (resJson.error) { setError(resJson.error) setData([]) } else { setData(resJson.result) } setSql(resJson.sql) setLoading(false) } const loadTableData = async () => { const res = await fetch('/api/analyze/tables') const resJson = await res.json() setTables(resJson) } useEffect(() => { loadTableData() }, []) const selectOptions = (tables || []).map((t) => ({ value: [t.database, t.table].join('>>>>>'), label: [t.database, t.table].join('.'), })) return ( <>

Select the tables you'd like to query: