diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx index 2edabf8..0e3d7da 100644 --- a/frontend/src/Layout.tsx +++ b/frontend/src/Layout.tsx @@ -6,6 +6,7 @@ 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 Errors from './pages/Errors/Errors' import { Switch, Route, useHistory } from 'react-router-dom' @@ -14,6 +15,7 @@ import RunningQueries from './pages/RunningQueries/RunningQueries' import Logs from './pages/Logs/Logs' import { ApartmentOutlined, + CloudServerOutlined, CodeOutlined, DashboardOutlined, HddOutlined, @@ -36,6 +38,7 @@ type MenuItem = Required['items'][number] const items: MenuItem[] = [ { key: '', icon: , label: 'Overview' }, + { key: 'clusters', label: 'Clusters', icon: }, { key: 'query_performance', label: 'Query performance', icon: }, { key: 'running_queries', label: 'Running queries', icon: }, { key: 'schema', label: 'Schema stats', icon: }, @@ -98,6 +101,7 @@ export default function AppLayout(): JSX.Element { + diff --git a/frontend/src/pages/Clusters/Clusters.tsx b/frontend/src/pages/Clusters/Clusters.tsx new file mode 100644 index 0000000..580b27f --- /dev/null +++ b/frontend/src/pages/Clusters/Clusters.tsx @@ -0,0 +1,53 @@ +import React, { useEffect, useState } from 'react' +import { Line } from '@ant-design/charts' +import { Card, Col, Row, Tooltip, notification } from 'antd' +import { InfoCircleOutlined } from '@ant-design/icons' + +interface Cluster { + cluster: string +} + +interface Clusters { + clusters: Cluster[] +} + +export default function Clusters() { + const [clusters, setClusters] = useState({ + clusters: [], + }) + + const loadData = async () => { + try { + const res = await fetch('/api/clusters') + const resJson = await res.json() + const clusters = { clusters: resJson } + setClusters(clusters) + } catch (err) { + notification.error({ message: 'Failed to load data' }) + } + } + + useEffect(() => { + loadData() + }, []) + + const now = new Date() + const dayOfTheYear = Math.floor( + (now.getTime() - new Date(now.getFullYear(), 0, 0).getTime()) / (1000 * 60 * 60 * 24) + ) + + return ( +
+

Clusters

+
+ +
    + {clusters.clusters.map((cluster) => ( +
  • {cluster.cluster}
  • + ))} +
+
+
+
+ ) +} diff --git a/housewatch/api/analyze.py b/housewatch/api/analyze.py index 36a8e95..4227043 100644 --- a/housewatch/api/analyze.py +++ b/housewatch/api/analyze.py @@ -268,7 +268,6 @@ def tables(self, request: Request): @action(detail=False, methods=["POST"]) def natural_language_query(self, request: Request): - table_schema_sql_conditions = [] for full_table_name in request.data["tables_to_query"]: database, table = full_table_name.split(">>>>>") diff --git a/housewatch/api/cluster.py b/housewatch/api/cluster.py new file mode 100644 index 0000000..e243515 --- /dev/null +++ b/housewatch/api/cluster.py @@ -0,0 +1,17 @@ +import structlog +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet +from housewatch.clickhouse import clusters + + +logger = structlog.get_logger(__name__) + + +class ClusterViewset(GenericViewSet): + def list(self, request: Request) -> Response: + return Response(clusters.get_clusters()) + + def retrieve(self, request: Request, pk: str) -> Response: + return Response(clusters.get_cluster(pk)) diff --git a/housewatch/api/instance.py b/housewatch/api/instance.py index cbfc30b..217b2cd 100644 --- a/housewatch/api/instance.py +++ b/housewatch/api/instance.py @@ -1,7 +1,11 @@ import structlog +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from rest_framework.serializers import ModelSerializer from housewatch.models import Instance +from housewatch.clickhouse import clusters logger = structlog.get_logger(__name__) diff --git a/housewatch/celery.py b/housewatch/celery.py index 83f6219..90e775f 100644 --- a/housewatch/celery.py +++ b/housewatch/celery.py @@ -17,10 +17,6 @@ # Load task modules from all registered Django app configs. app.autodiscover_tasks() -# Make sure Redis doesn't add too many connections -# https://stackoverflow.com/questions/47106592/redis-connections-not-being-released-after-celery-task-is-complete -app.conf.broker_pool_limit = 0 - app.steps["worker"].add(DjangoStructLogInitStep) diff --git a/housewatch/clickhouse/clusters.py b/housewatch/clickhouse/clusters.py new file mode 100644 index 0000000..a044c09 --- /dev/null +++ b/housewatch/clickhouse/clusters.py @@ -0,0 +1,11 @@ +from housewatch.clickhouse.client import run_query + + +def get_clusters(): + QUERY = """Select cluster FROM system.clusters GROUP BY cluster""" + return run_query(QUERY) + + +def get_cluster(cluster): + QUERY = """Select * FROM system.clusters WHERE cluster = '%(cluster_name)s' """ + return run_query(QUERY, {"cluster_name": cluster}) diff --git a/housewatch/urls.py b/housewatch/urls.py index e545345..4269fb9 100644 --- a/housewatch/urls.py +++ b/housewatch/urls.py @@ -3,6 +3,7 @@ from django.urls import path from rest_framework_extensions.routers import ExtendedDefaultRouter from housewatch.api.instance import InstanceViewset +from housewatch.api.cluster import ClusterViewset from housewatch.api.analyze import AnalyzeViewset from housewatch.api.async_migration import AsyncMigrationsViewset from housewatch.views import healthz @@ -19,6 +20,7 @@ def __init__(self, *args, **kwargs): router = DefaultRouterPlusPlus() router.register(r"api/instance", InstanceViewset, basename="instance") +router.register(r"api/clusters", ClusterViewset, basename="cluster") router.register(r"api/analyze", AnalyzeViewset, basename="analyze") router.register(r"api/async_migrations", AsyncMigrationsViewset, basename="async_migrations") router.register(r"api/saved_queries", SavedQueryViewset, basename="saved_queries")