From 9e290bc473487a52fe57f2f8f0d0cc7467b1854b Mon Sep 17 00:00:00 2001 From: Enrico Olivelli Date: Mon, 12 Jul 2021 10:54:47 +0200 Subject: [PATCH] Remove Pulsar Dashboard --- dashboard/Dockerfile | 54 -- dashboard/README.md | 28 - dashboard/conf/nginx-app.conf | 37 -- dashboard/conf/postgresql.conf | 38 -- dashboard/conf/supervisor-app.conf | 73 --- dashboard/conf/uwsgi.ini | 45 -- dashboard/conf/uwsgi_params | 16 - dashboard/django/collector.py | 473 --------------- dashboard/django/collector.sh | 23 - dashboard/django/dashboard/__init__.py | 19 - dashboard/django/dashboard/settings.py | 188 ------ dashboard/django/dashboard/urls.py | 45 -- dashboard/django/dashboard/wsgi.py | 35 -- dashboard/django/manage.py | 41 -- dashboard/django/stats/__init__.py | 19 - dashboard/django/stats/admin.py | 34 -- dashboard/django/stats/apps.py | 26 - .../django/stats/migrations/0001_initial.py | 202 ------- .../0002_support_deleted_objects.py | 53 -- .../django/stats/migrations/0003_updates.py | 28 - dashboard/django/stats/migrations/__init__.py | 18 - dashboard/django/stats/models.py | 216 ------- .../django/stats/static/stats/additional.css | 113 ---- .../django/stats/templates/stats/base.html | 100 ---- .../django/stats/templates/stats/broker.html | 71 --- .../django/stats/templates/stats/brokers.html | 90 --- .../stats/templates/stats/clusters.html | 106 ---- .../django/stats/templates/stats/home.html | 74 --- .../stats/templates/stats/messages.html | 103 ---- .../stats/templates/stats/namespace.html | 101 ---- .../stats/templates/stats/property.html | 72 --- .../django/stats/templates/stats/topic.html | 216 ------- .../django/stats/templates/stats/topics.html | 91 --- .../django/stats/templatetags/__init__.py | 19 - .../django/stats/templatetags/stats_extras.py | 68 --- dashboard/django/stats/templatetags/table.py | 91 --- dashboard/django/stats/tests.py | 22 - dashboard/django/stats/urls.py | 41 -- dashboard/django/stats/views.py | 553 ------------------ dashboard/django/utils/__init__.py | 19 - dashboard/django/utils/import_utils.py | 27 - dashboard/init-postgres.sh | 32 - dashboard/pom.xml | 103 ---- dashboard/requirements.txt | 8 - dashboard/start.sh | 27 - docker/publish.sh | 4 - pom.xml | 1 - 47 files changed, 3863 deletions(-) delete mode 100644 dashboard/Dockerfile delete mode 100644 dashboard/README.md delete mode 100644 dashboard/conf/nginx-app.conf delete mode 100644 dashboard/conf/postgresql.conf delete mode 100644 dashboard/conf/supervisor-app.conf delete mode 100644 dashboard/conf/uwsgi.ini delete mode 100644 dashboard/conf/uwsgi_params delete mode 100755 dashboard/django/collector.py delete mode 100755 dashboard/django/collector.sh delete mode 100644 dashboard/django/dashboard/__init__.py delete mode 100644 dashboard/django/dashboard/settings.py delete mode 100644 dashboard/django/dashboard/urls.py delete mode 100644 dashboard/django/dashboard/wsgi.py delete mode 100755 dashboard/django/manage.py delete mode 100644 dashboard/django/stats/__init__.py delete mode 100644 dashboard/django/stats/admin.py delete mode 100644 dashboard/django/stats/apps.py delete mode 100644 dashboard/django/stats/migrations/0001_initial.py delete mode 100644 dashboard/django/stats/migrations/0002_support_deleted_objects.py delete mode 100644 dashboard/django/stats/migrations/0003_updates.py delete mode 100644 dashboard/django/stats/migrations/__init__.py delete mode 100644 dashboard/django/stats/models.py delete mode 100644 dashboard/django/stats/static/stats/additional.css delete mode 100644 dashboard/django/stats/templates/stats/base.html delete mode 100644 dashboard/django/stats/templates/stats/broker.html delete mode 100644 dashboard/django/stats/templates/stats/brokers.html delete mode 100644 dashboard/django/stats/templates/stats/clusters.html delete mode 100644 dashboard/django/stats/templates/stats/home.html delete mode 100644 dashboard/django/stats/templates/stats/messages.html delete mode 100644 dashboard/django/stats/templates/stats/namespace.html delete mode 100644 dashboard/django/stats/templates/stats/property.html delete mode 100644 dashboard/django/stats/templates/stats/topic.html delete mode 100644 dashboard/django/stats/templates/stats/topics.html delete mode 100644 dashboard/django/stats/templatetags/__init__.py delete mode 100644 dashboard/django/stats/templatetags/stats_extras.py delete mode 100644 dashboard/django/stats/templatetags/table.py delete mode 100644 dashboard/django/stats/tests.py delete mode 100644 dashboard/django/stats/urls.py delete mode 100644 dashboard/django/stats/views.py delete mode 100644 dashboard/django/utils/__init__.py delete mode 100644 dashboard/django/utils/import_utils.py delete mode 100755 dashboard/init-postgres.sh delete mode 100644 dashboard/pom.xml delete mode 100644 dashboard/requirements.txt delete mode 100755 dashboard/start.sh diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile deleted file mode 100644 index ab737d37f085f..0000000000000 --- a/dashboard/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -FROM python:3.7-stretch - -LABEL maintainer="Apache Pulsar " - -RUN bash -c "echo deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main >> /etc/apt/sources.list.d/pgdg.list" -RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - - -RUN apt-get update -RUN apt-get -y install postgresql-11 postgresql-contrib libpq-dev sudo nginx supervisor - -# Postgres configuration -COPY conf/postgresql.conf /etc/postgresql/11/main/ - -# Configure nginx and supervisor -RUN echo "daemon off;" >> /etc/nginx/nginx.conf -COPY conf/nginx-app.conf /etc/nginx/sites-available/default -COPY conf/supervisor-app.conf /etc/supervisor/conf.d/ - -# Copy web-app sources -RUN mkdir /pulsar -COPY . /pulsar/ - -# Python dependencies -RUN pip install -r /pulsar/requirements.txt - -# Collect all static files needed by Django in a -# single place. Needed to run the app outside the -# Django test web server -RUN cd /pulsar/django && ./manage.py collectstatic --no-input - -RUN mkdir /data - -EXPOSE 80 - -CMD ["/pulsar/start.sh"] diff --git a/dashboard/README.md b/dashboard/README.md deleted file mode 100644 index bf9f84cd77279..0000000000000 --- a/dashboard/README.md +++ /dev/null @@ -1,28 +0,0 @@ - - -# Pulsar Dashboard - -Pulsar dashboard is a web application that monitors -a complete instance of Pulsar and gives insights -about about all the topics. - -See [site2/docs/administration-dashboard.md](https://github.com/apache/pulsar/blob/master/site2/docs/administration-dashboard.md) for more information. diff --git a/dashboard/conf/nginx-app.conf b/dashboard/conf/nginx-app.conf deleted file mode 100644 index 6f57c1019c6cb..0000000000000 --- a/dashboard/conf/nginx-app.conf +++ /dev/null @@ -1,37 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -upstream django { - server unix:/tmp/uwsgi.sock; -} - -server { - listen 80 default_server; - - charset utf-8; - - location /static { - alias /pulsar/django/static; - } - - location / { - uwsgi_pass django; - include /pulsar/conf/uwsgi_params; - } -} diff --git a/dashboard/conf/postgresql.conf b/dashboard/conf/postgresql.conf deleted file mode 100644 index 85dfe1b1bdfdd..0000000000000 --- a/dashboard/conf/postgresql.conf +++ /dev/null @@ -1,38 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# Relax durability to increase write throughput -fsync = off -full_page_writes = off -synchronous_commit = off - -# Default configs -data_directory = '/data' -hba_file = '/etc/postgresql/11/main/pg_hba.conf' -ident_file = '/etc/postgresql/11/main/pg_ident.conf' -external_pid_file = '/var/run/postgresql/11-main.pid' - -port = 5432 -max_connections = 100 - -datestyle = 'iso, mdy' -default_text_search_config = 'pg_catalog.english' -stats_temp_directory = '/var/run/postgresql/11-main.pg_stat_tmp' -timezone = 'UTC' -log_timezone = 'UTC' diff --git a/dashboard/conf/supervisor-app.conf b/dashboard/conf/supervisor-app.conf deleted file mode 100644 index 1a435ccd3c9ff..0000000000000 --- a/dashboard/conf/supervisor-app.conf +++ /dev/null @@ -1,73 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -[program:uwsgi] -command=/usr/local/bin/uwsgi --ini /pulsar/conf/uwsgi.ini -autostart=true -autorestart=true -startsecs=5 -stopasgroup=true -killasgroup=true -stopsignal=TERM -log_stdout=true -log_stderr=true -priority=500 -stdout_logfile=/var/log/supervisor/uwsgi.log -stderr_logfile=/var/log/supervisor/uwsgi-error.log -stdout_logfile_maxbytes=50MB -stdout_logfile_backups=2 -stderr_logfile_maxbytes=50MB -stderr_logfile_backups=2 - - -[program:nginx] -command=/usr/sbin/nginx -autostart=true -autorestart=true -startsecs=5 -stopasgroup=true -killasgroup=true -stopsignal=TERM -log_stdout=true -log_stderr=true -priority=500 -stdout_logfile=/var/log/supervisor/nginx.log -stderr_logfile=/var/log/supervisor/nginx-error.log -stdout_logfile_maxbytes=50MB -stdout_logfile_backups=2 -stderr_logfile_maxbytes=50MB -stderr_logfile_backups=2 - -[program:collector] -command=/pulsar/django/collector.sh -autostart=true -autorestart=true -startsecs=5 -stopasgroup=true -killasgroup=true -stopsignal=TERM -log_stdout=true -log_stderr=true -priority=500 -stdout_logfile=/var/log/supervisor/collector.log -stderr_logfile=/var/log/supervisor/collector-error.log -stdout_logfile_maxbytes=50MB -stdout_logfile_backups=2 -stderr_logfile_maxbytes=50MB -stderr_logfile_backups=2 diff --git a/dashboard/conf/uwsgi.ini b/dashboard/conf/uwsgi.ini deleted file mode 100644 index f027436aad8f9..0000000000000 --- a/dashboard/conf/uwsgi.ini +++ /dev/null @@ -1,45 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# django.ini file -[uwsgi] - -uid = www-data -gid = www-data - -# master -master = true - -# maximum number of processes -processes = 10 - -# the socket (use the full path to be safe) -socket = /tmp/uwsgi.sock - -# with appropriate permissions - *may* be needed -# chmod-socket = 664 - -# the base directory -chdir = /pulsar/django - -# Django's wsgi file -module = dashboard.wsgi - -# clear environment on exit -vacuum = true diff --git a/dashboard/conf/uwsgi_params b/dashboard/conf/uwsgi_params deleted file mode 100644 index f539451b6f581..0000000000000 --- a/dashboard/conf/uwsgi_params +++ /dev/null @@ -1,16 +0,0 @@ - -uwsgi_param QUERY_STRING $query_string; -uwsgi_param REQUEST_METHOD $request_method; -uwsgi_param CONTENT_TYPE $content_type; -uwsgi_param CONTENT_LENGTH $content_length; - -uwsgi_param REQUEST_URI $request_uri; -uwsgi_param PATH_INFO $document_uri; -uwsgi_param DOCUMENT_ROOT $document_root; -uwsgi_param SERVER_PROTOCOL $server_protocol; -uwsgi_param HTTPS $https if_not_empty; - -uwsgi_param REMOTE_ADDR $remote_addr; -uwsgi_param REMOTE_PORT $remote_port; -uwsgi_param SERVER_PORT $server_port; -uwsgi_param SERVER_NAME $server_name; diff --git a/dashboard/django/collector.py b/dashboard/django/collector.py deleted file mode 100755 index ba862fd2fa9b2..0000000000000 --- a/dashboard/django/collector.py +++ /dev/null @@ -1,473 +0,0 @@ -#!/usr/bin/env python -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -import logging -import os -import django -import requests -import pytz -import multiprocessing -import traceback -import sys -from django.utils import timezone -from django.utils.dateparse import parse_datetime -from django.db import connection -import time -import argparse -import random - -current_milli_time = lambda: int(round(time.time() * 1000)) -logger = logging.getLogger(__name__) - -def get(base_url, path): - if base_url.endswith('/'): path = path[1:] - return requests.get(base_url + path, - headers=http_headers, - proxies=http_proxyes, - ).json() - - -def parse_date(d): - if d: - dt = parse_datetime(d) - if dt.tzinfo: - # There is already the timezone set - return dt - else: - # Assume UTC if no timezone - return pytz.timezone('UTC').localize(parse_datetime(d)) - else: - return None - - -# Fetch the stats for a given broker -def fetch_broker_stats(cluster, broker_url, timestamp): - try: - _fetch_broker_stats(cluster, broker_url, timestamp) - except Exception as e: - traceback.print_exc(file=sys.stderr) - raise e - - -def _fetch_broker_stats(cluster, broker_host_port, timestamp): - broker_url = 'http://%s/' % broker_host_port - logger.info('Getting stats for %s' % broker_host_port) - - broker, _ = Broker.objects.get_or_create( - url=broker_host_port, - cluster=cluster - ) - active_broker = ActiveBroker(broker=broker, timestamp=timestamp) - active_broker.save() - - # Get topics stats - topics_stats = get(broker_url, '/admin/v2/broker-stats/topics') - - clusters = dict((cluster.name, cluster) for cluster in Cluster.objects.all()) - - db_create_bundles = [] - db_update_bundles = [] - db_create_topics = [] - db_update_topics = [] - db_create_subscriptions = [] - db_update_subscriptions = [] - db_create_consumers = [] - db_update_consumers = [] - db_replication = [] - - for namespace_name, bundles_stats in topics_stats.items(): - property_name = namespace_name.split('/')[0] - property, _ = Property.objects.get_or_create(name=property_name) - - namespace, _ = Namespace.objects.get_or_create( - name=namespace_name, - property=property, - timestamp=timestamp) - namespace.clusters.add(cluster) - namespace.save() - - for bundle_range, topics_stats in bundles_stats.items(): - - bundle = Bundle.objects.filter( - cluster_id=cluster.id, - namespace_id=namespace.id, - range=bundle_range) - if bundle: - temp_bundle = bundle.first() - temp_bundle.timestame = timestamp - db_update_bundles.append(temp_bundle) - bundle = temp_bundle - else: - bundle = Bundle( - broker=broker, - namespace=namespace, - range=bundle_range, - cluster=cluster, - timestamp=timestamp) - db_create_bundles.append(bundle) - - for topic_name, stats in topics_stats['persistent'].items(): - topic = Topic.objects.filter( - cluster_id=cluster.id, - bundle_id=bundle.id, - namespace_id=namespace.id, - broker_id=broker.id, - name=topic_name) - if topic: - temp_topic = topic.first() - temp_topic.timestamp = timestamp - temp_topic.averageMsgSize = stats['averageMsgSize'] - temp_topic.msgRateIn = stats['msgRateIn'] - temp_topic.msgRateOut = stats['msgRateOut'] - temp_topic.msgThroughputIn = stats['msgThroughputIn'] - temp_topic.msgThroughputOut = stats['msgThroughputOut'] - temp_topic.pendingAddEntriesCount = stats['pendingAddEntriesCount'] - temp_topic.producerCount = stats['producerCount'] - temp_topic.storageSize = stats['storageSize'] - db_update_topics.append(temp_topic) - topic = temp_topic - else: - topic = Topic( - broker=broker, - active_broker=active_broker, - name=topic_name, - namespace=namespace, - bundle=bundle, - cluster=cluster, - timestamp=timestamp, - averageMsgSize=stats['averageMsgSize'], - msgRateIn=stats['msgRateIn'], - msgRateOut=stats['msgRateOut'], - msgThroughputIn=stats['msgThroughputIn'], - msgThroughputOut=stats['msgThroughputOut'], - pendingAddEntriesCount=stats['pendingAddEntriesCount'], - producerCount=stats['producerCount'], - storageSize=stats['storageSize'] - ) - db_create_topics.append(topic) - totalBacklog = 0 - numSubscriptions = 0 - numConsumers = 0 - - for subscription_name, subStats in stats['subscriptions'].items(): - numSubscriptions += 1 - subscription = Subscription.objects.filter( - topic_id=topic.id, - namespace_id=namespace.id, - name=subscription_name) - if subscription: - temp_subscription = subscription.first() - temp_subscription.timestamp = timestamp - temp_subscription.msgBacklog = subStats['msgBacklog'] - temp_subscription.msgRateExpired = subStats['msgRateExpired'] - temp_subscription.msgRateOut = subStats['msgRateOut'] - temp_subscription.msgRateRedeliver = subStats.get('msgRateRedeliver', 0) - temp_subscription.msgThroughputOut = subStats['msgThroughputOut'] - temp_subscription.subscriptionType = subStats['type'][0] - temp_subscription.unackedMessages = subStats.get('unackedMessages', 0) - db_update_subscriptions.append(temp_subscription) - subscription = temp_subscription - else: - subscription = Subscription( - topic=topic, - name=subscription_name, - namespace=namespace, - timestamp=timestamp, - msgBacklog=subStats['msgBacklog'], - msgRateExpired=subStats['msgRateExpired'], - msgRateOut=subStats['msgRateOut'], - msgRateRedeliver=subStats.get('msgRateRedeliver', 0), - msgThroughputOut=subStats['msgThroughputOut'], - subscriptionType=subStats['type'][0], - unackedMessages=subStats.get('unackedMessages', 0), - ) - db_create_subscriptions.append(subscription) - - totalBacklog += subStats['msgBacklog'] - - for consStats in subStats['consumers']: - numConsumers += 1 - consumer = Consumer.objects.filter( - subscription_id=subscription.id, - consumerName=consStats.get('consumerName')) - if consumer: - temp_consumer = consumer.first() - temp_consumer.timestamp = timestamp - temp_consumer.address = consStats['address'] - temp_consumer.availablePermits = consStats.get('availablePermits', 0) - temp_consumer.connectedSince = parse_date(consStats.get('connectedSince')) - temp_consumer.msgRateOut = consStats.get('msgRateOut', 0) - temp_consumer.msgRateRedeliver = consStats.get('msgRateRedeliver', 0) - temp_consumer.msgThroughputOut = consStats.get('msgThroughputOut', 0) - temp_consumer.unackedMessages = consStats.get('unackedMessages', 0) - temp_consumer.blockedConsumerOnUnackedMsgs = consStats.get('blockedConsumerOnUnackedMsgs', - False) - db_update_consumers.append(temp_consumer) - consumer = temp_consumer - else: - consumer = Consumer( - subscription=subscription, - timestamp=timestamp, - address=consStats['address'], - availablePermits=consStats.get('availablePermits', 0), - connectedSince=parse_date(consStats.get('connectedSince')), - consumerName=consStats.get('consumerName'), - msgRateOut=consStats.get('msgRateOut', 0), - msgRateRedeliver=consStats.get('msgRateRedeliver', 0), - msgThroughputOut=consStats.get('msgThroughputOut', 0), - unackedMessages=consStats.get('unackedMessages', 0), - blockedConsumerOnUnackedMsgs=consStats.get('blockedConsumerOnUnackedMsgs', False) - ) - db_create_consumers.append(consumer) - - topic.backlog = totalBacklog - topic.subscriptionCount = numSubscriptions - topic.consumerCount = numConsumers - - replicationMsgIn = 0 - replicationMsgOut = 0 - replicationThroughputIn = 0 - replicationThroughputOut = 0 - replicationBacklog = 0 - - for remote_cluster, replStats in stats['replication'].items(): - replication = Replication( - timestamp=timestamp, - topic=topic, - local_cluster=cluster, - remote_cluster=clusters[remote_cluster], - - msgRateIn=replStats['msgRateIn'], - msgRateOut=replStats['msgRateOut'], - msgThroughputIn=replStats['msgThroughputIn'], - msgThroughputOut=replStats['msgThroughputOut'], - replicationBacklog=replStats['replicationBacklog'], - connected=replStats['connected'], - replicationDelayInSeconds=replStats['replicationDelayInSeconds'], - msgRateExpired=replStats['msgRateExpired'], - - inboundConnectedSince=parse_date(replStats.get('inboundConnectedSince')), - outboundConnectedSince=parse_date(replStats.get('outboundConnectedSince')), - ) - - db_replication.append(replication) - - replicationMsgIn += replication.msgRateIn - replicationMsgOut += replication.msgRateOut - replicationThroughputIn += replication.msgThroughputIn - replicationThroughputOut += replication.msgThroughputOut - replicationBacklog += replication.replicationBacklog - - topic.replicationRateIn = replicationMsgIn - topic.replicationRateOut = replicationMsgOut - topic.replicationThroughputIn = replicationThroughputIn - topic.replicationThroughputOut = replicationThroughputOut - topic.replicationBacklog = replicationBacklog - topic.localRateIn = topic.msgRateIn - replicationMsgIn - topic.localRateOut = topic.msgRateOut - replicationMsgOut - topic.localThroughputIn = topic.msgThroughputIn - replicationThroughputIn - topic.localThroughputOut = topic.msgThroughputIn - replicationThroughputOut - - if connection.vendor == 'postgresql': - # Bulk insert into db - Bundle.objects.bulk_create(db_create_bundles, batch_size=10000) - - # Trick to refresh primary keys after previous bulk import - for topic in db_create_topics: topic.bundle = topic.bundle - Topic.objects.bulk_create(db_create_topics, batch_size=10000) - - for subscription in db_create_subscriptions: subscription.topic = subscription.topic - Subscription.objects.bulk_create(db_create_subscriptions, batch_size=10000) - - for consumer in db_create_consumers: consumer.subscription = consumer.subscription - Consumer.objects.bulk_create(db_create_consumers, batch_size=10000) - - for replication in db_replication: replication.topic = replication.topic - Replication.objects.bulk_create(db_replication, batch_size=10000) - - update_or_create_object( - db_update_bundles, - db_update_topics, - db_update_consumers, - db_update_subscriptions) - - else: - update_or_create_object( - db_create_bundles, - db_create_topics, - db_create_consumers, - db_create_subscriptions) - update_or_create_object( - db_update_bundles, - db_update_topics, - db_update_consumers, - db_update_subscriptions) - - for replication in db_replication: - replication.topic = replication.topic - replication.save() - - tenants = get(broker_url, '/admin/v2/tenants') - for tenant_name in tenants: - namespaces = get(broker_url, '/admin/v2/namespaces/' + tenant_name) - for namespace_name in namespaces: - property, _ = Property.objects.get_or_create(name=tenant_name) - namespace, _ = Namespace.objects.get_or_create( - name=namespace_name, - property=property, - timestamp=timestamp) - namespace.clusters.add(cluster) - namespace.save() - - -def update_or_create_object(db_bundles, db_topics, db_consumers, db_subscriptions): - # For DB providers we have to insert or update one by one - # to be able to retrieve the PK of the newly inserted records - for bundle in db_bundles: - bundle.save() - - for topic in db_topics: - topic.bundle = topic.bundle - topic.save() - - for subscription in db_subscriptions: - subscription.topic = subscription.topic - subscription.save() - - for consumer in db_consumers: - consumer.subscription = consumer.subscription - consumer.save() - - -def fetch_stats(): - logger.info("Begin fetch stats") - timestamp = current_milli_time() - - pool = multiprocessing.Pool(args.workers) - - futures = [] - - for cluster_name in get(args.serviceUrl, '/admin/v2/clusters'): - if cluster_name == 'global': continue - - cluster_url = get(args.serviceUrl, '/admin/v2/clusters/' + cluster_name)['serviceUrl'] - if cluster_url.find(',')>=0: - cluster_url_list = cluster_url.split(',') - index = random.randint(0,len(cluster_url_list)-1) - if index==0: - cluster_url = cluster_url_list[index] - else: - protocol = ("https://" if(cluster_url.find("https")>=0) else "http://") - cluster_url = protocol+cluster_url_list[index] - - logger.info('Cluster:{} -> {}'.format(cluster_name, cluster_url)) - cluster, created = Cluster.objects.get_or_create(name=cluster_name) - if cluster_url != cluster.serviceUrl: - cluster.serviceUrl = cluster_url - cluster.save() - - # Get the list of brokers for each cluster - for cluster in Cluster.objects.all(): - try: - for broker_host_port in get(cluster.serviceUrl, '/admin/v2/brokers/' + cluster.name): - f = pool.apply_async(fetch_broker_stats, (cluster, broker_host_port, timestamp)) - futures.append(f) - except Exception as e: - logger.error('ERROR: ', e) - - pool.close() - - for f in futures: - f.get() - - pool.join() - - # Update Timestamp in DB - latest, _ = LatestTimestamp.objects.get_or_create(name='latest') - latest.timestamp = timestamp - latest.save() - logger.info("Finished fetch stats") - - -def purge_db(): - logger.info("Begin purge db") - now = current_milli_time() - ttl_minutes = args.purge - threshold = now - (ttl_minutes * 60 * 1000) - - Bundle.objects.filter(timestamp__lt=threshold).delete() - Topic.objects.filter(timestamp__lt=threshold).delete() - Subscription.objects.filter(timestamp__lt=threshold).delete() - Consumer.objects.filter(timestamp__lt=threshold).delete() - Namespace.objects.filter(timestamp__lt=threshold).delete() - logger.info("Finished purge db") - - -def collect_and_purge(): - logger.info('Starting stats collection') - fetch_stats() - purge_db() - logger.info('Finished collecting stats') - - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard.settings") - django.setup() - - from stats.models import * - - parser = argparse.ArgumentParser(description='Pulsar Stats collector') - parser.add_argument(action="store", dest="serviceUrl", help='Service URL of one cluster in the Pulsar instance') - - parser.add_argument('--proxy', action='store', - help="Connect using an HTTP proxy", dest="proxy") - parser.add_argument('--header', action="append", dest="header", - help='Add an additional HTTP header to all requests') - parser.add_argument('--purge', action="store", dest="purge", type=int, default=60, - help='Purge statistics older than PURGE minutes. (default: 60min)') - - parser.add_argument('--workers', action="store", dest="workers", type=int, default=64, - help='Number of worker processes to be used to fetch the stats (default: 64)') - - global args - args = parser.parse_args(sys.argv[1:]) - - global http_headers - http_headers = {} - jwt_token = os.getenv("JWT_TOKEN", None) - if jwt_token is not None: - http_headers = { - "Authorization": "Bearer {}".format(jwt_token)} - if args.header: - http_headers = dict(x.split(': ') for x in args.header) - logger.info(http_headers) - - global http_proxyes - http_proxyes = { "no_proxy": os.getenv("NO_PROXY", "") } - if args.proxy: - http_proxyes['http'] = args.proxy - http_proxyes['https'] = args.proxy - - # Schedule a new collection every 1min - while True: - p = multiprocessing.Process(target=collect_and_purge) - p.start() - time.sleep(int(os.getenv("COLLECTION_INTERVAL", 60))) diff --git a/dashboard/django/collector.sh b/dashboard/django/collector.sh deleted file mode 100755 index d2439fa4d7768..0000000000000 --- a/dashboard/django/collector.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -cd /pulsar/django -./collector.py $SERVICE_URL diff --git a/dashboard/django/dashboard/__init__.py b/dashboard/django/dashboard/__init__.py deleted file mode 100644 index d8a500d9d8d41..0000000000000 --- a/dashboard/django/dashboard/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - diff --git a/dashboard/django/dashboard/settings.py b/dashboard/django/dashboard/settings.py deleted file mode 100644 index 5b229f688305f..0000000000000 --- a/dashboard/django/dashboard/settings.py +++ /dev/null @@ -1,188 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -""" -Django settings for dashboard project. - -Generated by 'django-admin startproject' using Django 1.10.3. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.10/ref/settings/ -""" - -import os - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'kxmt78byexqz$9m!9o3f7!h3d$lz@2fe9-+te7!=0rfwm2afcb' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ['*'] - -# Application definition - -INSTALLED_APPS = [ - 'stats.apps.StatsConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.humanize', -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'dashboard.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'django.template.context_processors.request', - ], - }, - }, -] - -WSGI_APPLICATION = 'dashboard.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), -# } -# } - -# - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'USER': 'docker', - 'PASSWORD': 'docker', - 'HOST': 'localhost', - 'NAME': 'pulsar_dashboard', - } -} - -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.mysql', -# 'USER' : 'root', -# 'HOST' : 'localhost', -# 'NAME' : 'pulsar_dashboard', -# } -# } - - -# Password validation -# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - -# Internationalization -# https://docs.djangoproject.com/en/1.10/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.10/howto/static-files/ - -STATIC_URL = '/static/' - -STATIC_ROOT = os.path.join(BASE_DIR, "static/") - -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "verbose": { - "format": "[%(asctime)s.%(msecs)03dZ] %(levelname)s %(process)d [%(threadName)-10s] %(module)s:%(lineno)d %(message)s", - "datefmt": "%Y-%m-%dT%H:%M:%S" - } - }, - "handlers": { - "console": { - "level": "DEBUG", - "class": "logging.StreamHandler", - "formatter": "verbose", - "stream": "ext://sys.stdout" - }, - }, - "loggers": { - "": { - "handlers": ["console"], - "level": "INFO" - } - } -} - -SERVICE_URL = os.getenv("SERVICE_URL") diff --git a/dashboard/django/dashboard/urls.py b/dashboard/django/dashboard/urls.py deleted file mode 100644 index 71b1f1177ebbf..0000000000000 --- a/dashboard/django/dashboard/urls.py +++ /dev/null @@ -1,45 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -"""dashboard URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import include, url -from django.contrib import admin - -from stats import views - - -urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^stats/', include('stats.urls')), - url(r'^$', views.home, name='home'), -] diff --git a/dashboard/django/dashboard/wsgi.py b/dashboard/django/dashboard/wsgi.py deleted file mode 100644 index 9bdcc640255bb..0000000000000 --- a/dashboard/django/dashboard/wsgi.py +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -""" -WSGI config for dashboard project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard.settings") - -application = get_wsgi_application() diff --git a/dashboard/django/manage.py b/dashboard/django/manage.py deleted file mode 100755 index 44022a4ebecaa..0000000000000 --- a/dashboard/django/manage.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard.settings") - try: - from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise - execute_from_command_line(sys.argv) diff --git a/dashboard/django/stats/__init__.py b/dashboard/django/stats/__init__.py deleted file mode 100644 index d8a500d9d8d41..0000000000000 --- a/dashboard/django/stats/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - diff --git a/dashboard/django/stats/admin.py b/dashboard/django/stats/admin.py deleted file mode 100644 index b1bb3607998ba..0000000000000 --- a/dashboard/django/stats/admin.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from django.contrib import admin - -# Register your models here. -from .models import * -admin.site.register(Cluster) -admin.site.register(Property) -admin.site.register(Namespace) -admin.site.register(Bundle) - - -class TopicAdmin(admin.ModelAdmin): - list_filter = ('cluster', 'namespace__property__name') - - -admin.site.register(Topic, TopicAdmin) diff --git a/dashboard/django/stats/apps.py b/dashboard/django/stats/apps.py deleted file mode 100644 index 5941cbeb72909..0000000000000 --- a/dashboard/django/stats/apps.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from __future__ import unicode_literals - -from django.apps import AppConfig - - -class StatsConfig(AppConfig): - name = 'stats' diff --git a/dashboard/django/stats/migrations/0001_initial.py b/dashboard/django/stats/migrations/0001_initial.py deleted file mode 100644 index ebb32cad91521..0000000000000 --- a/dashboard/django/stats/migrations/0001_initial.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.5 on 2017-02-21 21:20 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='ActiveBroker', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.BigIntegerField(db_index=True)), - ], - ), - migrations.CreateModel( - name='Broker', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('url', models.URLField(db_index=True)), - ], - ), - migrations.CreateModel( - name='Bundle', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.BigIntegerField(db_index=True)), - ('range', models.CharField(max_length=200)), - ('broker', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Broker')), - ], - ), - migrations.CreateModel( - name='Cluster', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), - ('serviceUrl', models.URLField()), - ], - ), - migrations.CreateModel( - name='Consumer', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.BigIntegerField(db_index=True)), - ('address', models.CharField(max_length=64, null=True)), - ('availablePermits', models.IntegerField(default=0)), - ('connectedSince', models.DateTimeField(null=True)), - ('consumerName', models.CharField(max_length=64, null=True)), - ('msgRateOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgRateRedeliver', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgThroughputOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('unackedMessages', models.BigIntegerField(default=0)), - ('blockedConsumerOnUnackedMsgs', models.BooleanField(default=False)), - ], - ), - migrations.CreateModel( - name='LatestTimestamp', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=10, unique=True)), - ('timestamp', models.BigIntegerField(default=0)), - ], - ), - migrations.CreateModel( - name='Namespace', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), - ('clusters', models.ManyToManyField(to='stats.Cluster')), - ], - ), - migrations.CreateModel( - name='Property', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), - ], - options={ - 'verbose_name_plural': 'properties', - }, - ), - migrations.CreateModel( - name='Replication', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.BigIntegerField(db_index=True)), - ('msgRateIn', models.DecimalField(decimal_places=1, max_digits=12)), - ('msgThroughputIn', models.DecimalField(decimal_places=1, max_digits=12)), - ('msgRateOut', models.DecimalField(decimal_places=1, max_digits=12)), - ('msgThroughputOut', models.DecimalField(decimal_places=1, max_digits=12)), - ('msgRateExpired', models.DecimalField(decimal_places=1, max_digits=12)), - ('replicationBacklog', models.BigIntegerField(default=0)), - ('connected', models.BooleanField(default=False)), - ('replicationDelayInSeconds', models.IntegerField(default=0)), - ('inboundConnectedSince', models.DateTimeField(null=True)), - ('outboundConnectedSince', models.DateTimeField(null=True)), - ('local_cluster', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Cluster')), - ('remote_cluster', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='remote_cluster', to='stats.Cluster')), - ], - ), - migrations.CreateModel( - name='Subscription', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('timestamp', models.BigIntegerField(db_index=True)), - ('msgBacklog', models.BigIntegerField(default=0)), - ('msgRateExpired', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgRateOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgRateRedeliver', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgThroughputOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('subscriptionType', models.CharField(choices=[('N', 'Not connected'), ('E', 'Exclusive'), ('S', 'Shared'), ('F', 'Failover')], default='N', max_length=1)), - ('unackedMessages', models.BigIntegerField(default=0)), - ('namespace', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Namespace')), - ], - ), - migrations.CreateModel( - name='Topic', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(db_index=True, max_length=1024)), - ('timestamp', models.BigIntegerField(db_index=True)), - ('averageMsgSize', models.IntegerField(default=0)), - ('msgRateIn', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgRateOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgThroughputIn', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('msgThroughputOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('pendingAddEntriesCount', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('producerCount', models.IntegerField(default=0)), - ('subscriptionCount', models.IntegerField(default=0)), - ('consumerCount', models.IntegerField(default=0)), - ('storageSize', models.BigIntegerField(default=0)), - ('backlog', models.BigIntegerField(default=0)), - ('localRateIn', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('localRateOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('localThroughputIn', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('localThroughputOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('replicationRateIn', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('replicationRateOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('replicationThroughputIn', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('replicationThroughputOut', models.DecimalField(decimal_places=1, default=0, max_digits=12)), - ('replicationBacklog', models.BigIntegerField(default=0)), - ('active_broker', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.ActiveBroker')), - ('broker', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Broker')), - ('bundle', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Bundle')), - ('cluster', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Cluster')), - ('namespace', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Namespace')), - ], - ), - migrations.AddField( - model_name='subscription', - name='topic', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Topic'), - ), - migrations.AddField( - model_name='replication', - name='topic', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Topic'), - ), - migrations.AddField( - model_name='namespace', - name='property', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Property'), - ), - migrations.AddField( - model_name='consumer', - name='subscription', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Subscription'), - ), - migrations.AddField( - model_name='bundle', - name='cluster', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Cluster'), - ), - migrations.AddField( - model_name='bundle', - name='namespace', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Namespace'), - ), - migrations.AddField( - model_name='broker', - name='cluster', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Cluster'), - ), - migrations.AddField( - model_name='activebroker', - name='broker', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='stats.Broker'), - ), - migrations.AlterIndexTogether( - name='topic', - index_together=set([('name', 'cluster', 'timestamp')]), - ), - ] diff --git a/dashboard/django/stats/migrations/0002_support_deleted_objects.py b/dashboard/django/stats/migrations/0002_support_deleted_objects.py deleted file mode 100644 index 6e04eecf6ead0..0000000000000 --- a/dashboard/django/stats/migrations/0002_support_deleted_objects.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.20 on 2019-06-03 13:24 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('stats', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='namespace', - name='deleted', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='namespace', - name='timestamp', - field=models.BigIntegerField(db_index=True, default=0), - preserve_default=False, - ), - migrations.AddField( - model_name='subscription', - name='deleted', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='topic', - name='deleted', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='namespace', - name='name', - field=models.CharField(max_length=200), - ), - migrations.AlterIndexTogether( - name='namespace', - index_together={('name', 'timestamp', 'deleted')}, - ), - migrations.AlterIndexTogether( - name='subscription', - index_together={('name', 'topic', 'timestamp', 'deleted')}, - ), - migrations.AlterIndexTogether( - name='topic', - index_together={('name', 'cluster', 'timestamp', 'deleted')}, - ), - ] diff --git a/dashboard/django/stats/migrations/0003_updates.py b/dashboard/django/stats/migrations/0003_updates.py deleted file mode 100644 index 5dbaac6a74a3c..0000000000000 --- a/dashboard/django/stats/migrations/0003_updates.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.29 on 2020-10-01 14:07 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('stats', '0002_support_deleted_objects'), - ] - - operations = [ - migrations.AlterField( - model_name='consumer', - name='consumerName', - field=models.CharField(max_length=256, null=True), - ), - migrations.AlterUniqueTogether( - name='subscription', - unique_together=set([('name', 'topic', 'timestamp')]), - ), - migrations.AlterIndexTogether( - name='subscription', - index_together=set([]), - ), - ] diff --git a/dashboard/django/stats/migrations/__init__.py b/dashboard/django/stats/migrations/__init__.py deleted file mode 100644 index 5fb255256df6d..0000000000000 --- a/dashboard/django/stats/migrations/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# \ No newline at end of file diff --git a/dashboard/django/stats/models.py b/dashboard/django/stats/models.py deleted file mode 100644 index 80904b37b73cb..0000000000000 --- a/dashboard/django/stats/models.py +++ /dev/null @@ -1,216 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from __future__ import unicode_literals - -from django.utils.encoding import python_2_unicode_compatible -from django.db.models import * -from django.urls import reverse - -# Used to store the latest -class LatestTimestamp(Model): - name = CharField(max_length=10, unique=True) - timestamp = BigIntegerField(default=0) - -@python_2_unicode_compatible -class Cluster(Model): - name = CharField(max_length=200, unique=True) - serviceUrl = URLField() - - def __str__(self): - return self.name - -@python_2_unicode_compatible -class Broker(Model): - url = URLField(db_index=True) - cluster = ForeignKey(Cluster, on_delete=SET_NULL, db_index=True, null=True) - - def __str__(self): - return self.url - -@python_2_unicode_compatible -class ActiveBroker(Model): - broker = ForeignKey(Broker, on_delete=SET_NULL, db_index=True, null=True) - timestamp = BigIntegerField(db_index=True) - - def __str__(self): - return self.broker.url - -@python_2_unicode_compatible -class Property(Model): - name = CharField(max_length=200, unique=True) - - def __str__(self): - return self.name - - class Meta: - verbose_name_plural = 'properties' - -@python_2_unicode_compatible -class Namespace(Model): - name = CharField(max_length=200) - property = ForeignKey(Property, on_delete=SET_NULL, db_index=True, null=True) - clusters = ManyToManyField(Cluster) - timestamp = BigIntegerField(db_index=True) - deleted = BooleanField(default=False) - - def is_global(self): - return self.name.split('/', 2)[1] == 'global' - - def is_v2(self): - return len(self.name.split('/', 2)) == 2 - - def __str__(self): - return self.name - - class Meta: - index_together = ('name', 'timestamp', 'deleted') - -@python_2_unicode_compatible -class Bundle(Model): - timestamp = BigIntegerField(db_index=True) - broker = ForeignKey(Broker, on_delete=SET_NULL, db_index=True, null=True) - namespace = ForeignKey(Namespace, on_delete=SET_NULL, db_index=True, null=True) - cluster = ForeignKey(Cluster, on_delete=SET_NULL, db_index=True, null=True) - range = CharField(max_length=200) - - def __str__(self): - return str(self.pk) + '--' + self.namespace.name + '/' + self.range - -@python_2_unicode_compatible -class Topic(Model): - name = CharField(max_length=1024, db_index=True) - active_broker = ForeignKey(ActiveBroker, on_delete=SET_NULL, db_index=True, null=True) - broker = ForeignKey(Broker, on_delete=SET_NULL, db_index=True, null=True) - namespace = ForeignKey(Namespace, on_delete=SET_NULL, db_index=True, null=True) - cluster = ForeignKey(Cluster, on_delete=SET_NULL, db_index=True, null=True) - bundle = ForeignKey(Bundle, on_delete=SET_NULL, db_index=True, null=True) - - timestamp = BigIntegerField(db_index=True) - deleted = BooleanField(default=False) - averageMsgSize = IntegerField(default=0) - msgRateIn = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgRateOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgThroughputIn = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgThroughputOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - pendingAddEntriesCount = DecimalField(max_digits = 12, decimal_places=1, default=0) - producerCount = IntegerField(default=0) - subscriptionCount = IntegerField(default=0) - consumerCount = IntegerField(default=0) - storageSize = BigIntegerField(default=0) - backlog = BigIntegerField(default=0) - - localRateIn = DecimalField(max_digits = 12, decimal_places=1, default=0) - localRateOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - localThroughputIn = DecimalField(max_digits = 12, decimal_places=1, default=0) - localThroughputOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - - replicationRateIn = DecimalField(max_digits = 12, decimal_places=1, default=0) - replicationRateOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - replicationThroughputIn = DecimalField(max_digits = 12, decimal_places=1, default=0) - replicationThroughputOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - replicationBacklog = BigIntegerField(default=0) - - def short_name(self): - return self.name.split('/', 5)[-1] - - def is_global(self): - return self.namespace.is_global() - - def is_v2(self): - return self.namespace.is_v2() - - def url_name(self): - return '/'.join(self.name.split('://', 1)) - - def get_absolute_url(self): - url = reverse('topic', args=[self.url_name()]) - if self.namespace.is_global(): - url += '?cluster=' + self.cluster.name - return url - - class Meta: - index_together = ('name', 'cluster', 'timestamp', 'deleted') - - def __str__(self): - return self.name - -@python_2_unicode_compatible -class Subscription(Model): - name = CharField(max_length=200) - topic = ForeignKey(Topic, on_delete=SET_NULL, null=True) - namespace = ForeignKey(Namespace, on_delete=SET_NULL, null=True, db_index=True) - - timestamp = BigIntegerField(db_index=True) - deleted = BooleanField(default=False) - msgBacklog = BigIntegerField(default=0) - msgRateExpired = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgRateOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgRateRedeliver = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgThroughputOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - - SUBSCRIPTION_TYPES = ( - ('N', 'Not connected'), - ('E', 'Exclusive'), - ('S', 'Shared'), - ('F', 'Failover'), - ) - subscriptionType = CharField(max_length=1, choices=SUBSCRIPTION_TYPES, default='N') - unackedMessages = BigIntegerField(default=0) - - class Meta: - unique_together = ('name', 'topic', 'timestamp') - - def __str__(self): - return self.name - - -class Consumer(Model): - timestamp = BigIntegerField(db_index=True) - subscription = ForeignKey(Subscription, on_delete=SET_NULL, db_index=True, null=True) - - address = CharField(max_length=64, null=True) - availablePermits = IntegerField(default=0) - connectedSince = DateTimeField(null=True) - consumerName = CharField(max_length=256, null=True) - msgRateOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgRateRedeliver = DecimalField(max_digits = 12, decimal_places=1, default=0) - msgThroughputOut = DecimalField(max_digits = 12, decimal_places=1, default=0) - unackedMessages = BigIntegerField(default=0) - blockedConsumerOnUnackedMsgs = BooleanField(default=False) - - -class Replication(Model): - timestamp = BigIntegerField(db_index=True) - topic = ForeignKey(Topic, on_delete=SET_NULL, null=True) - local_cluster = ForeignKey(Cluster, on_delete=SET_NULL, null=True) - remote_cluster = ForeignKey(Cluster, on_delete=SET_NULL, null=True, related_name='remote_cluster') - - msgRateIn = DecimalField(max_digits = 12, decimal_places=1) - msgThroughputIn = DecimalField(max_digits = 12, decimal_places=1) - msgRateOut = DecimalField(max_digits = 12, decimal_places=1) - msgThroughputOut = DecimalField(max_digits = 12, decimal_places=1) - msgRateExpired = DecimalField(max_digits = 12, decimal_places=1) - replicationBacklog = BigIntegerField(default=0) - - connected = BooleanField(default=False) - replicationDelayInSeconds = IntegerField(default=0) - - inboundConnectedSince = DateTimeField(null=True) - outboundConnectedSince = DateTimeField(null=True) diff --git a/dashboard/django/stats/static/stats/additional.css b/dashboard/django/stats/static/stats/additional.css deleted file mode 100644 index db1705bf38f71..0000000000000 --- a/dashboard/django/stats/static/stats/additional.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -.autoscroll { - overflow: auto; -} - -.em { - font-weight: bold; -} - -.tab { - border-bottom: 1px solid #79aec8; -} - -.tab span { - cursor: pointer; - display: inline-block; - float: left; - font-weight: bold; - height: 30px; - line-height: 30px; - padding: 0 15px; -} - -.tab span.active { - background-color: #79aec8; - color: #fff; -} - -.tab span.warn { - background-color: #c13b3b; -} - -.tab-content { - display: none -} - -.clearfix:after{ - content: ""; - display: table; - clear: both; -} - -.message { - max-width: 800px; -} - -.message-title { - padding-right: 25px; -} - -.message-content { - margin: 10px 0; - padding: 0 10px; - border-left: 5px solid #79aec8; - border-radius: 0 2px 2px 0; - background-color: #f2f2f2; - max-height: 400px; -} - -input.small-button { - padding: 2px; -} - -/*Create a red cross using class x*/ -.x{ - width: 20px; - height: 20px; - position:relative; - border-radius:2px; -} -.x::after,.x::before{ - position:absolute; - top:9px; - left:0px; - content:''; - display:block; - width:20px; - height:2px; - background-color:red; - -} -.x::after{ - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); -} -.x::before{ - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -ms-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); -} \ No newline at end of file diff --git a/dashboard/django/stats/templates/stats/base.html b/dashboard/django/stats/templates/stats/base.html deleted file mode 100644 index d798460badb43..0000000000000 --- a/dashboard/django/stats/templates/stats/base.html +++ /dev/null @@ -1,100 +0,0 @@ - -{% load static %} - - - -Pulsar Dashboard - - - - - - -{% block extrastyle %}{% endblock %} -{% block extrahead %}{% endblock %} -{% block blockbots %}{% endblock %} - - - - - -
- - {% if not is_popup %} - - - {% endblock %} -
- - - - {% block breadcrumbs %} - - {% endblock %} - {% endif %} - - {% block messages %} - {% if messages %} - - {% endif %} - {% endblock messages %} - - -
- {% block pretitle %}{% endblock %} - {% block content_title %}{% if title %}

