From ff30b14a212f38cf59084d30e9f13f9f92d2be3b Mon Sep 17 00:00:00 2001 From: maxbr Date: Mon, 26 Sep 2016 16:36:11 +0200 Subject: Restructure dashboard project for docker deploying Change-Id: I13cad51270504ee4bed8558598a8891af58a26ab Signed-off-by: maxbr --- tools/pharos-dashboard/.gitignore | 12 +- tools/pharos-dashboard/Makefile | 35 ++++ tools/pharos-dashboard/account/__init__.py | 10 -- tools/pharos-dashboard/account/admin.py | 15 -- tools/pharos-dashboard/account/apps.py | 15 -- tools/pharos-dashboard/account/forms.py | 22 --- tools/pharos-dashboard/account/jira_util.py | 66 -------- tools/pharos-dashboard/account/middleware.py | 32 ---- .../account/migrations/__init__.py | 10 -- tools/pharos-dashboard/account/models.py | 30 ---- tools/pharos-dashboard/account/rsa.pem | 17 -- tools/pharos-dashboard/account/rsa.pub | 6 - tools/pharos-dashboard/account/tests/__init__.py | 10 -- .../pharos-dashboard/account/tests/test_general.py | 50 ------ tools/pharos-dashboard/account/urls.py | 36 ---- tools/pharos-dashboard/account/views.py | 132 --------------- tools/pharos-dashboard/api/__init__.py | 10 -- tools/pharos-dashboard/api/serializers.py | 32 ---- tools/pharos-dashboard/api/urls.py | 38 ----- tools/pharos-dashboard/api/views.py | 33 ---- tools/pharos-dashboard/booking/__init__.py | 10 -- tools/pharos-dashboard/booking/admin.py | 15 -- tools/pharos-dashboard/booking/apps.py | 15 -- tools/pharos-dashboard/booking/forms.py | 19 --- .../booking/migrations/__init__.py | 10 -- tools/pharos-dashboard/booking/models.py | 75 --------- tools/pharos-dashboard/booking/tests/__init__.py | 10 -- .../pharos-dashboard/booking/tests/test_models.py | 107 ------------ tools/pharos-dashboard/booking/tests/test_views.py | 81 --------- tools/pharos-dashboard/booking/urls.py | 37 ----- tools/pharos-dashboard/booking/views.py | 113 ------------- tools/pharos-dashboard/celerybeat-schedule | Bin 16384 -> 0 bytes tools/pharos-dashboard/config.env.sample | 18 ++ .../config/nginx/pharos_dashboard.conf | 24 +++ .../docker-entrypoint-initdb.d/pharos_dashboard.sh | 14 ++ tools/pharos-dashboard/dashboard/__init__.py | 10 -- tools/pharos-dashboard/dashboard/admin.py | 16 -- tools/pharos-dashboard/dashboard/apps.py | 15 -- .../dashboard/fixtures/dashboard.json | 164 ------------------ .../dashboard/migrations/__init__.py | 10 -- tools/pharos-dashboard/dashboard/models.py | 81 --------- tools/pharos-dashboard/dashboard/tasks.py | 23 --- .../dashboard/templatetags/__init__.py | 10 -- .../dashboard/templatetags/jenkins_filters.py | 38 ----- .../dashboard/templatetags/jira_filters.py | 18 -- tools/pharos-dashboard/dashboard/urls.py | 40 ----- tools/pharos-dashboard/dashboard/views.py | 143 ---------------- tools/pharos-dashboard/deploy.org | 45 ----- tools/pharos-dashboard/docker-compose.yml | 69 ++++++++ tools/pharos-dashboard/jenkins/__init__.py | 10 -- tools/pharos-dashboard/jenkins/adapter.py | 134 --------------- tools/pharos-dashboard/jenkins/apps.py | 15 -- .../jenkins/migrations/__init__.py | 10 -- tools/pharos-dashboard/jenkins/models.py | 60 ------- tools/pharos-dashboard/jenkins/tasks.py | 49 ------ tools/pharos-dashboard/jenkins/tests.py | 52 ------ tools/pharos-dashboard/manage.py | 32 ---- tools/pharos-dashboard/notification/__init__.py | 11 -- tools/pharos-dashboard/notification/admin.py | 17 -- tools/pharos-dashboard/notification/apps.py | 18 -- tools/pharos-dashboard/notification/models.py | 32 ---- tools/pharos-dashboard/notification/signals.py | 25 --- tools/pharos-dashboard/notification/tasks.py | 33 ---- .../notification_framework/__init__.py | 10 -- .../notification_framework/notification.py | 114 ------------- .../pharos-dashboard/pharos_dashboard/__init__.py | 13 -- tools/pharos-dashboard/pharos_dashboard/celery.py | 30 ---- tools/pharos-dashboard/pharos_dashboard/urls.py | 44 ----- tools/pharos-dashboard/pharos_dashboard/wsgi.py | 26 --- tools/pharos-dashboard/readme.txt | 35 ++++ tools/pharos-dashboard/src/account/__init__.py | 10 ++ tools/pharos-dashboard/src/account/admin.py | 15 ++ tools/pharos-dashboard/src/account/apps.py | 15 ++ tools/pharos-dashboard/src/account/forms.py | 22 +++ tools/pharos-dashboard/src/account/jira_util.py | 66 ++++++++ tools/pharos-dashboard/src/account/middleware.py | 32 ++++ .../src/account/migrations/__init__.py | 10 ++ tools/pharos-dashboard/src/account/models.py | 30 ++++ tools/pharos-dashboard/src/account/rsa.pem | 17 ++ tools/pharos-dashboard/src/account/rsa.pub | 6 + .../pharos-dashboard/src/account/tests/__init__.py | 10 ++ .../src/account/tests/test_general.py | 50 ++++++ tools/pharos-dashboard/src/account/urls.py | 36 ++++ tools/pharos-dashboard/src/account/views.py | 132 +++++++++++++++ tools/pharos-dashboard/src/api/__init__.py | 10 ++ tools/pharos-dashboard/src/api/serializers.py | 32 ++++ tools/pharos-dashboard/src/api/urls.py | 38 +++++ tools/pharos-dashboard/src/api/views.py | 33 ++++ tools/pharos-dashboard/src/booking/__init__.py | 10 ++ tools/pharos-dashboard/src/booking/admin.py | 15 ++ tools/pharos-dashboard/src/booking/apps.py | 15 ++ tools/pharos-dashboard/src/booking/forms.py | 19 +++ .../src/booking/migrations/__init__.py | 10 ++ tools/pharos-dashboard/src/booking/models.py | 75 +++++++++ .../pharos-dashboard/src/booking/tests/__init__.py | 10 ++ .../src/booking/tests/test_models.py | 107 ++++++++++++ .../src/booking/tests/test_views.py | 81 +++++++++ tools/pharos-dashboard/src/booking/urls.py | 37 +++++ tools/pharos-dashboard/src/booking/views.py | 113 +++++++++++++ tools/pharos-dashboard/src/dashboard/__init__.py | 10 ++ tools/pharos-dashboard/src/dashboard/admin.py | 16 ++ tools/pharos-dashboard/src/dashboard/apps.py | 15 ++ .../src/dashboard/fixtures/dashboard.json | 164 ++++++++++++++++++ .../src/dashboard/migrations/__init__.py | 10 ++ tools/pharos-dashboard/src/dashboard/models.py | 81 +++++++++ tools/pharos-dashboard/src/dashboard/tasks.py | 23 +++ .../src/dashboard/templatetags/__init__.py | 10 ++ .../src/dashboard/templatetags/jenkins_filters.py | 38 +++++ .../src/dashboard/templatetags/jira_filters.py | 18 ++ tools/pharos-dashboard/src/dashboard/urls.py | 40 +++++ tools/pharos-dashboard/src/dashboard/views.py | 143 ++++++++++++++++ tools/pharos-dashboard/src/jenkins/__init__.py | 10 ++ tools/pharos-dashboard/src/jenkins/adapter.py | 134 +++++++++++++++ tools/pharos-dashboard/src/jenkins/admin.py | 17 ++ tools/pharos-dashboard/src/jenkins/apps.py | 15 ++ .../src/jenkins/migrations/__init__.py | 10 ++ tools/pharos-dashboard/src/jenkins/models.py | 60 +++++++ tools/pharos-dashboard/src/jenkins/tasks.py | 49 ++++++ tools/pharos-dashboard/src/jenkins/tests.py | 52 ++++++ tools/pharos-dashboard/src/manage.py | 32 ++++ .../pharos-dashboard/src/notification/__init__.py | 11 ++ tools/pharos-dashboard/src/notification/admin.py | 17 ++ tools/pharos-dashboard/src/notification/apps.py | 18 ++ .../src/notification/migrations/__init__.py | 10 ++ tools/pharos-dashboard/src/notification/models.py | 32 ++++ tools/pharos-dashboard/src/notification/signals.py | 25 +++ tools/pharos-dashboard/src/notification/tasks.py | 33 ++++ .../src/notification_framework/__init__.py | 10 ++ .../src/notification_framework/notification.py | 114 +++++++++++++ .../src/pharos_dashboard/__init__.py | 13 ++ .../src/pharos_dashboard/celery.py | 30 ++++ .../src/pharos_dashboard/settings.py | 176 ++++++++++++++++++++ .../pharos-dashboard/src/pharos_dashboard/urls.py | 44 +++++ .../pharos-dashboard/src/pharos_dashboard/wsgi.py | 26 +++ tools/pharos-dashboard/src/static/bower.json | 24 +++ tools/pharos-dashboard/src/static/css/theme.css | 13 ++ .../src/static/js/booking-calendar.js | 46 ++++++ .../src/static/js/dataTables-sort.js | 36 ++++ .../src/static/js/datetimepicker-options.js | 13 ++ .../src/static/js/flot-pie-chart.js | 30 ++++ .../src/static/js/fullcalendar-options.js | 101 +++++++++++ .../src/templates/account/user_list.html | 46 ++++++ .../templates/account/userprofile_update_form.html | 30 ++++ tools/pharos-dashboard/src/templates/base.html | 104 ++++++++++++ .../src/templates/booking/booking_calendar.html | 94 +++++++++++ .../src/templates/booking/booking_detail.html | 26 +++ .../src/templates/booking/booking_table.html | 33 ++++ .../src/templates/dashboard/ci_pods.html | 60 +++++++ .../src/templates/dashboard/dev_pods.html | 69 ++++++++ .../src/templates/dashboard/jenkins_slaves.html | 45 +++++ .../src/templates/dashboard/resource.html | 58 +++++++ .../src/templates/dashboard/resource_all.html | 73 ++++++++ .../src/templates/dashboard/resource_detail.html | 184 +++++++++++++++++++++ .../src/templates/dashboard/server_table.html | 30 ++++ .../src/templates/dashboard/table.html | 50 ++++++ tools/pharos-dashboard/src/templates/layout.html | 73 ++++++++ .../src/templates/rest_framework/api.html | 9 + tools/pharos-dashboard/static/bower.json | 24 --- tools/pharos-dashboard/static/css/theme.css | 13 -- .../pharos-dashboard/static/js/booking-calendar.js | 46 ------ .../pharos-dashboard/static/js/dataTables-sort.js | 36 ---- .../static/js/datetimepicker-options.js | 13 -- tools/pharos-dashboard/static/js/flot-pie-chart.js | 30 ---- .../static/js/fullcalendar-options.js | 101 ----------- .../templates/account/user_list.html | 46 ------ .../templates/account/userprofile_update_form.html | 30 ---- tools/pharos-dashboard/templates/base.html | 109 ------------ .../templates/booking/booking_calendar.html | 94 ----------- .../templates/booking/booking_detail.html | 26 --- .../templates/booking/booking_table.html | 33 ---- .../templates/dashboard/ci_pods.html | 60 ------- .../templates/dashboard/dev_pods.html | 69 -------- .../templates/dashboard/jenkins_slaves.html | 45 ----- .../templates/dashboard/resource.html | 58 ------- .../templates/dashboard/resource_all.html | 73 -------- .../templates/dashboard/resource_detail.html | 184 --------------------- .../templates/dashboard/server_table.html | 30 ---- .../templates/dashboard/table.html | 50 ------ tools/pharos-dashboard/templates/layout.html | 73 -------- .../templates/rest_framework/api.html | 9 - tools/pharos-dashboard/web/Dockerfile | 7 + tools/pharos-dashboard/web/requirements.txt | 16 ++ tools/pharos-dashboard/worker/Dockerfile | 8 + tools/pharos-dashboard/worker/requirements.txt | 16 ++ 184 files changed, 4061 insertions(+), 3658 deletions(-) create mode 100644 tools/pharos-dashboard/Makefile delete mode 100644 tools/pharos-dashboard/account/__init__.py delete mode 100644 tools/pharos-dashboard/account/admin.py delete mode 100644 tools/pharos-dashboard/account/apps.py delete mode 100644 tools/pharos-dashboard/account/forms.py delete mode 100644 tools/pharos-dashboard/account/jira_util.py delete mode 100644 tools/pharos-dashboard/account/middleware.py delete mode 100644 tools/pharos-dashboard/account/migrations/__init__.py delete mode 100644 tools/pharos-dashboard/account/models.py delete mode 100644 tools/pharos-dashboard/account/rsa.pem delete mode 100644 tools/pharos-dashboard/account/rsa.pub delete mode 100644 tools/pharos-dashboard/account/tests/__init__.py delete mode 100644 tools/pharos-dashboard/account/tests/test_general.py delete mode 100644 tools/pharos-dashboard/account/urls.py delete mode 100644 tools/pharos-dashboard/account/views.py delete mode 100644 tools/pharos-dashboard/api/__init__.py delete mode 100644 tools/pharos-dashboard/api/serializers.py delete mode 100644 tools/pharos-dashboard/api/urls.py delete mode 100644 tools/pharos-dashboard/api/views.py delete mode 100644 tools/pharos-dashboard/booking/__init__.py delete mode 100644 tools/pharos-dashboard/booking/admin.py delete mode 100644 tools/pharos-dashboard/booking/apps.py delete mode 100644 tools/pharos-dashboard/booking/forms.py delete mode 100644 tools/pharos-dashboard/booking/migrations/__init__.py delete mode 100644 tools/pharos-dashboard/booking/models.py delete mode 100644 tools/pharos-dashboard/booking/tests/__init__.py delete mode 100644 tools/pharos-dashboard/booking/tests/test_models.py delete mode 100644 tools/pharos-dashboard/booking/tests/test_views.py delete mode 100644 tools/pharos-dashboard/booking/urls.py delete mode 100644 tools/pharos-dashboard/booking/views.py delete mode 100644 tools/pharos-dashboard/celerybeat-schedule create mode 100644 tools/pharos-dashboard/config.env.sample create mode 100644 tools/pharos-dashboard/config/nginx/pharos_dashboard.conf create mode 100755 tools/pharos-dashboard/config/postgres/docker-entrypoint-initdb.d/pharos_dashboard.sh delete mode 100644 tools/pharos-dashboard/dashboard/__init__.py delete mode 100644 tools/pharos-dashboard/dashboard/admin.py delete mode 100644 tools/pharos-dashboard/dashboard/apps.py delete mode 100644 tools/pharos-dashboard/dashboard/fixtures/dashboard.json delete mode 100644 tools/pharos-dashboard/dashboard/migrations/__init__.py delete mode 100644 tools/pharos-dashboard/dashboard/models.py delete mode 100644 tools/pharos-dashboard/dashboard/tasks.py delete mode 100644 tools/pharos-dashboard/dashboard/templatetags/__init__.py delete mode 100644 tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py delete mode 100644 tools/pharos-dashboard/dashboard/templatetags/jira_filters.py delete mode 100644 tools/pharos-dashboard/dashboard/urls.py delete mode 100644 tools/pharos-dashboard/dashboard/views.py delete mode 100644 tools/pharos-dashboard/deploy.org create mode 100644 tools/pharos-dashboard/docker-compose.yml delete mode 100644 tools/pharos-dashboard/jenkins/__init__.py delete mode 100644 tools/pharos-dashboard/jenkins/adapter.py delete mode 100644 tools/pharos-dashboard/jenkins/apps.py delete mode 100644 tools/pharos-dashboard/jenkins/migrations/__init__.py delete mode 100644 tools/pharos-dashboard/jenkins/models.py delete mode 100644 tools/pharos-dashboard/jenkins/tasks.py delete mode 100644 tools/pharos-dashboard/jenkins/tests.py delete mode 100644 tools/pharos-dashboard/manage.py delete mode 100644 tools/pharos-dashboard/notification/__init__.py delete mode 100644 tools/pharos-dashboard/notification/admin.py delete mode 100644 tools/pharos-dashboard/notification/apps.py delete mode 100644 tools/pharos-dashboard/notification/models.py delete mode 100644 tools/pharos-dashboard/notification/signals.py delete mode 100644 tools/pharos-dashboard/notification/tasks.py delete mode 100644 tools/pharos-dashboard/notification_framework/__init__.py delete mode 100644 tools/pharos-dashboard/notification_framework/notification.py delete mode 100644 tools/pharos-dashboard/pharos_dashboard/__init__.py delete mode 100644 tools/pharos-dashboard/pharos_dashboard/celery.py delete mode 100644 tools/pharos-dashboard/pharos_dashboard/urls.py delete mode 100644 tools/pharos-dashboard/pharos_dashboard/wsgi.py create mode 100644 tools/pharos-dashboard/readme.txt create mode 100644 tools/pharos-dashboard/src/account/__init__.py create mode 100644 tools/pharos-dashboard/src/account/admin.py create mode 100644 tools/pharos-dashboard/src/account/apps.py create mode 100644 tools/pharos-dashboard/src/account/forms.py create mode 100644 tools/pharos-dashboard/src/account/jira_util.py create mode 100644 tools/pharos-dashboard/src/account/middleware.py create mode 100644 tools/pharos-dashboard/src/account/migrations/__init__.py create mode 100644 tools/pharos-dashboard/src/account/models.py create mode 100644 tools/pharos-dashboard/src/account/rsa.pem create mode 100644 tools/pharos-dashboard/src/account/rsa.pub create mode 100644 tools/pharos-dashboard/src/account/tests/__init__.py create mode 100644 tools/pharos-dashboard/src/account/tests/test_general.py create mode 100644 tools/pharos-dashboard/src/account/urls.py create mode 100644 tools/pharos-dashboard/src/account/views.py create mode 100644 tools/pharos-dashboard/src/api/__init__.py create mode 100644 tools/pharos-dashboard/src/api/serializers.py create mode 100644 tools/pharos-dashboard/src/api/urls.py create mode 100644 tools/pharos-dashboard/src/api/views.py create mode 100644 tools/pharos-dashboard/src/booking/__init__.py create mode 100644 tools/pharos-dashboard/src/booking/admin.py create mode 100644 tools/pharos-dashboard/src/booking/apps.py create mode 100644 tools/pharos-dashboard/src/booking/forms.py create mode 100644 tools/pharos-dashboard/src/booking/migrations/__init__.py create mode 100644 tools/pharos-dashboard/src/booking/models.py create mode 100644 tools/pharos-dashboard/src/booking/tests/__init__.py create mode 100644 tools/pharos-dashboard/src/booking/tests/test_models.py create mode 100644 tools/pharos-dashboard/src/booking/tests/test_views.py create mode 100644 tools/pharos-dashboard/src/booking/urls.py create mode 100644 tools/pharos-dashboard/src/booking/views.py create mode 100644 tools/pharos-dashboard/src/dashboard/__init__.py create mode 100644 tools/pharos-dashboard/src/dashboard/admin.py create mode 100644 tools/pharos-dashboard/src/dashboard/apps.py create mode 100644 tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json create mode 100644 tools/pharos-dashboard/src/dashboard/migrations/__init__.py create mode 100644 tools/pharos-dashboard/src/dashboard/models.py create mode 100644 tools/pharos-dashboard/src/dashboard/tasks.py create mode 100644 tools/pharos-dashboard/src/dashboard/templatetags/__init__.py create mode 100644 tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py create mode 100644 tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py create mode 100644 tools/pharos-dashboard/src/dashboard/urls.py create mode 100644 tools/pharos-dashboard/src/dashboard/views.py create mode 100644 tools/pharos-dashboard/src/jenkins/__init__.py create mode 100644 tools/pharos-dashboard/src/jenkins/adapter.py create mode 100644 tools/pharos-dashboard/src/jenkins/admin.py create mode 100644 tools/pharos-dashboard/src/jenkins/apps.py create mode 100644 tools/pharos-dashboard/src/jenkins/migrations/__init__.py create mode 100644 tools/pharos-dashboard/src/jenkins/models.py create mode 100644 tools/pharos-dashboard/src/jenkins/tasks.py create mode 100644 tools/pharos-dashboard/src/jenkins/tests.py create mode 100644 tools/pharos-dashboard/src/manage.py create mode 100644 tools/pharos-dashboard/src/notification/__init__.py create mode 100644 tools/pharos-dashboard/src/notification/admin.py create mode 100644 tools/pharos-dashboard/src/notification/apps.py create mode 100644 tools/pharos-dashboard/src/notification/migrations/__init__.py create mode 100644 tools/pharos-dashboard/src/notification/models.py create mode 100644 tools/pharos-dashboard/src/notification/signals.py create mode 100644 tools/pharos-dashboard/src/notification/tasks.py create mode 100644 tools/pharos-dashboard/src/notification_framework/__init__.py create mode 100644 tools/pharos-dashboard/src/notification_framework/notification.py create mode 100644 tools/pharos-dashboard/src/pharos_dashboard/__init__.py create mode 100644 tools/pharos-dashboard/src/pharos_dashboard/celery.py create mode 100644 tools/pharos-dashboard/src/pharos_dashboard/settings.py create mode 100644 tools/pharos-dashboard/src/pharos_dashboard/urls.py create mode 100644 tools/pharos-dashboard/src/pharos_dashboard/wsgi.py create mode 100644 tools/pharos-dashboard/src/static/bower.json create mode 100644 tools/pharos-dashboard/src/static/css/theme.css create mode 100644 tools/pharos-dashboard/src/static/js/booking-calendar.js create mode 100644 tools/pharos-dashboard/src/static/js/dataTables-sort.js create mode 100644 tools/pharos-dashboard/src/static/js/datetimepicker-options.js create mode 100644 tools/pharos-dashboard/src/static/js/flot-pie-chart.js create mode 100644 tools/pharos-dashboard/src/static/js/fullcalendar-options.js create mode 100644 tools/pharos-dashboard/src/templates/account/user_list.html create mode 100644 tools/pharos-dashboard/src/templates/account/userprofile_update_form.html create mode 100644 tools/pharos-dashboard/src/templates/base.html create mode 100644 tools/pharos-dashboard/src/templates/booking/booking_calendar.html create mode 100644 tools/pharos-dashboard/src/templates/booking/booking_detail.html create mode 100644 tools/pharos-dashboard/src/templates/booking/booking_table.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/ci_pods.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/dev_pods.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/jenkins_slaves.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/resource.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/resource_all.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/resource_detail.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/server_table.html create mode 100644 tools/pharos-dashboard/src/templates/dashboard/table.html create mode 100644 tools/pharos-dashboard/src/templates/layout.html create mode 100644 tools/pharos-dashboard/src/templates/rest_framework/api.html delete mode 100644 tools/pharos-dashboard/static/bower.json delete mode 100644 tools/pharos-dashboard/static/css/theme.css delete mode 100644 tools/pharos-dashboard/static/js/booking-calendar.js delete mode 100644 tools/pharos-dashboard/static/js/dataTables-sort.js delete mode 100644 tools/pharos-dashboard/static/js/datetimepicker-options.js delete mode 100644 tools/pharos-dashboard/static/js/flot-pie-chart.js delete mode 100644 tools/pharos-dashboard/static/js/fullcalendar-options.js delete mode 100644 tools/pharos-dashboard/templates/account/user_list.html delete mode 100644 tools/pharos-dashboard/templates/account/userprofile_update_form.html delete mode 100644 tools/pharos-dashboard/templates/base.html delete mode 100644 tools/pharos-dashboard/templates/booking/booking_calendar.html delete mode 100644 tools/pharos-dashboard/templates/booking/booking_detail.html delete mode 100644 tools/pharos-dashboard/templates/booking/booking_table.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/ci_pods.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/dev_pods.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/resource.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/resource_all.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/resource_detail.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/server_table.html delete mode 100644 tools/pharos-dashboard/templates/dashboard/table.html delete mode 100644 tools/pharos-dashboard/templates/layout.html delete mode 100644 tools/pharos-dashboard/templates/rest_framework/api.html create mode 100644 tools/pharos-dashboard/web/Dockerfile create mode 100644 tools/pharos-dashboard/web/requirements.txt create mode 100644 tools/pharos-dashboard/worker/Dockerfile create mode 100644 tools/pharos-dashboard/worker/requirements.txt diff --git a/tools/pharos-dashboard/.gitignore b/tools/pharos-dashboard/.gitignore index 2b73909a..4154fdd3 100644 --- a/tools/pharos-dashboard/.gitignore +++ b/tools/pharos-dashboard/.gitignore @@ -19,8 +19,9 @@ coverage.xml # Django: *.log *.pot -migrations/ -settings.py + +# Celery +celerybeat-schedule.db # KDE: .directory @@ -36,3 +37,10 @@ venv/ # Bower Components: bower_components/ + +# Production settings +config.env + +# rsa key files +rsa.pem +rsa.pub diff --git a/tools/pharos-dashboard/Makefile b/tools/pharos-dashboard/Makefile new file mode 100644 index 00000000..e52e15d2 --- /dev/null +++ b/tools/pharos-dashboard/Makefile @@ -0,0 +1,35 @@ +build: + docker-compose build + +up: + docker-compose up -d + +start: + docker-compose start + +stop: + docker-compose stop + +shell-nginx: + docker exec -ti ng01 bash + +shell-web: + docker exec -ti dg01 bash + +shell-db: + docker exec -ti ps01 bash + +log-nginx: + docker-compose logs nginx + +log-web: + docker-compose logs web + +log-ps: + docker-compose logs postgres + +log-rmq: + docker-compose logs rabbitmq + +log-worker: + docker-compose logs worker diff --git a/tools/pharos-dashboard/account/__init__.py b/tools/pharos-dashboard/account/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/account/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/account/admin.py b/tools/pharos-dashboard/account/admin.py deleted file mode 100644 index 18b2e1a8..00000000 --- a/tools/pharos-dashboard/account/admin.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.contrib import admin - -from account.models import UserProfile - -admin.site.register(UserProfile) \ No newline at end of file diff --git a/tools/pharos-dashboard/account/apps.py b/tools/pharos-dashboard/account/apps.py deleted file mode 100644 index 9814648f..00000000 --- a/tools/pharos-dashboard/account/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.apps import AppConfig - - -class AccountsConfig(AppConfig): - name = 'account' diff --git a/tools/pharos-dashboard/account/forms.py b/tools/pharos-dashboard/account/forms.py deleted file mode 100644 index 7653e2b1..00000000 --- a/tools/pharos-dashboard/account/forms.py +++ /dev/null @@ -1,22 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import django.forms as forms -import pytz as pytz - -from account.models import UserProfile - - -class AccountSettingsForm(forms.ModelForm): - class Meta: - model = UserProfile - fields = ['company', 'ssh_public_key', 'pgp_public_key', 'timezone'] - - timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC') diff --git a/tools/pharos-dashboard/account/jira_util.py b/tools/pharos-dashboard/account/jira_util.py deleted file mode 100644 index c333f8c4..00000000 --- a/tools/pharos-dashboard/account/jira_util.py +++ /dev/null @@ -1,66 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import base64 -import os - -import oauth2 as oauth -from jira import JIRA -from tlslite.utils import keyfactory - -from django.conf import settings - - -class SignatureMethod_RSA_SHA1(oauth.SignatureMethod): - name = 'RSA-SHA1' - - def signing_base(self, request, consumer, token): - if not hasattr(request, 'normalized_url') or request.normalized_url is None: - raise ValueError("Base URL for request is not set.") - - sig = ( - oauth.escape(request.method), - oauth.escape(request.normalized_url), - oauth.escape(request.get_normalized_parameters()), - ) - - key = '%s&' % oauth.escape(consumer.secret) - if token: - key += oauth.escape(token.secret) - raw = '&'.join(sig) - return key, raw - - def sign(self, request, consumer, token): - """Builds the base signature string.""" - key, raw = self.signing_base(request, consumer, token) - - module_dir = os.path.dirname(__file__) # get current directory - with open(module_dir + '/rsa.pem', 'r') as f: - data = f.read() - privateKeyString = data.strip() - privatekey = keyfactory.parsePrivateKey(privateKeyString) - raw = str.encode(raw) - signature = privatekey.hashAndSign(raw) - return base64.b64encode(signature) - - -def get_jira(user): - module_dir = os.path.dirname(__file__) # get current directory - with open(module_dir + '/rsa.pem', 'r') as f: - key_cert = f.read() - - oauth_dict = { - 'access_token': user.userprofile.oauth_token, - 'access_token_secret': user.userprofile.oauth_secret, - 'consumer_key': settings.OAUTH_CONSUMER_KEY, - 'key_cert': key_cert - } - - return JIRA(server=settings.JIRA_URL, oauth=oauth_dict) \ No newline at end of file diff --git a/tools/pharos-dashboard/account/middleware.py b/tools/pharos-dashboard/account/middleware.py deleted file mode 100644 index 0f1dbd86..00000000 --- a/tools/pharos-dashboard/account/middleware.py +++ /dev/null @@ -1,32 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.utils import timezone -from django.utils.deprecation import MiddlewareMixin - -from account.models import UserProfile - - -class TimezoneMiddleware(MiddlewareMixin): - """ - Activate the timezone from request.user.userprofile if user is authenticated, - deactivate the timezone otherwise and use default (UTC) - """ - def process_request(self, request): - if request.user.is_authenticated: - try: - tz = request.user.userprofile.timezone - timezone.activate(tz) - except UserProfile.DoesNotExist: - UserProfile.objects.create(user=request.user) - tz = request.user.userprofile.timezone - timezone.activate(tz) - else: - timezone.deactivate() diff --git a/tools/pharos-dashboard/account/migrations/__init__.py b/tools/pharos-dashboard/account/migrations/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/account/migrations/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/account/models.py b/tools/pharos-dashboard/account/models.py deleted file mode 100644 index 621f6697..00000000 --- a/tools/pharos-dashboard/account/models.py +++ /dev/null @@ -1,30 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.db import models - -from django.contrib.auth.models import User - -from dashboard.models import Resource - -def upload_to(object, filename): - return object.user.username + '/' + filename - -class UserProfile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) - timezone = models.CharField(max_length=100, blank=False, default='UTC') - ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) - pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) - company = models.CharField(max_length=200, blank=False) - oauth_token = models.CharField(max_length=1024, blank=False) - oauth_secret = models.CharField(max_length=1024, blank=False) - - class Meta: - db_table = 'user_profile' diff --git a/tools/pharos-dashboard/account/rsa.pem b/tools/pharos-dashboard/account/rsa.pem deleted file mode 100644 index dbd4eedd..00000000 --- a/tools/pharos-dashboard/account/rsa.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V -A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d -7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ -hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H -X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm -uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw -rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z -zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn -qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG -WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno -cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ -3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 -AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 -Lw03eHTNQghS0A== ------END PRIVATE KEY----- - diff --git a/tools/pharos-dashboard/account/rsa.pub b/tools/pharos-dashboard/account/rsa.pub deleted file mode 100644 index cc50e45e..00000000 --- a/tools/pharos-dashboard/account/rsa.pub +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0YjCwIfYoprq/FQO6lb3asXrx -LlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlYzypSRjVxwxrsuRcP3e641SdASwfr -mzyvIgP08N4S0IFzEURkV1wp/IpH7kH41EtbmUmrXSwfNZsnQRE5SYSOhh+LcK2w -yQkdgcMv11l4KoBkcwIDAQAB ------END PUBLIC KEY----- diff --git a/tools/pharos-dashboard/account/tests/__init__.py b/tools/pharos-dashboard/account/tests/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/account/tests/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/account/tests/test_general.py b/tools/pharos-dashboard/account/tests/test_general.py deleted file mode 100644 index 72e7ea11..00000000 --- a/tools/pharos-dashboard/account/tests/test_general.py +++ /dev/null @@ -1,50 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.contrib.auth.models import User -from django.test import Client -from django.test import TestCase -from django.urls import reverse -from django.utils import timezone - -from account.models import UserProfile - - -class AccountMiddlewareTestCase(TestCase): - def setUp(self): - self.client = Client() - self.user1 = User.objects.create(username='user1') - self.user1.set_password('user1') - self.user1profile = UserProfile.objects.create(user=self.user1) - self.user1.save() - - def test_timezone_middleware(self): - """ - The timezone should be UTC for anonymous users, for authenticated users it should be set - to user.userprofile.timezone - """ - #default - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - url = reverse('account:settings') - # anonymous request - self.client.get(url) - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - # authenticated user with UTC timezone (userprofile default) - self.client.login(username='user1', password='user1') - self.client.get(url) - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - # authenticated user with custom timezone (userprofile default) - self.user1profile.timezone = 'Etc/Greenwich' - self.user1profile.save() - self.client.get(url) - self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich') diff --git a/tools/pharos-dashboard/account/urls.py b/tools/pharos-dashboard/account/urls.py deleted file mode 100644 index 3962a0c6..00000000 --- a/tools/pharos-dashboard/account/urls.py +++ /dev/null @@ -1,36 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -"""pharos_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 url - -from account.views import * - -urlpatterns = [ - url(r'^settings/', AccountSettingsView.as_view(), name='settings'), - url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'), - url(r'^login/$', JiraLoginView.as_view(), name='login'), - url(r'^logout/$', JiraLogoutView.as_view(), name='logout'), - url(r'^users/$', UserListView.as_view(), name='users'), -] diff --git a/tools/pharos-dashboard/account/views.py b/tools/pharos-dashboard/account/views.py deleted file mode 100644 index 3b4269de..00000000 --- a/tools/pharos-dashboard/account/views.py +++ /dev/null @@ -1,132 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import os -import urllib - -import oauth2 as oauth -from django.contrib import messages -from django.contrib.auth import logout, authenticate, login -from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.models import User -from django.urls import reverse -from django.utils.decorators import method_decorator -from django.views.generic import RedirectView -from django.views.generic import TemplateView -from django.views.generic import UpdateView -from jira import JIRA - -from account.forms import AccountSettingsForm -from account.jira_util import SignatureMethod_RSA_SHA1 -from account.models import UserProfile -from django.conf import settings - -consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET) - - -@method_decorator(login_required, name='dispatch') -class AccountSettingsView(UpdateView): - model = UserProfile - form_class = AccountSettingsForm - template_name_suffix = '_update_form' - - def get_success_url(self): - messages.add_message(self.request, messages.INFO, - 'Settings saved') - return '/' - - def get_object(self, queryset=None): - return self.request.user.userprofile - - -class JiraLoginView(RedirectView): - def get_redirect_url(self, *args, **kwargs): - client = oauth.Client(consumer) - client.set_signature_method(SignatureMethod_RSA_SHA1()) - - # Step 1. Get a request token from Jira. - resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST") - if resp['status'] != '200': - raise Exception("Invalid response %s: %s" % (resp['status'], content)) - - # Step 2. Store the request token in a session for later use. - self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode())) - # Step 3. Redirect the user to the authentication URL. - url = settings.OAUTH_AUTHORIZE_URL + '?oauth_token=' + \ - self.request.session['request_token']['oauth_token'] + \ - '&oauth_callback=' + settings.OAUTH_CALLBACK_URL - return url - - -class JiraLogoutView(LoginRequiredMixin, RedirectView): - def get_redirect_url(self, *args, **kwargs): - logout(self.request) - return '/' - - -class JiraAuthenticatedView(RedirectView): - def get_redirect_url(self, *args, **kwargs): - # Step 1. Use the request token in the session to build a new client. - token = oauth.Token(self.request.session['request_token']['oauth_token'], - self.request.session['request_token']['oauth_token_secret']) - client = oauth.Client(consumer, token) - client.set_signature_method(SignatureMethod_RSA_SHA1()) - - # Step 2. Request the authorized access token from Jira. - resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST") - if resp['status'] != '200': - return '/' - - access_token = dict(urllib.parse.parse_qsl(content.decode())) - - module_dir = os.path.dirname(__file__) # get current directory - with open(module_dir + '/rsa.pem', 'r') as f: - key_cert = f.read() - - oauth_dict = { - 'access_token': access_token['oauth_token'], - 'access_token_secret': access_token['oauth_token_secret'], - 'consumer_key': settings.OAUTH_CONSUMER_KEY, - 'key_cert': key_cert - } - - jira = JIRA(server=settings.JIRA_URL, oauth=oauth_dict) - username = jira.current_user() - url = '/' - # Step 3. Lookup the user or create them if they don't exist. - try: - user = User.objects.get(username=username) - except User.DoesNotExist: - # Save our permanent token and secret for later. - user = User.objects.create_user(username=username, - password=access_token['oauth_token_secret']) - profile = UserProfile() - profile.user = user - profile.save() - url = reverse('account:settings') - user.userprofile.oauth_token = access_token['oauth_token'] - user.userprofile.oauth_secret = access_token['oauth_token_secret'] - user.userprofile.save() - user.set_password(access_token['oauth_token_secret']) - user.save() - user = authenticate(username=username, password=access_token['oauth_token_secret']) - login(self.request, user) - # redirect user to settings page to complete profile - return url - -class UserListView(TemplateView): - template_name = "account/user_list.html" - - def get_context_data(self, **kwargs): - users = User.objects.all() - context = super(UserListView, self).get_context_data(**kwargs) - context.update({'title': "Dashboard Users", 'users': users}) - return context diff --git a/tools/pharos-dashboard/api/__init__.py b/tools/pharos-dashboard/api/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/api/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/api/serializers.py b/tools/pharos-dashboard/api/serializers.py deleted file mode 100644 index 78e6020e..00000000 --- a/tools/pharos-dashboard/api/serializers.py +++ /dev/null @@ -1,32 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from rest_framework import serializers - -from booking.models import Booking -from dashboard.models import Server, Resource - - -class BookingSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Booking - fields = ('id', 'resource', 'start', 'end', 'purpose') - - -class ServerSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Server - fields = ('id', 'resource', 'name', 'model', 'cpu', 'ram', 'storage') - - -class ResourceSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Resource - fields = ('id', 'name', 'description', 'url', 'server_set') diff --git a/tools/pharos-dashboard/api/urls.py b/tools/pharos-dashboard/api/urls.py deleted file mode 100644 index 5206ac7f..00000000 --- a/tools/pharos-dashboard/api/urls.py +++ /dev/null @@ -1,38 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -"""pharos_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 url, include -from rest_framework import routers - -from api.views import * - -router = routers.DefaultRouter() -router.register(r'resources', ResourceViewSet) -router.register(r'servers', ServerViewSet) -router.register(r'bookings', BookingViewSet) - -urlpatterns = [ - url(r'^', include(router.urls)), -] \ No newline at end of file diff --git a/tools/pharos-dashboard/api/views.py b/tools/pharos-dashboard/api/views.py deleted file mode 100644 index 761ce6e7..00000000 --- a/tools/pharos-dashboard/api/views.py +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from rest_framework import viewsets - -from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer -from booking.models import Booking -from dashboard.models import Resource, Server - - -class BookingViewSet(viewsets.ModelViewSet): - queryset = Booking.objects.all() - serializer_class = BookingSerializer - filter_fields = ('resource', 'user') - - -class ServerViewSet(viewsets.ModelViewSet): - queryset = Server.objects.all() - serializer_class = ServerSerializer - filter_fields = ('resource', 'name') - - -class ResourceViewSet(viewsets.ModelViewSet): - queryset = Resource.objects.all() - serializer_class = ResourceSerializer - filter_fields = ('name',) \ No newline at end of file diff --git a/tools/pharos-dashboard/booking/__init__.py b/tools/pharos-dashboard/booking/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/booking/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/booking/admin.py b/tools/pharos-dashboard/booking/admin.py deleted file mode 100644 index 7a7f251a..00000000 --- a/tools/pharos-dashboard/booking/admin.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.contrib import admin - -from booking.models import Booking - -admin.site.register(Booking) \ No newline at end of file diff --git a/tools/pharos-dashboard/booking/apps.py b/tools/pharos-dashboard/booking/apps.py deleted file mode 100644 index 99bf115f..00000000 --- a/tools/pharos-dashboard/booking/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.apps import AppConfig - - -class BookingConfig(AppConfig): - name = 'booking' diff --git a/tools/pharos-dashboard/booking/forms.py b/tools/pharos-dashboard/booking/forms.py deleted file mode 100644 index 02ac887a..00000000 --- a/tools/pharos-dashboard/booking/forms.py +++ /dev/null @@ -1,19 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import django.forms as forms - - -class BookingForm(forms.Form): - fields = ['start', 'end', 'purpose'] - - start = forms.DateTimeField() - end = forms.DateTimeField() - purpose = forms.CharField(max_length=300) \ No newline at end of file diff --git a/tools/pharos-dashboard/booking/migrations/__init__.py b/tools/pharos-dashboard/booking/migrations/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/booking/migrations/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/booking/models.py b/tools/pharos-dashboard/booking/models.py deleted file mode 100644 index 200dc830..00000000 --- a/tools/pharos-dashboard/booking/models.py +++ /dev/null @@ -1,75 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.contrib.auth.models import User -from django.db import models -from jira import JIRA -from jira import JIRAError - -from dashboard.models import Resource -from django.conf import settings - - -class Booking(models.Model): - id = models.AutoField(primary_key=True) - user = models.ForeignKey(User, models.CASCADE) # delete if user is deleted - resource = models.ForeignKey(Resource, models.PROTECT) - start = models.DateTimeField() - end = models.DateTimeField() - jira_issue_id = models.IntegerField(null=True) - jira_issue_status = models.CharField(max_length=50) - - purpose = models.CharField(max_length=300, blank=False) - - class Meta: - db_table = 'booking' - - def get_jira_issue(self): - try: - jira = JIRA(server=settings.JIRA_URL, - basic_auth=(settings.JIRA_USER_NAME, settings.JIRA_USER_PASSWORD)) - issue = jira.issue(self.jira_issue_id) - return issue - except JIRAError: - return None - - def authorization_test(self): - """ - Return True if self.user is authorized to make this booking. - """ - user = self.user - # Check if User is troubleshooter / admin - if user.has_perm('booking.add_booking'): - return True - # Check if User owns this resource - if user == self.resource.owner: - return True - return False - - def save(self, *args, **kwargs): - """ - Save the booking if self.user is authorized and there is no overlapping booking. - Raise PermissionError if the user is not authorized - Raise ValueError if there is an overlapping booking - """ - if not self.authorization_test(): - raise PermissionError('Insufficient permissions to save this booking.') - if self.start >= self.end: - raise ValueError('Start date is after end date') - # conflicts end after booking starts, and start before booking ends - conflicting_dates = Booking.objects.filter(resource=self.resource).exclude(id=self.id) - conflicting_dates = conflicting_dates.filter(end__gt=self.start) - conflicting_dates = conflicting_dates.filter(start__lt=self.end) - if conflicting_dates.count() > 0: - raise ValueError('This booking overlaps with another booking') - return super(Booking, self).save(*args, **kwargs) - - def __str__(self): - return str(self.resource) + ' from ' + str(self.start) + ' until ' + str(self.end) diff --git a/tools/pharos-dashboard/booking/tests/__init__.py b/tools/pharos-dashboard/booking/tests/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/booking/tests/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/booking/tests/test_models.py b/tools/pharos-dashboard/booking/tests/test_models.py deleted file mode 100644 index 612b35c0..00000000 --- a/tools/pharos-dashboard/booking/tests/test_models.py +++ /dev/null @@ -1,107 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from datetime import timedelta - -from django.contrib.auth.models import User, Permission -from django.test import TestCase -from django.utils import timezone - -from account.models import UserProfile -from booking.models import Booking -from dashboard.models import Resource -from jenkins.models import JenkinsSlave - - -class BookingModelTestCase(TestCase): - def setUp(self): - self.slave = JenkinsSlave.objects.create(name='test', url='test') - self.owner = User.objects.create(username='owner') - - self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', - url='x',owner=self.owner) - self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x', - url='x',owner=self.owner) - - self.user1 = User.objects.create(username='user1') - - self.add_booking_perm = Permission.objects.get(codename='add_booking') - self.user1.user_permissions.add(self.add_booking_perm) - - self.user1 = User.objects.get(pk=self.user1.id) - - def test_start__end(self): - """ - if the start of a booking is greater or equal then the end, saving should raise a - ValueException - """ - start = timezone.now() - end = start - timedelta(weeks=1) - self.assertRaises(ValueError, Booking.objects.create, start=start, end=end, - resource=self.res1, user=self.user1) - end = start - self.assertRaises(ValueError, Booking.objects.create, start=start, end=end, - resource=self.res1, user=self.user1) - - def test_conflicts(self): - """ - saving an overlapping booking on the same resource should raise a ValueException - saving for different resources should succeed - """ - start = timezone.now() - end = start + timedelta(weeks=1) - self.assertTrue( - Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)) - - self.assertRaises(ValueError, Booking.objects.create, start=start, - end=end, resource=self.res1, user=self.user1) - self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1), - end=end - timedelta(days=1), resource=self.res1, user=self.user1) - - self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1), - end=end, resource=self.res1, user=self.user1) - self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1), - end=end - timedelta(days=1), resource=self.res1, user=self.user1) - - self.assertRaises(ValueError, Booking.objects.create, start=start, - end=end + timedelta(days=1), resource=self.res1, user=self.user1) - self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1), - end=end + timedelta(days=1), resource=self.res1, user=self.user1) - - self.assertTrue(Booking.objects.create(start=start - timedelta(days=1), end=start, - user=self.user1, resource=self.res1)) - self.assertTrue(Booking.objects.create(start=end, end=end + timedelta(days=1), - user=self.user1, resource=self.res1)) - - self.assertTrue( - Booking.objects.create(start=start - timedelta(days=2), end=start - timedelta(days=1), - user=self.user1, resource=self.res1)) - self.assertTrue( - Booking.objects.create(start=end + timedelta(days=1), end=end + timedelta(days=2), - user=self.user1, resource=self.res1)) - self.assertTrue( - Booking.objects.create(start=start, end=end, - user=self.user1, resource=self.res2)) - - def test_authorization(self): - user = User.objects.create(username='user') - user.userprofile = UserProfile.objects.create(user=user) - self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(), - end=timezone.now() + timedelta(days=1), resource=self.res1, user=user) - self.res1.owner = user - self.assertTrue( - Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1), - resource=self.res1, user=user)) - self.res1.owner = self.owner - user.user_permissions.add(self.add_booking_perm) - user = User.objects.get(pk=user.id) - self.assertTrue( - Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1), - resource=self.res2, user=user)) diff --git a/tools/pharos-dashboard/booking/tests/test_views.py b/tools/pharos-dashboard/booking/tests/test_views.py deleted file mode 100644 index e568c155..00000000 --- a/tools/pharos-dashboard/booking/tests/test_views.py +++ /dev/null @@ -1,81 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from datetime import timedelta - -from django.contrib import auth -from django.test import Client -from django.utils import timezone -from django.contrib.auth.models import Permission -from django.test import TestCase -from django.urls import reverse -from django.utils.encoding import force_text -from registration.forms import User - -from account.models import UserProfile -from booking.models import Booking -from dashboard.models import Resource -from jenkins.models import JenkinsSlave - - -class BookingViewTestCase(TestCase): - def setUp(self): - self.client = Client() - self.slave = JenkinsSlave.objects.create(name='test', url='test') - self.owner = User.objects.create(username='owner') - self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', - url='x',owner=self.owner) - self.user1 = User.objects.create(username='user1') - self.user1.set_password('user1') - self.user1profile = UserProfile.objects.create(user=self.user1) - self.user1.save() - - self.add_booking_perm = Permission.objects.get(codename='add_booking') - self.user1.user_permissions.add(self.add_booking_perm) - - self.user1 = User.objects.get(pk=self.user1.id) - - - def test_resource_bookings_json(self): - url = reverse('booking:bookings_json', kwargs={'resource_id': 0}) - self.assertEqual(self.client.get(url).status_code, 404) - - url = reverse('booking:bookings_json', kwargs={'resource_id': self.res1.id}) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(force_text(response.content), {"bookings": []}) - booking1 = Booking.objects.create(start=timezone.now(), - end=timezone.now() + timedelta(weeks=1), user=self.user1, - resource=self.res1) - response = self.client.get(url) - json = response.json() - self.assertEqual(response.status_code, 200) - self.assertIn('bookings', json) - self.assertEqual(len(json['bookings']), 1) - self.assertIn('start', json['bookings'][0]) - self.assertIn('end', json['bookings'][0]) - self.assertIn('id', json['bookings'][0]) - self.assertIn('purpose', json['bookings'][0]) - - def test_booking_form_view(self): - url = reverse('booking:create', kwargs={'resource_id': 0}) - self.assertEqual(self.client.get(url).status_code, 404) - - # authenticated user - url = reverse('booking:create', kwargs={'resource_id': self.res1.id}) - self.client.login(username='user1',password='user1') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed('booking/booking_calendar.html') - self.assertTemplateUsed('booking/booking_form.html') - self.assertIn('resource', response.context) - - - diff --git a/tools/pharos-dashboard/booking/urls.py b/tools/pharos-dashboard/booking/urls.py deleted file mode 100644 index 53206233..00000000 --- a/tools/pharos-dashboard/booking/urls.py +++ /dev/null @@ -1,37 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -"""pharos_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 url - -from booking.views import * - -urlpatterns = [ - url(r'^(?P[0-9]+)/$', BookingFormView.as_view(), name='create'), - url(r'^(?P[0-9]+)/bookings_json/$', ResourceBookingsJSON.as_view(), - name='bookings_json'), - - url(r'^detail/$', BookingView.as_view(), name='detail_prefix'), - url(r'^detail/(?P[0-9]+)/$', BookingView.as_view(), name='detail'), -] diff --git a/tools/pharos-dashboard/booking/views.py b/tools/pharos-dashboard/booking/views.py deleted file mode 100644 index 2fe167af..00000000 --- a/tools/pharos-dashboard/booking/views.py +++ /dev/null @@ -1,113 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from datetime import timedelta - -from django.conf import settings -from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin -from django.http import JsonResponse -from django.shortcuts import get_object_or_404 -from django.shortcuts import redirect -from django.urls import reverse -from django.utils import timezone -from django.views import View -from django.views.generic import FormView -from django.views.generic import TemplateView -from jira import JIRAError - -from account.jira_util import get_jira -from booking.forms import BookingForm -from booking.models import Booking -from dashboard.models import Resource - - -def create_jira_ticket(user, booking): - jira = get_jira(user) - issue_dict = { - 'project': 'PHAROS', - 'summary': str(booking.resource) + ': Access Request', - 'description': booking.purpose, - 'issuetype': {'name': 'Task'}, - 'components': [{'name': 'POD Access Request'}], - 'assignee': {'name': booking.resource.owner.username} - } - issue = jira.create_issue(fields=issue_dict) - jira.add_attachment(issue, user.userprofile.pgp_public_key) - jira.add_attachment(issue, user.userprofile.ssh_public_key) - booking.jira_issue_id = issue.id - booking.save() - - -class BookingFormView(LoginRequiredMixin, FormView): - template_name = "booking/booking_calendar.html" - form_class = BookingForm - - def dispatch(self, request, *args, **kwargs): - self.resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) - return super(BookingFormView, self).dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - title = 'Booking: ' + self.resource.name - context = super(BookingFormView, self).get_context_data(**kwargs) - context.update({'title': title, 'resource': self.resource}) - return context - - def get_success_url(self): - return reverse('booking:create', kwargs=self.kwargs) - - def form_valid(self, form): - user = self.request.user - if not user.userprofile.ssh_public_key or not user.userprofile.pgp_public_key: - messages.add_message(self.request, messages.INFO, - 'Please upload your private keys before booking') - return redirect('account:settings') - booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'], - purpose=form.cleaned_data['purpose'], resource=self.resource, - user=user) - try: - booking.save() - except ValueError as err: - messages.add_message(self.request, messages.ERROR, err) - return super(BookingFormView, self).form_invalid(form) - except PermissionError as err: - messages.add_message(self.request, messages.ERROR, err) - return super(BookingFormView, self).form_invalid(form) - try: - if settings.CREATE_JIRA_TICKET: - create_jira_ticket(user, booking) - except JIRAError: - messages.add_message(self.request, messages.ERROR, 'Failed to create Jira Ticket. ' - 'Please check your Jira ' - 'permissions.') - booking.delete() - return super(BookingFormView, self).form_invalid(form) - messages.add_message(self.request, messages.SUCCESS, 'Booking saved') - return super(BookingFormView, self).form_valid(form) - - -class BookingView(TemplateView): - template_name = "booking/booking_detail.html" - - def get_context_data(self, **kwargs): - booking = get_object_or_404(Booking, id=self.kwargs['booking_id']) - jira_issue = booking.get_jira_issue() - title = 'Booking Details' - context = super(BookingView, self).get_context_data(**kwargs) - context.update({'title': title, 'booking': booking, 'jira_issue': jira_issue}) - return context - - -class ResourceBookingsJSON(View): - def get(self, request, *args, **kwargs): - resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) - bookings = resource.booking_set.get_queryset().values('id', 'start', 'end', 'purpose', - 'jira_issue_status') - return JsonResponse({'bookings': list(bookings)}) \ No newline at end of file diff --git a/tools/pharos-dashboard/celerybeat-schedule b/tools/pharos-dashboard/celerybeat-schedule deleted file mode 100644 index 7e5fe853..00000000 Binary files a/tools/pharos-dashboard/celerybeat-schedule and /dev/null differ diff --git a/tools/pharos-dashboard/config.env.sample b/tools/pharos-dashboard/config.env.sample new file mode 100644 index 00000000..bd936164 --- /dev/null +++ b/tools/pharos-dashboard/config.env.sample @@ -0,0 +1,18 @@ +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG=False + +DB_NAME=sample_name +DB_USER=sample_user +DB_PASS=sample_pass +DB_SERVICE=postgres +DB_PORT=5432 + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY=http://www.miniwebtool.com/django-secret-key-generator/ + +OAUTH_CONSUMER_KEY=sample_key +OAUTH_CONSUMER_SECRET=sample_secret + +JIRA_URL=sample_url +JIRA_USER_NAME=sample_jira_user +JIRA_USER_PASSWORD=sample_jira_pass \ No newline at end of file diff --git a/tools/pharos-dashboard/config/nginx/pharos_dashboard.conf b/tools/pharos-dashboard/config/nginx/pharos_dashboard.conf new file mode 100644 index 00000000..87b6f8e8 --- /dev/null +++ b/tools/pharos-dashboard/config/nginx/pharos_dashboard.conf @@ -0,0 +1,24 @@ +upstream web { + ip_hash; + server web:8000; +} + +# portal +server { + listen 80; + server_name localhost; + charset utf-8; + + location /static { + alias /static; + } + + location /media { + alias /media; + } + + location / { + proxy_set_header Host $host; + proxy_pass http://web/; + } +} diff --git a/tools/pharos-dashboard/config/postgres/docker-entrypoint-initdb.d/pharos_dashboard.sh b/tools/pharos-dashboard/config/postgres/docker-entrypoint-initdb.d/pharos_dashboard.sh new file mode 100755 index 00000000..526228a8 --- /dev/null +++ b/tools/pharos-dashboard/config/postgres/docker-entrypoint-initdb.d/pharos_dashboard.sh @@ -0,0 +1,14 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +#!/bin/env bash + +psql -U postgres -c "CREATE USER $DB_USER PASSWORD '$DB_PASS'" +psql -U postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER" diff --git a/tools/pharos-dashboard/dashboard/__init__.py b/tools/pharos-dashboard/dashboard/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/dashboard/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/dashboard/admin.py b/tools/pharos-dashboard/dashboard/admin.py deleted file mode 100644 index a1463a7a..00000000 --- a/tools/pharos-dashboard/dashboard/admin.py +++ /dev/null @@ -1,16 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.contrib import admin - -from dashboard.models import * - -admin.site.register(Resource) -admin.site.register(Server) diff --git a/tools/pharos-dashboard/dashboard/apps.py b/tools/pharos-dashboard/dashboard/apps.py deleted file mode 100644 index e0c4f442..00000000 --- a/tools/pharos-dashboard/dashboard/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.apps import AppConfig - - -class DashboardConfig(AppConfig): - name = 'dashboard' diff --git a/tools/pharos-dashboard/dashboard/fixtures/dashboard.json b/tools/pharos-dashboard/dashboard/fixtures/dashboard.json deleted file mode 100644 index f0ac3b2f..00000000 --- a/tools/pharos-dashboard/dashboard/fixtures/dashboard.json +++ /dev/null @@ -1,164 +0,0 @@ -[ -{ - "model": "dashboard.resource", - "pk": 1, - "fields": { - "name": "Linux Foundation POD 1", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab" - } -}, -{ - "model": "dashboard.resource", - "pk": 2, - "fields": { - "name": "Linux Foundation POD 2", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab" - } -}, -{ - "model": "dashboard.resource", - "pk": 3, - "fields": { - "name": "Ericsson POD 2", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process" - } -}, -{ - "model": "dashboard.resource", - "pk": 4, - "fields": { - "name": "Intel POD 2", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2" - } -}, -{ - "model": "dashboard.resource", - "pk": 5, - "fields": { - "name": "Intel POD 5", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5" - } -}, -{ - "model": "dashboard.resource", - "pk": 6, - "fields": { - "name": "Intel POD 6", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6" - } -}, -{ - "model": "dashboard.resource", - "pk": 7, - "fields": { - "name": "Intel POD 8", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8" - } -}, -{ - "model": "dashboard.resource", - "pk": 8, - "fields": { - "name": "Huawei POD 1", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" - } -}, -{ - "model": "dashboard.resource", - "pk": 9, - "fields": { - "name": "Intel POD 3", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3" - } -}, -{ - "model": "dashboard.resource", - "pk": 10, - "fields": { - "name": "Dell POD 1", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting" - } -}, -{ - "model": "dashboard.resource", - "pk": 11, - "fields": { - "name": "Dell POD 2", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting" - } -}, -{ - "model": "dashboard.resource", - "pk": 12, - "fields": { - "name": "Orange POD 2", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2" - } -}, -{ - "model": "dashboard.resource", - "pk": 13, - "fields": { - "name": "Arm POD 1", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab" - } -}, -{ - "model": "dashboard.resource", - "pk": 14, - "fields": { - "name": "Ericsson POD 1", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process" - } -}, -{ - "model": "dashboard.resource", - "pk": 15, - "fields": { - "name": "Huawei POD 2", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" - } -}, -{ - "model": "dashboard.resource", - "pk": 16, - "fields": { - "name": "Huawei POD 3", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" - } -}, -{ - "model": "dashboard.resource", - "pk": 17, - "fields": { - "name": "Huawei POD 4", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" - } -}, -{ - "model": "dashboard.resource", - "pk": 18, - "fields": { - "name": "Intel POD 9", - "description": "Some description", - "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9" - } -} -] diff --git a/tools/pharos-dashboard/dashboard/migrations/__init__.py b/tools/pharos-dashboard/dashboard/migrations/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/dashboard/migrations/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/dashboard/models.py b/tools/pharos-dashboard/dashboard/models.py deleted file mode 100644 index 050834ea..00000000 --- a/tools/pharos-dashboard/dashboard/models.py +++ /dev/null @@ -1,81 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from datetime import timedelta - -from django.utils import timezone -from django.contrib.auth.models import User -from django.db import models - -from jenkins.models import JenkinsSlave - - -class Resource(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=100, unique=True) - description = models.CharField(max_length=300, blank=True, null=True) - url = models.CharField(max_length=100, blank=True, null=True) - owner = models.ForeignKey(User, related_name='user_lab_owner', null=True) - vpn_users = models.ManyToManyField(User, related_name='user_vpn_users') - slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True) - - def get_booking_utilization(self, weeks): - """ - Return a dictionary containing the count of booked and free seconds for a resource in the - range [now,now + weeks] if weeks is positive, - or [now-weeks, now] if weeks is negative - """ - - length = timedelta(weeks=abs(weeks)) - now = timezone.now() - - start = now - end = now + length - if weeks < 0: - start = now - length - end = now - - bookings = self.booking_set.filter(start__lt=start + length, end__gt=start) - - booked_seconds = 0 - for booking in bookings: - booking_start = booking.start - booking_end = booking.end - if booking_start < start: - booking_start = start - if booking_end > end: - booking_end = start + length - total = booking_end - booking_start - booked_seconds += total.total_seconds() - - return {'booked_seconds': booked_seconds, - 'available_seconds': length.total_seconds() - booked_seconds} - - class Meta: - db_table = 'resource' - - def __str__(self): - return self.name - - -class Server(models.Model): - id = models.AutoField(primary_key=True) - resource = models.ForeignKey(Resource, on_delete=models.CASCADE) - name = models.CharField(max_length=100, blank=True) - model = models.CharField(max_length=100, blank=True) - cpu = models.CharField(max_length=100, blank=True) - ram = models.CharField(max_length=100, blank=True) - storage = models.CharField(max_length=100, blank=True) - - class Meta: - db_table = 'server' - - def __str__(self): - return self.name diff --git a/tools/pharos-dashboard/dashboard/tasks.py b/tools/pharos-dashboard/dashboard/tasks.py deleted file mode 100644 index 4c09bf90..00000000 --- a/tools/pharos-dashboard/dashboard/tasks.py +++ /dev/null @@ -1,23 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from celery import shared_task -from datetime import timedelta -from django.utils import timezone - -from jenkins.models import JenkinsStatistic -from notification.models import BookingNotification - - -@shared_task -def database_cleanup(): - now = timezone.now() - JenkinsStatistic.objects.filter(timestamp__lt=now - timedelta(weeks=4)).delete() - BookingNotification.objects.filter(submit_time__lt=now - timedelta(weeks=4)).delete() \ No newline at end of file diff --git a/tools/pharos-dashboard/dashboard/templatetags/__init__.py b/tools/pharos-dashboard/dashboard/templatetags/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/dashboard/templatetags/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py b/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py deleted file mode 100644 index e7e14257..00000000 --- a/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py +++ /dev/null @@ -1,38 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.template.defaultfilters import register - - -@register.filter -def jenkins_job_color(job_result): - if job_result == 'SUCCESS': - return '#5cb85c' - if job_result == 'FAILURE': - return '#d9534f' - if job_result == 'UNSTABLE': - return '#EDD62B' - return '#646F73' # job is still building - - -@register.filter -def jenkins_status_color(slave_status): - if slave_status == 'offline': - return '#d9534f' - if slave_status == 'online': - return '#5cb85c' - if slave_status == 'online / idle': - return '#5bc0de' - - -@register.filter -def jenkins_job_blink(job_result): - if job_result == '': # job is still building - return 'class=blink_me' diff --git a/tools/pharos-dashboard/dashboard/templatetags/jira_filters.py b/tools/pharos-dashboard/dashboard/templatetags/jira_filters.py deleted file mode 100644 index 70208436..00000000 --- a/tools/pharos-dashboard/dashboard/templatetags/jira_filters.py +++ /dev/null @@ -1,18 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.template.defaultfilters import register - -from django.conf import settings - - -@register.filter -def jira_issue_url(issue): - return settings.JIRA_URL + '/browse/' + str(issue) diff --git a/tools/pharos-dashboard/dashboard/urls.py b/tools/pharos-dashboard/dashboard/urls.py deleted file mode 100644 index f04f5ca9..00000000 --- a/tools/pharos-dashboard/dashboard/urls.py +++ /dev/null @@ -1,40 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -"""pharos_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 url -from dashboard.views import * - -urlpatterns = [ - url(r'^ci_pods/$', CIPodsView.as_view(), name='ci_pods'), - url(r'^dev_pods/$', DevelopmentPodsView.as_view(), name='dev_pods'), - url(r'^jenkins_slaves/$', JenkinsSlavesView.as_view(), name='jenkins_slaves'), - url(r'^resource/all/$', LabOwnerView.as_view(), name='resources'), - url(r'^resource/(?P[0-9]+)/$', ResourceView.as_view(), name='resource'), - url(r'^resource/(?P[0-9]+)/booking_utilization/(?P-?\d+)/$', - BookingUtilizationJSON.as_view(), name='booking_utilization'), - url(r'^resource/(?P[0-9]+)/jenkins_utilization/(?P-?\d+)/$', - JenkinsUtilizationJSON.as_view(), name='jenkins_utilization'), - url(r'^$', DevelopmentPodsView.as_view(), name="index"), -] diff --git a/tools/pharos-dashboard/dashboard/views.py b/tools/pharos-dashboard/dashboard/views.py deleted file mode 100644 index 022a4af0..00000000 --- a/tools/pharos-dashboard/dashboard/views.py +++ /dev/null @@ -1,143 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from datetime import timedelta - -from django.http import JsonResponse -from django.shortcuts import get_object_or_404 -from django.utils import timezone -from django.views import View -from django.views.generic import TemplateView - -from booking.models import Booking -from dashboard.models import Resource -from jenkins.models import JenkinsSlave - - -class JenkinsSlavesView(TemplateView): - template_name = "dashboard/jenkins_slaves.html" - - def get_context_data(self, **kwargs): - slaves = JenkinsSlave.objects.all() - context = super(JenkinsSlavesView, self).get_context_data(**kwargs) - context.update({'title': "Jenkins Slaves", 'slaves': slaves}) - return context - - -class CIPodsView(TemplateView): - template_name = "dashboard/ci_pods.html" - - def get_context_data(self, **kwargs): - ci_pods = Resource.objects.filter(slave__ci_slave=True) - context = super(CIPodsView, self).get_context_data(**kwargs) - context.update({'title': "CI Pods", 'ci_pods': ci_pods}) - return context - - -class DevelopmentPodsView(TemplateView): - template_name = "dashboard/dev_pods.html" - - def get_context_data(self, **kwargs): - resources = Resource.objects.filter(slave__dev_pod=True) - - bookings = Booking.objects.filter(start__lte=timezone.now()) - bookings = bookings.filter(end__gt=timezone.now()) - - dev_pods = [] - for resource in resources: - booking_utilization = resource.get_booking_utilization(weeks=4) - total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds'] - try: - utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) / - total * 100) - except (ValueError, ZeroDivisionError): - return "" - - dev_pod = (resource, None, utilization_percentage) - for booking in bookings: - if booking.resource == resource: - dev_pod = (resource, booking, utilization_percentage) - dev_pods.append(dev_pod) - - context = super(DevelopmentPodsView, self).get_context_data(**kwargs) - context.update({'title': "Development Pods", 'dev_pods': dev_pods}) - return context - - -class ResourceView(TemplateView): - template_name = "dashboard/resource.html" - - def get_context_data(self, **kwargs): - resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) - utilization = resource.slave.get_utilization(timedelta(days=7)) - bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now()) - context = super(ResourceView, self).get_context_data(**kwargs) - context.update({'title': str(resource), 'resource': resource, 'utilization': utilization, - 'bookings': bookings}) - return context - - -class LabOwnerView(TemplateView): - template_name = "dashboard/resource_all.html" - - def get_context_data(self, **kwargs): - resources = Resource.objects.filter(slave__dev_pod=True) - pods = [] - for resource in resources: - utilization = resource.slave.get_utilization(timedelta(days=7)) - bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now()) - pods.append((resource, utilization, bookings)) - context = super(LabOwnerView, self).get_context_data(**kwargs) - context.update({'title': "Overview", 'pods': pods}) - return context - - -class BookingUtilizationJSON(View): - def get(self, request, *args, **kwargs): - resource = get_object_or_404(Resource, id=kwargs['resource_id']) - utilization = resource.get_booking_utilization(int(kwargs['weeks'])) - utilization = [ - { - 'label': 'Booked', - 'data': utilization['booked_seconds'], - 'color': '#d9534f' - }, - { - 'label': 'Available', - 'data': utilization['available_seconds'], - 'color': '#5cb85c' - }, - ] - return JsonResponse({'data': utilization}) - - -class JenkinsUtilizationJSON(View): - def get(self, request, *args, **kwargs): - resource = get_object_or_404(Resource, id=kwargs['resource_id']) - weeks = int(kwargs['weeks']) - utilization = resource.slave.get_utilization(timedelta(weeks=weeks)) - utilization = [ - { - 'label': 'Offline', - 'data': utilization['offline'], - 'color': '#d9534f' - }, - { - 'label': 'Online', - 'data': utilization['online'], - 'color': '#5cb85c' - }, - { - 'label': 'Idle', - 'data': utilization['idle'], - 'color': '#5bc0de' - }, - ] - return JsonResponse({'data': utilization}) diff --git a/tools/pharos-dashboard/deploy.org b/tools/pharos-dashboard/deploy.org deleted file mode 100644 index b8399215..00000000 --- a/tools/pharos-dashboard/deploy.org +++ /dev/null @@ -1,45 +0,0 @@ -* Database - -** Setup -- sudo -u postgres psql -- postgres=# CREATE DATABASE pharos_dashboard -- postgres=# CREATE USER opnfv WITH PASSWORD 'opnfvopnfv' -- postgres# createuser --interactive -- postgres# ALTER ROLE opnfv SET client_encoding TO 'utf8'; -- postgres# ALTER ROLE opnfv SET default_transaction_isolation TO 'read committed'; -- postgres# ALTER ROLE opnfv SET timezone TO 'UTC'; -- postgres# GRANT ALL PRIVILEGES ON DATABASE pharos_dashboard TO opnfv; - -** Dump data - -- log out all users, stop server -- (venv) # python manage.py dumpdata > dashboard/fixtures/.json - -** Load dump - -- setup clean database, run migrate -- (venv) # python manage.py loaddata - -* Django - -** Virtualenv setup - -- # virtualenv venv -- # source venv/bin/activate -- (venv) # pip install -r requirements.txt - -** initializing or after change in models.py - -- (venv) # python manage.py makemigrations -- (venv) # python manage.py migrate - -** Development - -- (venv) # python manage.py runserver -- (venv) # python manage.py shell - -* Dependencies - -Javascript / CSS dependencies are managed with bower. To install them, you have to install bower, switch directory to the dashboard/static folder and run -# bower install -Bower will download and install the right versions of all the static files. diff --git a/tools/pharos-dashboard/docker-compose.yml b/tools/pharos-dashboard/docker-compose.yml new file mode 100644 index 00000000..00a8d151 --- /dev/null +++ b/tools/pharos-dashboard/docker-compose.yml @@ -0,0 +1,69 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +version: '2' +services: + nginx: + restart: always + image: nginx:latest + container_name: ng01 + ports: + - "80:80" + volumes: + - ./config/nginx:/etc/nginx/conf.d + - /var/lib/pharos_dashboard/static:/static + - /var/lib/pharos_dashboard/media:/media + depends_on: + - web + + web: + restart: always + build: ./web/ + container_name: dg01 + command: bash -c "python manage.py makemigrations && python manage.py migrate && python manage.py collectstatic --no-input && gunicorn pharos_dashboard.wsgi -b 0.0.0.0:8000" + depends_on: + - postgres + links: + - postgres + env_file: config.env + volumes: + - ./src:/src + - /var/lib/pharos_dashboard/static:/static + - /var/lib/pharos_dashboard/media:/media + expose: + - "8000" + + postgres: + restart: always + image: postgres:latest + container_name: ps01 + env_file: config.env + volumes: + - ./config/postgres/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d + - /var/lib/pharos_dashboard/postgresql:/var/lib/postgresql + + rabbitmq: + restart: always + image: rabbitmq:latest + container_name: rm01 + expose: + - "5672" + + worker: + restart: always + build: ./worker/ + command: bash -c "celery -A pharos_dashboard worker -l info -B" + env_file: config.env + links: + - postgres + - rabbitmq + volumes: + - ./src:/src + \ No newline at end of file diff --git a/tools/pharos-dashboard/jenkins/__init__.py b/tools/pharos-dashboard/jenkins/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/jenkins/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/jenkins/adapter.py b/tools/pharos-dashboard/jenkins/adapter.py deleted file mode 100644 index ff0508d9..00000000 --- a/tools/pharos-dashboard/jenkins/adapter.py +++ /dev/null @@ -1,134 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import logging - -import re -import requests -from django.core.cache import cache - -logger = logging.getLogger(__name__) - -# TODO: implement caching decorator, cache get_* functions -def get_json(url): - if cache.get(url) is None: - try: - response = requests.get(url) - json = response.json() - cache.set(url, json, 180) # cache result for 180 seconds - return json - except requests.exceptions.RequestException as e: - logger.exception(e) - except ValueError as e: - logger.exception(e) - else: - return cache.get(url) - - -def get_all_slaves(): - url = "https://build.opnfv.org/ci/computer/api/json?tree=computer[displayName,offline,idle]" - json = get_json(url) - if json is not None: - return json['computer'] # return list of dictionaries - return [] - - -def get_slave(slavename): - slaves = get_all_slaves() - for slave in slaves: - if slave['displayName'] == slavename: - return slave - return {} - - -def get_ci_slaves(): - url = "https://build.opnfv.org/ci/label/ci-pod/api/json?tree=nodes[nodeName,offline,idle]" - json = get_json(url) - if json is not None: - return json['nodes'] - return [] - - -def get_all_jobs(): - url = "https://build.opnfv.org/ci/api/json?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp,result]]" - json = get_json(url) - if json is not None: - return json['jobs'] # return list of dictionaries - return [] - - -def get_jenkins_job(slavename): - jobs = get_all_jobs() - max_time = 0 - last_job = None - for job in jobs: - if job['lastBuild'] is not None: - if job['lastBuild']['builtOn'] == slavename: - if job['lastBuild']['building'] is True: - return job # return active build - if job['lastBuild']['timestamp'] > max_time: - last_job = job - max_time = job['lastBuild']['timestamp'] - return last_job - - -def is_ci_slave(slavename): - ci_slaves = get_ci_slaves() - for ci_slave in ci_slaves: - if ci_slave['nodeName'] == slavename: - return True - return False - - -def is_dev_pod(slavename): - if is_ci_slave(slavename): - return False - if slavename.find('pod') != -1: - return True - return False - - -def parse_job(job): - result = parse_job_string(job['lastBuild']['fullDisplayName']) - result['building'] = job['lastBuild']['building'] - result['result'] = '' - if not job['lastBuild']['building']: - result['result'] = job['lastBuild']['result'] - result['url'] = job['url'] - return result - - -def parse_job_string(full_displayname): - job = {} - job['scenario'] = '' - job['installer'] = '' - job['branch'] = '' - tokens = re.split(r'[ -]', full_displayname) - for i in range(len(tokens)): - if tokens[i] == 'os': - job['scenario'] = '-'.join(tokens[i: i + 4]) - elif tokens[i] in ['fuel', 'joid', 'apex', 'compass']: - job['installer'] = tokens[i] - elif tokens[i] in ['master', 'arno', 'brahmaputra', 'colorado']: - job['branch'] = tokens[i] - tokens = full_displayname.split(' ') - job['name'] = tokens[0] - return job - -def get_slave_url(slave): - return 'https://build.opnfv.org/ci/computer/' + slave['displayName'] - - -def get_slave_status(slave): - if not slave['offline'] and slave['idle']: - return 'online / idle' - if not slave['offline']: - return 'online' - return 'offline' diff --git a/tools/pharos-dashboard/jenkins/apps.py b/tools/pharos-dashboard/jenkins/apps.py deleted file mode 100644 index 41faf600..00000000 --- a/tools/pharos-dashboard/jenkins/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.apps import AppConfig - - -class JenkinsConfig(AppConfig): - name = 'jenkins' diff --git a/tools/pharos-dashboard/jenkins/migrations/__init__.py b/tools/pharos-dashboard/jenkins/migrations/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/jenkins/migrations/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/jenkins/models.py b/tools/pharos-dashboard/jenkins/models.py deleted file mode 100644 index 0875bba5..00000000 --- a/tools/pharos-dashboard/jenkins/models.py +++ /dev/null @@ -1,60 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.db import models -from django.utils import timezone - - -class JenkinsSlave(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=100, unique=True) - status = models.CharField(max_length=30, default='offline') - url = models.CharField(max_length=1024) - ci_slave = models.BooleanField(default=False) - dev_pod = models.BooleanField(default=False) - - building = models.BooleanField(default=False) - - last_job_name = models.CharField(max_length=1024, default='') - last_job_url = models.CharField(max_length=1024, default='') - last_job_scenario = models.CharField(max_length=50, default='') - last_job_branch = models.CharField(max_length=50, default='') - last_job_installer = models.CharField(max_length=50, default='') - last_job_result = models.CharField(max_length=30, default='') - - def get_utilization(self, timedelta): - """ - Return a dictionary containing the count of idle, online and offline measurements in the time from - now-timedelta to now - """ - utilization = {'idle': 0, 'online': 0, 'offline': 0} - statistics = self.jenkinsstatistic_set.filter(timestamp__gte=timezone.now() - timedelta) - utilization['idle'] = statistics.filter(idle=True).count() - utilization['online'] = statistics.filter(online=True).count() - utilization['offline'] = statistics.filter(offline=True).count() - return utilization - - class Meta: - db_table = 'jenkins_slave' - - def __str__(self): - return self.name - - -class JenkinsStatistic(models.Model): - id = models.AutoField(primary_key=True) - slave = models.ForeignKey(JenkinsSlave, on_delete=models.CASCADE) - offline = models.BooleanField(default=False) - idle = models.BooleanField(default=False) - online = models.BooleanField(default=False) - timestamp = models.DateTimeField(auto_now_add=True) - - class Meta: - db_table = 'jenkins_statistic' diff --git a/tools/pharos-dashboard/jenkins/tasks.py b/tools/pharos-dashboard/jenkins/tasks.py deleted file mode 100644 index 7c037827..00000000 --- a/tools/pharos-dashboard/jenkins/tasks.py +++ /dev/null @@ -1,49 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from celery import shared_task - -from jenkins.models import JenkinsSlave, JenkinsStatistic -from .adapter import * - - -@shared_task -def sync_jenkins(): - update_jenkins_slaves() - - -def update_jenkins_slaves(): - jenkins_slaves = get_all_slaves() - for slave in jenkins_slaves: - jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'], - url=get_slave_url(slave)) - jenkins_slave.ci_slave = is_ci_slave(slave['displayName']) - jenkins_slave.dev_pod = is_dev_pod(slave['displayName']) - jenkins_slave.status = get_slave_status(slave) - - last_job = get_jenkins_job(jenkins_slave.name) - if last_job is not None: - last_job = parse_job(last_job) - jenkins_slave.last_job_name = last_job['name'] - jenkins_slave.last_job_url = last_job['url'] - jenkins_slave.last_job_scenario = last_job['scenario'] - jenkins_slave.last_job_branch = last_job['branch'] - jenkins_slave.last_job_installer = last_job['installer'] - jenkins_slave.last_job_result = last_job['result'] - jenkins_slave.save() - - jenkins_statistic = JenkinsStatistic(slave=jenkins_slave) - if jenkins_slave.status == 'online' or jenkins_slave.status == 'building': - jenkins_statistic.online = True - if jenkins_slave.status == 'offline': - jenkins_statistic.offline = True - if jenkins_slave.status == 'online / idle': - jenkins_statistic.idle = True - jenkins_statistic.save() diff --git a/tools/pharos-dashboard/jenkins/tests.py b/tools/pharos-dashboard/jenkins/tests.py deleted file mode 100644 index 4f350d20..00000000 --- a/tools/pharos-dashboard/jenkins/tests.py +++ /dev/null @@ -1,52 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from unittest import TestCase - -import jenkins.adapter as jenkins - - -# Tests that the data we get with the jenkinsadapter contains all the -# data we need. These test will fail if; -# - there is no internet connection -# - the opnfv jenkins url has changed -# - the jenkins api has changed -# - jenkins is not set up / there is no data -class JenkinsAdapterTestCase(TestCase): - def test_get_all_slaves(self): - slaves = jenkins.get_all_slaves() - self.assertTrue(len(slaves) > 0) - for slave in slaves: - self.assertTrue('displayName' in slave) - self.assertTrue('idle' in slave) - self.assertTrue('offline' in slave) - - def test_get_ci_slaves(self): - slaves = jenkins.get_ci_slaves() - self.assertTrue(len(slaves) > 0) - for slave in slaves: - self.assertTrue('nodeName' in slave) - - def test_get_all_jobs(self): - jobs = jenkins.get_all_jobs() - lastBuild = False - self.assertTrue(len(jobs) > 0) - for job in jobs: - self.assertTrue('displayName' in job) - self.assertTrue('url' in job) - self.assertTrue('lastBuild' in job) - if job['lastBuild'] is not None: - lastBuild = True - self.assertTrue('building' in job['lastBuild']) - self.assertTrue('fullDisplayName' in job['lastBuild']) - self.assertTrue('result' in job['lastBuild']) - self.assertTrue('timestamp' in job['lastBuild']) - self.assertTrue('builtOn' in job['lastBuild']) - self.assertTrue(lastBuild) diff --git a/tools/pharos-dashboard/manage.py b/tools/pharos-dashboard/manage.py deleted file mode 100644 index 80c496f3..00000000 --- a/tools/pharos-dashboard/manage.py +++ /dev/null @@ -1,32 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pharos_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/tools/pharos-dashboard/notification/__init__.py b/tools/pharos-dashboard/notification/__init__.py deleted file mode 100644 index 37dcbddf..00000000 --- a/tools/pharos-dashboard/notification/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -default_app_config = 'notification.apps.NotificationConfig' \ No newline at end of file diff --git a/tools/pharos-dashboard/notification/admin.py b/tools/pharos-dashboard/notification/admin.py deleted file mode 100644 index bcaa1ab7..00000000 --- a/tools/pharos-dashboard/notification/admin.py +++ /dev/null @@ -1,17 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.conf import settings -from django.contrib import admin - -from notification.models import BookingNotification - -if settings.DEBUG: - admin.site.register(BookingNotification) \ No newline at end of file diff --git a/tools/pharos-dashboard/notification/apps.py b/tools/pharos-dashboard/notification/apps.py deleted file mode 100644 index 2de22c4e..00000000 --- a/tools/pharos-dashboard/notification/apps.py +++ /dev/null @@ -1,18 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.apps import AppConfig - - -class NotificationConfig(AppConfig): - name = 'notification' - - def ready(self): - import notification.signals #noqa \ No newline at end of file diff --git a/tools/pharos-dashboard/notification/models.py b/tools/pharos-dashboard/notification/models.py deleted file mode 100644 index 2d199181..00000000 --- a/tools/pharos-dashboard/notification/models.py +++ /dev/null @@ -1,32 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.db import models - -class BookingNotification(models.Model): - id = models.AutoField(primary_key=True) - type = models.CharField(max_length=100) - booking = models.ForeignKey('booking.Booking', on_delete=models.CASCADE) - submit_time = models.DateTimeField() - submitted = models.BooleanField(default=False) - - def get_content(self): - return { - 'start': self.booking.start.isoformat(), - 'end': self.booking.end.isoformat(), - 'user': self.booking.user.username, - 'purpose': self.booking.purpose - } - - def save(self, *args, **kwargs): - notifications = self.booking.bookingnotification_set.filter(type=self.type) - if notifications.count() > 1: - raise ValueError('Doubled Notification') - return super(BookingNotification, self).save(*args, **kwargs) \ No newline at end of file diff --git a/tools/pharos-dashboard/notification/signals.py b/tools/pharos-dashboard/notification/signals.py deleted file mode 100644 index 936c25ba..00000000 --- a/tools/pharos-dashboard/notification/signals.py +++ /dev/null @@ -1,25 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.db.models.signals import post_save -from django.dispatch import receiver - -from booking.models import Booking -from notification.models import BookingNotification - - -@receiver(post_save, sender=Booking) -def booking_notification_handler(sender, instance, **kwargs): - BookingNotification.objects.update_or_create( - booking=instance, type='booking_start', defaults={'submit_time': instance.start} - ) - BookingNotification.objects.update_or_create( - booking=instance, type='booking_end', defaults={'submit_time': instance.end} - ) \ No newline at end of file diff --git a/tools/pharos-dashboard/notification/tasks.py b/tools/pharos-dashboard/notification/tasks.py deleted file mode 100644 index 61ab14af..00000000 --- a/tools/pharos-dashboard/notification/tasks.py +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from celery import shared_task -from datetime import timedelta - -from django.conf import settings -from django.utils import timezone - -from notification.models import BookingNotification -from notification_framework.notification import Notification - - -@shared_task -def send_booking_notifications(): - messaging = Notification(dashboard_url=settings.RABBITMQ_URL) - - now = timezone.now() - notifications = BookingNotification.objects.filter(submitted=False, - submit_time__gt=now, - submit_time__lt=now + timedelta(minutes=5)) - for notification in notifications: - messaging.send(notification.type, notification.booking.resource.name, - notification.get_content()) - notification.submitted = True - notification.save() diff --git a/tools/pharos-dashboard/notification_framework/__init__.py b/tools/pharos-dashboard/notification_framework/__init__.py deleted file mode 100644 index b5914ce7..00000000 --- a/tools/pharos-dashboard/notification_framework/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - diff --git a/tools/pharos-dashboard/notification_framework/notification.py b/tools/pharos-dashboard/notification_framework/notification.py deleted file mode 100644 index 84fbcffa..00000000 --- a/tools/pharos-dashboard/notification_framework/notification.py +++ /dev/null @@ -1,114 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import json -import re - -import pika - - -class Notification(object): - """ - This class can be used by the dashboard and the labs to exchange notifications about booking - events and pod status. It utilizes rabbitmq to communicate. - - Notifications are associated to an event and to a topic. - Events are: - [ 'booking_start', 'booking_stop', 'pod_status' ] - The topic is usually a POD name, ie: - 'Intel POD 2' - """ - - def __init__(self, dashboard_url, verbose=False): - self.rabbitmq_broker = dashboard_url - self.verbose = verbose - self._registry = {} - - self.connection = pika.BlockingConnection(pika.ConnectionParameters( - host=self.rabbitmq_broker)) - self.channel = self.connection.channel() - - self.channel.exchange_declare(exchange='notifications', type='topic') - - self.result = self.channel.queue_declare(exclusive=True) - self.queue_name = self.result.method.queue - - def register(self, function, event, regex): - """ - Registers a function to be called for the specified event. - :param function: the function to register - :param event: the event type - :param regex: a regex to specify for wich topics the function will be called. Some - possible Expressions can be: - 'Intel POD 2' : Intel POD 2 - 'Intel POD .*' : All Intel Pods - '.*' : All Topics - """ - - if event not in self._registry: - self._registry[event] = [(function, regex)] - else: - self._registry[event].append((function, regex)) - - def receive(self): - """ - Start receiving notifications. This is a blocking operation, if a notification is received, - the registered functions will be called. - """ - if self.verbose: - print('Start receiving Notifications. Keys: ', self._registry.keys()) - self._receive_message(self._registry.keys()) - - def send(self, event, topic, content): - """ - Send an event notification. - :param event: the event type - :param topic: the pod name - :param content: a JSON-serializable dictionary - """ - message = { - 'event': event, - 'topic': topic, - 'content': content - } - self._send_message(message) - - def _send_message(self, event): - routing_key = event['type'] - message = json.dumps(event) - self.channel.basic_publish(exchange='notifications', - routing_key=routing_key, - body=message, - properties=pika.BasicProperties( - content_type='application/json' - )) - if self.verbose: - print(" [x] Sent %r:%r" % (routing_key, message)) - - def _receive_message(self, binding_keys): - for key in binding_keys: - self.channel.queue_bind(exchange='notifications', - queue=self.queue_name, - routing_key=key) - self.channel.basic_consume(self._message_callback, - queue=self.queue_name, - no_ack=True) - self.channel.start_consuming() - - def _message_callback(self, ch, method, properties, body): - if self.verbose: - print(" [x] Got %r:%r" % (method.routing_key, body)) - if method.routing_key not in self._registry: - return - for func, regex in self._registry[method.routing_key]: - message = json.loads(body.decode()) - match = re.match(regex, message['topic']) - if match: - func(body) diff --git a/tools/pharos-dashboard/pharos_dashboard/__init__.py b/tools/pharos-dashboard/pharos_dashboard/__init__.py deleted file mode 100644 index f104c4db..00000000 --- a/tools/pharos-dashboard/pharos_dashboard/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -# This will make sure the app is always imported when -# Django starts so that shared_task will use this app. -from .celery import app as celery_app # noqa diff --git a/tools/pharos-dashboard/pharos_dashboard/celery.py b/tools/pharos-dashboard/pharos_dashboard/celery.py deleted file mode 100644 index f60f2433..00000000 --- a/tools/pharos-dashboard/pharos_dashboard/celery.py +++ /dev/null @@ -1,30 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import os - -from celery import Celery - -# set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pharos_dashboard.settings') - -from django.conf import settings # noqa - -app = Celery('pharos_dashboard') - -# Using a string here means the worker will not have to -# pickle the object when using Windows. -app.config_from_object('django.conf:settings') -app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) - - -@app.task(bind=True) -def debug_task(self): - print('Request: {0!r}'.format(self.request)) \ No newline at end of file diff --git a/tools/pharos-dashboard/pharos_dashboard/urls.py b/tools/pharos-dashboard/pharos_dashboard/urls.py deleted file mode 100644 index adcb5b8f..00000000 --- a/tools/pharos-dashboard/pharos_dashboard/urls.py +++ /dev/null @@ -1,44 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -"""pharos_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 import settings -from django.conf.urls import url, include -from django.conf.urls.static import static -from django.contrib import admin - - -urlpatterns = [ - url(r'^', include('dashboard.urls', namespace='dashboard')), - url(r'^booking/', include('booking.urls', namespace='booking')), - url(r'^accounts/', include('account.urls', namespace='account')), - - url(r'^admin/', admin.site.urls), - url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), - - url(r'^api/', include('api.urls')) -] - -if settings.DEBUG is True: - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/tools/pharos-dashboard/pharos_dashboard/wsgi.py b/tools/pharos-dashboard/pharos_dashboard/wsgi.py deleted file mode 100644 index 3d43361b..00000000 --- a/tools/pharos-dashboard/pharos_dashboard/wsgi.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -""" -WSGI config for pharos_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", "pharos_dashboard.settings") - -application = get_wsgi_application() diff --git a/tools/pharos-dashboard/readme.txt b/tools/pharos-dashboard/readme.txt new file mode 100644 index 00000000..b21320f3 --- /dev/null +++ b/tools/pharos-dashboard/readme.txt @@ -0,0 +1,35 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +The dashboard is deployed using docker-compose. +Application / database files are saved in /var/lib/pharos_dashboard/. + +Deployment: + +- clone the repository +- complete the config.env.sample file and save it as config.env +- install docker, docker-compose and bower +- run 'bower install' in ./src/static/ to fetch javascript dependencies +- run 'make build' to build the containers +- run 'make up' to run the dashboard + +Updating: + +- make stop +- git pull +- run 'bower install' if javascript dependencies changed +- make build +- make start + +If there is migrations that need user input (like renaming a field), they need to be run manually! + +Logs / Shell access: + +- there is some shortcuts in the makefile \ No newline at end of file diff --git a/tools/pharos-dashboard/src/account/__init__.py b/tools/pharos-dashboard/src/account/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/account/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/account/admin.py b/tools/pharos-dashboard/src/account/admin.py new file mode 100644 index 00000000..18b2e1a8 --- /dev/null +++ b/tools/pharos-dashboard/src/account/admin.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib import admin + +from account.models import UserProfile + +admin.site.register(UserProfile) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/account/apps.py b/tools/pharos-dashboard/src/account/apps.py new file mode 100644 index 00000000..9814648f --- /dev/null +++ b/tools/pharos-dashboard/src/account/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + name = 'account' diff --git a/tools/pharos-dashboard/src/account/forms.py b/tools/pharos-dashboard/src/account/forms.py new file mode 100644 index 00000000..7653e2b1 --- /dev/null +++ b/tools/pharos-dashboard/src/account/forms.py @@ -0,0 +1,22 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import django.forms as forms +import pytz as pytz + +from account.models import UserProfile + + +class AccountSettingsForm(forms.ModelForm): + class Meta: + model = UserProfile + fields = ['company', 'ssh_public_key', 'pgp_public_key', 'timezone'] + + timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC') diff --git a/tools/pharos-dashboard/src/account/jira_util.py b/tools/pharos-dashboard/src/account/jira_util.py new file mode 100644 index 00000000..c333f8c4 --- /dev/null +++ b/tools/pharos-dashboard/src/account/jira_util.py @@ -0,0 +1,66 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import base64 +import os + +import oauth2 as oauth +from jira import JIRA +from tlslite.utils import keyfactory + +from django.conf import settings + + +class SignatureMethod_RSA_SHA1(oauth.SignatureMethod): + name = 'RSA-SHA1' + + def signing_base(self, request, consumer, token): + if not hasattr(request, 'normalized_url') or request.normalized_url is None: + raise ValueError("Base URL for request is not set.") + + sig = ( + oauth.escape(request.method), + oauth.escape(request.normalized_url), + oauth.escape(request.get_normalized_parameters()), + ) + + key = '%s&' % oauth.escape(consumer.secret) + if token: + key += oauth.escape(token.secret) + raw = '&'.join(sig) + return key, raw + + def sign(self, request, consumer, token): + """Builds the base signature string.""" + key, raw = self.signing_base(request, consumer, token) + + module_dir = os.path.dirname(__file__) # get current directory + with open(module_dir + '/rsa.pem', 'r') as f: + data = f.read() + privateKeyString = data.strip() + privatekey = keyfactory.parsePrivateKey(privateKeyString) + raw = str.encode(raw) + signature = privatekey.hashAndSign(raw) + return base64.b64encode(signature) + + +def get_jira(user): + module_dir = os.path.dirname(__file__) # get current directory + with open(module_dir + '/rsa.pem', 'r') as f: + key_cert = f.read() + + oauth_dict = { + 'access_token': user.userprofile.oauth_token, + 'access_token_secret': user.userprofile.oauth_secret, + 'consumer_key': settings.OAUTH_CONSUMER_KEY, + 'key_cert': key_cert + } + + return JIRA(server=settings.JIRA_URL, oauth=oauth_dict) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/account/middleware.py b/tools/pharos-dashboard/src/account/middleware.py new file mode 100644 index 00000000..0f1dbd86 --- /dev/null +++ b/tools/pharos-dashboard/src/account/middleware.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.utils import timezone +from django.utils.deprecation import MiddlewareMixin + +from account.models import UserProfile + + +class TimezoneMiddleware(MiddlewareMixin): + """ + Activate the timezone from request.user.userprofile if user is authenticated, + deactivate the timezone otherwise and use default (UTC) + """ + def process_request(self, request): + if request.user.is_authenticated: + try: + tz = request.user.userprofile.timezone + timezone.activate(tz) + except UserProfile.DoesNotExist: + UserProfile.objects.create(user=request.user) + tz = request.user.userprofile.timezone + timezone.activate(tz) + else: + timezone.deactivate() diff --git a/tools/pharos-dashboard/src/account/migrations/__init__.py b/tools/pharos-dashboard/src/account/migrations/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/account/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/account/models.py b/tools/pharos-dashboard/src/account/models.py new file mode 100644 index 00000000..621f6697 --- /dev/null +++ b/tools/pharos-dashboard/src/account/models.py @@ -0,0 +1,30 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.db import models + +from django.contrib.auth.models import User + +from dashboard.models import Resource + +def upload_to(object, filename): + return object.user.username + '/' + filename + +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + timezone = models.CharField(max_length=100, blank=False, default='UTC') + ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) + pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) + company = models.CharField(max_length=200, blank=False) + oauth_token = models.CharField(max_length=1024, blank=False) + oauth_secret = models.CharField(max_length=1024, blank=False) + + class Meta: + db_table = 'user_profile' diff --git a/tools/pharos-dashboard/src/account/rsa.pem b/tools/pharos-dashboard/src/account/rsa.pem new file mode 100644 index 00000000..dbd4eedd --- /dev/null +++ b/tools/pharos-dashboard/src/account/rsa.pem @@ -0,0 +1,17 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V +A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d +7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ +hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H +X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm +uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw +rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z +zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn +qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG +WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno +cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ +3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 +AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 +Lw03eHTNQghS0A== +-----END PRIVATE KEY----- + diff --git a/tools/pharos-dashboard/src/account/rsa.pub b/tools/pharos-dashboard/src/account/rsa.pub new file mode 100644 index 00000000..cc50e45e --- /dev/null +++ b/tools/pharos-dashboard/src/account/rsa.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0YjCwIfYoprq/FQO6lb3asXrx +LlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlYzypSRjVxwxrsuRcP3e641SdASwfr +mzyvIgP08N4S0IFzEURkV1wp/IpH7kH41EtbmUmrXSwfNZsnQRE5SYSOhh+LcK2w +yQkdgcMv11l4KoBkcwIDAQAB +-----END PUBLIC KEY----- diff --git a/tools/pharos-dashboard/src/account/tests/__init__.py b/tools/pharos-dashboard/src/account/tests/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/account/tests/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/account/tests/test_general.py b/tools/pharos-dashboard/src/account/tests/test_general.py new file mode 100644 index 00000000..72e7ea11 --- /dev/null +++ b/tools/pharos-dashboard/src/account/tests/test_general.py @@ -0,0 +1,50 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib.auth.models import User +from django.test import Client +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone + +from account.models import UserProfile + + +class AccountMiddlewareTestCase(TestCase): + def setUp(self): + self.client = Client() + self.user1 = User.objects.create(username='user1') + self.user1.set_password('user1') + self.user1profile = UserProfile.objects.create(user=self.user1) + self.user1.save() + + def test_timezone_middleware(self): + """ + The timezone should be UTC for anonymous users, for authenticated users it should be set + to user.userprofile.timezone + """ + #default + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') + + url = reverse('account:settings') + # anonymous request + self.client.get(url) + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') + + # authenticated user with UTC timezone (userprofile default) + self.client.login(username='user1', password='user1') + self.client.get(url) + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') + + # authenticated user with custom timezone (userprofile default) + self.user1profile.timezone = 'Etc/Greenwich' + self.user1profile.save() + self.client.get(url) + self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich') diff --git a/tools/pharos-dashboard/src/account/urls.py b/tools/pharos-dashboard/src/account/urls.py new file mode 100644 index 00000000..3962a0c6 --- /dev/null +++ b/tools/pharos-dashboard/src/account/urls.py @@ -0,0 +1,36 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_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 url + +from account.views import * + +urlpatterns = [ + url(r'^settings/', AccountSettingsView.as_view(), name='settings'), + url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'), + url(r'^login/$', JiraLoginView.as_view(), name='login'), + url(r'^logout/$', JiraLogoutView.as_view(), name='logout'), + url(r'^users/$', UserListView.as_view(), name='users'), +] diff --git a/tools/pharos-dashboard/src/account/views.py b/tools/pharos-dashboard/src/account/views.py new file mode 100644 index 00000000..3b4269de --- /dev/null +++ b/tools/pharos-dashboard/src/account/views.py @@ -0,0 +1,132 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import os +import urllib + +import oauth2 as oauth +from django.contrib import messages +from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.models import User +from django.urls import reverse +from django.utils.decorators import method_decorator +from django.views.generic import RedirectView +from django.views.generic import TemplateView +from django.views.generic import UpdateView +from jira import JIRA + +from account.forms import AccountSettingsForm +from account.jira_util import SignatureMethod_RSA_SHA1 +from account.models import UserProfile +from django.conf import settings + +consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET) + + +@method_decorator(login_required, name='dispatch') +class AccountSettingsView(UpdateView): + model = UserProfile + form_class = AccountSettingsForm + template_name_suffix = '_update_form' + + def get_success_url(self): + messages.add_message(self.request, messages.INFO, + 'Settings saved') + return '/' + + def get_object(self, queryset=None): + return self.request.user.userprofile + + +class JiraLoginView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + client = oauth.Client(consumer) + client.set_signature_method(SignatureMethod_RSA_SHA1()) + + # Step 1. Get a request token from Jira. + resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST") + if resp['status'] != '200': + raise Exception("Invalid response %s: %s" % (resp['status'], content)) + + # Step 2. Store the request token in a session for later use. + self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode())) + # Step 3. Redirect the user to the authentication URL. + url = settings.OAUTH_AUTHORIZE_URL + '?oauth_token=' + \ + self.request.session['request_token']['oauth_token'] + \ + '&oauth_callback=' + settings.OAUTH_CALLBACK_URL + return url + + +class JiraLogoutView(LoginRequiredMixin, RedirectView): + def get_redirect_url(self, *args, **kwargs): + logout(self.request) + return '/' + + +class JiraAuthenticatedView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + # Step 1. Use the request token in the session to build a new client. + token = oauth.Token(self.request.session['request_token']['oauth_token'], + self.request.session['request_token']['oauth_token_secret']) + client = oauth.Client(consumer, token) + client.set_signature_method(SignatureMethod_RSA_SHA1()) + + # Step 2. Request the authorized access token from Jira. + resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST") + if resp['status'] != '200': + return '/' + + access_token = dict(urllib.parse.parse_qsl(content.decode())) + + module_dir = os.path.dirname(__file__) # get current directory + with open(module_dir + '/rsa.pem', 'r') as f: + key_cert = f.read() + + oauth_dict = { + 'access_token': access_token['oauth_token'], + 'access_token_secret': access_token['oauth_token_secret'], + 'consumer_key': settings.OAUTH_CONSUMER_KEY, + 'key_cert': key_cert + } + + jira = JIRA(server=settings.JIRA_URL, oauth=oauth_dict) + username = jira.current_user() + url = '/' + # Step 3. Lookup the user or create them if they don't exist. + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + # Save our permanent token and secret for later. + user = User.objects.create_user(username=username, + password=access_token['oauth_token_secret']) + profile = UserProfile() + profile.user = user + profile.save() + url = reverse('account:settings') + user.userprofile.oauth_token = access_token['oauth_token'] + user.userprofile.oauth_secret = access_token['oauth_token_secret'] + user.userprofile.save() + user.set_password(access_token['oauth_token_secret']) + user.save() + user = authenticate(username=username, password=access_token['oauth_token_secret']) + login(self.request, user) + # redirect user to settings page to complete profile + return url + +class UserListView(TemplateView): + template_name = "account/user_list.html" + + def get_context_data(self, **kwargs): + users = User.objects.all() + context = super(UserListView, self).get_context_data(**kwargs) + context.update({'title': "Dashboard Users", 'users': users}) + return context diff --git a/tools/pharos-dashboard/src/api/__init__.py b/tools/pharos-dashboard/src/api/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/api/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/api/serializers.py b/tools/pharos-dashboard/src/api/serializers.py new file mode 100644 index 00000000..78e6020e --- /dev/null +++ b/tools/pharos-dashboard/src/api/serializers.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from rest_framework import serializers + +from booking.models import Booking +from dashboard.models import Server, Resource + + +class BookingSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Booking + fields = ('id', 'resource', 'start', 'end', 'purpose') + + +class ServerSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Server + fields = ('id', 'resource', 'name', 'model', 'cpu', 'ram', 'storage') + + +class ResourceSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Resource + fields = ('id', 'name', 'description', 'url', 'server_set') diff --git a/tools/pharos-dashboard/src/api/urls.py b/tools/pharos-dashboard/src/api/urls.py new file mode 100644 index 00000000..5206ac7f --- /dev/null +++ b/tools/pharos-dashboard/src/api/urls.py @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_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 url, include +from rest_framework import routers + +from api.views import * + +router = routers.DefaultRouter() +router.register(r'resources', ResourceViewSet) +router.register(r'servers', ServerViewSet) +router.register(r'bookings', BookingViewSet) + +urlpatterns = [ + url(r'^', include(router.urls)), +] \ No newline at end of file diff --git a/tools/pharos-dashboard/src/api/views.py b/tools/pharos-dashboard/src/api/views.py new file mode 100644 index 00000000..761ce6e7 --- /dev/null +++ b/tools/pharos-dashboard/src/api/views.py @@ -0,0 +1,33 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from rest_framework import viewsets + +from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer +from booking.models import Booking +from dashboard.models import Resource, Server + + +class BookingViewSet(viewsets.ModelViewSet): + queryset = Booking.objects.all() + serializer_class = BookingSerializer + filter_fields = ('resource', 'user') + + +class ServerViewSet(viewsets.ModelViewSet): + queryset = Server.objects.all() + serializer_class = ServerSerializer + filter_fields = ('resource', 'name') + + +class ResourceViewSet(viewsets.ModelViewSet): + queryset = Resource.objects.all() + serializer_class = ResourceSerializer + filter_fields = ('name',) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/__init__.py b/tools/pharos-dashboard/src/booking/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/booking/admin.py b/tools/pharos-dashboard/src/booking/admin.py new file mode 100644 index 00000000..7a7f251a --- /dev/null +++ b/tools/pharos-dashboard/src/booking/admin.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib import admin + +from booking.models import Booking + +admin.site.register(Booking) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/apps.py b/tools/pharos-dashboard/src/booking/apps.py new file mode 100644 index 00000000..99bf115f --- /dev/null +++ b/tools/pharos-dashboard/src/booking/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class BookingConfig(AppConfig): + name = 'booking' diff --git a/tools/pharos-dashboard/src/booking/forms.py b/tools/pharos-dashboard/src/booking/forms.py new file mode 100644 index 00000000..02ac887a --- /dev/null +++ b/tools/pharos-dashboard/src/booking/forms.py @@ -0,0 +1,19 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import django.forms as forms + + +class BookingForm(forms.Form): + fields = ['start', 'end', 'purpose'] + + start = forms.DateTimeField() + end = forms.DateTimeField() + purpose = forms.CharField(max_length=300) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/migrations/__init__.py b/tools/pharos-dashboard/src/booking/migrations/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/booking/models.py b/tools/pharos-dashboard/src/booking/models.py new file mode 100644 index 00000000..200dc830 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/models.py @@ -0,0 +1,75 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib.auth.models import User +from django.db import models +from jira import JIRA +from jira import JIRAError + +from dashboard.models import Resource +from django.conf import settings + + +class Booking(models.Model): + id = models.AutoField(primary_key=True) + user = models.ForeignKey(User, models.CASCADE) # delete if user is deleted + resource = models.ForeignKey(Resource, models.PROTECT) + start = models.DateTimeField() + end = models.DateTimeField() + jira_issue_id = models.IntegerField(null=True) + jira_issue_status = models.CharField(max_length=50) + + purpose = models.CharField(max_length=300, blank=False) + + class Meta: + db_table = 'booking' + + def get_jira_issue(self): + try: + jira = JIRA(server=settings.JIRA_URL, + basic_auth=(settings.JIRA_USER_NAME, settings.JIRA_USER_PASSWORD)) + issue = jira.issue(self.jira_issue_id) + return issue + except JIRAError: + return None + + def authorization_test(self): + """ + Return True if self.user is authorized to make this booking. + """ + user = self.user + # Check if User is troubleshooter / admin + if user.has_perm('booking.add_booking'): + return True + # Check if User owns this resource + if user == self.resource.owner: + return True + return False + + def save(self, *args, **kwargs): + """ + Save the booking if self.user is authorized and there is no overlapping booking. + Raise PermissionError if the user is not authorized + Raise ValueError if there is an overlapping booking + """ + if not self.authorization_test(): + raise PermissionError('Insufficient permissions to save this booking.') + if self.start >= self.end: + raise ValueError('Start date is after end date') + # conflicts end after booking starts, and start before booking ends + conflicting_dates = Booking.objects.filter(resource=self.resource).exclude(id=self.id) + conflicting_dates = conflicting_dates.filter(end__gt=self.start) + conflicting_dates = conflicting_dates.filter(start__lt=self.end) + if conflicting_dates.count() > 0: + raise ValueError('This booking overlaps with another booking') + return super(Booking, self).save(*args, **kwargs) + + def __str__(self): + return str(self.resource) + ' from ' + str(self.start) + ' until ' + str(self.end) diff --git a/tools/pharos-dashboard/src/booking/tests/__init__.py b/tools/pharos-dashboard/src/booking/tests/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/tests/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/booking/tests/test_models.py b/tools/pharos-dashboard/src/booking/tests/test_models.py new file mode 100644 index 00000000..612b35c0 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/tests/test_models.py @@ -0,0 +1,107 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.contrib.auth.models import User, Permission +from django.test import TestCase +from django.utils import timezone + +from account.models import UserProfile +from booking.models import Booking +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class BookingModelTestCase(TestCase): + def setUp(self): + self.slave = JenkinsSlave.objects.create(name='test', url='test') + self.owner = User.objects.create(username='owner') + + self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', + url='x',owner=self.owner) + self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x', + url='x',owner=self.owner) + + self.user1 = User.objects.create(username='user1') + + self.add_booking_perm = Permission.objects.get(codename='add_booking') + self.user1.user_permissions.add(self.add_booking_perm) + + self.user1 = User.objects.get(pk=self.user1.id) + + def test_start__end(self): + """ + if the start of a booking is greater or equal then the end, saving should raise a + ValueException + """ + start = timezone.now() + end = start - timedelta(weeks=1) + self.assertRaises(ValueError, Booking.objects.create, start=start, end=end, + resource=self.res1, user=self.user1) + end = start + self.assertRaises(ValueError, Booking.objects.create, start=start, end=end, + resource=self.res1, user=self.user1) + + def test_conflicts(self): + """ + saving an overlapping booking on the same resource should raise a ValueException + saving for different resources should succeed + """ + start = timezone.now() + end = start + timedelta(weeks=1) + self.assertTrue( + Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)) + + self.assertRaises(ValueError, Booking.objects.create, start=start, + end=end, resource=self.res1, user=self.user1) + self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1), + end=end - timedelta(days=1), resource=self.res1, user=self.user1) + + self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1), + end=end, resource=self.res1, user=self.user1) + self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1), + end=end - timedelta(days=1), resource=self.res1, user=self.user1) + + self.assertRaises(ValueError, Booking.objects.create, start=start, + end=end + timedelta(days=1), resource=self.res1, user=self.user1) + self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1), + end=end + timedelta(days=1), resource=self.res1, user=self.user1) + + self.assertTrue(Booking.objects.create(start=start - timedelta(days=1), end=start, + user=self.user1, resource=self.res1)) + self.assertTrue(Booking.objects.create(start=end, end=end + timedelta(days=1), + user=self.user1, resource=self.res1)) + + self.assertTrue( + Booking.objects.create(start=start - timedelta(days=2), end=start - timedelta(days=1), + user=self.user1, resource=self.res1)) + self.assertTrue( + Booking.objects.create(start=end + timedelta(days=1), end=end + timedelta(days=2), + user=self.user1, resource=self.res1)) + self.assertTrue( + Booking.objects.create(start=start, end=end, + user=self.user1, resource=self.res2)) + + def test_authorization(self): + user = User.objects.create(username='user') + user.userprofile = UserProfile.objects.create(user=user) + self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(), + end=timezone.now() + timedelta(days=1), resource=self.res1, user=user) + self.res1.owner = user + self.assertTrue( + Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1), + resource=self.res1, user=user)) + self.res1.owner = self.owner + user.user_permissions.add(self.add_booking_perm) + user = User.objects.get(pk=user.id) + self.assertTrue( + Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1), + resource=self.res2, user=user)) diff --git a/tools/pharos-dashboard/src/booking/tests/test_views.py b/tools/pharos-dashboard/src/booking/tests/test_views.py new file mode 100644 index 00000000..e568c155 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/tests/test_views.py @@ -0,0 +1,81 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.contrib import auth +from django.test import Client +from django.utils import timezone +from django.contrib.auth.models import Permission +from django.test import TestCase +from django.urls import reverse +from django.utils.encoding import force_text +from registration.forms import User + +from account.models import UserProfile +from booking.models import Booking +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class BookingViewTestCase(TestCase): + def setUp(self): + self.client = Client() + self.slave = JenkinsSlave.objects.create(name='test', url='test') + self.owner = User.objects.create(username='owner') + self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', + url='x',owner=self.owner) + self.user1 = User.objects.create(username='user1') + self.user1.set_password('user1') + self.user1profile = UserProfile.objects.create(user=self.user1) + self.user1.save() + + self.add_booking_perm = Permission.objects.get(codename='add_booking') + self.user1.user_permissions.add(self.add_booking_perm) + + self.user1 = User.objects.get(pk=self.user1.id) + + + def test_resource_bookings_json(self): + url = reverse('booking:bookings_json', kwargs={'resource_id': 0}) + self.assertEqual(self.client.get(url).status_code, 404) + + url = reverse('booking:bookings_json', kwargs={'resource_id': self.res1.id}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(force_text(response.content), {"bookings": []}) + booking1 = Booking.objects.create(start=timezone.now(), + end=timezone.now() + timedelta(weeks=1), user=self.user1, + resource=self.res1) + response = self.client.get(url) + json = response.json() + self.assertEqual(response.status_code, 200) + self.assertIn('bookings', json) + self.assertEqual(len(json['bookings']), 1) + self.assertIn('start', json['bookings'][0]) + self.assertIn('end', json['bookings'][0]) + self.assertIn('id', json['bookings'][0]) + self.assertIn('purpose', json['bookings'][0]) + + def test_booking_form_view(self): + url = reverse('booking:create', kwargs={'resource_id': 0}) + self.assertEqual(self.client.get(url).status_code, 404) + + # authenticated user + url = reverse('booking:create', kwargs={'resource_id': self.res1.id}) + self.client.login(username='user1',password='user1') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed('booking/booking_calendar.html') + self.assertTemplateUsed('booking/booking_form.html') + self.assertIn('resource', response.context) + + + diff --git a/tools/pharos-dashboard/src/booking/urls.py b/tools/pharos-dashboard/src/booking/urls.py new file mode 100644 index 00000000..53206233 --- /dev/null +++ b/tools/pharos-dashboard/src/booking/urls.py @@ -0,0 +1,37 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_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 url + +from booking.views import * + +urlpatterns = [ + url(r'^(?P[0-9]+)/$', BookingFormView.as_view(), name='create'), + url(r'^(?P[0-9]+)/bookings_json/$', ResourceBookingsJSON.as_view(), + name='bookings_json'), + + url(r'^detail/$', BookingView.as_view(), name='detail_prefix'), + url(r'^detail/(?P[0-9]+)/$', BookingView.as_view(), name='detail'), +] diff --git a/tools/pharos-dashboard/src/booking/views.py b/tools/pharos-dashboard/src/booking/views.py new file mode 100644 index 00000000..2fe167af --- /dev/null +++ b/tools/pharos-dashboard/src/booking/views.py @@ -0,0 +1,113 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import JsonResponse +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.urls import reverse +from django.utils import timezone +from django.views import View +from django.views.generic import FormView +from django.views.generic import TemplateView +from jira import JIRAError + +from account.jira_util import get_jira +from booking.forms import BookingForm +from booking.models import Booking +from dashboard.models import Resource + + +def create_jira_ticket(user, booking): + jira = get_jira(user) + issue_dict = { + 'project': 'PHAROS', + 'summary': str(booking.resource) + ': Access Request', + 'description': booking.purpose, + 'issuetype': {'name': 'Task'}, + 'components': [{'name': 'POD Access Request'}], + 'assignee': {'name': booking.resource.owner.username} + } + issue = jira.create_issue(fields=issue_dict) + jira.add_attachment(issue, user.userprofile.pgp_public_key) + jira.add_attachment(issue, user.userprofile.ssh_public_key) + booking.jira_issue_id = issue.id + booking.save() + + +class BookingFormView(LoginRequiredMixin, FormView): + template_name = "booking/booking_calendar.html" + form_class = BookingForm + + def dispatch(self, request, *args, **kwargs): + self.resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) + return super(BookingFormView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + title = 'Booking: ' + self.resource.name + context = super(BookingFormView, self).get_context_data(**kwargs) + context.update({'title': title, 'resource': self.resource}) + return context + + def get_success_url(self): + return reverse('booking:create', kwargs=self.kwargs) + + def form_valid(self, form): + user = self.request.user + if not user.userprofile.ssh_public_key or not user.userprofile.pgp_public_key: + messages.add_message(self.request, messages.INFO, + 'Please upload your private keys before booking') + return redirect('account:settings') + booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'], + purpose=form.cleaned_data['purpose'], resource=self.resource, + user=user) + try: + booking.save() + except ValueError as err: + messages.add_message(self.request, messages.ERROR, err) + return super(BookingFormView, self).form_invalid(form) + except PermissionError as err: + messages.add_message(self.request, messages.ERROR, err) + return super(BookingFormView, self).form_invalid(form) + try: + if settings.CREATE_JIRA_TICKET: + create_jira_ticket(user, booking) + except JIRAError: + messages.add_message(self.request, messages.ERROR, 'Failed to create Jira Ticket. ' + 'Please check your Jira ' + 'permissions.') + booking.delete() + return super(BookingFormView, self).form_invalid(form) + messages.add_message(self.request, messages.SUCCESS, 'Booking saved') + return super(BookingFormView, self).form_valid(form) + + +class BookingView(TemplateView): + template_name = "booking/booking_detail.html" + + def get_context_data(self, **kwargs): + booking = get_object_or_404(Booking, id=self.kwargs['booking_id']) + jira_issue = booking.get_jira_issue() + title = 'Booking Details' + context = super(BookingView, self).get_context_data(**kwargs) + context.update({'title': title, 'booking': booking, 'jira_issue': jira_issue}) + return context + + +class ResourceBookingsJSON(View): + def get(self, request, *args, **kwargs): + resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) + bookings = resource.booking_set.get_queryset().values('id', 'start', 'end', 'purpose', + 'jira_issue_status') + return JsonResponse({'bookings': list(bookings)}) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/dashboard/__init__.py b/tools/pharos-dashboard/src/dashboard/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/admin.py b/tools/pharos-dashboard/src/dashboard/admin.py new file mode 100644 index 00000000..a1463a7a --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/admin.py @@ -0,0 +1,16 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib import admin + +from dashboard.models import * + +admin.site.register(Resource) +admin.site.register(Server) diff --git a/tools/pharos-dashboard/src/dashboard/apps.py b/tools/pharos-dashboard/src/dashboard/apps.py new file mode 100644 index 00000000..e0c4f442 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + name = 'dashboard' diff --git a/tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json b/tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json new file mode 100644 index 00000000..f0ac3b2f --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json @@ -0,0 +1,164 @@ +[ +{ + "model": "dashboard.resource", + "pk": 1, + "fields": { + "name": "Linux Foundation POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab" + } +}, +{ + "model": "dashboard.resource", + "pk": 2, + "fields": { + "name": "Linux Foundation POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab" + } +}, +{ + "model": "dashboard.resource", + "pk": 3, + "fields": { + "name": "Ericsson POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process" + } +}, +{ + "model": "dashboard.resource", + "pk": 4, + "fields": { + "name": "Intel POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2" + } +}, +{ + "model": "dashboard.resource", + "pk": 5, + "fields": { + "name": "Intel POD 5", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5" + } +}, +{ + "model": "dashboard.resource", + "pk": 6, + "fields": { + "name": "Intel POD 6", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6" + } +}, +{ + "model": "dashboard.resource", + "pk": 7, + "fields": { + "name": "Intel POD 8", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8" + } +}, +{ + "model": "dashboard.resource", + "pk": 8, + "fields": { + "name": "Huawei POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 9, + "fields": { + "name": "Intel POD 3", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3" + } +}, +{ + "model": "dashboard.resource", + "pk": 10, + "fields": { + "name": "Dell POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 11, + "fields": { + "name": "Dell POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 12, + "fields": { + "name": "Orange POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2" + } +}, +{ + "model": "dashboard.resource", + "pk": 13, + "fields": { + "name": "Arm POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab" + } +}, +{ + "model": "dashboard.resource", + "pk": 14, + "fields": { + "name": "Ericsson POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process" + } +}, +{ + "model": "dashboard.resource", + "pk": 15, + "fields": { + "name": "Huawei POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 16, + "fields": { + "name": "Huawei POD 3", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 17, + "fields": { + "name": "Huawei POD 4", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 18, + "fields": { + "name": "Intel POD 9", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9" + } +} +] diff --git a/tools/pharos-dashboard/src/dashboard/migrations/__init__.py b/tools/pharos-dashboard/src/dashboard/migrations/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/models.py b/tools/pharos-dashboard/src/dashboard/models.py new file mode 100644 index 00000000..050834ea --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/models.py @@ -0,0 +1,81 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.utils import timezone +from django.contrib.auth.models import User +from django.db import models + +from jenkins.models import JenkinsSlave + + +class Resource(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=100, unique=True) + description = models.CharField(max_length=300, blank=True, null=True) + url = models.CharField(max_length=100, blank=True, null=True) + owner = models.ForeignKey(User, related_name='user_lab_owner', null=True) + vpn_users = models.ManyToManyField(User, related_name='user_vpn_users') + slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True) + + def get_booking_utilization(self, weeks): + """ + Return a dictionary containing the count of booked and free seconds for a resource in the + range [now,now + weeks] if weeks is positive, + or [now-weeks, now] if weeks is negative + """ + + length = timedelta(weeks=abs(weeks)) + now = timezone.now() + + start = now + end = now + length + if weeks < 0: + start = now - length + end = now + + bookings = self.booking_set.filter(start__lt=start + length, end__gt=start) + + booked_seconds = 0 + for booking in bookings: + booking_start = booking.start + booking_end = booking.end + if booking_start < start: + booking_start = start + if booking_end > end: + booking_end = start + length + total = booking_end - booking_start + booked_seconds += total.total_seconds() + + return {'booked_seconds': booked_seconds, + 'available_seconds': length.total_seconds() - booked_seconds} + + class Meta: + db_table = 'resource' + + def __str__(self): + return self.name + + +class Server(models.Model): + id = models.AutoField(primary_key=True) + resource = models.ForeignKey(Resource, on_delete=models.CASCADE) + name = models.CharField(max_length=100, blank=True) + model = models.CharField(max_length=100, blank=True) + cpu = models.CharField(max_length=100, blank=True) + ram = models.CharField(max_length=100, blank=True) + storage = models.CharField(max_length=100, blank=True) + + class Meta: + db_table = 'server' + + def __str__(self): + return self.name diff --git a/tools/pharos-dashboard/src/dashboard/tasks.py b/tools/pharos-dashboard/src/dashboard/tasks.py new file mode 100644 index 00000000..4c09bf90 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tasks.py @@ -0,0 +1,23 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from celery import shared_task +from datetime import timedelta +from django.utils import timezone + +from jenkins.models import JenkinsStatistic +from notification.models import BookingNotification + + +@shared_task +def database_cleanup(): + now = timezone.now() + JenkinsStatistic.objects.filter(timestamp__lt=now - timedelta(weeks=4)).delete() + BookingNotification.objects.filter(submit_time__lt=now - timedelta(weeks=4)).delete() \ No newline at end of file diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/__init__.py b/tools/pharos-dashboard/src/dashboard/templatetags/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/templatetags/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py b/tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py new file mode 100644 index 00000000..e7e14257 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.template.defaultfilters import register + + +@register.filter +def jenkins_job_color(job_result): + if job_result == 'SUCCESS': + return '#5cb85c' + if job_result == 'FAILURE': + return '#d9534f' + if job_result == 'UNSTABLE': + return '#EDD62B' + return '#646F73' # job is still building + + +@register.filter +def jenkins_status_color(slave_status): + if slave_status == 'offline': + return '#d9534f' + if slave_status == 'online': + return '#5cb85c' + if slave_status == 'online / idle': + return '#5bc0de' + + +@register.filter +def jenkins_job_blink(job_result): + if job_result == '': # job is still building + return 'class=blink_me' diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py new file mode 100644 index 00000000..70208436 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py @@ -0,0 +1,18 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.template.defaultfilters import register + +from django.conf import settings + + +@register.filter +def jira_issue_url(issue): + return settings.JIRA_URL + '/browse/' + str(issue) diff --git a/tools/pharos-dashboard/src/dashboard/urls.py b/tools/pharos-dashboard/src/dashboard/urls.py new file mode 100644 index 00000000..f04f5ca9 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/urls.py @@ -0,0 +1,40 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_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 url +from dashboard.views import * + +urlpatterns = [ + url(r'^ci_pods/$', CIPodsView.as_view(), name='ci_pods'), + url(r'^dev_pods/$', DevelopmentPodsView.as_view(), name='dev_pods'), + url(r'^jenkins_slaves/$', JenkinsSlavesView.as_view(), name='jenkins_slaves'), + url(r'^resource/all/$', LabOwnerView.as_view(), name='resources'), + url(r'^resource/(?P[0-9]+)/$', ResourceView.as_view(), name='resource'), + url(r'^resource/(?P[0-9]+)/booking_utilization/(?P-?\d+)/$', + BookingUtilizationJSON.as_view(), name='booking_utilization'), + url(r'^resource/(?P[0-9]+)/jenkins_utilization/(?P-?\d+)/$', + JenkinsUtilizationJSON.as_view(), name='jenkins_utilization'), + url(r'^$', DevelopmentPodsView.as_view(), name="index"), +] diff --git a/tools/pharos-dashboard/src/dashboard/views.py b/tools/pharos-dashboard/src/dashboard/views.py new file mode 100644 index 00000000..022a4af0 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/views.py @@ -0,0 +1,143 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.http import JsonResponse +from django.shortcuts import get_object_or_404 +from django.utils import timezone +from django.views import View +from django.views.generic import TemplateView + +from booking.models import Booking +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class JenkinsSlavesView(TemplateView): + template_name = "dashboard/jenkins_slaves.html" + + def get_context_data(self, **kwargs): + slaves = JenkinsSlave.objects.all() + context = super(JenkinsSlavesView, self).get_context_data(**kwargs) + context.update({'title': "Jenkins Slaves", 'slaves': slaves}) + return context + + +class CIPodsView(TemplateView): + template_name = "dashboard/ci_pods.html" + + def get_context_data(self, **kwargs): + ci_pods = Resource.objects.filter(slave__ci_slave=True) + context = super(CIPodsView, self).get_context_data(**kwargs) + context.update({'title': "CI Pods", 'ci_pods': ci_pods}) + return context + + +class DevelopmentPodsView(TemplateView): + template_name = "dashboard/dev_pods.html" + + def get_context_data(self, **kwargs): + resources = Resource.objects.filter(slave__dev_pod=True) + + bookings = Booking.objects.filter(start__lte=timezone.now()) + bookings = bookings.filter(end__gt=timezone.now()) + + dev_pods = [] + for resource in resources: + booking_utilization = resource.get_booking_utilization(weeks=4) + total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds'] + try: + utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) / + total * 100) + except (ValueError, ZeroDivisionError): + return "" + + dev_pod = (resource, None, utilization_percentage) + for booking in bookings: + if booking.resource == resource: + dev_pod = (resource, booking, utilization_percentage) + dev_pods.append(dev_pod) + + context = super(DevelopmentPodsView, self).get_context_data(**kwargs) + context.update({'title': "Development Pods", 'dev_pods': dev_pods}) + return context + + +class ResourceView(TemplateView): + template_name = "dashboard/resource.html" + + def get_context_data(self, **kwargs): + resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) + utilization = resource.slave.get_utilization(timedelta(days=7)) + bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now()) + context = super(ResourceView, self).get_context_data(**kwargs) + context.update({'title': str(resource), 'resource': resource, 'utilization': utilization, + 'bookings': bookings}) + return context + + +class LabOwnerView(TemplateView): + template_name = "dashboard/resource_all.html" + + def get_context_data(self, **kwargs): + resources = Resource.objects.filter(slave__dev_pod=True) + pods = [] + for resource in resources: + utilization = resource.slave.get_utilization(timedelta(days=7)) + bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now()) + pods.append((resource, utilization, bookings)) + context = super(LabOwnerView, self).get_context_data(**kwargs) + context.update({'title': "Overview", 'pods': pods}) + return context + + +class BookingUtilizationJSON(View): + def get(self, request, *args, **kwargs): + resource = get_object_or_404(Resource, id=kwargs['resource_id']) + utilization = resource.get_booking_utilization(int(kwargs['weeks'])) + utilization = [ + { + 'label': 'Booked', + 'data': utilization['booked_seconds'], + 'color': '#d9534f' + }, + { + 'label': 'Available', + 'data': utilization['available_seconds'], + 'color': '#5cb85c' + }, + ] + return JsonResponse({'data': utilization}) + + +class JenkinsUtilizationJSON(View): + def get(self, request, *args, **kwargs): + resource = get_object_or_404(Resource, id=kwargs['resource_id']) + weeks = int(kwargs['weeks']) + utilization = resource.slave.get_utilization(timedelta(weeks=weeks)) + utilization = [ + { + 'label': 'Offline', + 'data': utilization['offline'], + 'color': '#d9534f' + }, + { + 'label': 'Online', + 'data': utilization['online'], + 'color': '#5cb85c' + }, + { + 'label': 'Idle', + 'data': utilization['idle'], + 'color': '#5bc0de' + }, + ] + return JsonResponse({'data': utilization}) diff --git a/tools/pharos-dashboard/src/jenkins/__init__.py b/tools/pharos-dashboard/src/jenkins/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/jenkins/adapter.py b/tools/pharos-dashboard/src/jenkins/adapter.py new file mode 100644 index 00000000..ff0508d9 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/adapter.py @@ -0,0 +1,134 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import logging + +import re +import requests +from django.core.cache import cache + +logger = logging.getLogger(__name__) + +# TODO: implement caching decorator, cache get_* functions +def get_json(url): + if cache.get(url) is None: + try: + response = requests.get(url) + json = response.json() + cache.set(url, json, 180) # cache result for 180 seconds + return json + except requests.exceptions.RequestException as e: + logger.exception(e) + except ValueError as e: + logger.exception(e) + else: + return cache.get(url) + + +def get_all_slaves(): + url = "https://build.opnfv.org/ci/computer/api/json?tree=computer[displayName,offline,idle]" + json = get_json(url) + if json is not None: + return json['computer'] # return list of dictionaries + return [] + + +def get_slave(slavename): + slaves = get_all_slaves() + for slave in slaves: + if slave['displayName'] == slavename: + return slave + return {} + + +def get_ci_slaves(): + url = "https://build.opnfv.org/ci/label/ci-pod/api/json?tree=nodes[nodeName,offline,idle]" + json = get_json(url) + if json is not None: + return json['nodes'] + return [] + + +def get_all_jobs(): + url = "https://build.opnfv.org/ci/api/json?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp,result]]" + json = get_json(url) + if json is not None: + return json['jobs'] # return list of dictionaries + return [] + + +def get_jenkins_job(slavename): + jobs = get_all_jobs() + max_time = 0 + last_job = None + for job in jobs: + if job['lastBuild'] is not None: + if job['lastBuild']['builtOn'] == slavename: + if job['lastBuild']['building'] is True: + return job # return active build + if job['lastBuild']['timestamp'] > max_time: + last_job = job + max_time = job['lastBuild']['timestamp'] + return last_job + + +def is_ci_slave(slavename): + ci_slaves = get_ci_slaves() + for ci_slave in ci_slaves: + if ci_slave['nodeName'] == slavename: + return True + return False + + +def is_dev_pod(slavename): + if is_ci_slave(slavename): + return False + if slavename.find('pod') != -1: + return True + return False + + +def parse_job(job): + result = parse_job_string(job['lastBuild']['fullDisplayName']) + result['building'] = job['lastBuild']['building'] + result['result'] = '' + if not job['lastBuild']['building']: + result['result'] = job['lastBuild']['result'] + result['url'] = job['url'] + return result + + +def parse_job_string(full_displayname): + job = {} + job['scenario'] = '' + job['installer'] = '' + job['branch'] = '' + tokens = re.split(r'[ -]', full_displayname) + for i in range(len(tokens)): + if tokens[i] == 'os': + job['scenario'] = '-'.join(tokens[i: i + 4]) + elif tokens[i] in ['fuel', 'joid', 'apex', 'compass']: + job['installer'] = tokens[i] + elif tokens[i] in ['master', 'arno', 'brahmaputra', 'colorado']: + job['branch'] = tokens[i] + tokens = full_displayname.split(' ') + job['name'] = tokens[0] + return job + +def get_slave_url(slave): + return 'https://build.opnfv.org/ci/computer/' + slave['displayName'] + + +def get_slave_status(slave): + if not slave['offline'] and slave['idle']: + return 'online / idle' + if not slave['offline']: + return 'online' + return 'offline' diff --git a/tools/pharos-dashboard/src/jenkins/admin.py b/tools/pharos-dashboard/src/jenkins/admin.py new file mode 100644 index 00000000..c4996702 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/admin.py @@ -0,0 +1,17 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.conf import settings +from django.contrib import admin + +from jenkins.models import JenkinsSlave + +if settings.DEBUG: + admin.site.register(JenkinsSlave) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/jenkins/apps.py b/tools/pharos-dashboard/src/jenkins/apps.py new file mode 100644 index 00000000..41faf600 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class JenkinsConfig(AppConfig): + name = 'jenkins' diff --git a/tools/pharos-dashboard/src/jenkins/migrations/__init__.py b/tools/pharos-dashboard/src/jenkins/migrations/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/jenkins/models.py b/tools/pharos-dashboard/src/jenkins/models.py new file mode 100644 index 00000000..0875bba5 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/models.py @@ -0,0 +1,60 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.db import models +from django.utils import timezone + + +class JenkinsSlave(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=100, unique=True) + status = models.CharField(max_length=30, default='offline') + url = models.CharField(max_length=1024) + ci_slave = models.BooleanField(default=False) + dev_pod = models.BooleanField(default=False) + + building = models.BooleanField(default=False) + + last_job_name = models.CharField(max_length=1024, default='') + last_job_url = models.CharField(max_length=1024, default='') + last_job_scenario = models.CharField(max_length=50, default='') + last_job_branch = models.CharField(max_length=50, default='') + last_job_installer = models.CharField(max_length=50, default='') + last_job_result = models.CharField(max_length=30, default='') + + def get_utilization(self, timedelta): + """ + Return a dictionary containing the count of idle, online and offline measurements in the time from + now-timedelta to now + """ + utilization = {'idle': 0, 'online': 0, 'offline': 0} + statistics = self.jenkinsstatistic_set.filter(timestamp__gte=timezone.now() - timedelta) + utilization['idle'] = statistics.filter(idle=True).count() + utilization['online'] = statistics.filter(online=True).count() + utilization['offline'] = statistics.filter(offline=True).count() + return utilization + + class Meta: + db_table = 'jenkins_slave' + + def __str__(self): + return self.name + + +class JenkinsStatistic(models.Model): + id = models.AutoField(primary_key=True) + slave = models.ForeignKey(JenkinsSlave, on_delete=models.CASCADE) + offline = models.BooleanField(default=False) + idle = models.BooleanField(default=False) + online = models.BooleanField(default=False) + timestamp = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'jenkins_statistic' diff --git a/tools/pharos-dashboard/src/jenkins/tasks.py b/tools/pharos-dashboard/src/jenkins/tasks.py new file mode 100644 index 00000000..7c037827 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/tasks.py @@ -0,0 +1,49 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from celery import shared_task + +from jenkins.models import JenkinsSlave, JenkinsStatistic +from .adapter import * + + +@shared_task +def sync_jenkins(): + update_jenkins_slaves() + + +def update_jenkins_slaves(): + jenkins_slaves = get_all_slaves() + for slave in jenkins_slaves: + jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'], + url=get_slave_url(slave)) + jenkins_slave.ci_slave = is_ci_slave(slave['displayName']) + jenkins_slave.dev_pod = is_dev_pod(slave['displayName']) + jenkins_slave.status = get_slave_status(slave) + + last_job = get_jenkins_job(jenkins_slave.name) + if last_job is not None: + last_job = parse_job(last_job) + jenkins_slave.last_job_name = last_job['name'] + jenkins_slave.last_job_url = last_job['url'] + jenkins_slave.last_job_scenario = last_job['scenario'] + jenkins_slave.last_job_branch = last_job['branch'] + jenkins_slave.last_job_installer = last_job['installer'] + jenkins_slave.last_job_result = last_job['result'] + jenkins_slave.save() + + jenkins_statistic = JenkinsStatistic(slave=jenkins_slave) + if jenkins_slave.status == 'online' or jenkins_slave.status == 'building': + jenkins_statistic.online = True + if jenkins_slave.status == 'offline': + jenkins_statistic.offline = True + if jenkins_slave.status == 'online / idle': + jenkins_statistic.idle = True + jenkins_statistic.save() diff --git a/tools/pharos-dashboard/src/jenkins/tests.py b/tools/pharos-dashboard/src/jenkins/tests.py new file mode 100644 index 00000000..4f350d20 --- /dev/null +++ b/tools/pharos-dashboard/src/jenkins/tests.py @@ -0,0 +1,52 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from unittest import TestCase + +import jenkins.adapter as jenkins + + +# Tests that the data we get with the jenkinsadapter contains all the +# data we need. These test will fail if; +# - there is no internet connection +# - the opnfv jenkins url has changed +# - the jenkins api has changed +# - jenkins is not set up / there is no data +class JenkinsAdapterTestCase(TestCase): + def test_get_all_slaves(self): + slaves = jenkins.get_all_slaves() + self.assertTrue(len(slaves) > 0) + for slave in slaves: + self.assertTrue('displayName' in slave) + self.assertTrue('idle' in slave) + self.assertTrue('offline' in slave) + + def test_get_ci_slaves(self): + slaves = jenkins.get_ci_slaves() + self.assertTrue(len(slaves) > 0) + for slave in slaves: + self.assertTrue('nodeName' in slave) + + def test_get_all_jobs(self): + jobs = jenkins.get_all_jobs() + lastBuild = False + self.assertTrue(len(jobs) > 0) + for job in jobs: + self.assertTrue('displayName' in job) + self.assertTrue('url' in job) + self.assertTrue('lastBuild' in job) + if job['lastBuild'] is not None: + lastBuild = True + self.assertTrue('building' in job['lastBuild']) + self.assertTrue('fullDisplayName' in job['lastBuild']) + self.assertTrue('result' in job['lastBuild']) + self.assertTrue('timestamp' in job['lastBuild']) + self.assertTrue('builtOn' in job['lastBuild']) + self.assertTrue(lastBuild) diff --git a/tools/pharos-dashboard/src/manage.py b/tools/pharos-dashboard/src/manage.py new file mode 100644 index 00000000..80c496f3 --- /dev/null +++ b/tools/pharos-dashboard/src/manage.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pharos_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/tools/pharos-dashboard/src/notification/__init__.py b/tools/pharos-dashboard/src/notification/__init__.py new file mode 100644 index 00000000..37dcbddf --- /dev/null +++ b/tools/pharos-dashboard/src/notification/__init__.py @@ -0,0 +1,11 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +default_app_config = 'notification.apps.NotificationConfig' \ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/admin.py b/tools/pharos-dashboard/src/notification/admin.py new file mode 100644 index 00000000..bcaa1ab7 --- /dev/null +++ b/tools/pharos-dashboard/src/notification/admin.py @@ -0,0 +1,17 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.conf import settings +from django.contrib import admin + +from notification.models import BookingNotification + +if settings.DEBUG: + admin.site.register(BookingNotification) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/apps.py b/tools/pharos-dashboard/src/notification/apps.py new file mode 100644 index 00000000..2de22c4e --- /dev/null +++ b/tools/pharos-dashboard/src/notification/apps.py @@ -0,0 +1,18 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class NotificationConfig(AppConfig): + name = 'notification' + + def ready(self): + import notification.signals #noqa \ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/migrations/__init__.py b/tools/pharos-dashboard/src/notification/migrations/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/notification/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/notification/models.py b/tools/pharos-dashboard/src/notification/models.py new file mode 100644 index 00000000..2d199181 --- /dev/null +++ b/tools/pharos-dashboard/src/notification/models.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.db import models + +class BookingNotification(models.Model): + id = models.AutoField(primary_key=True) + type = models.CharField(max_length=100) + booking = models.ForeignKey('booking.Booking', on_delete=models.CASCADE) + submit_time = models.DateTimeField() + submitted = models.BooleanField(default=False) + + def get_content(self): + return { + 'start': self.booking.start.isoformat(), + 'end': self.booking.end.isoformat(), + 'user': self.booking.user.username, + 'purpose': self.booking.purpose + } + + def save(self, *args, **kwargs): + notifications = self.booking.bookingnotification_set.filter(type=self.type) + if notifications.count() > 1: + raise ValueError('Doubled Notification') + return super(BookingNotification, self).save(*args, **kwargs) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/signals.py b/tools/pharos-dashboard/src/notification/signals.py new file mode 100644 index 00000000..936c25ba --- /dev/null +++ b/tools/pharos-dashboard/src/notification/signals.py @@ -0,0 +1,25 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.db.models.signals import post_save +from django.dispatch import receiver + +from booking.models import Booking +from notification.models import BookingNotification + + +@receiver(post_save, sender=Booking) +def booking_notification_handler(sender, instance, **kwargs): + BookingNotification.objects.update_or_create( + booking=instance, type='booking_start', defaults={'submit_time': instance.start} + ) + BookingNotification.objects.update_or_create( + booking=instance, type='booking_end', defaults={'submit_time': instance.end} + ) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/tasks.py b/tools/pharos-dashboard/src/notification/tasks.py new file mode 100644 index 00000000..61ab14af --- /dev/null +++ b/tools/pharos-dashboard/src/notification/tasks.py @@ -0,0 +1,33 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from celery import shared_task +from datetime import timedelta + +from django.conf import settings +from django.utils import timezone + +from notification.models import BookingNotification +from notification_framework.notification import Notification + + +@shared_task +def send_booking_notifications(): + messaging = Notification(dashboard_url=settings.RABBITMQ_URL) + + now = timezone.now() + notifications = BookingNotification.objects.filter(submitted=False, + submit_time__gt=now, + submit_time__lt=now + timedelta(minutes=5)) + for notification in notifications: + messaging.send(notification.type, notification.booking.resource.name, + notification.get_content()) + notification.submitted = True + notification.save() diff --git a/tools/pharos-dashboard/src/notification_framework/__init__.py b/tools/pharos-dashboard/src/notification_framework/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/notification_framework/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/notification_framework/notification.py b/tools/pharos-dashboard/src/notification_framework/notification.py new file mode 100644 index 00000000..84fbcffa --- /dev/null +++ b/tools/pharos-dashboard/src/notification_framework/notification.py @@ -0,0 +1,114 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import json +import re + +import pika + + +class Notification(object): + """ + This class can be used by the dashboard and the labs to exchange notifications about booking + events and pod status. It utilizes rabbitmq to communicate. + + Notifications are associated to an event and to a topic. + Events are: + [ 'booking_start', 'booking_stop', 'pod_status' ] + The topic is usually a POD name, ie: + 'Intel POD 2' + """ + + def __init__(self, dashboard_url, verbose=False): + self.rabbitmq_broker = dashboard_url + self.verbose = verbose + self._registry = {} + + self.connection = pika.BlockingConnection(pika.ConnectionParameters( + host=self.rabbitmq_broker)) + self.channel = self.connection.channel() + + self.channel.exchange_declare(exchange='notifications', type='topic') + + self.result = self.channel.queue_declare(exclusive=True) + self.queue_name = self.result.method.queue + + def register(self, function, event, regex): + """ + Registers a function to be called for the specified event. + :param function: the function to register + :param event: the event type + :param regex: a regex to specify for wich topics the function will be called. Some + possible Expressions can be: + 'Intel POD 2' : Intel POD 2 + 'Intel POD .*' : All Intel Pods + '.*' : All Topics + """ + + if event not in self._registry: + self._registry[event] = [(function, regex)] + else: + self._registry[event].append((function, regex)) + + def receive(self): + """ + Start receiving notifications. This is a blocking operation, if a notification is received, + the registered functions will be called. + """ + if self.verbose: + print('Start receiving Notifications. Keys: ', self._registry.keys()) + self._receive_message(self._registry.keys()) + + def send(self, event, topic, content): + """ + Send an event notification. + :param event: the event type + :param topic: the pod name + :param content: a JSON-serializable dictionary + """ + message = { + 'event': event, + 'topic': topic, + 'content': content + } + self._send_message(message) + + def _send_message(self, event): + routing_key = event['type'] + message = json.dumps(event) + self.channel.basic_publish(exchange='notifications', + routing_key=routing_key, + body=message, + properties=pika.BasicProperties( + content_type='application/json' + )) + if self.verbose: + print(" [x] Sent %r:%r" % (routing_key, message)) + + def _receive_message(self, binding_keys): + for key in binding_keys: + self.channel.queue_bind(exchange='notifications', + queue=self.queue_name, + routing_key=key) + self.channel.basic_consume(self._message_callback, + queue=self.queue_name, + no_ack=True) + self.channel.start_consuming() + + def _message_callback(self, ch, method, properties, body): + if self.verbose: + print(" [x] Got %r:%r" % (method.routing_key, body)) + if method.routing_key not in self._registry: + return + for func, regex in self._registry[method.routing_key]: + message = json.loads(body.decode()) + match = re.match(regex, message['topic']) + if match: + func(body) diff --git a/tools/pharos-dashboard/src/pharos_dashboard/__init__.py b/tools/pharos-dashboard/src/pharos_dashboard/__init__.py new file mode 100644 index 00000000..f104c4db --- /dev/null +++ b/tools/pharos-dashboard/src/pharos_dashboard/__init__.py @@ -0,0 +1,13 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app # noqa diff --git a/tools/pharos-dashboard/src/pharos_dashboard/celery.py b/tools/pharos-dashboard/src/pharos_dashboard/celery.py new file mode 100644 index 00000000..f60f2433 --- /dev/null +++ b/tools/pharos-dashboard/src/pharos_dashboard/celery.py @@ -0,0 +1,30 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pharos_dashboard.settings') + +from django.conf import settings # noqa + +app = Celery('pharos_dashboard') + +# Using a string here means the worker will not have to +# pickle the object when using Windows. +app.config_from_object('django.conf:settings') +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) + + +@app.task(bind=True) +def debug_task(self): + print('Request: {0!r}'.format(self.request)) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/pharos_dashboard/settings.py b/tools/pharos-dashboard/src/pharos_dashboard/settings.py new file mode 100644 index 00000000..2c4e8ccf --- /dev/null +++ b/tools/pharos-dashboard/src/pharos_dashboard/settings.py @@ -0,0 +1,176 @@ +import os +from datetime import timedelta + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = os.environ['DEBUG'] + +# Application definition + +INSTALLED_APPS = [ + 'dashboard', + 'booking', + 'account', + 'jenkins', + 'notification', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.humanize', + 'bootstrap3', + 'crispy_forms', + 'rest_framework', +] + +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', + 'account.middleware.TimezoneMiddleware', +] + +ROOT_URLCONF = 'pharos_dashboard.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')] + , + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'pharos_dashboard.wsgi.application' + +# 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/ +MEDIA_URL = '/media/' +STATIC_URL = '/static/' + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), +] + +LOGIN_REDIRECT_URL = '/' + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ['SECRET_KEY'] + +BOOTSTRAP3 = { + 'set_placeholder': False, +} + +ALLOWED_HOSTS = ['*'] + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ['DB_NAME'], + 'USER': os.environ['DB_USER'], + 'PASSWORD': os.environ['DB_PASS'], + 'HOST': os.environ['DB_SERVICE'], + 'PORT': os.environ['DB_PORT'] + } +} + + +# Rest API Settings +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + ], + 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',) +} + +MEDIA_ROOT = '/media' +STATIC_ROOT = '/static' + +# Jira Settings +CREATE_JIRA_TICKET = False + +JIRA_URL = os.environ['JIRA_URL'] + +JIRA_USER_NAME = os.environ['JIRA_USER_NAME'] +JIRA_USER_PASSWORD = os.environ['JIRA_USER_PASSWORD'] + +OAUTH_CONSUMER_KEY = os.environ['OAUTH_CONSUMER_KEY'] +OAUTH_CONSUMER_SECRET = os.environ['OAUTH_CONSUMER_SECRET'] + +OAUTH_REQUEST_TOKEN_URL = JIRA_URL + '/plugins/servlet/oauth/request-token' +OAUTH_ACCESS_TOKEN_URL = JIRA_URL + '/plugins/servlet/oauth/access-token' +OAUTH_AUTHORIZE_URL = JIRA_URL + '/plugins/servlet/oauth/authorize' + +OAUTH_CALLBACK_URL = JIRA_URL + '/accounts/authenticated' + +# Celery Settings +CELERY_TIMEZONE = 'UTC' + +RABBITMQ_URL = 'rabbitmq' +BROKER_URL = 'amqp://guest:guest@rabbitmq:5672//' + +CELERYBEAT_SCHEDULE = { + 'sync-jenkins': { + 'task': 'jenkins.tasks.sync_jenkins', + 'schedule': timedelta(minutes=5) + }, + 'send-booking-notifications': { + 'task': 'notification.tasks.send_booking_notifications', + 'schedule': timedelta(minutes=5) + }, + 'clean-database': { + 'task': 'dashboard.tasks.database_cleanup', + 'schedule': timedelta(hours=24) + }, +} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/pharos_dashboard/urls.py b/tools/pharos-dashboard/src/pharos_dashboard/urls.py new file mode 100644 index 00000000..adcb5b8f --- /dev/null +++ b/tools/pharos-dashboard/src/pharos_dashboard/urls.py @@ -0,0 +1,44 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_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 import settings +from django.conf.urls import url, include +from django.conf.urls.static import static +from django.contrib import admin + + +urlpatterns = [ + url(r'^', include('dashboard.urls', namespace='dashboard')), + url(r'^booking/', include('booking.urls', namespace='booking')), + url(r'^accounts/', include('account.urls', namespace='account')), + + url(r'^admin/', admin.site.urls), + url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), + + url(r'^api/', include('api.urls')) +] + +if settings.DEBUG is True: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/pharos_dashboard/wsgi.py b/tools/pharos-dashboard/src/pharos_dashboard/wsgi.py new file mode 100644 index 00000000..3d43361b --- /dev/null +++ b/tools/pharos-dashboard/src/pharos_dashboard/wsgi.py @@ -0,0 +1,26 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +""" +WSGI config for pharos_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", "pharos_dashboard.settings") + +application = get_wsgi_application() diff --git a/tools/pharos-dashboard/src/static/bower.json b/tools/pharos-dashboard/src/static/bower.json new file mode 100644 index 00000000..f473747f --- /dev/null +++ b/tools/pharos-dashboard/src/static/bower.json @@ -0,0 +1,24 @@ +{ + "name": "pharos-dashboard-dependencies", + "authors": [ + "maxbr " + ], + "description": "This package contains all the Js/CSS dependencies needed to run the Pharos Dashboard.", + "main": "", + "license": "Apache2", + "homepage": "", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "eonasdan-bootstrap-datetimepicker": "^4.17.37", + "fullcalendar": "^2.9.0", + "jquery-migrate": "^3.0.0", + "startbootstrap-sb-admin-2-blackrockdigital": "^3.3.7" + } +} diff --git a/tools/pharos-dashboard/src/static/css/theme.css b/tools/pharos-dashboard/src/static/css/theme.css new file mode 100644 index 00000000..bd156372 --- /dev/null +++ b/tools/pharos-dashboard/src/static/css/theme.css @@ -0,0 +1,13 @@ +.blink_me { + animation: blinker 1.5s linear infinite; +} + +@keyframes blinker { + 20% { + opacity: 0.4; + } +} + +.modal p { + word-wrap: break-word; +} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/static/js/booking-calendar.js b/tools/pharos-dashboard/src/static/js/booking-calendar.js new file mode 100644 index 00000000..9cb0f32f --- /dev/null +++ b/tools/pharos-dashboard/src/static/js/booking-calendar.js @@ -0,0 +1,46 @@ +/***************************************************************************** +* Copyright (c) 2016 Max Breitenfeldt and others. +* +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Apache License, Version 2.0 +* which accompanies this distribution, and is available at +* http://www.apache.org/licenses/LICENSE-2.0 +*****************************************************************************/ + + +function parseCalendarEvents(bookings) { + var events = []; + for (var i = 0; i < bookings.length; i++) { + // convert ISO 8601 timestring to moment, needed for timezone handling + start = moment(bookings[i]['start']); + end = moment(bookings[i]['end']); + event = { + id: bookings[i]['id'], + title: bookings[i]['purpose'], + start: start, + end: end, + }; + events.push(event); + } + return events; +} + +function loadEvents(url) { + $.ajax({ + url: url, + type: 'get', + success: function (data) { + $('#calendar').fullCalendar('addEventSource', parseCalendarEvents(data['bookings'])); + }, + failure: function (data) { + alert('Error loading booking data'); + } + }); +} + +$(document).ready(function () { + $('#calendar').fullCalendar(calendarOptions); + loadEvents(bookings_url); + $('#starttimepicker').datetimepicker(timepickerOptions); + $('#endtimepicker').datetimepicker(timepickerOptions); +}); diff --git a/tools/pharos-dashboard/src/static/js/dataTables-sort.js b/tools/pharos-dashboard/src/static/js/dataTables-sort.js new file mode 100644 index 00000000..3072d2f1 --- /dev/null +++ b/tools/pharos-dashboard/src/static/js/dataTables-sort.js @@ -0,0 +1,36 @@ +/***************************************************************************** +* Copyright (c) 2016 Max Breitenfeldt and others. +* +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Apache License, Version 2.0 +* which accompanies this distribution, and is available at +* http://www.apache.org/licenses/LICENSE-2.0 +*****************************************************************************/ + + +/** + * This is a sort function for dataTables to sort tables by the status column. + * The order should be: online < online/idle < offline + */ +jQuery.extend(jQuery.fn.dataTableExt.oSort, { + "status-pre": function (a) { + switch (a) { + case 'online': + return 1; + case 'online / idle': + return 2; + case 'offline': + return 3; + default: + return a; + } + }, + + "status-asc": function (a, b) { + return ((a < b) ? -1 : ((a > b) ? 1 : 0)); + }, + + "status-desc": function (a, b) { + return ((a < b) ? 1 : ((a > b) ? -1 : 0)); + } +}); \ No newline at end of file diff --git a/tools/pharos-dashboard/src/static/js/datetimepicker-options.js b/tools/pharos-dashboard/src/static/js/datetimepicker-options.js new file mode 100644 index 00000000..d43f5fb8 --- /dev/null +++ b/tools/pharos-dashboard/src/static/js/datetimepicker-options.js @@ -0,0 +1,13 @@ +/***************************************************************************** +* Copyright (c) 2016 Max Breitenfeldt and others. +* +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Apache License, Version 2.0 +* which accompanies this distribution, and is available at +* http://www.apache.org/licenses/LICENSE-2.0 +*****************************************************************************/ + + +var timepickerOptions = { + format: 'MM/DD/YYYY HH:00' +}; \ No newline at end of file diff --git a/tools/pharos-dashboard/src/static/js/flot-pie-chart.js b/tools/pharos-dashboard/src/static/js/flot-pie-chart.js new file mode 100644 index 00000000..3b80b2a2 --- /dev/null +++ b/tools/pharos-dashboard/src/static/js/flot-pie-chart.js @@ -0,0 +1,30 @@ +/***************************************************************************** +* Copyright (c) 2016 Max Breitenfeldt and others. +* +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Apache License, Version 2.0 +* which accompanies this distribution, and is available at +* http://www.apache.org/licenses/LICENSE-2.0 +*****************************************************************************/ + + +function loadChartData(chart_id, url) { + $.ajax({ + url: url, + type: 'get', + success: function (data) { + var data = data['data']; + var plotObj = $.plot($("#" + chart_id), data, { + series: { + pie: { + show: true + } + } + }); + }, + failure: function (data) { + alert('Error loading data'); + } + }); + +} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/static/js/fullcalendar-options.js b/tools/pharos-dashboard/src/static/js/fullcalendar-options.js new file mode 100644 index 00000000..22a1b95e --- /dev/null +++ b/tools/pharos-dashboard/src/static/js/fullcalendar-options.js @@ -0,0 +1,101 @@ +/***************************************************************************** +* Copyright (c) 2016 Max Breitenfeldt and others. +* +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Apache License, Version 2.0 +* which accompanies this distribution, and is available at +* http://www.apache.org/licenses/LICENSE-2.0 +*****************************************************************************/ + + +var tmpevent; + +function sendEventToForm(event) { + $('#starttimepicker').data("DateTimePicker").date(event.start); + $('#endtimepicker').data("DateTimePicker").date(event.end); +} + +var calendarOptions = { + height: 600, + header: { + left: 'prev,next today', + center: 'title', + right: 'agendaWeek,month' + }, + timezone: user_timezone, // set in booking_calendar.html + defaultView: 'month', + slotDuration: '00:60:00', + slotLabelFormat: "HH:mm", + firstDay: 1, + allDaySlot: false, + selectOverlap: false, + eventOverlap: false, + selectable: true, + editable: false, + eventLimit: true, // allow "more" link when too many events + timeFormat: 'H(:mm)', // uppercase H for 24-hour clock + unselectAuto: true, + nowIndicator: true, + + // selectHelper is only working in the agendaWeek view, this is a workaround: + // if an event is selected, the existing selection is removed and a temporary event is added + // to the calendar + select: function (start, end) { + if (tmpevent != undefined) { + $('#calendar').fullCalendar('removeEvents', tmpevent.id); + $('#calendar').fullCalendar('rerenderEvents'); + tmpevent = undefined; + } + // the times need to be converted here to make them show up in the agendaWeek view if they + // are created in the month view. If they are not converted, the tmpevent will only show + // up in the (deactivated) allDaySlot + start = moment(start); + end = moment(end); + + tmpevent = { + id: '537818f62bc63518ece15338fb86c8be', + title: 'New Booking', + start: start, + end: end, + editable: true + }; + + $('#calendar').fullCalendar('renderEvent', tmpevent, true); + sendEventToForm(tmpevent); + }, + + eventClick: function (event) { + if (tmpevent != undefined) { + if (event.id != tmpevent.id) { + $('#calendar').fullCalendar('removeEvents', tmpevent.id); + $('#calendar').fullCalendar('rerenderEvents'); + tmpevent = undefined; + } + } + + // tmpevent is deleted if a real event is clicked, load event details + if (tmpevent == undefined) { + var booking_detail_url = booking_detail_prefix + event.id; + + $.ajax({ + url: booking_detail_url, + type: 'get', + success: function (data) { + $('#booking_detail_content').html(data); + }, + failure: function (data) { + alert('Error loading booking details'); + } + }); + $('#booking_detail_modal').modal('show'); + } + }, + + eventDrop: function (event) { + sendEventToForm(event); + }, + + eventResize: function (event) { + sendEventToForm(event); + } +}; \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/account/user_list.html b/tools/pharos-dashboard/src/templates/account/user_list.html new file mode 100644 index 00000000..c2b81938 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/account/user_list.html @@ -0,0 +1,46 @@ +{% extends "dashboard/table.html" %} +{% load staticfiles %} + +{% block table %} + + + Username + Company + SSH Key + GPG Key + + + + {% for user in users %} + + + {{ user.username }} + + + {{ user.userprofile.company }} + + + {% if user.userprofile.ssh_public_key %} + SSH + {% endif %} + + + {% if user.userprofile.pgp_public_key %} + GPG + {% endif %} + + + {% endfor %} + +{% endblock table %} + + +{% block tablejs %} + +{% endblock tablejs %} diff --git a/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html b/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html new file mode 100644 index 00000000..542ea81e --- /dev/null +++ b/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html @@ -0,0 +1,30 @@ +{% extends "layout.html" %} +{% load bootstrap3 %} + +{% block basecontent %} +
+
+
+ {% bootstrap_messages %} + +
+
+
+{% endblock basecontent %} diff --git a/tools/pharos-dashboard/src/templates/base.html b/tools/pharos-dashboard/src/templates/base.html new file mode 100644 index 00000000..5bb55473 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/base.html @@ -0,0 +1,104 @@ +{% extends "layout.html" %} +{% load bootstrap3 %} + +{% block basecontent %} +
+ + + + +
+
+
+

