aboutsummaryrefslogtreecommitdiffstats
path: root/src/templates/base/booking
diff options
context:
space:
mode:
authorParker Berberian <pberberian@iol.unh.edu>2020-01-30 13:33:35 -0500
committerParker Berberian <pberberian@iol.unh.edu>2020-01-30 16:12:18 -0500
commit078273eb7db5a481a4131d44a943f3c9e34b6b88 (patch)
tree9e6e569e18712d77b8a65c6ebf4dda50ade1b55a /src/templates/base/booking
parent899e1a4baa95d0bc6f0eef34de66f0e257174878 (diff)
Adds Template Overrides
Changes the structure of the template directories to allow a new project to define their own set of override templates that inherit from a common base. I have slightly modified landing.html here as an example. In comming changes we will try to move all the "laas" specific content into the laas directory Change-Id: I46151be182de901f870debb247b305ea34ae77ba Signed-off-by: Parker Berberian <pberberian@iol.unh.edu>
Diffstat (limited to 'src/templates/base/booking')
-rw-r--r--src/templates/base/booking/booking_calendar.html207
-rw-r--r--src/templates/base/booking/booking_delete.html11
-rw-r--r--src/templates/base/booking/booking_detail.html303
-rw-r--r--src/templates/base/booking/booking_grid_item.html11
-rw-r--r--src/templates/base/booking/booking_list.html44
-rw-r--r--src/templates/base/booking/booking_table.html37
-rw-r--r--src/templates/base/booking/quick_deploy.html140
-rw-r--r--src/templates/base/booking/stats.html71
-rw-r--r--src/templates/base/booking/steps/booking_meta.html38
9 files changed, 862 insertions, 0 deletions
diff --git a/src/templates/base/booking/booking_calendar.html b/src/templates/base/booking/booking_calendar.html
new file mode 100644
index 0000000..450c97f
--- /dev/null
+++ b/src/templates/base/booking/booking_calendar.html
@@ -0,0 +1,207 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% load bootstrap4 %}
+
+{% block extrahead %}
+ {{ block.super }}
+
+ <link href="{% static "bower_components/fullcalendar/dist/fullcalendar.css" %}"
+ rel='stylesheet'/>
+ <link href="{% static "bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css" %}"
+ rel='stylesheet'/>
+{% endblock extrahead %}
+
+{% block content %}
+ <div class="col-lg-8">
+ <div class="container-fluid">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <i class="fa fa-calendar fa-fw"></i>Calendar
+ </div>
+ <div class="panel-body">
+ <div id='calendar'>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-lg-4">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <i class="fa fa-edit fa-fw"></i>Booking
+ </div>
+ <div class="panel-body">
+ {% 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.opsys %}
+ {% bootstrap_field form.purpose %}
+ {% bootstrap_field form.installer %}
+ {% bootstrap_field form.scenario %}
+ {% buttons %}
+ <button type="submit" class="btn btn btn-success">
+ Book
+ </button>
+ <p>
+ <br>By continuing, you agree to our
+ <a href="https://wiki.opnfv.org/display/INF/Lab-as-a-Service+at+the+UNH-IOL#Lab-as-a-ServiceattheUNH-IOL-AcceptableUsagePolicy">Acceptable Usage Policy</a>
+ </p>
+ {% endbuttons %}
+ </form>
+ </div>
+ <div id="booking_edit_form_div">
+ {% bootstrap_form_errors form type='non_fields' %}
+ <form method="post" action="" class="form" id="bookingeditform">
+ {% csrf_token %}
+ <div id='hide_if_noext'>
+ <div class='input-group' id='starttimeeditpicker'>
+ {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+ </div>
+ <div class='input-group' id='endtimeeditpicker'>
+ {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+ </div>
+ </div>
+ <script type="text/javascript">
+ if( {{ booking.ext_count }} <= 0 ){
+ var hidediv = document.getElementById('hide_if_noext');
+ hidediv.style.display = 'none';
+ var par = document.createElement("div");
+ var text = document.createTextNode("No Extensions Remaining");
+ par.appendChild(text);
+ par.style.fontWeight = 'bold';
+ par.style.paddingBottom = '15pt';
+ hidediv.parentNode.insertBefore(par,hidediv.nextSibling);
+ }
+ </script>
+ {% bootstrap_field form.opsys %}
+ {% bootstrap_field form.purpose %}
+ {% bootstrap_field form.installer %}
+ {% bootstrap_field form.scenario %}
+ {% bootstrap_field form.reset %}
+ {% buttons %}
+ <button type="submit" class="btn btn btn-success">
+ Confirm Edit
+ </button>
+ {% endbuttons %}
+ </form>
+ </div>
+ <script type="text/javascript">
+ //Check if current view is an edit or a standard booking view
+ if(window.location.href.includes('edit')) {
+ var element = document.getElementById("bookingform");
+ element.parentNode.removeChild(element); }
+ else {
+ var element = document.getElementById("bookingeditform");
+ element.parentNode.removeChild(element); }
+ </script>
+ {% else %}
+ <p>Please
+ <a href="{% url 'account:login' %}">
+ login with Jira</a>
+ to book this Pod</p>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+
+ <div id="booking_detail_modal" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+
+ <!-- Modal content-->
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title">Booking Detail</h4>
+ </div>
+ <div class="modal-body" id="booking_detail_content">
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">Close
+ </button>
+ </div>
+ </div>
+
+ </div>
+ </div>
+{% endblock content %}
+
+{% block extrajs %}
+ <script type="text/javascript">
+ var bookings_url = "{% url 'booking:bookings_json' resource_id=resource.id %}";
+ var booking_detail_prefix = "{% url 'booking:detail_prefix' %}";
+ var booking_delete_prefix = "{% url 'booking:delete_prefix' %}";
+ var user_timezone = "{{ request.user.userprofile.timezone }}"
+ {% autoescape off %}
+ var sup_installer_dict = {{ installer_filter }}
+ var sup_scenario_dict = {{ scenario_filter }}
+ {% endautoescape %}
+
+ </script>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $("#id_installer").children().hide();
+ $("#id_scenario").children().hide();
+ });
+
+ function installerHider() {
+ dropFilter("id_installer", sup_installer_dict, "id_opsys");
+ scenarioHider();
+ }
+ document.getElementById('id_opsys').addEventListener('change', installerHider);
+
+ function scenarioHider() {
+ dropFilter("id_scenario", sup_scenario_dict, "id_installer");
+ }
+ document.getElementById('id_installer').addEventListener('change', scenarioHider);
+
+ function dropFilter(target, target_filter, master) {
+ ob = document.getElementById(target);
+
+ for(var i=0; i<ob.options.length; i++) {
+ if ( ob.options[i].text == '---------' ) {
+ ob.selectedIndex = i;
+ }
+ }
+
+ targ_id = "#" + target;
+ $(targ_id).children().hide();
+ var drop = document.getElementById(master);
+ var opts = target_filter[drop.options[drop.selectedIndex].value];
+ if (!opts) {
+ opts = {};
+ }
+ var emptyMap = {}
+
+ var map = Object.create(null);
+ for (var i = 0; i < opts.length; i++) {
+ var j = opts[i];
+ map[j] = true;
+ }
+
+ for (var i = 0; i < document.getElementById(target).childNodes.length; i++) {
+ if (document.getElementById(target).childNodes[i].value in opts && !(document.getElementById(target).childNodes[i].value in emptyMap) ) {
+ document.getElementById(target).childNodes[i].style.display = "inherit";
+ }
+ }
+ }
+
+ </script>
+ <script src={% static "bower_components/moment/moment.js" %}></script>
+ <script src={% static "bower_components/fullcalendar/dist/fullcalendar.js" %}></script>
+ <script type="text/javascript"
+ src={% static "bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js" %}></script>
+ <script src={% static "js/fullcalendar-options.js" %}></script>
+ <script src={% static "js/datetimepicker-options.js" %}></script>
+ <script src={% static "js/booking-calendar.js" %}></script>
+{% endblock extrajs %}
diff --git a/src/templates/base/booking/booking_delete.html b/src/templates/base/booking/booking_delete.html
new file mode 100644
index 0000000..b89eb15
--- /dev/null
+++ b/src/templates/base/booking/booking_delete.html
@@ -0,0 +1,11 @@
+{% load jira_filters %}
+{% load bootstrap4 %}
+
+<p>
+ Really delete Booking from {{ booking.start}} to {{ booking.end }}?
+</p>
+<p>
+ <div id="booking_delete_form_div">
+ <a href="{% url 'booking:delete_booking' booking_id=booking.id %}" class="btn btn btn-danger">Delete Booking</a>
+ </div>
+</p> \ No newline at end of file
diff --git a/src/templates/base/booking/booking_detail.html b/src/templates/base/booking/booking_detail.html
new file mode 100644
index 0000000..fea2bb6
--- /dev/null
+++ b/src/templates/base/booking/booking_detail.html
@@ -0,0 +1,303 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load bootstrap4 %}
+
+{% block extrahead %}
+ {{block.super}}
+ <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></script>
+{% endblock %}
+
+{% block content %}
+<div class="row">
+ <div class="col-12 col-lg-5">
+ <div class="card mb-3">
+ <div class="card-header d-flex">
+ <h4 class="d-inline">Overview</h4>
+ <button data-toggle="collapse" data-target="#panel_overview" class="btn btn-outline-secondary ml-auto">Expand</button>
+ </div>
+ <div class="collapse show" id="panel_overview">
+ <table class="table m-0">
+ <tr>
+ <td>Purpose</td>
+ <td>{{ booking.purpose }}</td>
+ </tr>
+ <tr>
+ <td>Project</td>
+ <td>{{ booking.project }}</td>
+ </tr>
+ <tr>
+ <td>Start Time</td>
+ <td>{{ booking.start }}</td>
+ </tr>
+ <tr>
+ <td>End Time</td>
+ <td>{{ booking.end }}</td>
+ </tr>
+ <tr>
+ <td>Pod Definition</td>
+ <td>{{ booking.resource.template }}</td>
+ </tr>
+ <tr>
+ <td>Pod Configuration</td>
+ <td>{{ booking.config_bundle }}</td>
+ </tr>
+ <tr>
+ <td>Lab Deployed At</td>
+ <td>{{ booking.lab }}</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div class="card my-3">
+ <div class="card-header d-flex">
+ <h4 class="d-inline">Pod</h4>
+ <button data-toggle="collapse" data-target="#pod_panel" class="btn btn-outline-secondary ml-auto">Expand</button>
+ </div>
+ <div class="collapse show" id="pod_panel">
+ <div class="card-body">
+ {% for host in booking.resource.hosts.all %}
+ <h4>{{host.template.resource.name}}</h4>
+ <div class="overflow-auto">
+ <table class="table m-0">
+ <tr>
+ <td>Hostname:</td>
+ <td>{{host.template.resource.name}}</td>
+ </tr>
+ <tr>
+ <td>Machine:</td>
+ <td>{{host.name}}</td>
+ </tr>
+ <tr>
+ <td>Role:</td>
+ <td>{{host.config.opnfvRole}}</td>
+ </tr>
+ <tr>
+ <td>Image:</td>
+ <td id="host_image_{{host.id}}">
+ {{host.config.image}}
+ <button
+ class="btn btn-primary ml-4"
+ data-toggle="modal"
+ data-target="#imageModal"
+ onclick="set_image_dropdown('{{host.profile.name}}', {{host.id}});"
+ >Change/Reset</button></td>
+ </tr>
+ <tr>
+ <td>RAM:</td>
+ <td>{{host.profile.ramprofile.first.amount}}G,
+ {{host.profile.ramprofile.first.channels}} channels</td>
+ </tr>
+ <tr>
+ <td>CPU:</td>
+ <td>
+ <table class="table m-0">
+ <tr>
+ <td>Arch:</td>
+ <td>{{host.profile.cpuprofile.first.architecture}}</td>
+ </tr>
+ <tr>
+ <td>Cores:</td>
+ <td>{{host.profile.cpuprofile.first.cores}}</td>
+ </tr>
+ <tr>
+ <td>Sockets:</td>
+ <td>{{host.profile.cpuprofile.first.cpus}}</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td>DISK:</td>
+ <td>
+ <table class="table m-0">
+ <tr>
+ <td>Size:</td>
+ <td>{{host.profile.storageprofile.first.size}} GiB</td>
+ </tr>
+ <tr>
+ <td>Type:</td>
+ <td>{{host.profile.storageprofile.first.media_type}}</td>
+ </tr>
+ <tr>
+ <td>Mount Point:</td>
+ <td>{{host.profile.storageprofile.first.name}}</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td>Interfaces:</td>
+ <td>
+ <table class="table m-0">
+ {% for intprof in host.profile.interfaceprofile.all %}
+ <tr>
+ <td>
+ <table class="table table-sm table-borderless m-0">
+ <tr>
+ <td>Name:</td>
+ <td>{{intprof.name}}</td>
+ </tr>
+ <tr>
+ <td>Speed:</td>
+ <td>{{intprof.speed}}</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col">
+ <div class="card mb-3">
+ <div class="card-header d-flex">
+ <h4 class="d-inline">Deployment Progress</h4>
+ <p>These are the different tasks that have to be completed before your deployment is ready</p>
+ <button data-toggle="collapse" data-target="#panel_tasks" class="btn btn-outline-secondary ml-auto">Expand</button>
+ </div>
+ <div class="collapse show" id="panel_tasks">
+ <table class="table m-0">
+ <tr>
+ <th></th>
+ <th>Status</th>
+ <th>Lab Response</th>
+ <th>Type</th>
+ </tr>
+ {% for task in booking.job.get_tasklist %}
+ <tr>
+ <td>
+ {% if task.status < 100 %}
+ <div class="rounded-circle bg-secondary square-20"></div>
+ {% elif task.status < 200 %}
+ <div class="spinner-border text-primary square-20"></div>
+ {% else %}
+ <div class="rounded-circle bg-success square-20"></div>
+ {% endif %}
+ </td>
+ <td>
+ {% if task.status < 100 %}
+ PENDING
+ {% elif task.status < 200 %}
+ IN PROGRESS
+ {% else %}
+ DONE
+ {% 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>
+ {% else %}
+ Message from Lab: <pre class="text-break pre-wrap">{{ task.message }}</pre>
+ {% endif %}
+ {% else %}
+ No response provided (yet)
+ {% endif %}
+ </td>
+ <td>
+ {{ task.type_str }}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ </div>
+ <div class="card my-3">
+ <div class="card-header d-flex">
+ <h4 class="d-inline">PDF</h4>
+ <button data-toggle="collapse" data-target="#pdf_panel" class="btn btn-outline-secondary ml-auto">Expand</button>
+ </div>
+ <div class="collapse show" id="pdf_panel">
+ <div class="card-body">
+ <pre class="prettyprint lang-yaml m-0 border-0 text-break pre-wrap">
+{{pdf}}
+ </pre>
+ </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" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title d-inline float-left" id="exampleModalLabel">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" id="image_select" name="image_id">
+ </select>
+ <input id="host_id_input" type="hidden" name="host_id">
+ </form>
+ </div>
+ <div class="modal-footer d-flex flex-column">
+ <div>
+ <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button>
+ <button type="button" class="btn btn-danger" data-toggle="collapse" data-target="#modal_warning" aria-expanded="false">Reset Host</button>
+ </div>
+ <div class="border-top collapse mt-3 py-2 text-center w-100" id="modal_warning">
+ <h3>Are You Sure?</h3>
+ <p>This will wipe the disk and reimage the host</p>
+ <button class="btn btn-outline-secondary" data-dismiss="modal">Nevermind</button>
+ <button class="btn btn-danger" data-dismiss="modal" onclick="submit_image_form();">I'm Sure</button>
+ </div>
+ </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").classList.add("collapse");
+ }
+
+ 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 %}
diff --git a/src/templates/base/booking/booking_grid_item.html b/src/templates/base/booking/booking_grid_item.html
new file mode 100644
index 0000000..3c72fd2
--- /dev/null
+++ b/src/templates/base/booking/booking_grid_item.html
@@ -0,0 +1,11 @@
+<p>{{ id|default:"id" }}</p>
+<p>{{ lab|default:"lab" }}</p>
+<p>{{ resource_name|default:"resource name" }}</p>
+<p>{{ start|default:"start" }}</p>
+<p>{{ end|default:"end" }}<p>
+<p>Collaborators:</p>
+<ul>
+ {% for collaborator in collaborators %}
+ <li>{{ collaborator }}</li>
+ {% endfor %}
+</ul>
diff --git a/src/templates/base/booking/booking_list.html b/src/templates/base/booking/booking_list.html
new file mode 100644
index 0000000..591ecc9
--- /dev/null
+++ b/src/templates/base/booking/booking_list.html
@@ -0,0 +1,44 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load bootstrap4 %}
+
+{% block extrahead %}
+ {{ block.super }}
+ <!-- DataTables CSS -->
+ <link href="{% static "bower_components/datatables.net-bs4/css/dataTables.bootstrap4.min.css" %}"
+ rel="stylesheet">
+
+ <!-- DataTables Responsive CSS -->
+ <link href="{% static "bower_components/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css" %}"
+ rel="stylesheet">
+{% endblock extrahead %}
+
+{% block content %}
+ <div class="row">
+ <div class="col">
+ <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>
+ </div>
+ </div>
+ </div>
+{% endblock content %}
+
+{% block extrajs %}
+ <!-- DataTables JavaScript -->
+ <script src={% static "bower_components/datatables.net/js/jquery.dataTables.min.js" %}></script>
+ <script src={% static "bower_components/datatables.net-bs4/js/dataTables.bootstrap4.min.js" %}></script>
+
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('#table').DataTable({
+ scrollX: true,
+ });
+ });
+ </script>
+{% endblock extrajs %}
diff --git a/src/templates/base/booking/booking_table.html b/src/templates/base/booking/booking_table.html
new file mode 100644
index 0000000..32a0146
--- /dev/null
+++ b/src/templates/base/booking/booking_table.html
@@ -0,0 +1,37 @@
+{% load jira_filters %}
+
+
+<thead>
+<tr>
+ <th>Owner</th>
+ <th>Purpose</th>
+ <th>Project</th>
+ <th>Start</th>
+ <th>End</th>
+ <th>Operating System</th>
+</tr>
+</thead>
+<tbody>
+{% for booking in bookings %}
+ <tr>
+ <td>
+ {{ booking.owner.username }}
+ </td>
+ <td>
+ {{ booking.purpose }}
+ </td>
+ <td>
+ {{ booking.project }}
+ </td>
+ <td>
+ {{ booking.start }}
+ </td>
+ <td>
+ {{ booking.end }}
+ </td>
+ <td>
+ {{ booking.resource.get_head_node.config.image.os.name }}
+ </td>
+ </tr>
+{% endfor %}
+</tbody>
diff --git a/src/templates/base/booking/quick_deploy.html b/src/templates/base/booking/quick_deploy.html
new file mode 100644
index 0000000..8570f25
--- /dev/null
+++ b/src/templates/base/booking/quick_deploy.html
@@ -0,0 +1,140 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load bootstrap4 %}
+{% block content %}
+
+{% bootstrap_form_errors form type='non_fields' %}
+<form id="quick_booking_form" action="/booking/quick/" method="POST" class="form">
+ {% csrf_token %}
+ <div class="row mx-0 px-0">
+ <div class="col-12 mx-0 px-0 mt-2">
+ <p class="my-0">Please select a host type you wish to book. Only available types are shown.</p>
+ {% bootstrap_field form.filter_field show_label=False %}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-12 col-lg-3 my-2">
+ <div class="col border rounded py-2 h-100">
+ {% bootstrap_field form.purpose %}
+ {% bootstrap_field form.project %}
+ {% bootstrap_field form.length %}
+ <span>Days: </span><output id="daysout">0</output>
+ <script>
+ document.getElementById("id_length").setAttribute("oninput", "daysout.value=this.value");
+ document.getElementById("daysout").value = document.getElementById("id_length").value;
+ </script>
+ </div>
+ </div>
+ <div class="col-12 col-lg-3 my-2">
+ <div class="col border rounded py-2 h-100">
+ <label>Collaborators</label>
+ {{ form.users }}
+ </div>
+ </div>
+ <div class="col-12 col-lg-3 my-2">
+ <div class="col border rounded py-2 h-100">
+ {% bootstrap_field form.hostname %}
+ {% bootstrap_field form.image %}
+ </div>
+ </div>
+ <div class="col-12 col-lg-3 my-2">
+ <div class="col border rounded py-2 h-100">
+ <strong>OPNFV: (Optional)</strong>
+ {% bootstrap_field form.installer %}
+ {% bootstrap_field form.scenario %}
+ </div>
+ </div>
+ <div class="col-12 d-flex mt-2 justify-content-end">
+ <button id="quick_booking_confirm" onclick="submit_form();" type="button" class="btn btn-success">Confirm</button>
+ </div>
+ </div>
+</form>
+
+<script type="text/javascript">
+ function submit_form()
+ {
+ run_form_callbacks();
+ document.getElementById("quick_booking_form").submit();
+ }
+
+ function hide_dropdown(drop_id) {
+ var drop = document.getElementById(drop_id);
+ //select 'blank' option
+ for( var i=0; i < drop.length; i++ )
+ {
+ if ( drop.options[i].text == '---------' )
+ drop.selectedIndex = i;
+ }
+ }
+
+ function get_selected_value(key){
+ for( var attr in multi_filter_widget.result[key] ){
+ if(!(attr in {}) )
+ return attr;
+ }
+ }
+
+ var sup_image_dict = {{image_filter | safe}};
+ var sup_installer_dict = {{installer_filter | safe}};
+ var sup_scenario_dict = {{scenario_filter | safe}};
+
+ function imageFilter() {
+ var drop = document.getElementById("id_image");
+ var lab_pk = get_selected_value("lab");
+ var host_pk = get_selected_value("host");
+
+ for (const childNode of drop.childNodes) {
+ var image_object = sup_image_dict[childNode.value];
+ if (image_object) //weed out empty option
+ {
+ childNode.disabled = !(image_object.host_profile == host_pk && image_object.lab == lab_pk);
+ }
+ }
+ }
+
+ imageFilter();
+ $('#id_installer').children().hide();
+ $('#id_scenario').children().hide();
+
+
+ Array.from(document.getElementsByClassName("grid-item-select-btn")).forEach(function (element) {
+ element.addEventListener('click', imageFilter);
+ });
+
+ function installerHider() {
+ dropFilter("id_installer", sup_installer_dict, "id_image");
+ scenarioHider();
+ }
+ document.getElementById('id_image').addEventListener('change', installerHider);
+
+ function scenarioHider() {
+ dropFilter("id_scenario", sup_scenario_dict, "id_installer");
+ }
+ document.getElementById('id_installer').addEventListener('change', scenarioHider);
+
+ function dropFilter(target, target_filter, master) {
+ var dropdown = document.getElementById(target);
+
+ hide_dropdown(target);
+
+ var drop = document.getElementById(master);
+ var opts = target_filter[drop.options[drop.selectedIndex].value];
+ if (!opts) {
+ opts = {};
+ }
+
+ var map = Object.create(null);
+ for (var i = 0; i < opts.length; i++) {
+ var j = opts[i];
+ map[j] = true;
+ }
+
+ for (var i = 0; i < dropdown.childNodes.length; i++) {
+ if (dropdown.childNodes[i].value in opts && !(dropdown.childNodes[i].value in {})) {
+ dropdown.childNodes[i].style.display = "inherit";
+ dropdown.childNodes[i].disabled = false;
+ }
+ }
+ }
+</script>
+{% endblock %}
diff --git a/src/templates/base/booking/stats.html b/src/templates/base/booking/stats.html
new file mode 100644
index 0000000..94239f9
--- /dev/null
+++ b/src/templates/base/booking/stats.html
@@ -0,0 +1,71 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block extrahead %}
+{{ block.super }}
+<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
+<script>
+function drawGraph(data, graph_id, graph_title){
+ var container = document.getElementById(graph_id);
+ var plot_data = { x: data[0],
+ y: data[1],
+ fill: "tonexty",
+ type: "scatter",
+ mode: "none",
+ };
+ var layout = {
+ title: graph_title
+ };
+ Plotly.newPlot(container, [plot_data], layout);
+}
+function getData(){
+ var req = new XMLHttpRequest();
+ var url = "/booking/stats/json";
+ var day_input = document.getElementById("number_days");
+ var days = day_input.value;
+ //var days = document.getElementById("number_days").value;
+ if(days){
+ url = url + "?days=" + days;
+ }
+ req.onreadystatechange = function(){
+ if( req.readyState == XMLHttpRequest.DONE) {
+ var data = JSON.parse(req.responseText);
+ drawGraph(data["booking"], "booking_graph_container", "Active Bookings");
+ drawGraph(data["user"], "user_graph_container", "Active Users");
+ }
+ }
+ req.open("GET", url, true);
+ req.send();
+}
+</script>
+{% endblock %}
+
+{% block content %}
+<div class="row">
+ <div class="col-auto">
+ <p>Number of days to plot: </p>
+ <div class="form-group d-flex align-content-center">
+ <input id="number_days" type="number" class="form-control d-inline-block w-auto" min="1" step="1"/>
+ <button class="btn btn-primary ml-1" onclick="getData();">Submit</button>
+ </div>
+ </div>
+</div>
+<div class="row">
+ <div class="col-12 col-md-10">
+ <!-- These graphs do NOT get redrawn when the browser size is changed -->
+ <div id="all_graph_container border" class="mw-100">
+ <div id="booking_graph_wrapper">
+ <div id="booking_graph_container"/>
+ </div>
+ <div id="user_graph_wrapper">
+ <div id="user_graph_container"/>
+ </div>
+ </div>
+ </div>
+</div>
+<script>
+var data = {{data|safe}};
+drawGraph(data["booking"], "booking_graph_container", "Active Bookings");
+drawGraph(data["user"], "user_graph_container", "Active Users");
+</script>
+{% endblock content %}
diff --git a/src/templates/base/booking/steps/booking_meta.html b/src/templates/base/booking/steps/booking_meta.html
new file mode 100644
index 0000000..f12496e
--- /dev/null
+++ b/src/templates/base/booking/steps/booking_meta.html
@@ -0,0 +1,38 @@
+{% extends "workflow/viewport-element.html" %}
+{% load staticfiles %}
+
+{% load bootstrap4 %}
+
+{% block content %}
+
+{% bootstrap_form_errors form type='non_fields' %}
+<form id="step_form" method="POST" class="form">
+{% csrf_token %}
+<div id="form_div" class="container-fluid">
+ <div class="row">
+ <div class="p-4 col">
+ {% bootstrap_field form.purpose %}
+ {% bootstrap_field form.project %}
+ {% bootstrap_field form.length %}
+ <span>Days: </span><output id="daysout">0</output>
+ <script>
+ document.getElementById("id_length").oninput = function() { daysout.value=this.value; }
+ document.getElementById("daysout").value = document.getElementById("id_length").value;
+ </script>
+ {% bootstrap_field form.info_file %}
+ <p>You must provide a url to your project's INFO.yaml file if you are a PTL and you are trying to book a POD with multiple servers in it.</p>
+ {% bootstrap_field form.deploy_opnfv %}
+ </div>
+ <div class="p-4 col">
+ <p>You may add collaborators on your booking to share resources with coworkers.</p>
+ {% bootstrap_field form.users label="Collaborators" %}
+ </div>
+ </div>
+ <div class="panel_wrap">
+ {% buttons %}
+ <button type="submit" class="btn btn-success d-none">Confirm</button>
+ {% endbuttons %}
+ </div>
+</div>
+</form>
+{% endblock content %}