{{ title }}

{% endif %}{% endblock %} - {% block content_subtitle %}{% if subtitle %}

{{ subtitle }}

{% endif %}{% endblock %} - {% block content %} - {% block object-tools %}{% endblock %} - {{ content }} - {% endblock %} - {% block sidebar %}{% endblock %} -
-
- - - {% block footer %}{% endblock %} - - - - - diff --git a/dashboard/django/stats/templates/stats/broker.html b/dashboard/django/stats/templates/stats/broker.html deleted file mode 100644 index 38f68a9bf10f4..0000000000000 --- a/dashboard/django/stats/templates/stats/broker.html +++ /dev/null @@ -1,71 +0,0 @@ - -{% extends "stats/base.html" %} -{% load humanize %} -{% load table %} - -{% block title %}Broker | {{property.name}}{% endblock %} - -{% block breadcrumbs %} - -{% endblock %} - - -{% block content %} - - - - - {% column_header topics 'namespace__name' 'Namespace' %} - {% column_header topics 'name' 'Topic' %} - {% column_header topics 'msgRateIn' 'Msg/s in' %} - {% column_header topics 'msgRateOut' 'Msg/s out' %} - {% column_header topics 'msgThroughputIn' 'Bytes/s in' %} - {% column_header topics 'msgThroughputOut' 'Bytes/s out' %} - {% column_header topics 'backlog' 'Backlog' %} - - - - -{% for topic in topics.results %} - - - - - - - - - -{% empty %} - -{% endfor %} - -
{{topic.namespace}}{{topic.short_name}}{{topic.msgRateIn | intcomma}}{{topic.msgRateOut | intcomma}}{{topic.msgThroughputIn | intcomma}}{{topic.msgThroughputOut | intcomma}}{{topic.backlog | intcomma}}
No topics
- -{% table_footer topics %} - - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/brokers.html b/dashboard/django/stats/templates/stats/brokers.html deleted file mode 100644 index b022a878aa3d7..0000000000000 --- a/dashboard/django/stats/templates/stats/brokers.html +++ /dev/null @@ -1,90 +0,0 @@ - -{% extends "stats/base.html" %} - -{% load humanize %} -{% load table %} -{% load stats_extras %} - -{% block title %}Brokers{% endblock %} - -{% block content %} - -
-
-

