import pharos dashboard code 09/17809/2
authormaxbr <maxbr@mi.fu-berlin.de>
Fri, 29 Jul 2016 10:43:43 +0000 (12:43 +0200)
committerMax Breitenfeldt <max.breitenfeldt@gmail.com>
Fri, 29 Jul 2016 11:25:20 +0000 (11:25 +0000)
JIRA: RELENG-12

The last commit was missing some JS/CSS dependencies of the site. This
happened because they are in folders that are named 'build' or 'dist'.
This commit adds a bower.json file, that specifies dependencies.
Dependencies can now be installed by running 'bower install' in the
dashboard/static folder.

Change-Id: I054f319c66771f767e97711cb678d79d3bd6bee4
Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
44 files changed:
tools/pharos-dashboard/.gitignore [new file with mode: 0644]
tools/pharos-dashboard/README.md [new file with mode: 0644]
tools/pharos-dashboard/TODO [new file with mode: 0644]
tools/pharos-dashboard/dashboard/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/admin.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/apps.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json [new file with mode: 0644]
tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json [new file with mode: 0644]
tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json [new file with mode: 0644]
tools/pharos-dashboard/dashboard/forms/booking_form.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/0001_initial.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/models.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/bower.json [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/css/theme.css [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/js/booking-calendar.js [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/js/csrf.js [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js [new file with mode: 0644]
tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js [new file with mode: 0644]
tools/pharos-dashboard/dashboard/tests.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/urls.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/views/booking.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/views/registration.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/views/table_views.py [new file with mode: 0644]
tools/pharos-dashboard/deploy.org [new file with mode: 0644]
tools/pharos-dashboard/issues.org [new file with mode: 0644]
tools/pharos-dashboard/manage.py [new file with mode: 0755]
tools/pharos-dashboard/pharos_dashboard/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/pharos_dashboard/settings.py [new file with mode: 0644]
tools/pharos-dashboard/pharos_dashboard/urls.py [new file with mode: 0644]
tools/pharos-dashboard/pharos_dashboard/wsgi.py [new file with mode: 0644]
tools/pharos-dashboard/requirements.txt [new file with mode: 0644]
tools/pharos-dashboard/templates/dashboard/base.html [new file with mode: 0644]
tools/pharos-dashboard/templates/dashboard/booking_calendar.html [new file with mode: 0644]
tools/pharos-dashboard/templates/dashboard/table.html [new file with mode: 0644]
tools/pharos-dashboard/templates/layout/base.html [new file with mode: 0644]
tools/pharos-dashboard/templates/registration/login.html [new file with mode: 0644]
tools/pharos-dashboard/templates/tables/ci_pods.html [new file with mode: 0644]
tools/pharos-dashboard/templates/tables/dev_pods.html [new file with mode: 0644]
tools/pharos-dashboard/templates/tables/jenkins_slaves.html [new file with mode: 0644]
tools/pharos_dashboard [deleted submodule]

diff --git a/tools/pharos-dashboard/.gitignore b/tools/pharos-dashboard/.gitignore
new file mode 100644 (file)
index 0000000..b5e9284
--- /dev/null
@@ -0,0 +1,36 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Django:
+*.log
+*.pot
+
+# KDE:
+.directory
+
+# Pycharm:
+.idea/
+
+# Virtualenv:
+venv/
+
+# Vim:
+*.swp
+
+# Bower Components:
+bower_components/
diff --git a/tools/pharos-dashboard/README.md b/tools/pharos-dashboard/README.md
new file mode 100644 (file)
index 0000000..be27bf5
--- /dev/null
@@ -0,0 +1 @@
+# Pharos Dashboard
diff --git a/tools/pharos-dashboard/TODO b/tools/pharos-dashboard/TODO
new file mode 100644 (file)
index 0000000..1c539d6
--- /dev/null
@@ -0,0 +1,3 @@
+- implement ajax booking form to call from fullcalendar, then implement editing of multiple events
+- if the user is behind a VPN, his timezone settings might be wrong, there should be an option in the
+user settings to override the browser timezone.
diff --git a/tools/pharos-dashboard/dashboard/__init__.py b/tools/pharos-dashboard/dashboard/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/dashboard/admin.py b/tools/pharos-dashboard/dashboard/admin.py
new file mode 100644 (file)
index 0000000..532173f
--- /dev/null
@@ -0,0 +1,9 @@
+from django.contrib import admin
+from .models import *
+
+# Register your models here.
+admin.site.register(Booking)
+admin.site.register(Pod)
+admin.site.register(Resource)
+admin.site.register(Server)
+admin.site.register(UserResource)
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/apps.py b/tools/pharos-dashboard/dashboard/apps.py
new file mode 100644 (file)
index 0000000..50878e7
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class DashboardConfig(AppConfig):
+    name = 'dashboard'
diff --git a/tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json
new file mode 100644 (file)
index 0000000..5aaccf7
--- /dev/null
@@ -0,0 +1 @@
+[{"model": "dashboard.pod", "pk": 91, "fields": {"resource": 95, "chassis": null}}, {"model": "dashboard.pod", "pk": 92, "fields": {"resource": 96, "chassis": null}}, {"model": "dashboard.pod", "pk": 93, "fields": {"resource": 97, "chassis": null}}, {"model": "dashboard.pod", "pk": 94, "fields": {"resource": 98, "chassis": null}}, {"model": "dashboard.pod", "pk": 95, "fields": {"resource": 99, "chassis": null}}, {"model": "dashboard.pod", "pk": 96, "fields": {"resource": 100, "chassis": null}}, {"model": "dashboard.pod", "pk": 97, "fields": {"resource": 101, "chassis": null}}, {"model": "dashboard.pod", "pk": 98, "fields": {"resource": 102, "chassis": null}}, {"model": "dashboard.pod", "pk": 99, "fields": {"resource": 103, "chassis": null}}, {"model": "dashboard.pod", "pk": 100, "fields": {"resource": 104, "chassis": null}}, {"model": "dashboard.pod", "pk": 101, "fields": {"resource": 105, "chassis": null}}, {"model": "dashboard.pod", "pk": 102, "fields": {"resource": 106, "chassis": null}}, {"model": "dashboard.pod", "pk": 103, "fields": {"resource": 107, "chassis": null}}, {"model": "dashboard.pod", "pk": 104, "fields": {"resource": 108, "chassis": null}}, {"model": "dashboard.pod", "pk": 105, "fields": {"resource": 109, "chassis": null}}, {"model": "dashboard.pod", "pk": 106, "fields": {"resource": 110, "chassis": null}}, {"model": "dashboard.pod", "pk": 107, "fields": {"resource": 111, "chassis": null}}, {"model": "dashboard.pod", "pk": 108, "fields": {"resource": 112, "chassis": null}}, {"model": "dashboard.resource", "pk": 95, "fields": {"name": "Linux Foundation POD 1", "slavename": "lf-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 96, "fields": {"name": "Linux Foundation POD 2", "slavename": "lf-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 97, "fields": {"name": "Ericsson  POD 2", "slavename": "ericsson-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 98, "fields": {"name": "Intel POD 2", "slavename": "intel-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 99, "fields": {"name": "Intel POD 5", "slavename": "intel-pod5", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 100, "fields": {"name": "Intel POD 6", "slavename": "intel-pod6", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 101, "fields": {"name": "Intel POD 8", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 102, "fields": {"name": "Huawei POD 1", "slavename": "huawei-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 103, "fields": {"name": "Intel POD 3", "slavename": "intel-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 104, "fields": {"name": "Dell POD 1", "slavename": "dell-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 105, "fields": {"name": "Dell POD 2", "slavename": "dell-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 106, "fields": {"name": "Orange POD 2", "slavename": "orange-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 107, "fields": {"name": "Arm POD 1", "slavename": "arm-build1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 108, "fields": {"name": "Ericsson  POD 1", "slavename": "ericsson-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 109, "fields": {"name": "Huawei POD 2", "slavename": "huawei-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 110, "fields": {"name": "Huawei POD 3", "slavename": "huawei-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 111, "fields": {"name": "Huawei POD 4", "slavename": "huawei-pod4", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 112, "fields": {"name": "Intel POD 9", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9", "bookable": false, "active": true}}]
diff --git a/tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json
new file mode 100644 (file)
index 0000000..1913281
--- /dev/null
@@ -0,0 +1 @@
+[{"model": "dashboard.pod", "pk": 91, "fields": {"resource": 95, "chassis": null}}, {"model": "dashboard.pod", "pk": 92, "fields": {"resource": 96, "chassis": null}}, {"model": "dashboard.pod", "pk": 93, "fields": {"resource": 97, "chassis": null}}, {"model": "dashboard.pod", "pk": 94, "fields": {"resource": 98, "chassis": null}}, {"model": "dashboard.pod", "pk": 95, "fields": {"resource": 99, "chassis": null}}, {"model": "dashboard.pod", "pk": 96, "fields": {"resource": 100, "chassis": null}}, {"model": "dashboard.pod", "pk": 97, "fields": {"resource": 101, "chassis": null}}, {"model": "dashboard.pod", "pk": 98, "fields": {"resource": 102, "chassis": null}}, {"model": "dashboard.pod", "pk": 99, "fields": {"resource": 103, "chassis": null}}, {"model": "dashboard.pod", "pk": 100, "fields": {"resource": 104, "chassis": null}}, {"model": "dashboard.pod", "pk": 101, "fields": {"resource": 105, "chassis": null}}, {"model": "dashboard.pod", "pk": 102, "fields": {"resource": 106, "chassis": null}}, {"model": "dashboard.pod", "pk": 103, "fields": {"resource": 107, "chassis": null}}, {"model": "dashboard.pod", "pk": 104, "fields": {"resource": 108, "chassis": null}}, {"model": "dashboard.pod", "pk": 105, "fields": {"resource": 109, "chassis": null}}, {"model": "dashboard.pod", "pk": 106, "fields": {"resource": 110, "chassis": null}}, {"model": "dashboard.pod", "pk": 107, "fields": {"resource": 111, "chassis": null}}, {"model": "dashboard.pod", "pk": 108, "fields": {"resource": 112, "chassis": null}}, {"model": "dashboard.resource", "pk": 95, "fields": {"name": "Linux Foundation POD 1", "slavename": "lf-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 96, "fields": {"name": "Linux Foundation POD 2", "slavename": "lf-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 97, "fields": {"name": "Ericsson  POD 2", "slavename": "ericsson-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 98, "fields": {"name": "Intel POD 2", "slavename": "intel-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 99, "fields": {"name": "Intel POD 5", "slavename": "intel-pod5", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 100, "fields": {"name": "Intel POD 6", "slavename": "intel-pod6", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 101, "fields": {"name": "Intel POD 8", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 102, "fields": {"name": "Huawei POD 1", "slavename": "huawei-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 103, "fields": {"name": "Intel POD 3", "slavename": "intel-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 104, "fields": {"name": "Dell POD 1", "slavename": "dell-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 105, "fields": {"name": "Dell POD 2", "slavename": "dell-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 106, "fields": {"name": "Orange POD 2", "slavename": "orange-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 107, "fields": {"name": "Arm POD 1", "slavename": "arm-build1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 108, "fields": {"name": "Ericsson  POD 1", "slavename": "ericsson-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 109, "fields": {"name": "Huawei POD 2", "slavename": "huawei-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 110, "fields": {"name": "Huawei POD 3", "slavename": "huawei-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 111, "fields": {"name": "Huawei POD 4", "slavename": "huawei-pod4", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 112, "fields": {"name": "Intel POD 9", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9", "bookable": false, "active": true}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "dashboard", "model": "booking"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "dashboard", "model": "pod"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "dashboard", "model": "resource"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "dashboard", "model": "server"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "dashboard", "model": "userresource"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 11, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add booking", "content_type": 1, "codename": "add_booking"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change booking", "content_type": 1, "codename": "change_booking"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete booking", "content_type": 1, "codename": "delete_booking"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can add pod", "content_type": 2, "codename": "add_pod"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can change pod", "content_type": 2, "codename": "change_pod"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can delete pod", "content_type": 2, "codename": "delete_pod"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can add resource", "content_type": 3, "codename": "add_resource"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can change resource", "content_type": 3, "codename": "change_resource"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can delete resource", "content_type": 3, "codename": "delete_resource"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can add server", "content_type": 4, "codename": "add_server"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can change server", "content_type": 4, "codename": "change_server"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can delete server", "content_type": 4, "codename": "delete_server"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add user resource", "content_type": 5, "codename": "add_userresource"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change user resource", "content_type": 5, "codename": "change_userresource"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete user resource", "content_type": 5, "codename": "delete_userresource"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can add log entry", "content_type": 6, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can change log entry", "content_type": 6, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can delete log entry", "content_type": 6, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can add permission", "content_type": 7, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can change permission", "content_type": 7, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can delete permission", "content_type": 7, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can add group", "content_type": 8, "codename": "add_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can change group", "content_type": 8, "codename": "change_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can delete group", "content_type": 8, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 9, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 9, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 9, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can add content type", "content_type": 10, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can change content type", "content_type": 10, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can delete content type", "content_type": 10, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can add session", "content_type": 11, "codename": "add_session"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can change session", "content_type": 11, "codename": "change_session"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can delete session", "content_type": 11, "codename": "delete_session"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$24000$kcQ5X8WQg1tH$KHynJPYTsoBq7vAipLsP0EZyo3qAu1VGJwaoEQeUz3E=", "last_login": "2016-07-24T13:01:31.199Z", "is_superuser": true, "username": "max", "first_name": "", "last_name": "", "email": "max.breitenfeldt@gmail.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:49:54.488Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$24000$6z3xKCpZp0xl$76IaKRzJocbGmGG9JUtPz3is2AjlMEIfb9omiTEn2N8=", "last_login": null, "is_superuser": true, "username": "jose", "first_name": "Jose", "last_name": "Lausuch", "email": "jose.lausuch@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:51:30Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 3, "fields": {"password": "pbkdf2_sha256$24000$466rSOMbdzZk$3TXRz9CRoephCrjNbEa7+nLJ4xwz4W0jvTfs4A69D1o=", "last_login": null, "is_superuser": true, "username": "daniel", "first_name": "Daniel", "last_name": "Smith", "email": "daniel.smith@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:52:23Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 4, "fields": {"password": "pbkdf2_sha256$24000$wjeFIjdiblRl$6ugfj1neDEsaxMQbQ0xV+zg/FKw8ImIDyuPqYWkUxE4=", "last_login": null, "is_superuser": true, "username": "jack", "first_name": "Jack", "last_name": "Morgan", "email": "jack.morgan@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:02Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 5, "fields": {"password": "pbkdf2_sha256$24000$qYMcbKTahNGK$fdPgcQKTKhjSHDZx+C2bymU46HpIl/0n5vbXMpplXWE=", "last_login": null, "is_superuser": true, "username": "fatih", "first_name": "Fatih", "last_name": "Degirmenci", "email": "fatih.degirmenci@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:38Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 6, "fields": {"password": "pbkdf2_sha256$24000$Ndxyrota7u3U$KWmjRYo3GW25pcgf9NUuz5PSXvH8hU8E2dUARoKY7EI=", "last_login": null, "is_superuser": true, "username": "trevor", "first_name": "Trevor", "last_name": "Cooper", "email": "trevor.cooper@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:54:32Z", "groups": [], "user_permissions": []}}, {"model": "admin.logentry", "pk": 1, "fields": {"action_time": "2016-07-24T12:51:30.147Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 2, "fields": {"action_time": "2016-07-24T12:51:58.928Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 3, "fields": {"action_time": "2016-07-24T12:52:23.821Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 4, "fields": {"action_time": "2016-07-24T12:52:40.446Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 5, "fields": {"action_time": "2016-07-24T12:52:45.866Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 6, "fields": {"action_time": "2016-07-24T12:53:02.303Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 7, "fields": {"action_time": "2016-07-24T12:53:22.399Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 8, "fields": {"action_time": "2016-07-24T12:53:24.541Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 9, "fields": {"action_time": "2016-07-24T12:53:38.910Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 10, "fields": {"action_time": "2016-07-24T12:53:54.585Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 11, "fields": {"action_time": "2016-07-24T12:53:56.777Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 12, "fields": {"action_time": "2016-07-24T12:54:32.683Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 13, "fields": {"action_time": "2016-07-24T12:54:53.188Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 14, "fields": {"action_time": "2016-07-24T12:55:45.789Z", "user": 1, "content_type": 5, "object_id": "1", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 15, "fields": {"action_time": "2016-07-24T12:55:51.347Z", "user": 1, "content_type": 5, "object_id": "2", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 16, "fields": {"action_time": "2016-07-24T12:55:56.704Z", "user": 1, "content_type": 5, "object_id": "3", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 17, "fields": {"action_time": "2016-07-24T12:56:08.238Z", "user": 1, "content_type": 5, "object_id": "4", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 18, "fields": {"action_time": "2016-07-24T12:56:17.849Z", "user": 1, "content_type": 5, "object_id": "5", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 19, "fields": {"action_time": "2016-07-24T12:56:24.215Z", "user": 1, "content_type": 5, "object_id": "6", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 20, "fields": {"action_time": "2016-07-24T12:56:33.608Z", "user": 1, "content_type": 5, "object_id": "7", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 21, "fields": {"action_time": "2016-07-24T12:56:37.554Z", "user": 1, "content_type": 5, "object_id": "8", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "dashboard.userresource", "pk": 1, "fields": {"user": 3, "resource": 108}}, {"model": "dashboard.userresource", "pk": 2, "fields": {"user": 3, "resource": 97}}, {"model": "dashboard.userresource", "pk": 3, "fields": {"user": 4, "resource": 98}}, {"model": "dashboard.userresource", "pk": 4, "fields": {"user": 4, "resource": 103}}, {"model": "dashboard.userresource", "pk": 5, "fields": {"user": 4, "resource": 99}}, {"model": "dashboard.userresource", "pk": 6, "fields": {"user": 4, "resource": 100}}, {"model": "dashboard.userresource", "pk": 7, "fields": {"user": 4, "resource": 101}}, {"model": "dashboard.userresource", "pk": 8, "fields": {"user": 4, "resource": 112}}, {"model": "dashboard.booking", "pk": 1, "fields": {"resource": 103, "user": 1, "start_date_time": "2016-07-23T00:00:00Z", "end_date_time": "2016-08-08T00:00:00Z", "creation": "2016-07-24T13:01:47.945Z", "purpose": "Test1"}}, {"model": "dashboard.booking", "pk": 2, "fields": {"resource": 103, "user": 1, "start_date_time": "2016-07-18T03:00:00Z", "end_date_time": "2016-07-20T10:00:00Z", "creation": "2016-07-24T13:02:00.033Z", "purpose": "test2"}}, {"model": "dashboard.booking", "pk": 3, "fields": {"resource": 109, "user": 1, "start_date_time": "2016-07-23T06:00:00Z", "end_date_time": "2016-07-26T17:00:00Z", "creation": "2016-07-24T13:02:23.024Z", "purpose": "test3"}}, {"model": "dashboard.booking", "pk": 4, "fields": {"resource": 110, "user": 1, "start_date_time": "2016-07-09T00:00:00Z", "end_date_time": "2016-08-01T00:00:00Z", "creation": "2016-07-24T13:02:35.138Z", "purpose": "test4"}}, {"model": "dashboard.booking", "pk": 5, "fields": {"resource": 111, "user": 1, "start_date_time": "2016-07-20T07:00:00Z", "end_date_time": "2016-07-20T10:00:00Z", "creation": "2016-07-24T13:02:45.153Z", "purpose": "test5"}}, {"model": "dashboard.booking", "pk": 6, "fields": {"resource": 111, "user": 1, "start_date_time": "2016-07-22T03:00:00Z", "end_date_time": "2016-07-24T10:00:00Z", "creation": "2016-07-24T13:02:50.050Z", "purpose": "test6"}}, {"model": "dashboard.booking", "pk": 7, "fields": {"resource": 111, "user": 1, "start_date_time": "2016-07-24T11:00:00Z", "end_date_time": "2016-07-24T19:00:00Z", "creation": "2016-07-24T13:02:57.207Z", "purpose": "test7"}}]
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json
new file mode 100644 (file)
index 0000000..d21be1f
--- /dev/null
@@ -0,0 +1 @@
+[{"model": "dashboard.pod", "pk": 91, "fields": {"resource": 95, "chassis": null}}, {"model": "dashboard.pod", "pk": 92, "fields": {"resource": 96, "chassis": null}}, {"model": "dashboard.pod", "pk": 93, "fields": {"resource": 97, "chassis": null}}, {"model": "dashboard.pod", "pk": 94, "fields": {"resource": 98, "chassis": null}}, {"model": "dashboard.pod", "pk": 95, "fields": {"resource": 99, "chassis": null}}, {"model": "dashboard.pod", "pk": 96, "fields": {"resource": 100, "chassis": null}}, {"model": "dashboard.pod", "pk": 97, "fields": {"resource": 101, "chassis": null}}, {"model": "dashboard.pod", "pk": 98, "fields": {"resource": 102, "chassis": null}}, {"model": "dashboard.pod", "pk": 99, "fields": {"resource": 103, "chassis": null}}, {"model": "dashboard.pod", "pk": 100, "fields": {"resource": 104, "chassis": null}}, {"model": "dashboard.pod", "pk": 101, "fields": {"resource": 105, "chassis": null}}, {"model": "dashboard.pod", "pk": 102, "fields": {"resource": 106, "chassis": null}}, {"model": "dashboard.pod", "pk": 103, "fields": {"resource": 107, "chassis": null}}, {"model": "dashboard.pod", "pk": 104, "fields": {"resource": 108, "chassis": null}}, {"model": "dashboard.pod", "pk": 105, "fields": {"resource": 109, "chassis": null}}, {"model": "dashboard.pod", "pk": 106, "fields": {"resource": 110, "chassis": null}}, {"model": "dashboard.pod", "pk": 107, "fields": {"resource": 111, "chassis": null}}, {"model": "dashboard.pod", "pk": 108, "fields": {"resource": 112, "chassis": null}}, {"model": "dashboard.resource", "pk": 95, "fields": {"name": "Linux Foundation POD 1", "slavename": "lf-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 96, "fields": {"name": "Linux Foundation POD 2", "slavename": "lf-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 97, "fields": {"name": "Ericsson  POD 2", "slavename": "ericsson-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 98, "fields": {"name": "Intel POD 2", "slavename": "intel-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 99, "fields": {"name": "Intel POD 5", "slavename": "intel-pod5", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 100, "fields": {"name": "Intel POD 6", "slavename": "intel-pod6", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 101, "fields": {"name": "Intel POD 8", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 102, "fields": {"name": "Huawei POD 1", "slavename": "huawei-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 103, "fields": {"name": "Intel POD 3", "slavename": "intel-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 104, "fields": {"name": "Dell POD 1", "slavename": "dell-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 105, "fields": {"name": "Dell POD 2", "slavename": "dell-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 106, "fields": {"name": "Orange POD 2", "slavename": "orange-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 107, "fields": {"name": "Arm POD 1", "slavename": "arm-build1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 108, "fields": {"name": "Ericsson  POD 1", "slavename": "ericsson-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 109, "fields": {"name": "Huawei POD 2", "slavename": "huawei-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 110, "fields": {"name": "Huawei POD 3", "slavename": "huawei-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 111, "fields": {"name": "Huawei POD 4", "slavename": "huawei-pod4", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 112, "fields": {"name": "Intel POD 9", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9", "bookable": false, "active": true}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "dashboard", "model": "booking"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "dashboard", "model": "pod"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "dashboard", "model": "resource"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "dashboard", "model": "server"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "dashboard", "model": "userresource"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 11, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add booking", "content_type": 1, "codename": "add_booking"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change booking", "content_type": 1, "codename": "change_booking"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete booking", "content_type": 1, "codename": "delete_booking"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can add pod", "content_type": 2, "codename": "add_pod"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can change pod", "content_type": 2, "codename": "change_pod"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can delete pod", "content_type": 2, "codename": "delete_pod"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can add resource", "content_type": 3, "codename": "add_resource"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can change resource", "content_type": 3, "codename": "change_resource"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can delete resource", "content_type": 3, "codename": "delete_resource"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can add server", "content_type": 4, "codename": "add_server"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can change server", "content_type": 4, "codename": "change_server"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can delete server", "content_type": 4, "codename": "delete_server"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add user resource", "content_type": 5, "codename": "add_userresource"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change user resource", "content_type": 5, "codename": "change_userresource"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete user resource", "content_type": 5, "codename": "delete_userresource"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can add log entry", "content_type": 6, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can change log entry", "content_type": 6, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can delete log entry", "content_type": 6, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can add permission", "content_type": 7, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can change permission", "content_type": 7, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can delete permission", "content_type": 7, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can add group", "content_type": 8, "codename": "add_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can change group", "content_type": 8, "codename": "change_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can delete group", "content_type": 8, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 9, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 9, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 9, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can add content type", "content_type": 10, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can change content type", "content_type": 10, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can delete content type", "content_type": 10, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can add session", "content_type": 11, "codename": "add_session"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can change session", "content_type": 11, "codename": "change_session"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can delete session", "content_type": 11, "codename": "delete_session"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$24000$kcQ5X8WQg1tH$KHynJPYTsoBq7vAipLsP0EZyo3qAu1VGJwaoEQeUz3E=", "last_login": "2016-07-24T12:50:11.954Z", "is_superuser": true, "username": "max", "first_name": "", "last_name": "", "email": "max.breitenfeldt@gmail.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:49:54.488Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$24000$6z3xKCpZp0xl$76IaKRzJocbGmGG9JUtPz3is2AjlMEIfb9omiTEn2N8=", "last_login": null, "is_superuser": true, "username": "jose", "first_name": "Jose", "last_name": "Lausuch", "email": "jose.lausuch@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:51:30Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 3, "fields": {"password": "pbkdf2_sha256$24000$466rSOMbdzZk$3TXRz9CRoephCrjNbEa7+nLJ4xwz4W0jvTfs4A69D1o=", "last_login": null, "is_superuser": true, "username": "daniel", "first_name": "Daniel", "last_name": "Smith", "email": "daniel.smith@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:52:23Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 4, "fields": {"password": "pbkdf2_sha256$24000$wjeFIjdiblRl$6ugfj1neDEsaxMQbQ0xV+zg/FKw8ImIDyuPqYWkUxE4=", "last_login": null, "is_superuser": true, "username": "jack", "first_name": "Jack", "last_name": "Morgan", "email": "jack.morgan@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:02Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 5, "fields": {"password": "pbkdf2_sha256$24000$qYMcbKTahNGK$fdPgcQKTKhjSHDZx+C2bymU46HpIl/0n5vbXMpplXWE=", "last_login": null, "is_superuser": true, "username": "fatih", "first_name": "Fatih", "last_name": "Degirmenci", "email": "fatih.degirmenci@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:38Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 6, "fields": {"password": "pbkdf2_sha256$24000$Ndxyrota7u3U$KWmjRYo3GW25pcgf9NUuz5PSXvH8hU8E2dUARoKY7EI=", "last_login": null, "is_superuser": true, "username": "trevor", "first_name": "Trevor", "last_name": "Cooper", "email": "trevor.cooper@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:54:32Z", "groups": [], "user_permissions": []}}, {"model": "admin.logentry", "pk": 1, "fields": {"action_time": "2016-07-24T12:51:30.147Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 2, "fields": {"action_time": "2016-07-24T12:51:58.928Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 3, "fields": {"action_time": "2016-07-24T12:52:23.821Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 4, "fields": {"action_time": "2016-07-24T12:52:40.446Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 5, "fields": {"action_time": "2016-07-24T12:52:45.866Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 6, "fields": {"action_time": "2016-07-24T12:53:02.303Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 7, "fields": {"action_time": "2016-07-24T12:53:22.399Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 8, "fields": {"action_time": "2016-07-24T12:53:24.541Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 9, "fields": {"action_time": "2016-07-24T12:53:38.910Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 10, "fields": {"action_time": "2016-07-24T12:53:54.585Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 11, "fields": {"action_time": "2016-07-24T12:53:56.777Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 12, "fields": {"action_time": "2016-07-24T12:54:32.683Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 13, "fields": {"action_time": "2016-07-24T12:54:53.188Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 14, "fields": {"action_time": "2016-07-24T12:55:45.789Z", "user": 1, "content_type": 5, "object_id": "1", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 15, "fields": {"action_time": "2016-07-24T12:55:51.347Z", "user": 1, "content_type": 5, "object_id": "2", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 16, "fields": {"action_time": "2016-07-24T12:55:56.704Z", "user": 1, "content_type": 5, "object_id": "3", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 17, "fields": {"action_time": "2016-07-24T12:56:08.238Z", "user": 1, "content_type": 5, "object_id": "4", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 18, "fields": {"action_time": "2016-07-24T12:56:17.849Z", "user": 1, "content_type": 5, "object_id": "5", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 19, "fields": {"action_time": "2016-07-24T12:56:24.215Z", "user": 1, "content_type": 5, "object_id": "6", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 20, "fields": {"action_time": "2016-07-24T12:56:33.608Z", "user": 1, "content_type": 5, "object_id": "7", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 21, "fields": {"action_time": "2016-07-24T12:56:37.554Z", "user": 1, "content_type": 5, "object_id": "8", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "dashboard.userresource", "pk": 1, "fields": {"user": 3, "resource": 108}}, {"model": "dashboard.userresource", "pk": 2, "fields": {"user": 3, "resource": 97}}, {"model": "dashboard.userresource", "pk": 3, "fields": {"user": 4, "resource": 98}}, {"model": "dashboard.userresource", "pk": 4, "fields": {"user": 4, "resource": 103}}, {"model": "dashboard.userresource", "pk": 5, "fields": {"user": 4, "resource": 99}}, {"model": "dashboard.userresource", "pk": 6, "fields": {"user": 4, "resource": 100}}, {"model": "dashboard.userresource", "pk": 7, "fields": {"user": 4, "resource": 101}}, {"model": "dashboard.userresource", "pk": 8, "fields": {"user": 4, "resource": 112}}]
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/forms/booking_form.py b/tools/pharos-dashboard/dashboard/forms/booking_form.py
new file mode 100644 (file)
index 0000000..9cf8048
--- /dev/null
@@ -0,0 +1,37 @@
+from dashboard.models import Booking
+import django.forms as forms
+from django.utils.translation import ugettext_lazy as _
+
+
+class BookingForm(forms.ModelForm):
+    class Meta:
+        model = Booking
+        fields = ['start_date_time', 'end_date_time', 'purpose', 'booking_id']
+
+        PURPOSE = {
+            'id': 'purposefield',
+            'type': 'text',
+            'placeholder': 'Booking purpose',
+        }
+
+        widgets = {
+            'purpose': forms.TextInput(attrs=PURPOSE),
+        }
+
+    # DATETIMEFORMAT should be equivalent to the moment.js format string that datetimepicker is
+    # using ('YYYY-MM-DD HH:00 ZZ'). The string is used to create a timezone aware datetime object
+    DATETIMEFORMAT = '%Y-%m-%d %H:%M %z'
+    start_date_time = forms.DateTimeField(input_formats=[DATETIMEFORMAT, ], label='Start')
+    end_date_time = forms.DateTimeField(input_formats=[DATETIMEFORMAT, ], label='End')
+
+    # we need this to determine if we create a new booking or change an existing booking
+    booking_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
+
+    def clean(self):
+        cleaned_data = super(BookingForm, self).clean()
+        if 'start_date_time' not in cleaned_data or 'end_date_time' not in cleaned_data:
+            raise forms.ValidationError('Date Missing', code='missing_date')
+        if cleaned_data['start_date_time'] >= cleaned_data['end_date_time']:
+            raise forms.ValidationError(
+                'Start date is after end date', code='invalid_dates')
+        return cleaned_data
diff --git a/tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py b/tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py
new file mode 100644 (file)
index 0000000..cd848eb
--- /dev/null
@@ -0,0 +1,84 @@
+import requests
+import logging
+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 response.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
diff --git a/tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py b/tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py
new file mode 100644 (file)
index 0000000..ba94563
--- /dev/null
@@ -0,0 +1,70 @@
+import dashboard.jenkins.jenkins_adapter as jenkins
+import re
+
+
+def parse_slave_data(slave_dict, slave):
+    slave_dict['status'] = get_slave_status(slave)
+    slave_dict['status_color'] = get_status_color(slave)
+    slave_dict['slaveurl'] = get_slave_url(slave)
+    job = jenkins.get_jenkins_job(slave['displayName'])
+    if job is not None:
+        slave_dict['last_job'] = parse_job(job)
+
+
+def parse_job(job):
+    result = parse_job_string(job['lastBuild']['fullDisplayName'])
+    result['url'] = job['url']
+    result['color'] = get_job_color(job)
+    if job['lastBuild']['building']:
+        result['blink'] = 'class=blink_me'
+    return result
+
+
+def parse_job_string(full_displayname):
+    job = {}
+    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
+
+
+# TODO: use css
+def get_job_color(job):
+    if job['lastBuild']['building'] is True:
+        return '#646F73'
+    result = job['lastBuild']['result']
+    if result == 'SUCCESS':
+        return '#33cc00'
+    if result == 'FAILURE':
+        return '#FF5555'
+    if result == 'UNSTABLE':
+        return '#EDD62B'
+
+
+# TODO: use css
+def get_status_color(slave):
+    if not slave['offline'] and slave['idle']:
+        return '#C8D6C3'
+    if not slave['offline']:
+        return '#BEFAAA'
+    return '#FAAAAB'
+
+
+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/dashboard/migrations/0001_initial.py b/tools/pharos-dashboard/dashboard/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..12de299
--- /dev/null
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.8 on 2016-07-24 13:06
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Booking',
+            fields=[
+                ('booking_id', models.AutoField(primary_key=True, serialize=False)),
+                ('start_date_time', models.DateTimeField()),
+                ('end_date_time', models.DateTimeField()),
+                ('creation', models.DateTimeField(auto_now=True)),
+                ('purpose', models.CharField(max_length=300)),
+            ],
+            options={
+                'db_table': 'booking',
+            },
+        ),
+        migrations.CreateModel(
+            name='Pod',
+            fields=[
+                ('pod_id', models.AutoField(primary_key=True, serialize=False)),
+                ('chassis', models.CharField(blank=True, max_length=500, null=True)),
+            ],
+            options={
+                'db_table': 'pod',
+            },
+        ),
+        migrations.CreateModel(
+            name='Resource',
+            fields=[
+                ('resource_id', models.AutoField(
+                    primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=100, unique=True)),
+                ('slavename', models.CharField(blank=True, max_length=50, null=True)),
+                ('description', models.CharField(
+                    blank=True, max_length=300, null=True)),
+                ('url', models.CharField(blank=True, max_length=100, null=True)),
+                ('bookable', models.BooleanField(default=False)),
+                ('active', models.BooleanField(default=True)),
+            ],
+            options={
+                'db_table': 'resource',
+            },
+        ),
+        migrations.CreateModel(
+            name='Server',
+            fields=[
+                ('server_id', models.AutoField(primary_key=True, serialize=False)),
+                ('model', models.CharField(blank=True, max_length=200, null=True)),
+                ('cpu', models.CharField(blank=True, max_length=200, null=True)),
+                ('ram', models.CharField(blank=True, max_length=200, null=True)),
+                ('storage', models.CharField(blank=True, max_length=200, null=True)),
+                ('count', models.IntegerField(default=1)),
+                ('resource', models.ForeignKey(
+                    on_delete=django.db.models.deletion.DO_NOTHING, to='dashboard.Resource')),
+            ],
+            options={
+                'db_table': 'server',
+            },
+        ),
+        migrations.CreateModel(
+            name='UserResource',
+            fields=[
+                ('user_resource_id', models.AutoField(
+                    primary_key=True, serialize=False)),
+                ('resource', models.ForeignKey(
+                    on_delete=django.db.models.deletion.CASCADE, to='dashboard.Resource')),
+                ('user', models.ForeignKey(
+                    on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'db_table': 'user_resource',
+            },
+        ),
+        migrations.AddField(
+            model_name='pod',
+            name='resource',
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE, to='dashboard.Resource'),
+        ),
+        migrations.AddField(
+            model_name='booking',
+            name='resource',
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.PROTECT, to='dashboard.Resource'),
+        ),
+        migrations.AddField(
+            model_name='booking',
+            name='user',
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/__init__.py b/tools/pharos-dashboard/dashboard/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/dashboard/models.py b/tools/pharos-dashboard/dashboard/models.py
new file mode 100644 (file)
index 0000000..132ba7c
--- /dev/null
@@ -0,0 +1,89 @@
+from __future__ import unicode_literals
+
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class Booking(models.Model):
+    booking_id = models.AutoField(primary_key=True)
+    # Bookings should be deleted before resources
+    resource = models.ForeignKey('Resource', models.PROTECT)
+    # delete Booking when user is deleted
+    user = models.ForeignKey(User, models.CASCADE)
+    start_date_time = models.DateTimeField()
+    end_date_time = models.DateTimeField()
+    creation = models.DateTimeField(auto_now=True)
+    purpose = models.CharField(max_length=300)
+
+    class Meta:
+        db_table = 'booking'
+
+    # check for conflicting bookings before saving
+    def save(self, *args, **kwargs):
+        # conflicts end after booking starts, and start before booking ends
+        conflicting_bookings = Booking.objects.filter(resource_id=self.resource_id)
+        conflicting_bookings = conflicting_bookings.filter(end_date_time__gt=self.start_date_time)
+        conflicting_bookings = conflicting_bookings.filter(start_date_time__lt=self.end_date_time)
+        # we may change a booking, so it is not a conflict
+        conflicting_bookings = conflicting_bookings.exclude(booking_id=self.booking_id)
+        if conflicting_bookings.count() > 0:
+            raise ValueError('This booking overlaps with another booking')
+        super(Booking, self).save(*args, **kwargs)
+
+
+def __str__(self):
+    return 'Booking: ' + str(self.resource)
+
+
+class Pod(models.Model):
+    pod_id = models.AutoField(primary_key=True)
+    # Delete Pod with resource
+    resource = models.ForeignKey('Resource', models.CASCADE)
+    chassis = models.CharField(max_length=500, blank=True, null=True)
+
+    class Meta:
+        db_table = 'pod'
+
+    def __str__(self):
+        if self.chassis is None:
+            return str(self.pod_id) + ' ' + str(self.resource)
+        return str(self.pod_id) + ' ' + self.chassis
+
+
+class Resource(models.Model):
+    resource_id = models.AutoField(primary_key=True)
+    name = models.CharField(max_length=100, unique=True)
+    slavename = models.CharField(max_length=50, blank=True, null=True)
+    description = models.CharField(max_length=300, blank=True, null=True)
+    url = models.CharField(max_length=100, blank=True, null=True)
+    bookable = models.BooleanField(default=False)
+    active = models.BooleanField(default=True)
+
+    class Meta:
+        db_table = 'resource'
+
+    def __str__(self):
+        return self.name
+
+
+class Server(models.Model):
+    server_id = models.AutoField(primary_key=True)
+    resource = models.ForeignKey(Resource, models.DO_NOTHING)
+    model = models.CharField(max_length=200, blank=True, null=True)
+    cpu = models.CharField(max_length=200, blank=True, null=True)
+    ram = models.CharField(max_length=200, blank=True, null=True)
+    storage = models.CharField(max_length=200, blank=True, null=True)
+    count = models.IntegerField(default=1)
+
+    class Meta:
+        db_table = 'server'
+
+
+class UserResource(models.Model):
+    user_resource_id = models.AutoField(primary_key=True)
+    user = models.ForeignKey(User, models.CASCADE)
+    # Delete if Resource is deleted
+    resource = models.ForeignKey(Resource, models.CASCADE)
+
+    class Meta:
+        db_table = 'user_resource'
diff --git a/tools/pharos-dashboard/dashboard/static/bower.json b/tools/pharos-dashboard/dashboard/static/bower.json
new file mode 100644 (file)
index 0000000..7840621
--- /dev/null
@@ -0,0 +1,24 @@
+{
+  "name": "pharos-dashboard-dependencies",
+  "authors": [
+    "maxbr <maxbr@mi.fu-berlin.de>"
+  ],
+  "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": "^1.0.9"
+  }
+}
diff --git a/tools/pharos-dashboard/dashboard/static/css/theme.css b/tools/pharos-dashboard/dashboard/static/css/theme.css
new file mode 100644 (file)
index 0000000..4cec341
--- /dev/null
@@ -0,0 +1,7 @@
+.blink_me {
+  animation: blinker 1.5s linear infinite;
+}
+
+@keyframes blinker {
+  20% { opacity: 0.4; }
+}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/booking-calendar.js b/tools/pharos-dashboard/dashboard/static/js/booking-calendar.js
new file mode 100644 (file)
index 0000000..edc2055
--- /dev/null
@@ -0,0 +1,68 @@
+function parseDisabledTimeIntervals(bookings) {
+    var timeIntervals = [];
+
+    for (var i = 0; i < bookings.length; i++) {
+        var interval = [
+            moment(bookings[i]['start_date_time']),
+            moment(bookings[i]['end_date_time'])
+        ];
+        timeIntervals.push(interval);
+    }
+    return timeIntervals;
+}
+
+function parseCalendarEvents(bookings) {
+    var events = [];
+    for (var i = 0; i < bookings.length; i++) {
+        event = {
+            id: bookings[i]['booking_id'],
+            title: bookings[i]['purpose'],
+            start: bookings[i]['start_date_time'],
+            end: bookings[i]['end_date_time'],
+            editable: true
+        };
+        events.push(event);
+    }
+    return events;
+}
+
+function loadEvents(bookings_url) {
+    $.ajax({
+        url: bookings_url,
+        type: 'get',
+        success: function (data) {
+            $('#calendar').fullCalendar('addEventSource', parseCalendarEvents(data['bookings']));
+            var intervals = parseDisabledTimeIntervals(data['bookings']);
+            $('#starttimepicker').data("DateTimePicker").disabledTimeIntervals(intervals);
+            $('#endtimepicker').data("DateTimePicker").disabledTimeIntervals(intervals);
+        },
+        failure: function (data) {
+            alert('Error loading booking data');
+        }
+    });
+}
+
+$(document).ready(function () {
+    $('#calendar').fullCalendar(calendarOptions);
+    $('#starttimepicker').datetimepicker(timepickerOptions);
+    $('#endtimepicker').datetimepicker(timepickerOptions);
+
+    loadEvents(bookings_url);
+
+    // send Post request to delete url if button is clicked
+    $("#deletebutton").click(function () {
+        var booking_id = $('#id_booking_id').val();
+        $.ajax({
+            type: 'post',
+            url: '/booking/' + booking_id + '/delete',
+            success: function () {
+                $('#calendar').fullCalendar('removeEvents');
+                loadEvents(bookings_url);
+                $('#calendar').fullCalendar('rerenderEvents');
+            },
+            failure: function () {
+                alert('Deleting failed')
+            }
+        })
+    })
+});
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/csrf.js b/tools/pharos-dashboard/dashboard/static/js/csrf.js
new file mode 100644 (file)
index 0000000..12429b3
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * use django csrf token in ajax requests
+ * source: https://docs.djangoproject.com/en/1.8/ref/csrf/#ajax
+ */
+// using jQuery
+function getCookie(name) {
+    var cookieValue = null;
+    if (document.cookie && document.cookie != '') {
+        var cookies = document.cookie.split(';');
+        for (var i = 0; i < cookies.length; i++) {
+            var cookie = jQuery.trim(cookies[i]);
+            // Does this cookie string begin with the name we want?
+            if (cookie.substring(0, name.length + 1) == (name + '=')) {
+                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                break;
+            }
+        }
+    }
+    return cookieValue;
+}
+var csrftoken = getCookie('csrftoken');
+
+function csrfSafeMethod(method) {
+    // these HTTP methods do not require CSRF protection
+    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+}
+
+$.ajaxSetup({
+    beforeSend: function (xhr, settings) {
+        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+            xhr.setRequestHeader("X-CSRFToken", csrftoken);
+        }
+    }
+});
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js b/tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js
new file mode 100644 (file)
index 0000000..7189ca1
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * 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/dashboard/static/js/datetimepicker-options.js b/tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js
new file mode 100644 (file)
index 0000000..1deb819
--- /dev/null
@@ -0,0 +1,7 @@
+/**
+ * Created by max on 7/25/16.
+ */
+var timepickerOptions = {
+    format: 'YYYY-MM-DD HH:00 ZZ',
+    sideBySide: true
+};
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js b/tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js
new file mode 100644 (file)
index 0000000..c0417eb
--- /dev/null
@@ -0,0 +1,65 @@
+// converts a moment to a readable fomat for the backend
+function convertInputTime(time) {
+    return moment(time).format('YYYY-MM-DD HH:00 ZZ');
+}
+
+function sendEventToForm(event) {
+    var start = convertInputTime(event.start);
+    var end = convertInputTime(event.end);
+    $('#starttimepicker').data("DateTimePicker").date(start);
+    $('#endtimepicker').data("DateTimePicker").date(end);
+    $('#submitform').html("Change Booking");
+    $('#purposefield').val(event.title);
+    $('#id_booking_id').val(event.id); // create a new booking
+    $("#deletebutton").removeClass('hidden'); // show delete button
+}
+
+var calendarOptions = {
+    height: 600,
+    header: {
+        left: 'prev,next today',
+        center: 'title',
+        right: 'agendaWeek,month'
+    },
+    timezone: 'local',
+    defaultView: 'agendaWeek',
+    slotDuration: '00:60:00',
+    slotLabelFormat: "HH:mm",
+    firstDay: 1,
+    allDaySlot: false,
+    selectOverlap: false,
+    eventOverlap: false,
+    selectable: true,
+    selectHelper: true,
+    editable: false,
+    eventLimit: true, // allow "more" link when too many events
+    timeFormat: 'H(:mm)', // uppercase H for 24-hour clock
+    unselectAuto: false,
+
+    select: function (start, end) {
+        var start = convertInputTime(start);
+        var end = convertInputTime(end);
+
+        $('#starttimepicker').data("DateTimePicker").date(start);
+        $('#endtimepicker').data("DateTimePicker").date(end);
+        $('#submitform').html("Book Pod");
+        $('#purposefield').val('');
+        $('#id_booking_id').val(''); // create a new booking
+        $("#deletebutton").addClass('hidden'); // hide delete button
+    },
+
+    eventClick: function (event, jsEvent, view) {
+        $('#calendar').fullCalendar('unselect');
+        sendEventToForm(event);
+    },
+
+    eventDrop: function (event) {
+        $('#calendar').fullCalendar('unselect');
+        sendEventToForm(event);
+    },
+
+    eventResize: function (event) {
+        $('#calendar').fullCalendar('unselect');
+        sendEventToForm(event);
+    }
+};
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/tests.py b/tools/pharos-dashboard/dashboard/tests.py
new file mode 100644 (file)
index 0000000..7509578
--- /dev/null
@@ -0,0 +1,41 @@
+import dashboard.jenkins.jenkins_adapter as jenkins
+from django.test import SimpleTestCase
+
+
+# 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(SimpleTestCase):
+    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/dashboard/urls.py b/tools/pharos-dashboard/dashboard/urls.py
new file mode 100644 (file)
index 0000000..8050eb8
--- /dev/null
@@ -0,0 +1,35 @@
+from dashboard.views.booking import BookingCalendarView, ResourceBookingsView, DeleteBookingView
+from dashboard.views.table_views import CIPodsView, DevelopmentPodsView, JenkinsSlavesView
+from django.conf.urls import url, include
+from django.contrib.auth import views as auth_views
+
+
+urlpatterns = [
+    # registration
+    url(r'^accounts/login/$', auth_views.login, name='login'),
+    url(r'^accounts/logout/$', auth_views.logout, name='logout'),
+
+    # Index
+    url(r'^index/$', CIPodsView.as_view(), name='index'),
+    url(r'^index/$', CIPodsView.as_view(), name='index'),
+    url(r'^$', CIPodsView.as_view(), name=""),
+
+    # Tables
+    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'),
+
+    # Booking Calendar
+    url(r'^booking_calendar/$', DevelopmentPodsView.as_view(),
+        name='booking_calendar'),
+    url(r'^booking_calendar/(?P<resource_id>[0-9]+)/$',
+        BookingCalendarView.as_view(), name='booking_calendar'),
+    url(r'^booking_calendar/(?P<resource_id>[0-9]+)/(?P<booking_id>[0-9]+)/$',
+        BookingCalendarView.as_view(), name='booking_calendar'),
+
+    # AJAX urls
+    url(r'^resource/(?P<resource_id>[0-9]+)/bookings/$',
+        ResourceBookingsView.as_view(), name='resource_bookings'),
+    url(r'^booking/(?P<booking_id>[0-9]+)/delete$',
+        DeleteBookingView.as_view(), name='delete_booking'),
+]
diff --git a/tools/pharos-dashboard/dashboard/views/booking.py b/tools/pharos-dashboard/dashboard/views/booking.py
new file mode 100644 (file)
index 0000000..1499edb
--- /dev/null
@@ -0,0 +1,69 @@
+from dashboard.forms.booking_form import BookingForm
+from dashboard.models import Resource, Booking
+from dashboard.views.registration import BookingUserTestMixin
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.http import Http404, JsonResponse
+from django.shortcuts import get_object_or_404
+from django.utils.decorators import method_decorator
+from django.views.generic import FormView, View
+
+
+@method_decorator(login_required, name='dispatch')
+class BookingCalendarView(BookingUserTestMixin, FormView):
+    template_name = "dashboard/booking_calendar.html"
+    form_class = BookingForm
+
+    # set instance variables
+    def dispatch(self, request, *args, **kwargs):
+        self.foo = request.GET.get('foo', False)
+        self.resource = get_object_or_404(Resource, resource_id=self.kwargs['resource_id'])
+        return super(BookingCalendarView, self).dispatch(request, *args, **kwargs)
+
+    def form_valid(self, form):
+        self.success_url = self.request.path
+        booking = None
+        # change existing booking
+        if form.cleaned_data['booking_id'] is not None:
+            booking = get_object_or_404(Booking, booking_id=form.cleaned_data['booking_id'])
+        # create new booking
+        else:
+            booking = Booking()
+            booking.resource = self.resource
+            booking.user = self.request.user
+        booking.start_date_time = form.cleaned_data['start_date_time']
+        booking.end_date_time = form.cleaned_data['end_date_time']
+        booking.purpose = form.cleaned_data['purpose']
+        try:
+            booking.save()
+        except ValueError:
+            messages.add_message(self.request, messages.ERROR,
+                                 'This booking overlaps with another booking')
+            return super(BookingCalendarView, self).form_invalid(form)
+        messages.add_message(self.request, messages.SUCCESS,
+                             'Booking saved')
+        return super(BookingCalendarView, self).form_valid(form)
+
+    def get_context_data(self, **kwargs):
+        title = 'Booking: ' + self.resource.name
+        context = super(BookingCalendarView, self).get_context_data(**kwargs)
+        context.update({'title': title, 'resource': self.resource})
+        return context
+
+
+@method_decorator(login_required, name='dispatch')
+class ResourceBookingsView(BookingUserTestMixin, View):
+    def get(self, request, *args, **kwargs):
+        resource = Resource.objects.get(resource_id=self.kwargs['resource_id'])
+        bookings = resource.booking_set.get_queryset().values(
+            'booking_id', 'user__username', 'user__email',
+            'start_date_time', 'end_date_time', 'purpose')
+        return JsonResponse({'bookings': list(bookings)})
+
+
+@method_decorator(login_required, name='dispatch')
+class DeleteBookingView(BookingUserTestMixin, View):
+    def post(self, request, *args, **kwargs):
+        booking = get_object_or_404(Booking, booking_id=self.kwargs['booking_id'])
+        booking.delete()
+        return JsonResponse({True: self.kwargs['booking_id']})
diff --git a/tools/pharos-dashboard/dashboard/views/registration.py b/tools/pharos-dashboard/dashboard/views/registration.py
new file mode 100644 (file)
index 0000000..516fb29
--- /dev/null
@@ -0,0 +1,16 @@
+from django.contrib.auth.mixins import UserPassesTestMixin
+
+
+class BookingUserTestMixin(UserPassesTestMixin):
+    # Test if a user has permission to book this Pod
+    def test_func(self):
+        user = self.request.user
+        # Check if User is troubleshooter / admin
+        if user.has_perm(('dashboard.add_booking')):
+            return True
+        # Check if User owns this resource
+        user_resources = user.userresource_set.get_queryset()
+        for user_resource in user_resources:
+            if user_resource.resource_id == self.resource.resource_id:
+                return True
+        return False
diff --git a/tools/pharos-dashboard/dashboard/views/table_views.py b/tools/pharos-dashboard/dashboard/views/table_views.py
new file mode 100644 (file)
index 0000000..4404234
--- /dev/null
@@ -0,0 +1,62 @@
+import dashboard.jenkins.jenkins_util as jenkins_util
+
+import dashboard.jenkins.jenkins_adapter as jenkins
+from dashboard.models import Resource, Booking
+from django.utils import timezone
+from django.views.generic import TemplateView
+
+
+class JenkinsSlavesView(TemplateView):
+    template_name = "tables/jenkins_slaves.html"
+
+    def get_context_data(self, **kwargs):
+        slaves = jenkins.get_all_slaves()
+        for slave in slaves:
+            jenkins_util.parse_slave_data(slave, slave)
+
+        context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
+        context.update({'title': "Jenkins Slaves", 'slaves': slaves})
+        return context
+
+
+class CIPodsView(TemplateView):
+    template_name = "tables/ci_pods.html"
+
+    def get_context_data(self, **kwargs):
+        resources = Resource.objects.filter().values()  # get resources as a set of dicts
+        ci_pods = []
+        for resource in resources:
+            if not jenkins.is_ci_slave(resource['slavename']):
+                continue
+            ci_slave = jenkins.get_slave(resource['slavename'])
+            jenkins_util.parse_slave_data(resource, ci_slave)
+            ci_pods.append(resource)
+
+        context = super(CIPodsView, self).get_context_data(**kwargs)
+        context.update({'title': "CI Pods", 'ci_pods': ci_pods})
+        return context
+
+
+class DevelopmentPodsView(TemplateView):
+    template_name = "tables/dev_pods.html"
+
+    def get_context_data(self, **kwargs):
+        resources = Resource.objects.filter().values()  # get resources as a set of dicts
+        dev_pods = []
+
+        current_bookings = Booking.objects.filter(start_date_time__lte=timezone.now())
+        current_bookings = current_bookings.filter(end_date_time__gt=timezone.now())
+
+        for resource in resources:
+            if not jenkins.is_dev_pod(resource['slavename']):
+                continue
+            dev_pod = jenkins.get_slave(resource['slavename'])
+            jenkins_util.parse_slave_data(resource, dev_pod)
+            for booking in current_bookings:
+                if booking.resource.slavename == resource['slavename']:
+                    resource['current_booking'] = booking
+            dev_pods.append(resource)
+
+        context = super(DevelopmentPodsView, self).get_context_data(**kwargs)
+        context.update({'title': "Development Pods", 'dev_pods': dev_pods})
+        return context
diff --git a/tools/pharos-dashboard/deploy.org b/tools/pharos-dashboard/deploy.org
new file mode 100644 (file)
index 0000000..b839921
--- /dev/null
@@ -0,0 +1,45 @@
+* 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/<dump_name>.json
+
+** Load dump
+
+-   setup clean database, run migrate
+-   (venv) # python manage.py loaddata <dump_name>
+
+* 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/issues.org b/tools/pharos-dashboard/issues.org
new file mode 100644 (file)
index 0000000..0a7d3ca
--- /dev/null
@@ -0,0 +1,6 @@
+* Fullcalendar
+- no selectHelper for month select
+- if an event is selected in month select, browser timezone is ignored
+
+* layout
+- datatable does not stay in its panel if zoom is to high / browser window is not maximized
diff --git a/tools/pharos-dashboard/manage.py b/tools/pharos-dashboard/manage.py
new file mode 100755 (executable)
index 0000000..65e6fc6
--- /dev/null
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pharos_dashboard.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)
diff --git a/tools/pharos-dashboard/pharos_dashboard/__init__.py b/tools/pharos-dashboard/pharos_dashboard/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/pharos_dashboard/settings.py b/tools/pharos-dashboard/pharos_dashboard/settings.py
new file mode 100644 (file)
index 0000000..2bc9496
--- /dev/null
@@ -0,0 +1,124 @@
+"""
+Django settings for opnfvdashboard project.
+
+Generated by 'django-admin startproject' using Django 1.9.7.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.9/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '=awtgkzaq@ytwbsp$$n=7=m&9*cm7gci7o-dy07)!x1um=g(gf'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+# Application definition
+
+INSTALLED_APPS = [
+    'dashboard',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'bootstrap3'
+]
+
+MIDDLEWARE_CLASSES = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+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'
+
+# Database
+# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': 'pharos_dashboard',
+        'USER': 'opnfv',
+        'PASSWORD': 'opnfvopnfv',
+        'HOST': 'localhost',
+        'PORT': '',
+    }
+}
+
+# Password validation
+# https://docs.djangoproject.com/en/1.9/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',
+    },
+]
+
+LOGIN_REDIRECT_URL = '/'
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.9/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.9/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/tools/pharos-dashboard/pharos_dashboard/urls.py b/tools/pharos-dashboard/pharos_dashboard/urls.py
new file mode 100644 (file)
index 0000000..03b9c25
--- /dev/null
@@ -0,0 +1,23 @@
+"""opnfvdashboard URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.9/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+
+from django.conf.urls import include, url
+from django.contrib import admin
+
+urlpatterns = [
+    url(r'^', include('dashboard.urls', namespace='dashboard')),
+    url(r'^admin/', include(admin.site.urls)),
+]
\ No newline at end of file
diff --git a/tools/pharos-dashboard/pharos_dashboard/wsgi.py b/tools/pharos-dashboard/pharos_dashboard/wsgi.py
new file mode 100644 (file)
index 0000000..54f5735
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+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.9/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/requirements.txt b/tools/pharos-dashboard/requirements.txt
new file mode 100644 (file)
index 0000000..6306c0b
--- /dev/null
@@ -0,0 +1,5 @@
+Django==1.9.8
+django-bootstrap3==7.0.1
+psycopg2==2.6.2
+pytz==2016.6.1
+requests==2.10.0
diff --git a/tools/pharos-dashboard/templates/dashboard/base.html b/tools/pharos-dashboard/templates/dashboard/base.html
new file mode 100644 (file)
index 0000000..544bf0b
--- /dev/null
@@ -0,0 +1,93 @@
+{% extends "layout/base.html" %}
+{% load bootstrap3 %}
+
+{% block basecontent %}
+    <div id="wrapper">
+        <!-- Navigation -->
+        <nav class="navbar navbar-default navbar-static-top" role="navigation"
+             style="margin-bottom: 0">
+            <div class="navbar-header">
+                <button type="button" class="navbar-toggle" data-toggle="collapse"
+                        data-target=".navbar-collapse">
+                    <span class="sr-only">Toggle navigation</span>
+                    <span class="icon-bar"></span>
+                    <span class="icon-bar"></span>
+                    <span class="icon-bar"></span>
+                </button>
+                <a href="https://www.opnfv.org/" class="navbar-left"><img
+                        src="https://www.opnfv.org/sites/all/themes/opnfv/logo.png"></a>
+                <a class="navbar-brand" href={% url 'dashboard:' %}>Pharos Dashboard</a>
+            </div>
+            <!-- /.navbar-header -->
+
+            <ul class="nav navbar-top-links navbar-right">
+                <li class="dropdown">
+                    <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                        <i class="fa fa-user fa-fw"></i> <i class="fa fa-caret-down"></i>
+                    </a>
+                    <ul class="dropdown-menu dropdown-user">
+                        {% if user.is_authenticated %}
+                            <li><a href="#"><i class="fa fa-user fa-fw"></i> User Profile</a>
+                            </li>
+                            <li><a href="#"><i class="fa fa-gear fa-fw"></i> Settings</a>
+                            </li>
+                            <li class="divider"></li>
+                            <li><a href="{% url 'dashboard:logout' %}?next={{ request.path }}"><i
+                                    class="fa fa-sign-out fa-fw"></i>
+                                Logout</a>
+                            </li>
+                        {% else %}
+                            <li><a href="{% url 'dashboard:login' %}"><i
+                                class="fa fa-sign-out fa-fw"></i>
+                            Login</a>
+                        {% endif %}
+                    </ul>
+                    <!-- /.dropdown-user -->
+                </li>
+                <!-- /.dropdown -->
+            </ul>
+            <!-- /.navbar-top-links -->
+
+            <div class="navbar-default sidebar" role="navigation">
+                <div class="sidebar-nav navbar-collapse">
+                    <ul class="nav" id="side-menu">
+                        <li>
+                            <a href="{% url 'dashboard:ci_pods' %}"><i
+                                    class="fa fa-table fa-fw"></i>CI-Pods</a>
+                        </li>
+                        <li>
+                            <a href="{% url 'dashboard:dev_pods' %}"><i
+                                    class="fa fa-table fa-fw"></i>Development
+                                Pods</a>
+                        </li>
+                        <li>
+                            <a href="{% url 'dashboard:jenkins_slaves' %}"><i
+                                    class="fa fa-table fa-fw"></i>Jenkins
+                                Slaves</a>
+                        </li>
+                    </ul>
+                </div>
+                <!-- /.sidebar-collapse -->
+            </div>
+            <!-- /.navbar-static-side -->
+        </nav>
+
+        <!-- Page Content -->
+        <div id="page-wrapper">
+            <div class="row">
+                <div class="col-lg-12">
+                    <h1 class="page-header">{{ title }}</h1>
+                </div>
+                <!-- /.col-lg-12 -->
+            </div>
+
+            {% bootstrap_messages %}
+
+            {% block content %}
+
+            {% endblock content %}
+        </div>
+        <!-- /#page-wrapper -->
+    </div>
+    <!-- /#wrapper -->
+{% endblock basecontent %}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/booking_calendar.html b/tools/pharos-dashboard/templates/dashboard/booking_calendar.html
new file mode 100644 (file)
index 0000000..0f6bece
--- /dev/null
@@ -0,0 +1,79 @@
+{% extends "dashboard/base.html" %}
+{% load staticfiles %}
+{% load bootstrap3 %}
+
+{% block extrahead %}
+    <link href="{% static "bower_components/fullcalendar/dist/fullcalendar.css" %}"
+          rel='stylesheet'/>
+    <link href="{% static "bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css" %}"
+          rel='stylesheet'/>
+{% endblock extrahead %}
+
+{% block content %}
+    <div class="row">
+        <div class="col-lg-8">
+            <div class="container-fluid">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        Calendar
+                    </div>
+                    <div class="panel-body">
+                        <div id='calendar'>
+                        </div>
+                    </div>
+                    <!-- /.panel-body -->
+                </div>
+                <!-- /.panel -->
+            </div>
+        </div>
+
+        <div class="col-lg-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    Booking
+                </div>
+                <div class="panel-body">
+                    {% bootstrap_form_errors form type='non_fields' %}
+
+                    <form method="post" class="form" id="bookingform">
+                        {% csrf_token %}
+                        <div class='input-group' id='starttimepicker'>
+                            {% bootstrap_field form.start_date_time addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+                        </div>
+                        <div class='input-group' id='endtimepicker'>
+                            {% bootstrap_field form.end_date_time addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+                        </div>
+                        {% bootstrap_field form.purpose %}
+                        {{ form.booking_id }}
+                        {% buttons %}
+                            <button type="submit" class="btn btn btn-success"
+                                    id="submitform">
+                                Book Pod
+                            </button>
+                            <button type="button" class="btn btn btn-danger hidden"
+                                    id="deletebutton">
+                                Delete Booking
+                            </button>
+                        {% endbuttons %}
+                    </form>
+
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock content %}
+
+{% block extrajs %}
+    <script type="text/javascript">
+        var bookings_url = '/resource/' + {{ resource.resource_id }} +'/bookings/';
+    </script>
+
+    <script src={% static "bower_components/moment/moment.js" %}></script>
+    <script src={% static "bower_components/fullcalendar/dist/fullcalendar.js" %}></script>
+    <script type="text/javascript"
+            src={% static "bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js" %}></script>
+    <script src={% static "js/fullcalendar-options.js" %}></script>
+    <script src={% static "js/datetimepicker-options.js" %}></script>
+    <script src={% static "js/csrf.js" %}></script>
+    <script src={% static "js/booking-calendar.js" %}></script>
+{% endblock extrajs %}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/table.html b/tools/pharos-dashboard/templates/dashboard/table.html
new file mode 100644 (file)
index 0000000..2d0b82e
--- /dev/null
@@ -0,0 +1,50 @@
+{% extends "dashboard/base.html" %}
+{% load staticfiles %}
+
+{% block extrahead %}
+    <!-- DataTables CSS -->
+    <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}"
+          rel="stylesheet">
+
+    <!-- DataTables Responsive CSS -->
+    <link href="{% static "bower_components/datatables-responsive/css/dataTables.responsive.css" %}" rel="stylesheet">
+{% endblock extrahead %}
+
+{% block content %}
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-body">
+                    <div class="dataTables_wrapper">
+                        <table class="table table-striped table-bordered table-hover" id="table" cellspacing="0"
+                               width="100%">
+
+                            {% block table %}
+                            {% endblock table %}
+
+                        </table>
+                    </div>
+                    <!-- /.table-responsive -->
+                </div>
+                <!-- /.panel-body -->
+            </div>
+            <!-- /.panel -->
+        </div>
+        <!-- /.col-lg-12 -->
+    </div>
+{% endblock content %}
+
+{% block extrajs %}
+    <!-- DataTables JavaScript -->
+    <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}"
+          rel="stylesheet">
+
+
+    <script src={% static "bower_components/datatables/media/js/jquery.dataTables.min.js" %}></script>
+    <script src={% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js" %}></script>
+
+    <script src={% static "js/dataTables-sort.js" %}></script>
+
+    {% block tablejs %}
+    {% endblock tablejs %}
+{% endblock extrajs %}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/layout/base.html b/tools/pharos-dashboard/templates/layout/base.html
new file mode 100644 (file)
index 0000000..e9e1cd1
--- /dev/null
@@ -0,0 +1,70 @@
+{% load staticfiles %}
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>OPNFV Pharos {{ title }}</title>
+
+    <!-- Bootstrap Core CSS -->
+    <link href="{% static "bower_components/bootstrap/dist/css/bootstrap.min.css" %}"
+          rel="stylesheet">
+
+    <!-- MetisMenu CSS -->
+    <link href="{% static "bower_components/metisMenu/dist/metisMenu.min.css" %}" rel="stylesheet">
+
+    <!-- Custom CSS -->
+    <link href="{% static "bower_components/startbootstrap-sb-admin-2/dist/css/sb-admin-2.css" %}"
+          rel="stylesheet">
+    <link href="{% static "css/theme.css" %}" rel="stylesheet">
+
+    <!-- Custom Fonts -->
+    <link href="{% static "bower_components/font-awesome/css/font-awesome.min.css" %}"
+          rel="stylesheet" type="text/css">
+
+    <!-- Favicon -->
+    <link rel="shortcut icon" href="{% static 'favicon.ico' %}">
+
+    {% block extrahead %}
+    {% endblock extrahead %}
+
+    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+    <!--[if lt IE 9]>
+        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
+    <![endif]-->
+
+</head>
+
+{% block extrastyle %}
+{% endblock extrastyle %}
+
+<body>
+{% block basecontent %}
+{% endblock basecontent %}
+
+<!-- jQuery -->
+<script src="{% static "bower_components/jquery/dist/jquery.min.js" %}"></script>
+<script src="{% static "bower_components/jquery-migrate/jquery-migrate.min.js" %}"></script>
+
+{#<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>#}
+<!-- Bootstrap Core JavaScript -->
+<script src="{% static "bower_components/bootstrap/dist/js/bootstrap.min.js" %}"></script>
+
+<!-- Metis Menu Plugin JavaScript -->
+<script src="{% static "bower_components/metisMenu/dist/metisMenu.min.js" %}"></script>
+
+<!-- Custom Theme JavaScript -->
+<script src="{% static "bower_components/startbootstrap-sb-admin-2/dist/js/sb-admin-2.js" %}"></script>
+
+{% block extrajs %}
+{% endblock extrajs %}
+</body>
+</html>
diff --git a/tools/pharos-dashboard/templates/registration/login.html b/tools/pharos-dashboard/templates/registration/login.html
new file mode 100644 (file)
index 0000000..efdcd1f
--- /dev/null
@@ -0,0 +1,61 @@
+{% extends "layout/base.html" %}
+
+{% block basecontent %}
+    <div class="container">
+        <div class="row">
+            <div class="col-md-4 col-md-offset-4">
+                {% if next %}
+                    <div class="alert alert-dismissable alert-info">
+                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                        {% if user.is_authenticated %}
+                            Your account doesn't have access to this page. To proceed,
+                            please login with an account that has access.
+                        {% else %}
+                            Please login to see this page.
+                        {% endif %}
+                    </div>
+                {% endif %}
+                {% if form.errors %}
+                    <div class="alert alert-danger alert-dismissable">
+                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                        Your username and password didn't match. Please try again.
+                    </div>
+                {% endif %}
+            </div>
+        </div>
+        <div class="row">
+            <div class="col-md-4 col-md-offset-4">
+                <div class="login-panel panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            Login
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <form method="post" action="{% url 'dashboard:login' %}">
+                            {% csrf_token %}
+                            <fieldset>
+                                <div class="form-group">
+                                    <input class="form-control" placeholder="Username" name="username" type="text"
+                                           autofocus>
+                                </div>
+                                <div class="form-group">
+                                    <input class="form-control" placeholder="Password" name="password"
+                                           type="password" value="">
+                                </div>
+                                <input type="submit" value="Login" class="btn btn-lg btn-success btn-block"/>
+                                <input type="hidden" name="next" value="{{ next }}"/>
+                            </fieldset>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    {# Assumes you setup the password_reset view in your URLconf #}
+    {# <p><a href="{% url 'password_reset' %}">Lost password?</a></p>#}
+{% endblock basecontent %}
diff --git a/tools/pharos-dashboard/templates/tables/ci_pods.html b/tools/pharos-dashboard/templates/tables/ci_pods.html
new file mode 100644 (file)
index 0000000..3889664
--- /dev/null
@@ -0,0 +1,59 @@
+{% extends "dashboard/table.html" %}
+{% load staticfiles %}
+
+{% block table %}
+    <thead>
+    <tr>
+        <th>Name</th>
+        <th>Slave Name</th>
+        <th>Status</th>
+        <th>Installer</th>
+        <th>Scenario</th>
+        <th>Branch</th>
+        <th>Job</th>
+    </tr>
+    </thead>
+    <tbody>
+    {% for pod in ci_pods %}
+        <tr>
+            <th>
+                <a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
+            </th>
+            <th>
+                <a target='_blank' href={{ pod.slaveurl }}>{{ pod.slavename }}</a>
+            </th>
+            <th style="background-color:{{ pod.status_color }}">
+                {{ pod.status }}
+            </th>
+            <th {{ pod.last_job.blink }}>
+                {{ pod.last_job.installer }}
+            </th>
+            <th {{ pod.last_job.blink }}>
+                {{ pod.last_job.scenario }}
+            </th>
+            <th {{ pod.last_job.blink }}>
+                {{ pod.last_job.branch }}
+            </th>
+            <th><a {{ pod.last_job.blink }} style="color:{{ pod.last_job.color }}"
+                                           target='_blank'
+                                           href={{ pod.last_job.url }}>{{ pod.last_job.name }}</a>
+            </th>
+        </tr>
+    {% endfor %}`
+    </tbody>
+    </table>
+{% endblock table %}
+
+
+{% block tablejs %}
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $('#table').DataTable({
+                columnDefs: [
+                    {type: 'status', targets: 2}
+                ],
+                "order": [[2, "asc"]]
+            });
+        });
+    </script>
+{% endblock tablejs %}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/tables/dev_pods.html b/tools/pharos-dashboard/templates/tables/dev_pods.html
new file mode 100644 (file)
index 0000000..730aa95
--- /dev/null
@@ -0,0 +1,58 @@
+{% extends "dashboard/table.html" %}
+{% load staticfiles %}
+
+{% block table %}
+    <thead>
+    <tr>
+        <th>Name</th>
+        <th>Slave Name</th>
+        <th>Booked by</th>
+        <th>Booked until</th>
+        <th>Purpose</th>
+        <th>Status</th>
+        <th></th>
+    </tr>
+    </thead>
+    <tbody>
+    {% for pod in dev_pods %}
+        <tr>
+            <th>
+                <a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
+            </th>
+            <th>
+                <a target='_blank' href={{ pod.slaveurl }}>{{ pod.slavename }}</a>
+            </th>
+            <th>
+                {{ pod.current_booking.user }}
+            </th>
+            <th>
+                {{ pod.current_booking.end_date_time }}
+            </th>
+            <th>
+                {{ pod.current_booking.purpose }}
+            </th>
+            <th style="background-color:{{ pod.status_color }}">
+                {{ pod.status }}
+            </th>
+            <th>
+                <a href='{% url 'dashboard:booking_calendar' %}{{ pod.resource_id }}' class="btn btn-primary">
+                    Book
+                </a>
+            </th>
+        </tr>
+    {% endfor %}
+    </tbody>
+{% endblock table %}
+
+{% block tablejs %}
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $('#table').DataTable({
+                columnDefs: [
+                    {type: 'status', targets: 5}
+                ],
+                "order": [[5, "asc"]]
+            });
+        });
+    </script>
+{% endblock tablejs %}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/tables/jenkins_slaves.html b/tools/pharos-dashboard/templates/tables/jenkins_slaves.html
new file mode 100644 (file)
index 0000000..2d011b4
--- /dev/null
@@ -0,0 +1,42 @@
+{% extends "dashboard/table.html" %}
+{% load staticfiles %}
+
+{% block table %}
+    <thead>
+    <tr>
+        <th>Slave name</th>
+        <th>Status</th>
+        <th>Job</th>
+    </tr>
+    </thead>
+    <tbody>
+    {% for slave in slaves %}
+        <tr>
+            <th><a target='_blank'
+                   href={{ slave.slaveurl }}>{{ slave.displayName }}</a>
+            </th>
+            <th style="background-color:{{ slave.status_color }}">
+                {{ slave.status }}
+            </th>
+            <th><a {{ slave.last_job.blink }} style="color:{{ slave.last_job.color }}"
+                                              target="_blank" href={{ slave.last_job.url }}>
+                {{ slave.last_job.name }}</a>
+            </th>
+        </tr>
+    {% endfor %}
+    </tbody>
+{% endblock table %}
+
+
+{% block tablejs %}
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $('#table').DataTable({
+                columnDefs: [
+                    {type: 'status', targets: 1}
+                ],
+                "order": [[1, "asc"]]
+            });
+        });
+    </script>
+{% endblock tablejs %}
\ No newline at end of file
diff --git a/tools/pharos_dashboard b/tools/pharos_dashboard
deleted file mode 160000 (submodule)
index 88df205..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 88df205bde4fe9bda20a59f2ab63a6e0408f3c35