Merge "fix image path"
authorMorgan Richomme <morgan.richomme@orange.com>
Tue, 13 Sep 2016 16:32:02 +0000 (16:32 +0000)
committerGerrit Code Review <gerrit@172.30.200.206>
Tue, 13 Sep 2016 16:32:02 +0000 (16:32 +0000)
23 files changed:
tools/pharos-dashboard/.gitignore
tools/pharos-dashboard/account/jira_util.py
tools/pharos-dashboard/account/urls.py
tools/pharos-dashboard/account/views.py
tools/pharos-dashboard/booking/models.py
tools/pharos-dashboard/booking/urls.py
tools/pharos-dashboard/booking/views.py
tools/pharos-dashboard/dashboard/models.py
tools/pharos-dashboard/dashboard/templatetags/jira_filters.py
tools/pharos-dashboard/dashboard/urls.py
tools/pharos-dashboard/dashboard/views.py
tools/pharos-dashboard/pharos_dashboard/urls.py
tools/pharos-dashboard/static/bower.json
tools/pharos-dashboard/static/js/flot-pie-chart.js [new file with mode: 0644]
tools/pharos-dashboard/templates/account/user_list.html [new file with mode: 0644]
tools/pharos-dashboard/templates/base.html
tools/pharos-dashboard/templates/dashboard/ci_pods.html
tools/pharos-dashboard/templates/dashboard/dev_pods.html
tools/pharos-dashboard/templates/dashboard/resource.html
tools/pharos-dashboard/templates/dashboard/resource_all.html
tools/pharos-dashboard/templates/dashboard/resource_detail.html
tools/pharos-dashboard/templates/dashboard/server_table.html
tools/pharos-dashboard/templates/layout.html

index 9eb1cfd..2b73909 100644 (file)
@@ -20,6 +20,7 @@ coverage.xml
 *.log
 *.pot
 migrations/
+settings.py
 
 # KDE:
 .directory
index bd07ff3..c066a68 100644 (file)
@@ -5,7 +5,7 @@ import oauth2 as oauth
 from jira import JIRA
 from tlslite.utils import keyfactory
 
-from pharos_dashboard import settings
+from django.conf import settings
 
 
 class SignatureMethod_RSA_SHA1(oauth.SignatureMethod):
index b837814..7289da6 100644 (file)
@@ -21,5 +21,6 @@ urlpatterns = [
     url(r'^settings/', AccountSettingsView.as_view(), name='settings'),
     url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'),
     url(r'^login/$', JiraLoginView.as_view(), name='login'),
-    url(r'^logout/$', JiraLogoutView.as_view(), name='logout')
+    url(r'^logout/$', JiraLogoutView.as_view(), name='logout'),
+    url(r'^users/$', UserListView.as_view(), name='users'),
 ]
index 7d2c9bd..fd1762e 100644 (file)
@@ -10,13 +10,14 @@ from django.contrib.auth.models import User
 from django.urls import reverse
 from django.utils.decorators import method_decorator
 from django.views.generic import RedirectView
+from django.views.generic import TemplateView
 from django.views.generic import UpdateView
 from jira import JIRA
 
 from account.forms import AccountSettingsForm
 from account.jira_util import SignatureMethod_RSA_SHA1
 from account.models import UserProfile
-from pharos_dashboard import settings
+from django.conf import settings
 
 consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
 
@@ -50,7 +51,8 @@ class JiraLoginView(RedirectView):
         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
 
 
@@ -109,3 +111,12 @@ class JiraAuthenticatedView(RedirectView):
         login(self.request, user)
         # redirect user to settings page to complete profile
         return url
+
+class UserListView(TemplateView):
+    template_name = "account/user_list.html"
+
+    def get_context_data(self, **kwargs):
+        users = User.objects.all()
+        context = super(UserListView, self).get_context_data(**kwargs)
+        context.update({'title': "Dashboard Users", 'users': users})
+        return context
index 4be8cca..e772fb5 100644 (file)
@@ -1,10 +1,10 @@
 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 pharos_dashboard import settings
-
+from django.conf import settings
 
 class Booking(models.Model):
     id = models.AutoField(primary_key=True)
