summaryrefslogtreecommitdiffstats
path: root/dashboard/src
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/src')
-rw-r--r--dashboard/src/account/views.py4
-rw-r--r--dashboard/src/booking/forms.py10
-rw-r--r--dashboard/src/booking/urls.py7
-rw-r--r--dashboard/src/booking/views.py58
-rw-r--r--dashboard/src/templates/booking/booking_detail.html146
5 files changed, 175 insertions, 50 deletions
diff --git a/dashboard/src/account/views.py b/dashboard/src/account/views.py
index 09c5266..e880208 100644
--- a/dashboard/src/account/views.py
+++ b/dashboard/src/account/views.py
@@ -181,8 +181,8 @@ def account_booking_view(request):
if not request.user.is_authenticated:
return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
template = "account/booking_list.html"
- bookings = list(Booking.objects.filter(owner=request.user))
- collab_bookings = list(request.user.collaborators.all())
+ bookings = list(Booking.objects.filter(owner=request.user).order_by("-start"))
+ collab_bookings = list(request.user.collaborators.all().order_by("-start"))
context = {"title": "My Bookings", "bookings": bookings, "collab_bookings": collab_bookings}
return render(request, template, context=context)
diff --git a/dashboard/src/booking/forms.py b/dashboard/src/booking/forms.py
index cb76383..7ba5af0 100644
--- a/dashboard/src/booking/forms.py
+++ b/dashboard/src/booking/forms.py
@@ -1,5 +1,5 @@
##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -28,7 +28,7 @@ class QuickBookingForm(forms.Form):
installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False)
- def __init__(self, data=None, *args, user=None, **kwargs):
+ def __init__(self, data=None, user=None, *args, **kwargs):
chosen_users = []
if "default_user" in kwargs:
default_user = kwargs.pop("default_user")
@@ -104,3 +104,9 @@ class QuickBookingForm(forms.Form):
'edit': False
}
return attrs
+
+
+class HostReImageForm(forms.Form):
+
+ image_id = forms.IntegerField()
+ host_id = forms.IntegerField()
diff --git a/dashboard/src/booking/urls.py b/dashboard/src/booking/urls.py
index c6504e0..310aaa7 100644
--- a/dashboard/src/booking/urls.py
+++ b/dashboard/src/booking/urls.py
@@ -33,7 +33,8 @@ from booking.views import (
BookingListView,
booking_stats_view,
booking_stats_json,
- quick_create
+ quick_create,
+ booking_modify_image
)
app_name = "booking"
@@ -42,12 +43,10 @@ urlpatterns = [
url(r'^detail/(?P<booking_id>[0-9]+)/$', booking_detail_view, name='detail'),
url(r'^(?P<booking_id>[0-9]+)/$', booking_detail_view, name='booking_detail'),
-
url(r'^delete/$', BookingDeleteView.as_view(), name='delete_prefix'),
url(r'^delete/(?P<booking_id>[0-9]+)/$', BookingDeleteView.as_view(), name='delete'),
-
url(r'^delete/(?P<booking_id>[0-9]+)/confirm/$', bookingDelete, name='delete_booking'),
-
+ url(r'^modify/(?P<booking_id>[0-9]+)/image/$', booking_modify_image, name='modify_booking_image'),
url(r'^list/$', BookingListView.as_view(), name='list'),
url(r'^stats/$', booking_stats_view, name='stats'),
url(r'^stats/json$', booking_stats_json, name='stats_json'),
diff --git a/dashboard/src/booking/views.py b/dashboard/src/booking/views.py
index bc1d2c9..3be9c7b 100644
--- a/dashboard/src/booking/views.py
+++ b/dashboard/src/booking/views.py
@@ -10,17 +10,20 @@
from django.contrib import messages
from django.shortcuts import get_object_or_404
-from django.http import JsonResponse
+from django.http import JsonResponse, HttpResponse
from django.utils import timezone
from django.views import View
from django.views.generic import TemplateView
from django.shortcuts import redirect, render
+from django.db.models import Q
-from account.models import Lab
+from resource_inventory.models import ResourceBundle, HostProfile, Image, Host
from resource_inventory.resource_manager import ResourceManager
-from resource_inventory.models import ResourceBundle
+from account.models import Lab
from booking.models import Booking
from booking.stats import StatisticsManager
+from booking.forms import HostReImageForm
+from api.models import HostHardwareRelation, JobStatus
from workflow.views import login
from booking.forms import QuickBookingForm
from booking.quick_deployer import create_from_form, drop_filter
@@ -125,6 +128,19 @@ class ResourceBookingsJSON(View):
return JsonResponse({'bookings': list(bookings)})
+def build_image_mapping(lab, user):
+ mapping = {}
+ for profile in HostProfile.objects.filter(labs=lab):
+ images = Image.objects.filter(
+ from_lab=lab,
+ host_type=profile
+ ).filter(
+ Q(public=True) | Q(owner=user)
+ )
+ mapping[profile.name] = [{"name": image.name, "value": image.id} for image in images]
+ return mapping
+
+
def booking_detail_view(request, booking_id):
user = None
if request.user.is_authenticated:
@@ -138,15 +154,39 @@ def booking_detail_view(request, booking_id):
if user not in allowed_users:
return render(request, "dashboard/login.html", {'title': 'This page is private'})
+ context = {
+ 'title': 'Booking Details',
+ 'booking': booking,
+ 'pdf': booking.pdf,
+ 'user_id': user.id,
+ 'image_mapping': build_image_mapping(booking.lab, user)
+ }
+
return render(
request,
"booking/booking_detail.html",
- {
- 'title': 'Booking Details',
- 'booking': booking,
- 'pdf': booking.pdf,
- 'user_id': user.id
- })
+ context
+ )
+
+
+def booking_modify_image(request, booking_id):
+ form = HostReImageForm(request.POST)
+ if form.is_valid():
+ booking = Booking.objects.get(id=booking_id)
+ if request.user != booking.owner:
+ return HttpResponse("unauthorized")
+ if timezone.now() > booking.end:
+ return HttpResponse("unauthorized")
+ new_image = Image.objects.get(id=form.cleaned_data['image_id'])
+ host = Host.objects.get(id=form.cleaned_data['host_id'])
+ relation = HostHardwareRelation.objects.get(host=host, job__booking=booking)
+ config = relation.config
+ config.set_image(new_image.lab_id)
+ config.save()
+ relation.status = JobStatus.NEW
+ relation.save()
+ return HttpResponse(new_image.name)
+ return HttpResponse("error")
def booking_stats_view(request):
diff --git a/dashboard/src/templates/booking/booking_detail.html b/dashboard/src/templates/booking/booking_detail.html
index cae0e25..51dd328 100644
--- a/dashboard/src/templates/booking/booking_detail.html
+++ b/dashboard/src/templates/booking/booking_detail.html
@@ -1,20 +1,29 @@
{% extends "base.html" %}
{% load staticfiles %}
+{% load bootstrap3 %}
{% block extrahead %}
{{block.super}}
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></script>
-<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
{% endblock %}
{% block content %}
+
+<style>
+#modal_warning {
+ transition: max-height 0.5s ease-out;
+ overflow: hidden;
+}
+
+</style>
+
<div class="container-fluid">
<div class="row">
- <div class="col-lg-6">
+ <div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h4 style="display: inline;">Overview</h4>
- <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+ <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;">Expand</a>
</div>
<div class="panel-body" id="panel_overview">
<table class="table">
@@ -50,9 +59,7 @@
</div>
</div>
<div class="row">
-
- <div class="col-lg-6">
-
+ <div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h4 style="display: inline;">Pod</h4>
@@ -79,7 +86,15 @@
</tr>
<tr>
<td>Image:</td>
- <td>{{host.config.image}}</td>
+ <td id="host_image_{{host.id}}">
+ {{host.config.image}}
+ <button
+ style="margin-left:10px;"
+ class="btn btn-primary"
+ data-toggle="modal"
+ data-target="#imageModal"
+ onclick="set_image_dropdown('{{host.profile.name}}', {{host.id}});"
+ >Change/Reset</button></td>
</tr>
<tr>
<td>RAM:</td>
@@ -152,10 +167,7 @@
</table>
</td>
</tr>
-
-
</table>
-
</td>
{% endfor %}
</tr>
@@ -163,31 +175,15 @@
</div>
</div>
</div>
- <div class="col-lg-6">
-
- <div class="panel panel-default">
- <div class="panel-heading clearfix">
- <h4 style="display: inline;">PDF</h4>
- <a data-toggle="collapse" data-target="#pdf_panel" class="btn pull-right" style="line-height: 1;" >Expand</a>
- </div>
-
- <div class="panel-body" id="pdf_panel" style="padding: 0px;">
- <pre class="prettyprint lang-yaml" style="margin: 0px; padding: 0px; border: none;">
-{{pdf}}
- </pre>
- </div>
- </div>
- </div>
</div>
</div>
- <div class="col-lg-6">
+ <div class="col-lg-8">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h4 style="display: inline;">Deployment Progress</h4>
<p style="display: inline; margin-left: 10px;"> These are the different tasks that have to be completed before your deployment is ready</p>
<a data-toggle="collapse" data-target="#panel_tasks" class="btn pull-right" style="line-height: 1;" >Expand</a>
</div>
-
<div class="panel-body" id="panel_tasks">
<table class="table">
<style>
@@ -215,7 +211,6 @@
border-radius: 50%;
animation: fadeInOut 1s infinite alternate;
-
}
@keyframes fadeInOut {
from { opacity: 0;}
@@ -244,9 +239,7 @@
{% else %}
<div class="done"></div>
{% endif %}
- </td>
-
-
+ </td>
<td>
{% if task.status < 100 %}
PENDING
@@ -257,7 +250,6 @@
{% endif %}
</td>
<td>
-
{% if task.message %}
{% if task.type_str == "Access Task" and user_id != task.config.user.id %}
Message from Lab: <pre>--secret--</pre>
@@ -270,16 +262,104 @@
</td>
<td>
{{ task.type_str }}
-
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
+ <div class="row">
+ <div class="col-lg-8">
+ <div class="panel panel-default">
+ <div class="panel-heading clearfix">
+ <h4 style="display: inline;">PDF</h4>
+ <a data-toggle="collapse" data-target="#pdf_panel" class="btn pull-right" style="line-height: 1;" >Expand</a>
+ </div>
+ <div class="panel-body" id="pdf_panel" style="padding: 0px;">
+ <pre class="prettyprint lang-yaml" style="margin: 0px; padding: 15px; border: none;">
+{{pdf}}
+ </pre>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
+<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
+ <div class="modal-dialog" style="width: 450px;" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title" id="exampleModalLabel" style="display: inline; float: left;">Host Image</h4>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form id="image_host_form">
+ {% csrf_token %}
+ <select class="form-control" style="width: 80%; margin-left: 10%" id="image_select" name="image_id">
+ </select>
+ <input id="host_id_input" type="hidden" name="host_id">
+ </input>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+ <button type="button" class="btn btn-primary" onclick="document.getElementById('modal_warning').style['max-height'] = '500px';">Reset Host</button>
+ </div>
+ <div id="modal_warning" class="modal-footer" style="max-height:0px;" >
+ <div style="text-align:center; margin: 5px">
+ <h3>Are You Sure?</h3>
+ <p>This will wipe the disk and reimage the host</p>
+ <button class="btn" onclick="document.getElementById('modal_warning').style['max-height'] = '0px';">Nevermind</button>
+ <button class="btn btn-danger" data-dismiss="modal" onclick="submit_image_form();">I'm Sure</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ var image_mapping = {{image_mapping|safe}};
+ var current_host_id = 0;
+ function set_image_dropdown(profile_name, host_id) {
+ document.getElementById("host_id_input").value = host_id;
+ current_host_id = host_id;
+ var dropdown = document.getElementById("image_select");
+ var length = dropdown.length;
+ //clear dropdown
+ for(i=length-1; i>=0; i--){
+ dropdown.options.remove(i);
+ }
+ var images = image_mapping[profile_name];
+ var image_length = images.length;
+ for(i=0; i<image_length; i++){
+ var opt = document.createElement("OPTION");
+ opt.value = images[i].value;
+ opt.appendChild(document.createTextNode(images[i].name));
+ dropdown.options.add(opt);
+ }
+
+ document.getElementById("modal_warning").style['max-height'] = '0px';
+ }
+
+ function submit_image_form() {
+ var ajaxForm = $("#image_host_form");
+ var formData = ajaxForm.serialize();
+ req = new XMLHttpRequest();
+ req.open("POST", "/booking/modify/{{booking.id}}/image/", true);
+ req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ req.onerror = function() { alert("problem submitting form"); }
+ req.onreadystatechange = function() {
+ if(req.readyState === 4) {
+ node = document.getElementById("host_image_" + current_host_id);
+ text = document.createTextNode(req.responseText);
+ node.replaceChild(text, node.firstChild);
+ }
+ }
+ req.send(formData);
+ }
+</script>
{% endblock content %}