Clusters

-
    -
  • - All
  • - - {% for cluster in clusters.all %} -
  • {{cluster}}
  • - {% endfor %} - -
  • -
-
-
- - - - - {% column_header brokers 'url' 'Broker' %} - {% column_header brokers 'numBundles' 'Bundles' %} - {% column_header brokers 'numTopics' 'Topics' %} - {% column_header brokers 'numProducers' 'Producers' %} - {% column_header brokers 'numSubscriptions' 'Subscriptions' %} - {% column_header brokers 'numConsumers' 'Consumers' %} - {% column_header brokers 'rateIn' 'Rate In' %} - {% column_header brokers 'rateOut' 'Rate Out' %} - {% column_header brokers 'throughputIn' 'Mbps In' %} - {% column_header brokers 'throughputOut' 'Mbps Out' %} - {% column_header brokers 'backlog' 'Backlog' %} - - - - -{% for broker in brokers.results %} - - - - - - - - - - - - - -{% empty %} - -{% endfor %} - -
- {{broker.url}} - {{broker.numBundles | safe_intcomma}}{{broker.numTopics | safe_intcomma}}{{broker.numProducers | safe_intcomma}}{{broker.numSubscriptions | safe_intcomma}}{{broker.numConsumers | safe_intcomma}}{{broker.rateIn | safe_intcomma}}{{broker.rateOut | safe_intcomma}}{{broker.throughputIn | mbps | floatformat | intcomma}}{{broker.throughputOut | mbps | floatformat | intcomma}}{{broker.backlog | safe_intcomma}}
No Brokers
- -{% table_footer brokers %} - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/clusters.html b/dashboard/django/stats/templates/stats/clusters.html deleted file mode 100644 index afcf22ed933b0..0000000000000 --- a/dashboard/django/stats/templates/stats/clusters.html +++ /dev/null @@ -1,106 +0,0 @@ - -{% extends "stats/base.html" %} - -{% load humanize %} -{% load table %} -{% load stats_extras %} - -{% block title %}Clusters{% endblock %} - -{% block content %} - - - - - - {% column_header clusters 'name' 'Cluster' %} - {% column_header clusters 'numTopics' 'Topics' %} - - {% column_header clusters 'localRateIn' 'Local Rate In' %} - {% column_header clusters 'localRateOut' 'Local Rate Out' %} - {% column_header clusters 'replicationRateIn' 'Replication Rate In' %} - {% column_header clusters 'replicationRateOut' 'Replication Rate Out' %} - - {% column_header clusters 'localBacklog' 'Local Backlog' %} - {% column_header clusters 'replicationBacklog' 'Replication Backlog' %} - {% column_header clusters 'storage' 'Storage' %} - - - - -{% for cluster in clusters.results %} - - - - - - - - - - - - - - - - - - - - -{% empty %} - -{% endfor %} - -
{{cluster.name}}{{cluster.numTopics | intcomma}}{{cluster.localRateIn | intcomma}}{{cluster.localRateOut | intcomma}}{{cluster.replicationRateIn | intcomma}}{{cluster.replicationRateOut | intcomma}}{{cluster.localBacklog | intcomma}}{{cluster.replicationBacklog | intcomma}}{{cluster.storage | filesizeformat}}
- - - - - - - - - - - - - {% for peer in cluster.peers %} - - - - - - - - - {% empty %} - - {% endfor %} - -
Remote ClusterRate inRate outMbps inMbps outReplication backlog
{{peer.remote_cluster__name}}{{peer.msgRateIn__sum | intcomma}}{{peer.msgRateOut__sum | intcomma}}{{peer.msgThroughputIn__sum | mbps | floatformat | intcomma}}{{peer.msgThroughputOut__sum | mbps | floatformat | intcomma}}{{peer.replicationBacklog__sum | intcomma}}
No replication
-
No Clusters
- -{% table_footer clusters %} - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/home.html b/dashboard/django/stats/templates/stats/home.html deleted file mode 100644 index 7e9cb803ac837..0000000000000 --- a/dashboard/django/stats/templates/stats/home.html +++ /dev/null @@ -1,74 +0,0 @@ - -{% extends "stats/base.html" %} - -{% load table %} -{% load humanize %} - -{% block title %}Home{% endblock %} - -{% block content %} - - - - - {% column_header properties 'name' 'Tenant' %} - {% column_header properties 'numNamespaces' 'Namespaces' %} - {% column_header properties 'numTopics' 'Topics' %} - {% column_header properties 'numProducers' 'Producers' %} - {% column_header properties 'numSubscriptions' 'Subscriptions' %} - {% column_header properties 'numConsumers' 'Consumers' %} - {% column_header properties 'rateIn' 'Rate In' %} - {% column_header properties 'rateOut' 'Rate Out' %} - {% column_header properties 'throughputIn' 'Throughput In' %} - {% column_header properties 'throughputOut' 'Throughput Out' %} - {% column_header properties 'backlog' 'Backlog' %} - {% column_header properties 'storage' 'Storage' %} - - - - -{% for property in properties.results %} - - - - - - - - - - - - - - -{% empty %} - -{% endfor %} - -
- {{property.name}} - {{property.numNamespaces | intcomma}}{{property.numTopics | intcomma}}{{property.numProducers | intcomma}}{{property.numSubscriptions | intcomma}}{{property.numConsumers | intcomma}}{{property.rateIn | intcomma}}{{property.rateOut | intcomma}}{{property.throughputIn | intcomma}}{{property.throughputOut | intcomma}}{{property.backlog | intcomma}}{{property.storage | filesizeformat}}
No tenants
- - - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/messages.html b/dashboard/django/stats/templates/stats/messages.html deleted file mode 100644 index 653048f889138..0000000000000 --- a/dashboard/django/stats/templates/stats/messages.html +++ /dev/null @@ -1,103 +0,0 @@ - -{% extends "stats/base.html" %} - -{% load humanize %} -{% load stats_extras %} -{% block extrahead %} -{% load static %} - - - -{% endblock %} - -{% block title %}Topic | {{topic.name}}{% endblock %} - - -{% block breadcrumbs %} - -{% endblock %} - -{% block content %} -
- - -
- -{% if message %} - - -{% for msg in message %} -
-
-{% for type in msg.keys %} - {% with tab_key=type|lower|cut:':' %} - {{type}} - {% endwith %} -{% endfor %} -
-{% for type, content in msg.items %} - {% with tab_key=type|lower|cut:':' %} -
-
{{content}}
-
- {% endwith %} -{% endfor %} -
- -{% endfor %} - -{% endif %} -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/namespace.html b/dashboard/django/stats/templates/stats/namespace.html deleted file mode 100644 index 11563de747206..0000000000000 --- a/dashboard/django/stats/templates/stats/namespace.html +++ /dev/null @@ -1,101 +0,0 @@ - -{% extends "stats/base.html" %} -{% load humanize %} -{% load table %} - -{% block title %}Namespace | {{property.name}}{% endblock %} - -{% block breadcrumbs %} - -{% endblock %} - - -{% block content %} - -{% if namespace.is_global %} -
-
-