@@ -13,6 +13,7 @@ class Booking(models.Model):
     start = models.DateTimeField()
     end = models.DateTimeField()
     jira_issue_id = models.IntegerField(null=True)
+    jira_issue_status = models.CharField(max_length=50)
 
     purpose = models.CharField(max_length=300, blank=False)
 
@@ -20,9 +21,13 @@ class Booking(models.Model):
         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
+        try:
+            jira = JIRA(server=settings.JIRA_URL,
+                        basic_auth=(settings.JIRA_USER_NAME, settings.JIRA_USER_PASSWORD))
+            issue = jira.issue(self.jira_issue_id)
+            return issue
+        except JIRAError:
+            return None
 
     def authorization_test(self):
         """
index f6429da..bdcd52d 100644 (file)
@@ -21,6 +21,7 @@ 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'),
 ]
index fde8d81..d0b2aef 100644 (file)
@@ -1,9 +1,13 @@
+from datetime import timedelta
+
+from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.http import JsonResponse
 from django.shortcuts import get_object_or_404
 from django.shortcuts import redirect
 from django.urls import reverse
+from django.utils import timezone
 from django.views import View
 from django.views.generic import FormView
 from django.views.generic import TemplateView
@@ -67,7 +71,8 @@ class BookingFormView(LoginRequiredMixin, FormView):
             messages.add_message(self.request, messages.ERROR, err)
             return super(BookingFormView, self).form_invalid(form)
         try:
-            create_jira_ticket(user, booking)
+            if settings.CREATE_JIRA_TICKET:
+                create_jira_ticket(user, booking)
         except JIRAError:
             messages.add_message(self.request, messages.ERROR, 'Failed to create Jira Ticket. '
                                                                'Please check your Jira '
@@ -93,5 +98,6 @@ class BookingView(TemplateView):
 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)})
+        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
index d645cd5..734da38 100644 (file)
@@ -1,3 +1,6 @@
+from datetime import timedelta
+
+from django.utils import timezone
 from django.contrib.auth.models import User
 from django.db import models
 
@@ -9,9 +12,42 @@ class Resource(models.Model):
     name = models.CharField(max_length=100, unique=True)
     description = models.CharField(max_length=300, blank=True, null=True)
     url = models.CharField(max_length=100, blank=True, null=True)
-    owner = models.ForeignKey(User)
+    owner = models.ForeignKey(User, related_name='user_lab_owner', null=True)
+    vpn_users = models.ManyToManyField(User, related_name='user_vpn_users')
     slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True)
 
+    def get_booking_utilization(self, weeks):
+        """
+        Return a dictionary containing the count of booked and free seconds for a resource in the
+        range [now,now + weeks] if weeks is positive,
+        or [now-weeks, now] if weeks is negative
+        """
+
+        length = timedelta(weeks=abs(weeks))
+        now = timezone.now()
+
+        start = now
+        end = now + length
+        if weeks < 0:
+            start = now - length
+            end = now
+
+        bookings = self.booking_set.filter(start__lt=start + length, end__gt=start)
+
+        booked_seconds = 0
+        for booking in bookings:
+            booking_start = booking.start
+            booking_end = booking.end
+            if booking_start < start:
+                booking_start = start
+            if booking_end > end:
+                booking_end = start + length
+            total = booking_end - booking_start
+            booked_seconds += total.total_seconds()
+
+        return {'booked_seconds': booked_seconds,
+                'available_seconds': length.total_seconds() - booked_seconds}
+
     class Meta:
         db_table = 'resource'
 
index d9c2761..1be0600 100644 (file)
@@ -1,6 +1,6 @@
 from django.template.defaultfilters import register
 
-from pharos_dashboard import settings
+from django.conf import settings
 
 
 @register.filter
index 809204c..baa2d63 100644 (file)
@@ -23,6 +23,9 @@ urlpatterns = [
     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'),
-
+    url(r'^resource/(?P<resource_id>[0-9]+)/booking_utilization/(?P<weeks>-?\d+)/$',
+        BookingUtilizationJSON.as_view(), name='booking_utilization'),
+    url(r'^resource/(?P<resource_id>[0-9]+)/jenkins_utilization/(?P<weeks>-?\d+)/$',
+        JenkinsUtilizationJSON.as_view(), name='jenkins_utilization'),
     url(r'^$', DevelopmentPodsView.as_view(), name="index"),
 ]
index ef1845c..0eddc13 100644 (file)
@@ -1,7 +1,9 @@
 from datetime import timedelta
 
+from django.http import JsonResponse
 from django.shortcuts import get_object_or_404
 from django.utils import timezone
+from django.views import View
 from django.views.generic import TemplateView
 
 from booking.models import Booking
@@ -40,10 +42,18 @@ class DevelopmentPodsView(TemplateView):
 
         dev_pods = []
         for resource in resources:
-            dev_pod = (resource, None)
+            booking_utilization = resource.get_booking_utilization(weeks=4)
+            total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds']
+            try:
+               utilization_percentage =  "%d%%" % (float(booking_utilization['booked_seconds']) /
+                                                   total * 100)
+            except (ValueError, ZeroDivisionError):
+                return ""
+
+            dev_pod = (resource, None, utilization_percentage)
             for booking in bookings:
                 if booking.resource == resource:
-                    dev_pod = (resource, booking)
+                    dev_pod = (resource, booking, utilization_percentage)
             dev_pods.append(dev_pod)
 
         context = super(DevelopmentPodsView, self).get_context_data(**kwargs)
@@ -59,7 +69,8 @@ class ResourceView(TemplateView):
         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})
+        context.update({'title': str(resource), 'resource': resource, 'utilization': utilization,
+                        'bookings': bookings})
         return context
 
 
@@ -76,3 +87,47 @@ class LabOwnerView(TemplateView):
         context = super(LabOwnerView, self).get_context_data(**kwargs)
         context.update({'title': "Overview", 'pods': pods})
         return context
+
+
+class BookingUtilizationJSON(View):
+    def get(self, request, *args, **kwargs):
+        resource = get_object_or_404(Resource, id=kwargs['resource_id'])
+        utilization = resource.get_booking_utilization(int(kwargs['weeks']))
+        utilization = [
+            {
+                'label': 'Booked',
+                'data': utilization['booked_seconds'],
+                'color': '#d9534f'
+            },
+            {
+                'label': 'Available',
+                'data': utilization['available_seconds'],
+                'color': '#5cb85c'
+            },
+        ]
+        return JsonResponse({'data': utilization})
+
+
+class JenkinsUtilizationJSON(View):
+    def get(self, request, *args, **kwargs):
+        resource = get_object_or_404(Resource, id=kwargs['resource_id'])
+        weeks = int(kwargs['weeks'])
+        utilization = resource.slave.get_utilization(timedelta(weeks=weeks))
+        utilization = [
+            {
+                'label': 'Offline',
+                'data': utilization['offline'],
+                'color': '#d9534f'
+            },
+            {
+                'label': 'Online',
+                'data': utilization['online'],
+                'color': '#5cb85c'
+            },
+            {
+                'label': 'Idle',
+                'data': utilization['idle'],
+                'color': '#5bc0de'
+            },
+        ]
+        return JsonResponse({'data': utilization})
index 26ab367..d8bf560 100644 (file)
@@ -13,13 +13,18 @@ Including another URLconf
     1. Import the include() function: from django.conf.urls import url, include
     2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
 """