{{ title }}

+
+ +
+ + {% bootstrap_messages %} + + {% block content %} + + {% endblock content %} +
+ +
+ +{% endblock basecontent %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/booking/booking_calendar.html b/tools/pharos-dashboard/src/templates/booking/booking_calendar.html new file mode 100644 index 00000000..de3e3b3d --- /dev/null +++ b/tools/pharos-dashboard/src/templates/booking/booking_calendar.html @@ -0,0 +1,94 @@ +{% extends "dashboard/table.html" %} +{% load staticfiles %} + +{% load bootstrap3 %} + +{% block extrahead %} + + +{% endblock extrahead %} + +{% block content %} +
+
+
+
+ Calendar +
+
+
+
+
+ +
+ +
+
+ +
+
+
+ Booking +
+
+
+ {% bootstrap_form_errors form type='non_fields' %} +
+ {% csrf_token %} + +
+ {% bootstrap_field form.start addon_after='' %} +
+
+ {% bootstrap_field form.end addon_after='' %} +
+ {% bootstrap_field form.purpose %} + + {% buttons %} + + {% endbuttons %} +
+
+
+
+
+ + +{% endblock content %} + +{% block extrajs %} + + + + + + + + +{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/booking/booking_detail.html b/tools/pharos-dashboard/src/templates/booking/booking_detail.html new file mode 100644 index 00000000..d3f47538 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/booking/booking_detail.html @@ -0,0 +1,26 @@ +{% load jira_filters %} + +

+ Resource: + + {{ booking.resource.name }} + +

+

+ User: {{ booking.user.username }} +

+

+ Start: {{ booking.start }} +

+

+ End: {{ booking.end }} +

+

+ Purpose: {{ booking.purpose }} +

+

+ Jira: + + {{ jira_issue }} + +

\ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/booking/booking_table.html b/tools/pharos-dashboard/src/templates/booking/booking_table.html new file mode 100644 index 00000000..216eaf57 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/booking/booking_table.html @@ -0,0 +1,33 @@ +{% load jira_filters %} + + + + + User + Purpose + Start + End + Jira + + + +{% for booking in bookings %} + + + {{ booking.user.username }} + + + {{ booking.purpose }} + + + {{ booking.start }} + + + {{ booking.end }} + + {{ booking.get_jira_issue }} + + +{% endfor %} + \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/ci_pods.html b/tools/pharos-dashboard/src/templates/dashboard/ci_pods.html new file mode 100644 index 00000000..7ef62a43 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/ci_pods.html @@ -0,0 +1,60 @@ +{% extends "dashboard/table.html" %} +{% load staticfiles %} +{% load jenkins_filters %} + +{% block table %} + + + Name + Slave Name + Status + Installer + Scenario + Branch + Job + + + + {% for pod in ci_pods %} + + + {{ pod.name }} + + + {{ pod.slave.name }} + + + {{ pod.slave.status }} + + + {{ pod.slave.last_job_installer }} + + + {{ pod.slave.last_job_scenario }} + + + {{ pod.slave.last_job_branch }} + + {{ pod.slave.last_job_name }} + + + {% endfor %} + +{% endblock table %} + + +{% block tablejs %} + +{% endblock tablejs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/dev_pods.html b/tools/pharos-dashboard/src/templates/dashboard/dev_pods.html new file mode 100644 index 00000000..2b4b0177 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/dev_pods.html @@ -0,0 +1,69 @@ +{% extends "dashboard/table.html" %} +{% load staticfiles %} +{% load jenkins_filters %} + +{% block table %} + + + Name + Slave Name + Booked by + Booked until + Purpose + Utilization + Status + + + + + + {% for pod, booking, utilization in dev_pods %} + + + {{ pod.name }} + + + {{ pod.slave.name }} + + + {{ booking.user.username }} + + + {{ booking.end }} + + + {{ booking.purpose }} + + + {{ utilization }} + + + {{ pod.slave.status }} + + + + Book + + + + + Info + + + + {% endfor %} + +{% endblock table %} + +{% block tablejs %} + +{% endblock tablejs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/jenkins_slaves.html b/tools/pharos-dashboard/src/templates/dashboard/jenkins_slaves.html new file mode 100644 index 00000000..aa74507c --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/jenkins_slaves.html @@ -0,0 +1,45 @@ +{% extends "dashboard/table.html" %} +{% load staticfiles %} + +{% load jenkins_filters %} + +{% block table %} + + + Slave name + Status + Job + + + + {% for slave in slaves %} + + {{ slave.name }} + + + {{ slave.status }} + + + {{ slave.last_job_name }} + + + {% endfor %} + +{% endblock table %} + + +{% block tablejs %} + +{% endblock tablejs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/resource.html b/tools/pharos-dashboard/src/templates/dashboard/resource.html new file mode 100644 index 00000000..c9e57354 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/resource.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block extrahead %} + + + + + + + + +{% endblock extrahead %} + + +{% block content %} + {% include "dashboard/resource_detail.html" %} +{% endblock content %} + + +{% block extrajs %} + + + + + + + + + + + + + + + + + + + +{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/resource_all.html b/tools/pharos-dashboard/src/templates/dashboard/resource_all.html new file mode 100644 index 00000000..a770d4e8 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/resource_all.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block extrahead %} + + + + + + + + +{% endblock extrahead %} + + +{% block content %} + {% for resource, utilization, bookings in pods %} +
+
+
+
+ {{ resource.name }} +
+
+ {% include "dashboard/resource_detail.html" %} +
+
+
+
+ {% endfor %} +{% endblock content %} + + +{% block extrajs %} + + + + + + + + + + + + + + + + < + + +{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html b/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html new file mode 100644 index 00000000..657d5656 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html @@ -0,0 +1,184 @@ +{% load jenkins_filters %} + +
+
+
+
+ Jenkins Utilization +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ Servers +
+
+
+ + {% include "dashboard/server_table.html" %} +
+
+
+
+
+
+
+
+
+
+ Booking Utilization +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ Bookings +
+
+
+ + {% include "booking/booking_table.html" %} +
+
+
+
+
+
+
+
+
+
+ Contact +
+
+

+ Lab Owner: + {{ resource.owner.username }} +

+

+ Email: +

+

+ + Booking + + + OPNFV Wiki + +

+
+
+
+
+
+
+ Jenkins Status +
+
+

+ Slave Name: + {{ resource.slave.name }} +

+

+ Status: + {{ resource.slave.status }} +

+

+ Last Job: + + {{ resource.slave.last_job_name }} + +

+
+
+
+
+
+
+ VPN Users +
+
+
+ + + + + + + + + + {% for user in resource.vpn_users.all %} + + + + + + {% endfor %} +
UserEmailCompany
+ {{ user.username }} + + {{ user.email }} + + {{ user.userprofile.company }} +
+ +
+
+
+
+
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/server_table.html b/tools/pharos-dashboard/src/templates/dashboard/server_table.html new file mode 100644 index 00000000..f01bd603 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/server_table.html @@ -0,0 +1,30 @@ + + + Server + Model + CPU + RAM + Storage + + + +{% for server in resource.server_set.all %} + + + {{ server.name }} + + + {{ server.model }} + + + {{ server.cpu }} + + + {{ server.ram }} + + + {{ server.storage }} + + +{% endfor %} + \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/dashboard/table.html b/tools/pharos-dashboard/src/templates/dashboard/table.html new file mode 100644 index 00000000..addd5c12 --- /dev/null +++ b/tools/pharos-dashboard/src/templates/dashboard/table.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block extrahead %} + + + + + +{% endblock extrahead %} + +{% block content %} +
+
+
+
+
+ + + {% block table %} + {% endblock table %} + +
+
+ +
+ +
+ +
+ +
+{% endblock content %} + +{% block extrajs %} + + + + + + + + + + {% block tablejs %} + {% endblock tablejs %} +{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/layout.html b/tools/pharos-dashboard/src/templates/layout.html new file mode 100644 index 00000000..64fed4ae --- /dev/null +++ b/tools/pharos-dashboard/src/templates/layout.html @@ -0,0 +1,73 @@ +{% load staticfiles %} + + + + + + + + + + + + OPNFV Pharos {{ title }} + + + + + + + + + + + + + + + + + + {% block extrahead %} + {% endblock extrahead %} + + + + + + + +{% block extrastyle %} +{% endblock extrastyle %} + + +{% block basecontent %} +{% endblock basecontent %} + + + +{##} +{##} +{##} + +{##} + + + + + + + + + +{% block extrajs %} +{% endblock extrajs %} + + diff --git a/tools/pharos-dashboard/src/templates/rest_framework/api.html b/tools/pharos-dashboard/src/templates/rest_framework/api.html new file mode 100644 index 00000000..9c6c4f7d --- /dev/null +++ b/tools/pharos-dashboard/src/templates/rest_framework/api.html @@ -0,0 +1,9 @@ +{% extends "rest_framework/base.html" %} + +{% block title %}Pharos Dashboard API{% endblock %} + +{% block branding %} + + Pharos Dashboard API + +{% endblock %} \ No newline at end of file diff --git a/tools/pharos-dashboard/static/bower.json b/tools/pharos-dashboard/static/bower.json deleted file mode 100644 index f473747f..00000000 --- a/tools/pharos-dashboard/static/bower.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "pharos-dashboard-dependencies", - "authors": [ - "maxbr " - ], - "description": "This package contains all the Js/CSS dependencies needed to run the Pharos Dashboard.", - "main": "", - "license": "Apache2", - "homepage": "", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "eonasdan-bootstrap-datetimepicker": "^4.17.37", - "fullcalendar": "^2.9.0", - "jquery-migrate": "^3.0.0", - "startbootstrap-sb-admin-2-blackrockdigital": "^3.3.7" - } -} diff --git a/tools/pharos-dashboard/static/css/theme.css b/tools/pharos-dashboard/static/css/theme.css deleted file mode 100644 index bd156372..00000000 --- a/tools/pharos-dashboard/static/css/theme.css +++ /dev/null @@ -1,13 +0,0 @@ -.blink_me { - animation: blinker 1.5s linear infinite; -} - -@keyframes blinker { - 20% { - opacity: 0.4; - } -} - -.modal p { - word-wrap: break-word; -} \ No newline at end of file diff --git a/tools/pharos-dashboard/static/js/booking-calendar.js b/tools/pharos-dashboard/static/js/booking-calendar.js deleted file mode 100644 index 9cb0f32f..00000000 --- a/tools/pharos-dashboard/static/js/booking-calendar.js +++ /dev/null @@ -1,46 +0,0 @@ -/***************************************************************************** -* Copyright (c) 2016 Max Breitenfeldt and others. -* -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Apache License, Version 2.0 -* which accompanies this distribution, and is available at -* http://www.apache.org/licenses/LICENSE-2.0 -*****************************************************************************/ - - -function parseCalendarEvents(bookings) { - var events = []; - for (var i = 0; i < bookings.length; i++) { - // convert ISO 8601 timestring to moment, needed for timezone handling - start = moment(bookings[i]['start']); - end = moment(bookings[i]['end']); - event = { - id: bookings[i]['id'], - title: bookings[i]['purpose'], - start: start, - end: end, - }; - events.push(event); - } - return events; -} - -function loadEvents(url) { - $.ajax({ - url: url, - type: 'get', - success: function (data) { - $('#calendar').fullCalendar('addEventSource', parseCalendarEvents(data['bookings'])); - }, - failure: function (data) { - alert('Error loading booking data'); - } - }); -} - -$(document).ready(function () { - $('#calendar').fullCalendar(calendarOptions); - loadEvents(bookings_url); - $('#starttimepicker').datetimepicker(timepickerOptions); - $('#endtimepicker').datetimepicker(timepickerOptions); -}); diff --git a/tools/pharos-dashboard/static/js/dataTables-sort.js b/tools/pharos-dashboard/static/js/dataTables-sort.js deleted file mode 100644 index 3072d2f1..00000000 --- a/tools/pharos-dashboard/static/js/dataTables-sort.js +++ /dev/null @@ -1,36 +0,0 @@ -/***************************************************************************** -* Copyright (c) 2016 Max Breitenfeldt and others. -* -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Apache License, Version 2.0 -* which accompanies this distribution, and is available at -* http://www.apache.org/licenses/LICENSE-2.0 -*****************************************************************************/ - - -/** - * This is a sort function for dataTables to sort tables by the status column. - * The order should be: online < online/idle < offline - */ -jQuery.extend(jQuery.fn.dataTableExt.oSort, { - "status-pre": function (a) { - switch (a) { - case 'online': - return 1; - case 'online / idle': - return 2; - case 'offline': - return 3; - default: - return a; - } - }, - - "status-asc": function (a, b) { - return ((a < b) ? -1 : ((a > b) ? 1 : 0)); - }, - - "status-desc": function (a, b) { - return ((a < b) ? 1 : ((a > b) ? -1 : 0)); - } -}); \ No newline at end of file diff --git a/tools/pharos-dashboard/static/js/datetimepicker-options.js b/tools/pharos-dashboard/static/js/datetimepicker-options.js deleted file mode 100644 index d43f5fb8..00000000 --- a/tools/pharos-dashboard/static/js/datetimepicker-options.js +++ /dev/null @@ -1,13 +0,0 @@ -/***************************************************************************** -* Copyright (c) 2016 Max Breitenfeldt and others. -* -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Apache License, Version 2.0 -* which accompanies this distribution, and is available at -* http://www.apache.org/licenses/LICENSE-2.0 -*****************************************************************************/ - - -var timepickerOptions = { - format: 'MM/DD/YYYY HH:00' -}; \ No newline at end of file diff --git a/tools/pharos-dashboard/static/js/flot-pie-chart.js b/tools/pharos-dashboard/static/js/flot-pie-chart.js deleted file mode 100644 index 3b80b2a2..00000000 --- a/tools/pharos-dashboard/static/js/flot-pie-chart.js +++ /dev/null @@ -1,30 +0,0 @@ -/***************************************************************************** -* Copyright (c) 2016 Max Breitenfeldt and others. -* -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Apache License, Version 2.0 -* which accompanies this distribution, and is available at -* http://www.apache.org/licenses/LICENSE-2.0 -*****************************************************************************/ - - -function loadChartData(chart_id, url) { - $.ajax({ - url: url, - type: 'get', - success: function (data) { - var data = data['data']; - var plotObj = $.plot($("#" + chart_id), data, { - series: { - pie: { - show: true - } - } - }); - }, - failure: function (data) { - alert('Error loading data'); - } - }); - -} \ No newline at end of file diff --git a/tools/pharos-dashboard/static/js/fullcalendar-options.js b/tools/pharos-dashboard/static/js/fullcalendar-options.js deleted file mode 100644 index 22a1b95e..00000000 --- a/tools/pharos-dashboard/static/js/fullcalendar-options.js +++ /dev/null @@ -1,101 +0,0 @@ -/***************************************************************************** -* Copyright (c) 2016 Max Breitenfeldt and others. -* -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Apache License, Version 2.0 -* which accompanies this distribution, and is available at -* http://www.apache.org/licenses/LICENSE-2.0 -*****************************************************************************/ - - -var tmpevent; - -function sendEventToForm(event) { - $('#starttimepicker').data("DateTimePicker").date(event.start); - $('#endtimepicker').data("DateTimePicker").date(event.end); -} - -var calendarOptions = { - height: 600, - header: { - left: 'prev,next today', - center: 'title', - right: 'agendaWeek,month' - }, - timezone: user_timezone, // set in booking_calendar.html - defaultView: 'month', - slotDuration: '00:60:00', - slotLabelFormat: "HH:mm", - firstDay: 1, - allDaySlot: false, - selectOverlap: false, - eventOverlap: false, - selectable: true, - editable: false, - eventLimit: true, // allow "more" link when too many events - timeFormat: 'H(:mm)', // uppercase H for 24-hour clock - unselectAuto: true, - nowIndicator: true, - - // selectHelper is only working in the agendaWeek view, this is a workaround: - // if an event is selected, the existing selection is removed and a temporary event is added - // to the calendar - select: function (start, end) { - if (tmpevent != undefined) { - $('#calendar').fullCalendar('removeEvents', tmpevent.id); - $('#calendar').fullCalendar('rerenderEvents'); - tmpevent = undefined; - } - // the times need to be converted here to make them show up in the agendaWeek view if they - // are created in the month view. If they are not converted, the tmpevent will only show - // up in the (deactivated) allDaySlot - start = moment(start); - end = moment(end); - - tmpevent = { - id: '537818f62bc63518ece15338fb86c8be', - title: 'New Booking', - start: start, - end: end, - editable: true - }; - - $('#calendar').fullCalendar('renderEvent', tmpevent, true); - sendEventToForm(tmpevent); - }, - - eventClick: function (event) { - if (tmpevent != undefined) { - if (event.id != tmpevent.id) { - $('#calendar').fullCalendar('removeEvents', tmpevent.id); - $('#calendar').fullCalendar('rerenderEvents'); - tmpevent = undefined; - } - } - - // tmpevent is deleted if a real event is clicked, load event details - if (tmpevent == undefined) { - var booking_detail_url = booking_detail_prefix + event.id; - - $.ajax({ - url: booking_detail_url, - type: 'get', - success: function (data) { - $('#booking_detail_content').html(data); - }, - failure: function (data) { - alert('Error loading booking details'); - } - }); - $('#booking_detail_modal').modal('show'); - } - }, - - eventDrop: function (event) { - sendEventToForm(event); - }, - - eventResize: function (event) { - sendEventToForm(event); - } -}; \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/account/user_list.html b/tools/pharos-dashboard/templates/account/user_list.html deleted file mode 100644 index c2b81938..00000000 --- a/tools/pharos-dashboard/templates/account/user_list.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "dashboard/table.html" %} -{% load staticfiles %} - -{% block table %} - - - Username - Company - SSH Key - GPG Key - - - - {% for user in users %} - - - {{ user.username }} - - - {{ user.userprofile.company }} - - - {% if user.userprofile.ssh_public_key %} - SSH - {% endif %} - - - {% if user.userprofile.pgp_public_key %} - GPG - {% endif %} - - - {% endfor %} - -{% endblock table %} - - -{% block tablejs %} - -{% endblock tablejs %} diff --git a/tools/pharos-dashboard/templates/account/userprofile_update_form.html b/tools/pharos-dashboard/templates/account/userprofile_update_form.html deleted file mode 100644 index 542ea81e..00000000 --- a/tools/pharos-dashboard/templates/account/userprofile_update_form.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "layout.html" %} -{% load bootstrap3 %} - -{% block basecontent %} -
-
-
- {% bootstrap_messages %} - -
-
-
-{% endblock basecontent %} diff --git a/tools/pharos-dashboard/templates/base.html b/tools/pharos-dashboard/templates/base.html deleted file mode 100644 index c5da483d..00000000 --- a/tools/pharos-dashboard/templates/base.html +++ /dev/null @@ -1,109 +0,0 @@ -{% extends "layout.html" %} -{% load bootstrap3 %} - -{% block basecontent %} -
- - - - -
-
-
-

{{ title }}

-
- -
- - {% bootstrap_messages %} - - {% block content %} - - {% endblock content %} -
- -
- -{% endblock basecontent %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/booking/booking_calendar.html b/tools/pharos-dashboard/templates/booking/booking_calendar.html deleted file mode 100644 index de3e3b3d..00000000 --- a/tools/pharos-dashboard/templates/booking/booking_calendar.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "dashboard/table.html" %} -{% load staticfiles %} - -{% load bootstrap3 %} - -{% block extrahead %} - - -{% endblock extrahead %} - -{% block content %} -
-
-
-
- Calendar -
-
-
-
-
- -
- -
-
- -
-
-
- Booking -
-
-
- {% bootstrap_form_errors form type='non_fields' %} -
- {% csrf_token %} - -
- {% bootstrap_field form.start addon_after='' %} -
-
- {% bootstrap_field form.end addon_after='' %} -
- {% bootstrap_field form.purpose %} - - {% buttons %} - - {% endbuttons %} -
-
-
-
-
- - -{% endblock content %} - -{% block extrajs %} - - - - - - - - -{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/booking/booking_detail.html b/tools/pharos-dashboard/templates/booking/booking_detail.html deleted file mode 100644 index d3f47538..00000000 --- a/tools/pharos-dashboard/templates/booking/booking_detail.html +++ /dev/null @@ -1,26 +0,0 @@ -{% load jira_filters %} - -

- Resource: - - {{ booking.resource.name }} - -

-

- User: {{ booking.user.username }} -

-

- Start: {{ booking.start }} -

-

- End: {{ booking.end }} -

-

- Purpose: {{ booking.purpose }} -

-

- Jira: - - {{ jira_issue }} - -

\ No newline at end of file diff --git a/tools/pharos-dashboard/templates/booking/booking_table.html b/tools/pharos-dashboard/templates/booking/booking_table.html deleted file mode 100644 index 216eaf57..00000000 --- a/tools/pharos-dashboard/templates/booking/booking_table.html +++ /dev/null @@ -1,33 +0,0 @@ -{% load jira_filters %} - - - - - User - Purpose - Start - End - Jira - - - -{% for booking in bookings %} - - - {{ booking.user.username }} - - - {{ booking.purpose }} - - - {{ booking.start }} - - - {{ booking.end }} - - {{ booking.get_jira_issue }} - - -{% endfor %} - \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/ci_pods.html b/tools/pharos-dashboard/templates/dashboard/ci_pods.html deleted file mode 100644 index 7ef62a43..00000000 --- a/tools/pharos-dashboard/templates/dashboard/ci_pods.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends "dashboard/table.html" %} -{% load staticfiles %} -{% load jenkins_filters %} - -{% block table %} - - - Name - Slave Name - Status - Installer - Scenario - Branch - Job - - - - {% for pod in ci_pods %} - - - {{ pod.name }} - - - {{ pod.slave.name }} - - - {{ pod.slave.status }} - - - {{ pod.slave.last_job_installer }} - - - {{ pod.slave.last_job_scenario }} - - - {{ pod.slave.last_job_branch }} - - {{ pod.slave.last_job_name }} - - - {% endfor %} - -{% endblock table %} - - -{% block tablejs %} - -{% endblock tablejs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/dev_pods.html b/tools/pharos-dashboard/templates/dashboard/dev_pods.html deleted file mode 100644 index 2b4b0177..00000000 --- a/tools/pharos-dashboard/templates/dashboard/dev_pods.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "dashboard/table.html" %} -{% load staticfiles %} -{% load jenkins_filters %} - -{% block table %} - - - Name - Slave Name - Booked by - Booked until - Purpose - Utilization - Status - - - - - - {% for pod, booking, utilization in dev_pods %} - - - {{ pod.name }} - - - {{ pod.slave.name }} - - - {{ booking.user.username }} - - - {{ booking.end }} - - - {{ booking.purpose }} - - - {{ utilization }} - - - {{ pod.slave.status }} - - - - Book - - - - - Info - - - - {% endfor %} - -{% endblock table %} - -{% block tablejs %} - -{% endblock tablejs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html b/tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html deleted file mode 100644 index aa74507c..00000000 --- a/tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "dashboard/table.html" %} -{% load staticfiles %} - -{% load jenkins_filters %} - -{% block table %} - - - Slave name - Status - Job - - - - {% for slave in slaves %} - - {{ slave.name }} - - - {{ slave.status }} - - - {{ slave.last_job_name }} - - - {% endfor %} - -{% endblock table %} - - -{% block tablejs %} - -{% endblock tablejs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/resource.html b/tools/pharos-dashboard/templates/dashboard/resource.html deleted file mode 100644 index c9e57354..00000000 --- a/tools/pharos-dashboard/templates/dashboard/resource.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block extrahead %} - - - - - - - - -{% endblock extrahead %} - - -{% block content %} - {% include "dashboard/resource_detail.html" %} -{% endblock content %} - - -{% block extrajs %} - - - - - - - - - - - - - - - - - - - -{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/resource_all.html b/tools/pharos-dashboard/templates/dashboard/resource_all.html deleted file mode 100644 index a770d4e8..00000000 --- a/tools/pharos-dashboard/templates/dashboard/resource_all.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block extrahead %} - - - - - - - - -{% endblock extrahead %} - - -{% block content %} - {% for resource, utilization, bookings in pods %} -
-
-
-
- {{ resource.name }} -
-
- {% include "dashboard/resource_detail.html" %} -
-
-
-
- {% endfor %} -{% endblock content %} - - -{% block extrajs %} - - - - - - - - - - - - - - - - < - - -{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/resource_detail.html b/tools/pharos-dashboard/templates/dashboard/resource_detail.html deleted file mode 100644 index 657d5656..00000000 --- a/tools/pharos-dashboard/templates/dashboard/resource_detail.html +++ /dev/null @@ -1,184 +0,0 @@ -{% load jenkins_filters %} - -
-
-
-
- Jenkins Utilization -
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
- Servers -
-
-
- - {% include "dashboard/server_table.html" %} -
-
-
-
-
-
-
-
-
-
- Booking Utilization -
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
- Bookings -
-
-
- - {% include "booking/booking_table.html" %} -
-
-
-
-
-
-
-
-
-
- Contact -
-
-

- Lab Owner: - {{ resource.owner.username }} -

-

- Email: -

-

- - Booking - - - OPNFV Wiki - -

-
-
-
-
-
-
- Jenkins Status -
-
-

- Slave Name: - {{ resource.slave.name }} -

-

- Status: - {{ resource.slave.status }} -

-

- Last Job: - - {{ resource.slave.last_job_name }} - -

-
-
-
-
-
-
- VPN Users -
-
-
- - - - - - - - - - {% for user in resource.vpn_users.all %} - - - - - - {% endfor %} -
UserEmailCompany
- {{ user.username }} - - {{ user.email }} - - {{ user.userprofile.company }} -
- -
-
-
-
-
\ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/server_table.html b/tools/pharos-dashboard/templates/dashboard/server_table.html deleted file mode 100644 index f01bd603..00000000 --- a/tools/pharos-dashboard/templates/dashboard/server_table.html +++ /dev/null @@ -1,30 +0,0 @@ - - - Server - Model - CPU - RAM - Storage - - - -{% for server in resource.server_set.all %} - - - {{ server.name }} - - - {{ server.model }} - - - {{ server.cpu }} - - - {{ server.ram }} - - - {{ server.storage }} - - -{% endfor %} - \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/dashboard/table.html b/tools/pharos-dashboard/templates/dashboard/table.html deleted file mode 100644 index addd5c12..00000000 --- a/tools/pharos-dashboard/templates/dashboard/table.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block extrahead %} - - - - - -{% endblock extrahead %} - -{% block content %} -
-
-
-
-
- - - {% block table %} - {% endblock table %} - -
-
- -
- -
- -
- -
-{% endblock content %} - -{% block extrajs %} - - - - - - - - - - {% block tablejs %} - {% endblock tablejs %} -{% endblock extrajs %} \ No newline at end of file diff --git a/tools/pharos-dashboard/templates/layout.html b/tools/pharos-dashboard/templates/layout.html deleted file mode 100644 index 64fed4ae..00000000 --- a/tools/pharos-dashboard/templates/layout.html +++ /dev/null @@ -1,73 +0,0 @@ -{% load staticfiles %} - - - - - - - - - - - - OPNFV Pharos {{ title }} - - - - - - - - - - - - - - - - - - {% block extrahead %} - {% endblock extrahead %} - - - - - - - -{% block extrastyle %} -{% endblock extrastyle %} - - -{% block basecontent %} -{% endblock basecontent %} - - - -{##} -{##} -{##} - -{##} - - - - - - - - - -{% block extrajs %} -{% endblock extrajs %} - - diff --git a/tools/pharos-dashboard/templates/rest_framework/api.html b/tools/pharos-dashboard/templates/rest_framework/api.html deleted file mode 100644 index 9c6c4f7d..00000000 --- a/tools/pharos-dashboard/templates/rest_framework/api.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "rest_framework/base.html" %} - -{% block title %}Pharos Dashboard API{% endblock %} - -{% block branding %} - - Pharos Dashboard API - -{% endblock %} \ No newline at end of file diff --git a/tools/pharos-dashboard/web/Dockerfile b/tools/pharos-dashboard/web/Dockerfile new file mode 100644 index 00000000..d5432359 --- /dev/null +++ b/tools/pharos-dashboard/web/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.5 +ENV PYTHONUNBUFFERED 1 +RUN mkdir /config +ADD ./requirements.txt /config/ +RUN pip install -r /config/requirements.txt +RUN mkdir /src; +WORKDIR /src diff --git a/tools/pharos-dashboard/web/requirements.txt b/tools/pharos-dashboard/web/requirements.txt new file mode 100644 index 00000000..edb20d03 --- /dev/null +++ b/tools/pharos-dashboard/web/requirements.txt @@ -0,0 +1,16 @@ +celery==3.1.23 +cryptography==1.4 +Django==1.10 +django-bootstrap3==7.0.1 +django-crispy-forms==1.6.0 +django-filter==0.14.0 +django-registration==2.1.2 +djangorestframework==3.4.6 +gunicorn==19.6.0 +jira==1.0.7 +oauth2==1.9.0.post1 +oauthlib==1.1.2 +pika==0.10.0 +psycopg2==2.6.2 +PyJWT==1.4.2 +requests==2.11.0 diff --git a/tools/pharos-dashboard/worker/Dockerfile b/tools/pharos-dashboard/worker/Dockerfile new file mode 100644 index 00000000..86395e01 --- /dev/null +++ b/tools/pharos-dashboard/worker/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.5 +ENV PYTHONUNBUFFERED 1 +RUN mkdir /config +ADD ./requirements.txt /config/ +RUN pip install -r /config/requirements.txt +RUN useradd -ms /bin/bash celery +USER celery +WORKDIR /src diff --git a/tools/pharos-dashboard/worker/requirements.txt b/tools/pharos-dashboard/worker/requirements.txt new file mode 100644 index 00000000..edb20d03 --- /dev/null +++ b/tools/pharos-dashboard/worker/requirements.txt @@ -0,0 +1,16 @@ +celery==3.1.23 +cryptography==1.4 +Django==1.10 +django-bootstrap3==7.0.1 +django-crispy-forms==1.6.0 +django-filter==0.14.0 +django-registration==2.1.2 +djangorestframework==3.4.6 +gunicorn==19.6.0 +jira==1.0.7 +oauth2==1.9.0.post1 +oauthlib==1.1.2 +pika==0.10.0 +psycopg2==2.6.2 +PyJWT==1.4.2 +requests==2.11.0 -- cgit 1.2.3-korg