Clusters

-
    -
  • All
  • - - {% for cluster in namespace.clusters.all %} -
  • {{cluster}}
  • - {% endfor %} - -
  • -
-
-
-{% endif %} - -{% if topics.results %} - - - - {% if namespace.is_global %} - {% column_header topics 'cluster__name' 'Cluster' %} - {% endif %} - {% column_header topics 'name' 'Topic' %} - {% column_header topics 'msgRateIn' 'Msg/s in' %} - {% column_header topics 'msgRateOut' 'Msg/s out' %} - {% column_header topics 'msgThroughputIn' 'Bytes/s in' %} - {% column_header topics 'msgThroughputOut' 'Bytes/s out' %} - {% column_header topics 'backlog' 'Backlog' %} - {% column_header topics 'broker' 'Broker' %} - - - - -{% for topic in topics.results %} - - {% if namespace.is_global %} - - - - - - - - -{% empty %} - -{% endfor %} - -
{{topic.cluster}} - {% endif %} - - {{topic.short_name}}{{topic.msgRateIn | intcomma}}{{topic.msgRateOut | intcomma}}{{topic.msgThroughputIn | intcomma}}{{topic.msgThroughputOut | intcomma}}{{topic.backlog | intcomma}}{{topic.broker | escape | truncatechars:20 }}
No topics
- -{% table_footer topics %} -{% else %} -
- -
-{% endif %} - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/property.html b/dashboard/django/stats/templates/stats/property.html deleted file mode 100644 index 7958919bc3c7f..0000000000000 --- a/dashboard/django/stats/templates/stats/property.html +++ /dev/null @@ -1,72 +0,0 @@ - -{% extends "stats/base.html" %} - -{% load humanize %} -{% load table %} - -{% block title %}Property | {{property.name}}{% endblock %} - -{% block content %} - - - - - {% column_header namespaces 'name' 'Namespace' %} - {% column_header namespaces 'numTopics' 'Topics' %} - {% column_header namespaces 'numProducers' 'Producers' %} - {% column_header namespaces 'numSubscriptions' 'Subscriptions' %} - {% column_header namespaces 'numConsumers' 'Consumers' %} - {% column_header namespaces 'rateIn' 'Rate In' %} - {% column_header namespaces 'rateOut' 'Rate Out' %} - {% column_header namespaces 'throughputIn' 'Throughput In' %} - {% column_header namespaces 'throughputOut' 'Throughput Out' %} - {% column_header namespaces 'backlog' 'Backlog' %} - {% column_header namespaces 'storage' 'Storage' %} - - - - -{% for namespace in namespaces.results %} - - - - - - - - - - - - - -{% empty %} - -{% endfor %} - -
- {{namespace.name}} - {{namespace.numTopics | intcomma}}{{namespace.numProducers | intcomma}}{{namespace.numSubscriptions | intcomma}}{{namespace.numConsumers | intcomma}}{{namespace.rateIn | intcomma}}{{namespace.rateOut | intcomma}}{{namespace.throughputIn | intcomma}}{{namespace.throughputOut | intcomma}}{{namespace.backlog | intcomma}}{{namespace.storage | filesizeformat}}
No namespaces
- -{% table_footer namespaces %} - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/topic.html b/dashboard/django/stats/templates/stats/topic.html deleted file mode 100644 index ddba7bd260fd8..0000000000000 --- a/dashboard/django/stats/templates/stats/topic.html +++ /dev/null @@ -1,216 +0,0 @@ - -{% extends "stats/base.html" %} - -{% load humanize %} -{% load stats_extras %} - -{% block title %}Topic | {{topic.name}}{% endblock %} - -{% block breadcrumbs %} - -{% endblock %} - -{% block content %} - -{% if topic.is_global %} -
-
-