+from django.conf import settings
 from django.conf.urls import url, include
+from django.conf.urls.static import static
 from django.contrib import admin
 
 urlpatterns = [
     url(r'^', include('dashboard.urls', namespace='dashboard')),
     url(r'^booking/', include('booking.urls', namespace='booking')),
-    url(r'^account/', include('account.urls', namespace='account')),
+    url(r'^accounts/', include('account.urls', namespace='account')),
 
     url(r'^admin/', admin.site.urls),
-]
\ No newline at end of file
+]
+
+if settings.DEBUG is True:
+    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
\ No newline at end of file
index 7840621..f473747 100644 (file)
@@ -19,6 +19,6 @@
     "eonasdan-bootstrap-datetimepicker": "^4.17.37",
     "fullcalendar": "^2.9.0",
     "jquery-migrate": "^3.0.0",
-    "startbootstrap-sb-admin-2": "^1.0.9"
+    "startbootstrap-sb-admin-2-blackrockdigital": "^3.3.7"
   }
 }
diff --git a/tools/pharos-dashboard/static/js/flot-pie-chart.js b/tools/pharos-dashboard/static/js/flot-pie-chart.js
new file mode 100644 (file)
index 0000000..98d174e
--- /dev/null
@@ -0,0 +1,22 @@
+function loadChartData(chart_id, url) {
+    $.ajax({
+        url: url,
+        type: 'get',
+        success: function (data) {
+            var data = data['data'];
+            $(function () {
+                var plotObj = $.plot($("#" + chart_id), data, {
+                    series: {
+                        pie: {
+                            show: true
+                        }
+                    }
+                });
+            });
+        },
+        failure: function (data) {
+            alert('Error loading data');
+        }
+    });
+
+}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/account/user_list.html b/tools/pharos-dashboard/templates/account/user_list.html
new file mode 100644 (file)
index 0000000..7618dc9
--- /dev/null
@@ -0,0 +1,42 @@
+{% extends "dashboard/table.html" %}
+{% load staticfiles %}
+
+{% block table %}
+    <thead>
+    <tr>
+        <th>Username</th>
+        <th>Company</th>
+        <th>SSH Key</th>
+        <th>GPG Key</th>
+    </tr>
+    </thead>
+    <tbody>
+    {% for user in users %}
+        <tr>
+            <th>
+                {{ user.username }}
+            </th>
+            <th>
+                {{ user.userprofile.company }}
+            </th>
+            <th>
+                <a href={{ user.userprofile.ssh_public_key.url }}>Download</a>
+            </th>
+            <th>
+                <a href={{ user.userprofile.pgp_public_key.url }}>Download</a>
+            </th>
+        </tr>
+    {% endfor %}
+    </tbody>
+{% endblock table %}
+
+
+{% block tablejs %}
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $('#table').DataTable({
+                "order": [[0, "asc"]]
+            });
+        });
+    </script>
+{% endblock tablejs %}
\ No newline at end of file
index 64174a1..42156e3 100644 (file)
                                     class="fa fa-fw"></i>Resources
                             </a>
                         </li>
