aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorParker Berberian <pberberian@iol.unh.edu>2019-01-15 12:49:20 -0500
committerParker Berberian <pberberian@iol.unh.edu>2019-01-18 12:15:45 -0500
commiteae6892642b35ed0459e8cc86328c63cf5e87641 (patch)
tree3aca44950597e00d4cf33d59c8b727eed4a1212e
parent77b6cd08bf94ab330d770f2b7d05e76de12c4cca (diff)
Allow for Hosts to be Re-Imaged
This change adds a button the user can press on thier booking detail page to reset thier host. They can choose to deploy any available image to thier servers (not just the one already used) Change-Id: I97a9869d2b38389c54f13173bb28a68cc52bb8d5 Signed-off-by: Parker Berberian <pberberian@iol.unh.edu>
-rw-r--r--src/account/views.py4
-rw-r--r--src/booking/forms.py10
-rw-r--r--src/booking/urls.py7
-rw-r--r--src/booking/views.py58
-rw-r--r--src/templates/booking/booking_detail.html146
5 files changed, 175 insertions, 50 deletions
diff --git a/src/account/views.py b/src/account/views.py
index 09c5266..e880208 100644
--- a/src/account/views.py
+++ b/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/src/booking/forms.py b/src/booking/forms.py
index cb76383..7ba5af0 100644
--- a/src/booking/forms.py
+++ b/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/src/booking/urls.py b/src/booking/urls.py
index c6504e0..310aaa7 100644
--- a/src/booking/urls.py
+++ b/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/src/booking/views.py b/src/booking/views.py
index bc1d2c9..3be9c7b 100644
--- a/src/booking/views.py
+++ b/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/src/templates/booking/booking_detail.html b/src/templates/booking/booking_detail.html
index cae0e25..51dd328 100644
--- a/src/templates/booking/booking_detail.html
+++ b/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 %}