aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pharos_dashboard/urls.py2
-rw-r--r--src/static/css/base.css259
-rw-r--r--src/static/css/detail_view.css39
-rw-r--r--src/static/css/graph_common.css172
-rw-r--r--src/static/js/dashboard.js147
-rw-r--r--src/templates/account/booking_list.html181
-rw-r--r--src/templates/account/configuration_list.html72
-rw-r--r--src/templates/account/image_list.html90
-rw-r--r--src/templates/account/resource_list.html72
-rw-r--r--src/templates/account/userprofile_update_form.html2
-rw-r--r--src/templates/base.html90
-rw-r--r--src/templates/booking/booking_calendar.html2
-rw-r--r--src/templates/booking/booking_detail.html446
-rw-r--r--src/templates/booking/quick_deploy.html165
-rw-r--r--src/templates/booking/stats.html8
-rw-r--r--src/templates/booking/steps/booking_confirm.html25
-rw-r--r--src/templates/booking/steps/booking_meta.html97
-rw-r--r--src/templates/booking/steps/resource_select.html73
-rw-r--r--src/templates/booking/steps/swconfig_select.html73
-rw-r--r--src/templates/config_bundle/steps/config_software.html12
-rw-r--r--src/templates/config_bundle/steps/define_software.html9
-rw-r--r--src/templates/config_bundle/steps/pick_installer.html12
-rw-r--r--src/templates/config_bundle/steps/table_formset.html17
-rw-r--r--src/templates/dashboard/genericselect.html69
-rw-r--r--src/templates/dashboard/grid.html10
-rw-r--r--src/templates/dashboard/host_profile_detail.html1
-rw-r--r--src/templates/dashboard/lab_detail.html212
-rw-r--r--src/templates/dashboard/lab_list.html40
-rw-r--r--src/templates/dashboard/landing.html114
-rw-r--r--src/templates/dashboard/multiple_select_filter_widget.html164
-rw-r--r--src/templates/dashboard/searchable_select_multiple.html158
-rw-r--r--src/templates/dashboard/table.html20
-rw-r--r--src/templates/layout.html5
-rw-r--r--src/templates/notifier/inbox.html64
-rw-r--r--src/templates/notifier/notification.html45
-rw-r--r--src/templates/resource/hostprofile_detail.html134
-rw-r--r--src/templates/resource/steps/define_hardware.html10
-rw-r--r--src/templates/resource/steps/host_info.html11
-rw-r--r--src/templates/resource/steps/meta_info.html38
-rw-r--r--src/templates/resource/steps/pod_definition.html163
-rw-r--r--src/templates/snapshot_workflow/steps/meta.html19
-rw-r--r--src/templates/snapshot_workflow/steps/select_host.html135
-rw-r--r--src/templates/workflow/confirm.html140
-rw-r--r--src/templates/workflow/resource_select.html50
-rw-r--r--src/templates/workflow/viewport-base.html210
-rw-r--r--src/templates/workflow/viewport-element.html23
-rw-r--r--src/workflow/booking_workflow.py8
-rw-r--r--src/workflow/forms.py19
-rw-r--r--src/workflow/models.py40
-rw-r--r--src/workflow/opnfv_workflow.py23
-rw-r--r--src/workflow/resource_bundle_workflow.py75
-rw-r--r--src/workflow/snapshot_workflow.py15
-rw-r--r--src/workflow/sw_bundle_workflow.py16
-rw-r--r--src/workflow/tests/test_steps.py48
-rw-r--r--src/workflow/urls.py7
-rw-r--r--src/workflow/views.py57
-rw-r--r--src/workflow/workflow_manager.py52
57 files changed, 1526 insertions, 2734 deletions
diff --git a/src/pharos_dashboard/urls.py b/src/pharos_dashboard/urls.py
index 8535bed..fd791c3 100644
--- a/src/pharos_dashboard/urls.py
+++ b/src/pharos_dashboard/urls.py
@@ -32,7 +32,7 @@ from django.contrib import admin
urlpatterns = [
- url(r'^wf/', include('workflow.urls', namespace='workflow')),
+ url(r'^workflow/', include('workflow.urls', namespace='workflow')),
url(r'^', include('dashboard.urls', namespace='dashboard')),
url(r'^booking/', include('booking.urls', namespace='booking')),
url(r'^accounts/', include('account.urls', namespace='account')),
diff --git a/src/static/css/base.css b/src/static/css/base.css
index c51728c..9fec97e 100644
--- a/src/static/css/base.css
+++ b/src/static/css/base.css
@@ -1,3 +1,30 @@
+/* Sizing */
+#wrapper {
+ height: 100vh;
+}
+
+/* Used for turning divs into square */
+.square-20 {
+ height: 20px;
+ width: 20px;
+}
+
+/* Make links stay the same color with no underline */
+.discrete-a {
+ text-decoration: none;
+ color: inherit;
+}
+
+.discrete-a:hover {
+ text-decoration: none;
+ color: inherit;
+}
+
+/* Allow for sidebar to be small, but also resize on small screens */
+.sidebar {
+ min-width: 200px;
+}
+
/* Rotating arrows when dropdown happens */
i.fas.rotate {
transition: transform 0.3s ease-in-out;
@@ -6,3 +33,235 @@ i.fas.rotate {
a[aria-expanded="true"] > i.rotate {
transform: rotate(180deg);
}
+/* End rotating arrows */
+
+/* Start breadcrumbs for workflow */
+#topPagination .topcrumb {
+ flex: 1 1 0;
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ border: 1px solid #dee2e6;
+ border-left: none;
+}
+
+.topcrumb > span {
+ color: #343a40;
+ cursor: default;
+}
+
+.topcrumb.active > span {
+ background: #007bff;
+ color: white;
+}
+
+.topcrumb.disabled > span {
+ color: #6c757d;
+ background: #f8f9fa;
+}
+
+/* Booking Node Styles */
+.selected_node {
+ border-color: #40c640;
+ box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(109, 243, 76, 0.6);
+ transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
+}
+
+/* Cursor effects */
+.not-allowed {
+ cursor: not-allowed;
+}
+
+/* Used with position-absolute class to make a full height object */
+.topToBottom {
+ bottom: 0;
+ top: 0;
+}
+
+.z-2 {
+ z-index: 2;
+}
+
+.mh-30vh {
+ max-height: 30vh;
+}
+
+.overflow-ellipsis {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+/* Graphing for networks */
+div.mxRubberband {
+ position: absolute;
+ overflow: hidden;
+ border-style: solid;
+ border-width: 1px;
+ border-color: #0000FF;
+ background: #0077FF;
+}
+.mxCellEditor {
+ background: url(data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7);
+ _background: url('/static/img/mxgraph/transparent.gif');
+ border-color: transparent;
+ border-style: solid;
+ display: inline-block;
+ position: absolute;
+ overflow: visible;
+ word-wrap: normal;
+ border-width: 0;
+ min-width: 1px;
+ resize: none;
+ padding: 0px;
+ margin: 0px;
+}
+.mxPlainTextEditor * {
+ padding: 0px;
+ margin: 0px;
+}
+div.mxWindow {
+ background: url('../img/mxgraph/window.gif');
+ border:1px solid #c3c3c3;
+ position: absolute;
+ overflow: hidden;
+ z-index: 3;
+}
+table.mxWindow {
+ border-collapse: collapse;
+ table-layout: fixed;
+ font-family: Arial;
+ font-size: 8pt;
+}
+td.mxWindowTitle {
+ background: url('/static/img/mxgraph/window-title.gif') repeat-x;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-align: center;
+ font-weight: bold;
+ overflow: hidden;
+ height: 13px;
+ padding: 2px;
+ padding-top: 4px;
+ padding-bottom: 6px;
+ color: black;
+}
+td.mxWindowPane {
+ vertical-align: top;
+ padding: 0px;
+}
+div.mxWindowPane {
+ overflow: hidden;
+ position: relative;
+}
+td.mxWindowPane td {
+ font-family: Arial;
+ font-size: 8pt;
+}
+td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio {
+ font-family: Arial;
+ font-size: 8pt;
+ padding: 1px;
+}
+td.mxWindowPane button {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4;
+ display: inline-block;
+ margin: 2%;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.42857143;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+img.mxToolbarItem {
+ margin-right: 6px;
+ margin-bottom: 6px;
+ border-width: 1px;
+}
+select.mxToolbarCombo {
+ vertical-align: top;
+ border-style: inset;
+ border-width: 2px;
+}
+div.mxToolbarComboContainer {
+ padding: 2px;
+}
+img.mxToolbarMode {
+ margin: 2px;
+ margin-right: 4px;
+ margin-bottom: 4px;
+ border-width: 0px;
+}
+img.mxToolbarModeSelected {
+ margin: 0px;
+ margin-right: 2px;
+ margin-bottom: 2px;
+ border-width: 2px;
+ border-style: inset;
+}
+div.mxTooltip {
+ -webkit-box-shadow: 3px 3px 12px #C0C0C0;
+ -moz-box-shadow: 3px 3px 12px #C0C0C0;
+ box-shadow: 3px 3px 12px #C0C0C0;
+ background: #FFFFCC;
+ border-style: solid;
+ border-width: 1px;
+ border-color: black;
+ font-family: Arial;
+ font-size: 8pt;
+ position: absolute;
+ cursor: default;
+ padding: 4px;
+ color: black;
+}
+div.mxPopupMenu {
+ -webkit-box-shadow: 3px 3px 12px #C0C0C0;
+ -moz-box-shadow: 3px 3px 12px #C0C0C0;
+ box-shadow: 3px 3px 12px #C0C0C0;
+ background: url('/static/img/mxgraph/window.gif');
+ position: absolute;
+ border-style: solid;
+ border-width: 1px;
+ border-color: black;
+}
+table.mxPopupMenu {
+ border-collapse: collapse;
+ margin-top: 1px;
+ margin-bottom: 1px;
+}
+tr.mxPopupMenuItem {
+ color: black;
+ cursor: pointer;
+}
+tr.mxPopupMenuItemHover {
+ background-color: #000066;
+ color: #FFFFFF;
+ cursor: pointer;
+}
+td.mxPopupMenuItem {
+ padding: 2px 30px 2px 10px;
+ white-space: nowrap;
+ font-family: Arial;
+ font-size: 8pt;
+}
+td.mxPopupMenuIcon {
+ background-color: #D0D0D0;
+ padding: 2px 4px 2px 4px;
+}
+.mxDisabled {
+ opacity: 0.2 !important;
+ cursor:default !important;
+}
diff --git a/src/static/css/detail_view.css b/src/static/css/detail_view.css
deleted file mode 100644
index c3d0a4d..0000000
--- a/src/static/css/detail_view.css
+++ /dev/null
@@ -1,39 +0,0 @@
-.card_container {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr 1fr;
- grid-gap: 25px 25px;
- justify-items: stretch;
-}
-
-.card_container ul > li {
- padding: 7px !important;
- font-size: 16px;
-}
-
-.detail_button_container .btn {
- width: 49%;
-}
-
-.detail_button_container .btn-danger {
- float: right;
-}
-
-#modal_warning {
- transition: max-height 0.5s ease-out;
- overflow: hidden;
-}
-
-.detail_card {
- border-radius: 5px;
- border-top: 1px solid #ccc;
- border-left: 1px solid #ccc;
- border-right: 1px solid #ccc;
- border-bottom: 1px solid #ccc;
- margin: 5px;
- padding-left: 25px;
- padding-right: 25px;
- padding-bottom: 15px;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-}
diff --git a/src/static/css/graph_common.css b/src/static/css/graph_common.css
deleted file mode 100644
index cff1516..0000000
--- a/src/static/css/graph_common.css
+++ /dev/null
@@ -1,172 +0,0 @@
-div.mxRubberband {
- position: absolute;
- overflow: hidden;
- border-style: solid;
- border-width: 1px;
- border-color: #0000FF;
- background: #0077FF;
-}
-.mxCellEditor {
- background: url(data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7);
- _background: url('/static/img/mxgraph/transparent.gif');
- border-color: transparent;
- border-style: solid;
- display: inline-block;
- position: absolute;
- overflow: visible;
- word-wrap: normal;
- border-width: 0;
- min-width: 1px;
- resize: none;
- padding: 0px;
- margin: 0px;
-}
-.mxPlainTextEditor * {
- padding: 0px;
- margin: 0px;
-}
-div.mxWindow {
- background: url('../img/mxgraph/window.gif');
- border:1px solid #c3c3c3;
- position: absolute;
- overflow: hidden;
- z-index: 3;
-}
-table.mxWindow {
- border-collapse: collapse;
- table-layout: fixed;
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxWindowTitle {
- background: url('/static/img/mxgraph/window-title.gif') repeat-x;
- text-overflow: ellipsis;
- white-space: nowrap;
- text-align: center;
- font-weight: bold;
- overflow: hidden;
- height: 13px;
- padding: 2px;
- padding-top: 4px;
- padding-bottom: 6px;
- color: black;
-}
-td.mxWindowPane {
- vertical-align: top;
- padding: 0px;
-}
-div.mxWindowPane {
- overflow: hidden;
- position: relative;
-}
-td.mxWindowPane td {
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio {
- font-family: Arial;
- font-size: 8pt;
- padding: 1px;
-}
-td.mxWindowPane button {
- color: #fff;
- background-color: #337ab7;
- border-color: #2e6da4;
- display: inline-block;
- margin: 2%;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.42857143;
- text-align: center;
- white-space: nowrap;
- vertical-align: middle;
- -ms-touch-action: manipulation;
- touch-action: manipulation;
- cursor: pointer;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- background-image: none;
- border: 1px solid transparent;
- border-radius: 4px;
-}
-img.mxToolbarItem {
- margin-right: 6px;
- margin-bottom: 6px;
- border-width: 1px;
-}
-select.mxToolbarCombo {
- vertical-align: top;
- border-style: inset;
- border-width: 2px;
-}
-div.mxToolbarComboContainer {
- padding: 2px;
-}
-img.mxToolbarMode {
- margin: 2px;
- margin-right: 4px;
- margin-bottom: 4px;
- border-width: 0px;
-}
-img.mxToolbarModeSelected {
- margin: 0px;
- margin-right: 2px;
- margin-bottom: 2px;
- border-width: 2px;
- border-style: inset;
-}
-div.mxTooltip {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: #FFFFCC;
- border-style: solid;
- border-width: 1px;
- border-color: black;
- font-family: Arial;
- font-size: 8pt;
- position: absolute;
- cursor: default;
- padding: 4px;
- color: black;
-}
-div.mxPopupMenu {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: url('/static/img/mxgraph/window.gif');
- position: absolute;
- border-style: solid;
- border-width: 1px;
- border-color: black;
-}
-table.mxPopupMenu {
- border-collapse: collapse;
- margin-top: 1px;
- margin-bottom: 1px;
-}
-tr.mxPopupMenuItem {
- color: black;
- cursor: pointer;
-}
-tr.mxPopupMenuItemHover {
- background-color: #000066;
- color: #FFFFFF;
- cursor: pointer;
-}
-td.mxPopupMenuItem {
- padding: 2px 30px 2px 10px;
- white-space: nowrap;
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxPopupMenuIcon {
- background-color: #D0D0D0;
- padding: 2px 4px 2px 4px;
-}
-.mxDisabled {
- opacity: 0.2 !important;
- cursor:default !important;
-}
diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js
index 84c3703..a03dc98 100644
--- a/src/static/js/dashboard.js
+++ b/src/static/js/dashboard.js
@@ -1,3 +1,46 @@
+///////////////////
+// Global Variables
+///////////////////
+
+form_submission_callbacks = []; //all runnables will be executed before form submission
+
+
+///////////////////
+// Global Functions
+///////////////////
+
+function updatePage(data){
+ updateBreadcrumbs(data['meta']);
+ $("formContainer").html(data['content']);
+}
+
+function submitStepForm(next_step = "current"){
+ run_form_callbacks();
+ const step_form_data = $("#step_form").serialize();
+ const form_data = $.param({
+ "step": next_step,
+ "step_form": step_form_data,
+ "csrfmiddlewaretoken": $("[name=csrfmiddlewaretoken]").val()
+ });
+ console.log(form_data);
+ $.post(
+ '/workflow/manager/',
+ form_data,
+ (data) => updatePage(data),
+ 'json'
+ ).fail(() => alert("failure"));
+}
+
+function run_form_callbacks(){
+ for(f of form_submission_callbacks)
+ f();
+ form_submission_callbacks = [];
+}
+
+///////////////////
+//Class Definitions
+///////////////////
+
class MultipleSelectFilterWidget {
constructor(neighbors, items, initial) {
@@ -98,7 +141,7 @@ class MultipleSelectFilterWidget {
select(node) {
const elem = document.getElementById(node['id']);
node['selected'] = true;
- elem.classList.remove('disabled_node', 'cleared_node');
+ elem.classList.remove('bg-white', 'not-allowed', 'bg-light');
elem.classList.add('selected_node');
}
@@ -106,16 +149,16 @@ class MultipleSelectFilterWidget {
const elem = document.getElementById(node['id']);
node['selected'] = false;
node['selectable'] = true;
- elem.classList.add('cleared_node')
- elem.classList.remove('disabled_node', 'selected_node');
+ elem.classList.add('bg-white')
+ elem.classList.remove('not-allowed', 'bg-light', 'selected_node');
}
disable_node(node) {
const elem = document.getElementById(node['id']);
node['selected'] = false;
node['selectable'] = false;
- elem.classList.remove('cleared_node', 'selected_node');
- elem.classList.add('disabled_node');
+ elem.classList.remove('bg-white', 'selected_node');
+ elem.classList.add('not-allowed', 'bg-light');
}
processClick(id){
@@ -173,7 +216,7 @@ class MultipleSelectFilterWidget {
const button = document.createElement("BUTTON");
button.type = "button";
button.appendChild(document.createTextNode("Remove"));
- button.classList.add("btn", "btn-danger");
+ button.classList.add("btn", "btn-danger", "d-inline-block");
const that = this;
button.onclick = function(){ that.remove_dropdown(div.id, node.id); }
return button;
@@ -183,6 +226,7 @@ class MultipleSelectFilterWidget {
const input = document.createElement("INPUT");
input.type = node.form.type;
input.name = node.id + node.form.name
+ input.classList.add("form-control", "w-auto", "d-inline-block");
input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})";
input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"
input.placeholder = node.form.placeholder;
@@ -198,7 +242,7 @@ class MultipleSelectFilterWidget {
add_item_prepopulate(node, prepopulate){
const div = document.createElement("DIV");
div.id = "dropdown_" + this.dropdown_count;
- div.classList.add("dropdown_item");
+ div.classList.add("list-group-item");
this.dropdown_count++;
const label = document.createElement("H5")
label.appendChild(document.createTextNode(node['name']))
@@ -756,21 +800,28 @@ class NetworkStep {
}
makeSidebarNetwork(net_name, color, net_id){
- const newNet = document.createElement("li");
const colorBlob = document.createElement("div");
- colorBlob.className = "colorblob";
- const textContainer = document.createElement("p");
- textContainer.className = "network_innertext";
- newNet.id = net_id;
+ colorBlob.className = "square-20 rounded-circle";
+ colorBlob.style['background'] = color;
+
+ const textContainer = document.createElement("span");
+ textContainer.className = "ml-2";
+ textContainer.appendChild(document.createTextNode(net_name));
+
+ const timesIcon = document.createElement("i");
+ timesIcon.classList.add("fas", "fa-times");
+
const deletebutton = document.createElement("button");
- deletebutton.className = "btn btn-danger";
- deletebutton.style = "float: right; height: 20px; line-height: 8px; vertical-align: middle; width: 20px; padding-left: 5px;";
- deletebutton.appendChild(document.createTextNode("X"));
+ deletebutton.className = "btn btn-danger ml-auto square-20 p-0 d-flex justify-content-center";
+ deletebutton.appendChild(timesIcon);
deletebutton.addEventListener("click", function() { this.createDeleteDialog(net_id); }.bind(this), false);
- textContainer.appendChild(document.createTextNode(net_name));
- colorBlob.style['background'] = color;
+
+ const newNet = document.createElement("li");
+ newNet.classList.add("list-group-item", "d-flex", "bg-light");
+ newNet.id = net_id;
newNet.appendChild(colorBlob);
newNet.appendChild(textContainer);
+
if( net_name != "public" ) {
newNet.appendChild(deletebutton);
}
@@ -818,16 +869,9 @@ class NetworkStep {
this.graph.refresh(host);
}
- submitForm() {
- const form = document.getElementById("xml_form");
+ prepareForm() {
const input_elem = document.getElementById("hidden_xml_input");
input_elem.value = this.encodeGraph(this.graph);
- const req = new XMLHttpRequest();
- req.open("POST", "/wf/workflow/", false);
- req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- req.onerror = function() { alert("problem with form submission"); }
- const formData = $("#xml_form").serialize();
- req.send(formData);
}
}
@@ -1048,18 +1092,19 @@ class SearchableSelectMultipleWidget {
for( const id in ids )
{
- const result_entry = document.createElement("li");
- const result_button = document.createElement("a");
const obj = this.items[id];
const result_text = this.generate_element_text(obj);
- result_button.appendChild(document.createTextNode(result_text));
- result_button.onclick = function() { searchable_select_multiple_widget.select_item(obj.id); };
+ const result_entry = document.createElement("a");
+ result_entry.href = "#";
+ result_entry.innerText = result_text;
+ result_entry.title = result_text;
+ result_entry.classList.add("list-group-item", "list-group-item-action", "overflow-ellipsis", "flex-shrink-0");
+ result_entry.onclick = function() { searchable_select_multiple_widget.select_item(obj.id); };
const tooltip = document.createElement("span");
const tooltiptext = document.createTextNode(result_text);
tooltip.appendChild(tooltiptext);
- tooltip.setAttribute('class', 'entry_tooltip');
- result_button.appendChild(tooltip);
- result_entry.appendChild(result_button);
+ tooltip.classList.add("d-none");
+ result_entry.appendChild(tooltip);
drop.appendChild(result_entry);
}
@@ -1112,23 +1157,39 @@ class SearchableSelectMultipleWidget {
added_list.removeChild(added_list.firstChild);
}
- let list_html = "";
+ const list_html = document.createElement("div");
+ list_html.classList.add("list-group");
for( const item_id of this.added_items )
{
- const item = this.items[item_id];
+ const times = document.createElement("li");
+ times.classList.add("fas", "fa-times");
+
+ const deleteButton = document.createElement("a");
+ deleteButton.href = "#";
+ deleteButton.innerHTML = "<i class='fas fa-times'></i>"
+ // Setting .onclick/.addEventListener does not work,
+ // which is why I took the setAttribute approach
+ // If anyone knows why, please let me know :]
+ deleteButton.setAttribute("onclick", `searchable_select_multiple_widget.remove_item(${item_id});`);
+ deleteButton.classList.add("btn");
+ const deleteColumn = document.createElement("div");
+ deleteColumn.classList.add("col-auto");
+ deleteColumn.append(deleteButton);
+ const item = this.items[item_id];
const element_entry_text = this.generate_element_text(item);
+ const textColumn = document.createElement("div");
+ textColumn.classList.add("col", "overflow-ellipsis");
+ textColumn.innerText = element_entry_text;
+ textColumn.title = element_entry_text;
+
+ const itemRow = document.createElement("div");
+ itemRow.classList.add("list-group-item", "d-flex", "p-0", "align-items-center");
+ itemRow.append(textColumn, deleteColumn);
- list_html += '<div class="list_entry">'
- + '<p class="added_entry_text">'
- + element_entry_text
- + '</p>'
- + '<button onclick="searchable_select_multiple_widget.remove_item('
- + item_id
- + ')" class="btn-remove btn">remove</button>';
- list_html += '</div>';
+ list_html.append(itemRow);
}
- added_list.innerHTML = list_html;
+ added_list.innerHTML = list_html.innerHTML;
}
}
diff --git a/src/templates/account/booking_list.html b/src/templates/account/booking_list.html
index 98ab5c8..55c6c0d 100644
--- a/src/templates/account/booking_list.html
+++ b/src/templates/account/booking_list.html
@@ -1,88 +1,94 @@
{% extends "base.html" %}
{% block content %}
<h2>Bookings I Own</h2>
- <div class="card_container">
- {% for booking in bookings %}
- <div class="card">
- <div class="card-header">
- <h3>Booking {{booking.id}}</h3>
- </div>
- <div class="card-body">
- <ul class="list-group">
- <li class="list-group-item">id: {{booking.id}}</li>
- <li class="list-group-item">lab: {{booking.lab}}</li>
- <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
- <li class="list-group-item">start: {{booking.start}}</li>
- <li class="list-group-item">end: {{booking.end}}</li>
- <li class="list-group-item">purpose: {{booking.purpose}}</li>
- </ul>
- </div>
- <div class="card-footer d-flex">
- <a class="btn btn-primary ml-auto mr-2" href="/booking/detail/{{booking.id}}/">Details</a>
- <button
- class="btn btn-danger"
- onclick='cancel_booking({{booking.id}});'
- data-toggle="modal"
- data-target="#resModal"
- >Cancel</button>
+ <div class="row">
+ {% for booking in bookings %}
+ <div class="col-12 col-md-6 col-lg-4 col-xl-3 p-2">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3>Booking {{booking.id}}</h3>
+ </div>
+ <div class="card-body">
+ <ul class="list-group">
+ <li class="list-group-item">id: {{booking.id}}</li>
+ <li class="list-group-item">lab: {{booking.lab}}</li>
+ <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
+ <li class="list-group-item">start: {{booking.start}}</li>
+ <li class="list-group-item">end: {{booking.end}}</li>
+ <li class="list-group-item">purpose: {{booking.purpose}}</li>
+ </ul>
+ </div>
+ <div class="card-footer d-flex">
+ <a class="btn btn-primary ml-auto mr-2" href="/booking/detail/{{booking.id}}/">Details</a>
+ <button
+ class="btn btn-danger"
+ onclick='cancel_booking({{booking.id}});'
+ data-toggle="modal"
+ data-target="#resModal"
+ >Cancel</button>
+ </div>
+ </div>
</div>
- </div>
- {% endfor %}
+ {% endfor %}
</div>
<h2>Bookings I Collaborate On</h2>
- <div class="card_container">
+ <div class="row">
{% for booking in collab_bookings %}
- <div class="card">
- <div class="card-header">
- <h3>Booking {{booking.id}}</h3>
- </div>
- <div class="card-body">
- <ul class="list-group">
- <li class="list-group-item">id: {{booking.id}}</li>
- <li class="list-group-item">lab: {{booking.lab}}</li>
- <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
- <li class="list-group-item">start: {{booking.start}}</li>
- <li class="list-group-item">end: {{booking.end}}</li>
- <li class="list-group-item">purpose: {{booking.purpose}}</li>
- </ul>
- </div>
- <div class="card-footer d-flex">
- <a class="btn btn-primary ml-auto" href="/booking/detail/{{booking.id}}/">Details</a>
+ <div class="col-12 col-md-6 col-lg-4 col-xl-3 p-2">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3>Booking {{booking.id}}</h3>
+ </div>
+ <div class="card-body">
+ <ul class="list-group">
+ <li class="list-group-item">id: {{booking.id}}</li>
+ <li class="list-group-item">lab: {{booking.lab}}</li>
+ <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
+ <li class="list-group-item">start: {{booking.start}}</li>
+ <li class="list-group-item">end: {{booking.end}}</li>
+ <li class="list-group-item">purpose: {{booking.purpose}}</li>
+ </ul>
+ </div>
+ <div class="card-footer d-flex">
+ <a class="btn btn-primary ml-auto" href="/booking/detail/{{booking.id}}/">Details</a>
+ </div>
</div>
</div>
{% endfor %}
</div>
- <h2>Expired Bookings
- <i class="fa fa-fw fa-caret-down" onclick='toggle_display("expired_bookings");'></i>
- </h2>
- <div id="expired_bookings" class="card_container" style="display:none;">
- {% for booking in expired_bookings %}
- <div class="card">
- <div class="card-header">
- <h3>Booking {{booking.id}}</h3>
- </div>
- <div class="card-body">
- <ul class="list-group">
- <li class="list-group-item">id: {{booking.id}}</li>
- <li class="list-group-item">lab: {{booking.lab}}</li>
- <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
- <li class="list-group-item">start: {{booking.start}}</li>
- <li class="list-group-item">end: {{booking.end}}</li>
- <li class="list-group-item">purpose: {{booking.purpose}}</li>
- <li class="list-group-item">owner: {{booking.owner.userprofile.email_addr}}</li>
- </ul>
- </div>
- <div class="card-footer d-flex">
- <a class="btn btn-primary ml-auto" href="/booking/detail/{{booking.id}}/">Details</a>
+ <a href="#expired_bookings" data-toggle="collapse" class="h2 discrete-a">
+ Expired Bookings
+ <i class="fas fa-angle-down rotate"></i>
+ </a>
+ <div id="expired_bookings" class="row collapse">
+ {% for booking in expired_bookings %}
+ <div class="col-12 col-md-6 col-lg-4 col-xl-3 p-2">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3>Booking {{booking.id}}</h3>
+ </div>
+ <div class="card-body">
+ <ul class="list-group">
+ <li class="list-group-item">id: {{booking.id}}</li>
+ <li class="list-group-item">lab: {{booking.lab}}</li>
+ <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
+ <li class="list-group-item">start: {{booking.start}}</li>
+ <li class="list-group-item">end: {{booking.end}}</li>
+ <li class="list-group-item">purpose: {{booking.purpose}}</li>
+ <li class="list-group-item">owner: {{booking.owner.userprofile.email_addr}}</li>
+ </ul>
+ </div>
+ <div class="card-footer d-flex">
+ <a class="btn btn-primary ml-auto" href="/booking/detail/{{booking.id}}/">Details</a>
+ </div>
+ </div>
</div>
- </div>
- {% endfor %}
+ {% endfor %}
</div>
<script>
var current_booking_id = -1;
function cancel_booking(booking_id) {
current_booking_id = booking_id;
- document.getElementById('modal_warning').style['max-height'] = '0px';
}
function submit_cancel_form() {
@@ -95,22 +101,12 @@
req.onerror = function() { alert("problem submitting form"); }
req.send(formData);
}
-
- function toggle_display(elem_id){
- var e = document.getElementById(elem_id);
- if (e.style.display === "none"){
- e.style.display = "grid";
- } else {
- e.style.display = "none";
- }
- }
</script>
-<div class="modal fade" id="resModal" tabindex="-1" role="dialog" aria-labelledby="my_modal" aria-hidden="true">
- <div class="modal-dialog" style="width: 450px;" role="document">
+<div class="modal fade" id="resModal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="my_modal" style="display: inline; float: left;">Cancel Booking?</h4>
- <p>Everthing on your machine(s) will be lost</p>
+ <h4 class="modal-title d-inline float-left">Cancel Booking?</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -118,16 +114,19 @@
<form id="booking_cancel_form">
{% csrf_token %}
</form>
- <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';">Cancel Booking</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 cannot be undone</p>
- <button class="btn" onclick="document.getElementById('modal_warning').style['max-height'] = '0px';">Nevermind</button>
- <button class="btn btn-danger" id="confirm_cancel_button" data-dismiss="modal" onclick="submit_cancel_form();">I'm Sure</button>
+ <div class="modal-footer d-flex flex-column">
+ <p>Everything on your machine(s) will be lost</p>
+ <div class="mb-2">
+ <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="#warning">Cancel Booking</button>
+ </div>
+ <div class="collapse w-100 text-center border-top" id="warning">
+ <div class="p-4">
+ <h3>Are You Sure?</h3>
+ <p>This cannot be undone</p>
+ <button class="btn btn-outline-secondary" data-dismiss="modal">Nevermind</button>
+ <button class="btn btn-danger" id="confirm_cancel_button" data-dismiss="modal" onclick="submit_cancel_form();">I'm Sure</button>
+ </div>
</div>
</div>
</div>
diff --git a/src/templates/account/configuration_list.html b/src/templates/account/configuration_list.html
index 6f7844a..94a1120 100644
--- a/src/templates/account/configuration_list.html
+++ b/src/templates/account/configuration_list.html
@@ -1,26 +1,28 @@
{% extends "base.html" %}
{% block content %}
-<div class="card_container">
+<div class="row">
{% for config in configurations %}
- <div class="card">
- <div class="card-header">
- <h3>Configuration {{config.id}}</h3>
- </div>
- <div class="card-body">
- <ul class="list-group">
- <li class="list-group-item">id: {{config.id}}</li>
- <li class="list-group-item">name: {{config.name}}</li>
- <li class="list-group-item">description: {{config.description}}</li>
- <li class="list-group-item">resource: {{config.bundle}}</li>
- </ul>
- </div>
- <div class="card-footer">
- <button
- class="btn btn-danger w-100"
- onclick='delete_config({{config.id}});'
- data-toggle="modal"
- data-target="#configModal"
- >Delete</button>
+ <div class="p-2 col-12 col-md-6 col-lg-4 col-xl-3">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3>Configuration {{config.id}}</h3>
+ </div>
+ <div class="card-body">
+ <ul class="list-group">
+ <li class="list-group-item">id: {{config.id}}</li>
+ <li class="list-group-item">name: {{config.name}}</li>
+ <li class="list-group-item">description: {{config.description}}</li>
+ <li class="list-group-item">resource: {{config.bundle}}</li>
+ </ul>
+ </div>
+ <div class="card-footer">
+ <button
+ class="btn btn-danger w-100"
+ onclick='delete_config({{config.id}});'
+ data-toggle="modal"
+ data-target="#configModal"
+ >Delete</button>
+ </div>
</div>
</div>
{% endfor %}
@@ -29,7 +31,6 @@
var current_config_id = -1;
function delete_config(config_id) {
current_config_id = config_id;
- document.getElementById('modal_warning').style['max-height'] = '0px';
}
function submit_delete_form() {
@@ -43,11 +44,11 @@
req.send(formData);
}
</script>
-<div class="modal fade" id="configModal" tabindex="-1" role="dialog" aria-labelledby="my_modal" aria-hidden="true">
- <div class="modal-dialog" style="width: 450px;" role="document">
+<div class="modal fade" id="configModal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="my_modal" style="display: inline; float: left;">Delete Configuration?</h4>
+ <h4 class="modal-title d-inline float-left">Delete Configuration?</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -55,17 +56,20 @@
<form id="config_delete_form">
{% csrf_token %}
</form>
- <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';">Delete</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 cannot be undone</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_delete_form();">I'm Sure</button>
+ <div class="modal-footer d-flex flex-column">
+ <div class="mb-2">
+ <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="#warning">Delete</button>
</div>
+ <div class="collapse w-100 text-center border-top" id="warning">
+ <div class="p-4">
+ <h3>Are You Sure?</h3>
+ <p>This cannot be undone</p>
+ <button class="btn btn-outline-secondary" data-dismiss="modal">Nevermind</button>
+ <button class="btn btn-danger" data-dismiss="modal" onclick="submit_delete_form();">I'm Sure</button>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/src/templates/account/image_list.html b/src/templates/account/image_list.html
index 068e096..a626710 100644
--- a/src/templates/account/image_list.html
+++ b/src/templates/account/image_list.html
@@ -1,36 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h2>Images I Own</h2>
-<div class="card_container">
+<div class="row">
{% for image in images %}
- <div class="card">
- <div class="card-header">
- <h3>Image {{image.id}}</h3>
- </div>
- <div class="card-body">
- <ul class="list-group">
- <li class="list-group-item">id: {{image.id}}</li>
- <li class="list-group-item">lab: {{image.from_lab.name}}</li>
- <li class="list-group-item">name: {{image.name}}</li>
- <li class="list-group-item">description: {{image.description}}</li>
- <li class="list-group-item">host profile: {{image.host_type.name}}</li>
- </ul>
- </div>
- <div class="card-footer">
- <button
- class="btn btn-danger w-100"
- onclick='delete_image({{image.id}});'
- data-toggle="modal"
- data-target="#imageModal"
- >Delete</button>
- </div>
- </div>
-{% endfor %}
-</div>
-<h2>Public Images</h2>
-<div class="card_container">
- {% for image in public_images %}
- <div class="card">
+ <div class="p-2 col-12 col-md-6 col-lg-4 col-xl-3">
+ <div class="card h-100">
<div class="card-header">
<h3>Image {{image.id}}</h3>
</div>
@@ -43,6 +17,34 @@
<li class="list-group-item">host profile: {{image.host_type.name}}</li>
</ul>
</div>
+ <div class="card-footer">
+ <button class="btn btn-danger w-100" onclick='delete_image({{image.id}});'
+ data-toggle="modal" data-target="#imageModal">
+ Delete
+ </button>
+ </div>
+ </div>
+ </div>
+{% endfor %}
+</div>
+<h2>Public Images</h2>
+<div class="row">
+ {% for image in public_images %}
+ <div class="p-2 col-12 col-md-6 col-lg-4 col-xl-3">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3>Image {{image.id}}</h3>
+ </div>
+ <div class="card-body">
+ <ul class="list-group">
+ <li class="list-group-item">id: {{image.id}}</li>
+ <li class="list-group-item">lab: {{image.from_lab.name}}</li>
+ <li class="list-group-item">name: {{image.name}}</li>
+ <li class="list-group-item">description: {{image.description}}</li>
+ <li class="list-group-item">host profile: {{image.host_type.name}}</li>
+ </ul>
+ </div>
+ </div>
</div>
{% endfor %}
</div>
@@ -52,7 +54,6 @@
var used_images = {{used_images|safe|default:"{}"}};
function delete_image(image_id) {
current_image_id = image_id;
- document.getElementById('modal_warning').style['max-height'] = '0px';
var warning_header = document.getElementById("warning_header");
var warning_text = document.getElementById("warning_text");
var delete_image_button = document.getElementById("final_delete_b");
@@ -94,11 +95,11 @@
}
}
</script>
-<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-labelledby="my_modal" aria-hidden="true">
- <div class="modal-dialog" style="width: 450px;" role="document">
+<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="my_modal" style="display: inline; float: left;">Delete Configuration?</h4>
+ <h4 class="modal-title d-inline float-left">Delete Configuration?</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -106,17 +107,20 @@
<form id="image_delete_form">
{% csrf_token %}
</form>
- <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';">Delete</button>
- </div>
- <div id="modal_warning" class="modal-footer" style="max-height:0px;" >
- <div style="text-align:center; margin: 5px">
- <h3 id="warning_header">Are You Sure?</h3>
- <p id="warning_text">This cannot be undone</p>
- <button class="btn" onclick="document.getElementById('modal_warning').style['max-height'] = '0px';">Nevermind</button>
- <button id="final_delete_b" class="btn btn-danger" data-dismiss="modal" onclick="submit_delete_form();">I'm Sure</button>
+ <div class="modal-footer d-flex flex-column">
+ <div class="mb-2">
+ <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="#warning">Delete</button>
</div>
+ <div class="collapse w-100 text-center border-top" id="warning">
+ <div class="p-4">
+ <h3 id="warning_header">Are You Sure?</h3>
+ <p id="warning_text">This cannot be undone</p>
+ <button class="btn btn-outline-secondary" data-dismiss="modal">Nevermind</button>
+ <button id="final_delete_b" class="btn btn-danger" data-dismiss="modal" onclick="submit_delete_form();">I'm Sure</button>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/src/templates/account/resource_list.html b/src/templates/account/resource_list.html
index f92f78e..1203534 100644
--- a/src/templates/account/resource_list.html
+++ b/src/templates/account/resource_list.html
@@ -1,25 +1,27 @@
{% extends "base.html" %}
{% block content %}
-<div class="card_container">
+<div class="row">
{% for resource in resources %}
- <div class="card">
- <div class="card-header">
- <h3>Resource {{resource.id}}</h3>
- </div>
- <div class="card-body p-4">
- <ul class="list-group">
- <li class="list-group-item">id: {{resource.id}}</li>
- <li class="list-group-item">name: {{resource.name}}</li>
- <li class="list-group-item">description: {{resource.description}}</li>
- </ul>
- </div>
- <div class="card-footer">
- <button
- class="btn btn-danger w-100"
- onclick='delete_resource({{resource.id}});'
- data-toggle="modal"
- data-target="#resModal"
- >Delete</button>
+ <div class="col-12 col-md-6 col-lg-4 col-xl-3 p-2">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3>Resource {{resource.id}}</h3>
+ </div>
+ <div class="card-body p-4">
+ <ul class="list-group">
+ <li class="list-group-item">id: {{resource.id}}</li>
+ <li class="list-group-item">name: {{resource.name}}</li>
+ <li class="list-group-item">description: {{resource.description}}</li>
+ </ul>
+ </div>
+ <div class="card-footer">
+ <button
+ class="btn btn-danger w-100"
+ onclick='delete_resource({{resource.id}});'
+ data-toggle="modal"
+ data-target="#resModal"
+ >Delete</button>
+ </div>
</div>
</div>
{% endfor %}
@@ -63,7 +65,6 @@
warning_title.appendChild(title);
warning_text.appendChild(text);
- document.getElementById('modal_warning').style['max-height'] = '0px';
}
function list_configs(configs) {
@@ -87,11 +88,11 @@
req.send(formData);
}
</script>
-<div class="modal fade" id="resModal" tabindex="-1" role="dialog" aria-labelledby="my_modal" aria-hidden="true">
- <div class="modal-dialog" style="width: 450px;" role="document">
+<div class="modal fade" id="resModal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="my_modal" style="display: inline; float: left;">Delete Resource?</h4>
+ <h4 class="modal-title d-inline float-left">Delete Resource?</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -99,18 +100,21 @@
<form id="res_delete_form">
{% csrf_token %}
</form>
- <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';">Delete</button>
- </div>
- <div id="modal_warning" class="modal-footer" style="max-height:0px;" >
- <div style="text-align:center; margin: 5px">
- <h3 id="config_warning">Are You Sure?</h3>
- <p id="warning_subtext">This cannot be undone</p>
- <ul id="config_list"></ul>
- <button class="btn" onclick="document.getElementById('modal_warning').style['max-height'] = '0px';">Nevermind</button>
- <button class="btn btn-danger" id="confirm_delete_button" data-dismiss="modal" onclick="submit_delete_form();">I'm Sure</button>
+ <div class="modal-footer d-flex flex-column">
+ <div class="mb-2">
+ <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="#warning">Delete</button>
</div>
+ <div class="collapse w-100 text-center border-top" id="warning">
+ <div class="p-4">
+ <h3 id="config_warning">Are You Sure?</h3>
+ <p id="warning_subtext">This cannot be undone</p>
+ <ul id="config_list"></ul>
+ <button class="btn btn-outline-secondary" data-dismiss="modal">Nevermind</button>
+ <button class="btn btn-danger" id="confirm_delete_button" data-dismiss="modal" onclick="submit_delete_form();">I'm Sure</button>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/src/templates/account/userprofile_update_form.html b/src/templates/account/userprofile_update_form.html
index 6ab8242..5ddb867 100644
--- a/src/templates/account/userprofile_update_form.html
+++ b/src/templates/account/userprofile_update_form.html
@@ -16,7 +16,7 @@
Generate
</a>
</p>
- <p style="word-wrap: break-word;">{{ token.key }}</p>
+ <p class="text-break">{{ token.key }}</p>
<p></p>
{% buttons %}
diff --git a/src/templates/base.html b/src/templates/base.html
index 62a9ed5..891e0fc 100644
--- a/src/templates/base.html
+++ b/src/templates/base.html
@@ -5,7 +5,6 @@
<!-- Custom CSS -->
-<link href="{% static "css/detail_view.css" %}" rel="stylesheet">
<link href="{% static "css/base.css" %}" rel="stylesheet">
<script type="text/javascript">
function cwf(type) {
@@ -30,70 +29,33 @@
function continue_wf() {
window.location.replace("/wf/");
}
-
- function toggle_create_drop() {
- drop_div = document.getElementById("create_drop");
-
- if (drop_div.style.display === "none") {
- drop_div.style.display = "inherit";
- } else {
- drop_div.style.display = "none";
- }
- }
</script>
-<style>
- .navbar {
- min-width: 200px;
- }
-
- .create_drop {
- display: none;
- width: 100%;
- }
-
- .create_drop button {
- width: 100%;
- }
-
- .drop_btn {
- border-radius: 0px;
- background-color: CCCCCC
- }
-
- .drop_btn:hover {
- color: #555;
- border-top: 1px solid #E7E7E7;
- border-bottom: 1px solid #E7E7E7;
- }
-
- #wrapper {
- height: 100vh;
- }
-</style>
{% endblock %}
{% block basecontent %}
<div id="wrapper" class="d-flex flex-column">
<!-- Navigation -->
- <nav class="navbar navbar-light bg-light navbar-fixed-top border-bottom py-0" role="navigation" style="margin-bottom: 0">
- <div class="container-fluid">
- <div class="col order-2 order-lg-1 text-center text-lg-left">
+ <nav class="navbar navbar-light bg-light navbar-fixed-top border-bottom py-0 mb-0" role="navigation">
+ <div class="container-fluid pb-2 pb-sm-0">
+ <!-- Logo -->
+ <div class="col-12 col-sm order-1 order-sm-2 text-center text-lg-left">
<a href="https://www.opnfv.org/" class="navbar-brand">
<img src="{% static "img/opnfv-logo.png" %}">
</a>
- <a class="navbar-brand" href={% url 'dashboard:index' %}>
+ <a class="navbar-brand d-none d-lg-inline" href={% url 'dashboard:index' %}>
Pharos Dashboard
</a>
</div>
- <!-- /.navbar-header -->
- <div class="col-2 order-1 order-lg-3 d-lg-none">
- <button class="btn border" type="button" data-toggle="collapse" data-target="#sidebar"
+ <!-- Sidebar button -->
+ <div class="col-6 col-sm-2 d-flex order-1 order-lg-3 d-lg-none">
+ <button class="btn border mx-auto" type="button" data-toggle="collapse" data-target="#sidebar"
aria-expanded="false" aria-controls="sidebar">
- <i class="fas fa-bars"></i>
+ <i class="fas fa-bars d-inline"></i>
</button>
</div>
- <div class="col-2 order-3">
- <ul class="nav ml-auto">
+ <!-- Login dropdown -->
+ <div class="col-6 col-sm-2 order-3 d-flex">
+ <ul class="nav mx-auto mr-sm-0">
<li class="dropdown ml-auto">
<a class="nav-link p-0 text-dark p-2" data-toggle="dropdown" href="#">
{% if request.user.username %}
@@ -119,19 +81,18 @@
Login with Jira
</a>
{% endif %}
- </div> <!-- End dropdown-menu -->
- </li> <!-- End dropdown -->
+ </div>
+ </li>
</ul>
- </div> <!-- End top right account menu -->
+ </div>
</div>
</nav>
- <!-- /.navbar-top-links -->
<!-- Page Content -->
- <div class="container-fluid d-lg-flex flex-lg-grow-1 px-0">
- <div class="row h-100 w-100 mx-0">
+ <div class="container-fluid d-flex flex-grow-1 px-0 align-items-start flex-column">
+ <div class="row flex-grow-1 w-100 mx-0 align-content-start flex-lg-grow-1">
<div class="col-12 col-lg-auto px-0 border-right border-left bg-light" role="navigation">
- <nav class="navbar navbar-expand-lg border-bottom p-0 w-100">
+ <nav class="navbar navbar-expand-lg border-bottom p-0 w-100 sidebar">
<div class="collapse navbar-collapse" id="sidebar">
<div class="list-group list-group-flush w-100 bg-light">
<a href="/" class="list-group-item list-group-item-action bg-light">
@@ -176,9 +137,6 @@
<a href="{% url 'booking:stats' %}" class="list-group-item list-group-item-action bg-light">
Booking Statistics
</a>
- <!-- <a href="{% url 'account:my-account' %}" class="list-group-item list-group-item-action bg-light">
- Account
- </a> -->
<a class="list-group-item list-group-item-action bg-light" data-toggle="collapse"
href="#accountList" role="button">
Account <i class="fas fa-angle-down rotate"></i>
@@ -206,17 +164,13 @@
</div>
</div>
</nav>
- <!--/.well -->
</div>
- <!--/span-->
-
- <div class="col flex-grow-1 d-flex flex-column">
+ <div class="col overflow-auto flex-grow-1 d-flex flex-column h-100">
{% if title %}
<div class="row flex-shrink-1">
<div class="col-lg-12">
<h1 class="page-header">{{ title }}</h1>
</div>
- <!-- /.col-lg-12 -->
</div>
{% endif %}
<div id="bsm">{% bootstrap_messages %}</div>
@@ -224,13 +178,7 @@
{% block content %}
{% endblock content %}
</div>
- <!--/span-->
-
-
</div>
- <!--/row-->
</div>
- <!-- /#page-wrapper -->
</div>
-<!-- /#wrapper -->
{% endblock basecontent %}
diff --git a/src/templates/booking/booking_calendar.html b/src/templates/booking/booking_calendar.html
index ddcb45d..450c97f 100644
--- a/src/templates/booking/booking_calendar.html
+++ b/src/templates/booking/booking_calendar.html
@@ -23,9 +23,7 @@
<div id='calendar'>
</div>
</div>
- <!-- /.panel-body -->
</div>
- <!-- /.panel -->
</div>
</div>
diff --git a/src/templates/booking/booking_detail.html b/src/templates/booking/booking_detail.html
index 918f5af..99de747 100644
--- a/src/templates/booking/booking_detail.html
+++ b/src/templates/booking/booking_detail.html
@@ -4,174 +4,164 @@
{% block extrahead %}
{{block.super}}
-<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></script>
+ <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></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-12 col-lg-5">
<div class="card mb-4">
<div class="card-header d-flex">
- <h4 style="display: inline;">Overview</h4>
+ <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="card-body collapse show" id="panel_overview">
- <table class="table">
- <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 class="collapse show" id="panel_overview">
+ <div class="card-body">
+ <table class="table">
+ <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>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header d-flex">
- <h4 style="display: inline;">Pod</h4>
+ <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="card-body collapse show" id="pod_panel">
- <table class="table">
- {% for host in booking.resource.hosts.all %}
- <tr>
- <td><h4>{{host.template.resource.name}}</h4></td>
- <td>
- <table class="table">
- <tr>
- <td>Hostname:</td>
- <td>{{host.template.resource.name}}</td>
- </tr>
- <tr>
- <td>Profile:</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
- 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>
- <td>{{host.profile.ramprofile.first.amount}}G,
- {{host.profile.ramprofile.first.channels}} channels</td>
- </tr>
- <tr>
- <td>CPU:</td>
- <td>
- <table class="table">
- <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">
- <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>
- <style>
- .borderless td {
- border: none !important;
- }
- </style>
- <table class="table">
- {% for intprof in host.profile.interfaceprofile.all %}
- <tr>
- <td>
- <table class="table borderless">
+ <div class="collapse show" id="pod_panel">
+ <div class="card-body">
+ <table class="table">
+ {% for host in booking.resource.hosts.all %}
+ <tr>
+ <td><h4>{{host.template.resource.name}}</h4></td>
+ <td>
+ <table class="table">
+ <tr>
+ <td>Hostname:</td>
+ <td>{{host.template.resource.name}}</td>
+ </tr>
+ <tr>
+ <td>Profile:</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">
+ <tr>
+ <td>Arch:</td>
+ <td>{{host.profile.cpuprofile.first.architecture}}</td>
+ </tr>
<tr>
- <td>Name:</td>
- <td>{{intprof.name}}</td>
+ <td>Cores:</td>
+ <td>{{host.profile.cpuprofile.first.cores}}</td>
</tr>
<tr>
- <td>Speed:</td>
- <td>{{intprof.speed}}</td>
+ <td>Sockets:</td>
+ <td>{{host.profile.cpuprofile.first.cpus}}</td>
</tr>
</table>
- </td>
- </tr>
- {% endfor %}
- </table>
- </td>
- </tr>
- </table>
- </td>
- {% endfor %}
- </tr>
- </table>
+ </td>
+ </tr>
+ <tr>
+ <td>DISK:</td>
+ <td>
+ <table class="table">
+ <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">
+ {% for intprof in host.profile.interfaceprofile.all %}
+ <tr>
+ <td>
+ <table class="table table-borderless">
+ <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>
+ </td>
+ {% endfor %}
+ </tr>
+ </table>
+ </div>
</div>
</div>
</div>
@@ -180,105 +170,72 @@
<div class="col">
<div class="card mb-4">
<div class="card-header d-flex">
- <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>
+ <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="card-body collapse show" id="panel_tasks">
- <table class="table">
- <style>
- .progress {
- display: inline-block;
- border: 3px solid #f3f3f3;
- border-radius: 50%;
- border-top: 3px solid #12aebb;
- width: 20px;
- height: 20px;
- -webkit-animation: spin 2s linear infinite; /* Safari */
- animation: spin 2s linear infinite;
- }
-
- @keyframes spin {
- 0% {transform: rotate(0deg);}
- 100% {transform: rotate(360deg);}
- }
-
- .new {
- display: inline-block;
- width: 20px;
- height: 20px;
- background: #f3f3f3;
- border-radius: 50%;
- animation: fadeInOut 1s infinite alternate;
-
- }
- @keyframes fadeInOut {
- from { opacity: 0;}
- }
- .done {
- display: inline-block;
- width: 20px;
- height: 20px;
- background: #40B976;
- border-radius: 50%;
- }
- </style>
- <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="new"></div>
- {% elif task.status < 200 %}
- <div class="progress"></div>
- {% else %}
- <div class="done"></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>{{ task.message }}</pre>
- {% endif %}
- {% else %}
- No response provided (yet)
- {% endif %}
- </td>
- <td>
- {{ task.type_str }}
- </td>
- </tr>
- {% endfor %}
- </table>
+ <div class="collapse show" id="panel_tasks">
+ <div class="card-body">
+ <table class="table">
+ <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>{{ task.message }}</pre>
+ {% endif %}
+ {% else %}
+ No response provided (yet)
+ {% endif %}
+ </td>
+ <td>
+ {{ task.type_str }}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header d-flex">
- <h4 style="display: inline;">PDF</h4>
+ <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="card-body collapse show" id="pdf_panel" style="padding: 0px;">
- <pre class="prettyprint lang-yaml" style="margin: 0px; padding: 15px; border: none;">
- {{pdf}}
- </pre>
+ <div class="collapse show" id="pdf_panel">
+ <div class="card-body p-0">
+ <pre class="prettyprint lang-yaml m-0 p-4 border-0">
+ {{pdf}}
+ </pre>
+ </div>
</div>
</div>
</div>
@@ -289,10 +246,10 @@
<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-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="exampleModalLabel" style="display: inline; float: left;">Host Image</h4>
+ <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>
@@ -300,23 +257,24 @@
<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 class="form-control" 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">
+ <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" onclick="document.getElementById('modal_warning').style['max-height'] = '0px';">Nevermind</button>
+ <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>
@@ -342,7 +300,7 @@
dropdown.options.add(opt);
}
- document.getElementById("modal_warning").style['max-height'] = '0px';
+ document.getElementById("modal_warning").classList.add("collapse");
}
function submit_image_form() {
diff --git a/src/templates/booking/quick_deploy.html b/src/templates/booking/quick_deploy.html
index 07f3d89..6776980 100644
--- a/src/templates/booking/quick_deploy.html
+++ b/src/templates/booking/quick_deploy.html
@@ -2,88 +2,61 @@
{% load staticfiles %}
{% load bootstrap4 %}
{% block content %}
-<style>
- .grid_container {
- display: grid;
- grid-template-columns: repeat(12, 1fr);
- padding: 30px;
- }
- .grid_element {
- border-radius: 5px;
- border: 1px solid #ccc;
- margin: 10px;
- padding: 7px;
- }
- .grid_element_wide {
- grid-column-start: span 12;
- }
- .grid_element_half {
- grid-column-start: span 6;
- }
- .grid_element_1third {
- grid-column-start: span 4;
- }
- .grid_element_2third {
- grid-column-start: span 8;
- }
- .collaborator_pane {
- display: flex;
- flex-direction: column;
- }
- #id_length {
- -moz-appearance: none;
- border: none;
- box-shadow: none;
- }
- input[type=range]::-moz-range-track {
- background: #cccccc;
- }
-
- .grid_element {
- overflow: hidden;
- }
-</style>
{% bootstrap_form_errors form type='non_fields' %}
<form id="quick_booking_form" action="/booking/quick/" method="POST" class="form">
-{% csrf_token %}
-<div class="grid_container">
-<div class="grid_element host_select_pane grid_element_wide">
-<p>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 class="grid_element booking_info_pane grid_element_1third">
- {% bootstrap_field form.purpose %}
- {% bootstrap_field form.project %}
- {% bootstrap_field form.length %}
- <p style="display:inline;">Days: </p><output id="daysout" style="display:inline;">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 class="grid_element collaborator_pane grid_element_1third">
- <label>Collaborators</label>
- {{ form.users }}
-</div>
-<div class="grid_element_1third">
- <div class="configuration_pane grid_element">
- {% bootstrap_field form.hostname %}
- {% bootstrap_field form.image %}
+ {% csrf_token %}
+ <div class="container-fluid">
+ <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 px-1 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 px-1 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 px-1 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 px-1 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 px-0 mt-2 justify-content-end">
+ <button id="quick_booking_confirm" onclick="submit_form();" type="button" class="btn btn-success">Confirm</button>
+ </div>
+ </div>
</div>
- <div class="configuration_pane grid_element">
- <strong>OPNFV: (Optional)</strong>
- {% bootstrap_field form.installer %}
- {% bootstrap_field form.scenario %}
- </div>
-</div>
-</div>
-<script type="text/javascript">
+</form>
+<script type="text/javascript">
function submit_form()
{
- //formats data for form submission
- multi_filter_widget.finish();
+ run_form_callbacks();
+ document.getElementById("quick_booking_form").submit();
}
function hide_dropdown(drop_id) {
@@ -94,13 +67,6 @@
if ( drop.options[i].text == '---------' )
drop.selectedIndex = i;
}
-
- //cross browser hide children
- $('#id_image').children().hide();
- for( var i = 0; i < drop.childNodes.length; i++ )
- {
- drop.childNodes[i].disabled = true; // closest we can get on safari to hiding it outright
- }
}
function get_selected_value(key){
@@ -108,42 +74,33 @@
if(!(attr in {}) )
return attr;
}
- return null;
}
- var sup_image_dict = {{ image_filter|safe }};
- var sup_installer_dict = {{ installer_filter|safe }};
- var sup_scenario_dict = {{ scenario_filter|safe }};
+ var sup_image_dict = {{image_filter | safe}};
+ var sup_installer_dict = {{installer_filter | safe}};
+ var sup_scenario_dict = {{scenario_filter | safe}};
- function imageHider() {
+ function imageFilter() {
var drop = document.getElementById("id_image");
-
- hide_dropdown("id_image");
-
var lab_pk = get_selected_value("lab");
var host_pk = get_selected_value("host");
- for ( var i=0; i < drop.childNodes.length; i++ )
- {
- var image_object = sup_image_dict[drop.childNodes[i].value];
- if( image_object ) //weed out empty option
+ for (const childNode of drop.childNodes) {
+ var image_object = sup_image_dict[childNode.value];
+ if (image_object) //weed out empty option
{
- if( image_object.host_profile == host_pk && image_object.lab == lab_pk )
- {
- drop.childNodes[i].style.display = "inherit";
- drop.childNodes[i].disabled = false;
- }
+ childNode.disabled = !(image_object.host_profile == host_pk && image_object.lab == lab_pk);
}
}
}
- imageHider();
+ imageFilter();
$('#id_installer').children().hide();
$('#id_scenario').children().hide();
- Array.from(document.getElementsByClassName("grid-item-select-btn")).forEach(function(element) {
- element.addEventListener('click', imageHider);
+ Array.from(document.getElementsByClassName("grid-item-select-btn")).forEach(function (element) {
+ element.addEventListener('click', imageFilter);
});
function installerHider() {
@@ -175,13 +132,11 @@
}
for (var i = 0; i < dropdown.childNodes.length; i++) {
- if (dropdown.childNodes[i].value in opts && !(dropdown.childNodes[i].value in {}) ) {
+ if (dropdown.childNodes[i].value in opts && !(dropdown.childNodes[i].value in {})) {
dropdown.childNodes[i].style.display = "inherit";
dropdown.childNodes[i].disabled = false;
}
}
}
</script>
- <button id="quick_booking_confirm" onclick="submit_form();" class="btn btn-success">Confirm</button>
-</form>
{% endblock %}
diff --git a/src/templates/booking/stats.html b/src/templates/booking/stats.html
index 8bc68cd..ed34731 100644
--- a/src/templates/booking/stats.html
+++ b/src/templates/booking/stats.html
@@ -43,11 +43,11 @@ function getData(){
{% block content %}
<div class="container-fluid">
<div class="row">
- <div class="col">
+ <div class="col-auto">
<p>Number of days to plot: </p>
- <div class="form-group">
- <input id="number_days" type="number" class="form-control" min="1" step="1" style="display:inline;width:200px"/>
- <button class="btn btn-primary" onclick="getData();" style="display:inline;">Submit</button>
+ <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>
diff --git a/src/templates/booking/steps/booking_confirm.html b/src/templates/booking/steps/booking_confirm.html
deleted file mode 100644
index 40c30a9..0000000
--- a/src/templates/booking/steps/booking_confirm.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-<style>
- #form_div {
- width: 100%;
- padding: 5%;
- text-align: center;
- }
-</style>
-
-<div id="form_div">
-<p>confirm booking</p>
-<p>{{info|default:"booking info"}}</p>
-<form action="/wf/workflow/" method="post">
- {% csrf_token %}
- {{form|default:"<p> No Form Loaded</p>"}}
- <input type="submit" value="Submit" style="display: none;"/>
-</form>
-</div>
-{% endblock content %} \ No newline at end of file
diff --git a/src/templates/booking/steps/booking_meta.html b/src/templates/booking/steps/booking_meta.html
index 710d4ee..f12496e 100644
--- a/src/templates/booking/steps/booking_meta.html
+++ b/src/templates/booking/steps/booking_meta.html
@@ -5,83 +5,34 @@
{% block content %}
-<style>
- .bkmeta_panel {
- padding: 5%;
- }
-
- .bkcontrib_panel {
- display: flex;
- flex-direction: column;
- }
-
- .bkcontrib_panel > .form-group {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
-
- .panel{
- padding: 5%;
- /*border: solid 1px black;*/
- }
-
- .panel_wrap {
- width: 100%;
- display: grid;
- grid-template-columns: 45% 10% 45%;
- border: none;
- }
-
- #id_length {
- -moz-appearance: none;
- border: none;
- box-shadow: none;
- }
- input[type=range]::-moz-range-track {
- background: #cccccc;
- }
-</style>
-
{% bootstrap_form_errors form type='non_fields' %}
-<form id="booking_meta_form" action="/wf/workflow/" method="POST" class="form">
+<form id="step_form" method="POST" class="form">
{% csrf_token %}
-<div id="form_div">
- <div class="panel_wrap">
- <div class="panel bkmeta_panel">
- {% bootstrap_field form.purpose %}
- {% bootstrap_field form.project %}
- {% bootstrap_field form.length %}
- <p style="display:inline;">Days: </p><output id="daysout" style="display:inline;">0</output>
- <script>
- document.getElementById("id_length").setAttribute("oninput", "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="panel panel_center">
- </div>
- <div class="panel bkcontrib_panel">
- <p>You may add collaborators on your booking to share resources with coworkers.</p>
- {% bootstrap_field form.users label="Collaborators" %}
+<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>
-
- {% buttons %}
- <button type="submit" style="display: none;" class="btn btn-success">Confirm</button>
- {% endbuttons %}
+ <div class="panel_wrap">
+ {% buttons %}
+ <button type="submit" class="btn btn-success d-none">Confirm</button>
+ {% endbuttons %}
</div>
</div>
</form>
{% endblock content %}
-
-{% block onleave %}
-var ajaxForm = $("#booking_meta_form");
-var formData = ajaxForm.serialize();
-req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem submitting form"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/booking/steps/resource_select.html b/src/templates/booking/steps/resource_select.html
deleted file mode 100644
index 382316f..0000000
--- a/src/templates/booking/steps/resource_select.html
+++ /dev/null
@@ -1,73 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-<style>
- #resource_form_div {
- width: 100%;
- padding: 5%;
- }
-
- .panel {
- /*border: solid 1px black;*/
- border: none;
- }
- .select_panels {
- width: 100%;
- display: grid;
- grid-template-columns: 45% 10% 45%;
-
- }
-
- .panel_center {
- text-align: center;
- border: none;
-
-
- }
- .panel_center p{
- font-size: 20pt;
- }
-</style>
-
-<div id="resource_form_div">
- <div class="select_panels">
- <div class="panel_chooser panel">
- <form id="resource_select_form" method="post" action="" class="form" id="resourceselectorform">
- {% csrf_token %}
- {{ form|default:"<p>no form loaded</p>" }}
- {% buttons %}
-
- {% endbuttons %}
- </form>
- </div>
- <div class="panel_center panel"><p>OR</p></div>
- <div class="panel_add panel">
- <button class="btn {% if disabled %} disabled {% endif %}"
- style="width: 100%; height: 100%;"
- {% if not disabled %}onclick="parent.add_wf(1)"
- {% endif %}>Create Resource</button>
- </div>
-
-</div>
-</div>
-<script>
- {% if disabled %}
- disable();
- {% endif %}
-</script>
-
-{% endblock content %}
-{% block onleave %}
-var form = $("#resource_select_form");
-var formData = form.serialize();
-var req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem with form submission"); }
-req.send(formData);
-{% endblock %}
-
diff --git a/src/templates/booking/steps/swconfig_select.html b/src/templates/booking/steps/swconfig_select.html
deleted file mode 100644
index 60a0df7..0000000
--- a/src/templates/booking/steps/swconfig_select.html
+++ /dev/null
@@ -1,73 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-<style>
- #sw_form_div {
- width: 100%;
- padding: 5%;
- }
-
- .panel {
- /*border: solid 1px black;*/
- border: none;
- }
- .select_panels {
- width: 100%;
- display: grid;
- grid-template-columns: 45% 10% 45%;
-
- }
-
- .panel_center {
- text-align: center;
- border: none;
-
-
- }
- .panel_center p{
- font-size: 20pt;
- }
-</style>
-
-<div id="sw_form_div">
- <div class="select_panels">
- <div class="panel_chooser panel">
- <form id="software_select_form" method="post" action="" class="form" id="swselectorform">
- {% csrf_token %}
- {{ form|default:"<p>no form loaded</p>" }}
- {% buttons %}
-
- {% endbuttons %}
- </form>
- </div>
- <div class="panel_center panel"><p>OR</p></div>
- <div class="panel_add panel">
- <button class="btn {% if disabled %} disabled {% endif %}"
- style="width: 100%; height: 100%;"
- {% if not disabled %}onclick="parent.add_wf(2)"
- {% endif %}>Create Config</button>
- </div>
- <script>
- {% if disabled %}
- disable();
- {% endif %}
- </script>
-
-</div>
-</div>
-
-{% endblock content %}
-
-{% block onleave %}
-var form = $("#software_select_form");
-var formData = form.serialize();
-var req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem with form submission"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/config_bundle/steps/config_software.html b/src/templates/config_bundle/steps/config_software.html
index 68417bc..6fe0ac1 100644
--- a/src/templates/config_bundle/steps/config_software.html
+++ b/src/templates/config_bundle/steps/config_software.html
@@ -5,7 +5,7 @@
{% block content %}
-<form action="/wf/workflow/" method="POST" id="software_config_form" class="form">
+<form method="POST" id="step_form" class="form">
{% csrf_token %}
<p>Give it a name:</p>
{% bootstrap_field form.name %}
@@ -16,13 +16,3 @@
{% endblock content %}
-
-{% block onleave %}
-var ajaxForm = $("#software_config_form");
-var formData = ajaxForm.serialize();
-req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem submitting form"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/config_bundle/steps/define_software.html b/src/templates/config_bundle/steps/define_software.html
index 87e5997..7c47569 100644
--- a/src/templates/config_bundle/steps/define_software.html
+++ b/src/templates/config_bundle/steps/define_software.html
@@ -44,12 +44,3 @@ for(var i=0; i<parents.length; i++){
checkbox.value = "True";
}
}
-
-var form = $("#table_formset");
-var formData = form.serialize();
-var req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem with form submission"); }
-req.send(formData);
-{% endblock onleave %}
diff --git a/src/templates/config_bundle/steps/pick_installer.html b/src/templates/config_bundle/steps/pick_installer.html
index 31a06de..c3b505d 100644
--- a/src/templates/config_bundle/steps/pick_installer.html
+++ b/src/templates/config_bundle/steps/pick_installer.html
@@ -9,7 +9,7 @@
<h1>Please choose a config bundle first</h1>
{% else %}
-<form id="installer_form" action="/wf/workflow/" method="POST" id="installer_config_form" class="form">
+<form id="step_form" method="POST" class="form">
{% csrf_token %}
<p>Choose your installer:</p>
{% bootstrap_field form.installer %}
@@ -20,13 +20,3 @@
{% endif %}
{% endblock content %}
-
-{% block onleave %}
-var form = $("#installer_form");
-var formData = form.serialize();
-var req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem with form submission"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/config_bundle/steps/table_formset.html b/src/templates/config_bundle/steps/table_formset.html
index 18edc72..d25621e 100644
--- a/src/templates/config_bundle/steps/table_formset.html
+++ b/src/templates/config_bundle/steps/table_formset.html
@@ -15,10 +15,10 @@
{% block content %}
{% if error %}
- <h1 style="text-align:center;">{{ error }}</h1>
+ <h1 class="text-center">{{ error }}</h1>
{% else %}
-<div style="padding: 5%;">
- <form method="post" action="" class="form" id="table_formset">
+<div class="p-2">
+ <form method="post" class="form" id="step_form"> <!-- formset, requires special consideration -->
{% csrf_token %}
<div class="row">
@@ -51,14 +51,3 @@
{% block tablejs %}
{% endblock tablejs %}
{% endblock extrajs %}
-
-
-{% block onleave %}
-var form = $("#table_formset");
-var formData = form.serialize();
-var req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem with form submission"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/dashboard/genericselect.html b/src/templates/dashboard/genericselect.html
index 441d8dc..f54cd90 100644
--- a/src/templates/dashboard/genericselect.html
+++ b/src/templates/dashboard/genericselect.html
@@ -5,77 +5,18 @@
{% block content %}
-<style>
- #page-wrapper {
- display: flex;
- flex-direction: column;
- }
-
- #{{select_type}}_form_div div {
- }
-
- #{{select_type}}_form_div > * {
- margin-left: 10px;
- margin-right: 10px;
- margin-bottom: 20px;
- }
-
- #{{select_type}}_form_div div * {
- }
-
- #{{select_type}}_form_div {
- flex: 1;
- margin: 30px;
- display: flex;
- flex-direction: column;
- }
-
- #select_section {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
-
- #{{select_type}}_select_form {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
-
- .autocomplete {
- flex: 1;
- }
-
- #create_section {
- }
-
- #select_header_section {
- }
-
- h3 {
- margin-top: 0;
- margin-bottom: 0;
- vertical-align: middle;
- }
-
- .divider {
- border-top: 1px solid #ccc;
- }
-
-
-</style>
-
-<div id="{{select_type}}_form_div">
+<div id="{{select_type}}_form_div" class="h-100 border d-flex flex-column p-4">
<h3 id="create_section">Create a Resource
<button class="btn btn-primary {% if disabled %} disabled {% endif %}"
{% if not disabled %}onclick="parent.add_wf({{addable_type_num}})"
{% endif %}>Here
</button>
</h3>
- <div class="divider"></div>
+ <div class="border-top"></div>
<h3 id="select_header_section">Or select from the list below:</h3>
- <div id="select_section">
- <form id="{{select_type}}_select_form" method="post" action="" class="form" id="{{select_type}}selectorform">
+ <div id="select_section" class="d-flex flex-column">
+ <form id="{{select_type}}_select_form" method="post" action=""
+ class="form d-flex flex-column" id="{{select_type}}selectorform">
{% csrf_token %}
{{ form|default:"<p>no form loaded</p>" }}
{% buttons %}
diff --git a/src/templates/dashboard/grid.html b/src/templates/dashboard/grid.html
deleted file mode 100644
index ca47f44..0000000
--- a/src/templates/dashboard/grid.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "base.html" %}
-{% load staticfiles %}
-{% if script %} <script>{{ script }}</script> {% endif %}
-{% block content %}
- {% for item in grid_items %}
- <div class="grid-item" style="border:2px; border-style:solid; border-color:black; margin:3px">
- {{ item }}
- </div>
- {% endfor %}
-{% endblock content %}
diff --git a/src/templates/dashboard/host_profile_detail.html b/src/templates/dashboard/host_profile_detail.html
index abc7648..f65d4fe 100644
--- a/src/templates/dashboard/host_profile_detail.html
+++ b/src/templates/dashboard/host_profile_detail.html
@@ -18,7 +18,6 @@
}
);
</script>
-
<style media="screen">
@media screen and (min-width: 0px) and (max-width: 767px)
diff --git a/src/templates/dashboard/lab_detail.html b/src/templates/dashboard/lab_detail.html
index 336b32e..3c41caf 100644
--- a/src/templates/dashboard/lab_detail.html
+++ b/src/templates/dashboard/lab_detail.html
@@ -11,80 +11,92 @@
<div class="col-lg-4">
<div class="card my-2">
<div class="card-header d-flex">
- <h4>Lab Profile</h4>
- <button class="btn btn-outline-secondary ml-auto" data-toggle="collapse" data-target="#panel_overview">Expand</button>
+ <h4>Lab Profile</h4>
+ <button class="btn btn-outline-secondary ml-auto" data-toggle="collapse" data-target="#panel_overview">Expand</button>
</div>
- <div id="panel_overview" class="card-body collapse show">
- <table class="table">
- <tr>
- <td>Lab Name: </td><td>{{lab.name}}</td>
- </tr>
- <tr>
- <td>Lab Location: </td><td>{{lab.location}}</td>
- </tr>
- <tr>
- <td>Lab Email: </td>
- <td>{{lab.contact_email}}</td>
- </tr>
- {% if lab.contact_phone %}
- <tr>
- <td>Lab Phone: </td>
- <td>{{lab.contact_phone}}</td>
- </tr>
- {% endif %}
- <tr>
- <td>Lab Status: </td>
- {% if lab.status < 100 %}
- <td><div style="border-radius: 50%; background:#40B976; height: 20px; width: 20px; display: inline-block;"></div>
- Up</td>
- {% elif lab.status < 200 %}
- <td>
- <div style="border-radius: 50%; background:#eeee00; height: 20px; width: 20px; display: inline-block;"></div>
- Temporarily Offline</td>
- {% else %}
- <td>
- <div style="border-radius: 50%; background:#e50000; height: 20px; width: 20px; display: inline-block;"></div>
- Offline Indefinitely</td>
+ <div class="collapse show" id="panel_overview">
+ <div class="card-body">
+ <table class="table">
+ <tr>
+ <td>Lab Name: </td>
+ <td>{{lab.name}}</td>
+ </tr>
+ <tr>
+ <td>Lab Location: </td>
+ <td>{{lab.location}}</td>
+ </tr>
+ <tr>
+ <td>Lab Email: </td>
+ <td>{{lab.contact_email}}</td>
+ </tr>
+ {% if lab.contact_phone %}
+ <tr>
+ <td>Lab Phone: </td>
+ <td>{{lab.contact_phone}}</td>
+ </tr>
{% endif %}
- </tr>
- </table>
+ <tr>
+ <td>Lab Status: </td>
+ {% if lab.status < 100 %}
+ <td>
+ <div class="rounded-circle bg-success square-20 d-inline-block"></div>
+ Up
+ </td>
+ {% elif lab.status < 200 %}
+ <td>
+ <div class="rounded-circle bg-warning square-20 d-inline-block"></div>
+ Temporarily Offline
+ </td>
+ {% else %}
+ <td>
+ <div class="rounded-circle bg-danger square-20 d-inline-block"></div>
+ Offline Indefinitely
+ </td>
+ {% endif %}
+ </tr>
+ </table>
+ </div>
</div>
</div>
<div class="card my-2">
<div class="card-header d-flex">
<h4 class="d-inline-block">Host Profiles</h4>
- <button data-toggle="collapse" data-target="#profile_panel" class="btn btn-outline-secondary ml-auto" style="line-height: 1;" >Expand</button>
+ <button data-toggle="collapse" data-target="#profile_panel" class="btn btn-outline-secondary ml-auto">Expand</button>
</div>
- <div id="profile_panel" class="card-body collapse show">
- <table class="table">
- {% for profile in hostprofiles %}
- <tr>
- <td>{{profile.name}}</td>
- <td>{{profile.description}}</td>
- <td><a href="/resource/profiles/{{ profile.id }}" class="btn btn-info">Profile</a></td>
- </tr>
- {% endfor %}
- </table>
+ <div class="collapse show" id="profile_panel">
+ <div class="card-body">
+ <table class="table">
+ {% for profile in hostprofiles %}
+ <tr>
+ <td>{{profile.name}}</td>
+ <td>{{profile.description}}</td>
+ <td><a href="/resource/profiles/{{ profile.id }}" class="btn btn-info">Profile</a></td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
</div>
</div>
<div class="card my-2">
<div class="card-header d-flex">
- <h4 style="display: inline;">Networking Capabilities</h4>
- <button data-toggle="collapse" data-target="#network_panel" class="btn btn-outline-secondary ml-auto" style="line-height: 1;" >Expand</button>
+ <h4 class="d-inline">Networking Capabilities</h4>
+ <button data-toggle="collapse" data-target="#network_panel" class="btn btn-outline-secondary ml-auto">Expand</button>
</div>
- <div class="card-body collapse show" id="network_panel">
- <table class="table">
- <tr>
- <td>Block Size: (number of VLANs allowed per deployment)</td><td>{{lab.vlan_manager.block_size}}</td>
- </tr>
- <tr>
- <td>Overlapping Vlans Allowed (user can pick which VLANs they wish to use): </td>
- <td>{{lab.vlan_manager.allow_overlapping}}</td>
- </tr>
- </table>
+ <div class="collapse show" id="network_panel">
+ <div class="card-body">
+ <table class="table">
+ <tr>
+ <td>Block Size: (number of VLANs allowed per deployment)</td><td>{{lab.vlan_manager.block_size}}</td>
+ </tr>
+ <tr>
+ <td>Overlapping Vlans Allowed (user can pick which VLANs they wish to use): </td>
+ <td>{{lab.vlan_manager.allow_overlapping}}</td>
+ </tr>
+ </table>
+ </div>
</div>
</div>
<div class="card my-2">
@@ -92,23 +104,25 @@
<h4>Images</h4>
<button data-toggle="collapse" data-target="#image_panel" class="btn btn-outline-secondary ml-auto">Expand</button>
</div>
- <div class="card-body collapse show" id="image_panel">
- <table class="table">
- <tr>
- <th>Name</th>
- <th>Owner</th>
- <th>For Host Type</th>
- <th>Description</th>
- </tr>
- {% for image in images %}
- <tr>
- <td>{{image.name}}</td>
- <td>{{image.owner}}</td>
- <td>{{image.host_type}}</td>
- <td>{{image.description}}</td>
- </tr>
- {% endfor %}
- </table>
+ <div class="collapse show" id="image_panel">
+ <div class="card-body">
+ <table class="table">
+ <tr>
+ <th>Name</th>
+ <th>Owner</th>
+ <th>For Host Type</th>
+ <th>Description</th>
+ </tr>
+ {% for image in images %}
+ <tr>
+ <td>{{image.name}}</td>
+ <td>{{image.owner}}</td>
+ <td>{{image.host_type}}</td>
+ <td>{{image.description}}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
</div>
</div>
@@ -120,29 +134,31 @@
<button data-toggle="collapse" data-target="#lab_hosts_panel" class="btn btn-outline-secondary ml-auto">Expand</button>
</div>
- <div class="card-body collapse show" id="lab_hosts_panel">
- <table class="table">
- <tr>
- <th>Name</th>
- <th>Profile</th>
- <th>Booked</th>
- <th>Working</th>
- <th>Vendor</th>
- </tr>
- {% for host in lab.host_set.all %}
- <tr>
- <td>{{host.labid}}</td>
- <td>{{host.profile}}</td>
- <td>{{host.booked}}</td>
- {% if host.working %}
- <td style="background-color: #40B976;">{{host.working}}</td>
- {% else %}
- <td>{{host.working}}</td>
- {% endif %}
- <td>{{host.vendor}}</td>
- </tr>
- {% endfor %}
- </table>
+ <div class="collapse show" id="lab_hosts_panel">
+ <div class="card-body">
+ <table class="table">
+ <tr>
+ <th>Name</th>
+ <th>Profile</th>
+ <th>Booked</th>
+ <th>Working</th>
+ <th>Vendor</th>
+ </tr>
+ {% for host in lab.host_set.all %}
+ <tr>
+ <td>{{host.labid}}</td>
+ <td>{{host.profile}}</td>
+ <td>{{host.booked}}</td>
+ {% if host.working %}
+ <td class="bg-success text-white">{{host.working}}</td>
+ {% else %}
+ <td>{{host.working}}</td>
+ {% endif %}
+ <td>{{host.vendor}}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
</div>
</div>
</div>
diff --git a/src/templates/dashboard/lab_list.html b/src/templates/dashboard/lab_list.html
index 9cde80c..2efebfc 100644
--- a/src/templates/dashboard/lab_list.html
+++ b/src/templates/dashboard/lab_list.html
@@ -1,26 +1,28 @@
{% extends "base.html" %}
{% block content %}
<h2>Labs</h2>
-<div class="card_container">
+<div class="row">
{% for lab in labs %}
- <div class="card">
- <div class="card-header">
- <h3 class="mt-2">{{lab.name}}</h3>
- </div>
- <div class="p-4">
- <ul class="list-group">
- <li class="list-group-item">name: {{lab.name}}</li>
- <li class="list-group-item">description: {{lab.description}}</li>
- <li class="list-group-item">location: {{lab.location}}</li>
- {% if lab.status == 0 %}
- <li class="list-group-item">status: Up</li>
- {% elif lab.status == 100 %}
- <li class="list-group-item">status: Down for Maintenance</li>
- {% elif lab.status == 200 %}
- <li class="list-group-item">status: Down</li>
- {% endif %}
- </ul>
- <a class="btn btn-primary mt-4 w-100" href="/lab/{{lab.name}}/">Details</a>
+ <div class="p-2 col-12 col-md-6 col-lg-4 col-xl-3">
+ <div class="card h-100">
+ <div class="card-header">
+ <h3 class="mt-2">{{lab.name}}</h3>
+ </div>
+ <div class="p-4">
+ <ul class="list-group">
+ <li class="list-group-item">name: {{lab.name}}</li>
+ <li class="list-group-item">description: {{lab.description}}</li>
+ <li class="list-group-item">location: {{lab.location}}</li>
+ {% if lab.status == 0 %}
+ <li class="list-group-item">status: Up</li>
+ {% elif lab.status == 100 %}
+ <li class="list-group-item">status: Down for Maintenance</li>
+ {% elif lab.status == 200 %}
+ <li class="list-group-item">status: Down</li>
+ {% endif %}
+ </ul>
+ <a class="btn btn-primary mt-4 w-100" href="/lab/{{lab.name}}/">Details</a>
+ </div>
</div>
</div>
{% endfor %}
diff --git a/src/templates/dashboard/landing.html b/src/templates/dashboard/landing.html
index e6a235f..d4776cc 100644
--- a/src/templates/dashboard/landing.html
+++ b/src/templates/dashboard/landing.html
@@ -2,7 +2,7 @@
{% load staticfiles %}
{% block content %}
-<div class="" style="text-align: center;">
+<div class="text-center">
{% if not request.user.is_anonymous %}
{% if not request.user.userprofile.ssh_public_key %}
<div class="alert alert-danger" role="alert">
@@ -15,53 +15,11 @@
</div>
{% csrf_token %}
-<style>
- .wf_create {
- display: inline-block;
- text-align: center;
- }
-
- .wf_create_div {
- text-align: center;
- }
-
- .hidden_form {
- display: none;
- }
-
- .panel {
- border: none;
- }
-
- .panels {
- display: grid;
- grid-template-columns: 33% 34% 33%;
- }
-
- .landing_container {
- display: grid;
- grid-template-columns: 1fr 30px 1fr;
- }
-
- .grid_panel {
- padding: 30px;
- }
-
- .btn-primary {
- margin: 10px;
- }
-
- h2 {
- border-bottom: 1px solid #cccccc;
- }
-
- h1 {}
-</style>
<div class="container-fluid">
<div class="row">
<!-- About us -->
<div class="col-12 col-lg-6 mb-4">
- <h2>About Us:</h2>
+ <h2 class="border-bottom">About Us:</h2>
<p>The Lab as a Service (LaaS) project aims to help in the development and testing of LFN projects such as
OPNFV
by hosting hardware and providing access to the community. Currently, the only participating lab is the
@@ -73,56 +31,54 @@
</div>
<!-- Get started -->
<div class="col-12 col-lg-6 mb-4">
- <h2>Get Started:</h2>
+ <h2 class="border-bottom">Get Started:</h2>
{% if request.user.is_anonymous %}
- <h4 style="text-align:center;">To get started, please log in with your <a href="/accounts/login">Linux
- Foundation Jira account</a></h4>
+ <h4 class="text-center">
+ To get started, please log in with your <a href="/accounts/login">Linux Foundation Jira account</a>
+ </h4>
{% else %}
<p>To get started, book a server below:</p>
- <a class="wf_create btn btn-primary"
- style="display: flex; flex-direction: column; justify-content: center; margin: 20px; height: 100pt; vertical-align: middle; text-align: center; color: #FFF;"
+ <a class="btn btn-primary d-flex flex-column justify-content-center align-content-center border text-white p-4"
href="/booking/quick/">
- <p style="font-size: xx-large">Book a Server</p>
+ <h4>Book a Server</h4>
</a>
- <p>PTLs can use our advanced options to book multi-node pods. If you are a PTL, you may use the options
+ <p class="mt-4">PTLs can use our advanced options to book multi-node pods. If you are a PTL, you may use the options
below:
</p>
- <div class='container'>
- <div class="row">
- <div class="col-12 col-xl-4">
- <button class="wf_create btn btn-primary w-100" onclick="cwf(0)">Book a Pod</button>
- </div>
- <div class="col-12 col-xl-4">
- <button class="wf_create btn btn-primary w-100" onclick="cwf(1)">Design a Pod</button>
- </div>
- <div class="col-12 col-xl-4">
- <button class="wf_create btn btn-primary w-100" onclick="cwf(2)">Configure a Pod</button>
- </div>
+ <div class="row">
+ <div class="col-12 col-xl-4">
+ <button class="btn btn-primary w-100" onclick="cwf(0)">Book a Pod</button>
+ </div>
+ <div class="col-12 col-xl-4">
+ <button class="btn btn-primary w-100" onclick="cwf(1)">Design a Pod</button>
+ </div>
+ <div class="col-12 col-xl-4">
+ <button class="btn btn-primary w-100" onclick="cwf(2)">Configure a Pod</button>
</div>
- {% endif %}
</div>
+ {% endif %}
</div>
<!-- Returning users -->
{% if not request.user.is_anonymous %}
<div class="col-12 col-lg-6 offset-lg-6 mb-4 mt-lg-4">
- <h2 class="ht-4">Returning Users:</h2>
+ <h2 class="ht-4 border-bottom">Returning Users:</h2>
<p>If you're a returning user, some of the following options may be of interest:</p>
- <div class="container">
- <div class="row">
- <div class="col-12 col-xl-4">
- <button class="wf_create btn btn-primary w-100" onclick="cwf(3)">Snapshot a Host</button>
- </div>
- <div class="col-12 col-xl-4">
- <a class="wf_create btn btn-primary w-100" href="{% url 'account:my-bookings' %}">My
- Bookings</a>
- </div>
- {% if manager == True %}
+ <div class="row">
+ <div class="col-12 col-xl-4">
+ <button class="btn btn-primary w-100" onclick="cwf(3)">Snapshot a Host</button>
+ </div>
+ <div class="col-12 col-xl-4">
+ <a class="btn btn-primary w-100" href="{% url 'account:my-bookings' %}">
+ My Bookings
+ </a>
+ </div>
+ {% if manager == True %}
<div class="col-12 col-xl-4">
- <button class="wf_continue btn btn-primary w-100" onclick="continue_wf()">Resume
- Workflow</button>
+ <button class="btn btn-primary w-100" onclick="continue_wf()">
+ Resume Workflow
+ </button>
</div>
- {% endif %}
- </div>
+ {% endif %}
</div>
</div>
{% endif %}
@@ -155,7 +111,7 @@
}
</script>
-<div class="hidden_form" id="form_div">
+<div class="hidden_form d-none" id="form_div">
<form method="post" action="" class="form" id="wf_selection_form">
{% csrf_token %}
diff --git a/src/templates/dashboard/multiple_select_filter_widget.html b/src/templates/dashboard/multiple_select_filter_widget.html
index 4302543..4068919 100644
--- a/src/templates/dashboard/multiple_select_filter_widget.html
+++ b/src/templates/dashboard/multiple_select_filter_widget.html
@@ -1,139 +1,44 @@
<script src="/static/js/dashboard.js">
</script>
-<style>
-.object_class_wrapper {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- border: 0px;
-}
-
-.class_grid_wrapper {
- border: 0px;
- text-align: center;
- border-right: 1px;
- border-style: solid;
- border-color: grey;
-}
-
-.class_grid_wrapper:last-child {
- border-right: none;
-}
-
-.grid_wrapper {
- display: grid;
- grid-template-columns: 1fr 1fr;
-}
-
-.grid-item {
- cursor: pointer;
- border: 1px solid #cccccc;
- border-radius: 5px;
- margin: 20px;
- height: 200px;
- padding: 7px;
- transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
- box-shadow: 0 1px 1px rgba(0,0,0,.075);
-
- display: flex;
- flex-direction: column;
-}
-
-.grid-item > .btn:active, .grid-item > .btn:focus {
- outline: none; !important;
- box-shadow: none;
-}
-
-.grid-item-description {
- flex: 1;
-}
-
-.selected_node {
- border-color: #40c640;
- box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(109, 243, 76, 0.6);
- transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
-}
-
-.grid-item:hover:not(.selected_node):not(.disabled_node) {
- box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(100, 100, 100, 0.3);
- transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
-}
-
-.disabled_node {
- cursor: not-allowed;
- background-color: #EFEFEF;
-}
-
-.disabled_node:hover {}
-
-.cleared_node {
- background-color: #FFFFFF;
-}
-
-.grid-item-header {
- font-weight: bold;
- font-size: 20px;
- margin-top: 10px;
-}
-
-.dropdown_item {
- border: 1px;
- border-style: solid;
- border-color: lightgray;
- border-radius: 5px;
- margin: 20px;
- padding: 2px;
- grid-column: 1;
- display: grid;
- grid-template-columns: 1fr 3fr 1fr;
- justify-items: center;
-}
-
-.dropdown_item > button {
- margin: 2px;
- justify-self: end;
-}
-
-.dropdown_item > h5 {
- margin: auto;
-}
-
-.dropdown_item > input {
- padding: 7px;
- margin: 2px;
- width: 90%;
-}
-
-#dropdown_wrapper {
- display: grid;
- grid-template-columns: 4fr 5fr;
-}
-</style>
-
<input name="filter_field" id="filter_field" type="hidden"/>
-<div id="grid_wrapper" class="grid_wrapper">
-{% for object_class, object_list in display_objects %}
- <div class="class_grid_wrapper">
- <div style="display:inline-block;margin:auto">
- <h4>{{object_class}}</h4>
- </div>
- <div id="{{object_class}}" class="object_class_wrapper">
- {% for obj in object_list %}
- <div id="{{ obj.id|default:'not_provided' }}" class="grid-item" onclick="multi_filter_widget.processClick(
- '{{obj.id}}');">
- <p class="grid-item-header">{{obj.name}}</p>
- <p class="grid-item-description">{{obj.description}}</p>
- <button type="button" class="btn btn-success grid-item-select-btn">
- {% if obj.multiple %}Add{% else %}Select{% endif %}
- </button>
+<div class="row">
+ {% for object_class, object_list in display_objects %}
+ <div class="col-12 col-lg-6 d-flex flex-column pt-2 mx-0 px-1">
+ <div class="col mx-0 border rounded py-2 flex-grow-1 d-flex flex-column">
+ <div class="w-100">
+ <h4 class="text-capitalize">{{object_class}}</h4>
+ </div>
+ <div id="{{object_class}}" class="row flex-grow-1">
+ {% for obj in object_list %}
+ <div class="col-12 col-md-6 col-xl-4 my-2 d-flex flex-grow-1">
+ <div id="{{ obj.id|default:'not_provided' }}" class="card flex-grow-1">
+ <div class="card-header">
+ <p class="h5 font-weight-bold mt-2">{{obj.name}}</p>
+ </div>
+ <div class="card-body">
+ <p class="grid-item-description">{{obj.description}}</p>
+ </div>
+ <div class="card-footer">
+ <button type="button" class="btn btn-success grid-item-select-btn w-100 stretched-link"
+ onclick="multi_filter_widget.processClick('{{obj.id}}');">
+ {% if obj.multiple %}
+ Add
+ {% else %}
+ Select
+ {% endif %}
+ </button>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
</div>
- {% endfor %}
</div>
- </div>
-{% endfor %}
+ {% endfor %}
</div>
-<div id="dropdown_wrapper">
+<div id="dropdown_wrapper" class="px-3 list-group-flush w-25 mt-2">
</div>
<script>
function multipleSelectFilterWidgetEntry() {
@@ -141,8 +46,9 @@ function multipleSelectFilterWidgetEntry() {
const filter_items = {{ filter_items|safe }};
const initial_value = {{ initial_value|default_if_none:"{}"|safe }};
- //global variable
+ //global variables
multi_filter_widget = new MultipleSelectFilterWidget(graph_neighbors, filter_items, initial_value);
+ form_submission_callbacks.push(() => multi_filter_widget.finish());
}
multipleSelectFilterWidgetEntry();
diff --git a/src/templates/dashboard/searchable_select_multiple.html b/src/templates/dashboard/searchable_select_multiple.html
index 8bcf890..80e4ea4 100644
--- a/src/templates/dashboard/searchable_select_multiple.html
+++ b/src/templates/dashboard/searchable_select_multiple.html
@@ -1,19 +1,18 @@
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="/static/js/dashboard.js"></script>
-
-<div id="search_select_outer" class="autocomplete">
- <div id="warning_pane" style="background: #FFFFFF; color: #CC0000;">
- {% if incompatible == "true" %}
- <h3>Warning: Incompatible Configuration</h3>
- <p>Please make a different selection, as the current config conflicts with the selected pod</p>
- {% endif %}
- </div>
- <div id="added_counter">
- <p id="added_number">0</p>
- <p id="addable_limit">/ {% if selectable_limit > -1 %} {{ selectable_limit }} {% else %} &infin; {% endif %}added</p>
+<div id="search_select_outer" class="d-flex flex-column">
+ {% if incompatible == "true" %}
+ <div class="alert alert-danger" role="alert">
+ <h3>Warning: Incompatible Configuration</h3>
+ <p>Please make a different selection, as the current config conflicts with the selected pod</p>
+ </div>
+ {% endif %}
+ <div id="added_counter" class="text-center">
+ <span id="added_number">0</span>
+ <span id="addable_limit">/ {% if selectable_limit > -1 %} {{ selectable_limit }} {% else %} &infin; {% endif %}added</span>
</div>
- <div id="added_list">
+ <div id="added_list" class="pb-2">
</div>
@@ -22,143 +21,14 @@
>
</input>
- <input type="hidden" id="selector" name="{{ name }}" class="form-control" style="display: none;"
+ <input type="hidden" id="selector" name="{{ name }}" class="form-control d-none"
{% if disabled %} disabled {% endif %}
>
</input>
- <div id="scroll_restrictor">
- <ul id="drop_results"></ul>
+ <div id="scroll_restrictor" class="d-flex pb-4 position-relative">
+ <div id="drop_results" class="list-group w-100 z-2 overflow-auto position-absolute mh-30vh"></div>
</div>
- <style>
- #scroll_restrictor {
- flex: 1;
- position: relative;
- overflow-y: auto;
- padding-bottom: 10px;
- }
-
- #added_list {
- margin-bottom: 5px;
- }
-
- .autocomplete {
- display: flex;
- flex: 1;
- flex-direction: column;
- }
- #user_field {
- font-size: 14pt;
- padding: 5px;
- height: 40px;
- border: 1px solid #ccc;
- border-radius: 5px;
-
- }
-
- #drop_results{
- list-style-type: none;
- padding: 0;
- margin: 0;
- min-height: 0;
- border: solid 1px #ddd;
- border-top: none;
- border-bottom: none;
- visibility: inherit;
- flex: 1;
-
- position: absolute;
- width: 100%;
-
- }
-
- #drop_results li a{
- font-size: 14pt;
- background-color: #f6f6f6;
- padding: 7px;
- text-decoration: none;
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- #drop_results li a {
- border-bottom: 1px solid #ddd;
- }
-
- .list_entry {
- border: 1px solid #ccc;
- border-radius: 5px;
- margin-top: 5px;
- vertical-align: middle;
- line-height: 40px;
- height: 40px;
- padding-left: 12px;
- width: 100%;
- display: flex;
- }
-
- #drop_results li a:hover{
- background-color: #ffffff;
- }
-
- .added_entry_text {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- display: inline;
- width: 100%;
- }
-
- .btn-remove {
- float: right;
- height: 30px;
- margin: 4px;
- padding: 1px;
- max-width: 20%;
- width: 15%;
- min-width: 70px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .entry_tooltip {
- display: none;
- }
-
- #drop_results li a:hover .entry_tooltip {
- position: absolute;
- background: #444;
- color: #ddd;
- text-align: center;
- font-size: 12pt;
- border-radius: 3px;
-
- }
-
- #drop_results {
- max-width: 100%;
- display: inline-block;
- list-style-type: none;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
-
- #drop_results li {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- #added_counter {
- text-align: center;
- }
-
- #added_number, #addable_limit {
- display: inline;
- }
- </style>
</div>
<script type="text/javascript">
diff --git a/src/templates/dashboard/table.html b/src/templates/dashboard/table.html
index 0a37ded..2b4628e 100644
--- a/src/templates/dashboard/table.html
+++ b/src/templates/dashboard/table.html
@@ -15,20 +15,14 @@
{% block content %}
<div class="row">
<div class="col-lg-12">
- <div class="dataTables_wrapper">
- <table class="table table-striped table-bordered table-hover" id="table" cellspacing="0"
- width="100%">
-
- {% block table %}
- {% endblock table %}
-
- </table>
- </div>
- <!-- /.table-responsive -->
- <!-- /.panel-body -->
- <!-- /.panel -->
+ <div class="dataTables_wrapper table-responsive mw-100">
+ <table class="table table-striped table-bordered table-hover" id="table" cellspacing="0"
+ width="100%">
+ {% block table %}
+ {% endblock table %}
+ </table>
+ </div>
</div>
- <!-- /.col-lg-12 -->
</div>
{% endblock content %}
diff --git a/src/templates/layout.html b/src/templates/layout.html
index d37d4f5..205671b 100644
--- a/src/templates/layout.html
+++ b/src/templates/layout.html
@@ -16,13 +16,12 @@
<link href="{% static "bower_components/bootstrap/dist/css/bootstrap.min.css" %}"
rel="stylesheet">
- <!-- MetisMenu CSS -->
- <link href="{% static "bower_components/metisMenu/dist/metisMenu.min.css" %}" rel="stylesheet">
-
<!-- Custom Fonts -->
<link href="{% static "bower_components/Font-Awesome/css/all.min.css" %}"
rel="stylesheet" type="text/css">
+ <link href="{% static "css/base.css" %}" rel="stylesheet">
+
<!-- Favicon -->
<link rel="shortcut icon" href="{% static 'favicon.ico' %}">
<script src="https://code.jquery.com/jquery-2.2.4.min.js"
diff --git a/src/templates/notifier/inbox.html b/src/templates/notifier/inbox.html
index 72207ed..9d7b426 100644
--- a/src/templates/notifier/inbox.html
+++ b/src/templates/notifier/inbox.html
@@ -5,61 +5,6 @@
{% block content %}
-<style media="screen">
- .inbox-panel {
- display: grid;
- grid-template-columns: 30% 5% 65%;
- }
-
- .section-panel {
- padding: 10px;
- }
-
- .iframe-panel {
- padding: 0px;
- margin-top: 0px;
- }
-
- .card-container {
- border: 1px solid #cccccc;
- border-bottom: 0px;
- }
-
- .card {
- height: 50px;
- position: relative;
- border-bottom: 1px solid #cccccc;
- padding: 10px;
- width: 100%;
- background-color: #ffffff;
- z-index: 5;
- }
-
- .selected-card {
- background-color: #f3f3f3;
- }
-
- .card:hover {
- box-shadow: 0px 0 5px 2px #cccccc;
- z-index: 6;
- }
-
- .half_width {
- width: 50%;
- }
-
- #page-wrapper {
- padding: 0px;
- }
-
- .read_notification {
- background-color: #efefef;
- }
-
- .scrollable {
- overflow-y: auto;
- }
-</style>
<div class="container-fluid d-flex flex-grow-1 flex-column">
<div class="row mt-3 mb-2">
<div class="col-2 px-0">
@@ -73,7 +18,7 @@
<div class="row flex-grow-1" id="fixHeight">
<!-- Notification list && Controls -->
<div class="mb-2 mb-lg-0 col-lg-2 px-0 mh-100">
- <div class="list-group rounded-0 rounded-left scrollable mh-100 notifications" id="unreadNotifications" data-read="0">
+ <div class="list-group rounded-0 rounded-left overflow-auto mh-100 notifications" id="unreadNotifications" data-read="0">
{% for notification in unread_notifications %}
<a
href="#"
@@ -83,7 +28,7 @@
</a>
{% endfor %}
</div>
- <div class="list-group rounded-0 rounded-left scrollable mh-100 notifications" id="readNotifications" data-read="1">
+ <div class="list-group rounded-0 rounded-left overflow-auto mh-100 notifications" id="readNotifications" data-read="1">
{% for notification in read_notifications %}
<a
href="#"
@@ -96,15 +41,14 @@
</div>
<!-- Content -->
<div class="col ml-lg-2 border mh-100 p-4">
- <iframe class="w-100 h-100" id="inbox-iframe" frameBorder="0" scrolling="yes">Please select a notification</iframe>
+ <iframe name="messageView" class="w-100 h-100" id="inbox-iframe" frameBorder="0" scrolling="yes">Please select a notification</iframe>
</div>
</div>
</div>
<script type="text/javascript">
function showmessage(msg_id) {
- iframe = document.getElementById("inbox-iframe");
- iframe.src = "notification/" + msg_id;
+ window.frames["messageView"].location = "notification/" + msg_id;
}
function setactive(obj) {
diff --git a/src/templates/notifier/notification.html b/src/templates/notifier/notification.html
index 0eafa60..603edea 100644
--- a/src/templates/notifier/notification.html
+++ b/src/templates/notifier/notification.html
@@ -27,13 +27,12 @@
}
</script>
-<div>
- <h3 class="msg_header">{{notification.title}}
- <div class="btn_group">
- <button class="btn btn-primary inbox-btn" onclick="mark_unread()">Mark Unread</button>
- <button class="btn btn-danger inbox-btn" onclick="delete_notification()">Delete</button>
- </div>
- </h3>
+<div class="d-flex justify-content-between border-bottom">
+ <span class="h3">{{notification.title}}</span>
+ <div class="btn_group">
+ <button class="btn btn-primary inbox-btn" onclick="mark_unread()">Mark Unread</button>
+ <button class="btn btn-danger inbox-btn" onclick="delete_notification()">Delete</button>
+ </div>
</div>
<p class="content-divider"></p>
@@ -48,36 +47,4 @@
<form id="notification_action_form" action="." method="post">
{% csrf_token %}
</form>
-
-<style media="screen">
- .card-container {
- border: 1px solid #ffffff;
- margin-top: 11px;
- }
- .card {
- height: 50px;
- margin: 0px;
- position: relative;
- border-bottom: 1px solid #cccccc;
- padding: 0px;
- width: 100%;
- background-color: #ffffff;
- z-index: 5;
- }
- .sender {
- color: #636363;
- }
- .content-divider {
- border-bottom: 1px solid #cccccc;
- padding-bottom: 15px;
- clear: right;
- }
- .inbox-btn{
- display: inline;
- margin: 3px;
- }
- .btn_group{
- float: right;
- }
-</style>
{% endblock %}
diff --git a/src/templates/resource/hostprofile_detail.html b/src/templates/resource/hostprofile_detail.html
index dc20600..c26d774 100644
--- a/src/templates/resource/hostprofile_detail.html
+++ b/src/templates/resource/hostprofile_detail.html
@@ -6,95 +6,105 @@
<div class="col-lg-6">
<div class="card mb-4">
<div class="card-header d-flex">
- <h4 style="display: inline;">Available at</h4>
- <button data-toggle="collapse" data-target="#avilableAt" class="btn ml-auto btn-outline-secondary">Expand</button>
+ <h4 class="d-inline">Available at</h4>
+ <button data-toggle="collapse" data-target="#availableAt" class="btn ml-auto btn-outline-secondary">Expand</button>
</div>
- <div class="card-body collapse show" id="avilableAt">
- <ul class="list-group">
- {% for lab in hostprofile.labs.all %}
- <li class="list-group-item">{{lab.name}}</li>
- {% endfor %}
- </ul>
+ <div class="collapse show" id="availableAt">
+ <div class="card-body">
+ <ul class="list-group">
+ {% for lab in hostprofile.labs.all %}
+ <li class="list-group-item">{{lab.name}}</li>
+ {% endfor %}
+ </ul>
+ </div>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex">
- <h4 style="display: inline;">RAM</h4>
+ <h4 class="d-inline">RAM</h4>
<button data-toggle="collapse" data-target="#ramPanel" class="btn ml-auto btn-outline-secondary">Expand</button>
</div>
- <div class="card-body collapse show" id="ramPanel">
- {{hostprofile.ramprofile.first.amount}}G,
- {{hostprofile.ramprofile.first.channels}} channels
+ <div id="ramPanel" class="collapse show">
+ <div class="card-body">
+ {{hostprofile.ramprofile.first.amount}}G,
+ {{hostprofile.ramprofile.first.channels}} channels
+ </div>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex">
- <h4 style="display: inline;">CPU</h4>
+ <h4 class="d-inline">CPU</h4>
<button data-toggle="collapse" data-target="#cpuPanel" class="btn ml-auto btn-outline-secondary">Expand</button>
</div>
- <div class="card-body collapse show" id="cpuPanel">
- <table class="table">
- <tr>
- <td>Arch:</td>
- <td>{{hostprofile.cpuprofile.first.architecture}}</td>
- </tr>
- <tr>
- <td>Cores:</td>
- <td>{{hostprofile.cpuprofile.first.cores}}</td>
- </tr>
- <tr>
- <td>Sockets:</td>
- <td>{{hostprofile.cpuprofile.first.cpus}}</td>
- </tr>
- </table>
+ <div class="collapse show" id="cpuPanel">
+ <div class="card-body">
+ <table class="table">
+ <tr>
+ <td>Arch:</td>
+ <td>{{hostprofile.cpuprofile.first.architecture}}</td>
+ </tr>
+ <tr>
+ <td>Cores:</td>
+ <td>{{hostprofile.cpuprofile.first.cores}}</td>
+ </tr>
+ <tr>
+ <td>Sockets:</td>
+ <td>{{hostprofile.cpuprofile.first.cpus}}</td>
+ </tr>
+ </table>
+ </div>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex">
- <h4 style="display: inline;">Disk</h4>
+ <h4 class="d-inline">Disk</h4>
<button data-toggle="collapse" data-target="#diskPanel" class="btn ml-auto btn-outline-secondary">Expand</button>
</div>
- <div class="card-body collapse show" id="diskPanel">
- <table class="table">
- <tr>
- <td>Size:</td>
- <td>{{hostprofile.storageprofile.first.size}} GiB</td>
- </tr>
- <tr>
- <td>Type:</td>
- <td>{{hostprofile.storageprofile.first.media_type}}</td>
- </tr>
- <tr>
- <td>Mount Point:</td>
- <td>{{hostprofile.storageprofile.first.name}}</td>
- </tr>
- </table>
+ <div class="collapse show" id="diskPanel">
+ <div class="card-body">
+ <table class="table">
+ <tr>
+ <td>Size:</td>
+ <td>{{hostprofile.storageprofile.first.size}} GiB</td>
+ </tr>
+ <tr>
+ <td>Type:</td>
+ <td>{{hostprofile.storageprofile.first.media_type}}</td>
+ </tr>
+ <tr>
+ <td>Mount Point:</td>
+ <td>{{hostprofile.storageprofile.first.name}}</td>
+ </tr>
+ </table>
+ </div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header d-flex">
- <h4 style="display: inline;">Interfaces</h4>
+ <h4 class="d-inline">Interfaces</h4>
<button data-toggle="collapse" data-target="#interfacePanel" class="btn ml-auto btn-outline-secondary">Expand</button>
</div>
- <div class="card-body collapse show" id="interfacePanel">
- <table class="table">
- <thead>
- <tr>
- <th>Name</th>
- <th>Speed</th>
- </tr>
- </thead>
- <tbody>
- {% for intprof in hostprofile.interfaceprofile.all %}
- <tr>
- <td>{{intprof.name}}</td>
- <td>{{intprof.speed}}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
+ <div class="collapse show" id="interfacePanel">
+ <div class="card-body">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Speed</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for intprof in hostprofile.interfaceprofile.all %}
+ <tr>
+ <td>{{intprof.name}}</td>
+ <td>{{intprof.speed}}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
</div>
</div>
</div>
diff --git a/src/templates/resource/steps/define_hardware.html b/src/templates/resource/steps/define_hardware.html
index 57078e9..2cb37da 100644
--- a/src/templates/resource/steps/define_hardware.html
+++ b/src/templates/resource/steps/define_hardware.html
@@ -9,16 +9,8 @@ As you make your selections, labs and hosts that are not compatible
with your current configuration will become unavailable.</p>
<h4>NOTE: Only PTL's are able to create multi-node PODs. See <a href="https://google.com">here</a>
for more details</h4>
-<form id="define_hardware_form" action="/wf/workflow/" method="post">
+<form id="step_form" method="post" class="px-3">
{% csrf_token %}
{{form.filter_field|default:"<p>No Form</p>"}}
</form>
{% endblock content %}
-{% block onleave %}
-multi_filter_widget.finish();
-var formData = $("#define_hardware_form").serialize();
-req = new XMLHttpRequest();
-req.open('POST', '/wf/workflow/', false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/resource/steps/host_info.html b/src/templates/resource/steps/host_info.html
index bbbafdc..3230d8f 100644
--- a/src/templates/resource/steps/host_info.html
+++ b/src/templates/resource/steps/host_info.html
@@ -10,7 +10,7 @@
{% else %}
-<form id="host_meta_form" method="post" action="/wf/workflow/">
+<form id="step_form" method="post">
{% csrf_token %}
<table>
<thead>
@@ -32,12 +32,3 @@
</form>
{% endif %}
{% endblock content %}
-
-{% block onleave %}
-var formData = $("#host_meta_form").serialize();
-var req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("There was a problem submitting the form"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/resource/steps/meta_info.html b/src/templates/resource/steps/meta_info.html
index cebd343..b6a17a9 100644
--- a/src/templates/resource/steps/meta_info.html
+++ b/src/templates/resource/steps/meta_info.html
@@ -5,42 +5,10 @@
{% block content %}
-<style>
-#resource_meta_form {
- padding: 80px;
- display: grid;
-}
-
-#resource_meta_form td > * {
- width: 100%;
- margin-bottom: 20px;
- margin-top: 20px;
-}
-
-#resource_meta_form > table > tbody > tr {
- border-bottom: 1px solid #cccccc;
-}
-
-#resource_meta_form > table > tbody > tr:last-child {
- border-bottom: none;
-}
-
-</style>
-
-<form id="resource_meta_form" method="post" action="/wf/workflow/">
+<form id="step_form" method="post">
{% csrf_token %}
- <table>
- {{form}}
+ <table class="px-4">
+ {% bootstrap_form form field_class="px-4" label_class="px-4 mt-2" %}
</table>
</form>
{% endblock content %}
-
-{% block onleave %}
-var ajaxForm = $("#resource_meta_form");
-var formData = ajaxForm.serialize();
-req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem submitting form"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/resource/steps/pod_definition.html b/src/templates/resource/steps/pod_definition.html
index 5826ccb..4392dbd 100644
--- a/src/templates/resource/steps/pod_definition.html
+++ b/src/templates/resource/steps/pod_definition.html
@@ -13,127 +13,72 @@
<!-- Calls the main function after the page has loaded. Container is dynamically created. -->
{% block content %}
- <div id="graphParent"
- style="position:absolute;overflow:hidden;top:0px;bottom:0px;width:75%;left:0px;">
- <div id="graphContainer"
- style="position:relative;overflow:hidden;top:36px;bottom:0px;left:0px;right:0px;background-image:url('/static/img/mxgraph/grid.gif');cursor:default;">
+ <div class="row p-0 w-100 mx-0 position-absolute overflow-hidden topToBottom">
+ <div id="graphParent" class="col px-0">
+ <div class="row">
+ <div class="col pr-0">
+ <div id="toolbarContainer" class="bg-light pl-4"></div>
+ </div>
+ </div>
+ <!-- Creates a container for the sidebar -->
+ <div id="graphContainer"></div>
</div>
-
- <!-- Creates a container for the sidebar -->
- <div id="toolbarContainer"
- style="position:absolute;white-space:nowrap;overflow:hidden;top:0px;left:0px;right:0px;padding:6px;">
- </div>
-
- <!-- Creates a container for the outline -->
- <div id="outlineContainer"
- style="position:absolute;overflow:hidden;top:36px;right:0px;width:200px;height:140px;background:transparent;border-style:solid;border-color:black;">
+ <div id="network_select" class="p-0 w-25 ml-auto col-2">
+ <div class="px-0 mb-2">
+ <!-- Creates a container for the outline -->
+ <div id="outlineContainer" class="border"></div>
+ </div>
+ <div>
+ <button id="btn_add_network" type="button" class="btn btn-primary w-100" onclick="network_step.newNetworkWindow();">Add Network</button>
+ </div>
+ <ul id="network_list" class="list-group">
+ </ul>
+ <button type="button" class="d-none" onclick="network_step.submitForm();">Submit</button>
</div>
</div>
-
- <style>
- p {
- word-break: normal;
- white-space: normal;
- }
- #network_select {
- background: inherit;
- padding: 0px;
- padding-top: 0px;
- }
- #toolbarContainer {
- background: #DDDDDD;
- height: 36px;
- }
- #toolbar_extension {
- height: 36px;
- background: #DDDDDD;
- }
- #btn_add_network {
- width: 100%;
- }
- #vlan_notice {
- margin: 20px;
- }
- #network_list li {
- border-radius: 2px;
- margin: 5px;
- padding: 5px;
- vertical-align: middle;
- background: #DDDDDD;
- }
- #network_list {
- list-style-type: none;
- padding: 0;
- }
- .colorblob {
- width: 20px;
- height: 20px;
- border-radius: 50%;
- display: inline-block;
- vertical-align: middle;
- }
- .network_innertext {
- display: inline-block;
- padding-left: 10px;
- vertical-align: middle;
- padding-bottom: 0px;
- margin-bottom: 2px;
- }
- .mxWindow {
- background: #FFFFFF;
- }
- </style>
-
- <div id="network_select" style="position:absolute;top:0px;bottom:0px;width:25%;right:0px;left:auto;">
- <div id="toolbar_extension">
- <button id="btn_add_network" type="button" class="btn btn-primary" onclick="network_step.newNetworkWindow();">Add Network</button>
- </div>
- <ul id="network_list">
- </ul>
- <button type="button" style="display: none" onclick="network_step.submitForm();">Submit</button>
- </div>
- <form id="xml_form" method="post" action="/wf/workflow/">
+ <form id="step_form" method="post">
{% csrf_token %}
<input type="hidden" id="hidden_xml_input" name="xml" />
</form>
+ <script type="text/javascript" src="/static/js/mxClient.min.js" ></script>
+ <script>
+ //gather context data
+ let debug = false;
+ {% if debug %}
+ debug = true;
+ {% endif %}
-<script>
- //gather context data
- let debug = false;
- {% if debug %}
- debug = true;
- {% endif %}
+ let xml = '';
+ {% if xml %}
+ xml = '{{xml|safe}}';
+ {% endif %}
- let xml = '';
- {% if xml %}
- xml = '{{xml|safe}}';
- {% endif %}
+ let hosts = [];
+ {% for host in hosts %}
+ hosts.push({{host|safe}});
+ {% endfor %}
- let hosts = [];
- {% for host in hosts %}
- hosts.push({{host|safe}});
- {% endfor %}
-
- let added_hosts = [];
- {% for host in added_hosts %}
- added_hosts.push({{host|safe}});
- {% endfor %}
+ let added_hosts = [];
+ {% for host in added_hosts %}
+ added_hosts.push({{host|safe}});
+ {% endfor %}
- let removed_host_ids = {{removed_hosts|safe}};
+ let removed_host_ids = {{removed_hosts|safe}};
- network_step = new NetworkStep(
- debug,
- xml,
- hosts,
- added_hosts,
- removed_host_ids,
- document.getElementById('graphContainer'),
- document.getElementById('outlineContainer'),
- document.getElementById('toolbarContainer'),
- document.getElementById('sidebarContainer')
- );
-</script>
+ network_step = new NetworkStep(
+ debug,
+ xml,
+ hosts,
+ added_hosts,
+ removed_host_ids,
+ document.getElementById('graphContainer'),
+ document.getElementById('outlineContainer'),
+ document.getElementById('toolbarContainer'),
+ document.getElementById('sidebarContainer')
+ );
+ form_submission_callbacks.push(() => network_step.prepareForm());
+ </script>
{% endblock content %}
{% block onleave %}
network_step.submitForm();
diff --git a/src/templates/snapshot_workflow/steps/meta.html b/src/templates/snapshot_workflow/steps/meta.html
index bea475d..88136d2 100644
--- a/src/templates/snapshot_workflow/steps/meta.html
+++ b/src/templates/snapshot_workflow/steps/meta.html
@@ -4,14 +4,9 @@
{% load bootstrap4 %}
{% block content %}
-<style>
-.meta_container {
- padding: 50px;
-}
-</style>
{% bootstrap_form_errors form type='non_fields' %}
-<div class="meta_container">
- <form id="meta_form" action="/wf/workflow/" method="POST" class="form">
+<div class="p-4">
+ <form id="step_form" method="POST" class="form">
{% csrf_token %}
<div class="form-group">
{% bootstrap_field form.name %}
@@ -20,13 +15,3 @@
</form>
</div>
{% endblock content %}
-
-{% block onleave %}
-var ajaxForm = $("#meta_form");
-var formData = ajaxForm.serialize();
-req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem submitting form"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/snapshot_workflow/steps/select_host.html b/src/templates/snapshot_workflow/steps/select_host.html
index f438bac..4243145 100644
--- a/src/templates/snapshot_workflow/steps/select_host.html
+++ b/src/templates/snapshot_workflow/steps/select_host.html
@@ -5,73 +5,30 @@
{% block content %}
-<style>
- .booking {
- border-style: none;
- border-color: black;
- border: 2px;
- border-radius: 5px;
- margin: 20px;
- padding-left: 25px;
- padding-right: 25px;
- padding-bottom: 25px;
- box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
- transition-property: box-shadow;
- transition-duration: 0.1s;
- float: left;
- }
- .booking:hover {
- box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.75);
- transition-property: box-shadow;
- transition-duration: 0.1s;
- }
- .host {
- cursor: pointer;
- border-style: solid;
- border-color: black;
- border-width: 1px;
- border-radius: 5px;
- margin: 5px;
- padding: 5px;
- text-align: center;
- box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.75);
- transition-property: box-shadow;
- transition-duration: 0.1s;
- }
- .host:hover {
- box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.75);
- transition-property: box-shadow;
- transition-duration: 0.1s;
- background-color: rgba(144,238,144,0.3);
- }
- .selected {
- background-color: lightgreen !important;
- }
- .booking_container {
- overflow: auto;
- padding: 30px;
- }
-</style>
{% bootstrap_form_errors form type='non_fields' %}
-<form id="host_select_form" action="/wf/workflow/" method="POST" class="form">
+<form id="step_form" method="POST" class="form">
{% csrf_token %}
<input type="hidden" id="hidden_json_input", name="host"/>
</form>
-<div id="host_select_container" class="booking_container">
+<div class="container-fluid">
+ <div class="row" id="host_select_container">
+ </div>
</div>
<script>
var selected_host = null;
var initial = {{chosen|safe|default:'null'}};
-function select(booking_id, host_name){
+function select(obj){
+ var booking_id = $(obj).attr("booking");
+ var host_name = $(obj).attr("hostname");
var input = document.getElementById("hidden_json_input");
input.value = JSON.stringify({"booking": booking_id, "name": host_name});
// clear out and highlist host
- if(selected_host){
- selected_host.classList.remove("selected");
+ if(selected_host != null){
+ selected_host.classList.remove("active");
}
selected_host = document.getElementById("booking_" + booking_id + "_host_" + host_name);
- selected_host.classList.add("selected");
+ selected_host.classList.add("active");
}
function draw_bookings(){
@@ -79,35 +36,43 @@ function draw_bookings(){
var bookings = [];
var container = document.getElementById("host_select_container");
for(var booking_id in booking_hosts){
- var booking = document.createElement("DIV");
- var heading = document.createElement("H3");
- heading.appendChild(document.createTextNode("Booking " + booking_id));
- booking.appendChild(heading);
- booking.appendChild(document.createTextNode("start: " + booking_hosts[booking_id].start));
- booking.appendChild(document.createElement("BR"));
- booking.appendChild(document.createTextNode("end: " + booking_hosts[booking_id].end));
- booking.appendChild(document.createElement("BR"));
- booking.appendChild(document.createTextNode("purpose: " + booking_hosts[booking_id].purpose));
- booking.appendChild(document.createElement("BR"));
- booking.appendChild(document.createTextNode("hosts:"));
- booking.id = "booking_" + booking_id;
- booking.className = "booking";
+ // Create a column with a card
+ var column = $("<div/>", {
+ class: "col-12 col-md-6 col-lg-3 col-xl-2 my-2"
+ }).appendTo(container);
+ var booking = $("<div/>", {
+ class: "card"
+ }).appendTo(column);
+ var heading = $("<div/>", {
+ class: "card-header"
+ }).text(`Booking ${booking_id}`).appendTo(booking);
+ var body = $("<ul/>", {
+ class: "list-group list-group-flush"
+ }).appendTo(booking);
+ var footer = $("<div/>", {
+ text: "Hosts:",
+ class: "card-footer d-flex flex-column"
+ }).appendTo(booking);
+
+ // Append information to the card body
+ $(`<li class="list-group-item">Start: ${booking_hosts[booking_id].start}</li>`).appendTo(body);
+ $(`<li class="list-group-item">End: ${booking_hosts[booking_id].end}</li>`).appendTo(body);
+ $(`<li class="list-group-item">Purpose: ${booking_hosts[booking_id].purpose}</li>`).appendTo(body);
+
+ // Append hosts to footer
var hosts = booking_hosts[booking_id].hosts;
- for(var i=0; i<hosts.length; i++){
- var host = document.createElement("DIV");
- host.id = "booking_" + booking_id + "_host_" + hosts[i].name;
- host.classList.add("host");
- host.appendChild(document.createTextNode(hosts[i].name));
- var hostname = hosts[i].name;
- host.booking = booking_id;
- host.hostname = hostname;
- host.onclick = function() {
- select(this.booking, this.hostname);
- }
- booking.appendChild(host);
+ for (const host of hosts) {
+ $("<button/>", {
+ class: "btn btn-outline-primary w-100 mt-1 hostbtn",
+ id: `booking_${booking_id}_host_${host.name}`,
+ text: host.name,
+ booking: booking_id,
+ hostname: host.name,
+ click: function() {
+ select(this);
+ }
+ }).appendTo(footer);
}
- bookings.push(booking);
- container.appendChild(booking);
}
}
draw_bookings();
@@ -116,13 +81,3 @@ if(initial){
}
</script>
{% endblock content %}
-
-{% block onleave %}
-var ajaxForm = $("#host_select_form");
-var formData = ajaxForm.serialize();
-req = new XMLHttpRequest();
-req.open("POST", "/wf/workflow/", false);
-req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-req.onerror = function() { alert("problem submitting form"); }
-req.send(formData);
-{% endblock %}
diff --git a/src/templates/workflow/confirm.html b/src/templates/workflow/confirm.html
index c1f3440..b7e6c46 100644
--- a/src/templates/workflow/confirm.html
+++ b/src/templates/workflow/confirm.html
@@ -5,26 +5,7 @@
{% block content %}
-<style>
- #form_div {
- width: 100%;
- padding: 5%;
- text-align: center;
- }
- #text_display {
- text-align: left;
- display: inline-block;
- }
- #text_wrapper {
- text-align: center; /*centers child div*/
- }
- p {
- margin:0;
- padding:0;
- }
-</style>
-
-<div style="text-align:center;">
+<div class="text-center">
<h3>Confirm Session</h3>
</div>
<div id="vlan_warning"></div>
@@ -32,29 +13,34 @@
{% csrf_token %}
<input id="vlan_input" name="vlan_input" type="hidden"/>
</form>
-<div id="text_wrapper">
- <div id="text_display">
- <pre>{{confirmation_info|escape}}</pre>
+<div class="container">
+ <div class="row justify-content-center">
+ <div class="col-auto">
+ <pre>{{confirmation_info|escape}}</pre>
+ </div>
</div>
-</div>
-<div id="form_div">
-<form id="confirmation_form" action="/wf/workflow/" method="post">
- {% csrf_token %}
- <div style="display: none;">
- {{form|default:"<p> No Form Loaded</p>"}}
+ <div class="row">
+ <div class="col">
+ <div id="form_div" class="text-center p-4">
+ <form id="confirmation_form" action="/wf/workflow/" method="post">
+ {% csrf_token %}
+ <div class="d-none">
+ {{form|default:"<p> No Form Loaded</p>"}}
+ </div>
+ </form>
+ <div class="cform_buttons mx-auto">
+ <button id="confirm_button" class="btn btn-success" onclick="formconfirm()">Confirm</button>
+ <button id="cancel_button" class="btn btn-danger" onclick="formcancel()">Cancel</button>
+ </div>
+ <div class="d-none">
+ <form id="manager_delete_form" action="/wf/workflow/finish/" method="post">
+ {% csrf_token %}
+ </form>
+ </div>
+ </div>
+ </div>
</div>
-</form>
-<div class="cform_buttons">
- <button id="confirm_button" class="btn btn-success" onclick="formconfirm()">Confirm</button>
- <button id="cancel_button" class="btn btn-danger" onclick="formcancel()">Cancel</button>
-</div>
-
-<div style="display: none;">
-<form id="manager_delete_form" action="/wf/workflow/finish/" method="post">
- {% csrf_token %}
-</form>
</div>
-
<script>
var select = document.getElementById("id_confirm");
@@ -114,47 +100,45 @@
}
</script>
<script>
-
-function fixVlans() {
- document.getElementById("vlan_input").value = "True";
- var form = $("#vlan_form");
- var formData = form.serialize();
- var req = new XMLHttpRequest();
- req.open("POST", "/wf/workflow/", false);
- req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- req.onerror = function() { alert("problem submitting form"); }
- req.onreadystatechange = function() { //replaces current page with response
- if(req.readyState === 4 ) {
- var d = document.getElementById("vlan_warning").innerHTML = "";
- document.getElementById("confirm_button").disabled = false;
- document.getElementById("cancel_button").disabled = false;
+ function fixVlans() {
+ document.getElementById("vlan_input").value = "True";
+ var form = $("#vlan_form");
+ var formData = form.serialize();
+ var req = new XMLHttpRequest();
+ req.open("POST", "/wf/workflow/", false);
+ req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ req.onerror = function() { alert("problem submitting form"); }
+ req.onreadystatechange = function() { //replaces current page with response
+ if(req.readyState === 4 ) {
+ var d = document.getElementById("vlan_warning").innerHTML = "";
+ document.getElementById("confirm_button").disabled = false;
+ document.getElementById("cancel_button").disabled = false;
+ }
}
+ req.send(formData);
+ }
+ var problem = {{vlan_warning|default:'false'}};
+ if(problem){
+ var d = document.getElementById("vlan_warning");
+ var h3 = document.createElement("h3");
+ h3.innerHTML = "WARNING: Vlans not available";
+ var h4 = document.createElement("h4");
+ h4.innerHTML = "The vlans you selected are not currently available. Would you like to automatically change them?";
+ var button1 = document.createElement("button");
+ button1.innerHTML = "Correct Vlans For Me";
+ button1.onclick = function() { fixVlans(); }
+
+ var button2 = document.createElement("button");
+ button2.innerHTML = "Cancel. I will change my vlans";
+ button2.onclick = function() { formcancel(); }
+ d.appendChild(h3);
+ d.appendChild(h4);
+ d.appendChild(button1);
+ d.appendChild(button2);
+ document.getElementById("confirm_button").disabled = true;
+ document.getElementById("cancel_button").disabled = true;
}
- req.send(formData);
-}
-var problem = {{vlan_warning|default:'false'}};
-if(problem){
- var d = document.getElementById("vlan_warning");
- var h3 = document.createElement("h3");
- h3.innerHTML = "WARNING: Vlans not available";
- var h4 = document.createElement("h4");
- h4.innerHTML = "The vlans you selected are not currently available. Would you like to automatically change them?";
- var button1 = document.createElement("button");
- button1.innerHTML = "Correct Vlans For Me";
- button1.onclick = function() { fixVlans(); }
-
- var button2 = document.createElement("button");
- button2.innerHTML = "Cancel. I will change my vlans";
- button2.onclick = function() { formcancel(); }
- d.appendChild(h3);
- d.appendChild(h4);
- d.appendChild(button1);
- d.appendChild(button2);
- document.getElementById("confirm_button").disabled = true;
- document.getElementById("cancel_button").disabled = true;
-}
</script>
-</div>
{% block element_messages %}
{% endblock element_messages %}
diff --git a/src/templates/workflow/resource_select.html b/src/templates/workflow/resource_select.html
deleted file mode 100644
index cd04137..0000000
--- a/src/templates/workflow/resource_select.html
+++ /dev/null
@@ -1,50 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-<p>resource selection template</p>
-
-<style>
- .db_pane_wrapper{
- display: grid;
- grid-template-columns: 49% 2% 49%;
- }
- .divider{
- border-style: solid;
- height: 100vh;
- }
-
- .hidden_form{
- display: none;
- }
-</style>
-
-<div class="db_pane_wrapper">
- <div class="pane_one">
- <p>Select Resource</p>
-
- </div>
- <div class="divider">
-
- </div>
- <div class="pane_two">
- <button>Create New Resource</button>
- </div>
-</div>
-
-<div class="hidden_form" id="form_div">
- <form method="post" action="" class="form" id="resource_selection_form">
- {% csrf_token %}
- {% bootstrap_field form.resourcebundle %}
- {% buttons %}
- <button type="submit" class="btn btn btn-success">
- Confirm Edit
- </button>
- {% endbuttons %}
- </form>
-</div>
-
-{% endblock content %} \ No newline at end of file
diff --git a/src/templates/workflow/viewport-base.html b/src/templates/workflow/viewport-base.html
index aa01d7e..103a095 100644
--- a/src/templates/workflow/viewport-base.html
+++ b/src/templates/workflow/viewport-base.html
@@ -5,165 +5,13 @@
{% block content %}
-<style>
- .go_btn {
-
- position: absolute;
- width: 100px;
- top: 170px;
- height: calc(100% - 170px);
-
- }
-
- .go_btn_disabled {
- background-color: #ffffff;
- }
-
- .go_forward {
- right: 0px;
- border-left: none;
- }
-
- .go_back {
- left: 251px;
- border-right: none;
- }
-
-
- .btn_wrapper {
- text-align: center;
- margin-bottom: 5px;
-
- }
-
- {% if DEBUG %}
- .add_btn_wrapper {
- right: 130px;
- top: 10px;
- position: absolute;
- }
- {% endif %}
-
- #breadcrumbs {
- margin-bottom: 0;
- }
-
- .btn_wrapper {
- margin: 0;
- }
-
- .step {
- display: inline;
- padding: 7px;
- margin: 1px;
- font-size: 14pt;
- cursor: default;
- }
-
- .step:active {
- -webkit-box-shadow: inherit;
- box-shadow: inherit;
- }
-
- .step_active:active {
- -webkit-box-shadow: inherit;
- box-shadow: inherit;
- }
-
- .step_active {
- display: inline;
- padding: 7px;
- margin: 1px;
- cursor: default;
- font-size: 14pt;
- padding-bottom: 4px !important;
- border-bottom: 4px solid #41ba78 !important;
- }
-
- .step_hidden {
- background: #EFEFEF;
- color: #999999;
- }
-
- .step_invalid::after {
- content: " \2612";
- color: #CC3300;
- }
-
- .step_valid::after {
- content: " \2611";
- color: #41ba78;
- }
-
- .step_untouched::after {
- content: " \2610";
- }
-
- .iframe_div {
- width: calc(100% - 450px);
- margin-left: 70px;
- height: calc(100vh - 155px);
- position: absolute;
- border: none;
- }
-
- .iframe_elem {
- width: 100%;
- height: calc(100vh - 155px);
- border: none;
- }
-
- #breadcrumbs {
- background-color: inherit;
- }
-
- #breadcrumbs.breadcrumb>li {
- border: 1px solid #cccccc;
- border-left: none;
- }
-
- #breadcrumbs.breadcrumb>li:first-child {
- border-left: 1px solid #cccccc;
- }
-
- #breadcrumbs.breadcrumb>li+li:before {
- content: "";
- width: 0;
- margin: 0;
- padding: 0;
- }
-
- #topPagination .topcrumb {
- flex: 1 1 0;
- display: flex;
- align-content: center;
- justify-content: center;
- border: 1px solid #dee2e6;
- border-left: none;
- }
-
- .topcrumb > span {
- color: #343a40;
- cursor: default;
- }
-
- .topcrumb.active > span {
- background: #007bff;
- color: white;
- }
-
- .topcrumb.disabled > span {
- color: #6c757d;
- background: #f8f9fa;
- }
-</style>
<!-- Pagination -->
<div class="row mt-3">
<div class="col">
<nav>
<ul class="pagination d-flex flex-row" id="topPagination">
<li class="page-item flex-shrink-1 page-control">
- <a class="page-link" href="#" id="gob" onclick="go('prev')">
+ <a class="page-link" href="#" id="gob" onclick="submit_and_go('prev')">
<i class="fas fa-backward"></i> Back
</a>
</li>
@@ -193,7 +41,7 @@
</a>
</li>
<li class="page-item flex-shrink-1 page-control">
- <a class="page-link text-right" href="#" id="gof" onclick="go('next')">
+ <a class="page-link text-right" href="#" id="gof" onclick="submit_and_go('next')">
Next <i class="fas fa-forward"></i>
</a>
</li>
@@ -205,10 +53,10 @@
<div class="row px-4">
<div class="col">
<div id="iframe_header" class="row view-header">
- <div class="col-lg-12 step_header">
- <h1 class="step_title d-inline-block" id="view_title"></h1>
+ <div class="col-lg-12">
+ <h1 class="d-inline-block" id="view_title"></h1>
<span class="description text-muted" id="view_desc"></span>
- <p class="step_message" id="view_message"></p>
+ <p id="view_message"></p>
</div>
<script>
function update_description(title, desc) {
@@ -222,39 +70,30 @@
document.getElementById("view_message").classList.add("message_" + stepstatus);
}
</script>
- <!-- /.col-lg-12 -->
</div>
</div>
<div class="col-auto align-self-center d-flex">
<button id="cancel_btn" class="btn btn-danger ml-auto" onclick="cancel_wf()">Cancel</button>
</div>
</div>
-<!-- Content here -->
<div class="row d-flex flex-column flex-grow-1">
<div class="container-fluid d-flex flex-column h-100">
<div class="row d-flex flex-grow-1 p-4">
- <!-- iframe workflow -->
- <div class="col-12 d-flex border flex-grow-1">
- <!-- This was where the iframe went -->
- <iframe src="/wf/workflow" class="w-100 h-100" scrolling="yes" id="viewport-iframe"
- frameBorder="0"></iframe>
+ <div class="col-12 d-flex border flex-grow-1 px-0">
+ <div id="formContainer" class="h-100 w-100"></div>
</div>
</div>
</div>
</div>
-<div class="btn_wrapper">
-</div>
{% csrf_token %}
-
+<script src="{% static "js/dashboard.js" %}"></script>
<script type="text/javascript">
update_context();
var step = 0;
var page_count = 0;
- var context_data = false;
- function go(to) {
- step_on_leave();
- request_leave(to);
+ function submit_and_go(to) {
+ submitStepForm(to);
}
function request_leave(to) {
@@ -289,7 +128,7 @@
req.onload = function (e) {
if (req.readyState === 4) {
if (req.status < 300) {
- document.getElementById("viewport-iframe").srcdoc = this.responseText;
+ write_iframe(this.responseText);
} else {
problem();
}
@@ -301,10 +140,6 @@
req.send();
}
- function step_on_leave() {
- document.getElementById("viewport-iframe").contentWindow.step_on_leave();
- }
-
function errors_exist(data) {
var stat = data['steps'][data['active']]['valid'];
if (stat >= 100 && stat < 200) {
@@ -328,8 +163,7 @@
});
}
- function update_page(data) {
- context_data = data;
+ function updateBreadcrumbs(data) {
update_breadcrumbs(data);
if (data["workflow_count"] == 1) {
document.getElementById("cancel_btn").innerText = "Exit Workflow";
@@ -430,16 +264,13 @@
url = "/wf/workflow/";
req.open("GET", url, true);
req.onload = function (e) {
- var doc = document.getElementById("viewport-iframe").contentWindow.document;
- doc.open();
- doc.write(this.responseText);
- doc.close();
+ write_iframe(this.responseText);
}
req.send();
}
function write_iframe(contents) {
- document.getElementById("viewport-iframe").contentWindow.document.innerHTML = contents;
+ $("#formContainer").html(contents);
}
function redirect_root() {
@@ -477,10 +308,19 @@
function refresh_wf_iframe() {
window.location = window.location;
}
+
+ // Load the actual first page
+ $(document).ready(function(){
+ $.ajax("/wf/workflow", {
+ success: function(data) {
+ write_iframe(data);
+ }
+ });
+ });
</script>
-<div style="display: none;" id="workflow_pop_form_div">
+<div class="d-none" id="workflow_pop_form_div">
<form id="workflow_pop_form" action="/wf/workflow/finish/" method="post">
{% csrf_token %}
</form>
</div>
-{% endblock content %} \ No newline at end of file
+{% endblock content %}
diff --git a/src/templates/workflow/viewport-element.html b/src/templates/workflow/viewport-element.html
index 7a7165a..bf13304 100644
--- a/src/templates/workflow/viewport-element.html
+++ b/src/templates/workflow/viewport-element.html
@@ -1,19 +1,10 @@
-{% extends "layout.html" %}
{% load bootstrap4 %}
{% load staticfiles %}
{% block basecontent %}
- <div id="wrapper">
- <!-- Page Content -->
- <div id="page-wrapper">
-
- {% block content %}
-
- {% endblock content %}
- </div>
- <!-- /#page-wrapper -->
- </div>
+ {% block content %}
+ {% endblock content %}
{% block vport_comm %}
<script type="text/javascript">
var step_count = {{ step_number|default:0 }};
@@ -51,19 +42,9 @@
{% endblock validate_step %}
- <script>
- function step_on_leave() {
- {% block onleave %}
- alert("override onleave");
- {% endblock %}
- }
- </script>
-
<div class="messages">
{% block element_messages %}
{% bootstrap_messages %}
{% endblock %}
</div>
-
- <!-- /#wrapper -->
{% endblock basecontent %}
diff --git a/src/workflow/booking_workflow.py b/src/workflow/booking_workflow.py
index 42372ce..3698164 100644
--- a/src/workflow/booking_workflow.py
+++ b/src/workflow/booking_workflow.py
@@ -7,7 +7,6 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-from django.contrib import messages
from django.utils import timezone
from datetime import timedelta
@@ -171,8 +170,8 @@ class Booking_Meta(WorkflowStep):
context['form'] = BookingMetaForm(initial=initial, user_initial=default, owner=owner)
return context
- def post_render(self, request):
- form = BookingMetaForm(data=request.POST, owner=request.user)
+ def post(self, post_data, user):
+ form = BookingMetaForm(data=post_data, owner=user)
forms = self.repo_get(self.repo.BOOKING_FORMS, {})
@@ -212,9 +211,6 @@ class Booking_Meta(WorkflowStep):
self.repo_put(self.repo.BOOKING_MODELS, models)
self.repo_put(self.repo.CONFIRMATION, confirm)
- messages.add_message(request, messages.SUCCESS, 'Form Validated', fail_silently=True)
self.set_valid("Step Completed")
else:
- messages.add_message(request, messages.ERROR, "Form didn't validate", fail_silently=True)
self.set_invalid("Please complete the fields highlighted in red to continue")
- return self.render(request)
diff --git a/src/workflow/forms.py b/src/workflow/forms.py
index ee44ecd..a2746f9 100644
--- a/src/workflow/forms.py
+++ b/src/workflow/forms.py
@@ -15,6 +15,7 @@ from django.template.loader import render_to_string
from django.forms.widgets import NumberInput
import json
+import urllib
from account.models import Lab
from account.models import UserProfile
@@ -428,6 +429,24 @@ class ConfirmationForm(forms.Form):
)
+def validate_step(value):
+ if value not in ["prev", "next", "current"]:
+ raise ValidationError(str(value) + " is not allowed")
+
+
+def validate_step_form(value):
+ try:
+ urllib.parse.unquote_plus(value)
+ except Exception:
+ raise ValidationError("Value is not url encoded data")
+
+
+class ManagerForm(forms.Form):
+ step = forms.CharField(widget=forms.widgets.HiddenInput, validators=[validate_step])
+ step_form = forms.CharField(widget=forms.widgets.HiddenInput, validators=[validate_step_form])
+ # other fields?
+
+
class OPNFVSelectionForm(forms.Form):
installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=True)
scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=True)
diff --git a/src/workflow/models.py b/src/workflow/models.py
index 6c6bd9a..866f442 100644
--- a/src/workflow/models.py
+++ b/src/workflow/models.py
@@ -8,8 +8,7 @@
##############################################################################
-from django.shortcuts import render
-from django.contrib import messages
+from django.template.loader import get_template
from django.http import HttpResponse
from django.utils import timezone
@@ -219,12 +218,19 @@ class WorkflowStep(object):
return context
def render(self, request):
- self.context = self.get_context()
- return render(request, self.template, self.context)
+ return HttpResponse(self.render_to_string(request))
+
+ def render_to_string(self, request):
+ template = get_template(self.template)
+ return template.render(self.get_context(), request)
def post_render(self, request):
+ self.post(request.POST, request.user)
return self.render(request)
+ def post(self, post_content, user):
+ raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete post() method")
+
def test_render(self, request):
if request.method == "POST":
return self.post_render(request)
@@ -263,22 +269,18 @@ class AbstractSelectOrCreate(WorkflowStep):
def alert_bundle_missing(self): # override in subclasses to change message if field isn't filled out
self.set_invalid("Please select a valid bundle")
- def post_render(self, request):
- context = self.get_context()
- form = self.form(request.POST, queryset=self.get_form_queryset())
+ def post(self, post_data, user):
+ form = self.form(post_data, queryset=self.get_form_queryset())
if form.is_valid():
bundle = form.get_validated_bundle()
if not bundle:
self.alert_bundle_missing()
- return render(request, self.template, context)
+ return
self.repo_put(self.select_repo_key, bundle)
self.put_confirm_info(bundle)
self.set_valid("Step Completed")
else:
self.alert_bundle_missing()
- messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
-
- return self.render(request)
def get_context(self):
default = []
@@ -323,8 +325,8 @@ class Confirmation_Step(WorkflowStep):
if errors:
return errors
- def post_render(self, request):
- form = ConfirmationForm(request.POST)
+ def post(self, post_data, user):
+ form = ConfirmationForm(post_data)
if form.is_valid():
data = form.cleaned_data['confirm']
context = self.get_context()
@@ -332,20 +334,18 @@ class Confirmation_Step(WorkflowStep):
context["bypassed"] = "true"
errors = self.flush_to_db()
if errors:
- messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
+ self.set_invalid("ERROR OCCURRED: " + errors)
else:
- messages.add_message(request, messages.SUCCESS, "Confirmed")
+ self.set_valid("Confirmed")
- return HttpResponse('')
elif data == "False":
context["bypassed"] = "true"
- messages.add_message(request, messages.SUCCESS, "Canceled")
- return render(request, self.template, context)
+ self.set_valid("Canceled")
else:
- pass
+ self.set_invalid("Bad Form Contents")
else:
- pass
+ self.set_invalid("Bad Form Contents")
class Repository():
diff --git a/src/workflow/opnfv_workflow.py b/src/workflow/opnfv_workflow.py
index 7d499ec..a192d6e 100644
--- a/src/workflow/opnfv_workflow.py
+++ b/src/workflow/opnfv_workflow.py
@@ -74,8 +74,8 @@ class Pick_Installer(WorkflowStep):
context["form"] = OPNFVSelectionForm(initial=initial)
return context
- def post_render(self, request):
- form = OPNFVSelectionForm(request.POST)
+ def post(self, post_data, user):
+ form = OPNFVSelectionForm(post_data)
if form.is_valid():
installer = form.cleaned_data['installer']
scenario = form.cleaned_data['scenario']
@@ -88,8 +88,6 @@ class Pick_Installer(WorkflowStep):
else:
self.set_invalid("Please select an Installer and Scenario")
- return self.render(request)
-
class Assign_Network_Roles(WorkflowStep):
template = 'config_bundle/steps/assign_network_roles.html'
@@ -150,11 +148,11 @@ class Assign_Network_Roles(WorkflowStep):
confirm['network roles'][role['role']] = role['network'].name
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.OPNFV_MODELS, {})
config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
roles = OPNFV_SETTINGS.NETWORK_ROLES
- net_role_formset = self.create_netformset(roles, config_bundle, data=request.POST)
+ net_role_formset = self.create_netformset(roles, config_bundle, data=post_data)
if net_role_formset.is_valid():
results = []
for form in net_role_formset:
@@ -168,7 +166,6 @@ class Assign_Network_Roles(WorkflowStep):
self.update_confirmation()
else:
self.set_invalid("Please complete all fields")
- return self.render(request)
class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in sw workflow, merge the two?
@@ -227,8 +224,8 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in
confirm['host roles'][role['host_name']] = role['role'].name
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
- formset = self.create_host_role_formset(data=request.POST)
+ def post(self, post_data, user):
+ formset = self.create_host_role_formset(data=post_data)
models = self.repo_get(self.repo.OPNFV_MODELS, {})
host_roles = models.get("host_roles", [])
@@ -254,8 +251,6 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in
else:
self.set_invalid("Please complete all fields")
- return self.render(request)
-
class MetaInfo(WorkflowStep):
template = 'config_bundle/steps/config_software.html'
@@ -280,11 +275,11 @@ class MetaInfo(WorkflowStep):
confirm['description'] = meta['description']
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.OPNFV_MODELS, {})
info = models.get("meta", {})
- form = BasicMetaForm(request.POST)
+ form = BasicMetaForm(post_data)
if form.is_valid():
info['name'] = form.cleaned_data['name']
info['description'] = form.cleaned_data['description']
@@ -294,6 +289,4 @@ class MetaInfo(WorkflowStep):
self.set_valid("Complete")
else:
self.set_invalid("Please correct the errors shown below")
-
self.repo_put(self.repo.OPNFV_MODELS, models)
- return self.render(request)
diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py
index 06737d2..2f4aa5d 100644
--- a/src/workflow/resource_bundle_workflow.py
+++ b/src/workflow/resource_bundle_workflow.py
@@ -8,8 +8,6 @@
##############################################################################
-from django.shortcuts import render
-from django.forms import formset_factory
from django.conf import settings
import json
@@ -22,7 +20,6 @@ from workflow.forms import (
HardwareDefinitionForm,
NetworkDefinitionForm,
ResourceMetaForm,
- GenericHostMetaForm
)
from resource_inventory.models import (
GenericResourceBundle,
@@ -111,9 +108,9 @@ class Define_Hardware(WorkflowStep):
confirm['resource']['lab'] = models['lab'].lab_user.username
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
+ def post(self, post_data, user):
try:
- self.form = HardwareDefinitionForm(request.POST)
+ self.form = HardwareDefinitionForm(post_data)
if self.form.is_valid():
self.update_models(self.form.cleaned_data)
self.update_confirmation()
@@ -122,8 +119,6 @@ class Define_Hardware(WorkflowStep):
self.set_invalid("Please complete the fields highlighted in red to continue")
except Exception as e:
self.set_invalid(str(e))
- self.context = self.get_context()
- return render(request, self.template, self.context)
class Define_Nets(WorkflowStep):
@@ -206,13 +201,13 @@ class Define_Nets(WorkflowStep):
return context
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
if 'hosts' in models:
host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
self.repo_put(self.repo.GRB_LAST_HOSTLIST, host_set)
try:
- xmlData = request.POST.get("xml")
+ xmlData = post_data.get("xml")
self.updateModels(xmlData)
# update model with xml
self.set_valid("Networks applied successfully")
@@ -220,7 +215,6 @@ class Define_Nets(WorkflowStep):
self.set_invalid("Public network not availble")
except Exception as e:
self.set_invalid("An error occurred when applying networks: " + str(e))
- return self.render(request)
def updateModels(self, xmlData):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
@@ -380,8 +374,8 @@ class Resource_Meta_Info(WorkflowStep):
context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
return context
- def post_render(self, request):
- form = ResourceMetaForm(request.POST)
+ def post(self, post_data, user):
+ form = ResourceMetaForm(post_data)
if form.is_valid():
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
name = form.cleaned_data['bundle_name']
@@ -402,62 +396,5 @@ class Resource_Meta_Info(WorkflowStep):
confirm_info["description"] = tmp
self.repo_put(self.repo.CONFIRMATION, confirm)
self.set_valid("Step Completed")
-
else:
self.set_invalid("Please correct the fields highlighted in red to continue")
- pass
- return self.render(request)
-
-
-class Host_Meta_Info(WorkflowStep):
- template = "resource/steps/host_info.html"
- title = "Host Info"
- description = "We need a little bit of information about your chosen machines"
- short_title = "host info"
-
- def __init__(self, *args, **kwargs):
- super(Host_Meta_Info, self).__init__(*args, **kwargs)
- self.formset = formset_factory(GenericHostMetaForm, extra=0)
-
- def get_context(self):
- context = super(Host_Meta_Info, self).get_context()
- GenericHostFormset = self.formset
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- initial_data = []
- if "hosts" not in models:
- context['error'] = "Please go back and select your hosts"
- else:
- for host in models['hosts']:
- profile = host.profile.name
- name = host.resource.name
- if not name:
- name = ""
- initial_data.append({"host_profile": profile, "host_name": name})
- context['formset'] = GenericHostFormset(initial=initial_data)
- return context
-
- def post_render(self, request):
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- if 'hosts' not in models:
- models['hosts'] = []
- hosts = models['hosts']
- i = 0
- confirm_hosts = []
- GenericHostFormset = self.formset
- formset = GenericHostFormset(request.POST)
- if formset.is_valid():
- for form in formset:
- host = hosts[i]
- host.resource.name = form.cleaned_data['host_name']
- i += 1
- confirm_hosts.append({"name": host.resource.name, "profile": host.profile.name})
- models['hosts'] = hosts
- self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "resource" not in confirm:
- confirm['resource'] = {}
- confirm['resource']['hosts'] = confirm_hosts
- self.repo_put(self.repo.CONFIRMATION, confirm)
- else:
- pass
- return self.render(request)
diff --git a/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py
index 5414784..4266587 100644
--- a/src/workflow/snapshot_workflow.py
+++ b/src/workflow/snapshot_workflow.py
@@ -49,15 +49,15 @@ class Select_Host_Step(WorkflowStep):
context['chosen'] = chosen
return context
- def post_render(self, request):
- host_data = request.POST.get("host")
+ def post(self, post_data, user):
+ host_data = post_data.get("host")
if not host_data:
self.set_invalid("Please select a host")
- return self.render(request)
+ return
host = json.loads(host_data)
if 'name' not in host or 'booking' not in host:
self.set_invalid("Invalid host selected")
- return self.render(request)
+ return
name = host['name']
booking_id = host['booking']
booking = Booking.objects.get(pk=booking_id)
@@ -76,7 +76,6 @@ class Select_Host_Step(WorkflowStep):
confirm['snapshot'] = snap_confirm
self.repo_put(self.repo.CONFIRMATION, confirm)
self.set_valid("Success")
- return self.render(request)
class Image_Meta_Step(WorkflowStep):
@@ -97,8 +96,8 @@ class Image_Meta_Step(WorkflowStep):
context['form'] = form
return context
- def post_render(self, request):
- form = BasicMetaForm(request.POST)
+ def post(self, post_data, user):
+ form = BasicMetaForm(post_data)
if form.is_valid():
name = form.cleaned_data['name']
self.repo_put(self.repo.SNAPSHOT_NAME, name)
@@ -115,5 +114,3 @@ class Image_Meta_Step(WorkflowStep):
self.set_valid("Success")
else:
self.set_invalid("Please Fill out the Form")
-
- return self.render(request)
diff --git a/src/workflow/sw_bundle_workflow.py b/src/workflow/sw_bundle_workflow.py
index 0c558fc..4dc0b8e 100644
--- a/src/workflow/sw_bundle_workflow.py
+++ b/src/workflow/sw_bundle_workflow.py
@@ -104,7 +104,7 @@ class Define_Software(WorkflowStep):
return context
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.CONFIG_MODELS, {})
if "bundle" not in models:
models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
@@ -112,8 +112,8 @@ class Define_Software(WorkflowStep):
confirm = self.repo_get(self.repo.CONFIRMATION, {})
hosts = self.get_host_list()
- models['headnode_index'] = request.POST.get("headnode", 1)
- formset = self.create_hostformset(hosts, data=request.POST)
+ models['headnode_index'] = post_data.get("headnode", 1)
+ formset = self.create_hostformset(hosts, data=post_data)
has_headnode = False
if formset.is_valid():
models['host_configs'] = []
@@ -140,7 +140,7 @@ class Define_Software(WorkflowStep):
if not has_headnode:
self.set_invalid('Must have one "Headnode" per POD')
- return self.render(request)
+ return
self.repo_put(self.repo.CONFIG_MODELS, models)
if "configuration" not in confirm:
@@ -151,8 +151,6 @@ class Define_Software(WorkflowStep):
else:
self.set_invalid("Please complete all fields")
- return self.render(request)
-
class Config_Software(WorkflowStep):
template = 'config_bundle/steps/config_software.html'
@@ -172,7 +170,7 @@ class Config_Software(WorkflowStep):
context["form"] = BasicMetaForm(initial=initial)
return context
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.CONFIG_MODELS, {})
if "bundle" not in models:
models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
@@ -181,7 +179,7 @@ class Config_Software(WorkflowStep):
if "configuration" not in confirm:
confirm['configuration'] = {}
- form = BasicMetaForm(request.POST)
+ form = BasicMetaForm(post_data)
if form.is_valid():
models['bundle'].name = form.cleaned_data['name']
models['bundle'].description = form.cleaned_data['description']
@@ -194,5 +192,3 @@ class Config_Software(WorkflowStep):
self.repo_put(self.repo.CONFIG_MODELS, models)
self.repo_put(self.repo.CONFIRMATION, confirm)
-
- return self.render(request)
diff --git a/src/workflow/tests/test_steps.py b/src/workflow/tests/test_steps.py
index 380102a..cb676c7 100644
--- a/src/workflow/tests/test_steps.py
+++ b/src/workflow/tests/test_steps.py
@@ -12,7 +12,7 @@ from dashboard.populate_db import Populator
from workflow.tests import constants
from workflow.workflow_factory import WorkflowFactory
from workflow.models import Repository
-from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info, Host_Meta_Info
+from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info
from workflow.sw_bundle_workflow import SWConf_Resource_Select, Define_Software, Config_Software
from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, Booking_Meta
from django.http import QueryDict, HttpRequest
@@ -23,9 +23,6 @@ from resource_inventory.models import (
OPNFVRole,
Image,
GenericResourceBundle,
- GenericHost,
- HostProfile,
- GenericResource,
ConfigBundle
)
@@ -129,49 +126,6 @@ class DefineHardwareTestCase(BaseStepTestCase):
response, context = self.step_test(Define_Hardware, data)
-class HostMetaInfoTestCase(BaseStepTestCase):
-
- def makeRepo(self):
- """
- override to provide step with needed host info
- """
- repo = super(HostMetaInfoTestCase, self).makeRepo()
- # get models
- models = {}
- models['bundle'] = GenericResourceBundle()
- # make generic hosts
- gres1 = GenericResource(bundle=models['bundle'])
- prof1 = HostProfile.objects.get(name="Test profile 0")
- ghost1 = GenericHost(profile=prof1, resource=gres1)
-
- gres2 = GenericResource(bundle=models['bundle'])
- prof2 = HostProfile.objects.get(name="Test profile 3")
- ghost2 = GenericHost(profile=prof2, resource=gres2)
- models['hosts'] = [ghost1, ghost2]
- repo.el[repo.GRESOURCE_BUNDLE_MODELS] = models
- return repo
-
- def test_step_with_good_data(self):
- data = {"form-INITIAL_FORMS": 2, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 2
- data['form-0-host_name'] = "first host"
- data['form-1-host_name'] = "second host"
- response, context = self.step_test(Host_Meta_Info, data)
-
- def test_step_with_bad_data(self): # TODO
- data = {"form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 0
- response, context = self.step_test(Host_Meta_Info, data)
-
- def test_step_with_empty_data(self):
- data = {"form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 0
- response, context = self.step_test(Host_Meta_Info, data)
-
-
class DefineNetsTestCase(BaseStepTestCase):
def test_step_with_good_data(self):
diff --git a/src/workflow/urls.py b/src/workflow/urls.py
index 5a97904..ae620d0 100644
--- a/src/workflow/urls.py
+++ b/src/workflow/urls.py
@@ -11,7 +11,7 @@
from django.conf.urls import url
from django.conf import settings
-from workflow.views import step_view, delete_session, manager_view, viewport_view
+from workflow.views import delete_session, manager_view, viewport_view, add_workflow, cancel_workflow
from workflow.models import Repository
from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info
from workflow.booking_workflow import SWConfig_Select, Booking_Resource_Select, Booking_Meta
@@ -19,9 +19,10 @@ from workflow.booking_workflow import SWConfig_Select, Booking_Resource_Select,
app_name = 'workflow'
urlpatterns = [
- url(r'^workflow/$', step_view, name='workflow'),
- url(r'^workflow/finish/$', delete_session, name='delete_session'),
+ url(r'^finish/$', delete_session, name='delete_session'),
url(r'^manager/$', manager_view, name='manager'),
+ url(r'^add/$', add_workflow, name='add_workflow'),
+ url(r'^cancel/$', cancel_workflow, name='cancel_workflow'),
url(r'^$', viewport_view, name='viewport')
]
diff --git a/src/workflow/views.py b/src/workflow/views.py
index 7ed9031..47241e2 100644
--- a/src/workflow/views.py
+++ b/src/workflow/views.py
@@ -8,7 +8,7 @@
##############################################################################
-from django.http import HttpResponseGone, JsonResponse
+from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.urls import reverse
@@ -49,7 +49,7 @@ def delete_session(request):
manager = attempt_auth(request)
if not manager:
- return HttpResponseGone("No session found that relates to current request")
+ return no_workflow(request)
not_last_workflow, result = manager.pop_workflow()
@@ -60,48 +60,34 @@ def delete_session(request):
return JsonResponse(get_redirect_response(result))
-def step_view(request):
+def add_workflow(request):
manager = attempt_auth(request)
if not manager:
- # no manager found, redirect to "lost" page
return no_workflow(request)
- if request.GET.get('step') is not None:
- if request.GET.get('step') == 'next':
- manager.go_next()
- elif request.GET.get('step') == 'prev':
- manager.go_prev()
- else:
- raise Exception("requested action for new step had malformed contents: " + request.GET.get('step'))
- return manager.render(request)
+ try:
+ workflow_type = int(request.POST.get('workflow_type'))
+ except ValueError:
+ return HttpResponse(status=400)
+ manager.add_workflow(workflow_type=workflow_type)
+ return manager.render(request) # do we want this?
-def manager_view(request):
- manager = attempt_auth(request)
+def cancel_workflow(request):
+ manager = attempt_auth(request)
if not manager:
- return HttpResponseGone("No session found that relates to current request")
+ return no_workflow(request)
- if request.method == 'GET':
- # no need for this statement if only intercepting post requests
+ if not manager.pop_workflow():
+ del ManagerTracker.managers[request.session['manager_session']]
- # return general context for viewport page
- return manager.status(request)
- if request.method == 'POST':
- if request.POST.get('add') is not None:
- logger.debug("add found")
- target_id = None
- if 'target' in request.POST:
- target_id = int(request.POST.get('target'))
- manager.add_workflow(workflow_type=int(request.POST.get('add')), target_id=target_id)
- elif request.POST.get('edit') is not None and request.POST.get('edit_id') is not None:
- logger.debug("edit found")
- manager.add_workflow(workflow_type=request.POST.get('edit'), edit_object=int(request.POST.get('edit_id')))
- elif request.POST.get('cancel') is not None:
- if not manager.pop_workflow():
- del ManagerTracker.managers[request.session['manager_session']]
+def manager_view(request):
+ manager = attempt_auth(request)
+ if not manager:
+ return no_workflow(request)
- return manager.status(request)
+ return manager.handle_request(request)
def viewport_view(request):
@@ -129,10 +115,7 @@ def create_session(wf_type, request):
def no_workflow(request):
-
- logger.debug("There is no active workflow")
-
- return render(request, 'workflow/no_workflow.html', {'title': "Not Found"})
+ return render(request, 'workflow/no_workflow.html', {'title': "Not Found"}, status=404)
def login(request):
diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py
index 80b8a67..605eee7 100644
--- a/src/workflow/workflow_manager.py
+++ b/src/workflow/workflow_manager.py
@@ -9,6 +9,7 @@
from django.http import JsonResponse
+from django.http.request import QueryDict
from booking.models import Booking
from workflow.workflow_factory import WorkflowFactory
@@ -19,6 +20,7 @@ from resource_inventory.models import (
HostConfiguration,
OPNFVConfig
)
+from workflow.forms import ManagerForm
import logging
logger = logging.getLogger(__name__)
@@ -45,10 +47,7 @@ class SessionManager():
else:
step.disable()
- def add_workflow(self, workflow_type=None, target_id=None, **kwargs):
- if target_id is not None:
- self.prefill_repo(target_id, workflow_type)
-
+ def add_workflow(self, workflow_type=None, **kwargs):
repo = Repository()
if(len(self.workflows) >= 1):
defaults = self.workflows[-1].repository.get_child_defaults()
@@ -75,24 +74,37 @@ class SessionManager():
return (multiple_wfs, current_repo.el[current_repo.RESULT])
def status(self, request):
- try:
- meta_json = []
- for step in self.active_workflow().steps:
- meta_json.append(step.to_json())
- responsejson = {}
- responsejson["steps"] = meta_json
- responsejson["active"] = self.active_workflow().repository.el['active_step']
- responsejson["workflow_count"] = len(self.workflows)
- return JsonResponse(responsejson, safe=False)
- except Exception:
- pass
+ return {
+ "steps": [step.to_json() for step in self.active_workflow().steps],
+ "active": self.active_workflow().repository.el['active_step'],
+ "workflow_count": len(self.workflows)
+ }
+
+ def handle_post(self, request):
+ form = ManagerForm(request.POST)
+ if form.is_valid:
+ self.get_active_step().post(
+ QueryDict(form.cleaned_data['step_form']),
+ user=request.user
+ )
+ # change step
+ if form.cleaned_data['step'] == 'prev':
+ self.go_prev()
+ if form.cleaned_data['step'] == 'next':
+ self.go_next()
+ else:
+ pass # Exception?
+
+ def handle_request(self, request):
+ if request.method == 'POST':
+ self.handle_post(request)
+ return self.render()
def render(self, request, **kwargs):
- # filter out when a step needs to handle post/form data
- # if 'workflow' in post data, this post request was meant for me, not step
- if request.method == 'POST' and request.POST.get('workflow', None) is None:
- return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)
- return self.active_workflow().steps[self.active_workflow().active_index].render(request)
+ return JsonResponse({
+ "meta": self.status(),
+ "content": self.get_active_step().render_to_string(request)
+ })
def post_render(self, request):
return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)