+                        <li>
+                            <a href="{% url 'account:users' %}"><i
+                                    class="fa fa-fw"></i>Users
+                            </a>
+                        </li>
                     </ul>
                 </div>
                 <!-- /.sidebar-collapse -->
index 2982a6f..a754252 100644 (file)
@@ -41,7 +41,7 @@
                     href={{ pod.slave.last_job_url }}>{{ pod.slave.last_job_name }}</a>
             </th>
         </tr>
-    {% endfor %}`
+    {% endfor %}
     </tbody>
 {% endblock table %}
 
index 9c84bb9..c4cb1ba 100644 (file)
         <th>Booked by</th>
         <th>Booked until</th>
         <th>Purpose</th>
+        <th>Utilization</th>
         <th>Status</th>
         <th></th>
+        <th></th>
     </tr>
     </thead>
     <tbody>
-    {% for pod, booking in dev_pods %}
+    {% for pod, booking, utilization in dev_pods %}
         <tr>
             <th>
                 <a href={% url 'dashboard:resource' resource_id=pod.id %}>{{ pod.name }}</a>
@@ -32,6 +34,9 @@
             <th>
                 {{ booking.purpose }}
             </th>
+            <th>
+                {{ utilization }}
+            </th>
             <th style="background-color:{{ pod.slave.status | jenkins_status_color }}">
                 {{ pod.slave.status }}
             </th>
                     Book
                 </a>
             </th>
+            <th>
+                <a href="{% url 'dashboard:resource' resource_id=pod.id %}" class="btn btn-primary">
+                    Info
+                </a>
+            </th>
         </tr>
     {% endfor %}
     </tbody>
@@ -50,9 +60,9 @@
         $(document).ready(function () {
             $('#table').DataTable({
                 columnDefs: [
-                    {type: 'status', targets: 5}
+                    {type: 'status', targets: 6}
                 ],
-                "order": [[5, "asc"]]
+                "order": [[6, "asc"]]
             });
         });
     </script>
index 92d02f6..c9e5735 100644 (file)
     <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 src="{% static "js/flot-pie-chart.js" %}"></script>
+
     <script type="text/javascript">
         $(document).ready(function () {
             $('#{{ resource.id }}_server_table').DataTable({});
             $('#{{ resource.id }}_bookings_table').DataTable({});
+            $('#{{ resource.id }}_vpn_user_table').DataTable({});
 
-            $(function () {
-                var plotObj = $.plot($("#{{ resource.id }}_slave_utilization"), data_{{ resource.id }}, {
-                    series: {
-                        pie: {
-                            show: true
-                        }
-                    }
-                });
+            var chart_id = "{{ resource.id }}_booking_utilization";
+            var utilization_url = "{% url 'dashboard:booking_utilization' resource_id=resource.id weeks=4 %}";
+            loadChartData(chart_id, utilization_url);
 
-            });
+            var chart_id = "{{ resource.id }}_jenkins_utilization";
+            var utilization_url = "{% url 'dashboard:jenkins_utilization' resource_id=resource.id weeks=1 %}";
+            loadChartData(chart_id, utilization_url);
         });
     </script>
 {% endblock extrajs %}
\ No newline at end of file
index 2078475..a770d4e 100644 (file)
@@ -50,6 +50,7 @@
     <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 src="{% static "js/flot-pie-chart.js" %}"></script><
 
     <script type="text/javascript">
         $(document).ready(function () {
 
                 $('#{{ resource.id }}_server_table').DataTable({});
                 $('#{{ resource.id }}_bookings_table').DataTable({});
+                $('#{{ resource.id }}_vpn_user_table').DataTable({});
 
-                $(function () {
-                    var plotObj = $.plot($("#{{ resource.id }}_slave_utilization"), data_{{ resource.id }}, {
-                        series: {
-                            pie: {
-                                show: true
-                            }
-                        }
-                    });
+                var chart_id = "{{ resource.id }}_booking_utilization";
+                var utilization_url = "{% url 'dashboard:booking_utilization' resource_id=resource.id weeks=4 %}";
+                loadChartData(chart_id, utilization_url);
 
-                });
+                var chart_id = "{{ resource.id }}_jenkins_utilization";
+                var utilization_url = "{% url 'dashboard:jenkins_utilization' resource_id=resource.id weeks=1 %}";
+                loadChartData(chart_id, utilization_url);
             {% endfor %}
         });
     </script>
index 4fba476..6067e1e 100644 (file)
@@ -1,12 +1,27 @@
+{% load jenkins_filters %}
+
 <div class="row">
     <div class="col-lg-3">
         <div class="panel panel-default">
             <div class="panel-heading">
-                Utilization
+                Jenkins Utilization
+                <div class="pull-right">
+                    <div class="form-group">
+                        <select onchange="loadChartData('{{ resource.id }}_jenkins_utilization', this.value);">
+                            <option value="{% url 'dashboard:jenkins_utilization' resource_id=resource.id weeks=1 %}">
+                                Last Week
+                            </option>
+                            <option value="{% url 'dashboard:jenkins_utilization' resource_id=resource.id weeks=4 %}">
+                                Last Month
+                            </option>
+                        </select>
+                    </div>
+                </div>
             </div>
             <div class="panel-body">
                 <div class="flot-chart">
-                    <div class="flot-chart-content" id="{{ resource.id }}_slave_utilization"></div>
+                    <div class="flot-chart-content"
+                         id="{{ resource.id }}_jenkins_utilization"></div>
                 </div>
             </div>
         </div>
     </div>
 </div>
 <div class="row">
-    <div class="col-lg-6">
+    <div class="col-lg-3">
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                Booking Utilization
+                <div class="pull-right">
+                    <div class="form-group">
+                        <select onchange="loadChartData('{{ resource.id }}_booking_utilization', this.value);">
+                            <option value="{% url 'dashboard:booking_utilization' resource_id=resource.id weeks=-4 %}">
+                                Last Month
+                            </option>
+                            <option value="{% url 'dashboard:booking_utilization' resource_id=resource.id weeks=-1 %}">
+                                Last Week
+                            </option>
+                            <option value="{% url 'dashboard:booking_utilization' resource_id=resource.id weeks=1 %}">
+                                Next Week
+                            </option>
+                            <option selected="selected"
+                                    value="{% url 'dashboard:booking_utilization' resource_id=resource.id weeks=4 %}">
+                                Next Month
+                            </option>
+                        </select>
+                    </div>
+                </div>
+            </div>
+            <div class="panel-body">
+                <div class="flot-chart">
+                    <div class="flot-chart-content"
+                         id="{{ resource.id }}_booking_utilization"></div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="col-lg-9">
         <div class="panel panel-default">
             <div class="panel-heading">
                 Bookings
         </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
+<div class="row">
+    <div class="col-lg-3">
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                Contact
+            </div>
+            <div class="panel-body">
+                <p>
+                    <b>Lab Owner: </b>
+                    {{ resource.owner.username }}
+                </p>
+                <p>
+                    <b>Email: </b>
+                </p>
+                <p>
+                    <a href="{% url 'booking:create' resource_id=resource.id %}" class="btn
+                    btn-primary">
+                        Booking
+                    </a>
+                    <a href="{{ resource.url }}" class="btn
+                    btn-primary">
+                        OPNFV Wiki
+                    </a>
+                </p>
+            </div>
+        </div>
+    </div>
+    <div class="col-lg-3">
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                Jenkins Status
+            </div>
+            <div class="panel-body">
+                <p>
+                    <b>Slave Name: </b>
+                    <a target='_blank'
+                       href={{ resource.slave.url }}>{{ resource.slave.name }}</a>
+                </p>
+                <p>
+                    <b>Status: </b>
+                    {{ resource.slave.status }}
+                </p>
+                <p>
+                    <b>Last Job: </b>
+                    <a href="{{ resource.slave.last_job_url }}">
+                        {{ resource.slave.last_job_name }}
+                    </a>
+                </p>
+            </div>
+        </div>
+    </div>
+    <div class="col-lg-6">
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                VPN Users
+            </div>
+            <div class="panel-body">
+                <div class="dataTables_wrapper">
+                    <table class="table table-striped table-bordered table-hover"
+                           id="{{ resource.id }}_vpn_user_table" cellspacing="0"
+                           width="100%">
+                        <thead>
+                        <tr>
+                            <th>User</th>
+                            <th>Email</th>
+                            <th>Company</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        {% for user in resource.vpn_users.all %}
+                            <tr>
+                                <th>
+                                    {{ user.username }}
+                                </th>
+                                <th>
+                                    {{ user.email }}
+                                </th>
+                                <th>
+                                    {{ user.userprofile.company }}
+                                </th>
+                            </tr>
+                        {% endfor %}
+                    </table>
+                    </tbody>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
index d47e520..fee7e8b 100644 (file)
@@ -26,5 +26,5 @@
             {{ server.storage }}
         </th>
     </tr>
-{% endfor %}`
+{% endfor %}
 </tbody>
\ No newline at end of file
index db9490f..64fed4a 100644 (file)
@@ -20,7 +20,7 @@
     <link href="{% static "bower_components/metisMenu/dist/metisMenu.min.css" %}" rel="stylesheet">
 
     <!-- Custom CSS -->
-    <link href="{% static "bower_components/startbootstrap-sb-admin-2/dist/css/sb-admin-2.css" %}"
+    <link href="{% static "bower_components/startbootstrap-sb-admin-2-blackrockdigital/dist/css/sb-admin-2.css" %}"
           rel="stylesheet">
     <link href="{% static "css/theme.css" %}" rel="stylesheet">
 
@@ -65,7 +65,7 @@
 <script src="{% static "bower_components/metisMenu/dist/metisMenu.min.js" %}"></script>
 
 <!-- Custom Theme JavaScript -->
-<script src="{% static "bower_components/startbootstrap-sb-admin-2/dist/js/sb-admin-2.js" %}"></script>
+<script src="{% static "bower_components/startbootstrap-sb-admin-2-blackrockdigital/dist/js/sb-admin-2.js" %}"></script>
 
 {% block extrajs %}
 {% endblock extrajs %}