diff options
Diffstat (limited to 'tools/pharos-dashboard/src')
41 files changed, 732 insertions, 139 deletions
diff --git a/tools/pharos-dashboard/src/account/jira_util.py b/tools/pharos-dashboard/src/account/jira_util.py index c333f8c4..fdb87f77 100644 --- a/tools/pharos-dashboard/src/account/jira_util.py +++ b/tools/pharos-dashboard/src/account/jira_util.py @@ -12,11 +12,10 @@ import base64 import os import oauth2 as oauth +from django.conf import settings from jira import JIRA from tlslite.utils import keyfactory -from django.conf import settings - class SignatureMethod_RSA_SHA1(oauth.SignatureMethod): name = 'RSA-SHA1' diff --git a/tools/pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py b/tools/pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py new file mode 100644 index 00000000..33d2cc54 --- /dev/null +++ b/tools/pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-10-05 12:01 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='full_name', + field=models.CharField(default='', max_length=100), + ), + migrations.AddField( + model_name='userprofile', + name='jira_url', + field=models.CharField(default='', max_length=100), + ), + ] diff --git a/tools/pharos-dashboard/src/account/models.py b/tools/pharos-dashboard/src/account/models.py index 621f6697..c2e99028 100644 --- a/tools/pharos-dashboard/src/account/models.py +++ b/tools/pharos-dashboard/src/account/models.py @@ -8,11 +8,9 @@ ############################################################################## -from django.db import models - from django.contrib.auth.models import User +from django.db import models -from dashboard.models import Resource def upload_to(object, filename): return object.user.username + '/' + filename @@ -23,8 +21,15 @@ class UserProfile(models.Model): 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) + jira_url = models.CharField(max_length=100, default='') + full_name = models.CharField(max_length=100, default='') + class Meta: db_table = 'user_profile' + + def __str__(self): + return self.user.username diff --git a/tools/pharos-dashboard/src/account/tasks.py b/tools/pharos-dashboard/src/account/tasks.py new file mode 100644 index 00000000..bfb865dd --- /dev/null +++ b/tools/pharos-dashboard/src/account/tasks.py @@ -0,0 +1,34 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from celery import shared_task +from django.contrib.auth.models import User +from jira import JIRAError + +from account.jira_util import get_jira + + +@shared_task +def sync_jira_accounts(): + users = User.objects.all() + for user in users: + jira = get_jira(user) + try: + user_dict = jira.myself() + except JIRAError: + # User can be anonymous (local django admin account) + continue + user.email = user_dict['emailAddress'] + user.userprofile.url = user_dict['self'] + user.userprofile.full_name = user_dict['displayName'] + print(user_dict) + + user.userprofile.save() + user.save()
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/account/tests/test_general.py b/tools/pharos-dashboard/src/account/tests/test_general.py index 72e7ea11..e8f483b5 100644 --- a/tools/pharos-dashboard/src/account/tests/test_general.py +++ b/tools/pharos-dashboard/src/account/tests/test_general.py @@ -48,3 +48,13 @@ class AccountMiddlewareTestCase(TestCase): self.user1profile.save() self.client.get(url) self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich') + + # if there is no profile for a user, it should be created + user2 = User.objects.create(username='user2') + user2.set_password('user2') + user2.save() + self.client.login(username='user2', password='user2') + self.client.get(url) + self.assertTrue(user2.userprofile) + + diff --git a/tools/pharos-dashboard/src/account/views.py b/tools/pharos-dashboard/src/account/views.py index 3b4269de..17fbdc3a 100644 --- a/tools/pharos-dashboard/src/account/views.py +++ b/tools/pharos-dashboard/src/account/views.py @@ -12,6 +12,7 @@ import os import urllib import oauth2 as oauth +from django.conf import settings from django.contrib import messages from django.contrib.auth import logout, authenticate, login from django.contrib.auth.decorators import login_required @@ -19,17 +20,13 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.urls import reverse from django.utils.decorators import method_decorator -from django.views.generic import RedirectView -from django.views.generic import TemplateView -from django.views.generic import UpdateView +from django.views.generic import RedirectView, TemplateView, UpdateView from jira import JIRA +from rest_framework.authtoken.models import Token from account.forms import AccountSettingsForm from account.jira_util import SignatureMethod_RSA_SHA1 from account.models import UserProfile -from django.conf import settings - -consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET) @method_decorator(login_required, name='dispatch') @@ -46,22 +43,36 @@ class AccountSettingsView(UpdateView): def get_object(self, queryset=None): return self.request.user.userprofile + def get_context_data(self, **kwargs): + token, created = Token.objects.get_or_create(user=self.request.user) + context = super(AccountSettingsView, self).get_context_data(**kwargs) + context.update({'title': "Settings", 'token': token}) + return context + class JiraLoginView(RedirectView): def get_redirect_url(self, *args, **kwargs): + consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET) 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") + try: + resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST") + except Exception as e: + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') + return '/' if resp['status'] != '200': - raise Exception("Invalid response %s: %s" % (resp['status'], content)) + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') + return '/' # 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'] + \ + self.request.session['request_token']['oauth_token'] + \ '&oauth_callback=' + settings.OAUTH_CALLBACK_URL return url @@ -75,14 +86,22 @@ class JiraLogoutView(LoginRequiredMixin, RedirectView): class JiraAuthenticatedView(RedirectView): def get_redirect_url(self, *args, **kwargs): # Step 1. Use the request token in the session to build a new client. + consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET) 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") + try: + resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST") + except Exception as e: + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') + return '/' if resp['status'] != '200': + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') return '/' access_token = dict(urllib.parse.parse_qsl(content.decode())) @@ -122,6 +141,8 @@ class JiraAuthenticatedView(RedirectView): # redirect user to settings page to complete profile return url + +@method_decorator(login_required, name='dispatch') class UserListView(TemplateView): template_name = "account/user_list.html" diff --git a/tools/pharos-dashboard/src/api/urls.py b/tools/pharos-dashboard/src/api/urls.py index 5206ac7f..dfbe1ac9 100644 --- a/tools/pharos-dashboard/src/api/urls.py +++ b/tools/pharos-dashboard/src/api/urls.py @@ -35,4 +35,5 @@ router.register(r'bookings', BookingViewSet) urlpatterns = [ url(r'^', include(router.urls)), + url(r'^token$', GenerateTokenView.as_view(), name='generate_token'), ]
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/api/views.py b/tools/pharos-dashboard/src/api/views.py index 761ce6e7..2595e5ef 100644 --- a/tools/pharos-dashboard/src/api/views.py +++ b/tools/pharos-dashboard/src/api/views.py @@ -8,7 +8,12 @@ ############################################################################## +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect +from django.utils.decorators import method_decorator +from django.views import View from rest_framework import viewsets +from rest_framework.authtoken.models import Token from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer from booking.models import Booking @@ -30,4 +35,15 @@ class ServerViewSet(viewsets.ModelViewSet): class ResourceViewSet(viewsets.ModelViewSet): queryset = Resource.objects.all() serializer_class = ResourceSerializer - filter_fields = ('name',)
\ No newline at end of file + filter_fields = ('name',) + + +@method_decorator(login_required, name='dispatch') +class GenerateTokenView(View): + def get(self, request, *args, **kwargs): + user = self.request.user + token, created = Token.objects.get_or_create(user=user) + if not created: + token.delete() + Token.objects.create(user=user) + return redirect('account:settings') diff --git a/tools/pharos-dashboard/src/booking/admin.py b/tools/pharos-dashboard/src/booking/admin.py index 7a7f251a..d883be1c 100644 --- a/tools/pharos-dashboard/src/booking/admin.py +++ b/tools/pharos-dashboard/src/booking/admin.py @@ -10,6 +10,8 @@ from django.contrib import admin -from booking.models import Booking +from booking.models import * -admin.site.register(Booking)
\ No newline at end of file +admin.site.register(Booking) +admin.site.register(Installer) +admin.site.register(Scenario)
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/forms.py b/tools/pharos-dashboard/src/booking/forms.py index 02ac887a..2dbfacb0 100644 --- a/tools/pharos-dashboard/src/booking/forms.py +++ b/tools/pharos-dashboard/src/booking/forms.py @@ -10,10 +10,14 @@ import django.forms as forms +from booking.models import Installer, Scenario + class BookingForm(forms.Form): - fields = ['start', 'end', 'purpose'] + fields = ['start', 'end', 'purpose', 'installer', 'scenario'] start = forms.DateTimeField() end = forms.DateTimeField() - purpose = forms.CharField(max_length=300)
\ No newline at end of file + purpose = forms.CharField(max_length=300) + installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False) + scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False)
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py b/tools/pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py new file mode 100644 index 00000000..05684f0b --- /dev/null +++ b/tools/pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-10-05 12:02 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Installer', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=30)), + ], + ), + migrations.CreateModel( + name='Scenario', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=300)), + ], + ), + migrations.AddField( + model_name='booking', + name='installer', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='booking.Installer'), + ), + migrations.AddField( + model_name='booking', + name='scenario', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='booking.Scenario'), + ), + ] diff --git a/tools/pharos-dashboard/src/booking/models.py b/tools/pharos-dashboard/src/booking/models.py index 200dc830..0b3fa3b1 100644 --- a/tools/pharos-dashboard/src/booking/models.py +++ b/tools/pharos-dashboard/src/booking/models.py @@ -8,13 +8,28 @@ ############################################################################## +from django.conf import settings from django.contrib.auth.models import User from django.db import models from jira import JIRA from jira import JIRAError from dashboard.models import Resource -from django.conf import settings + + +class Installer(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=30) + + def __str__(self): + return self.name + +class Scenario(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=300) + + def __str__(self): + return self.name class Booking(models.Model): @@ -26,6 +41,8 @@ class Booking(models.Model): jira_issue_id = models.IntegerField(null=True) jira_issue_status = models.CharField(max_length=50) + installer = models.ForeignKey(Installer, models.DO_NOTHING, null=True) + scenario = models.ForeignKey(Scenario, models.DO_NOTHING, null=True) purpose = models.CharField(max_length=300, blank=False) class Meta: @@ -40,27 +57,12 @@ class Booking(models.Model): except JIRAError: return None - def authorization_test(self): - """ - Return True if self.user is authorized to make this booking. - """ - user = self.user - # Check if User is troubleshooter / admin - if user.has_perm('booking.add_booking'): - return True - # Check if User owns this resource - if user == self.resource.owner: - return True - return False - def save(self, *args, **kwargs): """ Save the booking if self.user is authorized and there is no overlapping booking. Raise PermissionError if the user is not authorized Raise ValueError if there is an overlapping booking """ - if not self.authorization_test(): - raise PermissionError('Insufficient permissions to save this booking.') if self.start >= self.end: raise ValueError('Start date is after end date') # conflicts end after booking starts, and start before booking ends diff --git a/tools/pharos-dashboard/src/booking/tests/test_models.py b/tools/pharos-dashboard/src/booking/tests/test_models.py index 612b35c0..b4cd1133 100644 --- a/tools/pharos-dashboard/src/booking/tests/test_models.py +++ b/tools/pharos-dashboard/src/booking/tests/test_models.py @@ -10,12 +10,11 @@ from datetime import timedelta -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import Permission from django.test import TestCase from django.utils import timezone -from account.models import UserProfile -from booking.models import Booking +from booking.models import * from dashboard.models import Resource from jenkins.models import JenkinsSlave @@ -37,7 +36,10 @@ class BookingModelTestCase(TestCase): self.user1 = User.objects.get(pk=self.user1.id) - def test_start__end(self): + self.installer = Installer.objects.create(name='TestInstaller') + self.scenario = Scenario.objects.create(name='TestScenario') + + def test_start_end(self): """ if the start of a booking is greater or equal then the end, saving should raise a ValueException @@ -88,20 +90,5 @@ class BookingModelTestCase(TestCase): 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)) + user=self.user1, resource=self.res2, scenario=self.scenario, + installer=self.installer))
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/tests/test_views.py b/tools/pharos-dashboard/src/booking/tests/test_views.py index e568c155..c1da013c 100644 --- a/tools/pharos-dashboard/src/booking/tests/test_views.py +++ b/tools/pharos-dashboard/src/booking/tests/test_views.py @@ -10,12 +10,10 @@ 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 import timezone from django.utils.encoding import force_text from registration.forms import User @@ -37,9 +35,6 @@ class BookingViewTestCase(TestCase): 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) @@ -78,4 +73,34 @@ class BookingViewTestCase(TestCase): self.assertIn('resource', response.context) + def test_booking_view(self): + start = timezone.now() + end = start + timedelta(weeks=1) + booking = Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1) + url = reverse('booking:detail', kwargs={'booking_id':0}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + url = reverse('booking:detail', kwargs={'booking_id':booking.id}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed('booking/booking_detail.html') + self.assertIn('booking', response.context) + + def test_booking_list_view(self): + start = timezone.now() - timedelta(weeks=2) + end = start + timedelta(weeks=1) + Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1) + + url = reverse('booking:list') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed('booking/booking_list.html') + self.assertTrue(len(response.context['bookings']) == 0) + + start = timezone.now() + end = start + timedelta(weeks=1) + Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1) + response = self.client.get(url) + self.assertTrue(len(response.context['bookings']) == 1)
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/urls.py b/tools/pharos-dashboard/src/booking/urls.py index 53206233..9e013164 100644 --- a/tools/pharos-dashboard/src/booking/urls.py +++ b/tools/pharos-dashboard/src/booking/urls.py @@ -34,4 +34,6 @@ urlpatterns = [ url(r'^detail/$', BookingView.as_view(), name='detail_prefix'), url(r'^detail/(?P<booking_id>[0-9]+)/$', BookingView.as_view(), name='detail'), + + url(r'^list/$', BookingListView.as_view(), name='list') ] diff --git a/tools/pharos-dashboard/src/booking/views.py b/tools/pharos-dashboard/src/booking/views.py index 2fe167af..6fdca0e0 100644 --- a/tools/pharos-dashboard/src/booking/views.py +++ b/tools/pharos-dashboard/src/booking/views.py @@ -8,14 +8,11 @@ ############################################################################## -from datetime import timedelta - from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.http import JsonResponse from django.shortcuts import get_object_or_404 -from django.shortcuts import redirect from django.urls import reverse from django.utils import timezone from django.views import View @@ -46,7 +43,7 @@ def create_jira_ticket(user, booking): booking.save() -class BookingFormView(LoginRequiredMixin, FormView): +class BookingFormView(FormView): template_name = "booking/booking_calendar.html" form_class = BookingForm @@ -64,22 +61,23 @@ class BookingFormView(LoginRequiredMixin, FormView): return reverse('booking:create', kwargs=self.kwargs) def form_valid(self, form): + if not self.request.user.is_authenticated: + messages.add_message(self.request, messages.ERROR, + 'You need to be logged in to book a Pod.') + return super(BookingFormView, self).form_invalid(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) + booking = Booking(start=form.cleaned_data['start'], + end=form.cleaned_data['end'], + purpose=form.cleaned_data['purpose'], + installer=form.cleaned_data['installer'], + scenario=form.cleaned_data['scenario'], + resource=self.resource, user=user) try: booking.save() except ValueError as err: messages.add_message(self.request, messages.ERROR, err) return super(BookingFormView, self).form_invalid(form) - except PermissionError as err: - messages.add_message(self.request, messages.ERROR, err) - return super(BookingFormView, self).form_invalid(form) try: if settings.CREATE_JIRA_TICKET: create_jira_ticket(user, booking) @@ -98,10 +96,20 @@ class BookingView(TemplateView): 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}) + context.update({'title': title, 'booking': booking}) + return context + + +class BookingListView(TemplateView): + template_name = "booking/booking_list.html" + + def get_context_data(self, **kwargs): + bookings = Booking.objects.filter(end__gte=timezone.now()) + title = 'Search Booking' + context = super(BookingListView, self).get_context_data(**kwargs) + context.update({'title': title, 'bookings': bookings}) return context @@ -109,5 +117,6 @@ class ResourceBookingsJSON(View): def get(self, request, *args, **kwargs): resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) bookings = resource.booking_set.get_queryset().values('id', 'start', 'end', 'purpose', - 'jira_issue_status') - return JsonResponse({'bookings': list(bookings)})
\ No newline at end of file + 'jira_issue_status', + 'installer__name', 'scenario__name') + return JsonResponse({'bookings': list(bookings)}) diff --git a/tools/pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py b/tools/pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py new file mode 100644 index 00000000..e47c3b13 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-10-05 12:02 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='resource', + name='vpn_users', + field=models.ManyToManyField(blank=True, related_name='user_vpn_users', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/tools/pharos-dashboard/src/dashboard/models.py b/tools/pharos-dashboard/src/dashboard/models.py index 050834ea..ec6fec76 100644 --- a/tools/pharos-dashboard/src/dashboard/models.py +++ b/tools/pharos-dashboard/src/dashboard/models.py @@ -10,9 +10,9 @@ from datetime import timedelta -from django.utils import timezone from django.contrib.auth.models import User from django.db import models +from django.utils import timezone from jenkins.models import JenkinsSlave @@ -23,7 +23,7 @@ class Resource(models.Model): description = models.CharField(max_length=300, blank=True, null=True) url = models.CharField(max_length=100, blank=True, null=True) owner = models.ForeignKey(User, related_name='user_lab_owner', null=True) - vpn_users = models.ManyToManyField(User, related_name='user_vpn_users') + vpn_users = models.ManyToManyField(User, related_name='user_vpn_users', blank=True) slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True) def get_booking_utilization(self, weeks): diff --git a/tools/pharos-dashboard/src/dashboard/tasks.py b/tools/pharos-dashboard/src/dashboard/tasks.py index 4c09bf90..c5ef5054 100644 --- a/tools/pharos-dashboard/src/dashboard/tasks.py +++ b/tools/pharos-dashboard/src/dashboard/tasks.py @@ -8,8 +8,9 @@ ############################################################################## -from celery import shared_task from datetime import timedelta + +from celery import shared_task from django.utils import timezone from jenkins.models import JenkinsStatistic diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py index 70208436..9a97c1d5 100644 --- a/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py +++ b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py @@ -8,9 +8,8 @@ ############################################################################## -from django.template.defaultfilters import register - from django.conf import settings +from django.template.defaultfilters import register @register.filter diff --git a/tools/pharos-dashboard/src/dashboard/tests/__init__.py b/tools/pharos-dashboard/src/dashboard/tests/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tests/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/tests/test_models.py b/tools/pharos-dashboard/src/dashboard/tests/test_models.py new file mode 100644 index 00000000..3a3aeab1 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tests/test_models.py @@ -0,0 +1,69 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta +from math import ceil, floor + +from django.test import TestCase +from django.utils import timezone + +from booking.models import * +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class ResourceModelTestCase(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) + + def test_booking_utilization(self): + utilization = self.res1.get_booking_utilization(1) + self.assertTrue(utilization['booked_seconds'] == 0) + self.assertTrue(utilization['available_seconds'] == timedelta(weeks=1).total_seconds()) + + start = timezone.now() + timedelta(days=1) + end = start + timedelta(days=1) + booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1, + user=self.owner) + + utilization = self.res1.get_booking_utilization(1) + booked_seconds = timedelta(days=1).total_seconds() + self.assertEqual(utilization['booked_seconds'], booked_seconds) + + utilization = self.res1.get_booking_utilization(-1) + self.assertEqual(utilization['booked_seconds'], 0) + + booking.delete() + start = timezone.now() - timedelta(days=1) + end = start + timedelta(days=2) + booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1, + user=self.owner) + booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds'] + # use ceil because a fraction of the booked time has already passed now + booked_seconds = ceil(booked_seconds) + self.assertEqual(booked_seconds, timedelta(days=1).total_seconds()) + + booking.delete() + start = timezone.now() + timedelta(days=6) + end = start + timedelta(days=2) + booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1, + user=self.owner) + booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds'] + booked_seconds = floor(booked_seconds) + self.assertEqual(booked_seconds, timedelta(days=1).total_seconds()) + + + + + diff --git a/tools/pharos-dashboard/src/dashboard/tests/test_views.py b/tools/pharos-dashboard/src/dashboard/tests/test_views.py new file mode 100644 index 00000000..f5e17c2a --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tests/test_views.py @@ -0,0 +1,75 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.test import TestCase +from django.urls import reverse + +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class DashboardViewTestCase(TestCase): + def setUp(self): + self.slave_active = JenkinsSlave.objects.create(name='slave_active', url='x', active=True) + self.slave_inactive = JenkinsSlave.objects.create(name='slave_inactive', url='x', + active=False) + self.res_active = Resource.objects.create(name='res_active', slave=self.slave_active, + description='x', url='x') + self.res_inactive = Resource.objects.create(name='res_inactive', slave=self.slave_inactive, + description='x', url='x') + + def test_booking_utilization_json(self): + url = reverse('dashboard:booking_utilization', kwargs={'resource_id': 0, 'weeks': 0}) + self.assertEqual(self.client.get(url).status_code, 404) + + url = reverse('dashboard:booking_utilization', kwargs={'resource_id': self.res_active.id, + 'weeks': 0}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'data') + + def test_jenkins_utilization_json(self): + url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': 0, 'weeks': 0}) + self.assertEqual(self.client.get(url).status_code, 404) + + url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': self.res_active.id, + 'weeks': 0}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'data') + + def test_jenkins_slaves_view(self): + url = reverse('dashboard:jenkins_slaves') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertIn(self.slave_active, response.context['slaves']) + self.assertNotIn(self.slave_inactive, response.context['slaves']) + + def test_ci_pods_view(self): + url = reverse('dashboard:ci_pods') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['ci_pods']), 0) + + self.slave_active.ci_slave = True + self.slave_inactive.ci_slave = True + self.slave_active.save() + self.slave_inactive.save() + + response = self.client.get(url) + self.assertIn(self.res_active, response.context['ci_pods']) + self.assertNotIn(self.res_inactive, response.context['ci_pods']) + + def test_dev_pods_view(self): + url = reverse('dashboard:dev_pods') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['dev_pods']), 0) + diff --git a/tools/pharos-dashboard/src/dashboard/urls.py b/tools/pharos-dashboard/src/dashboard/urls.py index f04f5ca9..609e5d6f 100644 --- a/tools/pharos-dashboard/src/dashboard/urls.py +++ b/tools/pharos-dashboard/src/dashboard/urls.py @@ -24,6 +24,7 @@ Including another URLconf 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url + from dashboard.views import * urlpatterns = [ diff --git a/tools/pharos-dashboard/src/dashboard/views.py b/tools/pharos-dashboard/src/dashboard/views.py index 022a4af0..1848844b 100644 --- a/tools/pharos-dashboard/src/dashboard/views.py +++ b/tools/pharos-dashboard/src/dashboard/views.py @@ -25,7 +25,7 @@ class JenkinsSlavesView(TemplateView): template_name = "dashboard/jenkins_slaves.html" def get_context_data(self, **kwargs): - slaves = JenkinsSlave.objects.all() + slaves = JenkinsSlave.objects.filter(active=True) context = super(JenkinsSlavesView, self).get_context_data(**kwargs) context.update({'title': "Jenkins Slaves", 'slaves': slaves}) return context @@ -35,7 +35,7 @@ class CIPodsView(TemplateView): template_name = "dashboard/ci_pods.html" def get_context_data(self, **kwargs): - ci_pods = Resource.objects.filter(slave__ci_slave=True) + ci_pods = Resource.objects.filter(slave__ci_slave=True, slave__active=True) context = super(CIPodsView, self).get_context_data(**kwargs) context.update({'title': "CI Pods", 'ci_pods': ci_pods}) return context @@ -45,7 +45,7 @@ class DevelopmentPodsView(TemplateView): template_name = "dashboard/dev_pods.html" def get_context_data(self, **kwargs): - resources = Resource.objects.filter(slave__dev_pod=True) + resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True) bookings = Booking.objects.filter(start__lte=timezone.now()) bookings = bookings.filter(end__gt=timezone.now()) @@ -55,7 +55,7 @@ class DevelopmentPodsView(TemplateView): booking_utilization = resource.get_booking_utilization(weeks=4) total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds'] try: - utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) / + utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) / total * 100) except (ValueError, ZeroDivisionError): return "" @@ -88,7 +88,7 @@ class LabOwnerView(TemplateView): template_name = "dashboard/resource_all.html" def get_context_data(self, **kwargs): - resources = Resource.objects.filter(slave__dev_pod=True) + resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True) pods = [] for resource in resources: utilization = resource.slave.get_utilization(timedelta(days=7)) diff --git a/tools/pharos-dashboard/src/jenkins/adapter.py b/tools/pharos-dashboard/src/jenkins/adapter.py index ff0508d9..edf502f6 100644 --- a/tools/pharos-dashboard/src/jenkins/adapter.py +++ b/tools/pharos-dashboard/src/jenkins/adapter.py @@ -9,8 +9,8 @@ import logging - import re + import requests from django.core.cache import cache diff --git a/tools/pharos-dashboard/src/jenkins/models.py b/tools/pharos-dashboard/src/jenkins/models.py index 0875bba5..8254ff39 100644 --- a/tools/pharos-dashboard/src/jenkins/models.py +++ b/tools/pharos-dashboard/src/jenkins/models.py @@ -29,6 +29,8 @@ class JenkinsSlave(models.Model): last_job_installer = models.CharField(max_length=50, default='') last_job_result = models.CharField(max_length=30, default='') + active = models.BooleanField(default=False) + def get_utilization(self, timedelta): """ Return a dictionary containing the count of idle, online and offline measurements in the time from diff --git a/tools/pharos-dashboard/src/jenkins/tasks.py b/tools/pharos-dashboard/src/jenkins/tasks.py index 7c037827..ea986c1f 100644 --- a/tools/pharos-dashboard/src/jenkins/tasks.py +++ b/tools/pharos-dashboard/src/jenkins/tasks.py @@ -10,6 +10,7 @@ from celery import shared_task +from dashboard.models import Resource from jenkins.models import JenkinsSlave, JenkinsStatistic from .adapter import * @@ -20,14 +21,28 @@ def sync_jenkins(): def update_jenkins_slaves(): + JenkinsSlave.objects.all().update(active=False) + 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.active = True 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) + # if this is a new slave and a pod, check if there is a resource for it, create one if not + if created and 'pod' in slave['displayName']: + # parse resource name from slave name + # naming example: orange-pod1, resource name: Orange POD 1 + tokens = slave['displayName'].split('-') + name = tokens[0].capitalize() + ' POD '# company name + name += tokens[1][3:] # remove 'pod' + resource, created = Resource.objects.get_or_create(name=name) + resource.slave = jenkins_slave + resource.save() + last_job = get_jenkins_job(jenkins_slave.name) if last_job is not None: last_job = parse_job(last_job) diff --git a/tools/pharos-dashboard/src/jenkins/tests.py b/tools/pharos-dashboard/src/jenkins/tests.py index 4f350d20..3723cd38 100644 --- a/tools/pharos-dashboard/src/jenkins/tests.py +++ b/tools/pharos-dashboard/src/jenkins/tests.py @@ -8,9 +8,11 @@ ############################################################################## +from datetime import timedelta from unittest import TestCase import jenkins.adapter as jenkins +from jenkins.models import * # Tests that the data we get with the jenkinsadapter contains all the @@ -28,12 +30,27 @@ class JenkinsAdapterTestCase(TestCase): self.assertTrue('idle' in slave) self.assertTrue('offline' in slave) + def test_get_slave(self): + slaves = jenkins.get_all_slaves() + self.assertEqual(slaves[0], jenkins.get_slave(slaves[0]['displayName'])) + self.assertEqual({}, jenkins.get_slave('098f6bcd4621d373cade4e832627b4f6')) + def test_get_ci_slaves(self): slaves = jenkins.get_ci_slaves() self.assertTrue(len(slaves) > 0) for slave in slaves: self.assertTrue('nodeName' in slave) + def test_get_jenkins_job(self): + slaves = jenkins.get_ci_slaves() + job = None + for slave in slaves: + job = jenkins.get_jenkins_job(slave['nodeName']) + if job is not None: + break + # We need to test at least one job + self.assertNotEqual(job, None) + def test_get_all_jobs(self): jobs = jenkins.get_all_jobs() lastBuild = False @@ -50,3 +67,63 @@ class JenkinsAdapterTestCase(TestCase): self.assertTrue('timestamp' in job['lastBuild']) self.assertTrue('builtOn' in job['lastBuild']) self.assertTrue(lastBuild) + + def test_parse_job(self): + job = { + "displayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado", + "url": "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/", + "lastBuild": { + "building": False, + "fullDisplayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado #37", + "result": "SUCCESS", + "timestamp": 1476283629917, + "builtOn": "lf-pod1" + } + } + + job = jenkins.parse_job(job) + self.assertEqual(job['scenario'], 'os-nosdn-fdio-noha') + self.assertEqual(job['installer'], 'apex') + self.assertEqual(job['branch'], 'colorado') + self.assertEqual(job['result'], 'SUCCESS') + self.assertEqual(job['building'], False) + self.assertEqual(job['url'], + "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/") + self.assertEqual(job['name'], + 'apex-deploy-baremetal-os-nosdn-fdio-noha-colorado') + + def test_get_slave_status(self): + slave = { + 'offline': True, + 'idle': False + } + self.assertEqual(jenkins.get_slave_status(slave), 'offline') + slave = { + 'offline': False, + 'idle': False + } + self.assertEqual(jenkins.get_slave_status(slave), 'online') + slave = { + 'offline': False, + 'idle': True + } + self.assertEqual(jenkins.get_slave_status(slave), 'online / idle') + + +class JenkinsModelTestCase(TestCase): + def test_get_utilization(self): + jenkins_slave = JenkinsSlave.objects.create(name='test', status='offline', url='') + utilization = jenkins_slave.get_utilization(timedelta(weeks=1)) + self.assertEqual(utilization['idle'], 0) + self.assertEqual(utilization['offline'], 0) + self.assertEqual(utilization['online'], 0) + + for i in range(10): + JenkinsStatistic.objects.create(slave=jenkins_slave, + offline=True, idle=True, + online=True) + + utilization = jenkins_slave.get_utilization(timedelta(weeks=1)) + self.assertEqual(utilization['idle'], 10) + self.assertEqual(utilization['offline'], 10) + self.assertEqual(utilization['online'], 10) diff --git a/tools/pharos-dashboard/src/notification/models.py b/tools/pharos-dashboard/src/notification/models.py index 2d199181..0ee275dd 100644 --- a/tools/pharos-dashboard/src/notification/models.py +++ b/tools/pharos-dashboard/src/notification/models.py @@ -26,7 +26,8 @@ class BookingNotification(models.Model): } def save(self, *args, **kwargs): - notifications = self.booking.bookingnotification_set.filter(type=self.type) - if notifications.count() > 1: + notifications = self.booking.bookingnotification_set.filter(type=self.type).exclude( + id=self.id) + if notifications.count() > 0: raise ValueError('Doubled Notification') return super(BookingNotification, self).save(*args, **kwargs)
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/tasks.py b/tools/pharos-dashboard/src/notification/tasks.py index 61ab14af..4173433c 100644 --- a/tools/pharos-dashboard/src/notification/tasks.py +++ b/tools/pharos-dashboard/src/notification/tasks.py @@ -8,9 +8,9 @@ ############################################################################## -from celery import shared_task from datetime import timedelta +from celery import shared_task from django.conf import settings from django.utils import timezone diff --git a/tools/pharos-dashboard/src/notification/tests.py b/tools/pharos-dashboard/src/notification/tests.py new file mode 100644 index 00000000..9df9aa60 --- /dev/null +++ b/tools/pharos-dashboard/src/notification/tests.py @@ -0,0 +1,41 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta +from unittest import TestCase + +from django.contrib.auth.models import User +from django.utils import timezone + +from booking.models import Booking +from dashboard.models import Resource +from jenkins.models import JenkinsSlave +from notification.models import * + + +class JenkinsModelTestCase(TestCase): + def setUp(self): + self.slave = JenkinsSlave.objects.create(name='test1', url='test') + self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', + url='x') + self.user1 = User.objects.create(username='user1') + + start = timezone.now() + end = start + timedelta(days=1) + self.booking = Booking.objects.create(start=start, end=end, purpose='test', + resource=self.res1, user=self.user1) + + def test_booking_notification(self): + BookingNotification.objects.create(type='test', booking=self.booking, + submit_time=timezone.now()) + + self.assertRaises(ValueError, BookingNotification.objects.create, type='test', + booking=self.booking, + submit_time=timezone.now()) diff --git a/tools/pharos-dashboard/src/static/js/booking-calendar.js b/tools/pharos-dashboard/src/static/js/booking-calendar.js index 9cb0f32f..f634293e 100644 --- a/tools/pharos-dashboard/src/static/js/booking-calendar.js +++ b/tools/pharos-dashboard/src/static/js/booking-calendar.js @@ -1,11 +1,11 @@ /***************************************************************************** -* Copyright (c) 2016 Max Breitenfeldt and others. -* -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Apache License, Version 2.0 -* which accompanies this distribution, and is available at -* http://www.apache.org/licenses/LICENSE-2.0 -*****************************************************************************/ + * Copyright (c) 2016 Max Breitenfeldt and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Apache License, Version 2.0 + * which accompanies this distribution, and is available at + * http://www.apache.org/licenses/LICENSE-2.0 + *****************************************************************************/ function parseCalendarEvents(bookings) { @@ -14,9 +14,21 @@ function parseCalendarEvents(bookings) { // convert ISO 8601 timestring to moment, needed for timezone handling start = moment(bookings[i]['start']); end = moment(bookings[i]['end']); + + installer = bookings[i]['installer__name']; + if (installer === null) { + installer = ''; + } + + scenario = bookings[i]['scenario__name']; + if (scenario === null) { + scenario = ''; + } + title = bookings[i]['purpose'] + ' ' + installer + ' ' + scenario; + event = { id: bookings[i]['id'], - title: bookings[i]['purpose'], + title: title, start: start, end: end, }; diff --git a/tools/pharos-dashboard/src/templates/account/user_list.html b/tools/pharos-dashboard/src/templates/account/user_list.html index c2b81938..f18e1618 100644 --- a/tools/pharos-dashboard/src/templates/account/user_list.html +++ b/tools/pharos-dashboard/src/templates/account/user_list.html @@ -5,6 +5,8 @@ <thead> <tr> <th>Username</th> + <th>Full Name</th> + <th>Email</th> <th>Company</th> <th>SSH Key</th> <th>GPG Key</th> @@ -17,17 +19,23 @@ {{ user.username }} </td> <td> + {{ user.userprofile.full_name }} + </td> + <td> + {{ user.email }} + </td> + <td> {{ user.userprofile.company }} </td> <td> - {% if user.userprofile.ssh_public_key %} - <a href={{ user.userprofile.ssh_public_key.url }}>SSH</a> - {% endif %} + {% if user.userprofile.ssh_public_key %} + <a href={{ user.userprofile.ssh_public_key.url }}>SSH</a> + {% endif %} </td> <td> - {% if user.userprofile.pgp_public_key %} - <a href={{ user.userprofile.pgp_public_key.url }}>GPG</a> - {% endif %} + {% if user.userprofile.pgp_public_key %} + <a href={{ user.userprofile.pgp_public_key.url }}>GPG</a> + {% endif %} </td> </tr> {% endfor %} diff --git a/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html b/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html index 542ea81e..f4bb7b55 100644 --- a/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html +++ b/tools/pharos-dashboard/src/templates/account/userprofile_update_form.html @@ -16,9 +16,17 @@ <form enctype="multipart/form-data" method="post"> {% csrf_token %} {% bootstrap_form form %} + <p><b>API Token</b> + <a href="{% url 'generate_token' %}" class="btn btn-default"> + Generate + </a> + </p> + <p style="word-wrap: break-word;">{{ token.key }}</p> + + <p></p> {% buttons %} <button type="submit" class="btn btn btn-success"> - Save + Save Profile </button> {% endbuttons %} </form> diff --git a/tools/pharos-dashboard/src/templates/base.html b/tools/pharos-dashboard/src/templates/base.html index 5bb55473..2ce22a3a 100644 --- a/tools/pharos-dashboard/src/templates/base.html +++ b/tools/pharos-dashboard/src/templates/base.html @@ -67,8 +67,15 @@ Slaves</a> </li> <li> + {% if user.is_authenticated %} <a href="{% url 'account:users' %}"><i - class="fa fa-fw"></i>Users + class="fa fa-fw"></i>User List + </a> + {% endif %} + </li> + <li> + <a href="{% url 'booking:list' %}"><i + class="fa fa-fw"></i>Booking List </a> </li> <li> diff --git a/tools/pharos-dashboard/src/templates/booking/booking_calendar.html b/tools/pharos-dashboard/src/templates/booking/booking_calendar.html index de3e3b3d..4644e855 100644 --- a/tools/pharos-dashboard/src/templates/booking/booking_calendar.html +++ b/tools/pharos-dashboard/src/templates/booking/booking_calendar.html @@ -1,4 +1,4 @@ -{% extends "dashboard/table.html" %} +{% extends "base.html" %} {% load staticfiles %} {% load bootstrap3 %} @@ -33,26 +33,34 @@ <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" action="" class="form" id="bookingform"> - {% csrf_token %} + {% if user.is_authenticated %} + <div id="booking_form_div"> + {% bootstrap_form_errors form type='non_fields' %} + <form method="post" action="" class="form" id="bookingform"> + {% csrf_token %} - <div class='input-group' id='starttimepicker'> - {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %} - </div> - <div class='input-group' id='endtimepicker'> - {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %} - </div> - {% bootstrap_field form.purpose %} - - {% buttons %} - <button type="submit" class="btn btn btn-success"> - Book - </button> - {% endbuttons %} - </form> - </div> + <div class='input-group' id='starttimepicker'> + {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %} + </div> + <div class='input-group' id='endtimepicker'> + {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %} + </div> + {% bootstrap_field form.purpose %} + {% bootstrap_field form.installer %} + {% bootstrap_field form.scenario %} + {% buttons %} + <button type="submit" class="btn btn btn-success"> + Book + </button> + {% endbuttons %} + </form> + </div> + {% else %} + <p>Please + <a href="{% url 'account:login' %}"> + login with Jira</a> + to book this Pod</p> + {% endif %} </div> </div> </div> @@ -69,7 +77,8 @@ <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> + <button type="button" class="btn btn-default" data-dismiss="modal">Close + </button> </div> </div> diff --git a/tools/pharos-dashboard/src/templates/booking/booking_detail.html b/tools/pharos-dashboard/src/templates/booking/booking_detail.html index d3f47538..4b016b29 100644 --- a/tools/pharos-dashboard/src/templates/booking/booking_detail.html +++ b/tools/pharos-dashboard/src/templates/booking/booking_detail.html @@ -19,8 +19,8 @@ <b>Purpose: </b> {{ booking.purpose }} </p> <p> - <b>Jira: </b> - <a href="{{ jira_issue | jira_issue_url }}"> - {{ jira_issue }} - </a> + <b>Installer: </b> {{ booking.installer }} +</p> +<p> + <b>Scenario: </b> {{ booking.scenario }} </p>
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/booking/booking_list.html b/tools/pharos-dashboard/src/templates/booking/booking_list.html new file mode 100644 index 00000000..f2991e4c --- /dev/null +++ b/tools/pharos-dashboard/src/templates/booking/booking_list.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block extrahead %} + <!-- DataTables CSS --> + <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + rel="stylesheet"> + + <!-- DataTables Responsive CSS --> + <link href="{% static "bower_components/datatables-responsive/css/dataTables.responsive.css" %}" + rel="stylesheet"> +{% endblock extrahead %} + +{% block content %} + <div class="row"> + <div class="col-lg-12"> + <div class="panel panel-default"> + <div class="panel-body"> + <div class="dataTables_wrapper"> + <table class="table table-striped table-bordered table-hover" id="table" + cellspacing="0" + width="100%"> + {% include "booking/booking_table.html" %} + </table> + </div> + <!-- /.table-responsive --> + </div> + <!-- /.panel-body --> + </div> + <!-- /.panel --> + </div> + <!-- /.col-lg-12 --> + </div> +{% endblock content %} + +{% block extrajs %} + <!-- DataTables JavaScript --> + <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + rel="stylesheet"> + + + <script src={% static "bower_components/datatables/media/js/jquery.dataTables.min.js" %}></script> + <script src={% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js" %}></script> + + <script type="text/javascript"> + $(document).ready(function () { + $('#table').DataTable({}); + }); + </script> +{% endblock extrajs %}
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/templates/booking/booking_table.html b/tools/pharos-dashboard/src/templates/booking/booking_table.html index 216eaf57..655b0131 100644 --- a/tools/pharos-dashboard/src/templates/booking/booking_table.html +++ b/tools/pharos-dashboard/src/templates/booking/booking_table.html @@ -7,7 +7,8 @@ <th>Purpose</th> <th>Start</th> <th>End</th> - <th>Jira</th> + <th>Installer</th> + <th>Scenario</th> </tr> </thead> <tbody> @@ -25,8 +26,11 @@ <td> {{ booking.end }} </td> - <td><a target='_blank' - href={{ booking.get_jira_issue | jira_issue_url }}>{{ booking.get_jira_issue }}</a> + <td> + {{ booking.installer }} + </td> + <td> + {{ booking.scenario }} </td> </tr> {% endfor %} diff --git a/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html b/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html index 657d5656..e0b29bd9 100644 --- a/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html +++ b/tools/pharos-dashboard/src/templates/dashboard/resource_detail.html @@ -106,6 +106,7 @@ </p> <p> <b>Email: </b> + {{ resource.owner.email }} </p> <p> <a href="{% url 'booking:create' resource_id=resource.id %}" class="btn |