diff options
Diffstat (limited to 'dashboard/src')
-rw-r--r-- | dashboard/src/account/views.py | 4 | ||||
-rw-r--r-- | dashboard/src/booking/forms.py | 10 | ||||
-rw-r--r-- | dashboard/src/booking/urls.py | 7 | ||||
-rw-r--r-- | dashboard/src/booking/views.py | 58 | ||||
-rw-r--r-- | dashboard/src/templates/booking/booking_detail.html | 146 |
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">×</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 %} |