Cluster

-
    - {% for cluster in clusters %} -
  • {{cluster}}
  • - {% endfor %} - -
  • -
-
-
-{% endif %} - - -

Stats

- - - - - - - - - - - - - - - - - - - - - -
Average msg size{{topic.averageMsgSize | file_size_value}}{{topic.averageMsgSize | file_size_unit}}
Rate in{{topic.msgRateIn | intcomma }}msg/s
Rate out{{topic.msgRateOut | intcomma }}msg/s
Throughput in{{topic.msgThroughputIn | file_size_value }}{{topic.msgThroughputIn | file_size_unit }} / sec
Throughput out{{topic.msgThroughputOut | file_size_value }}{{topic.msgThroughputOut | file_size_unit }} / sec
Pending add entries{{topic.pendingAddEntriesCount | intcomma }}
Producer count{{topic.producerCount | intcomma }}
Storage size{{topic.storageSize | file_size_value}}{{topic.storageSize | file_size_unit}}
Broker - {{topic.broker | escape }} -
- -

Subscriptions

- - - - - - - - - - - - - - - - - - -{% for sub, consumers in subscriptions %} - - {% if consumers %} - - {% else %} - - {% endif %} - - - - - - - {% if consumers %} - - {% else %} - - - {% endif %} - {% if sub.msgBacklog > 0 %} - - - {% else %} - - - {% endif %} - - - - - - -{% empty %} - -{% endfor %} - -
SubscriptionTypeBacklogRate outThroughput outRate expiredUnackedDelete SubscriptionClear BacklogPeek
{{sub.name}}{{sub.name}}{{sub.get_subscriptionType_display}}{{sub.msgBacklog | intcomma }}{{sub.msgRateOut | intcomma}}{{sub.msgThroughputOut | intcomma}}{{sub.msgRateExpired | intcomma}}{{sub.unackedMessages | intcomma}}
-
- -
clearView Messages
No subscriptions
- -{%if topic.is_global%} - -

