summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--docs/configguide/index.rst30
-rw-r--r--docs/configguide/pharosconfig.rst25
-rwxr-xr-xdocs/index.rst17
-rw-r--r--docs/information/index.rst14
-rw-r--r--docs/information/pharos.rst42
-rw-r--r--docs/lab-description/index.rst (renamed from docs/lab-description/templates.rst)10
-rw-r--r--docs/labs/ericsson/images/ericsson-opnfv-topology.pngbin0 -> 1011133 bytes
-rw-r--r--docs/labs/ericsson/index.rst (renamed from docs/labs/zte-nj-lab/zte-nj.rst)9
-rw-r--r--docs/labs/ericsson/lab_description.rst148
-rw-r--r--docs/labs/images/orange_pod2.pngbin0 -> 83662 bytes
-rwxr-xr-xdocs/labs/index.rst20
-rw-r--r--docs/labs/ool/index.rst (renamed from docs/labs/ool/ool.rst)0
-rw-r--r--docs/labs/orange-lannion-lab/index.rst215
-rw-r--r--docs/labs/orange-paris-lab/index.rst (renamed from docs/labs/orange-paris-lab/orange_paris.rst)0
-rw-r--r--docs/labs/zte-nj-lab/images/zte_nj_lab_topology.pngbin84145 -> 0 bytes
-rw-r--r--docs/labs/zte-nj-lab/lab_description.rst86
-rw-r--r--docs/labs/zte-nj-lab/pod1_description.rst127
-rw-r--r--docs/labs/zte-nj-lab/pod2_description.rst130
-rw-r--r--docs/labs/zte-sh-lab/images/zte_sh_lab_topology.pngbin241260 -> 223339 bytes
-rw-r--r--docs/labs/zte-sh-lab/images/zte_sh_pod_topology.png (renamed from docs/labs/zte-sh-lab/images/zte_sh_pod3_topology.png)bin125873 -> 125873 bytes
-rw-r--r--docs/labs/zte-sh-lab/index.rst (renamed from docs/labs/zte-sh-lab/zte-sh.rst)3
-rw-r--r--docs/labs/zte-sh-lab/lab_description.rst70
-rw-r--r--docs/labs/zte-sh-lab/pod1_description.rst160
-rw-r--r--docs/labs/zte-sh-lab/pod1_inventory.yaml (renamed from docs/labs/zte-nj-lab/pod1_inventory.yaml)23
-rw-r--r--docs/labs/zte-sh-lab/pod2_description.rst162
-rw-r--r--docs/labs/zte-sh-lab/pod2_inventory.yaml (renamed from docs/labs/zte-nj-lab/pod2_inventory.yaml)23
-rw-r--r--docs/labs/zte-sh-lab/pod3_description.rst28
-rw-r--r--tools/pharos-dashboard/.gitignore1
-rw-r--r--tools/pharos-dashboard/README.md1
-rw-r--r--tools/pharos-dashboard/TODO3
-rw-r--r--tools/pharos-dashboard/account/__init__.py0
-rw-r--r--tools/pharos-dashboard/account/admin.py5
-rw-r--r--tools/pharos-dashboard/account/apps.py5
-rw-r--r--tools/pharos-dashboard/account/forms.py12
-rw-r--r--tools/pharos-dashboard/account/jira_util.py56
-rw-r--r--tools/pharos-dashboard/account/middleware.py22
-rw-r--r--tools/pharos-dashboard/account/migrations/__init__.py0
-rw-r--r--tools/pharos-dashboard/account/models.py20
-rw-r--r--tools/pharos-dashboard/account/rsa.pem17
-rw-r--r--tools/pharos-dashboard/account/rsa.pub6
-rw-r--r--tools/pharos-dashboard/account/tests/__init__.py0
-rw-r--r--tools/pharos-dashboard/account/tests/test_general.py40
-rw-r--r--tools/pharos-dashboard/account/urls.py25
-rw-r--r--tools/pharos-dashboard/account/views.py111
-rw-r--r--tools/pharos-dashboard/booking/__init__.py0
-rw-r--r--tools/pharos-dashboard/booking/admin.py5
-rw-r--r--tools/pharos-dashboard/booking/apps.py5
-rw-r--r--tools/pharos-dashboard/booking/forms.py9
-rw-r--r--tools/pharos-dashboard/booking/migrations/__init__.py0
-rw-r--r--tools/pharos-dashboard/booking/models.py59
-rw-r--r--tools/pharos-dashboard/booking/tests/__init__.py0
-rw-r--r--tools/pharos-dashboard/booking/tests/test_models.py97
-rw-r--r--tools/pharos-dashboard/booking/tests/test_views.py71
-rw-r--r--tools/pharos-dashboard/booking/urls.py26
-rw-r--r--tools/pharos-dashboard/booking/views.py97
-rw-r--r--tools/pharos-dashboard/celerybeat-schedulebin0 -> 16384 bytes
-rw-r--r--tools/pharos-dashboard/dashboard/admin.py7
-rw-r--r--tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json1
-rw-r--r--tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json1
-rw-r--r--tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json1
-rw-r--r--tools/pharos-dashboard/dashboard/fixtures/dashboard.json164
-rw-r--r--tools/pharos-dashboard/dashboard/forms/booking_form.py37
-rw-r--r--tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py70
-rw-r--r--tools/pharos-dashboard/dashboard/migrations/0001_initial.py107
-rw-r--r--tools/pharos-dashboard/dashboard/models.py82
-rw-r--r--tools/pharos-dashboard/dashboard/static/css/theme.css7
-rw-r--r--tools/pharos-dashboard/dashboard/static/js/booking-calendar.js68
-rw-r--r--tools/pharos-dashboard/dashboard/static/js/csrf.js34
-rw-r--r--tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js7
-rw-r--r--tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js65
-rw-r--r--tools/pharos-dashboard/dashboard/templatetags/__init__.py0
-rw-r--r--tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py28
-rw-r--r--tools/pharos-dashboard/dashboard/templatetags/jira_filters.py8
-rw-r--r--tools/pharos-dashboard/dashboard/urls.py47
-rw-r--r--tools/pharos-dashboard/dashboard/views.py78
-rw-r--r--tools/pharos-dashboard/dashboard/views/booking.py69
-rw-r--r--tools/pharos-dashboard/dashboard/views/registration.py16
-rw-r--r--tools/pharos-dashboard/dashboard/views/table_views.py62
-rw-r--r--tools/pharos-dashboard/issues.org6
-rw-r--r--tools/pharos-dashboard/jenkins/__init__.py0
-rw-r--r--tools/pharos-dashboard/jenkins/adapter.py (renamed from tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py)46
-rw-r--r--tools/pharos-dashboard/jenkins/apps.py5
-rw-r--r--tools/pharos-dashboard/jenkins/migrations/__init__.py0
-rw-r--r--tools/pharos-dashboard/jenkins/models.py50
-rw-r--r--tools/pharos-dashboard/jenkins/tasks.py39
-rw-r--r--tools/pharos-dashboard/jenkins/tests.py (renamed from tools/pharos-dashboard/dashboard/tests.py)7
-rwxr-xr-xtools/pharos-dashboard/manage.py18
-rw-r--r--tools/pharos-dashboard/pharos_dashboard/__init__.py3
-rw-r--r--tools/pharos-dashboard/pharos_dashboard/celery.py20
-rw-r--r--tools/pharos-dashboard/pharos_dashboard/settings.py124
-rw-r--r--tools/pharos-dashboard/pharos_dashboard/urls.py12
-rw-r--r--tools/pharos-dashboard/pharos_dashboard/wsgi.py2
-rw-r--r--tools/pharos-dashboard/requirements.txt5
-rw-r--r--tools/pharos-dashboard/static/bower.json (renamed from tools/pharos-dashboard/dashboard/static/bower.json)0
-rw-r--r--tools/pharos-dashboard/static/css/theme.css13
-rw-r--r--tools/pharos-dashboard/static/js/booking-calendar.js36
-rw-r--r--tools/pharos-dashboard/static/js/dataTables-sort.js (renamed from tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js)0
-rw-r--r--tools/pharos-dashboard/static/js/datetimepicker-options.js3
-rw-r--r--tools/pharos-dashboard/static/js/fullcalendar-options.js91
-rw-r--r--tools/pharos-dashboard/templates/account/userprofile_update_form.html30
-rw-r--r--tools/pharos-dashboard/templates/base.html (renamed from tools/pharos-dashboard/templates/dashboard/base.html)30
-rw-r--r--tools/pharos-dashboard/templates/booking/booking_calendar.html (renamed from tools/pharos-dashboard/templates/dashboard/booking_calendar.html)81
-rw-r--r--tools/pharos-dashboard/templates/booking/booking_detail.html26
-rw-r--r--tools/pharos-dashboard/templates/booking/booking_table.html33
-rw-r--r--tools/pharos-dashboard/templates/dashboard/ci_pods.html (renamed from tools/pharos-dashboard/templates/tables/ci_pods.html)27
-rw-r--r--tools/pharos-dashboard/templates/dashboard/dev_pods.html (renamed from tools/pharos-dashboard/templates/tables/dev_pods.html)19
-rw-r--r--tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html (renamed from tools/pharos-dashboard/templates/tables/jenkins_slaves.html)13
-rw-r--r--tools/pharos-dashboard/templates/dashboard/resource.html58
-rw-r--r--tools/pharos-dashboard/templates/dashboard/resource_all.html74
-rw-r--r--tools/pharos-dashboard/templates/dashboard/resource_detail.html64
-rw-r--r--tools/pharos-dashboard/templates/dashboard/server_table.html30
-rw-r--r--tools/pharos-dashboard/templates/dashboard/table.html2
-rw-r--r--tools/pharos-dashboard/templates/layout.html (renamed from tools/pharos-dashboard/templates/layout/base.html)9
-rw-r--r--tools/pharos-dashboard/templates/registration/login.html61
115 files changed, 2636 insertions, 1398 deletions
diff --git a/.gitignore b/.gitignore
index bdc0ec9f..1b737286 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,3 +61,6 @@ target/
/docs_build/
/docs_output/
/releng/
+
+# macos preview
+.DS_Store
diff --git a/docs/configguide/index.rst b/docs/configguide/index.rst
new file mode 100644
index 00000000..c51f029b
--- /dev/null
+++ b/docs/configguide/index.rst
@@ -0,0 +1,30 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) 2016 OPNFV.
+
+.. Top level of Pharos configuration documents.
+
+********************
+Pharos Configuration
+********************
+
+OPNFV development, test and production activities rely on Pharos resources and support from the
+Pharos community. Lab owners and Pharos project committers/contributors will evolve the vision for
+Pharos as well as expand lab capabilities that are needed to help OPNFV be highly successful.
+
+Pharos configuration documents provide information on how to setup hardware and networks in a
+Pharos compliant lab. Jira is used to track Pharos activities including lab operations. Lab
+resources can be used for and declared as either *Development (bare-metal or virtual)* or
+*Production/CI (bare-metal or virtual)*. If a resource is used for and declared as *Development*
+resource, it can not be used for and declared as *Production/CI* resource at the same time and vice
+versa. Changing the resource declation must be brought in to Infra WG. Production/CI PODs are
+required to be connected to OPNFV Jenkins and available on a 24/7 basis other than scheduled
+maintenance and troubleshooting. Jenkins slave status can be seen on `Jenkins dashboard
+https://build.opnfv.org/ci/computer/`.
+
+.. toctree::
+ :maxdepth: 2
+
+ ./configguide.rst
+ ./lab_update_guide.rst
+ ./jumpserverinstall.rst
diff --git a/docs/configguide/pharosconfig.rst b/docs/configguide/pharosconfig.rst
deleted file mode 100644
index 6b48100a..00000000
--- a/docs/configguide/pharosconfig.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-.. This work is licensed under a Creative Commons Attribution 4.0 International License.
-.. http://creativecommons.org/licenses/by/4.0
-.. (c) 2016 OPNFV.
-
-.. Top level of Pharos configuration documents.
-
-********************
-Pharos Configuration
-********************
-
-OPNFV development, test and production activities rely on Pharos resources and support from the
-Pharos community. Lab owners and Pharos project committers/contributors will evolve the vision for
-Pharos as well as expand lab capabilities that are needed to help OPNFV be highly successful.
-
-Pharos configuration documents provide information on how to setup hardware and networks in a Pharos
-compliant lab. Jira is used to track Pharos activities including lab operations. PODs are connected
-to Jenkins and generally available 24/7 other than scheduled maintenance and troubleshooting. Lab
-resources are declared as either for *Development (bare-metal or virtual)*, *Production latest
-(bare-metal)* or *Production stable (bare-metal)*
-
-.. toctree::
-
- ./configguide.rst
- ./lab_update_guide.rst
- ./jumpserverinstall.rst
diff --git a/docs/index.rst b/docs/index.rst
index 36b48f52..94844fa9 100755
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,19 +17,12 @@ OPNFV Community Lab Infrastructure
:maxdepth: 3
:numbered: 3
- ./information/pharos.rst
- ./specification/pharosspec.rst
- ./lab-description/templates.rst
- ./configguide/pharosconfig.rst
- ./labs/Dell.rst
- ./labs/huawei-us-lab/huawei-us-lab.rst
- ./labs/ool/ool.rst
- ./labs/orange-paris-lab/orange_paris.rst
- ./labs/spirent.rst
- ./labs/zte-nj-lab/zte-nj.rst
- ./labs/zte-sh-lab/zte-sh.rst
+ ./information/index.rst
+ ./specification/index.rst
+ ./lab-description/index.rst
+ ./configguide/index.rst
+ ./labs/index.rst
Indices
=======
* :ref:`search`
-
diff --git a/docs/information/index.rst b/docs/information/index.rst
new file mode 100644
index 00000000..0311526a
--- /dev/null
+++ b/docs/information/index.rst
@@ -0,0 +1,14 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) 2016 OPNFV.
+
+
+**************************
+Pharos Project Information
+**************************
+
+
+.. toctree::
+ :maxdepth: 2
+
+ ./pharos.rst
diff --git a/docs/information/pharos.rst b/docs/information/pharos.rst
index 24e46d97..1679e380 100644
--- a/docs/information/pharos.rst
+++ b/docs/information/pharos.rst
@@ -2,13 +2,6 @@
.. http://creativecommons.org/licenses/by/4.0
.. (c) 2016 OPNFV.
-.. OPNFV Pharos Project Information file.
-
-.. _pharos_information:
-
-**************************
-Pharos Project Information
-**************************
Introduction
------------
@@ -30,28 +23,31 @@ federated lab infrastructure continues to be an objective. Virtual environments
provided by some labs. Jira is currently used for tracking lab operational issues as well as for
Pharos project activities.
-Future lab capabilities are currently focussed on 1) Deployment automation 2) Dashboards (for
-capability and usage) 3) *Virtual Labs* for developer on-boarding.
+Future lab capabilities are currently focused on:
-* `Pharos page <https://www.opnfv.org/developers/pharos>`_
-* `Pharos project Wiki <https://wiki.opnfv.org/display/pharos>`_
-* `Pharos Planning <https://wiki.opnfv.org/display/pharos/Pharos+Colorado+Plan>`_
+1) Automatic resource provisioning
+2) Dashboards (for capability and usage)
+3) *Virtual Labs* for developer on-boarding
Project Communication
---------------------
-* `Jira <https://jira.opnfv.org/projects/PHAROS/summary>`_
-* `Weekly Pharos meeting <https://wiki.opnfv.org/display/INF/Infra+Working+Group>`_
+* `Pharos page <https://www.opnfv.org/developers/pharos>`_
+* `Pharos project Wiki <https://wiki.opnfv.org/display/pharos>`_
+* `Pharos Planning <https://wiki.opnfv.org/display/pharos/Pharos+Colorado+Plan>`_
+* `Pharos Jira <https://jira.opnfv.org/projects/PHAROS/summary>`_
+* `Bi-weekly Pharos meeting <https://wiki.opnfv.org/display/pharos/Pharos+Meetings>`_
+* `Weekly INFRA WG meeting <https://wiki.opnfv.org/display/INF/Infra+Working+Group>`_
* `Weekly coordination meeting for Test related projects <https://wiki.opnfv.org/meetings/test>`_
-* IRC: freenode.net #opnfv-pharos http://webchat.freenode.net/?channels=opnfv-pharos
+* `IRC: freenode.net #opnfv-pharos <http://webchat.freenode.net/?channels=opnfv-pharos>`_
* Mailing List: use opnfv-tech-discuss and tag your emails with [Pharos] in the subject for filtering
Project Release Artifacts
-------------------------
-* Project Repository: https://gerrit.opnfv.org/gerrit/#/q/pharos
-* Continuous Integration https://build.opnfv.org/ci/view/pharos/
-* Documentation: http://artifacts.opnfv.org/pharos/docs/
+* `Project Repository <https://gerrit.opnfv.org/gerrit/gitweb?p=pharos.git>`_
+* `Continuous Integration <https://build.opnfv.org/ci/view/pharos/>`_
+* `Documentation <http://artifacts.opnfv.org/pharos/docs/>`_
Pharos Lab Process
------------------
@@ -103,7 +99,7 @@ An interactive map of OPNFV lab locations, lab owners and other lab information
| 11 | Orange | https://wiki.opnfv.org/display/pharos/Opnfv-orange | Paris, France |
| | | | |
+----+---------------+----------------------------------------------------------------------------+-------------------+
-| 12 | ZTE | https://wiki.opnfv.org/display/pharos/ZTE+NJ+Testlab | Nan Jing, China |
+| 12 | ZTE | https://wiki.opnfv.org/display/pharos/ZTE+SH+Testlab | Shanghai, China |
| | | | |
+----+---------------+----------------------------------------------------------------------------+-------------------+
| 13 | Okinawa | https://wiki.opnfv.org/display/pharos/OOL+TestLab | Okinawa |
@@ -117,6 +113,8 @@ Pharos project Key Facts
**Key Project Facts are maintained in the Pharos INFO file in the project repo**
- * Can be viewed on the project Wiki
- `INFO <https://wiki.opnfv.org/pharos?&#pharos_project_-_key_facts>`_
- * Project key facts in repo: pharos/INFO
+ * Can be viewed on the project
+ `wiki INFO <https://wiki.opnfv.org/pharos?&#pharos_project_-_key_facts>`_
+ * Project key facts in
+ `repo INFO <https://gerrit.opnfv.org/gerrit/gitweb?p=pharos.git;f=INFO;hb=refs/heads/master>`_
+
diff --git a/docs/lab-description/templates.rst b/docs/lab-description/index.rst
index 33cc5559..23b675ae 100644
--- a/docs/lab-description/templates.rst
+++ b/docs/lab-description/index.rst
@@ -11,16 +11,16 @@ Pharos Templates and Configuration Files
Lab and POD templates are provided to help lab owners document capabilities, configurations and
network topologies. Compute, network and storage specifications with network topology details are
required to help developers use lab resources efficiently while minimizing support needs. This also
-greatly assists with troubleshhoting. It is the responsibility of the lab owner to keep individual
-lab documents updated and determine appropriate level of detail that is exposed publically through
+greatly assists with troubleshoting. It is the responsibility of the lab owner to keep individual
+lab documents updated and determine appropriate level of detail that is exposed publicly through
the Wiki or maintained in a secure Pharos repo with controlled access.
-While human readable configuration files are needed, the goal is for full automation of deployments.
-This requires a common machine readable format for POD configurations as input to every installer.
-This is the "POD inventory" common format file.
+The goal of the Pharos Project is automation of resource provisioning. This requires machine
+readable inventory and network configuration files that follow common format.
.. toctree::
+ :maxdepth: 2
./lab_description.rst
./pod_description.rst
diff --git a/docs/labs/ericsson/images/ericsson-opnfv-topology.png b/docs/labs/ericsson/images/ericsson-opnfv-topology.png
new file mode 100644
index 00000000..b3c86d3e
--- /dev/null
+++ b/docs/labs/ericsson/images/ericsson-opnfv-topology.png
Binary files differ
diff --git a/docs/labs/zte-nj-lab/zte-nj.rst b/docs/labs/ericsson/index.rst
index 94fb322f..f7058eca 100644
--- a/docs/labs/zte-nj-lab/zte-nj.rst
+++ b/docs/labs/ericsson/index.rst
@@ -4,13 +4,12 @@
.. Top level of Pharos templates and configuration files
-*****************************************
-ZTE NJ Pharos Lab and Configuration Files
-*****************************************
+**************************************
+ERICSSON OPNFV Lab Configuration Files
+**************************************
.. toctree::
+ :maxdepth: 2
./lab_description.rst
- ./pod1_description.rst
- ./pod2_description.rst
diff --git a/docs/labs/ericsson/lab_description.rst b/docs/labs/ericsson/lab_description.rst
new file mode 100644
index 00000000..b4c5b856
--- /dev/null
+++ b/docs/labs/ericsson/lab_description.rst
@@ -0,0 +1,148 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) 2016 OPNFV.
+
+.. _pharos_lab:
+
+*********************************
+Ericssion OPNFV Lab Specification
+*********************************
+
+
+Introduction
+------------
+
+Ericsson OPNFV Lab currently has 2 Bare Metal and 3 Virtual PODs available globally (hosted in
+the GIC). Each POD has 5 servers, comprised of 3 controller nodes (HA) and 2 computes nodes. NOTE:
+(this make differ depending on scenario).
+
+.. _pharos_pod:
+
+These PODs are dedicated for use by Production/CI. These PODs focus on providing verification,
+build, deploy and testing for scenarios related with **test** projects, **installer** projects and
+perforamnce enhancement projects, such as KVM, OVS, FDS, etc.
+
+In addition to the full-time CI/CD resources, the Ericsson OPNFV lab provides developer labs (DRs)
+for project usage, testing and development.
+
+Scenarios services by this lab are:
+
+Scenario defitions can be found here:
+`Colorado Scenario Status <https://wiki.opnfv.org/display/SWREL/Colorado+Scenario+Status>`_
+
+Lab Resources
+-------------
+
+- `Ericsson Hostting And Request Page <https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process>`_
+
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| POD Name | Project(s) | PTL(s) | Email(s) | POD Role | Status | Notes |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| POD1 | CI/CD | Daniel | daniel.smith@ericsson.com | CI: latest | Active | BM-CI |
+| | | Smith | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| POD2 | CI/CD | Daniel | daniel.smith@ericsson.com | CI: latest | Active | BM-CI |
+| | | Smith | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| vPOD1 | CI/CD | Fatih | fatih.degirmenci@ericsson.com | CI: latest | Active | Virt-CI |
+| | | Degirmenci | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-166 | FUEL | Constant | constant.wette@ericsson.com | DR: B-rel | Active | Nested |
+| | | Wette | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-167 | OVSNFV | Billy | billy.omahoney@intel.com | DR: C-rel | Active | Hybrid |
+| | | O'Mahoney | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-174 | GLUON | Bin | bh526r@att.com | DR: D-rel | Active | Nested* |
+| | | Hu | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-180 | SAVI | Rick | richard.brunner@ericsson.com | DR: D-rel | Active | Nested* |
+| | | Brunner | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-181 | IPV6-MULTI | Bin | bh526r@att.com | DR: D-rel | Active | Nested* |
+| | | Hu | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-191 | AUTO-DEP | Peter | Peter.Barabas@ericsson.com | DR: C-rel | Active | Nested* |
+| | | Barabas | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-199 | SDN-L3 | Tim | Tim.Irnich@ericsson.com | DR: C-rel | Active | Nested* |
+| | | Irnich | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-236 | LLT-TOOL | Jose | Jose.Lausuch@ericsson.com | DR: C-rel | Active | Nested* |
+| | | Lausuch | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+| PHAROS-253 | ODL-II | Nikolas | Nikolas.Hermanns@ericsson.com | DR: C-rel | Active | Nested* |
+| | | Hermanns | | | | |
++------------+------------+------------+-------------------------------+------------+--------+---------+
+
+
+- `ACTIVE CI/CD LAB SPECS <https://wiki.opnfv.org/pages/viewpage.action?pageId=6829012>`_
+* `CI-ERICSSON-POD1 wiki page <https://wiki.opnfv.org/display/pharos/CI-ERICSSON-POD1>`_
+* `CI-ERICSSON-POD1 wiki page <https://wiki.opnfv.org/display/pharos/CI-ERICSSON-POD2>`_
+- `ACTIVE LAB SPECS <https://wiki.opnfv.org/display/pharos/Active+Lab+Specs>`_
+* `PHAROS-166 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-166%3A+++++++PaaS+PoC>`_
+* `PHAROS-167 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-167%3A+OVS-NFV+BareMetal+Lab>`_
+* `PHAROS-174 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-174%3A+Gluon+PoC+for+OPNFV+Summit>`_
+* `PHAROS-180 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-180%3A+++++++SAVI+CDN+POC>`_
+* `PHAROS-181 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-181%3A+IPV6+Multisite>`_
+* `PHAROS-191 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-191%3A+++++++Colorado+-+Autodeployer+Uplift>`_
+* `PHAROS-199 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-199%3A+++++++ODL-L3+troubleshooting>`_
+* `PHAROS-236 wiki page <https://wiki.opnfv.org/display/pharos/PHAROS-236%3A+Tracing+Tool+-+LLTng>`_
+* `PHAROS-253 wiki page <https://wiki.opnfv.org/pages/viewpage.action?pageId=6828594>`_
+- `Decommissioned Requets <https://wiki.opnfv.org/display/pharos/Decommissioned+Lab+Request>`_
+
+
+Acceptable Usage Policy
+-----------------------
+
+Resources located in Ericsson OPNFV lab shall only be used for CI, infra setup/configuration and
+troubleshooting purposes. No development work is allowed in these PODs. Development Work should
+only be performed on the DR labs assigned to individual projects.
+
+
+Remote Access Infrastructure
+----------------------------
+
+Ericsson OPNFV lab provides a SSH GW that allows for unlimited port-forwarding, as well as Remote
+Desktop, VNC and SOCKS proxy capability allowing the end user to feel as though directly connected
+to the lab.
+
+Remote Access Procedure
+-----------------------
+
+Access to this environment can be granted by sending an e-mail to: **daniel.smith@ericsson.com**.
+
+Subject: ericsson opnfv access.
+
+The following information should be provided in the request:
+
+::
+
+ Full name:
+ E-mail:
+ Organization:
+ Why is access needed:
+ How long is access needed:
+ Number of Hosts required:
+ Topology Required (HA, SA):
+ Feature/Plugins/Options Required (DPDK, ODL, ONOS):
+
+Enclosed a copy of your id_rsa.pub (public key) with your request and a login will be created for you
+
+
+Lab Documentation
+-----------------
+
+
+Lab Topology
+------------
+
+.. image:: ./images/ericsson_opnfv_topology.png
+ :alt: Lab diagram not found
+
+Each POD is an individual entity with its own set of independant networks allowing for
+interconnection between DR labs, intra connectinos within multiple Nested DRs all without touching
+the CI/CD running in production.
+
+Refer to each Lab specific wiki page for IP and Login and Topology Information.
+
diff --git a/docs/labs/images/orange_pod2.png b/docs/labs/images/orange_pod2.png
new file mode 100644
index 00000000..05693281
--- /dev/null
+++ b/docs/labs/images/orange_pod2.png
Binary files differ
diff --git a/docs/labs/index.rst b/docs/labs/index.rst
new file mode 100755
index 00000000..db07640b
--- /dev/null
+++ b/docs/labs/index.rst
@@ -0,0 +1,20 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) 2016 OPNFV.
+
+
+=====================
+PHAROS Community Labs
+=====================
+
+
+.. toctree::
+ :maxdepth: 2
+
+ ./Dell.rst
+ ./ericsson/index.rst
+ ./huawei-us-lab/huawei-us-lab.rst
+ ./ool/index.rst
+ ./orange-paris-lab/index.rst
+ ./spirent.rst
+ ./zte-sh-lab/index.rst
diff --git a/docs/labs/ool/ool.rst b/docs/labs/ool/index.rst
index 6be3f1be..6be3f1be 100644
--- a/docs/labs/ool/ool.rst
+++ b/docs/labs/ool/index.rst
diff --git a/docs/labs/orange-lannion-lab/index.rst b/docs/labs/orange-lannion-lab/index.rst
new file mode 100644
index 00000000..48235759
--- /dev/null
+++ b/docs/labs/orange-lannion-lab/index.rst
@@ -0,0 +1,215 @@
+Orange OPNFV Testlab
+==================================================
+
+Overview
+------------------
+
+Orange Labs is hosting an OPNFV testlab at its Lannion facility. The testlab would host baremetal
+servers for the use of OPNFV community as part of the OPNFV Pharos Project
+
+
+The Orange Testlab consists of PODs
+ * POD2 for Joid
+
+POD2 consists of 8 servers
+ * 1 Jump Server
+ * 4 Servers for Control Nodes
+ * 3 Servers for Compute Nodes
+
+
+
+Hardware details
+-----------------
+
+All the servers within the two PODs reside within a two chassis and have the
+following specifications:
+
+POD2-Joid
+^^^^^^^^^^^^
+
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Hostname| Model |Memory |Storage | Processor 1 |Cores|Threads| Processor 2 |Cores|Threads|
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node1 |ProLiant DL380 Gen9 |128 GB |2xIntel SSD S3500 480GB+1 SAS 300GB|Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz | 18 | 36 | Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz | 18 | 36 |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node2 |ProLiant DL380 Gen9 |128 GB |2xIntel SSD S3500 480GB+ SAS 300GB|Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz | 18 | 36 | Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz | 18 | 36 |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node3 |ProLiant DL380 Gen9 |128 GB |2xIntel SSD S3500 480GB+1 SAS 300GB|Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz | 18 | 36 | Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz | 18 | 36 |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node4 |ProLiant DL380 Gen9 |128 GB |2xIntel SSD S3500 480GB+1 SAS 300GB|Intel(R) Xeon(R) CPU E5-2609 v3 @ 1.90GHz | 6 | 6 | Intel(R) Xeon(R) CPU E5-2609 v3 @ 1.90GHz | 6 | 6 |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node5 |ProLiant DL360 Gen9 |32 GB |2xSAS 300GB |Intel(R) Xeon(R) CPU E5-2683 v3 @ 2.00GHz | 14 | 28 | N/A | | |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node6 |ProLiant DL360 Gen9 |32 GB |2xSAS 300GB |Intel(R) Xeon(R) CPU E5-2683 v3 @ 2.00GHz | 14 | 28 | N/A | | |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+| Node7 |ProLiant DL360 Gen9 |32 GB |2xSAS 300GB |Intel(R) Xeon(R) CPU E5-2683 v3 @ 2.00GHz | 14 | 28 | N/A | | |
++---------+--------------------+-------+-----------------------------------+------------------------------------------+-----+-------+-------------------------------------------+-----+-------+
+
+Software
+---------
+
+The Jump servers in the Testlab are pre-provisioned with the following softwares:
+
+ * Joid-Jump Server:
+
+ 1. OS: Ubuntu 14.04
+
+
+
+Networks
+----------
+
+POD2-Joid
+^^^^^^^^^^^^
+
+.. image:: images/Orange_POD2.jpg
+ :height: 721
+ :width: 785
+ :alt: POD2-Joid Overview
+ :align: left
+
+
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Hostname | NIC Model | Ports | MAC | BW | Roles |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node1 | 1, Broadcom NetXtreme BCM5719 | eth0 | 38:63:bb:3f:bc:c8 | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 38:63:bb:3f:bc:c9 | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 2, Broadcom NetXtreme BCM5719 | eth2 | 38:63:bb:3f:bc:ca | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 38:63:bb:3f:bc:cb | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 3, Intel X540-AT2 DPDK | eth4 | a0:36:9f:4e:88:5c | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth5 | a0:36:9f:4e:88:5e | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node2 | 1, Broadcom NetXtreme BCM5719 | eth0 | 38:63:bb:44:34:84 | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 38:63:bb:44:34:85 | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 2, Broadcom NetXtreme BCM5719 | eth2 | 38:63:bb:44:34:86 | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 38:63:bb:44:34:87 | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 3, Intel X540-AT2 DPDK | eth4 | a0:36:9f:4e:8b:0c | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth5 | a0:36:9f:4e:8b:0e | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node3 | 1, Broadcom NetXtreme BCM5719 | eth0 | 38:63:bb:3f:1d:8c | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 38:63:bb:3f:1d:8d | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 1, Broadcom NetXtreme BCM5719 | eth2 | 38:63:bb:3f:1d:8e | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 38:63:bb:3f:1d:8f | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 3, Intel X540-AT2 DPDK | eth4 | a0:36:9f:4e:88:38 | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth5 | a0:36:9f:4e:88:3a | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node4 | 1, Broadcom NetXtreme BCM5719 | eth0 | 38:63:bb:3f:2d:a8 | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 38:63:bb:3f:2d:a9 | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 1, Broadcom NetXtreme BCM5719 | eth2 | 38:63:bb:3f:2d:aa | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 38:63:bb:3f:2d:ab | 10G | N/A |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 3, Intel X540-AT2 DPDK | eth4 | a0:36:9f:4e:8b:18 | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth5 | a0:36:9f:4e:8b:1a | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node5 | 1, Broadcom NetXtreme BCM5719 | eth0 | 94:57:a5:52:c9:48 | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 94:57:a5:52:c9:49 | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 1, Broadcom NetXtreme BCM5719 | eth2 | 94:57:a5:52:c9:4a | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 94:57:a5:52:c9:4b | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node6 | 1, Broadcom NetXtreme BCM5719 | eth0 | 94:57:a5:52:63:b0 | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 94:57:a5:52:63:b1 | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 1, Broadcom NetXtreme BCM5719 | eth2 | 94:57:a5:52:63:b2 | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 94:57:a5:52:63:b3 | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| Node7 | 1, Broadcom NetXtreme BCM5719 | eth0 | 94:57:a5:52:f1:80 | 10G | Admin |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth1 | 94:57:a5:52:f1:81 | 10G | Public|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | 1, Broadcom NetXtreme BCM5719 | eth2 | 94:57:a5:52:f1:82 | 10G | Storage|
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+| | | eth3 | 94:57:a5:52:f1:83 | 10G | VM |
++--------------+--------------------------------------+-------+-------------------+-----+--------+
+
+
+
+
+
+
+Subnet allocations Pod2
+^^^^^^^^^^^^^^^^^^^^^^^^
+
++-----------+--------------+------------------+--------------+----------+
+| Network | Address | Mask | Gateway | VLAN id |
++-----------+--------------+------------------+--------------+----------+
+|Admin | 192.168.2.0 | 255.255.255.0 | 192.168.2.1 | 200 |
++-----------+--------------+------------------+--------------+----------+
+|Public | 161.105.231.0| 255.255.255.192 | 161.105.231.1| 135 |
++-----------+--------------+------------------+--------------+----------+
+|Storage | 192.168.12.0 | 255.255.255.0 | 192.168.2.1 | 210 |
++-----------+--------------+------------------+--------------+----------+
+|VM | 192.168.22.0 | 255.255.255.0 | 192.168.22.1 | 230 |
++-----------+--------------+------------------+--------------+----------+
+
+
+ILO Pod2
+^^^^^^^^
+
+**POD2**
+
++-----------+--------------------+-------------------+-------------+-------------+
+| Hostname | Lights-out address | MAC | Username | Password |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node1 | 192.168.2.11 | 38:63:bb:39:b2:2e |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node2 | 192.168.2.12 | 14:58:d0:48:7b:7a |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node3 | 192.168.2.13 | 38:63:bb:39:b2:86 |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node4 | 192.168.2.14 | 38:63:bb:39:b2:40 |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node5 | 192.168.2.15 | 94:57:a5:62:73:c2 |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node6 | 192.168.2.16 | 94:57:a5:62:72:90 |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+| Node7 | 192.168.2.17 | 94:57:a5:62:f4:c6 |Administrator| pod2Admin |
++-----------+--------------------+-------------------+-------------+-------------+
+
+
+Remote access infrastructure
+-----------------------------
+
+The Orange OPNFV testlab is free to use for the OPNFV community.
+
+To access the Testlab, please contact bertrand.lelamer AT orange.com with
+the following details:
+ * Name
+ * Email
+ * Designation
+ * Organization
+ * Purpose of using the lab
+ * SSH public key
+
+
+
+*Accessing the Orange Lannion Testlab*
+--------------------------------------
+
+POD2 JumpServer
+^^^^^^^^^^^^^^^
+
+
diff --git a/docs/labs/orange-paris-lab/orange_paris.rst b/docs/labs/orange-paris-lab/index.rst
index 113d5815..113d5815 100644
--- a/docs/labs/orange-paris-lab/orange_paris.rst
+++ b/docs/labs/orange-paris-lab/index.rst
diff --git a/docs/labs/zte-nj-lab/images/zte_nj_lab_topology.png b/docs/labs/zte-nj-lab/images/zte_nj_lab_topology.png
deleted file mode 100644
index 5885fc6e..00000000
--- a/docs/labs/zte-nj-lab/images/zte_nj_lab_topology.png
+++ /dev/null
Binary files differ
diff --git a/docs/labs/zte-nj-lab/lab_description.rst b/docs/labs/zte-nj-lab/lab_description.rst
deleted file mode 100644
index 0bb74933..00000000
--- a/docs/labs/zte-nj-lab/lab_description.rst
+++ /dev/null
@@ -1,86 +0,0 @@
-.. This work is licensed under a Creative Commons Attribution 4.0 International License.
-.. http://creativecommons.org/licenses/by/4.0
-.. (c) 2016 OPNFV.
-
-.. _pharos_lab:
-
-************************
-ZTE NJ Lab Specification
-************************
-
-
-Introduction
-------------
-
-ZTE NJ Pharos lab currently has two PODs available in Nanjing. Each POD has 5 servers, 3 controller
-nodes and 2 computer nodes. They are dedicatedly used for CI. These PODs focus scenarios related
-with **test** projects and **installer** projects.
-
-There are also several other developing PODs, which are not listed here.
-
-
-Lab Resources
--------------
-
-+----------+------------+-----------+-------------------------+------------+--------+-----------+
-| POD Name | Project(s) | PTL(s) | Email(s) | POD Role | Status | Notes |
-+----------+------------+-----------+-------------------------+------------+--------+-----------+
-| POD1 | FUEL | Gregory | gelkinbard@mirantis.com | CI: latest | Active | Yardstick |
-| | | Elkinbard | | | | Funtest |
-| | | | | | | Doctor |
-| | | | | | | Parser |
-+----------+------------+-----------+-------------------------+------------+--------+-----------+
-| POD2 | FUEL | Gregory | gelkinbard@mirantis.com | CI: latest | Active | Qtip |
-| | | Elkinbard | | | | |
-+----------+------------+-----------+-------------------------+------------+--------+-----------+
-
-* `POD1/POD2 wiki <https://wiki.opnfv.org/display/pharos/ZTE+NJ+Testlab>`_
-* `POD1 jenkins slave <https://build.opnfv.org/ci/computer/zte-pod1/>`_
-* `POD2 jenkins slave <https://build.opnfv.org/ci/computer/zte-pod2/>`_
-
-
-Acceptable Usage Policy
------------------------
-
-Resources located in OPNFV ZTE NJ Lab shall only be used for CI, infra setup/configuration and
-troubleshooting purposes. No development work is allowed in these PODs.
-
-
-Remote Access Infrastructure
-----------------------------
-
-ZTE lab provide the OpenVPN access for you.
-
-
-Remote Access Procedure
------------------------
-
-Access to this environment can be granted by sending an e-mail to:"wu.zhihui1@zte.com.cn"
-
-Subject: opnfv zte-pod[1-2] access.
-
-The following information should be provided in the request:
-
-::
-
- Full name:
- E-mail:
- Organization:
- Why is access needed:
- How long is access needed:
- What sepcific Host will be accessed:
- What support is needed from zte admin:
-
-Once access requirment is approved, the instructions for setting up VPN access will be send to you
-by mail.
-
-
-Lab Documentation
------------------
-
-
-Lab Topology
-------------
-
-.. image:: ./images/zte_nj_lab_topoloy.png
- :alt: POD diagram not found
diff --git a/docs/labs/zte-nj-lab/pod1_description.rst b/docs/labs/zte-nj-lab/pod1_description.rst
deleted file mode 100644
index 05acb567..00000000
--- a/docs/labs/zte-nj-lab/pod1_description.rst
+++ /dev/null
@@ -1,127 +0,0 @@
-.. This work is licensed under a Creative Commons Attribution 4.0 International License.
-.. http://creativecommons.org/licenses/by/4.0
-.. (c) 2016 OPNFV.
-
-.. _pharos_pod:
-
-**********************
-ZTE POD1 Specification
-**********************
-
-
-Introduction
-------------
-
-POD1(means ZTE-POD1) uses Fuel as the installer and performs os-odl_l2-nofeature-ha CI latest
-verification. Currently, test projects such as Yardstick, Functest are performing daily CI tasks.
-Fueature projects such as Doctor, Parser will perform daily and verify CI tasks.
-
-
-Additional Requirements
------------------------
-
-
-Server Specifications
----------------------
-
-
-**Jump Host**
-
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
-| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-| Frog | ZTE | R4300 | 277662500093 | E5-2620x2 | 32 | 600GB HDD | IF0: 10.20.0.1 | | |
-| | | | | | | | 98:F5:37:E1:B4:1C | | |
-| | | | | | | | vlan 1 | | |
-| | | | | | | | PXE | | |
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-
-
-**Nodes/Servers**
-
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| | | | | | Memory | Local | Lights-out network | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
-| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | (IPMI): IP/MAC, U/P | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node1 | ZTE | E9000 | 281498500141 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.101 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 4c:09:b4:b2:59:84 | 4c:09:b4:b2:59:87 | 4c:09:b4:b1:de:38 | |
-| | | | | | | | zteroot/superuser | vlan 1/PXE | vlan 1/ public | |
-| | | | | | | | | vlan 101/management | vlan 103/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 4c:09:b4:b1:de:3a | |
-| | | | | | | | | | vlan 102/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node2 | ZTE | E9000 | 281498500179 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.22 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 4c:09:b4:b2:59:f9 | 4c:09:b4:b2:59:fc | 4c:09:b4:b1:de:40 | |
-| | | | | | | | zteroot/superuser | vlan 1/PXE | vlan 1/ public | |
-| | | | | | | | | vlan 101/management | vlan 103/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 4c:09:b4:b1:de:42 | |
-| | | | | | | | | | vlan 102/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node3 | ZTE | E9000 | 281498500008 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.3 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 4c:09:b4:b2:59:9f | 4c:09:b4:b2:59:a2 | 4c:09:b4:b1:de:1c | |
-| | | | | | | | zteroot/superuser | vlan 1/PXE | vlan 1/ public | |
-| | | | | | | | | vlan 101/management | vlan 103/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 4c:09:b4:b1:de:1e | |
-| | | | | | | | | | vlan 102/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node4 | ZTE | E9000 | 281498500157 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.4 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 4c:09:b4:b2:59:d5 | 4c:09:b4:b2:59:d8 | 4c:09:b4:b1:de:18 | |
-| | | | | | | | zteroot/superuser | vlan 1/PXE | vlan 1/ public | |
-| | | | | | | | | vlan 101/management | vlan 103/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 4c:09:b4:b1:de:1a | |
-| | | | | | | | | | vlan 102/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node5 | ZTE | E9000 | 281498500119 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.5 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 4c:09:b4:b2:59:72 | 4c:09:b4:b2:59:75 | 4c:09:b4:b1:de:48 | |
-| | | | | | | | zteroot/superuser | vlan 1/PXE | vlan 1/ public | |
-| | | | | | | | | vlan 101/management | vlan 103/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 4c:09:b4:b1:de:4a | |
-| | | | | | | | | | vlan 102/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-
-**Subnet allocations**
-
-+----------------+--------------+----------------+------------+---------------+
-| Network name | Address | Mask | Gateway | VLAN id |
-+----------------+--------------+----------------+------------+---------------+
-| Public | 172.10.0.0 | 255.255.255.0 | 172.10.0.1 | untagged |
-+----------------+--------------+----------------+------------+---------------+
-| Fuel Admin/PXE | 10.20.0.0 | 255.255.255.0 | 10.20.0.2 | native vlan 1 |
-+----------------+--------------+----------------+------------+---------------+
-| Fuel Mangement | 192.168.10.0 | 255.255.255.0 | | 101 |
-+----------------+--------------+----------------+------------+---------------+
-| Fuel Storage | 192.168.11.0 | 255.255.255.0 | | 102 |
-+----------------+--------------+----------------+------------+---------------+
-
-
-VPN Users
----------
-
-+--------------+--------------+--------------+--------------+--------------+
-| Name | Email | Project | Role | Notes |
-+--------------+--------------+--------------+--------------+--------------+
-| | | | | |
-+--------------+--------------+--------------+--------------+--------------+
-
-
-Firewall Rules
---------------
-
-+---------------+---------+------+
-| Port(s) | Service | Note |
-+---------------+---------+------+
-| 1194(OpenVPN) | Jenkins | |
-+---------------+---------+------+
-
-
-POD Topology
-------------
-
-.. image:: ./images/zte_nj_pod1_topology.png
- :alt: POD diagram not found
diff --git a/docs/labs/zte-nj-lab/pod2_description.rst b/docs/labs/zte-nj-lab/pod2_description.rst
deleted file mode 100644
index a03ecd1a..00000000
--- a/docs/labs/zte-nj-lab/pod2_description.rst
+++ /dev/null
@@ -1,130 +0,0 @@
-.. This work is licensed under a Creative Commons Attribution 4.0 International License.
-.. http://creativecommons.org/licenses/by/4.0
-.. (c) 2016 OPNFV.
-
-.. _pharos_pod:
-
-**********************
-ZTE POD2 Specification
-**********************
-
-
-Introduction
-------------
-
-POD2(means ZTE-POD2) uses Fuel as the installer and performs os-odl_l2-nofeature-ha CI latest
-verification. Qtip daily CI task will be migrated from POD1 to POD2. Qtip is also working on
-integration with Yardstick umbrella project.
-
-
-Additional Requirements
------------------------
-
-
-Server Specifications
----------------------
-
-
-**Jump Host**
-
-POD2 share the same **Jump Host** with POD1:
-
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
-| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-| Frog | ZTE | R4300 | 277662500093 | E5-2620x2 | 32 | 600GB HDD | IF0: 10.20.0.1 | | |
-| | | | | | | | 98:F5:37:E1:B4:1C | | |
-| | | | | | | | VLAN 1 | | |
-| | | | | | | | PXE | | |
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-
-
-
-**Compute Nodes**
-
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| | | | | | Memory | Local | Lights-out network | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
-| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | (IPMI): IP/MAC, U/P | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node1 | ZTE | E9000 | 281498500141 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.9 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 3c:da:2a:e8:01:ea | 3c:da:2a:e8:01:ed | 3c:da:2a:e9:02:dc | |
-| | | | | | | | zteroot/superuser | vlan 500/PXE | vlan 500/ public | |
-| | | | | | | | | vlan 501/management | vlan 503/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 3c:da:2a:e9:02:de | |
-| | | | | | | | | | vlan 502/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node2 | ZTE | E9000 | 281498500179 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.10 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 3c:da:2a:e8:02:49 | 3c:da:2a:e8:02:4c | 3c:da:2a:e9:02:d0 | |
-| | | | | | | | zteroot/superuser | vlan 500/PXE | vlan 500/ public | |
-| | | | | | | | | vlan 501/management | vlan 503/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 3c:da:2a:e9:02:d2 | |
-| | | | | | | | | | vlan 502/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node3 | ZTE | E9000 | 281498500008 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.11 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 3c:da:2a:e8:01:a4 | 3c:da:2a:e8:01:a7 | 3c:da:2a:e9:02:ec | |
-| | | | | | | | zteroot/superuser | vlan 500/PXE | vlan 500/ public | |
-| | | | | | | | | vlan 501/management | vlan 503/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 3c:da:2a:e9:02:ee | |
-| | | | | | | | | | vlan 502/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node4 | ZTE | E9000 | 281498500157 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.12 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 3c:da:2a:e8:01:c7 | 3c:da:2a:e8:01:ca | 3c:da:2a:e9:02:d4 | |
-| | | | | | | | zteroot/superuser | vlan 500/PXE | vlan 500/ public | |
-| | | | | | | | | vlan 501/management | vlan 503/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 3c:da:2a:e9:02:d6 | |
-| | | | | | | | | | vlan 502/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-| node5 | ZTE | E9000 | 281498500119 | E5-2680x2 | 128 | 600GB HDD | 129.5.1.13 | enp129s0f0: | enp2s0f0: | |
-| | | | | | | | 3c:da:2a:e8:01:b3 | 3c:da:2a:e8:01:b6 | 3c:da:2a:e9:02:ac | |
-| | | | | | | | zteroot/superuser | vlan 500/PXE | vlan 500/ public | |
-| | | | | | | | | vlan 501/management | vlan 503/ private | |
-| | | | | | | | | | enp132s0f0: | |
-| | | | | | | | | | 3c:da:2a:e9:02:ae | |
-| | | | | | | | | | vlan 502/ storage | |
-+----------+--------+-------+---------------+-----------+--------+-----------+---------------------+---------------------+-------------------+-------+
-
-**Subnet allocations**
-
-+----------------+--------------+----------------+------------+-----------------+
-| Network name | Address | Mask | Gateway | VLAN id |
-+----------------+--------------+----------------+------------+-----------------+
-| Public | 172.30.0.0 | 255.255.255.0 | 172.30.0.1 | Untagged |
-+----------------+--------------+----------------+------------+-----------------+
-| Fuel Admin | 10.20.1.0 | 255.255.255.0 | 10.20.1.1 | native vlan 500 |
-+----------------+--------------+----------------+------------+-----------------+
-| Fuel Mangement | 192.168.30.0 | 255.255.255.0 | | 501 |
-+----------------+--------------+----------------+------------+-----------------+
-| Fuel Storage | 192.168.31.0 | 255.255.255.0 | | 502 |
-+----------------+--------------+----------------+------------+-----------------+
-
-
-VPN Users
----------
-
-+--------------+--------------+--------------+--------------+--------------+
-| Name | Email | Project | Role | Notes |
-+--------------+--------------+--------------+--------------+--------------+
-| | | | | |
-+--------------+--------------+--------------+--------------+--------------+
-
-
-Firewall Rules
---------------
-
-+---------------+---------+------+
-| Port(s) | Service | Note |
-+---------------+---------+------+
-| 1194(OpenVPN) | Jenkins | |
-+---------------+---------+------+
-
-
-POD Topology
-------------
-
-.. image:: ./zte_nj_pod2_topology.png
- :alt: POD diagram not found
diff --git a/docs/labs/zte-sh-lab/images/zte_sh_lab_topology.png b/docs/labs/zte-sh-lab/images/zte_sh_lab_topology.png
index 5c2576df..6c147dbc 100644
--- a/docs/labs/zte-sh-lab/images/zte_sh_lab_topology.png
+++ b/docs/labs/zte-sh-lab/images/zte_sh_lab_topology.png
Binary files differ
diff --git a/docs/labs/zte-sh-lab/images/zte_sh_pod3_topology.png b/docs/labs/zte-sh-lab/images/zte_sh_pod_topology.png
index 592b6077..592b6077 100644
--- a/docs/labs/zte-sh-lab/images/zte_sh_pod3_topology.png
+++ b/docs/labs/zte-sh-lab/images/zte_sh_pod_topology.png
Binary files differ
diff --git a/docs/labs/zte-sh-lab/zte-sh.rst b/docs/labs/zte-sh-lab/index.rst
index e48c21d4..f8ee36ed 100644
--- a/docs/labs/zte-sh-lab/zte-sh.rst
+++ b/docs/labs/zte-sh-lab/index.rst
@@ -10,6 +10,9 @@ ZTE SH Pharos Lab Configuration Files
.. toctree::
+ :maxdepth: 2
./lab_description.rst
+ ./pod1_description.rst
+ ./pod2_description.rst
./pod3_description.rst
diff --git a/docs/labs/zte-sh-lab/lab_description.rst b/docs/labs/zte-sh-lab/lab_description.rst
index bb125f1e..47caed45 100644
--- a/docs/labs/zte-sh-lab/lab_description.rst
+++ b/docs/labs/zte-sh-lab/lab_description.rst
@@ -12,9 +12,10 @@ ZTE SH Lab Specification
Introduction
------------
-ZTE SH Pharos lab currently has one POD available in Shanghai. The POD has 5 servers, 3 controller
-nodes and 2 computer nodes. It is dedicatedly used for baremetal CI. The POD is supposed to support
-scenarios related with performance enhancement projects, such as KVM, OVS, FDS, etc.
+ZTE SH Pharos lab currently has three PODs available in Shanghai. Each POD has 5 servers, 3
+controller nodes and 2 computer nodes. These PODs are dedicated for use by Production/CI. These PODs
+focus scenarios related with **test** projects, **installer** projects and performance enhancement
+projects, such as KVM, OVS, FDS, etc.
Scenarios planned are list here:
@@ -28,14 +29,24 @@ Scenarios are defined in
Lab Resources
-------------
-+----------+------------+-----------+-------------------------+------------+--------+---------+
-| POD Name | Project(s) | PTL(s) | Email(s) | POD Role | Status | Notes |
-+----------+------------+-----------+-------------------------+------------+--------+---------+
-| POD3 | FUEL | Gregory | gelkinbard@mirantis.com | CI: latest | Active | NFV-KVM |
-| | | Elkinbard | | | | OVSNFV |
-+----------+------------+-----------+-------------------------+------------+--------+---------+
-
-- `POD3 wiki page <https://wiki.opnfv.org/display/pharos/ZTE+SH+Testlab>`_
++----------+------------+-----------+-------------------------+------------+--------+-----------+
+| POD Name | Project(s) | PTL(s) | Email(s) | POD Role | Status | Notes |
++----------+------------+-----------+-------------------------+------------+--------+-----------+
+| POD1 | FUEL | Gregory | gelkinbard@mirantis.com | CI: latest | Active | Yardstick |
+| | | Elkinbard | | | | Funtest |
+| | | | | | | Doctor |
+| | | | | | | Parser |
++----------+------------+-----------+-------------------------+------------+--------+-----------+
+| POD2 | FUEL | Gregory | gelkinbard@mirantis.com | CI: latest | Active | Qtip |
+| | | Elkinbard | | | | |
++----------+------------+-----------+-------------------------+------------+--------+-----------+
+| POD3 | FUEL | Gregory | gelkinbard@mirantis.com | CI: latest | Active | NFV-KVM |
+| | | Elkinbard | | | | OVSNFV |
++----------+------------+-----------+-------------------------+------------+--------+-----------+
+
+- `POD1-3 wiki page <https://wiki.opnfv.org/display/pharos/ZTE+SH+Testlab>`_
+* `POD1 jenkins slave <https://build.opnfv.org/ci/computer/zte-pod1/>`_
+* `POD2 jenkins slave <https://build.opnfv.org/ci/computer/zte-pod2/>`_
- `POD3 jenkins slave <https://build.opnfv.org/ci/computer/zte-pod3/>`_
@@ -43,7 +54,7 @@ Acceptable Usage Policy
-----------------------
Resources located in OPNFV ZTE SH lab shall only be used for CI, infra setup/configuration and
-troubleshooting purposes. No development work is allowed in this Lab.
+troubleshooting purposes. No development work is allowed in these PODs.
Remote Access Infrastructure
@@ -57,7 +68,7 @@ Remote Access Procedure
Access to this environment can be granted by sending an e-mail to: **yangyang1@zte.com.cn**.
-Subject: opnfv zte-pod3 access.
+Subject: opnfv zte-pod[1-3] access.
The following information should be provided in the request:
@@ -68,7 +79,7 @@ The following information should be provided in the request:
Organization:
Why is access needed:
How long is access needed:
- What sepcific Host will be accessed:
+ What specific Host will be accessed:
What support is needed from zte admin:
Once access requirment is approved, the instructions for setting up VPN access will be send to you by mail.
@@ -83,3 +94,34 @@ Lab Topology
.. image:: ./images/zte_sh_lab_topology.png
:alt: Lab diagram not found
+
+All the PODs share the same **Jump Host** for only one public IP address is allocated for the
+pharos lab. Deploy servers are separated from Jump Host. Every 2 PODs share one **Deploy Server**.
+
+**Jump Host**
+
++----------+--------+-------+---------------+---------+--------+-----------+--------------------+------------------+-------+
+| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
+| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
++----------+--------+-------+---------------+---------+--------+-----------+--------------------+------------------+-------+
+| Rabbit | HP | 5500 | - | X5647x2 | 24 | 250GB SAS | IF0: | | |
+| | | | | | | 2 TB HDD | a0:36:9f:00:11:34/ | | |
+| | | | | | | | 192.168.1.1/ | | |
+| | | | | | | | native vlan/OA | | |
+| | | | | | | | IF1: | | |
+| | | | | | | | a0:36:9f:00:11:35/ | | |
+| | | | | | | | 172.10.0.1/ | | |
+| | | | | | | | vlan 103/Public | | |
+| | | | | | | | 172.20.0.1/ | | |
+| | | | | | | | vlan 113/Public | | |
+| | | | | | | | 172.60.0.1/ | | |
+| | | | | | | | vlan 163/Public | | |
+| | | | | | | | 172.70.0.1/ | | |
+| | | | | | | | vlan 173/Public | | |
+| | | | | | | | IF2: | | |
+| | | | | | | | a0.36:9:00:11:37/ | | |
+| | | | | | | | 116.228.53.183/ | | |
+| | | | | | | | native vlan/ | | |
+| | | | | | | | Internet | | |
++----------+--------+-------+---------------+---------+--------+-----------+--------------------+------------------+-------+
+
diff --git a/docs/labs/zte-sh-lab/pod1_description.rst b/docs/labs/zte-sh-lab/pod1_description.rst
new file mode 100644
index 00000000..7ec53a82
--- /dev/null
+++ b/docs/labs/zte-sh-lab/pod1_description.rst
@@ -0,0 +1,160 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) 2016 OPNFV.
+
+.. _pharos_pod:
+
+**********************
+ZTE POD1 Specification
+**********************
+
+
+Introduction
+------------
+
+POD1(means ZTE-POD1) uses Fuel as the installer and performs os-odl_l2-nofeature-ha CI latest
+verification. Currently, test projects such as Yardstick, Functest are performing daily CI tasks.
+Fueature projects such as Doctor, Parser will perform daily and verify CI tasks.
+
+
+Additional Requirements
+-----------------------
+
+
+Server Specifications
+---------------------
+
+
+**Jump Host**
+
+POD1 share the same **Jump Host** in the lab.
+
+**Deploy server**
+
++-----------+--------+-------+---------------+-----------+--------+-----------+--------------------+------------------+-------+
+| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
+| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
++-----------+--------+-------+---------------+-----------+--------+-----------+--------------------+------------------+-------+
+| Jellyfish | ZTE | R5300 | 277662500093 | E5-2620x2 | 128 | 600GB SAS | IF0: | | |
+| | | | | | | 4 TB HDD | 74:4a:a4:00:91:b3/ | | |
+| | | | | | | | 10.20.6.1/ | | |
+| | | | | | | | native vlan/PXE | | |
+| | | | | | | | IF1: | | |
+| | | | | | | | 74:4a:a4:00:91:b4/ | | |
+| | | | | | | | 10.20.7.1/ | | |
+| | | | | | | | native vlan/PXE | | |
++-----------+--------+-------+---------------+-----------+--------+-----------+--------------------+------------------+-------+
+
+
+**Nodes/Servers**
+
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| | | | | | Memory | Local | Lights-out network | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
+| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | (IPMI): IP/MAC, U/P | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node1 | ZTE | E9000 | 701763100025 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.101 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:cf:d9 | 74:4a:a4:00:cf:dc | 74:4a:a4:00:b0:e1 | |
+| | | | | | | | zteroot/superuser | native vlan 160/PXE | vlan 161/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:b0:e2 | |
+| | | | | | | | | | vlan 162/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:b0:dd | |
+| | | | | | | | | | vlan 1120/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:b0:de | |
+| | | | | | | | | | vlan 163/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node2 | ZTE | E9000 | 701763100224 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.102 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:ce:cb | 74:4a:a4:00:ce:ce | 74:4a:a4:00:d6:ad | |
+| | | | | | | | zteroot/superuser | native vlan 160/PXE | vlan 161/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:ae | |
+| | | | | | | | | | vlan 162/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d6:a9 | |
+| | | | | | | | | | vlan 1120/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:aa | |
+| | | | | | | | | | vlan 163/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node3 | ZTE | E9000 | 701763100064 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.103 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:cf:55 | 74:4a:a4:00:cf:58 | 74:4a:a4:00:d6:ab | |
+| | | | | | | | zteroot/superuser | native vlan 160/PXE | vlan 161/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:ac | |
+| | | | | | | | | | vlan 162/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d6:af | |
+| | | | | | | | | | vlan 1120/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:b0 | |
+| | | | | | | | | | vlan 163/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node4 | ZTE | E9000 | 289842100103 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.104 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:49:81 | 74:4a:a4:00:49:84 | 74:4a:a4:00:b1:a5 | |
+| | | | | | | | zteroot/superuser | native vlan 160/PXE | vlan 161/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:b1:a6 | |
+| | | | | | | | | | vlan 162/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:b1:b1 | |
+| | | | | | | | | | vlan 1120/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:b1:b2 | |
+| | | | | | | | | | vlan 163/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node5 | ZTE | E9000 | 701763100220 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.105 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:ce:bf | 74:4a:a4:00:ce:c2 | 74:4a:a4:00:d6:8d | |
+| | | | | | | | zteroot/superuser | native vlan 160/PXE | vlan 161/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:8e | |
+| | | | | | | | | | vlan 162/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d6:9b | |
+| | | | | | | | | | vlan 1120/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:9c | |
+| | | | | | | | | | vlan 163/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+
+**Subnet allocations**
+
++----------------+--------------+----------------+------------+-----------------+
+| Network name | Address | Mask | Gateway | VLAN id |
++----------------+--------------+----------------+------------+-----------------+
+| Public | 172.60.0.0 | 255.255.255.0 | 172.60.0.1 | 163 |
++----------------+--------------+----------------+------------+-----------------+
+| Fuel Admin/PXE | 10.20.6.0 | 255.255.255.0 | 10.20.6.2 | native vlan 160 |
++----------------+--------------+----------------+------------+-----------------+
+| Fuel Mangement | 192.168.61.0 | 255.255.255.0 | | 161 |
++----------------+--------------+----------------+------------+-----------------+
+| Fuel Storage | 192.168.62.0 | 255.255.255.0 | | 162 |
++----------------+--------------+----------------+------------+-----------------+
+
+
+VPN Users
+---------
+
++--------------+--------------+--------------+--------------+--------------+
+| Name | Email | Project | Role | Notes |
++--------------+--------------+--------------+--------------+--------------+
+| | | | | |
++--------------+--------------+--------------+--------------+--------------+
+
+
+Firewall Rules
+--------------
+
++---------------+---------+------+
+| Port(s) | Service | Note |
++---------------+---------+------+
+| 1194(OpenVPN) | Jenkins | |
++---------------+---------+------+
+
+
+POD Topology
+------------
+
+.. image:: ./images/zte_sh_pod_topology.png
+ :alt: POD diagram not found
diff --git a/docs/labs/zte-nj-lab/pod1_inventory.yaml b/docs/labs/zte-sh-lab/pod1_inventory.yaml
index 5ceb0dbd..3414bc12 100644
--- a/docs/labs/zte-nj-lab/pod1_inventory.yaml
+++ b/docs/labs/zte-sh-lab/pod1_inventory.yaml
@@ -2,48 +2,45 @@ nodes:
- name: node1
tags: control #optional param, other valid value "compute"
arch: "x86_64"
- mac_address: "4c:09:b4:b2:59:87" #pxe boot interface mac
+ mac_address: "74:4A:A4:00:CF:DC" #pxe boot interface mac
power:
type: ipmi
- address: 129.5.1.101
+ address: 192.168.1.101
user: zteroot
pass: superuser
- name: node2
tags: control
arch: "x86_64"
- mac_address: "4c:09:b4:b2:59:fc"
+ mac_address: "74:4A:A4:00:CE:CE"
power:
type: ipmi
- address: 129.5.1.22
+ address: 192.168.1.102
user: zteroot
pass: superuser
- name: node3
tags: control
arch: "x86_64"
- mac_address: "4c:09:b4:b2:59:a2"
+ mac_address: "74:4A:A4:00:CF:58"
power:
type: ipmi
- address: 129.5.1.3
+ address: 192.168.1.103
user: zteroot
pass: superuser
-
- name: node4
tags: compute
arch: "x86_64"
- mac_address: "4c:09:b4:b2:59:d8"
+ mac_address: "74:4A:A4:00:49:84"
power:
type: ipmi
- address: 129.5.1.4
+ address: 192.168.1.104
user: zteroot
pass: superuser
-
- name: node5
tags: compute
arch: "x86_64"
- mac_address: "4c:09:b4:b2:59:75"
+ mac_address: "74:4A:A4:00:CE:C2"
power:
type: ipmi
- address: 129.5.1.5
+ address: 192.168.1.105
user: zteroot
pass: superuser
-
diff --git a/docs/labs/zte-sh-lab/pod2_description.rst b/docs/labs/zte-sh-lab/pod2_description.rst
new file mode 100644
index 00000000..961aa2a0
--- /dev/null
+++ b/docs/labs/zte-sh-lab/pod2_description.rst
@@ -0,0 +1,162 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) 2016 OPNFV.
+
+.. _pharos_pod:
+
+**********************
+ZTE POD2 Specification
+**********************
+
+
+Introduction
+------------
+
+POD2(means ZTE-POD2) uses Fuel as the installer and performs os-odl_l2-nofeature-ha CI latest
+verification. Qtip daily CI task will be migrated from POD1 to POD2. Qtip is also working on
+integration with Yardstick umbrella project.
+
+
+Additional Requirements
+-----------------------
+
+
+Server Specifications
+---------------------
+
+**Jump Host**
+
+POD2 share the same **Jump Host** in the lab.
+
+**Deploy Server**
+
+POD2 share the same **Deploy Server** with POD1.
+
++-----------+--------+-------+---------------+-----------+--------+-----------+--------------------+------------------+-------+
+| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
+| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
++-----------+--------+-------+---------------+-----------+--------+-----------+--------------------+------------------+-------+
+| Jellyfish | ZTE | R5300 | 277662500093 | E5-2620x2 | 128 | 600GB SAS | IF0: | | |
+| | | | | | | 4 TB HDD | 74:4a:a4:00:91:b3/ | | |
+| | | | | | | | 10.20.6.1/ | | |
+| | | | | | | | native vlan/PXE | | |
+| | | | | | | | IF1: | | |
+| | | | | | | | 74:4a:a4:00:91:b4/ | | |
+| | | | | | | | 10.20.7.1/ | | |
+| | | | | | | | native vlan/PXE | | |
++-----------+--------+-------+---------------+-----------+--------+-----------+--------------------+------------------+-------+
+
+
+**Nodes/Servers**
+
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| | | | | | Memory | Local | Lights-out network | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
+| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | (IPMI): IP/MAC, U/P | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node1 | ZTE | E9000 | 701763100114 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.106 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:cd:6f | 74:4a:a4:00:cd:72 | 74:4a:a4:00:b0:e9 | |
+| | | | | | | | zteroot/superuser | native vlan 170/PXE | vlan 171/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:b0:ea | |
+| | | | | | | | | | vlan 172/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:b0:eb | |
+| | | | | | | | | | vlan 1130/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:b0:ec | |
+| | | | | | | | | | vlan 173/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node2 | ZTE | E9000 | 701360500105 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.107 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:ca:c9 | 74:4a:a4:00:ca:cc | 74:4a:a4:00:d6:a3 | |
+| | | | | | | | zteroot/superuser | native vlan 170/PXE | vlan 171/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:a4 | |
+| | | | | | | | | | vlan 172/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d6:99 | |
+| | | | | | | | | | vlan 1130/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:9a | |
+| | | | | | | | | | vlan 173/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node3 | ZTE | E9000 | 701360500026 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.108 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:cd:0f | 74:4a:a4:00:cd:12 | 74:4a:a4:00:d6:9d | |
+| | | | | | | | zteroot/superuser | native vlan 170/PXE | vlan 171/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:9e | |
+| | | | | | | | | | vlan 172/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d3:15 | |
+| | | | | | | | | | vlan 1130/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d3:16 | |
+| | | | | | | | | | vlan 173/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node4 | ZTE | E9000 | 701763100099 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.109 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:cf:3d | 74:4a:a4:00:cf:40 | 74:4a:a4:00:d6:a5 | |
+| | | | | | | | zteroot/superuser | native vlan 170/PXE | vlan 171/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:a6 | |
+| | | | | | | | | | vlan 172/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d6:a7 | |
+| | | | | | | | | | vlan 1130/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d6:a8 | |
+| | | | | | | | | | vlan 173/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+| node5 | ZTE | E9000 | 701763100018 | E5-2650x2 | 128 | 600GB*2 HDD | 192.168.1.110 | ens4f0: | ens12f0: | |
+| | | | | | | | 74:4a:a4:00:ce:d1 | 74:4a:a4:00:ce:d4 | 74:4a:a4:00:d2:c3 | |
+| | | | | | | | zteroot/superuser | native vlan 170/PXE | vlan 171/ management | |
+| | | | | | | | | | ens12f1: | |
+| | | | | | | | | | 74:4a:a4:00:d2:c4 | |
+| | | | | | | | | | vlan 172/ storage | |
+| | | | | | | | | | ens44f0: | |
+| | | | | | | | | | 74:4a:a4:00:d2:c1 | |
+| | | | | | | | | | vlan 1130/ private | |
+| | | | | | | | | | ens44f1: | |
+| | | | | | | | | | 74:4a:a4:00:d2:c2 | |
+| | | | | | | | | | vlan 173/ public | |
++----------+--------+-------+---------------+-----------+--------+-------------+---------------------+---------------------+----------------------+-------+
+
+
+**Subnet allocations**
+
++----------------+--------------+----------------+------------+-----------------+
+| Network name | Address | Mask | Gateway | VLAN id |
++----------------+--------------+----------------+------------+-----------------+
+| Public | 172.70.0.0 | 255.255.255.0 | 172.70.0.1 | 173 |
++----------------+--------------+----------------+------------+-----------------+
+| Fuel Admin | 10.20.7.0 | 255.255.255.0 | 10.20.7.1 | native vlan 170 |
++----------------+--------------+----------------+------------+-----------------+
+| Fuel Mangement | 192.168.71.0 | 255.255.255.0 | | 171 |
++----------------+--------------+----------------+------------+-----------------+
+| Fuel Storage | 192.168.72.0 | 255.255.255.0 | | 172 |
++----------------+--------------+----------------+------------+-----------------+
+
+
+VPN Users
+---------
+
++--------------+--------------+--------------+--------------+--------------+
+| Name | Email | Project | Role | Notes |
++--------------+--------------+--------------+--------------+--------------+
+| | | | | |
++--------------+--------------+--------------+--------------+--------------+
+
+
+Firewall Rules
+--------------
+
++---------------+---------+------+
+| Port(s) | Service | Note |
++---------------+---------+------+
+| 1194(OpenVPN) | Jenkins | |
++---------------+---------+------+
+
+
+POD Topology
+------------
+
+.. image:: ./images/zte_sh_pod_topology.png
+ :alt: POD diagram not found
diff --git a/docs/labs/zte-nj-lab/pod2_inventory.yaml b/docs/labs/zte-sh-lab/pod2_inventory.yaml
index c060a78f..a49ac190 100644
--- a/docs/labs/zte-nj-lab/pod2_inventory.yaml
+++ b/docs/labs/zte-sh-lab/pod2_inventory.yaml
@@ -2,48 +2,45 @@ nodes:
- name: node1
tags: control #optional param, other valid value "compute"
arch: "x86_64"
- mac_address: "3c:da:2a:e8:01:ed" #pxe boot interface mac
+ mac_address: "74:4A:A4:00:CD:72" #pxe boot interface mac
power:
type: ipmi
- address: 129.5.1.9
+ address: 192.168.1.106
user: zteroot
pass: superuser
- name: node2
tags: control
arch: "x86_64"
- mac_address: "3c:da:2a:e8:02:4c"
+ mac_address: "74:4A:A4:00:CA:CC"
power:
type: ipmi
- address: 129.5.1.10
+ address: 192.168.1.107
user: zteroot
pass: superuser
- name: node3
tags: control
arch: "x86_64"
- mac_address: "3c:da:2a:e8:01:a7"
+ mac_address: "74:4A:A4:00:CD:12"
power:
type: ipmi
- address: 129.5.1.11
+ address: 192.168.1.108
user: zteroot
pass: superuser
-
- name: node4
tags: compute
arch: "x86_64"
- mac_address: "3c:da:2a:e8:01:ca"
+ mac_address: "74:4A:A4:00:CF:40"
power:
type: ipmi
- address: 129.5.1.12
+ address: 192.168.1.109
user: zteroot
pass: superuser
-
- name: node5
tags: compute
arch: "x86_64"
- mac_address: "3c:da:2a:e8:01:b6"
+ mac_address: "74:4A:A4:00:CE:D4"
power:
type: ipmi
- address: 129.5.1.13
+ address: 192.168.1.110
user: zteroot
pass: superuser
-
diff --git a/docs/labs/zte-sh-lab/pod3_description.rst b/docs/labs/zte-sh-lab/pod3_description.rst
index fdc32d16..f0cfae7b 100644
--- a/docs/labs/zte-sh-lab/pod3_description.rst
+++ b/docs/labs/zte-sh-lab/pod3_description.rst
@@ -25,15 +25,23 @@ Server Specifications
**Jump Host**
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
-| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
-| Frog | ZTE | R5300 | 210077307607 | E5-2609x2 | 32 | 600GB SAS | IF0: 10.20.0.1 | | |
-| | | | | | | 2TB HDD | 74:4a:a4:00:21:0b | | |
-| | | | | | | | vlan 100/PXE | | |
-| | | | | | | | | | |
-+----------+--------+-------+---------------+-----------+--------+-----------+-------------------+------------------+-------+
+POD3 share the same **Jump Host** in the lab.
+
+**Deploy Server**
+
++----------+--------+-------+---------------+-----------+--------+------------+--------------------+------------------+-------+
+| | | | | | Memory | Local | 1GbE: NIC#/IP | 10GbE: NIC#/IP | |
+| Hostname | Vendor | Model | Serial Number | CPUs | (GB) | Storage | MAC/VLAN/Network | MAC/VLAN/Network | Notes |
++----------+--------+-------+---------------+-----------+--------+------------+--------------------+------------------+-------+
+| Spider | ZTE | R5300 | 210077307607 | E5-2609x1 | 32 | 600GB SAS | IF0: | | |
+| | | | | | | 1.2TB SCSI | 74:4a:a4:00:21:0b/ | | |
+| | | | | | | | 10.20.0.1/ | | |
+| | | | | | | | native vlan/PXE | | |
+| | | | | | | | IF1: | | |
+| | | | | | | | 74:4a:a4:00:21:0c/ | | |
+| | | | | | | | 10.20.1.1/ | | |
+| | | | | | | | native vlan/PXE | | |
++----------+--------+-------+---------------+-----------+--------+------------+--------------------+------------------+-------+
**Compute Nodes**
@@ -151,5 +159,5 @@ Firewall Rules
POD Topology
------------
-.. image:: ./images/zte_sh_pod3_topology.png
+.. image:: ./images/zte_sh_pod_topology.png
:alt: POD diagram not found
diff --git a/tools/pharos-dashboard/.gitignore b/tools/pharos-dashboard/.gitignore
index b5e9284a..9eb1cfde 100644
--- a/tools/pharos-dashboard/.gitignore
+++ b/tools/pharos-dashboard/.gitignore
@@ -19,6 +19,7 @@ coverage.xml
# Django:
*.log
*.pot
+migrations/
# KDE:
.directory
diff --git a/tools/pharos-dashboard/README.md b/tools/pharos-dashboard/README.md
deleted file mode 100644
index be27bf51..00000000
--- a/tools/pharos-dashboard/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Pharos Dashboard
diff --git a/tools/pharos-dashboard/TODO b/tools/pharos-dashboard/TODO
deleted file mode 100644
index 1c539d6f..00000000
--- a/tools/pharos-dashboard/TODO
+++ /dev/null
@@ -1,3 +0,0 @@
-- 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/account/__init__.py b/tools/pharos-dashboard/account/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/account/__init__.py
diff --git a/tools/pharos-dashboard/account/admin.py b/tools/pharos-dashboard/account/admin.py
new file mode 100644
index 00000000..7fab1238
--- /dev/null
+++ b/tools/pharos-dashboard/account/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from account.models import UserProfile
+
+admin.site.register(UserProfile) \ No newline at end of file
diff --git a/tools/pharos-dashboard/account/apps.py b/tools/pharos-dashboard/account/apps.py
new file mode 100644
index 00000000..999566ca
--- /dev/null
+++ b/tools/pharos-dashboard/account/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+ name = 'account'
diff --git a/tools/pharos-dashboard/account/forms.py b/tools/pharos-dashboard/account/forms.py
new file mode 100644
index 00000000..92c55d85
--- /dev/null
+++ b/tools/pharos-dashboard/account/forms.py
@@ -0,0 +1,12 @@
+import django.forms as forms
+import pytz as pytz
+
+from account.models import UserProfile
+
+
+class AccountSettingsForm(forms.ModelForm):
+ class Meta:
+ model = UserProfile
+ fields = ['company', 'ssh_public_key', 'pgp_public_key', 'timezone']
+
+ timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC')
diff --git a/tools/pharos-dashboard/account/jira_util.py b/tools/pharos-dashboard/account/jira_util.py
new file mode 100644
index 00000000..bd07ff3b
--- /dev/null
+++ b/tools/pharos-dashboard/account/jira_util.py
@@ -0,0 +1,56 @@
+import base64
+import os
+
+import oauth2 as oauth
+from jira import JIRA
+from tlslite.utils import keyfactory
+
+from pharos_dashboard import settings
+
+
+class SignatureMethod_RSA_SHA1(oauth.SignatureMethod):
+ name = 'RSA-SHA1'
+
+ def signing_base(self, request, consumer, token):
+ if not hasattr(request, 'normalized_url') or request.normalized_url is None:
+ raise ValueError("Base URL for request is not set.")
+
+ sig = (
+ oauth.escape(request.method),
+ oauth.escape(request.normalized_url),
+ oauth.escape(request.get_normalized_parameters()),
+ )
+
+ key = '%s&' % oauth.escape(consumer.secret)
+ if token:
+ key += oauth.escape(token.secret)
+ raw = '&'.join(sig)
+ return key, raw
+
+ def sign(self, request, consumer, token):
+ """Builds the base signature string."""
+ key, raw = self.signing_base(request, consumer, token)
+
+ module_dir = os.path.dirname(__file__) # get current directory
+ with open(module_dir + '/rsa.pem', 'r') as f:
+ data = f.read()
+ privateKeyString = data.strip()
+ privatekey = keyfactory.parsePrivateKey(privateKeyString)
+ raw = str.encode(raw)
+ signature = privatekey.hashAndSign(raw)
+ return base64.b64encode(signature)
+
+
+def get_jira(user):
+ module_dir = os.path.dirname(__file__) # get current directory
+ with open(module_dir + '/rsa.pem', 'r') as f:
+ key_cert = f.read()
+
+ oauth_dict = {
+ 'access_token': user.userprofile.oauth_token,
+ 'access_token_secret': user.userprofile.oauth_secret,
+ 'consumer_key': settings.OAUTH_CONSUMER_KEY,
+ 'key_cert': key_cert
+ }
+
+ return JIRA(server=settings.JIRA_URL, oauth=oauth_dict) \ No newline at end of file
diff --git a/tools/pharos-dashboard/account/middleware.py b/tools/pharos-dashboard/account/middleware.py
new file mode 100644
index 00000000..6f7cac7a
--- /dev/null
+++ b/tools/pharos-dashboard/account/middleware.py
@@ -0,0 +1,22 @@
+from django.utils import timezone
+from django.utils.deprecation import MiddlewareMixin
+
+from account.models import UserProfile
+
+
+class TimezoneMiddleware(MiddlewareMixin):
+ """
+ Activate the timezone from request.user.userprofile if user is authenticated,
+ deactivate the timezone otherwise and use default (UTC)
+ """
+ def process_request(self, request):
+ if request.user.is_authenticated:
+ try:
+ tz = request.user.userprofile.timezone
+ timezone.activate(tz)
+ except UserProfile.DoesNotExist:
+ UserProfile.objects.create(user=request.user)
+ tz = request.user.userprofile.timezone
+ timezone.activate(tz)
+ else:
+ timezone.deactivate()
diff --git a/tools/pharos-dashboard/account/migrations/__init__.py b/tools/pharos-dashboard/account/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/account/migrations/__init__.py
diff --git a/tools/pharos-dashboard/account/models.py b/tools/pharos-dashboard/account/models.py
new file mode 100644
index 00000000..fb2c8ddd
--- /dev/null
+++ b/tools/pharos-dashboard/account/models.py
@@ -0,0 +1,20 @@
+from django.db import models
+
+from django.contrib.auth.models import User
+
+from dashboard.models import Resource
+
+def upload_to(object, filename):
+ return object.user.username + '/' + filename
+
+class UserProfile(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ timezone = models.CharField(max_length=100, blank=False, default='UTC')
+ ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
+ pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
+ company = models.CharField(max_length=200, blank=False)
+ oauth_token = models.CharField(max_length=1024, blank=False)
+ oauth_secret = models.CharField(max_length=1024, blank=False)
+
+ class Meta:
+ db_table = 'user_profile'
diff --git a/tools/pharos-dashboard/account/rsa.pem b/tools/pharos-dashboard/account/rsa.pem
new file mode 100644
index 00000000..dbd4eedd
--- /dev/null
+++ b/tools/pharos-dashboard/account/rsa.pem
@@ -0,0 +1,17 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
+A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
+7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
+hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
+X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
+uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
+rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
+zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
+qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
+WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
+cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
+3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
+AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
+Lw03eHTNQghS0A==
+-----END PRIVATE KEY-----
+
diff --git a/tools/pharos-dashboard/account/rsa.pub b/tools/pharos-dashboard/account/rsa.pub
new file mode 100644
index 00000000..cc50e45e
--- /dev/null
+++ b/tools/pharos-dashboard/account/rsa.pub
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0YjCwIfYoprq/FQO6lb3asXrx
+LlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlYzypSRjVxwxrsuRcP3e641SdASwfr
+mzyvIgP08N4S0IFzEURkV1wp/IpH7kH41EtbmUmrXSwfNZsnQRE5SYSOhh+LcK2w
+yQkdgcMv11l4KoBkcwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tools/pharos-dashboard/account/tests/__init__.py b/tools/pharos-dashboard/account/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/account/tests/__init__.py
diff --git a/tools/pharos-dashboard/account/tests/test_general.py b/tools/pharos-dashboard/account/tests/test_general.py
new file mode 100644
index 00000000..ba80b62c
--- /dev/null
+++ b/tools/pharos-dashboard/account/tests/test_general.py
@@ -0,0 +1,40 @@
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+from django.utils import timezone
+
+from account.models import UserProfile
+
+
+class AccountMiddlewareTestCase(TestCase):
+ def setUp(self):
+ self.client = Client()
+ self.user1 = User.objects.create(username='user1')
+ self.user1.set_password('user1')
+ self.user1profile = UserProfile.objects.create(user=self.user1)
+ self.user1.save()
+
+ def test_timezone_middleware(self):
+ """
+ The timezone should be UTC for anonymous users, for authenticated users it should be set
+ to user.userprofile.timezone
+ """
+ #default
+ self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
+
+ url = reverse('account:settings')
+ # anonymous request
+ self.client.get(url)
+ self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
+
+ # authenticated user with UTC timezone (userprofile default)
+ self.client.login(username='user1', password='user1')
+ self.client.get(url)
+ self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
+
+ # authenticated user with custom timezone (userprofile default)
+ self.user1profile.timezone = 'Etc/Greenwich'
+ self.user1profile.save()
+ self.client.get(url)
+ self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich')
diff --git a/tools/pharos-dashboard/account/urls.py b/tools/pharos-dashboard/account/urls.py
new file mode 100644
index 00000000..b837814a
--- /dev/null
+++ b/tools/pharos-dashboard/account/urls.py
@@ -0,0 +1,25 @@
+"""pharos_dashboard URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.conf.urls import url, include
+ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url
+
+from account.views import *
+
+urlpatterns = [
+ url(r'^settings/', AccountSettingsView.as_view(), name='settings'),
+ url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'),
+ url(r'^login/$', JiraLoginView.as_view(), name='login'),
+ url(r'^logout/$', JiraLogoutView.as_view(), name='logout')
+]
diff --git a/tools/pharos-dashboard/account/views.py b/tools/pharos-dashboard/account/views.py
new file mode 100644
index 00000000..7d2c9bd0
--- /dev/null
+++ b/tools/pharos-dashboard/account/views.py
@@ -0,0 +1,111 @@
+import os
+import urllib
+
+import oauth2 as oauth
+from django.contrib import messages
+from django.contrib.auth import logout, authenticate, login
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.contrib.auth.models import User
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from django.views.generic import RedirectView
+from django.views.generic import UpdateView
+from jira import JIRA
+
+from account.forms import AccountSettingsForm
+from account.jira_util import SignatureMethod_RSA_SHA1
+from account.models import UserProfile
+from pharos_dashboard import settings
+
+consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
+
+
+@method_decorator(login_required, name='dispatch')
+class AccountSettingsView(UpdateView):
+ model = UserProfile
+ form_class = AccountSettingsForm
+ template_name_suffix = '_update_form'
+
+ def get_success_url(self):
+ messages.add_message(self.request, messages.INFO,
+ 'Settings saved')
+ return '/'
+
+ def get_object(self, queryset=None):
+ return self.request.user.userprofile
+
+
+class JiraLoginView(RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ client = oauth.Client(consumer)
+ client.set_signature_method(SignatureMethod_RSA_SHA1())
+
+ # Step 1. Get a request token from Jira.
+ resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+ if resp['status'] != '200':
+ raise Exception("Invalid response %s: %s" % (resp['status'], content))
+
+ # Step 2. Store the request token in a session for later use.
+ self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode()))
+ # Step 3. Redirect the user to the authentication URL.
+ url = settings.OAUTH_AUTHORIZE_URL + '?oauth_token=' + \
+ self.request.session['request_token']['oauth_token']
+ return url
+
+
+class JiraLogoutView(LoginRequiredMixin, RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ logout(self.request)
+ return '/'
+
+
+class JiraAuthenticatedView(RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ # Step 1. Use the request token in the session to build a new client.
+ token = oauth.Token(self.request.session['request_token']['oauth_token'],
+ self.request.session['request_token']['oauth_token_secret'])
+ client = oauth.Client(consumer, token)
+ client.set_signature_method(SignatureMethod_RSA_SHA1())
+
+ # Step 2. Request the authorized access token from Jira.
+ resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+ if resp['status'] != '200':
+ return '/'
+
+ access_token = dict(urllib.parse.parse_qsl(content.decode()))
+
+ module_dir = os.path.dirname(__file__) # get current directory
+ with open(module_dir + '/rsa.pem', 'r') as f:
+ key_cert = f.read()
+
+ oauth_dict = {
+ 'access_token': access_token['oauth_token'],
+ 'access_token_secret': access_token['oauth_token_secret'],
+ 'consumer_key': settings.OAUTH_CONSUMER_KEY,
+ 'key_cert': key_cert
+ }
+
+ jira = JIRA(server=settings.JIRA_URL, oauth=oauth_dict)
+ username = jira.current_user()
+ url = '/'
+ # Step 3. Lookup the user or create them if they don't exist.
+ try:
+ user = User.objects.get(username=username)
+ except User.DoesNotExist:
+ # Save our permanent token and secret for later.
+ user = User.objects.create_user(username=username,
+ password=access_token['oauth_token_secret'])
+ profile = UserProfile()
+ profile.user = user
+ profile.save()
+ url = reverse('account:settings')
+ user.userprofile.oauth_token = access_token['oauth_token']
+ user.userprofile.oauth_secret = access_token['oauth_token_secret']
+ user.userprofile.save()
+ user.set_password(access_token['oauth_token_secret'])
+ user.save()
+ user = authenticate(username=username, password=access_token['oauth_token_secret'])
+ login(self.request, user)
+ # redirect user to settings page to complete profile
+ return url
diff --git a/tools/pharos-dashboard/booking/__init__.py b/tools/pharos-dashboard/booking/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/booking/__init__.py
diff --git a/tools/pharos-dashboard/booking/admin.py b/tools/pharos-dashboard/booking/admin.py
new file mode 100644
index 00000000..6055bed9
--- /dev/null
+++ b/tools/pharos-dashboard/booking/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from booking.models import Booking
+
+admin.site.register(Booking) \ No newline at end of file
diff --git a/tools/pharos-dashboard/booking/apps.py b/tools/pharos-dashboard/booking/apps.py
new file mode 100644
index 00000000..2d5f36f2
--- /dev/null
+++ b/tools/pharos-dashboard/booking/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class BookingConfig(AppConfig):
+ name = 'booking'
diff --git a/tools/pharos-dashboard/booking/forms.py b/tools/pharos-dashboard/booking/forms.py
new file mode 100644
index 00000000..5b32c868
--- /dev/null
+++ b/tools/pharos-dashboard/booking/forms.py
@@ -0,0 +1,9 @@
+import django.forms as forms
+
+
+class BookingForm(forms.Form):
+ fields = ['start', 'end', 'purpose']
+
+ start = forms.DateTimeField()
+ end = forms.DateTimeField()
+ purpose = forms.CharField(max_length=300) \ No newline at end of file
diff --git a/tools/pharos-dashboard/booking/migrations/__init__.py b/tools/pharos-dashboard/booking/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/booking/migrations/__init__.py
diff --git a/tools/pharos-dashboard/booking/models.py b/tools/pharos-dashboard/booking/models.py
new file mode 100644
index 00000000..4be8ccab
--- /dev/null
+++ b/tools/pharos-dashboard/booking/models.py
@@ -0,0 +1,59 @@
+from django.contrib.auth.models import User
+from django.db import models
+from jira import JIRA
+
+from dashboard.models import Resource
+from pharos_dashboard import settings
+
+
+class Booking(models.Model):
+ id = models.AutoField(primary_key=True)
+ user = models.ForeignKey(User, models.CASCADE) # delete if user is deleted
+ resource = models.ForeignKey(Resource, models.PROTECT)
+ start = models.DateTimeField()
+ end = models.DateTimeField()
+ jira_issue_id = models.IntegerField(null=True)
+
+ purpose = models.CharField(max_length=300, blank=False)
+
+ class Meta:
+ db_table = 'booking'
+
+ def get_jira_issue(self):
+ jira = JIRA(server=settings.JIRA_URL, basic_auth=(settings.JIRA_USER_NAME, settings.JIRA_USER_PASSWORD))
+ issue = jira.issue(self.jira_issue_id)
+ return issue
+
+ def authorization_test(self):
+ """
+ Return True if self.user is authorized to make this booking.
+ """
+ user = self.user
+ # Check if User is troubleshooter / admin
+ if user.has_perm('booking.add_booking'):
+ return True
+ # Check if User owns this resource
+ if user == self.resource.owner:
+ return True
+ return False
+
+ def save(self, *args, **kwargs):
+ """
+ Save the booking if self.user is authorized and there is no overlapping booking.
+ Raise PermissionError if the user is not authorized
+ Raise ValueError if there is an overlapping booking
+ """
+ if not self.authorization_test():
+ raise PermissionError('Insufficient permissions to save this booking.')
+ if self.start >= self.end:
+ raise ValueError('Start date is after end date')
+ # conflicts end after booking starts, and start before booking ends
+ conflicting_dates = Booking.objects.filter(resource=self.resource).exclude(id=self.id)
+ conflicting_dates = conflicting_dates.filter(end__gt=self.start)
+ conflicting_dates = conflicting_dates.filter(start__lt=self.end)
+ if conflicting_dates.count() > 0:
+ raise ValueError('This booking overlaps with another booking')
+ return super(Booking, self).save(*args, **kwargs)
+
+ def __str__(self):
+ return str(self.resource) + ' from ' + str(self.start) + ' until ' + str(self.end)
diff --git a/tools/pharos-dashboard/booking/tests/__init__.py b/tools/pharos-dashboard/booking/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/booking/tests/__init__.py
diff --git a/tools/pharos-dashboard/booking/tests/test_models.py b/tools/pharos-dashboard/booking/tests/test_models.py
new file mode 100644
index 00000000..7a572c5f
--- /dev/null
+++ b/tools/pharos-dashboard/booking/tests/test_models.py
@@ -0,0 +1,97 @@
+from datetime import timedelta
+
+from django.contrib.auth.models import User, Permission
+from django.test import TestCase
+from django.utils import timezone
+
+from account.models import UserProfile
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class BookingModelTestCase(TestCase):
+ def setUp(self):
+ self.slave = JenkinsSlave.objects.create(name='test', url='test')
+ self.owner = User.objects.create(username='owner')
+
+ self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+ url='x',owner=self.owner)
+ self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x',
+ url='x',owner=self.owner)
+
+ self.user1 = User.objects.create(username='user1')
+
+ self.add_booking_perm = Permission.objects.get(codename='add_booking')
+ self.user1.user_permissions.add(self.add_booking_perm)
+
+ self.user1 = User.objects.get(pk=self.user1.id)
+
+ def test_start__end(self):
+ """
+ if the start of a booking is greater or equal then the end, saving should raise a
+ ValueException
+ """
+ start = timezone.now()
+ end = start - timedelta(weeks=1)
+ self.assertRaises(ValueError, Booking.objects.create, start=start, end=end,
+ resource=self.res1, user=self.user1)
+ end = start
+ self.assertRaises(ValueError, Booking.objects.create, start=start, end=end,
+ resource=self.res1, user=self.user1)
+
+ def test_conflicts(self):
+ """
+ saving an overlapping booking on the same resource should raise a ValueException
+ saving for different resources should succeed
+ """
+ start = timezone.now()
+ end = start + timedelta(weeks=1)
+ self.assertTrue(
+ Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1))
+
+ self.assertRaises(ValueError, Booking.objects.create, start=start,
+ end=end, resource=self.res1, user=self.user1)
+ self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1),
+ end=end - timedelta(days=1), resource=self.res1, user=self.user1)
+
+ self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1),
+ end=end, resource=self.res1, user=self.user1)
+ self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1),
+ end=end - timedelta(days=1), resource=self.res1, user=self.user1)
+
+ self.assertRaises(ValueError, Booking.objects.create, start=start,
+ end=end + timedelta(days=1), resource=self.res1, user=self.user1)
+ self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1),
+ end=end + timedelta(days=1), resource=self.res1, user=self.user1)
+
+ self.assertTrue(Booking.objects.create(start=start - timedelta(days=1), end=start,
+ user=self.user1, resource=self.res1))
+ self.assertTrue(Booking.objects.create(start=end, end=end + timedelta(days=1),
+ user=self.user1, resource=self.res1))
+
+ self.assertTrue(
+ Booking.objects.create(start=start - timedelta(days=2), end=start - timedelta(days=1),
+ user=self.user1, resource=self.res1))
+ self.assertTrue(
+ Booking.objects.create(start=end + timedelta(days=1), end=end + timedelta(days=2),
+ user=self.user1, resource=self.res1))
+ self.assertTrue(
+ Booking.objects.create(start=start, end=end,
+ user=self.user1, resource=self.res2))
+
+ def test_authorization(self):
+ user = User.objects.create(username='user')
+ user.userprofile = UserProfile.objects.create(user=user)
+ self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(),
+ end=timezone.now() + timedelta(days=1), resource=self.res1, user=user)
+ self.res1.owner = user
+ self.assertTrue(
+ Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
+ resource=self.res1, user=user))
+ self.res1.owner = self.owner
+ user.user_permissions.add(self.add_booking_perm)
+ user = User.objects.get(pk=user.id)
+ self.assertTrue(
+ Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
+ resource=self.res2, user=user))
diff --git a/tools/pharos-dashboard/booking/tests/test_views.py b/tools/pharos-dashboard/booking/tests/test_views.py
new file mode 100644
index 00000000..c5dff586
--- /dev/null
+++ b/tools/pharos-dashboard/booking/tests/test_views.py
@@ -0,0 +1,71 @@
+from datetime import timedelta
+
+from django.contrib import auth
+from django.test import Client
+from django.utils import timezone
+from django.contrib.auth.models import Permission
+from django.test import TestCase
+from django.urls import reverse
+from django.utils.encoding import force_text
+from registration.forms import User
+
+from account.models import UserProfile
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class BookingViewTestCase(TestCase):
+ def setUp(self):
+ self.client = Client()
+ self.slave = JenkinsSlave.objects.create(name='test', url='test')
+ self.owner = User.objects.create(username='owner')
+ self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+ url='x',owner=self.owner)
+ self.user1 = User.objects.create(username='user1')
+ self.user1.set_password('user1')
+ self.user1profile = UserProfile.objects.create(user=self.user1)
+ self.user1.save()
+
+ self.add_booking_perm = Permission.objects.get(codename='add_booking')
+ self.user1.user_permissions.add(self.add_booking_perm)
+
+ self.user1 = User.objects.get(pk=self.user1.id)
+
+
+ def test_resource_bookings_json(self):
+ url = reverse('booking:bookings_json', kwargs={'resource_id': 0})
+ self.assertEqual(self.client.get(url).status_code, 404)
+
+ url = reverse('booking:bookings_json', kwargs={'resource_id': self.res1.id})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertJSONEqual(force_text(response.content), {"bookings": []})
+ booking1 = Booking.objects.create(start=timezone.now(),
+ end=timezone.now() + timedelta(weeks=1), user=self.user1,
+ resource=self.res1)
+ response = self.client.get(url)
+ json = response.json()
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('bookings', json)
+ self.assertEqual(len(json['bookings']), 1)
+ self.assertIn('start', json['bookings'][0])
+ self.assertIn('end', json['bookings'][0])
+ self.assertIn('id', json['bookings'][0])
+ self.assertIn('purpose', json['bookings'][0])
+
+ def test_booking_form_view(self):
+ url = reverse('booking:create', kwargs={'resource_id': 0})
+ self.assertEqual(self.client.get(url).status_code, 404)
+
+ # authenticated user
+ url = reverse('booking:create', kwargs={'resource_id': self.res1.id})
+ self.client.login(username='user1',password='user1')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed('booking/booking_calendar.html')
+ self.assertTemplateUsed('booking/booking_form.html')
+ self.assertIn('resource', response.context)
+
+
+
diff --git a/tools/pharos-dashboard/booking/urls.py b/tools/pharos-dashboard/booking/urls.py
new file mode 100644
index 00000000..f6429daa
--- /dev/null
+++ b/tools/pharos-dashboard/booking/urls.py
@@ -0,0 +1,26 @@
+"""pharos_dashboard URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.conf.urls import url, include
+ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url
+
+from booking.views import *
+
+urlpatterns = [
+ url(r'^(?P<resource_id>[0-9]+)/$', BookingFormView.as_view(), name='create'),
+ url(r'^(?P<resource_id>[0-9]+)/bookings_json/$', ResourceBookingsJSON.as_view(),
+ name='bookings_json'),
+ url(r'^detail/$', BookingView.as_view(), name='detail_prefix'),
+ url(r'^detail/(?P<booking_id>[0-9]+)/$', BookingView.as_view(), name='detail'),
+]
diff --git a/tools/pharos-dashboard/booking/views.py b/tools/pharos-dashboard/booking/views.py
new file mode 100644
index 00000000..fde8d816
--- /dev/null
+++ b/tools/pharos-dashboard/booking/views.py
@@ -0,0 +1,97 @@
+from django.contrib import messages
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.http import JsonResponse
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
+from django.urls import reverse
+from django.views import View
+from django.views.generic import FormView
+from django.views.generic import TemplateView
+from jira import JIRAError
+
+from account.jira_util import get_jira
+from booking.forms import BookingForm
+from booking.models import Booking
+from dashboard.models import Resource
+
+
+def create_jira_ticket(user, booking):
+ jira = get_jira(user)
+ issue_dict = {
+ 'project': 'PHAROS',
+ 'summary': str(booking.resource) + ': Access Request',
+ 'description': booking.purpose,
+ 'issuetype': {'name': 'Task'},
+ 'components': [{'name': 'POD Access Request'}],
+ 'assignee': {'name': booking.resource.owner.username}
+ }
+ issue = jira.create_issue(fields=issue_dict)
+ jira.add_attachment(issue, user.userprofile.pgp_public_key)
+ jira.add_attachment(issue, user.userprofile.ssh_public_key)
+ booking.jira_issue_id = issue.id
+ booking.save()
+
+
+class BookingFormView(LoginRequiredMixin, FormView):
+ template_name = "booking/booking_calendar.html"
+ form_class = BookingForm
+
+ def dispatch(self, request, *args, **kwargs):
+ self.resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
+ return super(BookingFormView, self).dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ title = 'Booking: ' + self.resource.name
+ context = super(BookingFormView, self).get_context_data(**kwargs)
+ context.update({'title': title, 'resource': self.resource})
+ return context
+
+ def get_success_url(self):
+ return reverse('booking:create', kwargs=self.kwargs)
+
+ def form_valid(self, form):
+ user = self.request.user
+ if not user.userprofile.ssh_public_key or not user.userprofile.pgp_public_key:
+ messages.add_message(self.request, messages.INFO,
+ 'Please upload your private keys before booking')
+ return redirect('account:settings')
+ booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'],
+ purpose=form.cleaned_data['purpose'], resource=self.resource,
+ user=user)
+ try:
+ booking.save()
+ except ValueError as err:
+ messages.add_message(self.request, messages.ERROR, err)
+ return super(BookingFormView, self).form_invalid(form)
+ except PermissionError as err:
+ messages.add_message(self.request, messages.ERROR, err)
+ return super(BookingFormView, self).form_invalid(form)
+ try:
+ create_jira_ticket(user, booking)
+ except JIRAError:
+ messages.add_message(self.request, messages.ERROR, 'Failed to create Jira Ticket. '
+ 'Please check your Jira '
+ 'permissions.')
+ booking.delete()
+ return super(BookingFormView, self).form_invalid(form)
+ messages.add_message(self.request, messages.SUCCESS, 'Booking saved')
+ return super(BookingFormView, self).form_valid(form)
+
+
+class BookingView(TemplateView):
+ template_name = "booking/booking_detail.html"
+
+ def get_context_data(self, **kwargs):
+ booking = get_object_or_404(Booking, id=self.kwargs['booking_id'])
+ jira_issue = booking.get_jira_issue()
+ title = 'Booking Details'
+ context = super(BookingView, self).get_context_data(**kwargs)
+ context.update({'title': title, 'booking': booking, 'jira_issue': jira_issue})
+ return context
+
+
+class ResourceBookingsJSON(View):
+ def get(self, request, *args, **kwargs):
+ resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
+ bookings = resource.booking_set.get_queryset().values('id', 'start', 'end', 'purpose')
+ return JsonResponse({'bookings': list(bookings)})
diff --git a/tools/pharos-dashboard/celerybeat-schedule b/tools/pharos-dashboard/celerybeat-schedule
new file mode 100644
index 00000000..7e5fe853
--- /dev/null
+++ b/tools/pharos-dashboard/celerybeat-schedule
Binary files differ
diff --git a/tools/pharos-dashboard/dashboard/admin.py b/tools/pharos-dashboard/dashboard/admin.py
index 532173f0..71a1e7d2 100644
--- a/tools/pharos-dashboard/dashboard/admin.py
+++ b/tools/pharos-dashboard/dashboard/admin.py
@@ -1,9 +1,6 @@
from django.contrib import admin
-from .models import *
-# Register your models here.
-admin.site.register(Booking)
-admin.site.register(Pod)
+from dashboard.models import *
+
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/fixtures/DBdata_resources.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json
deleted file mode 100644
index 5aaccf7a..00000000
--- a/tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"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
deleted file mode 100644
index 19132810..00000000
--- a/tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"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
deleted file mode 100644
index d21be1fa..00000000
--- a/tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"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/fixtures/dashboard.json b/tools/pharos-dashboard/dashboard/fixtures/dashboard.json
new file mode 100644
index 00000000..f0ac3b2f
--- /dev/null
+++ b/tools/pharos-dashboard/dashboard/fixtures/dashboard.json
@@ -0,0 +1,164 @@
+[
+{
+ "model": "dashboard.resource",
+ "pk": 1,
+ "fields": {
+ "name": "Linux Foundation POD 1",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 2,
+ "fields": {
+ "name": "Linux Foundation POD 2",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 3,
+ "fields": {
+ "name": "Ericsson POD 2",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 4,
+ "fields": {
+ "name": "Intel POD 2",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 5,
+ "fields": {
+ "name": "Intel POD 5",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 6,
+ "fields": {
+ "name": "Intel POD 6",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 7,
+ "fields": {
+ "name": "Intel POD 8",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 8,
+ "fields": {
+ "name": "Huawei POD 1",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 9,
+ "fields": {
+ "name": "Intel POD 3",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 10,
+ "fields": {
+ "name": "Dell POD 1",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 11,
+ "fields": {
+ "name": "Dell POD 2",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 12,
+ "fields": {
+ "name": "Orange POD 2",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 13,
+ "fields": {
+ "name": "Arm POD 1",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 14,
+ "fields": {
+ "name": "Ericsson POD 1",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 15,
+ "fields": {
+ "name": "Huawei POD 2",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 16,
+ "fields": {
+ "name": "Huawei POD 3",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 17,
+ "fields": {
+ "name": "Huawei POD 4",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
+ }
+},
+{
+ "model": "dashboard.resource",
+ "pk": 18,
+ "fields": {
+ "name": "Intel POD 9",
+ "description": "Some description",
+ "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9"
+ }
+}
+]
diff --git a/tools/pharos-dashboard/dashboard/forms/booking_form.py b/tools/pharos-dashboard/dashboard/forms/booking_form.py
deleted file mode 100644
index 9cf8048f..00000000
--- a/tools/pharos-dashboard/dashboard/forms/booking_form.py
+++ /dev/null
@@ -1,37 +0,0 @@
-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_util.py b/tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py
deleted file mode 100644
index ba945639..00000000
--- a/tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py
+++ /dev/null
@@ -1,70 +0,0 @@
-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
deleted file mode 100644
index 12de299e..00000000
--- a/tools/pharos-dashboard/dashboard/migrations/0001_initial.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# -*- 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/models.py b/tools/pharos-dashboard/dashboard/models.py
index 132ba7c6..d645cd55 100644
--- a/tools/pharos-dashboard/dashboard/models.py
+++ b/tools/pharos-dashboard/dashboard/models.py
@@ -1,63 +1,16 @@
-from __future__ import unicode_literals
-
-from django.db import models
from django.contrib.auth.models import User
+from django.db import models
-
-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
+from jenkins.models import JenkinsSlave
class Resource(models.Model):
- resource_id = models.AutoField(primary_key=True)
+ 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)
+ owner = models.ForeignKey(User)
+ slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True)
class Meta:
db_table = 'resource'
@@ -67,23 +20,16 @@ class Resource(models.Model):
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)
+ id = models.AutoField(primary_key=True)
+ resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
+ name = models.CharField(max_length=100, blank=True)
+ model = models.CharField(max_length=100, blank=True)
+ cpu = models.CharField(max_length=100, blank=True)
+ ram = models.CharField(max_length=100, blank=True)
+ storage = models.CharField(max_length=100, blank=True)
class Meta:
db_table = 'server'
-
-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'
+ def __str__(self):
+ return self.name
diff --git a/tools/pharos-dashboard/dashboard/static/css/theme.css b/tools/pharos-dashboard/dashboard/static/css/theme.css
deleted file mode 100644
index 4cec341d..00000000
--- a/tools/pharos-dashboard/dashboard/static/css/theme.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.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
deleted file mode 100644
index edc20551..00000000
--- a/tools/pharos-dashboard/dashboard/static/js/booking-calendar.js
+++ /dev/null
@@ -1,68 +0,0 @@
-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
deleted file mode 100644
index 12429b38..00000000
--- a/tools/pharos-dashboard/dashboard/static/js/csrf.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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/datetimepicker-options.js b/tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js
deleted file mode 100644
index 1deb8191..00000000
--- a/tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * 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
deleted file mode 100644
index c0417eb1..00000000
--- a/tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// 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/templatetags/__init__.py b/tools/pharos-dashboard/dashboard/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/dashboard/templatetags/__init__.py
diff --git a/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py b/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py
new file mode 100644
index 00000000..f5038ea5
--- /dev/null
+++ b/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py
@@ -0,0 +1,28 @@
+from django.template.defaultfilters import register
+
+
+@register.filter
+def jenkins_job_color(job_result):
+ if job_result == 'SUCCESS':
+ return '#5cb85c'
+ if job_result == 'FAILURE':
+ return '#d9534f'
+ if job_result == 'UNSTABLE':
+ return '#EDD62B'
+ return '#646F73' # job is still building
+
+
+@register.filter
+def jenkins_status_color(slave_status):
+ if slave_status == 'offline':
+ return '#d9534f'
+ if slave_status == 'online':
+ return '#5cb85c'
+ if slave_status == 'online / idle':
+ return '#5bc0de'
+
+
+@register.filter
+def jenkins_job_blink(job_result):
+ if job_result == '': # job is still building
+ return 'class=blink_me'
diff --git a/tools/pharos-dashboard/dashboard/templatetags/jira_filters.py b/tools/pharos-dashboard/dashboard/templatetags/jira_filters.py
new file mode 100644
index 00000000..d9c27612
--- /dev/null
+++ b/tools/pharos-dashboard/dashboard/templatetags/jira_filters.py
@@ -0,0 +1,8 @@
+from django.template.defaultfilters import register
+
+from pharos_dashboard import settings
+
+
+@register.filter
+def jira_issue_url(issue):
+ return settings.JIRA_URL + '/browse/' + str(issue)
diff --git a/tools/pharos-dashboard/dashboard/urls.py b/tools/pharos-dashboard/dashboard/urls.py
index 8050eb88..809204c1 100644
--- a/tools/pharos-dashboard/dashboard/urls.py
+++ b/tools/pharos-dashboard/dashboard/urls.py
@@ -1,35 +1,28 @@
-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
+"""pharos_dashboard URL Configuration
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.conf.urls import url, include
+ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url
-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=""),
+from dashboard.views import *
- # Tables
+urlpatterns = [
url(r'^ci_pods/$', CIPodsView.as_view(), name='ci_pods'),
url(r'^dev_pods/$', DevelopmentPodsView.as_view(), name='dev_pods'),
url(r'^jenkins_slaves/$', JenkinsSlavesView.as_view(), name='jenkins_slaves'),
+ url(r'^resource/all/$', LabOwnerView.as_view(), name='resources'),
+ url(r'^resource/(?P<resource_id>[0-9]+)/$', ResourceView.as_view(), name='resource'),
- # 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'),
+ url(r'^$', DevelopmentPodsView.as_view(), name="index"),
]
diff --git a/tools/pharos-dashboard/dashboard/views.py b/tools/pharos-dashboard/dashboard/views.py
new file mode 100644
index 00000000..ef1845c8
--- /dev/null
+++ b/tools/pharos-dashboard/dashboard/views.py
@@ -0,0 +1,78 @@
+from datetime import timedelta
+
+from django.shortcuts import get_object_or_404
+from django.utils import timezone
+from django.views.generic import TemplateView
+
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class JenkinsSlavesView(TemplateView):
+ template_name = "dashboard/jenkins_slaves.html"
+
+ def get_context_data(self, **kwargs):
+ slaves = JenkinsSlave.objects.all()
+ context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
+ context.update({'title': "Jenkins Slaves", 'slaves': slaves})
+ return context
+
+
+class CIPodsView(TemplateView):
+ template_name = "dashboard/ci_pods.html"
+
+ def get_context_data(self, **kwargs):
+ ci_pods = Resource.objects.filter(slave__ci_slave=True)
+ context = super(CIPodsView, self).get_context_data(**kwargs)
+ context.update({'title': "CI Pods", 'ci_pods': ci_pods})
+ return context
+
+
+class DevelopmentPodsView(TemplateView):
+ template_name = "dashboard/dev_pods.html"
+
+ def get_context_data(self, **kwargs):
+ resources = Resource.objects.filter(slave__dev_pod=True)
+
+ bookings = Booking.objects.filter(start__lte=timezone.now())
+ bookings = bookings.filter(end__gt=timezone.now())
+
+ dev_pods = []
+ for resource in resources:
+ dev_pod = (resource, None)
+ for booking in bookings:
+ if booking.resource == resource:
+ dev_pod = (resource, booking)
+ dev_pods.append(dev_pod)
+
+ context = super(DevelopmentPodsView, self).get_context_data(**kwargs)
+ context.update({'title': "Development Pods", 'dev_pods': dev_pods})
+ return context
+
+
+class ResourceView(TemplateView):
+ template_name = "dashboard/resource.html"
+
+ def get_context_data(self, **kwargs):
+ resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
+ utilization = resource.slave.get_utilization(timedelta(days=7))
+ bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now())
+ context = super(ResourceView, self).get_context_data(**kwargs)
+ context.update({'title': str(resource), 'resource': resource, 'utilization': utilization, 'bookings': bookings})
+ return context
+
+
+class LabOwnerView(TemplateView):
+ template_name = "dashboard/resource_all.html"
+
+ def get_context_data(self, **kwargs):
+ resources = Resource.objects.filter(slave__dev_pod=True)
+ pods = []
+ for resource in resources:
+ utilization = resource.slave.get_utilization(timedelta(days=7))
+ bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now())
+ pods.append((resource, utilization, bookings))
+ context = super(LabOwnerView, self).get_context_data(**kwargs)
+ context.update({'title': "Overview", 'pods': pods})
+ return context
diff --git a/tools/pharos-dashboard/dashboard/views/booking.py b/tools/pharos-dashboard/dashboard/views/booking.py
deleted file mode 100644
index 1499edb7..00000000
--- a/tools/pharos-dashboard/dashboard/views/booking.py
+++ /dev/null
@@ -1,69 +0,0 @@
-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
deleted file mode 100644
index 516fb298..00000000
--- a/tools/pharos-dashboard/dashboard/views/registration.py
+++ /dev/null
@@ -1,16 +0,0 @@
-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
deleted file mode 100644
index 4404234e..00000000
--- a/tools/pharos-dashboard/dashboard/views/table_views.py
+++ /dev/null
@@ -1,62 +0,0 @@
-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/issues.org b/tools/pharos-dashboard/issues.org
deleted file mode 100644
index 0a7d3cae..00000000
--- a/tools/pharos-dashboard/issues.org
+++ /dev/null
@@ -1,6 +0,0 @@
-* 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/jenkins/__init__.py b/tools/pharos-dashboard/jenkins/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/jenkins/__init__.py
diff --git a/tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py b/tools/pharos-dashboard/jenkins/adapter.py
index cd848ebb..f9e352a1 100644
--- a/tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py
+++ b/tools/pharos-dashboard/jenkins/adapter.py
@@ -1,10 +1,11 @@
-import requests
import logging
+
+import re
+import requests
from django.core.cache import cache
logger = logging.getLogger(__name__)
-
# TODO: implement caching decorator, cache get_* functions
def get_json(url):
if cache.get(url) is None:
@@ -12,7 +13,7 @@ def get_json(url):
response = requests.get(url)
json = response.json()
cache.set(url, json, 180) # cache result for 180 seconds
- return response.json()
+ return json
except requests.exceptions.RequestException as e:
logger.exception(e)
except ValueError as e:
@@ -82,3 +83,42 @@ def is_dev_pod(slavename):
if slavename.find('pod') != -1:
return True
return False
+
+
+def parse_job(job):
+ result = parse_job_string(job['lastBuild']['fullDisplayName'])
+ result['building'] = job['lastBuild']['building']
+ result['result'] = ''
+ if not job['lastBuild']['building']:
+ result['result'] = job['lastBuild']['result']
+ result['url'] = job['url']
+ return result
+
+
+def parse_job_string(full_displayname):
+ job = {}
+ job['scenario'] = ''
+ job['installer'] = ''
+ job['branch'] = ''
+ tokens = re.split(r'[ -]', full_displayname)
+ for i in range(len(tokens)):
+ if tokens[i] == 'os':
+ job['scenario'] = '-'.join(tokens[i: i + 4])
+ elif tokens[i] in ['fuel', 'joid', 'apex', 'compass']:
+ job['installer'] = tokens[i]
+ elif tokens[i] in ['master', 'arno', 'brahmaputra', 'colorado']:
+ job['branch'] = tokens[i]
+ tokens = full_displayname.split(' ')
+ job['name'] = tokens[0]
+ return job
+
+def get_slave_url(slave):
+ return 'https://build.opnfv.org/ci/computer/' + slave['displayName']
+
+
+def get_slave_status(slave):
+ if not slave['offline'] and slave['idle']:
+ return 'online / idle'
+ if not slave['offline']:
+ return 'online'
+ return 'offline'
diff --git a/tools/pharos-dashboard/jenkins/apps.py b/tools/pharos-dashboard/jenkins/apps.py
new file mode 100644
index 00000000..5abd2155
--- /dev/null
+++ b/tools/pharos-dashboard/jenkins/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class JenkinsConfig(AppConfig):
+ name = 'jenkins'
diff --git a/tools/pharos-dashboard/jenkins/migrations/__init__.py b/tools/pharos-dashboard/jenkins/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/pharos-dashboard/jenkins/migrations/__init__.py
diff --git a/tools/pharos-dashboard/jenkins/models.py b/tools/pharos-dashboard/jenkins/models.py
new file mode 100644
index 00000000..354887ab
--- /dev/null
+++ b/tools/pharos-dashboard/jenkins/models.py
@@ -0,0 +1,50 @@
+from django.db import models
+from django.utils import timezone
+
+
+class JenkinsSlave(models.Model):
+ id = models.AutoField(primary_key=True)
+ name = models.CharField(max_length=100, unique=True)
+ status = models.CharField(max_length=30, default='offline')
+ url = models.CharField(max_length=1024)
+ ci_slave = models.BooleanField(default=False)
+ dev_pod = models.BooleanField(default=False)
+
+ building = models.BooleanField(default=False)
+
+ last_job_name = models.CharField(max_length=1024, default='')
+ last_job_url = models.CharField(max_length=1024, default='')
+ last_job_scenario = models.CharField(max_length=50, default='')
+ last_job_branch = models.CharField(max_length=50, default='')
+ last_job_installer = models.CharField(max_length=50, default='')
+ last_job_result = models.CharField(max_length=30, default='')
+
+ def get_utilization(self, timedelta):
+ """
+ Return a dictionary containing the count of idle, online and offline measurements in the time from
+ now-timedelta to now
+ """
+ utilization = {'idle': 0, 'online': 0, 'offline': 0}
+ statistics = self.jenkinsstatistic_set.filter(timestamp__gte=timezone.now() - timedelta)
+ utilization['idle'] = statistics.filter(idle=True).count()
+ utilization['online'] = statistics.filter(online=True).count()
+ utilization['offline'] = statistics.filter(offline=True).count()
+ return utilization
+
+ class Meta:
+ db_table = 'jenkins_slave'
+
+ def __str__(self):
+ return self.name
+
+
+class JenkinsStatistic(models.Model):
+ id = models.AutoField(primary_key=True)
+ slave = models.ForeignKey(JenkinsSlave, on_delete=models.CASCADE)
+ offline = models.BooleanField(default=False)
+ idle = models.BooleanField(default=False)
+ online = models.BooleanField(default=False)
+ timestamp = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ db_table = 'jenkins_statistic'
diff --git a/tools/pharos-dashboard/jenkins/tasks.py b/tools/pharos-dashboard/jenkins/tasks.py
new file mode 100644
index 00000000..6998cf3b
--- /dev/null
+++ b/tools/pharos-dashboard/jenkins/tasks.py
@@ -0,0 +1,39 @@
+from celery import shared_task
+
+from jenkins.models import JenkinsSlave, JenkinsStatistic
+from .adapter import *
+
+
+@shared_task
+def sync_jenkins():
+ update_jenkins_slaves()
+
+
+def update_jenkins_slaves():
+ jenkins_slaves = get_all_slaves()
+ for slave in jenkins_slaves:
+ jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'],
+ url=get_slave_url(slave))
+ jenkins_slave.ci_slave = is_ci_slave(slave['displayName'])
+ jenkins_slave.dev_pod = is_dev_pod(slave['displayName'])
+ jenkins_slave.status = get_slave_status(slave)
+
+ last_job = get_jenkins_job(jenkins_slave.name)
+ if last_job is not None:
+ last_job = parse_job(last_job)
+ jenkins_slave.last_job_name = last_job['name']
+ jenkins_slave.last_job_url = last_job['url']
+ jenkins_slave.last_job_scenario = last_job['scenario']
+ jenkins_slave.last_job_branch = last_job['branch']
+ jenkins_slave.last_job_installer = last_job['installer']
+ jenkins_slave.last_job_result = last_job['result']
+ jenkins_slave.save()
+
+ jenkins_statistic = JenkinsStatistic(slave=jenkins_slave)
+ if jenkins_slave.status == 'online' or jenkins_slave.status == 'building':
+ jenkins_statistic.online = True
+ if jenkins_slave.status == 'offline':
+ jenkins_statistic.offline = True
+ if jenkins_slave.status == 'online / idle':
+ jenkins_statistic.idle = True
+ jenkins_statistic.save()
diff --git a/tools/pharos-dashboard/dashboard/tests.py b/tools/pharos-dashboard/jenkins/tests.py
index 75095782..b1414515 100644
--- a/tools/pharos-dashboard/dashboard/tests.py
+++ b/tools/pharos-dashboard/jenkins/tests.py
@@ -1,5 +1,6 @@
-import dashboard.jenkins.jenkins_adapter as jenkins
-from django.test import SimpleTestCase
+from unittest import TestCase
+
+import jenkins.adapter as jenkins
# Tests that the data we get with the jenkinsadapter contains all the
@@ -8,7 +9,7 @@ from django.test import SimpleTestCase
# - the opnfv jenkins url has changed
# - the jenkins api has changed
# - jenkins is not set up / there is no data
-class JenkinsAdapterTestCase(SimpleTestCase):
+class JenkinsAdapterTestCase(TestCase):
def test_get_all_slaves(self):
slaves = jenkins.get_all_slaves()
self.assertTrue(len(slaves) > 0)
diff --git a/tools/pharos-dashboard/manage.py b/tools/pharos-dashboard/manage.py
index 65e6fc6a..97a5ba4a 100755
--- a/tools/pharos-dashboard/manage.py
+++ b/tools/pharos-dashboard/manage.py
@@ -4,7 +4,19 @@ import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pharos_dashboard.settings")
-
- from django.core.management import execute_from_command_line
-
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError:
+ # The above import may fail for some other reason. Ensure that the
+ # issue is really that Django is missing to avoid masking other
+ # exceptions on Python 2.
+ try:
+ import django
+ except ImportError:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ )
+ raise
execute_from_command_line(sys.argv)
diff --git a/tools/pharos-dashboard/pharos_dashboard/__init__.py b/tools/pharos-dashboard/pharos_dashboard/__init__.py
index e69de29b..b6fc8176 100644
--- a/tools/pharos-dashboard/pharos_dashboard/__init__.py
+++ b/tools/pharos-dashboard/pharos_dashboard/__init__.py
@@ -0,0 +1,3 @@
+# This will make sure the app is always imported when
+# Django starts so that shared_task will use this app.
+from .celery import app as celery_app # noqa
diff --git a/tools/pharos-dashboard/pharos_dashboard/celery.py b/tools/pharos-dashboard/pharos_dashboard/celery.py
new file mode 100644
index 00000000..4cf6a7af
--- /dev/null
+++ b/tools/pharos-dashboard/pharos_dashboard/celery.py
@@ -0,0 +1,20 @@
+import os
+
+from celery import Celery
+
+# set the default Django settings module for the 'celery' program.
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pharos_dashboard.settings')
+
+from django.conf import settings # noqa
+
+app = Celery('pharos_dashboard')
+
+# Using a string here means the worker will not have to
+# pickle the object when using Windows.
+app.config_from_object('django.conf:settings')
+app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
+
+
+@app.task(bind=True)
+def debug_task(self):
+ print('Request: {0!r}'.format(self.request)) \ No newline at end of file
diff --git a/tools/pharos-dashboard/pharos_dashboard/settings.py b/tools/pharos-dashboard/pharos_dashboard/settings.py
deleted file mode 100644
index 2bc94965..00000000
--- a/tools/pharos-dashboard/pharos_dashboard/settings.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""
-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
index 03b9c256..26ab3677 100644
--- a/tools/pharos-dashboard/pharos_dashboard/urls.py
+++ b/tools/pharos-dashboard/pharos_dashboard/urls.py
@@ -1,7 +1,7 @@
-"""opnfvdashboard URL Configuration
+"""pharos_dashboard URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
- https://docs.djangoproject.com/en/1.9/topics/http/urls/
+ https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
@@ -13,11 +13,13 @@ 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.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^', include('dashboard.urls', namespace='dashboard')),
- url(r'^admin/', include(admin.site.urls)),
+ url(r'^booking/', include('booking.urls', namespace='booking')),
+ url(r'^account/', include('account.urls', namespace='account')),
+
+ url(r'^admin/', 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
index 54f57355..b1277516 100644
--- a/tools/pharos-dashboard/pharos_dashboard/wsgi.py
+++ b/tools/pharos-dashboard/pharos_dashboard/wsgi.py
@@ -4,7 +4,7 @@ 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/
+https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
import os
diff --git a/tools/pharos-dashboard/requirements.txt b/tools/pharos-dashboard/requirements.txt
deleted file mode 100644
index 6306c0b5..00000000
--- a/tools/pharos-dashboard/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-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/dashboard/static/bower.json b/tools/pharos-dashboard/static/bower.json
index 78406217..78406217 100644
--- a/tools/pharos-dashboard/dashboard/static/bower.json
+++ b/tools/pharos-dashboard/static/bower.json
diff --git a/tools/pharos-dashboard/static/css/theme.css b/tools/pharos-dashboard/static/css/theme.css
new file mode 100644
index 00000000..bd156372
--- /dev/null
+++ b/tools/pharos-dashboard/static/css/theme.css
@@ -0,0 +1,13 @@
+.blink_me {
+ animation: blinker 1.5s linear infinite;
+}
+
+@keyframes blinker {
+ 20% {
+ opacity: 0.4;
+ }
+}
+
+.modal p {
+ word-wrap: break-word;
+} \ No newline at end of file
diff --git a/tools/pharos-dashboard/static/js/booking-calendar.js b/tools/pharos-dashboard/static/js/booking-calendar.js
new file mode 100644
index 00000000..f8a9a0ff
--- /dev/null
+++ b/tools/pharos-dashboard/static/js/booking-calendar.js
@@ -0,0 +1,36 @@
+function parseCalendarEvents(bookings) {
+ var events = [];
+ for (var i = 0; i < bookings.length; i++) {
+ // convert ISO 8601 timestring to moment, needed for timezone handling
+ start = moment(bookings[i]['start']);
+ end = moment(bookings[i]['end']);
+ event = {
+ id: bookings[i]['id'],
+ title: bookings[i]['purpose'],
+ start: start,
+ end: end,
+ };
+ events.push(event);
+ }
+ return events;
+}
+
+function loadEvents(url) {
+ $.ajax({
+ url: url,
+ type: 'get',
+ success: function (data) {
+ $('#calendar').fullCalendar('addEventSource', parseCalendarEvents(data['bookings']));
+ },
+ failure: function (data) {
+ alert('Error loading booking data');
+ }
+ });
+}
+
+$(document).ready(function () {
+ $('#calendar').fullCalendar(calendarOptions);
+ loadEvents(bookings_url);
+ $('#starttimepicker').datetimepicker(timepickerOptions);
+ $('#endtimepicker').datetimepicker(timepickerOptions);
+}); \ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js b/tools/pharos-dashboard/static/js/dataTables-sort.js
index 7189ca15..7189ca15 100644
--- a/tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js
+++ b/tools/pharos-dashboard/static/js/dataTables-sort.js
diff --git a/tools/pharos-dashboard/static/js/datetimepicker-options.js b/tools/pharos-dashboard/static/js/datetimepicker-options.js
new file mode 100644
index 00000000..2d19ddae
--- /dev/null
+++ b/tools/pharos-dashboard/static/js/datetimepicker-options.js
@@ -0,0 +1,3 @@
+var timepickerOptions = {
+ format: 'MM/DD/YYYY HH:00'
+}; \ No newline at end of file
diff --git a/tools/pharos-dashboard/static/js/fullcalendar-options.js b/tools/pharos-dashboard/static/js/fullcalendar-options.js
new file mode 100644
index 00000000..f4fa50b3
--- /dev/null
+++ b/tools/pharos-dashboard/static/js/fullcalendar-options.js
@@ -0,0 +1,91 @@
+var tmpevent;
+
+function sendEventToForm(event) {
+ $('#starttimepicker').data("DateTimePicker").date(event.start);
+ $('#endtimepicker').data("DateTimePicker").date(event.end);
+}
+
+var calendarOptions = {
+ height: 600,
+ header: {
+ left: 'prev,next today',
+ center: 'title',
+ right: 'agendaWeek,month'
+ },
+ timezone: user_timezone, // set in booking_calendar.html
+ defaultView: 'month',
+ slotDuration: '00:60:00',
+ slotLabelFormat: "HH:mm",
+ firstDay: 1,
+ allDaySlot: false,
+ selectOverlap: false,
+ eventOverlap: false,
+ selectable: true,
+ editable: false,
+ eventLimit: true, // allow "more" link when too many events
+ timeFormat: 'H(:mm)', // uppercase H for 24-hour clock
+ unselectAuto: true,
+ nowIndicator: true,
+
+ // selectHelper is only working in the agendaWeek view, this is a workaround:
+ // if an event is selected, the existing selection is removed and a temporary event is added
+ // to the calendar
+ select: function (start, end) {
+ if (tmpevent != undefined) {
+ $('#calendar').fullCalendar('removeEvents', tmpevent.id);
+ $('#calendar').fullCalendar('rerenderEvents');
+ tmpevent = undefined;
+ }
+ // the times need to be converted here to make them show up in the agendaWeek view if they
+ // are created in the month view. If they are not converted, the tmpevent will only show
+ // up in the (deactivated) allDaySlot
+ start = moment(start);
+ end = moment(end);
+
+ tmpevent = {
+ id: '537818f62bc63518ece15338fb86c8be',
+ title: 'New Booking',
+ start: start,
+ end: end,
+ editable: true
+ };
+
+ $('#calendar').fullCalendar('renderEvent', tmpevent, true);
+ sendEventToForm(tmpevent);
+ },
+
+ eventClick: function (event) {
+ if (tmpevent != undefined) {
+ if (event.id != tmpevent.id) {
+ $('#calendar').fullCalendar('removeEvents', tmpevent.id);
+ $('#calendar').fullCalendar('rerenderEvents');
+ tmpevent = undefined;
+ }
+ }
+
+ // tmpevent is deleted if a real event is clicked, load event details
+ if (tmpevent == undefined) {
+ var booking_detail_url = booking_detail_prefix + event.id;
+
+ $.ajax({
+ url: booking_detail_url,
+ type: 'get',
+ success: function (data) {
+ $('#booking_detail_content').html(data);
+ },
+ failure: function (data) {
+ alert('Error loading booking details');
+ }
+ });
+ $('#booking_detail_modal').modal('show');
+ }
+ },
+
+ eventDrop: function (event) {
+ sendEventToForm(event);
+ },
+
+ eventResize: function (event) {
+ sendEventToForm(event);
+ }
+}; \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/account/userprofile_update_form.html b/tools/pharos-dashboard/templates/account/userprofile_update_form.html
new file mode 100644
index 00000000..542ea81e
--- /dev/null
+++ b/tools/pharos-dashboard/templates/account/userprofile_update_form.html
@@ -0,0 +1,30 @@
+{% extends "layout.html" %}
+{% load bootstrap3 %}
+
+{% block basecontent %}
+ <div class="container">
+ <div class="row">
+ <div class="col-md-4 col-md-offset-4">
+ {% bootstrap_messages %}
+ <div class="login-panel panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ {{ title }}
+ </h3>
+ </div>
+ <div class="panel-body">
+ <form enctype="multipart/form-data" method="post">
+ {% csrf_token %}
+ {% bootstrap_form form %}
+ {% buttons %}
+ <button type="submit" class="btn btn btn-success">
+ Save
+ </button>
+ {% endbuttons %}
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock basecontent %}
diff --git a/tools/pharos-dashboard/templates/dashboard/base.html b/tools/pharos-dashboard/templates/base.html
index 544bf0b3..64174a1f 100644
--- a/tools/pharos-dashboard/templates/dashboard/base.html
+++ b/tools/pharos-dashboard/templates/base.html
@@ -1,4 +1,4 @@
-{% extends "layout/base.html" %}
+{% extends "layout.html" %}
{% load bootstrap3 %}
{% block basecontent %}
@@ -16,7 +16,7 @@
</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>
+ <a class="navbar-brand" href={% url 'dashboard:index' %}>Pharos Dashboard</a>
</div>
<!-- /.navbar-header -->
@@ -27,19 +27,20 @@
</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><a href="{% url 'account:settings' %}"><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
+ <li><a href="{% url 'account: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>
+ <li><a href="{% url 'account:login' %}"><i
+ class="fa fa-sign-in fa-fw"></i>
+ Login with Jira</a>
+ <li>
{% endif %}
</ul>
<!-- /.dropdown-user -->
@@ -53,18 +54,23 @@
<ul class="nav" id="side-menu">
<li>
<a href="{% url 'dashboard:ci_pods' %}"><i
- class="fa fa-table fa-fw"></i>CI-Pods</a>
+ class="fa fa-fw"></i>CI-Pods</a>
</li>
<li>
<a href="{% url 'dashboard:dev_pods' %}"><i
- class="fa fa-table fa-fw"></i>Development
+ class="fa fa-fw"></i>Development
Pods</a>
</li>
<li>
<a href="{% url 'dashboard:jenkins_slaves' %}"><i
- class="fa fa-table fa-fw"></i>Jenkins
+ class="fa fa-fw"></i>Jenkins
Slaves</a>
</li>
+ <li>
+ <a href="{% url 'dashboard:resources' %}"><i
+ class="fa fa-fw"></i>Resources
+ </a>
+ </li>
</ul>
</div>
<!-- /.sidebar-collapse -->
diff --git a/tools/pharos-dashboard/templates/dashboard/booking_calendar.html b/tools/pharos-dashboard/templates/booking/booking_calendar.html
index 0f6bece0..de3e3b3d 100644
--- a/tools/pharos-dashboard/templates/dashboard/booking_calendar.html
+++ b/tools/pharos-dashboard/templates/booking/booking_calendar.html
@@ -1,5 +1,6 @@
-{% extends "dashboard/base.html" %}
+{% extends "dashboard/table.html" %}
{% load staticfiles %}
+
{% load bootstrap3 %}
{% block extrahead %}
@@ -10,62 +11,77 @@
{% 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 class="col-lg-8">
+ <div class="container-fluid">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <i class="fa fa-calendar fa-fw"></i>Calendar
+ </div>
+ <div class="panel-body">
+ <div id='calendar'>
</div>
- <!-- /.panel-body -->
</div>
- <!-- /.panel -->
+ <!-- /.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">
+ <div class="col-lg-4">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <i class="fa fa-edit fa-fw"></i>Booking
+ </div>
+ <div class="panel-body">
+ <div id="booking_form_div">
{% bootstrap_form_errors form type='non_fields' %}
-
- <form method="post" class="form" id="bookingform">
+ <form method="post" action="" 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>' %}
+ {% bootstrap_field form.start 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>' %}
+ {% bootstrap_field form.end 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 type="submit" class="btn btn btn-success">
+ Book
</button>
{% endbuttons %}
</form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="booking_detail_modal" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <!-- Modal content-->
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title">Booking Detail</h4>
+ </div>
+ <div class="modal-body" id="booking_detail_content">
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
+
</div>
</div>
{% endblock content %}
{% block extrajs %}
<script type="text/javascript">
- var bookings_url = '/resource/' + {{ resource.resource_id }} +'/bookings/';
+ var bookings_url = "{% url 'booking:bookings_json' resource_id=resource.id %}";
+ var booking_detail_prefix = "{% url 'booking:detail_prefix' %}";
+ var user_timezone = "{{ request.user.userprofile.timezone }}"
</script>
<script src={% static "bower_components/moment/moment.js" %}></script>
@@ -74,6 +90,5 @@
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/booking/booking_detail.html b/tools/pharos-dashboard/templates/booking/booking_detail.html
new file mode 100644
index 00000000..d3f47538
--- /dev/null
+++ b/tools/pharos-dashboard/templates/booking/booking_detail.html
@@ -0,0 +1,26 @@
+{% load jira_filters %}
+
+<p>
+ <b>Resource: </b>
+ <a href="{{ booking.resource.url }}">
+ {{ booking.resource.name }}
+ </a>
+</p>
+<p>
+ <b>User: </b> {{ booking.user.username }}
+</p>
+<p>
+ <b>Start: </b> {{ booking.start }}
+</p>
+<p>
+ <b>End: </b> {{ booking.end }}
+</p>
+<p>
+ <b>Purpose: </b> {{ booking.purpose }}
+</p>
+<p>
+ <b>Jira: </b>
+ <a href="{{ jira_issue | jira_issue_url }}">
+ {{ jira_issue }}
+ </a>
+</p> \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/booking/booking_table.html b/tools/pharos-dashboard/templates/booking/booking_table.html
new file mode 100644
index 00000000..3d0b7575
--- /dev/null
+++ b/tools/pharos-dashboard/templates/booking/booking_table.html
@@ -0,0 +1,33 @@
+{% load jira_filters %}
+
+
+<thead>
+<tr>
+ <th>User</th>
+ <th>Purpose</th>
+ <th>Start</th>
+ <th>End</th>
+ <th>Jira</th>
+</tr>
+</thead>
+<tbody>
+{% for booking in bookings %}
+ <tr>
+ <th>
+ {{ booking.user.username }}
+ </th>
+ <th>
+ {{ booking.purpose }}
+ </th>
+ <th>
+ {{ booking.start }}
+ </th>
+ <th>
+ {{ booking.end }}
+ </th>
+ <th><a target='_blank'
+ href={{ booking.get_jira_issue | jira_issue_url }}>{{ booking.get_jira_issue }}</a>
+ </th>
+ </tr>
+{% endfor %}
+</tbody> \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/tables/ci_pods.html b/tools/pharos-dashboard/templates/dashboard/ci_pods.html
index 3889664b..2982a6ff 100644
--- a/tools/pharos-dashboard/templates/tables/ci_pods.html
+++ b/tools/pharos-dashboard/templates/dashboard/ci_pods.html
@@ -1,5 +1,6 @@
{% extends "dashboard/table.html" %}
{% load staticfiles %}
+{% load jenkins_filters %}
{% block table %}
<thead>
@@ -20,28 +21,28 @@
<a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
</th>
<th>
- <a target='_blank' href={{ pod.slaveurl }}>{{ pod.slavename }}</a>
+ <a target='_blank' href={{ pod.slave.url }}>{{ pod.slave.name }}</a>
</th>
- <th style="background-color:{{ pod.status_color }}">
- {{ pod.status }}
+ <th style="background-color:{{ pod.slave.status | jenkins_status_color }}">
+ {{ pod.slave.status }}
</th>
- <th {{ pod.last_job.blink }}>
- {{ pod.last_job.installer }}
+ <th {{ pod.slave.last_job_result | jenkins_job_blink }}>
+ {{ pod.slave.last_job_installer }}
</th>
- <th {{ pod.last_job.blink }}>
- {{ pod.last_job.scenario }}
+ <th {{ pod.slave.last_job_result | jenkins_job_blink }}>
+ {{ pod.slave.last_job_scenario }}
</th>
- <th {{ pod.last_job.blink }}>
- {{ pod.last_job.branch }}
+ <th {{ pod.slave.last_job_result | jenkins_job_blink }}>
+ {{ pod.slave.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><a {{ pod.slave.last_job_result | jenkins_job_blink }}
+ style="color:{{ pod.slave.last_job_result | jenkins_job_color }}"
+ target='_blank'
+ href={{ pod.slave.last_job_url }}>{{ pod.slave.last_job_name }}</a>
</th>
</tr>
{% endfor %}`
</tbody>
- </table>
{% endblock table %}
diff --git a/tools/pharos-dashboard/templates/tables/dev_pods.html b/tools/pharos-dashboard/templates/dashboard/dev_pods.html
index 730aa953..9c84bb91 100644
--- a/tools/pharos-dashboard/templates/tables/dev_pods.html
+++ b/tools/pharos-dashboard/templates/dashboard/dev_pods.html
@@ -1,5 +1,6 @@
{% extends "dashboard/table.html" %}
{% load staticfiles %}
+{% load jenkins_filters %}
{% block table %}
<thead>
@@ -14,28 +15,28 @@
</tr>
</thead>
<tbody>
- {% for pod in dev_pods %}
+ {% for pod, booking in dev_pods %}
<tr>
<th>
- <a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
+ <a href={% url 'dashboard:resource' resource_id=pod.id %}>{{ pod.name }}</a>
</th>
<th>
- <a target='_blank' href={{ pod.slaveurl }}>{{ pod.slavename }}</a>
+ <a target='_blank' href={{ pod.slave.url }}>{{ pod.slave.name }}</a>
</th>
<th>
- {{ pod.current_booking.user }}
+ {{ booking.user.username }}
</th>
<th>
- {{ pod.current_booking.end_date_time }}
+ {{ booking.end }}
</th>
<th>
- {{ pod.current_booking.purpose }}
+ {{ booking.purpose }}
</th>
- <th style="background-color:{{ pod.status_color }}">
- {{ pod.status }}
+ <th style="background-color:{{ pod.slave.status | jenkins_status_color }}">
+ {{ pod.slave.status }}
</th>
<th>
- <a href='{% url 'dashboard:booking_calendar' %}{{ pod.resource_id }}' class="btn btn-primary">
+ <a href="{% url 'booking:create' resource_id=pod.id %}" class="btn btn-primary">
Book
</a>
</th>
diff --git a/tools/pharos-dashboard/templates/tables/jenkins_slaves.html b/tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html
index 2d011b46..830ed198 100644
--- a/tools/pharos-dashboard/templates/tables/jenkins_slaves.html
+++ b/tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html
@@ -1,6 +1,8 @@
{% extends "dashboard/table.html" %}
{% load staticfiles %}
+{% load jenkins_filters %}
+
{% block table %}
<thead>
<tr>
@@ -13,14 +15,15 @@
{% for slave in slaves %}
<tr>
<th><a target='_blank'
- href={{ slave.slaveurl }}>{{ slave.displayName }}</a>
+ href={{ slave.url }}>{{ slave.name }}</a>
</th>
- <th style="background-color:{{ slave.status_color }}">
+ <th style="background-color:{{ slave.status | jenkins_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><a {{ slave.last_job_result | jenkins_job_blink }}
+ style="color:{{ slave.last_job_result | jenkins_job_color }}"
+ target="_blank" href={{ slave.last_job_url }}>
+ {{ slave.last_job_name }}</a>
</th>
</tr>
{% endfor %}
diff --git a/tools/pharos-dashboard/templates/dashboard/resource.html b/tools/pharos-dashboard/templates/dashboard/resource.html
new file mode 100644
index 00000000..92d02f66
--- /dev/null
+++ b/tools/pharos-dashboard/templates/dashboard/resource.html
@@ -0,0 +1,58 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block extrahead %}
+ <!-- Morris Charts CSS -->
+ <link href="{% static "bower_components/morrisjs/morris.css" %}" rel="stylesheet">
+
+ <!-- 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 %}
+ {% include "dashboard/resource_detail.html" %}
+{% 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>
+
+
+
+ <!-- Flot Charts JavaScript -->
+ <script src="{% static "bower_components/flot/excanvas.min.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.pie.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.resize.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.time.js" %}"></script>
+ <script src="{% static "bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js" %}"></script>
+
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('#{{ resource.id }}_server_table').DataTable({});
+ $('#{{ resource.id }}_bookings_table').DataTable({});
+
+ $(function () {
+ var plotObj = $.plot($("#{{ resource.id }}_slave_utilization"), data_{{ resource.id }}, {
+ series: {
+ pie: {
+ show: true
+ }
+ }
+ });
+
+ });
+ });
+ </script>
+{% endblock extrajs %} \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/resource_all.html b/tools/pharos-dashboard/templates/dashboard/resource_all.html
new file mode 100644
index 00000000..2078475f
--- /dev/null
+++ b/tools/pharos-dashboard/templates/dashboard/resource_all.html
@@ -0,0 +1,74 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block extrahead %}
+ <!-- Morris Charts CSS -->
+ <link href="{% static "bower_components/morrisjs/morris.css" %}" rel="stylesheet">
+
+ <!-- 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 %}
+ {% for resource, utilization, bookings in pods %}
+ <div class="row">
+ <div class="col-lg-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{ resource.name }}
+ </div>
+ <div class="panel-body">
+ {% include "dashboard/resource_detail.html" %}
+ </div>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+{% 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>
+
+
+
+ <!-- Flot Charts JavaScript -->
+ <script src="{% static "bower_components/flot/excanvas.min.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.pie.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.resize.js" %}"></script>
+ <script src="{% static "bower_components/flot/jquery.flot.time.js" %}"></script>
+ <script src="{% static "bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js" %}"></script>
+
+ <script type="text/javascript">
+ $(document).ready(function () {
+ {% for resource, utilization, bookings in pods %}
+
+ $('#{{ resource.id }}_server_table').DataTable({});
+ $('#{{ resource.id }}_bookings_table').DataTable({});
+
+ $(function () {
+ var plotObj = $.plot($("#{{ resource.id }}_slave_utilization"), data_{{ resource.id }}, {
+ series: {
+ pie: {
+ show: true
+ }
+ }
+ });
+
+ });
+ {% endfor %}
+ });
+ </script>
+{% endblock extrajs %} \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/resource_detail.html b/tools/pharos-dashboard/templates/dashboard/resource_detail.html
new file mode 100644
index 00000000..4fba4766
--- /dev/null
+++ b/tools/pharos-dashboard/templates/dashboard/resource_detail.html
@@ -0,0 +1,64 @@
+<div class="row">
+ <div class="col-lg-3">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Utilization
+ </div>
+ <div class="panel-body">
+ <div class="flot-chart">
+ <div class="flot-chart-content" id="{{ resource.id }}_slave_utilization"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-9">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Servers
+ </div>
+ <div class="panel-body">
+ <div class="dataTables_wrapper">
+ <table class="table table-striped table-bordered table-hover"
+ id="{{ resource.id }}_server_table" cellspacing="0"
+ width="100%">
+ {% include "dashboard/server_table.html" %}
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<div class="row">
+ <div class="col-lg-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Bookings
+ </div>
+ <div class="panel-body">
+ <div class="dataTables_wrapper">
+ <table class="table table-striped table-bordered table-hover"
+ id="{{ resource.id }}_bookings_table" cellspacing="0"
+ width="100%">
+ {% include "booking/booking_table.html" %}
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+ var data_{{ resource.id }} = [{
+ label: "Offline",
+ data: {{ utilization.offline }},
+ color: '#d9534f'
+ }, {
+ label: "Online",
+ data: {{ utilization.online }},
+ color: '#5cb85c'
+ }, {
+ label: "Idle",
+ data: {{ utilization.idle }},
+ color: '#5bc0de'
+ }];
+</script> \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/server_table.html b/tools/pharos-dashboard/templates/dashboard/server_table.html
new file mode 100644
index 00000000..d47e5204
--- /dev/null
+++ b/tools/pharos-dashboard/templates/dashboard/server_table.html
@@ -0,0 +1,30 @@
+<thead>
+<tr>
+ <th>Server</th>
+ <th>Model</th>
+ <th>CPU</th>
+ <th>RAM</th>
+ <th>Storage</th>
+</tr>
+</thead>
+<tbody>
+{% for server in resource.server_set.all %}
+ <tr>
+ <th>
+ {{ server.name }}
+ </th>
+ <th>
+ {{ server.model }}
+ </th>
+ <th>
+ {{ server.cpu }}
+ </th>
+ <th>
+ {{ server.ram }}
+ </th>
+ <th>
+ {{ server.storage }}
+ </th>
+ </tr>
+{% endfor %}`
+</tbody> \ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/table.html b/tools/pharos-dashboard/templates/dashboard/table.html
index 2d0b82ee..addd5c12 100644
--- a/tools/pharos-dashboard/templates/dashboard/table.html
+++ b/tools/pharos-dashboard/templates/dashboard/table.html
@@ -1,4 +1,4 @@
-{% extends "dashboard/base.html" %}
+{% extends "base.html" %}
{% load staticfiles %}
{% block extrahead %}
diff --git a/tools/pharos-dashboard/templates/layout/base.html b/tools/pharos-dashboard/templates/layout.html
index e9e1cd1f..db9490f5 100644
--- a/tools/pharos-dashboard/templates/layout/base.html
+++ b/tools/pharos-dashboard/templates/layout.html
@@ -50,9 +50,12 @@
{% 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.4.min.js"
+ integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+{#<!-- 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 -->
diff --git a/tools/pharos-dashboard/templates/registration/login.html b/tools/pharos-dashboard/templates/registration/login.html
deleted file mode 100644
index efdcd1fb..00000000
--- a/tools/pharos-dashboard/templates/registration/login.html
+++ /dev/null
@@ -1,61 +0,0 @@
-{% 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 %}