diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | src/dashboard/views.py | 39 | ||||
-rw-r--r-- | src/static/css/base.css | 17 | ||||
-rw-r--r-- | src/static/js/dashboard.js | 199 | ||||
-rw-r--r-- | src/templates/base.html | 35 | ||||
-rw-r--r-- | src/templates/booking/quick_deploy.html | 26 | ||||
-rw-r--r-- | src/templates/dashboard/genericselect.html | 21 | ||||
-rw-r--r-- | src/templates/dashboard/landing.html | 46 | ||||
-rw-r--r-- | src/templates/dashboard/multiple_select_filter_widget.html | 30 | ||||
-rw-r--r-- | src/templates/dashboard/searchable_select_multiple.html | 4 | ||||
-rw-r--r-- | src/templates/resource/steps/define_hardware.html | 5 | ||||
-rw-r--r-- | src/templates/resource/steps/pod_definition.html | 3 | ||||
-rw-r--r-- | src/templates/workflow/confirm.html | 97 | ||||
-rw-r--r-- | src/templates/workflow/viewport-base.html | 268 | ||||
-rw-r--r-- | src/templates/workflow/viewport-element.html | 36 | ||||
-rw-r--r-- | src/workflow/models.py | 6 | ||||
-rw-r--r-- | src/workflow/urls.py | 6 | ||||
-rw-r--r-- | src/workflow/views.py | 59 | ||||
-rw-r--r-- | src/workflow/workflow_manager.py | 36 |
19 files changed, 316 insertions, 619 deletions
@@ -1,3 +1,5 @@ +.PHONY: build dev-up dev-start dev-stop up start stop data shell-nginx shell-web shell-db log-nginx log-web log-ps log-rmq log-worker + build: docker-compose -f docker-compose.yml -f docker-compose.override-dev.yml build diff --git a/src/dashboard/views.py b/src/dashboard/views.py index aaad7ab..9416cb4 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -63,36 +63,15 @@ def host_profile_detail_view(request): def landing_view(request): - manager = None - manager_detected = False - if 'manager_session' in request.session: - - try: - manager = ManagerTracker.managers[request.session['manager_session']] - - except KeyError: - pass - - if manager is not None: - # no manager detected, don't display continue button - manager_detected = True - - if request.method == 'GET': - return render(request, 'dashboard/landing.html', {'manager': manager_detected, 'title': "Welcome to the Lab as a Service Dashboard"}) - - if request.method == 'POST': - try: - create = request.POST['create'] - - if manager is not None: - del manager - - mgr_uuid = create_session(create, request=request,) - request.session['manager_session'] = mgr_uuid - return HttpResponseRedirect('/wf/') - - except KeyError: - pass + manager = ManagerTracker.managers.get(request.session.get('manager_session')) + return render( + request, + 'dashboard/landing.html', + { + 'manager': manager is not None, + 'title': "Welcome to the Lab as a Service Dashboard" + } + ) class LandingView(TemplateView): diff --git a/src/static/css/base.css b/src/static/css/base.css index 1494e77..9fec97e 100644 --- a/src/static/css/base.css +++ b/src/static/css/base.css @@ -66,9 +66,6 @@ a[aria-expanded="true"] > i.rotate { 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_wrapper > .row > div:first-child { - border-right: 1px solid gray; -} /* Cursor effects */ .not-allowed { @@ -81,14 +78,18 @@ a[aria-expanded="true"] > i.rotate { top: 0; } -/* Dropdown for collaborators */ -#drop_results { - max-height: 10rem; +.z-2 { z-index: 2; } -#drop_results > li { - word-wrap: anywhere; +.mh-30vh { + max-height: 30vh; +} + +.overflow-ellipsis { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } /* Graphing for networks */ diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index f1eff77..b74ead9 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -4,14 +4,107 @@ form_submission_callbacks = []; //all runnables will be executed before form submission - /////////////////// // Global Functions /////////////////// +function update_page(response) { + if( response.redirect ) + { + window.location.replace(response.redirect); + return; + } + draw_breadcrumbs(response.meta); + update_exit_button(response.meta); + update_side_buttons(response.meta); + $("#formContainer").html(response.content); +} + +function update_side_buttons(meta) { + const step = meta.active; + const page_count = meta.steps.length; + + const back_button = document.getElementById("gob"); + if (step == 0) { + back_button.classList.add("disabled"); + back_button.disabled = true; + } else { + back_button.classList.remove("disabled"); + back_button.disabled = false; + } + + const forward_btn = document.getElementById("gof"); + if (step == page_count - 1) { + forward_btn.classList.add("disabled"); + forward_btn.disabled = true; + } else { + forward_btn.classList.remove("disabled"); + forward_btn.disabled = false; + } +} + +function update_exit_button(meta) { + if (meta.workflow_count == 1) { + document.getElementById("cancel_btn").innerText = "Exit Workflow"; + } else { + document.getElementById("cancel_btn").innerText = "Return to Parent"; + } +} + +function draw_breadcrumbs(meta) { + $("#topPagination").children().not(".page-control").remove(); + + for (const i in meta.steps) { + const step_btn = create_step(meta.steps[i], i == meta["active"]); + $("#topPagination li:last-child").before(step_btn); + } +} + +function create_step(step_json, active) { + const step_dom = document.createElement("li"); + // First create the dom object depending on active or not + step_dom.className = "topcrumb"; + if (active) { + step_dom.classList.add("active"); + } + $(step_dom).html(`<span class="d-flex align-items-center justify-content-center text-capitalize w-100">${step_json['title']}</span>`) + + const code = step_json.valid; + + let stat = ""; + let msg = ""; + if (code < 100) { + $(step_dom).children().first().append("<i class='ml-2 far fa-square'></i>") + stat = ""; + msg = ""; + } else if (code < 200) { + $(step_dom).children().first().append("<i class='ml-2 fas fa-minus-square'></i>") + stat = "invalid"; + msg = step_json.message; + } else if (code < 300) { + $(step_dom).children().first().append("<i class='ml-2 far fa-check-square'></i>") + stat = "valid"; + msg = step_json.message; + } + + if (step_json.enabled == false) { + step_dom.classList.add("disabled"); + } + if (active) { + update_message(msg, stat); + } -function updatePage(data){ - updateBreadcrumbs(data['meta']); - $("formContainer").html(data['content']); + return step_dom; +} + +function update_description(title, desc) { + document.getElementById("view_title").innerText = title; + document.getElementById("view_desc").innerText = desc; +} + +function update_message(message, stepstatus) { + document.getElementById("view_message").innerText = message; + document.getElementById("view_message").className = "step_message"; + document.getElementById("view_message").classList.add("message_" + stepstatus); } function submitStepForm(next_step = "current"){ @@ -22,11 +115,10 @@ function submitStepForm(next_step = "current"){ "step_form": step_form_data, "csrfmiddlewaretoken": $("[name=csrfmiddlewaretoken]").val() }); - console.log(form_data); $.post( '/workflow/manager/', form_data, - (data) => updatePage(data), + (data) => update_page(data), 'json' ).fail(() => alert("failure")); } @@ -37,6 +129,58 @@ function run_form_callbacks(){ form_submission_callbacks = []; } +function create_workflow(type) { + $.ajax({ + type: "POST", + url: "/workflow/create/", + data: { + "workflow_type": type + }, + headers: { + "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val() + } + }).done(function (data, textStatus, jqXHR) { + window.location = "/workflow/"; + }).fail(function (jqxHR, textstatus) { + alert("Something went wrong..."); + }); +} + +function add_workflow(type) { + data = $.ajax({ + type: "POST", + url: "/workflow/add/", + data: { + "workflow_type": type + }, + headers: { + "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val() + } + }).done(function (data, textStatus, jqXHR) { + update_page(data); + }).fail(function (jqxHR, textstatus) { + alert("Something went wrong..."); + }); +} + +function pop_workflow() { + data = $.ajax({ + type: "POST", + url: "/workflow/pop/", + headers: { + "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val() + } + }).done(function (data, textStatus, jqXHR) { + update_page(data); + }).fail(function (jqxHR, textstatus) { + alert("Something went wrong..."); + }); +} + +function continue_workflow() { + window.location.replace("/workflow/"); +} + /////////////////// //Class Definitions /////////////////// @@ -1092,12 +1236,13 @@ 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_entry.classList.add("list-group-item", "list-group-item-action"); + 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); @@ -1156,21 +1301,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="border rounded mt-2 w-100 d-flex align-items-center pl-2">' - + element_entry_text - + '<button onclick="searchable_select_multiple_widget.remove_item(' - + item_id - + ')" class="btn btn-danger ml-auto">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/base.html b/src/templates/base.html index 891e0fc..f59740d 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -6,30 +6,7 @@ <!-- Custom CSS --> <link href="{% static "css/base.css" %}" rel="stylesheet"> -<script type="text/javascript"> - function cwf(type) { - $.ajax({ - type: "POST", - url: "/", - data: { - "create": type - }, - beforeSend: function (request) { - request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val() - ); - } - }).done(function (data) { - window.location.replace("/wf/"); - }).fail(function (jqxHR, textstatus) { - alert("Something went wrong..."); - }); - } - - function continue_wf() { - window.location.replace("/wf/"); - } -</script> +<script src="/static/js/dashboard.js"></script> {% endblock %} {% block basecontent %} @@ -107,19 +84,19 @@ <a href="/booking/quick/" class="list-group-item list-group-item-action list-group-item-secondary"> Express Booking </a> - <a href="#" onclick="cwf(0)" class="list-group-item list-group-item-action list-group-item-secondary"> + <a href="#" onclick="create_workflow(0)" class="list-group-item list-group-item-action list-group-item-secondary"> Book a Pod </a> - <a href="#" onclick="cwf(1)" class="list-group-item list-group-item-action list-group-item-secondary"> + <a href="#" onclick="create_workflow(1)" class="list-group-item list-group-item-action list-group-item-secondary"> Design a Pod </a> - <a href="#" onclick="cwf(2)" class="list-group-item list-group-item-action list-group-item-secondary"> + <a href="#" onclick="create_workflow(2)" class="list-group-item list-group-item-action list-group-item-secondary"> Configure a Pod </a> - <a href="#" onclick="cwf(3)" class="list-group-item list-group-item-action list-group-item-secondary"> + <a href="#" onclick="create_workflow(3)" class="list-group-item list-group-item-action list-group-item-secondary"> Create a Snapshot </a> - <a href="#" onclick="cwf(4)" class="list-group-item list-group-item-action list-group-item-secondary"> + <a href="#" onclick="create_workflow(4)" class="list-group-item list-group-item-action list-group-item-secondary"> Configure OPNFV </a> </div> diff --git a/src/templates/booking/quick_deploy.html b/src/templates/booking/quick_deploy.html index 50ec59a..6776980 100644 --- a/src/templates/booking/quick_deploy.html +++ b/src/templates/booking/quick_deploy.html @@ -7,13 +7,13 @@ <form id="quick_booking_form" action="/booking/quick/" method="POST" class="form"> {% csrf_token %} <div class="container-fluid"> - <div class="row"> - <div class="col-12 px-1 my-2"> - <div class="col py-2 rounded border"> - <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="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 %} @@ -80,11 +80,8 @@ 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"); @@ -92,21 +89,18 @@ 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) { - childNode.style.display = "inherit"; - childNode.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); + element.addEventListener('click', imageFilter); }); function installerHider() { diff --git a/src/templates/dashboard/genericselect.html b/src/templates/dashboard/genericselect.html index f54cd90..863d33f 100644 --- a/src/templates/dashboard/genericselect.html +++ b/src/templates/dashboard/genericselect.html @@ -1,27 +1,22 @@ {% extends "workflow/viewport-element.html" %} -{% load staticfiles %} {% load bootstrap4 %} {% block content %} -<div id="{{select_type}}_form_div" class="h-100 border d-flex flex-column p-4"> +<div id="select_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}})" + {% if not disabled %}onclick="add_workflow({{addable_type_num}})" {% endif %}>Here </button> </h3> <div class="border-top"></div> <h3 id="select_header_section">Or select from the list below:</h3> <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"> + <form id="step_form" method="post" action="" class="form d-flex flex-column"> {% csrf_token %} {{ form|default:"<p>no form loaded</p>" }} - {% buttons %} - - {% endbuttons %} </form> </div> </div> @@ -33,13 +28,3 @@ </script> {% endblock content %} -{% block onleave %} -var form = $("#{{select_type}}_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/dashboard/landing.html b/src/templates/dashboard/landing.html index d4776cc..72f9e6e 100644 --- a/src/templates/dashboard/landing.html +++ b/src/templates/dashboard/landing.html @@ -47,13 +47,13 @@ </p> <div class="row"> <div class="col-12 col-xl-4"> - <button class="btn btn-primary w-100" onclick="cwf(0)">Book a Pod</button> + <button class="btn btn-primary w-100" onclick="create_workflow(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> + <button class="btn btn-primary w-100" onclick="create_workflow(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> + <button class="btn btn-primary w-100" onclick="create_workflow(2)">Configure a Pod</button> </div> </div> {% endif %} @@ -65,7 +65,7 @@ <p>If you're a returning user, some of the following options may be of interest:</p> <div class="row"> <div class="col-12 col-xl-4"> - <button class="btn btn-primary w-100" onclick="cwf(3)">Snapshot a Host</button> + <button class="btn btn-primary w-100" onclick="create_workflow(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' %}"> @@ -74,7 +74,7 @@ </div> {% if manager == True %} <div class="col-12 col-xl-4"> - <button class="btn btn-primary w-100" onclick="continue_wf()"> + <button class="btn btn-primary w-100" onclick="continue_workflow()"> Resume Workflow </button> </div> @@ -85,44 +85,10 @@ </div> </div> -<script type="text/javascript"> - function cwf(type) { - $.ajax({ - type: "POST", - url: "/", - data: { - "create": type - }, - beforeSend: function (request) { - request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val() - ); - } - - }).done(function (data) { - window.location.replace("/wf/"); - }).fail(function (jqxHR, textstatus) { - alert("Something went wrong..."); - }); - } - - function continue_wf() { - window.location.replace("/wf/"); - } -</script> - <div class="hidden_form d-none" id="form_div"> <form method="post" action="" class="form" id="wf_selection_form"> {% csrf_token %} - - <input type="hidden" id="landing_action"> - - <button type="submit" class="btn btn btn-success"> - Confirm Edit - </button> </form> </div> -{% block vport_comm %} -{% endblock %} -{% endblock content %}
\ No newline at end of file +{% endblock content %} diff --git a/src/templates/dashboard/multiple_select_filter_widget.html b/src/templates/dashboard/multiple_select_filter_widget.html index 4a65bd9..ad58ccb 100644 --- a/src/templates/dashboard/multiple_select_filter_widget.html +++ b/src/templates/dashboard/multiple_select_filter_widget.html @@ -1,18 +1,15 @@ -<script src="/static/js/dashboard.js"> -</script> - <input name="filter_field" id="filter_field" type="hidden"/> -<div id="grid_wrapper" class="container-fluid p-4"> - <div class="row"> - {% for object_class, object_list in display_objects %} - <div class="col-12 col-lg d-flex flex-column pt-2 my-2"> +<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 h-100"> + <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"> - <div id="{{ obj.id|default:'not_provided' }}" class="card h-100" onclick="multi_filter_widget.processClick('{{obj.id}}');"> + <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> @@ -20,15 +17,22 @@ <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">{% if obj.multiple %}Add{% else %}Select{% endif %}</button> + <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 %} </div> <div id="dropdown_wrapper" class="px-3 list-group-flush w-25 mt-2"> diff --git a/src/templates/dashboard/searchable_select_multiple.html b/src/templates/dashboard/searchable_select_multiple.html index 8299a55..be51989 100644 --- a/src/templates/dashboard/searchable_select_multiple.html +++ b/src/templates/dashboard/searchable_select_multiple.html @@ -1,5 +1,3 @@ -<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="d-flex flex-column"> {% if incompatible == "true" %} <div class="alert alert-danger" role="alert"> @@ -27,7 +25,7 @@ </input> <div id="scroll_restrictor" class="d-flex pb-4 position-relative"> - <ul id="drop_results" class="list-group w-100 overflow-auto position-absolute"></ul> + <div id="drop_results" class="list-group w-100 z-2 overflow-auto position-absolute mh-30vh"></div> </div> </div> diff --git a/src/templates/resource/steps/define_hardware.html b/src/templates/resource/steps/define_hardware.html index 33a7d8e..efd8a09 100644 --- a/src/templates/resource/steps/define_hardware.html +++ b/src/templates/resource/steps/define_hardware.html @@ -7,9 +7,10 @@ <p>Note that not all labs host every kind of machine. 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> +<h4>NOTE: Only PTL's are able to create multi-node PODs. See + <a href="https://wiki.opnfv.org/display/INF/Lab-as-a-Service+at+the+UNH-IOL">here</a> for more details</h4> -<form id="step_form" method="post"> +<form id="step_form" method="post" class="px-3"> {% csrf_token %} {{form.filter_field|default:"<p>No Form</p>"}} </form> diff --git a/src/templates/resource/steps/pod_definition.html b/src/templates/resource/steps/pod_definition.html index 4392dbd..d0a28ed 100644 --- a/src/templates/resource/steps/pod_definition.html +++ b/src/templates/resource/steps/pod_definition.html @@ -7,8 +7,6 @@ <script> var mxLoadStylesheets = false; </script> -<script type="text/javascript" src="/static/js/mxClient.min.js" ></script> -<script type="text/javascript" src="/static/js/dashboard.js" ></script> {% endblock extrahead %} <!-- Calls the main function after the page has loaded. Container is dynamically created. --> @@ -41,7 +39,6 @@ {% 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; diff --git a/src/templates/workflow/confirm.html b/src/templates/workflow/confirm.html index b7e6c46..2f99a41 100644 --- a/src/templates/workflow/confirm.html +++ b/src/templates/workflow/confirm.html @@ -8,11 +8,6 @@ <div class="text-center"> <h3>Confirm Session</h3> </div> -<div id="vlan_warning"></div> -<form id="vlan_form" action="/wf/workflow/" method="post"> - {% csrf_token %} - <input id="vlan_input" name="vlan_input" type="hidden"/> -</form> <div class="container"> <div class="row justify-content-center"> <div class="col-auto"> @@ -22,7 +17,7 @@ <div class="row"> <div class="col"> <div id="form_div" class="text-center p-4"> - <form id="confirmation_form" action="/wf/workflow/" method="post"> + <form id="step_form" action="/workflow/manager/" method="post"> {% csrf_token %} <div class="d-none"> {{form|default:"<p> No Form Loaded</p>"}} @@ -33,7 +28,7 @@ <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"> + <form id="manager_delete_form" action="/workflow/finish/" method="post"> {% csrf_token %} </form> </div> @@ -44,104 +39,24 @@ <script> var select = document.getElementById("id_confirm"); - function processResponseText(json) - { - var dict = JSON.parse(json); - - if( !dict["redir_url"] ) { - window.top.refresh_iframe(); - } else { - top.window.location.href = dict["redir_url"]; - } - } - - function delete_manager() - { - var form = $("#manager_delete_form"); - var formData = form.serialize(); - var req = new XMLHttpRequest(); - req.open("POST", "/wf/workflow/finish/", false); - req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - req.onerror = function() { alert("problem with cleaning up session"); } - req.onreadystatechange = function() { if(req.readyState === 4 ) { - processResponseText(req.responseText); - }} - req.send(formData); - } - - function submitForm() - { - var form = $("#confirmation_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 confirmation"); } - req.onreadystatechange = function() { if(req.readyState === 4 ) { delete_manager(); } } - req.send(formData); - } - - function formconfirm() { select.value = "True"; - submitForm(); + submitStepForm(); } function formcancel() { select.value = "False"; - submitForm(); + submitStepForm(); } - var confirmed = {{bypassed|default:"false"}}; + var confirmed = {{confirm_succeeded|default:"false"}}; if( confirmed ) { - delete_manager(); - } -</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; - } - } - 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; + pop_workflow(); } </script> {% block element_messages %} {% endblock element_messages %} {% endblock content %} -{% block onleave %} -{% endblock %} diff --git a/src/templates/workflow/viewport-base.html b/src/templates/workflow/viewport-base.html index 103a095..02c5597 100644 --- a/src/templates/workflow/viewport-base.html +++ b/src/templates/workflow/viewport-base.html @@ -15,29 +15,9 @@ <i class="fas fa-backward"></i> Back </a> </li> - <li class="page-item flex-grow-1 active"> - <a class="page-link disabled" href="#"> - Select <i class="far fa-check-square"></i> - </a> - </li> - <li class="page-item flex-grow-1"> - <a class="page-link disabled" href="#"> - Configure <i class="far fa-square"></i> - </a> - </li> - <li class="page-item flex-grow-1"> - <a class="page-link disabled" href="#"> - Information <i class="far fa-square"></i> - </a> - </li> - <li class="page-item flex-grow-1"> - <a class="page-link disabled" href="#"> - OPNFV <i class="far fa-square"></i> - </a> - </li> <li class="page-item flex-grow-1"> <a class="page-link disabled" href="#"> - Confirm <i class="far fa-square"></i> + <i class="far"></i> </a> </li> <li class="page-item flex-shrink-1 page-control"> @@ -58,22 +38,10 @@ <span class="description text-muted" id="view_desc"></span> <p id="view_message"></p> </div> - <script> - function update_description(title, desc) { - document.getElementById("view_title").innerText = title; - document.getElementById("view_desc").innerText = desc; - } - - function update_message(message, stepstatus) { - document.getElementById("view_message").innerText = message; - document.getElementById("view_message").className = "step_message"; - document.getElementById("view_message").classList.add("message_" + stepstatus); - } - </script> </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> + <button id="cancel_btn" class="btn btn-danger ml-auto" onclick="pop_workflow()">Cancel</button> </div> </div> <div class="row d-flex flex-column flex-grow-1"> @@ -86,240 +54,24 @@ </div> </div> {% csrf_token %} -<script src="{% static "js/dashboard.js" %}"></script> <script type="text/javascript"> - update_context(); - var step = 0; - var page_count = 0; - function submit_and_go(to) { submitStepForm(to); } - function request_leave(to) { - $.ajax({ - type: "GET", - url: "/wf/manager/", - beforeSend: function (request) { - request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val()); - }, - success: function (data) { - confirm_permission(to, data); - update_page(data); - } - }); - } - - function confirm_permission(to, data) { - if (errors_exist(data)) { - if (to != "prev") { - return; - } - } - - var problem = function () { - alert("There was a problem"); - } - //makes an asynch request - req = new XMLHttpRequest(); - url = "/wf/workflow/?step=" + to; - req.open("GET", url, true); - req.onload = function (e) { - if (req.readyState === 4) { - if (req.status < 300) { - write_iframe(this.responseText); - } else { - problem(); - } - } else { - problem(); - } - } - req.onerror = problem; - req.send(); - } - - function errors_exist(data) { - var stat = data['steps'][data['active']]['valid']; - if (stat >= 100 && stat < 200) { - return true; - } else { - return false; - } - } - - function update_context() { - $.ajax({ - type: "GET", - url: "/wf/manager/", - beforeSend: function (request) { - request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val()); - }, - success: function (data) { - update_page(data); - } - }); - } - - function updateBreadcrumbs(data) { - update_breadcrumbs(data); - if (data["workflow_count"] == 1) { - document.getElementById("cancel_btn").innerText = "Exit Workflow"; - } else { - document.getElementById("cancel_btn").innerText = "Return to Parent"; - } - } - - function update_breadcrumbs(meta_json) { - step = meta_json['active']; - page_count = meta_json['steps'].length; - if (step == 0) { - var btn = document.getElementById("gob"); - btn.classList.add("invisible"); - btn.disabled = true; - } else { - var btn = document.getElementById("gob"); - btn.classList.remove("invisible"); - btn.disabled = false; - } - if (step == page_count - 1) { - var btn = document.getElementById("gof"); - btn.classList.add("invisible"); - btn.disabled = true; - } else { - var btn = document.getElementById("gof"); - btn.classList.remove("invisible"); - btn.disabled = false; - } - //remove all children of breadcrumbs so we can redraw - $("#topPagination").children().not(".page-control").remove(); - draw_steps(meta_json); - } - - function draw_steps(meta_json) { - for (var i = 0; i < meta_json["steps"].length; i++) { - meta_json["steps"][i]["index"] = i; - var step_btn = create_step(meta_json["steps"][i], i == meta_json["active"]); - $("#topPagination li:last-child").before(step_btn); - } - } - - function create_step(step_json, active) { - var step_dom = document.createElement("li"); - // First create the dom object depending on active or not - if (active) { - step_dom.className = "topcrumb active"; - } else { - step_dom.className = "topcrumb"; - } - $(step_dom).html(`<span class="d-flex align-items-center justify-content-center text-capitalize w-100">${step_json['title']}</span>`) - var code = step_json['valid']; - stat = ""; - msg = ""; - if (code < 100) { - $(step_dom).children().first().append("<i class='ml-2 far fa-square'></i>") - stat = ""; - msg = ""; - } else if (code < 200) { - $(step_dom).children().first().append("<i class='ml-2 fas fa-minus-square'></i>") - stat = "invalid"; - msg = step_json['message']; - } else if (code < 300) { - $(step_dom).children().first().append("<i class='ml-2 far fa-check-square'></i>") - stat = "valid"; - msg = step_json['message']; - } - if (step_json['enabled'] == false) { - step_dom.classList.add("disabled"); - } - if (active) { - update_message(msg, stat); - } - - var step_number = step_json['index']; - return step_dom; - } - - function cancel_wf() { - var form = $("#workflow_pop_form"); - var formData = form.serialize(); - var req = new XMLHttpRequest(); - req.open("POST", "/wf/workflow/finish/", false); - req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - req.onerror = function () { - alert("problem occurred while trying to cancel current workflow"); - } - req.onreadystatechange = function () { - if (req.readyState === 4) { - refresh_iframe(); - } - }; - req.send(formData); - } - - function refresh_iframe() { - req = new XMLHttpRequest(); - url = "/wf/workflow/"; - req.open("GET", url, true); - req.onload = function (e) { - write_iframe(this.responseText); - } - req.send(); - } - - function write_iframe(contents) { - $("#formContainer").html(contents); - } - - function redirect_root() { - window.location.replace('/wf/'); - } - - function add_wf(type) { - add_wf_internal(type, false); - } - - function add_edit_wf(type, target) { - add_wf_internal(type, target); - } - - function add_wf_internal(type, itemid) { - data = { - "add": type - }; - if (itemid) { - data['target'] = itemid; - } - $.ajax({ - type: "POST", - url: "/wf/manager/", - data: data, - beforeSend: function (request) { - request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val() - ); - }, - success: refresh_wf_iframe() - }); - } - - 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); - } + $.ajax({ + url: "/workflow/manager/", + dataType: "json", + success: update_page }); }); </script> +<!-- lazy load scripts --> +<script type="text/javascript" src="/static/js/mxClient.min.js" ></script> +<!-- end lazy load scripts --> <div class="d-none" id="workflow_pop_form_div"> - <form id="workflow_pop_form" action="/wf/workflow/finish/" method="post"> + <form id="workflow_pop_form" action="/workflow/finish/" method="post"> {% csrf_token %} </form> </div> diff --git a/src/templates/workflow/viewport-element.html b/src/templates/workflow/viewport-element.html index bf13304..d16c924 100644 --- a/src/templates/workflow/viewport-element.html +++ b/src/templates/workflow/viewport-element.html @@ -5,42 +5,6 @@ {% block content %} {% endblock content %} - {% block vport_comm %} - <script type="text/javascript"> - var step_count = {{ step_number|default:0 }}; - var active_step = {{ active_step|default:0 }}; - var render_correct = {{ render_correct|default:"false" }}; - var title = "{{ step_title|default:"Workflow Step" }}"; - var description = "{{ description|default:"Contact the admins, because this field should have something else filled in here" }}"; - if(render_correct){ - parent.update_context(); - } - parent.update_description(title, description); - </script> - - {% endblock vport_comm %} - {% block validate_step %} - <script> - - function step_is_valid() - { - valid = confirm("Is this form valid?"); - if( valid ) - { - return true; - } - else{ - return false; - } - } - - function onError() - { - alert("Error: something!"); - } - </script> - - {% endblock validate_step %} <div class="messages"> {% block element_messages %} diff --git a/src/workflow/models.py b/src/workflow/models.py index 866f442..0521165 100644 --- a/src/workflow/models.py +++ b/src/workflow/models.py @@ -318,6 +318,9 @@ class Confirmation_Step(WorkflowStep): default_flow_style=False ).strip() + if self.valid == WorkflowStepStatus.VALID: + context["confirm_succeeded"] = "true" + return context def flush_to_db(self): @@ -329,9 +332,7 @@ class Confirmation_Step(WorkflowStep): form = ConfirmationForm(post_data) if form.is_valid(): data = form.cleaned_data['confirm'] - context = self.get_context() if data == "True": - context["bypassed"] = "true" errors = self.flush_to_db() if errors: self.set_invalid("ERROR OCCURRED: " + errors) @@ -339,7 +340,6 @@ class Confirmation_Step(WorkflowStep): self.set_valid("Confirmed") elif data == "False": - context["bypassed"] = "true" self.set_valid("Canceled") else: self.set_invalid("Bad Form Contents") diff --git a/src/workflow/urls.py b/src/workflow/urls.py index ae620d0..298db95 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 delete_session, manager_view, viewport_view, add_workflow, cancel_workflow +from workflow.views import manager_view, viewport_view, add_workflow, remove_workflow, create_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,10 +19,10 @@ from workflow.booking_workflow import SWConfig_Select, Booking_Resource_Select, app_name = 'workflow' urlpatterns = [ - 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'^create/$', create_workflow, name='create_workflow'), + url(r'^pop/$', remove_workflow, name='remove_workflow'), url(r'^$', viewport_view, name='viewport') ] diff --git a/src/workflow/views.py b/src/workflow/views.py index 47241e2..3ab4d30 100644 --- a/src/workflow/views.py +++ b/src/workflow/views.py @@ -8,9 +8,8 @@ ############################################################################## -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse, JsonResponse, HttpResponseRedirect from django.shortcuts import render -from django.urls import reverse import uuid @@ -31,33 +30,17 @@ def attempt_auth(request): return None -def get_redirect_response(result): - if not result: - return {} - - # need to get type of result, and switch on the type - # since has_result, result must be populated with a valid object - if isinstance(result, Booking): - return { - 'redir_url': reverse('booking:booking_detail', kwargs={'booking_id': result.id}) - } - else: - return {} - - -def delete_session(request): +def remove_workflow(request): manager = attempt_auth(request) if not manager: return no_workflow(request) - not_last_workflow, result = manager.pop_workflow() + has_more_workflows, result = manager.pop_workflow() - if not_last_workflow: # this was not the last workflow, so don't redirect away - return JsonResponse({}) - else: + if not has_more_workflows: # this was the last workflow, so delete the reference to it in the tracker del ManagerTracker.managers[request.session['manager_session']] - return JsonResponse(get_redirect_response(result)) + return manager.render(request) def add_workflow(request): @@ -73,15 +56,6 @@ def add_workflow(request): return manager.render(request) # do we want this? -def cancel_workflow(request): - manager = attempt_auth(request) - if not manager: - return no_workflow(request) - - if not manager.pop_workflow(): - del ManagerTracker.managers[request.session['manager_session']] - - def manager_view(request): manager = attempt_auth(request) if not manager: @@ -98,16 +72,27 @@ def viewport_view(request): if manager is None: return no_workflow(request) - if request.method == 'GET': - return render(request, 'workflow/viewport-base.html') - else: - pass + if request.method != 'GET': + return HttpResponse(status=405) + return render(request, 'workflow/viewport-base.html') + + +def create_workflow(request): + if request.method != 'POST': + return HttpResponse(status=405) + workflow_type = request.POST.get('workflow_type') + try: + workflow_type = int(workflow_type) + except Exception: + return HttpResponse(status=400) + mgr_uuid = create_session(workflow_type, request=request,) + request.session['manager_session'] = mgr_uuid + return HttpResponse() def create_session(wf_type, request): - wf = int(wf_type) smgr = SessionManager(request=request) - smgr.add_workflow(workflow_type=wf, target_id=request.POST.get("target")) + smgr.add_workflow(workflow_type=wf_type, target_id=request.POST.get("target")) manager_uuid = uuid.uuid4().hex ManagerTracker.getInstance().managers[manager_uuid] = smgr diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py index 605eee7..4677829 100644 --- a/src/workflow/workflow_manager.py +++ b/src/workflow/workflow_manager.py @@ -10,6 +10,7 @@ from django.http import JsonResponse from django.http.request import QueryDict +from django.urls import reverse from booking.models import Booking from workflow.workflow_factory import WorkflowFactory @@ -32,10 +33,9 @@ class SessionManager(): def __init__(self, request=None): self.workflows = [] - self.owner = request.user - self.factory = WorkflowFactory() + self.result = None def set_step_statuses(self, superclass_type, desired_enabled=True): workflow = self.active_workflow() @@ -62,6 +62,11 @@ class SessionManager(): ) ) + def get_redirect(self): + if isinstance(self.result, Booking): + return reverse('booking:booking_detail', kwargs={'booking_id': self.result.id}) + return "/" + def pop_workflow(self): multiple_wfs = len(self.workflows) > 1 if multiple_wfs: @@ -69,9 +74,13 @@ class SessionManager(): key = self.workflows[-1].repository.el[Repository.RESULT_KEY] result = self.workflows[-1].repository.el[Repository.RESULT] self.workflows[-2].repository.el[key] = result - self.workflows.pop() - current_repo = self.workflows[-1].repository - return (multiple_wfs, current_repo.el[current_repo.RESULT]) + prev_workflow = self.workflows.pop() + if self.workflows: + current_repo = self.workflows[-1].repository + else: + current_repo = prev_workflow.repository + self.result = current_repo.el[current_repo.RESULT] + return multiple_wfs, self.result def status(self, request): return { @@ -82,7 +91,7 @@ class SessionManager(): def handle_post(self, request): form = ManagerForm(request.POST) - if form.is_valid: + if form.is_valid(): self.get_active_step().post( QueryDict(form.cleaned_data['step_form']), user=request.user @@ -98,13 +107,18 @@ class SessionManager(): def handle_request(self, request): if request.method == 'POST': self.handle_post(request) - return self.render() + return self.render(request) def render(self, request, **kwargs): - return JsonResponse({ - "meta": self.status(), - "content": self.get_active_step().render_to_string(request) - }) + if self.workflows: + return JsonResponse({ + "meta": self.status(request), + "content": self.get_active_step().render_to_string(request), + }) + else: + return JsonResponse({ + "redirect": self.get_redirect() + }) def post_render(self, request): return self.active_workflow().steps[self.active_workflow().active_index].post_render(request) |