Replication from {{topic.cluster}}

- - - - - - - - - - - - - -{% for peer in peers %} - - - - - - - - -{% empty %} - -{% endfor %} - -
Remote ClusterRate inRate outMbps inMbps outReplication backlog
{{peer.remote_cluster__name}}{{peer.msgRateIn__sum | intcomma}}{{peer.msgRateOut__sum | intcomma}}{{peer.msgThroughputIn__sum | mbps | floatformat | intcomma}}{{peer.msgThroughputOut__sum | mbps | floatformat | intcomma}}{{peer.replicationBacklog__sum | intcomma}}
No replication
-{% endif %} - - -{% endblock %} diff --git a/dashboard/django/stats/templates/stats/topics.html b/dashboard/django/stats/templates/stats/topics.html deleted file mode 100644 index 021443cc98b8c..0000000000000 --- a/dashboard/django/stats/templates/stats/topics.html +++ /dev/null @@ -1,91 +0,0 @@ - -{% extends "stats/base.html" %} -{% load humanize %} -{% load table %} - -{% block title %}Namespace | {{property.name}}{% endblock %} - -{% block breadcrumbs %} - -{% endblock %} - - -{% block content %} - -
-
-

Clusters

-
    -
  • All
  • - - {% for cluster in clusters %} -
  • {{cluster}}
  • - {% endfor %} - -
  • -
-
-
- - - - - {% column_header topics 'cluster__name' 'Cluster' %} - {% column_header topics 'namespace__name' 'Namespace' %} - {% column_header topics 'name' 'Topic' %} - {% column_header topics 'msgRateIn' 'Msg/s in' %} - {% column_header topics 'msgRateOut' 'Msg/s out' %} - {% column_header topics 'msgThroughputIn' 'Bytes/s in' %} - {% column_header topics 'msgThroughputOut' 'Bytes/s out' %} - {% column_header topics 'backlog' 'Backlog' %} - {% column_header topics 'broker' 'Broker' %} - - - - -{% for topic in topics.results %} - - - - - - - - - - -{% empty %} - -{% endfor %} - -
{{topic.cluster}} - {{topic.namespace}}{{topic.short_name}}{{topic.msgRateIn | intcomma}}{{topic.msgRateOut | intcomma}}{{topic.msgThroughputIn | intcomma}}{{topic.msgThroughputOut | intcomma}}{{topic.backlog | intcomma}} - {{topic.broker | escape | truncatechars:20 }}
No topics
- -{% table_footer topics %} - - -{% endblock %} diff --git a/dashboard/django/stats/templatetags/__init__.py b/dashboard/django/stats/templatetags/__init__.py deleted file mode 100644 index d8a500d9d8d41..0000000000000 --- a/dashboard/django/stats/templatetags/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - diff --git a/dashboard/django/stats/templatetags/stats_extras.py b/dashboard/django/stats/templatetags/stats_extras.py deleted file mode 100644 index 81ca5cf525a2f..0000000000000 --- a/dashboard/django/stats/templatetags/stats_extras.py +++ /dev/null @@ -1,68 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -from django import template -from django.utils import formats -from django.contrib.humanize.templatetags.humanize import intcomma - -register = template.Library() - -KB = 1 << 10 -MB = 1 << 20 -GB = 1 << 30 -TB = 1 << 40 -PB = 1 << 50 - -def fmt(x): - return str(formats.number_format(round(x, 1), 1)) - -@register.filter(name='file_size_value') -def file_size_value(bytes_): - bytes_ = float(bytes_) - if bytes_ < KB: return str(bytes_) - elif bytes_ < MB: return fmt(bytes_ / KB) - elif bytes_ < GB: return fmt(bytes_ / MB) - elif bytes_ < TB: return fmt(bytes_ / GB) - elif bytes_ < PB: return fmt(bytes_ / TB) - else: return fmt(bytes_ / PB) - -@register.filter(name='file_size_unit') -def file_size_unit(bytes_): - if bytes_ < KB: return 'bytes' - elif bytes_ < MB: return 'KB' - elif bytes_ < GB: return 'MB' - elif bytes_ < TB: return 'GB' - elif bytes_ < PB: return 'TB' - else: return 'PB' - - -@register.filter(name='mbps') -def mbps(bytes_per_seconds): - if not bytes_per_seconds: return 0.0 - else: return float(bytes_per_seconds) * 8 / 1024 / 1024 - -@register.filter(name='safe_intcomma') -def safe_intcomma(n): - if not n: return 0 - else: return intcomma(n) - -@register.filter(name='times') -def times(number): - return range(1, number + 1) \ No newline at end of file diff --git a/dashboard/django/stats/templatetags/table.py b/dashboard/django/stats/templatetags/table.py deleted file mode 100644 index 2613092c268a3..0000000000000 --- a/dashboard/django/stats/templatetags/table.py +++ /dev/null @@ -1,91 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -from django import template -from django.utils.html import format_html -from django.utils.safestring import mark_safe -from django.core.paginator import Paginator - -register = template.Library() - -@register.simple_tag -def column_header(table, column, text): - selected = 'style="color: #D84A24"' - if table.sort == column: - sort = '-' + column - arrow = '↓' - elif table.sort == ('-' + column): - sort = column - arrow = '↑' - else: - sort = '-' + column - arrow = '' - selected = '' - - params = dict(table.request.GET) - params['sort'] = [sort] - params_str = '&'.join( (k + '=' + v[0]) for k,v in params.items()) - - return format_html('{} {}\n', - params_str, - mark_safe(selected), - mark_safe(arrow), - text - ) - -@register.simple_tag -def table_footer(table): - if table.show_all or table.paginator.num_pages == 1: - return '' - - params = dict(table.request.GET) - - footer = '

' - for page in table.paginator.page_range: - if page == table.page: - footer += '%d\n' % page - else: - params['page'] = [str(page)] - params_str = '&'.join( (k + '=' + v[0]) for k,v in params.items()) - footer += '%d\n' % (params_str, page) - - footer += ' Total: %d\n' % table.paginator.count - - del params['page'] - params['show-all'] = '1' - params_str = '&'.join( (k + '=' + v[0]) for k,v in params.items()) - footer += ' | Show all' % params_str - footer += '

' - return mark_safe(footer) - - -class Table: - def __init__(self, request, queryset, default_sort, default_page_size=25): - self.request = request - self.sort = request.GET.get('sort', default_sort) - self.page_size = int(request.GET.get('page-size', default_page_size)) - self.page = int(request.GET.get('page', 1)) - self.results = queryset.order_by(self.sort) - self.show_all = request.GET.get('show-all', False) - - if not self.show_all: - # Paginate results unless explicitely turned off - self.paginator = Paginator(self.results, self.page_size) - self.results = self.paginator.page(self.page) diff --git a/dashboard/django/stats/tests.py b/dashboard/django/stats/tests.py deleted file mode 100644 index 8cdbd9cdae9ba..0000000000000 --- a/dashboard/django/stats/tests.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from django.test import TestCase - -# Create your tests here. diff --git a/dashboard/django/stats/urls.py b/dashboard/django/stats/urls.py deleted file mode 100644 index fa831b3582cf0..0000000000000 --- a/dashboard/django/stats/urls.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from django.conf.urls import url - -from . import views - -urlpatterns = [ - url(r'^property/(?P.+)/$', views.property, name='property'), - url(r'^namespace/(?P.+)/$', views.namespace, name='namespace'), - url(r'^deleteNamespace/(?P.+)$', views.delete_namespace, name='deleteNamespace'), - - - url(r'^brokers/$', views.brokers, name='brokers'), - url(r'^brokers/(?P.+)/$', views.brokers_cluster, name='brokers_cluster'), - url(r'^broker/(?P.+)/$', views.broker, name='broker'), - - url(r'^topics/$', views.topics, name='topics'), - url(r'^topic/(?P.+)/$', views.topic, name='topic'), - - url(r'^clusters/$', views.clusters, name='clusters'), - url(r'^clearSubscription/(?P.+)/(?P.+)$', views.clear_subscription, name='clearSubscription'), - url(r'^deleteSubscription/(?P.+)/(?P.+)$', views.delete_subscription, name='deleteSubscription'), - url(r'^messages/(?P.+)/(?P.+)$', views.messages, name='messages'), -] diff --git a/dashboard/django/stats/views.py b/dashboard/django/stats/views.py deleted file mode 100644 index 02bacb99a6055..0000000000000 --- a/dashboard/django/stats/views.py +++ /dev/null @@ -1,553 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import logging -import struct -import chardet -import hexdump -import requests -import json -import re - -from django.shortcuts import render, get_object_or_404, redirect -from django.views import generic -from dashboard import settings - -from stats.models import * -from stats.templatetags.table import Table -from utils import import_utils - -pulsar = import_utils.try_import("pulsar") -logger = logging.getLogger(__name__) - - -class AdminPath: - v1 = "/admin" - v2 = "/admin/v2" - - @staticmethod - def get(is_v2): - return AdminPath.v2 if is_v2 else AdminPath.v1 - - -class TopicName: - def __init__(self, topic_name): - try: - self.scheme, self.path = topic_name.split("://", 1) - except ValueError: - self.scheme, self.path = topic_name.split("/", 1) - self.namespace_path, self.name = self.path.rsplit("/", 1) - self.namespace = NamespaceName(self.namespace_path) - - def is_v2(self): - return self.namespace.is_v2() - - def url_name(self): - return "/".join([self.scheme, self.path]) - - def full_name(self): - return "://".join([self.scheme, self.path]) - - def short_name(self): - return self.name - - def is_global(self): - return self.namespace.is_global() - - def admin_path(self): - b = AdminPath.get(self.is_v2()) - return settings.SERVICE_URL + b + "/" + self.url_name() - - -class NamespaceName: - - def __init__(self, namespace_name): - self.path = namespace_name.strip("/") - path_parts = self.path.split("/") - if len(path_parts) == 2: - self.tenant, self.namespace = path_parts - self.cluster = None - elif len(path_parts) == 3: - self.tenant, self.cluster, self.namespace = path_parts - else: - raise ValueError("invalid namespace:" + namespace_name) - - def is_v2(self): - return self.cluster is None - - def is_global(self): - return self.cluster == "global" - - def admin_path(self): - b = AdminPath.get(self.is_v2()) - return settings.SERVICE_URL + b + "/namespaces/" + self.path - - -def get_timestamp(): - try: - return LatestTimestamp.objects.get(name='latest').timestamp - except: - return 0 - -class HomeView(generic.ListView): - model = Property - template_name = 'stats/home.html' - -def home(request): - ts = get_timestamp() - properties = Property.objects.filter( - namespace__topic__timestamp = ts, - ).annotate( - numNamespaces = Count('namespace__name', distinct=True), - numTopics = Count('namespace__topic__name', distinct=True), - numProducers = Sum('namespace__topic__producerCount', filter=Q(topic__timestamp__eq=ts)), - numSubscriptions = Sum('namespace__topic__subscriptionCount'), - numConsumers = Sum('namespace__topic__consumerCount'), - backlog = Sum('namespace__topic__backlog'), - storage = Sum('namespace__topic__storageSize'), - rateIn = Sum('namespace__topic__msgRateIn'), - rateOut = Sum('namespace__topic__msgRateOut'), - throughputIn = Sum('namespace__topic__msgThroughputIn'), - throughputOut = Sum('namespace__topic__msgThroughputOut'), - ) - - logger.info(properties.query) - - properties = Table(request, properties, default_sort='name') - - return render(request, 'stats/home.html', { - 'properties': properties, - 'title' : 'Tenants', - }) - -def property(request, property_name): - property = get_object_or_404(Property, name=property_name) - ts = get_timestamp() - namespaces = Namespace.objects.filter( - property = property, - timestamp = ts, - deleted = False, - ).annotate( - numTopics = Count('topic'), - numProducers = Sum('topic__producerCount'), - numSubscriptions = Sum('topic__subscriptionCount'), - numConsumers = Sum('topic__consumerCount'), - backlog = Sum('topic__backlog'), - storage = Sum('topic__storageSize'), - rateIn = Sum('topic__msgRateIn'), - rateOut = Sum('topic__msgRateOut'), - throughputIn = Sum('topic__msgThroughputIn'), - throughputOut = Sum('topic__msgThroughputOut'), - ) - - namespaces = Table(request, namespaces, default_sort='name') - - return render(request, 'stats/property.html', { - 'property': property, - 'namespaces' : namespaces, - 'title' : property.name, - }) - - -def namespace(request, namespace_name): - selectedClusterName = request.GET.get('cluster') - - namespace = get_object_or_404(Namespace, name=namespace_name, timestamp=get_timestamp(), deleted=False) - topics = Topic.objects.select_related('broker', 'namespace', 'cluster') - if selectedClusterName: - topics = topics.filter( - namespace = namespace, - timestamp = get_timestamp(), - cluster__name = selectedClusterName, - deleted = False - ) - else: - topics = topics.filter( - namespace = namespace, - timestamp = get_timestamp(), - deleted = False - ) - - topics = Table(request, topics, default_sort='name') - return render(request, 'stats/namespace.html', { - 'namespace' : namespace, - 'topics' : topics, - 'title' : namespace.name, - 'selectedCluster' : selectedClusterName, - }) - - -def delete_namespace(request, namespace_name): - url = NamespaceName(namespace_name).admin_path() - response = requests.delete(url) - status = response.status_code - logger.debug("Delete namespace " + namespace_name + " status - " + str(status)) - if status == 204: - Namespace.objects.filter(name=namespace_name, timestamp=get_timestamp()).update(deleted=True) - return redirect('property', property_name=namespace_name.split('/', 1)[0]) - - -def topic(request, topic_name): - timestamp = get_timestamp() - topic_name = TopicName(topic_name).full_name() - cluster_name = request.GET.get('cluster') - clusters = [] - - if cluster_name: - topic = get_object_or_404(Topic, name=topic_name, cluster__name=cluster_name, timestamp=timestamp) - clusters = [x.cluster for x in Topic.objects.filter(name=topic_name, timestamp=timestamp).order_by('cluster__name')] - else: - topic = get_object_or_404(Topic, name=topic_name, timestamp=timestamp) - subscriptions = Subscription.objects.filter(topic=topic, timestamp=timestamp, deleted=False).order_by('name') - - subs = [] - - for sub in subscriptions: - consumers = Consumer.objects.filter(subscription=sub).order_by('address') - subs.append((sub, consumers)) - - if topic.is_global(): - peers_clusters = Replication.objects.filter( - timestamp = timestamp, - local_cluster__name = cluster_name - ).values('remote_cluster__name' - ).annotate( - Sum('msgRateIn'), - Sum('msgThroughputIn'), - Sum('msgRateOut'), - Sum('msgThroughputOut'), - Sum('replicationBacklog') - ) - else: - peers_clusters = [] - - return render(request, 'stats/topic.html', { - 'topic' : topic, - 'subscriptions' : subs, - 'title' : topic.name, - 'selectedCluster' : cluster_name, - 'clusters' : clusters, - 'peers' : peers_clusters, - }) - -def topics(request): - selectedClusterName = request.GET.get('cluster') - - topics = Topic.objects.select_related('broker', 'namespace', 'cluster') - if selectedClusterName: - topics = topics.filter( - timestamp = get_timestamp(), - cluster__name = selectedClusterName, - deleted = False - ) - else: - topics = topics.filter( - timestamp = get_timestamp(), - deleted = False - ) - - topics = Table(request, topics, default_sort='cluster__name') - return render(request, 'stats/topics.html', { - 'clusters' : Cluster.objects.all(), - 'topics' : topics, - 'title' : 'Topics', - 'selectedCluster' : selectedClusterName, - }) - - -def brokers(request): - return brokers_cluster(request, None) - -def brokers_cluster(request, cluster_name): - ts = get_timestamp() - - brokers = Broker.objects - if cluster_name: - brokers = brokers.filter( - Q(topic__timestamp=ts) | Q(topic__timestamp__isnull=True), - activebroker__timestamp = ts, - cluster__name = cluster_name, - - ) - else: - brokers = brokers.filter( - Q(topic__timestamp=ts) | Q(topic__timestamp__isnull=True), - activebroker__timestamp = ts - ) - - brokers = brokers.annotate( - numBundles = Count('topic__bundle', True), - numTopics = Count('topic'), - numProducers = Sum('topic__producerCount'), - numSubscriptions = Sum('topic__subscriptionCount'), - numConsumers = Sum('topic__consumerCount'), - backlog = Sum('topic__backlog'), - storage = Sum('topic__storageSize'), - rateIn = Sum('topic__msgRateIn'), - rateOut = Sum('topic__msgRateOut'), - throughputIn = Sum('topic__msgThroughputIn'), - throughputOut = Sum('topic__msgThroughputOut'), - ) - - brokers = Table(request, brokers, default_sort='url') - - return render(request, 'stats/brokers.html', { - 'clusters' : Cluster.objects.all(), - 'brokers' : brokers, - 'selectedCluster' : cluster_name, - }) - -def broker(request, broker_url): - broker = Broker.objects.get(url = broker_url) - topics = Topic.objects.filter( - timestamp = get_timestamp(), - broker__url = broker_url - ) - - topics = Table(request, topics, default_sort='namespace__name') - return render(request, 'stats/broker.html', { - 'topics' : topics, - 'title' : 'Broker - %s - %s' % (broker.cluster, broker_url), - 'broker_url' : broker_url - }) - - -def clusters(request): - ts = get_timestamp() - - clusters = Cluster.objects.filter( - topic__timestamp = ts - ) - - clusters = clusters.annotate( - numTopics = Count('topic'), - localBacklog = Sum('topic__backlog'), - replicationBacklog = Sum('topic__replicationBacklog'), - storage = Sum('topic__storageSize'), - localRateIn = Sum('topic__localRateIn'), - localRateOut = Sum('topic__localRateOut'), - replicationRateIn = Sum('topic__replicationRateIn'), - replicationRateOut = Sum('topic__replicationRateOut'), - ) - - clusters = Table(request, clusters, default_sort='name') - - for cluster in clusters.results: - # Fetch per-remote peer stats - peers = Replication.objects.filter( - timestamp = ts, - local_cluster = cluster, - ).values('remote_cluster__name') - - peers = peers.annotate( - Sum('msgRateIn'), - Sum('msgThroughputIn'), - Sum('msgRateOut'), - Sum('msgThroughputOut'), - Sum('replicationBacklog') - ) - cluster.peers = peers - - return render(request, 'stats/clusters.html', { - 'clusters' : clusters, - }) - - -def clear_subscription(request, topic_name, subscription_name): - url = "%s/subscription/%s/skip_all" % (TopicName(topic_name).admin_path(), subscription_name) - response = requests.post(url) - if response.status_code == 204: - ts = get_timestamp() - topic_db_name = TopicName(topic_name).full_name() - topic = Topic.objects.get(name=topic_db_name, timestamp=ts) - subscription = Subscription.objects.get(name=subscription_name, topic=topic, timestamp=ts) - topic.backlog = topic.backlog - subscription.msgBacklog - topic.save(update_fields=['backlog']) - subscription.msgBacklog = 0 - subscription.save(update_fields=['msgBacklog']) - return redirect('topic', topic_name=topic_name) - - -def delete_subscription(request, topic_name, subscription_name): - url = "%s/subscription/%s" % (TopicName(topic_name).admin_path(), subscription_name) - response = requests.delete(url) - status = response.status_code - if status == 204: - ts = get_timestamp() - topic_db_name = TopicName(topic_name).full_name() - topic = Topic.objects.get(name=topic_db_name, timestamp=ts) - deleted_subscription = Subscription.objects.get(name=subscription_name, topic=topic, timestamp=ts) - deleted_subscription.deleted = True - deleted_subscription.save(update_fields=['deleted']) - subscriptions = Subscription.objects.filter(topic=topic, deleted=False, timestamp=ts) - if not subscriptions: - topic.deleted=True - topic.save(update_fields=['deleted']) - m = re.search(r"persistent/(?P.*)/.*", topic_name) - namespace_name = m.group("namespace") - return redirect('namespace', namespace_name=namespace_name) - else: - topic.backlog = topic.backlog - deleted_subscription.msgBacklog - topic.save(update_fields=['backlog']) - return redirect('topic', topic_name=topic_name) - - -def messages(request, topic_name, subscription_name): - topic_name_obj = TopicName(topic_name) - topic_db_name = topic_name_obj.full_name() - timestamp = get_timestamp() - cluster_name = request.GET.get('cluster') - - if cluster_name: - topic_obj = get_object_or_404(Topic, - name=topic_db_name, - cluster__name=cluster_name, - timestamp=timestamp) - else: - topic_obj = get_object_or_404(Topic, - name=topic_db_name, timestamp=timestamp) - subscription_obj = get_object_or_404(Subscription, - topic=topic_obj, name=subscription_name) - - message = None - message_position = request.GET.get('message-position') - if message_position and message_position.isnumeric(): - message = peek_message(topic_name_obj, subscription_name, message_position) - if not isinstance(message, list): - message = [message] - - return render(request, 'stats/messages.html', { - 'topic': topic_obj, - 'subscription': subscription_obj, - 'title': topic_obj.name, - 'subtitle': subscription_name, - 'message': message, - 'position': message_position or 1, - }) - - -def message_skip_meta(message_view): - if not message_view or len(message_view) < 4: - raise ValueError("invalid message") - meta_size = struct.unpack(">I", message_view[:4]) - message_index = 4 + meta_size[0] - if len(message_view) < message_index: - raise ValueError("invalid message") - return message_view[message_index:] - - -def parse_batch_message_supported(): - if pulsar is None: - return False - try: - _ = pulsar.MessageBatch - return True - except AttributeError: - return False - - -def parse_batch_message_entry(response, message_id, batch_size): - message_id_parts = message_id.split(':') - batch_message_id = pulsar.MessageId(-1, - int(message_id_parts[0]), - int(message_id_parts[1]), - -1) - return pulsar.MessageBatch()\ - .with_message_id(batch_message_id)\ - .parse_from(response.content, batch_size) - - -def format_message_metas(properties): - return "Properties:\n%s" % json.dumps(properties, - ensure_ascii=False, indent=2) - - -def format_single_message(message_id, properties, data): - message = { - message_id: format_message_metas(properties), - "Hex": hexdump.hexdump(data, result='return'), - } - try: - text = str(data, - encoding=chardet.detect(data)['encoding'], - errors='strict') - message["Text"] = text - message["JSON"] = json.dumps(json.loads(text), - ensure_ascii=False, indent=2) - except Exception: - pass - return message - - -def get_batch_message_from_http_response(response, message_id, batch_size): - entries = parse_batch_message_entry(response, message_id, batch_size) - message_list = [] - for entry in entries: - single_msg_id = entry.message_id() - single_msg_id = "%s:%s:%s" % (single_msg_id.ledger_id(), - single_msg_id.entry_id(), - single_msg_id.batch_index(),) - message_list.append(format_single_message(single_msg_id, - entry.properties(), - entry.data())) - return message_list - - -def get_properties_from_http_header(response): - properties = {} - for k, v in response.headers.items(): - if k.startswith("X-Pulsar-PROPERTY-"): - properties[k.replace("X-Pulsar-PROPERTY-", "", 1)] = v - return properties - - -def get_message_from_http_response(response): - message_id = response.headers.get("X-Pulsar-Message-ID") - if message_id is None: - raise ValueError("invalid peek response") - batch_size = response.headers.get('X-Pulsar-num-batch-message') - if batch_size is not None: - batch_size = int(batch_size) - if parse_batch_message_supported(): - return get_batch_message_from_http_response(response, message_id, batch_size) - else: - if batch_size == 1: - message_id = message_id + ":0" - message_view = message_skip_meta(memoryview(response.content)) - return format_single_message(message_id, - {}, - message_view.tobytes()) - else: - return {"Batch": "(size=%d)" % batch_size} - else: - get_properties_from_http_header(response) - return format_single_message(message_id, - get_properties_from_http_header(response), - response.content) - - -def peek_message(topic_name, subscription_name, message_position): - if not isinstance(topic_name, TopicName): - topic_name = TopicName(topic_name) - peek_url = "%s/subscription/%s/position/%s" % ( - topic_name.admin_path(), subscription_name, message_position) - peek_response = requests.get(peek_url) - if peek_response.status_code != 200: - return {"ERROR": "%s(%d)" % (peek_response.reason, peek_response.status_code)} - return get_message_from_http_response(peek_response) diff --git a/dashboard/django/utils/__init__.py b/dashboard/django/utils/__init__.py deleted file mode 100644 index d8a500d9d8d41..0000000000000 --- a/dashboard/django/utils/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - diff --git a/dashboard/django/utils/import_utils.py b/dashboard/django/utils/import_utils.py deleted file mode 100644 index d3236d483f747..0000000000000 --- a/dashboard/django/utils/import_utils.py +++ /dev/null @@ -1,27 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import importlib - - -def try_import(name): - try: - return importlib.import_module(name) - except ImportError: - return None diff --git a/dashboard/init-postgres.sh b/dashboard/init-postgres.sh deleted file mode 100755 index 2f957a8c4d04c..0000000000000 --- a/dashboard/init-postgres.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -set -x -e - -rm -rf /data/* -chown -R postgres: /data -chmod 700 /data -sudo -u postgres /usr/lib/postgresql/11/bin/initdb /data/ -sudo -u postgres /usr/lib/postgresql/11/bin/pg_ctl -D /data/ start -sudo -u postgres psql --command "CREATE USER docker WITH PASSWORD 'docker';" -sudo -u postgres createdb -O docker pulsar_dashboard - -cd /pulsar/django -./manage.py migrate diff --git a/dashboard/pom.xml b/dashboard/pom.xml deleted file mode 100644 index 6938b0eec7437..0000000000000 --- a/dashboard/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - org.apache.pulsar - docker-images - 2.9.0-SNAPSHOT - ../docker - - 4.0.0 - dashboard-docker-image - Apache Pulsar :: Docker Images :: Pulsar Dashboard - pom - - - - docker - - - - com.spotify - dockerfile-maven-plugin - ${dockerfile-maven.version} - - - default - - build - - - - add-no-repo-tag-and-version - - tag - - - pulsar-dashboard - ${project.version} - - - - add-no-repo-tag-and-latest - - tag - - - pulsar-dashboard - latest - - - - tag-and-push-latest - - tag - push - - - ${docker.organization}/pulsar-dashboard - latest - - - - - ${docker.organization}/pulsar-dashboard - ${project.version} - false - - - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - ${skipBuildDistribution} - - - - - diff --git a/dashboard/requirements.txt b/dashboard/requirements.txt deleted file mode 100644 index ba160db43a186..0000000000000 --- a/dashboard/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -chardet -Django <2.0 -hexdump -psycopg2 -pulsar-client -pytz -requests -uwsgi diff --git a/dashboard/start.sh b/dashboard/start.sh deleted file mode 100755 index eb56f7b3ed718..0000000000000 --- a/dashboard/start.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -echo "Starting Pulsar dasboard" - -set -e -x - -/pulsar/init-postgres.sh - -supervisord -n diff --git a/docker/publish.sh b/docker/publish.sh index 21e9a1b1202d5..c064286b22bcf 100755 --- a/docker/publish.sh +++ b/docker/publish.sh @@ -65,25 +65,21 @@ set -e docker tag pulsar:latest ${docker_registry_org}/pulsar:latest docker tag pulsar-all:latest ${docker_registry_org}/pulsar-all:latest docker tag pulsar-grafana:latest ${docker_registry_org}/pulsar-grafana:latest -docker tag pulsar-dashboard:latest ${docker_registry_org}/pulsar-dashboard:latest docker tag pulsar-standalone:latest ${docker_registry_org}/pulsar-standalone:latest docker tag pulsar:latest ${docker_registry_org}/pulsar:$MVN_VERSION docker tag pulsar-all:latest ${docker_registry_org}/pulsar-all:$MVN_VERSION docker tag pulsar-grafana:latest ${docker_registry_org}/pulsar-grafana:$MVN_VERSION -docker tag pulsar-dashboard:latest ${docker_registry_org}/pulsar-dashboard:$MVN_VERSION docker tag pulsar-standalone:latest ${docker_registry_org}/pulsar-standalone:$MVN_VERSION # Push all images and tags docker push ${docker_registry_org}/pulsar:latest docker push ${docker_registry_org}/pulsar-all:latest docker push ${docker_registry_org}/pulsar-grafana:latest -docker push ${docker_registry_org}/pulsar-dashboard:latest docker push ${docker_registry_org}/pulsar-standalone:latest docker push ${docker_registry_org}/pulsar:$MVN_VERSION docker push ${docker_registry_org}/pulsar-all:$MVN_VERSION docker push ${docker_registry_org}/pulsar-grafana:$MVN_VERSION -docker push ${docker_registry_org}/pulsar-dashboard:$MVN_VERSION docker push ${docker_registry_org}/pulsar-standalone:$MVN_VERSION echo "Finished pushing images to ${docker_registry_org}" diff --git a/pom.xml b/pom.xml index 9df01ec9dd06e..5bfadbeaf3b0b 100644 --- a/pom.xml +++ b/pom.xml @@ -1963,7 +1963,6 @@ flexible messaging model and an intuitive client API. pulsar-broker-auth-athenz pulsar-client-auth-athenz pulsar-sql - dashboard pulsar-broker-auth-sasl pulsar-client-auth-sasl pulsar-config-validation