diff options
19 files changed, 353 insertions, 303 deletions
diff --git a/dashboard/src/static/css/detail_view.css b/dashboard/src/static/css/detail_view.css index b0e271d..89d0867 100644 --- a/dashboard/src/static/css/detail_view.css +++ b/dashboard/src/static/css/detail_view.css @@ -3,6 +3,7 @@ border-color: black; border-radius: 5px; margin: 5px; + padding: 5px; box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75); } diff --git a/dashboard/src/static/css/graph_common.css b/dashboard/src/static/css/graph_common.css index 7f90a66..cff1516 100644 --- a/dashboard/src/static/css/graph_common.css +++ b/dashboard/src/static/css/graph_common.css @@ -26,9 +26,6 @@ div.mxRubberband { margin: 0px; } div.mxWindow { - -webkit-box-shadow: 3px 3px 12px #C0C0C0; - -moz-box-shadow: 3px 3px 12px #C0C0C0; - box-shadow: 3px 3px 12px #C0C0C0; background: url('../img/mxgraph/window.gif'); border:1px solid #c3c3c3; position: absolute; @@ -67,19 +64,32 @@ td.mxWindowPane td { font-size: 8pt; } td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio { - border-color: #8C8C8C; - border-style: solid; - border-width: 1px; font-family: Arial; font-size: 8pt; padding: 1px; } td.mxWindowPane button { - background: url('/static/img/mxgraph/button.gif') repeat-x; - font-family: Arial; - font-size: 8pt; - padding: 2px; - float: left; + 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; diff --git a/dashboard/src/templates/account/booking_list.html b/dashboard/src/templates/account/booking_list.html index 4cd1408..21f6ff7 100644 --- a/dashboard/src/templates/account/booking_list.html +++ b/dashboard/src/templates/account/booking_list.html @@ -27,7 +27,7 @@ function edit_booking(pk){ <li>purpose: {{booking.purpose}}</li> </ul> <div class="detail_btn_group"> - <button class="btn" onclick="edit_booking({{booking.id}});">Edit</button> + <button style="display: none" class="btn" onclick="edit_booking({{booking.id}});">Edit</button> <button class="btn" onclick="location.href='/booking/detail/{{booking.id}}/';">Details</button> </div> </div> @@ -44,7 +44,7 @@ function edit_booking(pk){ <li>purpose: {{booking.purpose}}</li> </ul> <div class="detail_btn_group"> - <button class="btn" disabled=true onclick="edit_booking({{booking.id}});">Edit</button> + <button style="display: none" class="btn" disabled=true onclick="edit_booking({{booking.id}});">Edit</button> <button class="btn" onclick="location.href='/booking/detail/{{booking.id}}/';">Details</button> </div> </div> diff --git a/dashboard/src/templates/account/configuration_list.html b/dashboard/src/templates/account/configuration_list.html index f4b174b..b04535b 100644 --- a/dashboard/src/templates/account/configuration_list.html +++ b/dashboard/src/templates/account/configuration_list.html @@ -21,9 +21,10 @@ function edit_configuration(pk){ <li>id: {{config.id}}</li> <li>name: {{config.name}}</li> <li>description: {{config.description}}</li> + <li>resource: {{config.bundle}}</li> </ul> <div class="detail_btn_group"> - <button class="btn" onclick="edit_configuration({{config.id}});">Edit</button> + <button style="display: none" class="btn" onclick="edit_configuration({{config.id}});">Edit</button> </div> </div> {% endfor %} diff --git a/dashboard/src/templates/account/resource_list.html b/dashboard/src/templates/account/resource_list.html index e70cca7..2ef293b 100644 --- a/dashboard/src/templates/account/resource_list.html +++ b/dashboard/src/templates/account/resource_list.html @@ -23,7 +23,7 @@ function edit_resource(pk){ <li>description: {{resource.description}}</li> </ul> <div class="detail_btn_group"> - <button class="btn" onclick="edit_resource({{resource.id}});">Edit</button> + <button style="display: none" class="btn" onclick="edit_resource({{resource.id}});">Edit</button> </div> </div> {% endfor %} diff --git a/dashboard/src/templates/booking/steps/booking_meta.html b/dashboard/src/templates/booking/steps/booking_meta.html index a42e158..e4881ae 100644 --- a/dashboard/src/templates/booking/steps/booking_meta.html +++ b/dashboard/src/templates/booking/steps/booking_meta.html @@ -58,7 +58,6 @@ {% block onleave %} var ajaxForm = $("#booking_meta_form"); var formData = ajaxForm.serialize(); -console.log(formData); req = new XMLHttpRequest(); req.open("POST", "/wf/workflow/", false); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); diff --git a/dashboard/src/templates/dashboard/searchable_select_multiple.html b/dashboard/src/templates/dashboard/searchable_select_multiple.html index e7128b0..ee460dd 100644 --- a/dashboard/src/templates/dashboard/searchable_select_multiple.html +++ b/dashboard/src/templates/dashboard/searchable_select_multiple.html @@ -1,6 +1,12 @@ <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <div class="autocomplete" style="width:400px;"> + <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> <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="{{initial.name}}" oninput="search(this.value)" {% if disabled %} disabled {% endif %} > diff --git a/dashboard/src/templates/resource/steps/pod_definition.html b/dashboard/src/templates/resource/steps/pod_definition.html index b2b4998..a72e5a5 100644 --- a/dashboard/src/templates/resource/steps/pod_definition.html +++ b/dashboard/src/templates/resource/steps/pod_definition.html @@ -141,6 +141,14 @@ function main(graphContainer, overviewContainer, toolbarContainer) { } }); + createDeleteDialog = function(id) + { + var content = document.createElement('div'); + var innerHTML = "<button style='width: 46%;' onclick=deleteCell('" + id + "');>Remove</button>" + innerHTML += "<button style='width: 46%;' onclick='currentWindow.destroy();'>Cancel</button>" + content.innerHTML = innerHTML; + showWindow(currentGraph, 'Do you want to delete this network?', content, 200, 62); + } graph.dblClick = function(evt, cell) { if( cell != null ){ @@ -148,11 +156,7 @@ function main(graphContainer, overviewContainer, toolbarContainer) { cell = cell.getParent(); } if( cell.isEdge() || cell.getId().indexOf("network") > -1 ) { - var content = document.createElement('div'); - var innerHTML = "<button onclick=deleteCell('" + cell.getId() + "');>Remove</button>" - innerHTML += "<button onclick='currentWindow.destroy();'>Cancel</button>" - content.innerHTML = innerHTML; - showWindow(this, 'Delete?', content, 200, 200); + createDeleteDialog(cell.getId()); } else { showDetailWindow(cell); @@ -226,8 +230,8 @@ function deleteCell(cellId) { function newNetworkWindow() { var innerHtml = 'Name: <input type="text" name="net_name" id="net_name_input" style="margin:5px;"><br>'; innerHtml += 'Vlan: <input type="number" step="1" name="vlan_id" id="vlan_id_input" style="margin:5px;"><br>'; - innerHtml += '<button type="button" onclick="parseNetworkWindow()">Okay</button>'; - innerHtml += '<button type="button" onclick="currentWindow.destroy();">Cancel</button><br>'; + innerHtml += '<button style="width: 46%;" onclick="parseNetworkWindow()">Okay</button>'; + innerHtml += '<button style="width: 46%;" onclick="currentWindow.destroy();">Cancel</button><br>'; innerHtml += '<div id="current_window_vlans"/>'; innerHtml += '<div id="current_window_errors"/>'; var content = document.createElement("div"); @@ -503,6 +507,7 @@ function makeMxNetwork(vlan_id, net_name) { function addPublicNetwork() { var net = makeMxNetwork(-1, "public"); + network_names.add("public"); makeSidebarNetwork("public", "", net['color'], net['element_id']); } @@ -542,14 +547,32 @@ function updateHosts(removed) { function makeSidebarNetwork(net_name, vlan_id, color, net_id){ var newNet = document.createElement("li"); + var colorBlob = document.createElement("div"); + colorBlob.className = "colorblob"; + var textContainer = document.createElement("p"); + textContainer.className = "network_innertext"; newNet.id = net_id; + var 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;"; + deleteButtonText = document.createTextNode("X"); + deletebutton.appendChild(deleteButtonText); + deletebutton.addEventListener("click", function() { + createDeleteDialog(net_id); + }, false); var text = net_name; if(vlan_id){ text += " : " + vlan_id; } var newNetValue = document.createTextNode(text); - newNet.appendChild(newNetValue); - newNet.style['background'] = color; + textContainer.appendChild(newNetValue); + colorBlob.style['background'] = color; + newNet.appendChild(colorBlob); + newNet.appendChild(textContainer); + if( net_name != "public" ) + { + newNet.appendChild(deletebutton); + } document.getElementById("network_list").appendChild(newNet); } @@ -609,7 +632,7 @@ function submitForm() { <!-- 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:65%;left:0px;"> + 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> @@ -617,7 +640,7 @@ function submitForm() { <!-- Creates a container for the sidebar --> <div id="toolbarContainer" - style="position:absolute;white-space:nowrap;overflow:hidden;top:0px;left:0px;max-height:24px;height:36px;right:0px;padding:6px;background-image:url('/static/img/mxgraph/toolbar_bg.gif');"> + style="position:absolute;white-space:nowrap;overflow:hidden;top:0px;left:0px;right:0px;padding:6px;"> </div> <!-- Creates a container for the outline --> @@ -626,8 +649,60 @@ function submitForm() { </div> </div> - <div id="network_select" style="position:absolute;top:0px;bottom:0px;width:35%;right:0px;left:auto;background:grey"> - <button type="button" onclick="newNetworkWindow();">Add Network</button> + <style> + #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="newNetworkWindow();">Add Network</button> + </div> <ul id="network_list"> </ul> <p id="vlan_notice"></p> diff --git a/dashboard/src/templates/workflow/confirm.html b/dashboard/src/templates/workflow/confirm.html index 29b90c8..2510204 100644 --- a/dashboard/src/templates/workflow/confirm.html +++ b/dashboard/src/templates/workflow/confirm.html @@ -66,7 +66,9 @@ 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 ) { parent.redirect_root(); } } + req.onreadystatechange = function() { if(req.readyState === 4 ) { + window.top.refresh_iframe(); + }} req.send(formData); } diff --git a/dashboard/src/templates/workflow/exit_redirect.html b/dashboard/src/templates/workflow/exit_redirect.html new file mode 100644 index 0000000..b08df78 --- /dev/null +++ b/dashboard/src/templates/workflow/exit_redirect.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <script> + top.window.location.href='/'; + </script> +</html> diff --git a/dashboard/src/templates/workflow/no_workflow.html b/dashboard/src/templates/workflow/no_workflow.html index ff8aab3..0ac6549 100644 --- a/dashboard/src/templates/workflow/no_workflow.html +++ b/dashboard/src/templates/workflow/no_workflow.html @@ -1,7 +1,3 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block content %} -<h3>If you would like to create a booking or a resource, please use the links on the sidebar or from the homepage</h3> -<a href="/">Go Home</a> -{% endblock content %} +<script> + top.window.location.href='/'; +</script> diff --git a/dashboard/src/templates/workflow/viewport-base.html b/dashboard/src/templates/workflow/viewport-base.html index 37eff27..82c1324 100644 --- a/dashboard/src/templates/workflow/viewport-base.html +++ b/dashboard/src/templates/workflow/viewport-base.html @@ -71,7 +71,7 @@ .step_untouched { - background: #98B0AF; + background: #DDDDDD; } .step_invalid @@ -96,10 +96,12 @@ <button id="gob" onclick="go(step-1)" class="btn btn go_btn go_back">Go Back</button> <div class="options"> - <button class="btn" onclick="cancel_wf()">Cancel</button> + <button id="cancel_btn" class="btn" onclick="cancel_wf()">Cancel</button> </div> <div class="btn_wrapper"> -<div id="breadcrumbs"> +<div id="breadcrumbs" class="btn-group"> + <div class="btn-group" id="breadcrumb-wrapper"> + </div> </div> </div> {% csrf_token %} @@ -206,6 +208,14 @@ { context_data = 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) { @@ -240,60 +250,16 @@ while(container.firstChild){ container.removeChild(container.firstChild); } - //draw enough rows for all steps - var depth = meta_json['max_depth']; - for(var i=0; i<=depth; i++){ - var div = document.createElement("DIV"); - div.id = "row"+i; - if(i<depth){ - div.style['margin-bottom'] = "7px"; - } - if(i>0){ - div.style['margin-top'] = "7px"; - } - container.appendChild(div); - } + draw_steps(meta_json); } function draw_steps(meta_json){ - var all_relations = meta_json['relations']; - var relations = []; - var active_steps = []; - var active_step = step; - while(active_step < meta_json['steps'].length){ - active_steps.push(active_step); - var index = meta_json['parents'][active_step]; - var relation = all_relations[index]; - relations.push(relation); - active_step = relation['parent']; - } - var child_index = meta_json['children'][step]; - var my_children = all_relations[child_index]; - if(my_children){ - relations.push(my_children); - } - draw_relations(relations, meta_json, active_steps); - } - - function draw_relations(relations, meta_json, active_steps){ - for(var i=0; i<relations.length; i++){ - var relation = relations[i]; - var children_container = document.createElement("DIV"); - children_container.style['display'] = "inline"; - children_container.style['margin'] = "3px"; - children_container.style['padding'] = "3px"; - console.log("meta_json: "); - console.log(meta_json); - for(var j=0; j<relation['children'].length; j++){ - var step_json = meta_json['steps'][relation['children'][j]]; - step_json['index'] = relation['children'][j]; - var active = active_steps.indexOf(step_json['index']) > -1; - var step_button = create_step(meta_json['steps'][relation['children'][j]], active); - children_container.appendChild(step_button); - } - var parent_div = document.getElementById("row" + relation['depth']); - parent_div.appendChild(children_container); + 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"]); + document.getElementById("breadcrumbs").appendChild(step_btn); } } @@ -301,7 +267,6 @@ var step_dom = document.createElement("DIV"); if(active){ step_dom.className = "step_active"; - console.log(step_json['message']); } else{ step_dom.className = "step"; @@ -333,30 +298,45 @@ { update_message(msg, stat); } + step_dom.classList.add("btn"); var step_number = step_json['index']; step_dom.onclick = function(){ go(step_number); } - //TODO: background color and other style return step_dom; } function cancel_wf(){ - $.ajax({ - type: "POST", - url: "/wf/manager/", - data: {"cancel":"",}, - beforeSend: function(request) { - request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val() - ); - }, - success: redirect_root() - }); + 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) { + var doc = document.getElementById("viewport-iframe").contentWindow.document; + doc.open(); doc.write(this.responseText); doc.close(); + } + req.send(); + } + + function write_iframe(contents) + { + document.getElementById("viewport-iframe").contentWindow.document.innerHTML= contents; } function redirect_root() { - window.location.replace('/'); + window.location.replace('/wf/'); } function add_wf(type){ @@ -448,6 +428,11 @@ </script> <!-- /.col-lg-12 --> </div> +<div style="display: none;" id="workflow_pop_form_div"> +<form id="workflow_pop_form" action="/wf/workflow/finish/" method="post"> + {% csrf_token %} +</form> +</div> <iframe src="/wf/workflow" style="position: absolute; left: 351px; right: 105px; width: calc(100% - 450px); border-style: none; border-width: 1px; border-color: #888888;" scrolling="yes" id="viewport-iframe" onload="resize_iframe();"></iframe> {% endblock content %} diff --git a/dashboard/src/workflow/booking_workflow.py b/dashboard/src/workflow/booking_workflow.py index cd12ab6..76950b8 100644 --- a/dashboard/src/workflow/booking_workflow.py +++ b/dashboard/src/workflow/booking_workflow.py @@ -33,32 +33,15 @@ class Resource_Select(WorkflowStep): self.repo_check_key = False self.confirm_key = "booking" - def get_default_entry(self): - return None - def get_context(self): context = super(Resource_Select, self).get_context() default = [] - chosen_bundle = None - default_bundle = self.get_default_entry() - if default_bundle: - context['disabled'] = True - chosen_bundle = default_bundle - if chosen_bundle.id: - default.append(chosen_bundle.id) - else: - default.append("repo bundle") - else: - chosen_bundle = self.repo_get(self.repo_key, False) - if chosen_bundle: - if chosen_bundle.id: - default.append(chosen_bundle.id) - else: - default.append("repo bundle") - - bundle = default_bundle - if not bundle: - bundle = chosen_bundle + + chosen_bundle = self.repo_get(self.repo_key, False) + if chosen_bundle: + default.append(chosen_bundle.id) + + bundle = chosen_bundle edit = self.repo_get(self.repo.EDIT, False) user = self.repo_get(self.repo.SESSION_USER) context['form'] = ResourceSelectorForm( @@ -79,6 +62,9 @@ class Resource_Select(WorkflowStep): self.metastep.set_invalid("Please select a valid bundle") return render(request, self.template, context) selected_bundle = json.loads(data) + if len(selected_bundle) < 1: + self.metastep.set_invalid("Please select a valid bundle") + return render(request, self.template, context) selected_id = selected_bundle[0]['id'] gresource_bundle = None try: @@ -112,23 +98,9 @@ class Booking_Resource_Select(Resource_Select): def __init__(self, *args, **kwargs): super(Booking_Resource_Select, self).__init__(*args, **kwargs) - self.repo_key = self.repo.BOOKING_SELECTED_GRB + self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE self.confirm_key = "booking" - def get_default_entry(self): - default = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle") - mine = self.repo_get(self.repo_key) - if mine: - return None - try: - config_bundle = self.repo_get(self.repo.BOOKING_MODELS)['booking'].config_bundle - if default: - return default # select created grb, even if preselected config bundle - return config_bundle.bundle - except: - pass - return default - def get_context(self): context = super(Booking_Resource_Select, self).get_context() return context @@ -166,12 +138,20 @@ class SWConfig_Select(WorkflowStep): self.metastep.set_invalid("Please select a valid config") return self.render(request) bundle_json = json.loads(bundle_json) + if len(bundle_json) < 1: + self.metastep.set_invalid("Please select a valid config") + return self.render(request) bundle = None - try: - id = int(bundle_json[0]['id']) - bundle = ConfigBundle.objects.get(id=id) - except ValueError: - bundle = self.repo_get(self.repo.CONFIG_MODELS).get("bundle") + id = int(bundle_json[0]['id']) + bundle = ConfigBundle.objects.get(id=id) + + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE) + + if grb and bundle.bundle != grb: + self.metastep.set_invalid("Incompatible config selected for resource bundle") + return self.render(request) + if not grb: + self.repo_set(self.repo.SELECTED_GRESOURCE_BUNDLE, bundle.bundle) models = self.repo_get(self.repo.BOOKING_MODELS, {}) if "booking" not in models: @@ -196,7 +176,7 @@ class SWConfig_Select(WorkflowStep): default = [] bundle = None chosen_bundle = None - created_bundle = self.repo_get(self.repo.CONFIG_MODELS, {}).get("bundle", False) + created_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE) booking = self.repo_get(self.repo.BOOKING_MODELS, {}).get("booking", False) try: chosen_bundle = booking.config_bundle @@ -204,11 +184,10 @@ class SWConfig_Select(WorkflowStep): bundle = chosen_bundle except: if created_bundle: - default.append("repo bundle") + default.append(created_bundle.id) bundle = created_bundle - context['disabled'] = True edit = self.repo_get(self.repo.EDIT, False) - grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB) + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE) context['form'] = SWConfigSelectorForm(chosen_software=default, bundle=bundle, edit=edit, resource=grb) return context diff --git a/dashboard/src/workflow/forms.py b/dashboard/src/workflow/forms.py index feb32f2..f781663 100644 --- a/dashboard/src/workflow/forms.py +++ b/dashboard/src/workflow/forms.py @@ -41,6 +41,7 @@ class SearchableSelectMultipleWidget(widgets.SelectMultiple): self.default_entry = attrs.get("default_entry", "") self.edit = attrs.get("edit", False) self.wf_type = attrs.get("wf_type") + self.incompatible = attrs.get("incompatible", "false") super(SearchableSelectMultipleWidget, self).__init__(attrs) @@ -61,7 +62,8 @@ class SearchableSelectMultipleWidget(widgets.SelectMultiple): 'initial': self.initial, 'default_entry': self.default_entry, 'edit': self.edit, - 'wf_type': self.wf_type + 'wf_type': self.wf_type, + 'incompatible': self.incompatible } @@ -101,13 +103,6 @@ class ResourceSelectorForm(forms.Form): displayable['id'] = res.id resources[res.id] = displayable - if bundle: - displayable = {} - displayable['small_name'] = bundle.name - displayable['expanded_name'] = "Current bundle" - displayable['string'] = bundle.description - displayable['id'] = "repo bundle" - resources["repo bundle"] = displayable attrs = { 'set': resources, 'show_from_noentry': "true", @@ -159,13 +154,15 @@ class SWConfigSelectorForm(forms.Form): displayable['id'] = config.id configs[config.id] = displayable - if bundle: + incompatible_choice = "false" + if bundle and bundle.id not in configs: displayable = {} displayable['small_name'] = bundle.name - displayable['expanded_name'] = "Current configuration" + displayable['expanded_name'] = bundle.owner.username displayable['string'] = bundle.description - displayable['id'] = "repo bundle" - configs['repo bundle'] = displayable + displayable['id'] = bundle.id + configs[bundle.id] = displayable + incompatible_choice = "true" attrs = { 'set': configs, @@ -177,7 +174,8 @@ class SWConfigSelectorForm(forms.Form): 'placeholder': "config", 'initial': chosen, 'edit': edit, - 'wf_type': 2 + 'wf_type': 2, + 'incompatible': incompatible_choice } return attrs diff --git a/dashboard/src/workflow/models.py b/dashboard/src/workflow/models.py index 73a142e..495ce07 100644 --- a/dashboard/src/workflow/models.py +++ b/dashboard/src/workflow/models.py @@ -10,6 +10,7 @@ from django.shortcuts import render from django.contrib import messages +from django.http import HttpResponse import yaml import requests @@ -141,7 +142,6 @@ class BookingAuthManager(): class WorkflowStep(object): - template = 'bad_request.html' title = "Generic Step" description = "You were led here by mistake" @@ -189,9 +189,11 @@ class Confirmation_Step(WorkflowStep): description = "Does this all look right?" def get_vlan_warning(self): - grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False) + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False) if not grb: return 0 + if self.repo.BOOKING_MODELS not in self.repo.el: + return 0 vlan_manager = grb.lab.vlan_manager if vlan_manager is None: return 0 @@ -233,9 +235,10 @@ class Confirmation_Step(WorkflowStep): errors = self.flush_to_db() if errors: messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors) - return render(request, self.template, context) - messages.add_message(request, messages.SUCCESS, "Confirmed") - return render(request, self.template, context) + else: + messages.add_message(request, messages.SUCCESS, "Confirmed") + + return HttpResponse('') elif data == "False": context["bypassed"] = "true" messages.add_message(request, messages.SUCCESS, "Canceled") @@ -251,7 +254,7 @@ class Confirmation_Step(WorkflowStep): pass def translate_vlans(self): - grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False) + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False) if not grb: return 0 vlan_manager = grb.lab.vlan_manager @@ -285,6 +288,7 @@ class Repository(): RESOURCE_SELECT = "resource_select" CONFIRMATION = "confirmation" SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk" + SELECTED_CONFIG_BUNDLE = "selected config bundle pk" GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models" GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info" BOOKING = "booking" @@ -292,8 +296,6 @@ class Repository(): GRB_LAST_HOSTLIST = "grb_network_previous_hostlist" BOOKING_FORMS = "booking_forms" SWCONF_HOSTS = "swconf_hosts" - SWCONF_SELECTED_GRB = "swconf_selected_grb_pk" - BOOKING_SELECTED_GRB = "booking_selected_grb_pk" BOOKING_MODELS = "booking models" CONFIG_MODELS = "configuration bundle models" SESSION_USER = "session owner user account" @@ -307,6 +309,22 @@ class Repository(): SNAPSHOT_DESC = "description of the snapshot" BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking" + #migratory elements of segmented workflow + #each of these is the end result of a different workflow. + HAS_RESULT = "whether or not workflow has a result" + RESULT_KEY = "key for target index that result will be put into in parent" + RESULT = "result object from workflow" + + def get_child_defaults(self): + return_tuples = [] + for key in [self.SELECTED_GRESOURCE_BUNDLE, self.SESSION_USER]: + return_tuples.append((key, self.el.get(key))) + return return_tuples + + def set_defaults(self, defaults): + for key, value in defaults: + self.el[key] = value + def get(self, key, default, id): self.add_get_history(key, id) return self.el.get(key, default) @@ -337,11 +355,19 @@ class Repository(): errors = self.make_generic_resource_bundle() if errors: return errors + else: + self.el[self.HAS_RESULT] = True + self.el[self.RESULT_KEY] = self.SELECTED_GRESOURCE_BUNDLE + return if self.CONFIG_MODELS in self.el: errors = self.make_software_config_bundle() if errors: return errors + else: + self.el[self.HAS_RESULT] = True + self.el[self.RESULT_KEY] = self.SELECTED_CONFIG_BUNDLE + return if self.BOOKING_MODELS in self.el: errors = self.make_booking() @@ -434,7 +460,7 @@ class Repository(): else: return "GRB no models given. CODE:0x0001" - self.el[self.VALIDATED_MODEL_GRB] = bundle + self.el[self.RESULT] = bundle return False def make_software_config_bundle(self): @@ -472,15 +498,15 @@ class Repository(): else: pass - self.el[self.VALIDATED_MODEL_CONFIG] = bundle + self.el[self.RESULT] = bundle return False def make_booking(self): models = self.el[self.BOOKING_MODELS] owner = self.el[self.SESSION_USER] - if self.BOOKING_SELECTED_GRB in self.el: - selected_grb = self.el[self.BOOKING_SELECTED_GRB] + if self.SELECTED_GRESOURCE_BUNDLE in self.el: + selected_grb = self.el[self.SELECTED_GRESOURCE_BUNDLE] else: return "BOOK, no selected resource. CODE:0x000e" @@ -570,5 +596,6 @@ class Repository(): def __init__(self): self.el = {} self.el[self.CONFIRMATION] = {} + self.el["active_step"] = 0 self.get_history = {} self.put_history = {} diff --git a/dashboard/src/workflow/sw_bundle_workflow.py b/dashboard/src/workflow/sw_bundle_workflow.py index 1695b65..80d1b3d 100644 --- a/dashboard/src/workflow/sw_bundle_workflow.py +++ b/dashboard/src/workflow/sw_bundle_workflow.py @@ -20,16 +20,9 @@ from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConf class SWConf_Resource_Select(Resource_Select): def __init__(self, *args, **kwargs): super(SWConf_Resource_Select, self).__init__(*args, **kwargs) - self.repo_key = self.repo.SWCONF_SELECTED_GRB + self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE self.confirm_key = "configuration" - def get_default_entry(self): - booking_grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB) - if booking_grb: - return booking_grb - created_grb = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", None) - return created_grb - def post_render(self, request): response = super(SWConf_Resource_Select, self).post_render(request) models = self.repo_get(self.repo.CONFIG_MODELS, {}) @@ -80,7 +73,7 @@ class Define_Software(WorkflowStep): break excluded_images = Image.objects.exclude(owner=user).exclude(public=True) excluded_images = excluded_images | Image.objects.exclude(host_type=host.profile) - lab = self.repo_get(self.repo.SWCONF_SELECTED_GRB).lab + lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab excluded_images = excluded_images | Image.objects.exclude(from_lab=lab) filter_data["id_form-" + str(i) + "-image"] = [] for image in excluded_images: @@ -91,7 +84,7 @@ class Define_Software(WorkflowStep): def get_host_list(self, grb=None): if grb is None: - grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB, False) + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False) if not grb: return [] if grb.id: @@ -102,7 +95,7 @@ class Define_Software(WorkflowStep): def get_context(self): context = super(Define_Software, self).get_context() - grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB, False) + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False) if grb: context["grb"] = grb @@ -135,7 +128,7 @@ class Define_Software(WorkflowStep): i += 1 image = form.cleaned_data['image'] # checks image compatability - grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB) + grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE) lab = None if grb: lab = grb.lab diff --git a/dashboard/src/workflow/views.py b/dashboard/src/workflow/views.py index e5ef5c6..6d59b1c 100644 --- a/dashboard/src/workflow/views.py +++ b/dashboard/src/workflow/views.py @@ -30,6 +30,17 @@ def attempt_auth(request): def delete_session(request): + manager = attempt_auth(request) + + if not manager: + return HttpResponseGone("No session found that relates to current request") + + if manager.pop_workflow(): + return HttpResponse('') + else: + del ManagerTracker.managers[request.session['manager_session']] + return render(request, 'workflow/exit_redirect.html') + try: del ManagerTracker.managers[request.session['manager_session']] return HttpResponse('') @@ -70,7 +81,8 @@ def manager_view(request): 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: - del ManagerTracker.managers[request.session['manager_session']] + if not manager.pop_workflow(): + del ManagerTracker.managers[request.session['manager_session']] return manager.status(request) diff --git a/dashboard/src/workflow/workflow_factory.py b/dashboard/src/workflow/workflow_factory.py index 9a42d86..1f4a28a 100644 --- a/dashboard/src/workflow/workflow_factory.py +++ b/dashboard/src/workflow/workflow_factory.py @@ -12,6 +12,7 @@ from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info from workflow.sw_bundle_workflow import Config_Software, Define_Software, SWConf_Resource_Select from workflow.snapshot_workflow import Select_Host_Step, Image_Meta_Step +from workflow.models import Repository, Confirmation_Step import uuid @@ -34,23 +35,6 @@ class ConfigMetaWorkflow(object): workflow_type = 2 color = "#00ffcc" - -class MetaRelation(object): - def __init__(self, *args, **kwargs): - self.color = "#cccccc" - self.parent = 0 - self.children = [] - self.depth = -1 - - def to_json(self): - return { - 'color': self.color, - 'parent': self.parent, - 'children': self.children, - 'depth': self.depth, - } - - class MetaStep(object): UNTOUCHED = 0 @@ -92,12 +76,18 @@ class MetaStep(object): def __ne__(self, other): return self.id.int != other.id.int +class Workflow(object): + def __init__(self, steps, metasteps, repository): + self.repository = repository + self.steps = steps + self.metasteps = metasteps + self.active_index = 0 class WorkflowFactory(): booking_steps = [ Booking_Resource_Select, SWConfig_Select, - Booking_Meta + Booking_Meta, ] resource_steps = [ @@ -114,7 +104,7 @@ class WorkflowFactory(): snapshot_steps = [ Select_Host_Step, - Image_Meta_Step + Image_Meta_Step, ] def conjure(self, workflow_type=None, repo=None): @@ -129,8 +119,17 @@ class WorkflowFactory(): meta_steps = self.metaize(steps=steps, wf_type=workflow_type) return steps, meta_steps + def create_workflow(self, workflow_type=None, repo=None): + steps, meta_steps = self.conjure(workflow_type, repo) + c_step = self.make_step(Confirmation_Step, repo) + metaconfirm = MetaStep() + metaconfirm.short_title = "confirm" + metaconfirm.index = len(steps) + steps.append(c_step) + meta_steps.append(metaconfirm) + return Workflow(steps, meta_steps, repo) + def make_steps(self, step_types, repository): - repository.el['steps'] += len(step_types) steps = [] for step_type in step_types: steps.append(self.make_step(step_type, repository)) diff --git a/dashboard/src/workflow/workflow_manager.py b/dashboard/src/workflow/workflow_manager.py index 95fefbf..1d672cf 100644 --- a/dashboard/src/workflow/workflow_manager.py +++ b/dashboard/src/workflow/workflow_manager.py @@ -13,8 +13,8 @@ from django.http import JsonResponse import random from booking.models import Booking -from workflow.workflow_factory import WorkflowFactory, MetaStep, MetaRelation -from workflow.models import Repository, Confirmation_Step +from workflow.workflow_factory import WorkflowFactory, MetaStep +from workflow.models import Repository from resource_inventory.models import ( GenericResourceBundle, ConfigBundle, @@ -27,103 +27,65 @@ logger = logging.getLogger(__name__) class SessionManager(): + def active_workflow(self): + return self.workflows[-1] def __init__(self, request=None): - self.repository = Repository() - self.repository.el[self.repository.SESSION_USER] = request.user - self.repository.el['active_step'] = 0 - self.steps = [] + self.workflows = [] + + self.owner = request.user + self.factory = WorkflowFactory() - c_step = WorkflowFactory().make_step(Confirmation_Step, self.repository) - self.steps.append(c_step) - metaconfirm = MetaStep() - metaconfirm.index = 0 - metaconfirm.short_title = "confirm" - self.repository.el['steps'] = 1 - self.metaworkflow = None - self.metaworkflows = [] - self.metarelations = [] - self.relationreverselookup = {} - self.initialized = False - self.active_index = 0 - self.step_meta = [metaconfirm] - self.relation_depth = 0 def add_workflow(self, workflow_type=None, target_id=None, **kwargs): if target_id is not None: self.prefill_repo(target_id, workflow_type) - factory_steps, meta_info = self.factory.conjure(workflow_type=workflow_type, repo=self.repository) - offset = len(meta_info) - for relation in self.metarelations: - if relation.depth > self.relation_depth: - self.relation_depth = relation.depth - if relation.parent >= self.repository.el['active_step']: - relation.parent += offset - for i in range(0, len(relation.children)): - if relation.children[i] >= self.repository.el['active_step']: - relation.children[i] += offset - self.step_meta[self.active_index:self.active_index] = meta_info - self.steps[self.active_index:self.active_index] = factory_steps - - if self.initialized: - relation = MetaRelation() - relation.parent = self.repository.el['active_step'] + offset - relation.depth = self.relationreverselookup[self.step_meta[relation.parent]].depth + 1 - if relation.depth > self.relation_depth: - self.relation_depth = relation.depth - for i in range(self.repository.el['active_step'], offset + self.repository.el['active_step']): - relation.children.append(i) - self.relationreverselookup[self.step_meta[i]] = relation - relation.color = "#%06x" % random.randint(0, 0xFFFFFF) - self.metarelations.append(relation) - else: - relation = MetaRelation() - relation.depth = 0 - relation.parent = 500000000000 - for i in range(0, len(self.step_meta)): - relation.children.append(i) - self.relationreverselookup[self.step_meta[i]] = relation - self.metarelations.append(relation) - self.initialized = True + + repo = Repository() + if(len(self.workflows) >= 1): + defaults = self.workflows[-1].repository.get_child_defaults() + repo.set_defaults(defaults) + repo.el[repo.HAS_RESULT] = False + repo.el[repo.SESSION_USER] = self.owner + self.workflows.append(self.factory.create_workflow(workflow_type=workflow_type, repo = repo)) + + def pop_workflow(self): + if( len(self.workflows) <= 1 ): + return False + + if self.workflows[-1].repository.el[self.workflows[-1].repository.HAS_RESULT]: + key = self.workflows[-1].repository.el[self.workflows[-1].repository.RESULT_KEY] + result = self.workflows[-1].repository.el[self.workflows[-1].repository.RESULT] + self.workflows[-2].repository.el[key] = result + self.workflows.pop() + return True def status(self, request): try: - steps = [] - for step in self.step_meta: - steps.append(step.to_json()) - parents = {} - children = {} + meta_steps = [] + for step in self.active_workflow().metasteps: + meta_steps.append(step.to_json()) responsejson = {} - responsejson["steps"] = steps - responsejson["active"] = self.repository.el['active_step'] - responsejson["relations"] = [] - i = 0 - for relation in self.metarelations: - responsejson["relations"].append(relation.to_json()) - children[relation.parent] = i - for child in relation.children: - parents[child] = i - i += 1 - responsejson['max_depth'] = self.relation_depth - responsejson['parents'] = parents - responsejson['children'] = children + responsejson["steps"] = meta_steps + responsejson["active"] = self.active_workflow().repository.el['active_step'] + responsejson["workflow_count"] = len(self.workflows) return JsonResponse(responsejson, safe=False) - except Exception: + except Exception as e: pass 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.steps[self.active_index].post_render(request) - return self.steps[self.active_index].render(request) + 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) def post_render(self, request): - return self.steps[self.active_index].post_render(request) + return self.active_workflow().steps[self.active_workflow().active_index].post_render(request) def goto(self, num, **kwargs): - self.repository.el['active_step'] = int(num) - self.active_index = int(num) + self.active_workflow().repository.el['active_step'] = int(num) + self.active_workflow().active_index = int(num) # TODO: change to include some checking def prefill_repo(self, target_id, workflow_type): @@ -142,29 +104,28 @@ class SessionManager(): def prefill_booking(self, booking): models = self.make_booking_models(booking) confirmation = self.make_booking_confirm(booking) - self.repository.el[self.repository.BOOKING_MODELS] = models - self.repository.el[self.repository.CONFIRMATION] = confirmation - self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = self.make_grb_models(booking.resource.template) - self.repository.el[self.repository.BOOKING_SELECTED_GRB] = self.make_grb_models(booking.resource.template)['bundle'] - self.repository.el[self.repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle) + self.active_workflow().repository.el[self.active_workflow().repository.BOOKING_MODELS] = models + self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirmation + self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = self.make_grb_models(booking.resource.template) + self.active_workflow().repository.el[self.active_workflow().repository.SELECTED_GRESOURCE_BUNDLE] = self.make_grb_models(booking.resource.template)['bundle'] + self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle) def prefill_resource(self, resource): models = self.make_grb_models(resource) confirm = self.make_grb_confirm(resource) - self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = models - self.repository.el[self.repository.CONFIRMATION] = confirm + self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = models + self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm def prefill_config(self, config): models = self.make_config_models(config) confirm = self.make_config_confirm(config) - self.repository.el[self.repository.CONFIG_MODELS] = models - self.repository.el[self.repository.CONFIRMATION] = confirm + self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = models + self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm grb_models = self.make_grb_models(config.bundle) - self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = grb_models - self.repository.el[self.repository.SWCONF_SELECTED_GRB] = config.bundle + self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = grb_models def make_grb_models(self, resource): - models = self.repository.el.get(self.repository.GRESOURCE_BUNDLE_MODELS, {}) + models = self.active_workflow().repository.el.get(self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS, {}) models['hosts'] = [] models['bundle'] = resource models['interfaces'] = {} @@ -181,7 +142,7 @@ class SessionManager(): return models def make_grb_confirm(self, resource): - confirm = self.repository.el.get(self.repository.CONFIRMATION, {}) + confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {}) confirm['resource'] = {} confirm['resource']['hosts'] = [] confirm['resource']['lab'] = resource.lab.lab_user.username @@ -190,7 +151,7 @@ class SessionManager(): return confirm def make_config_models(self, config): - models = self.repository.el.get(self.repository.CONFIG_MODELS, {}) + models = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIG_MODELS, {}) models['bundle'] = config models['host_configs'] = [] for host_conf in HostConfiguration.objects.filter(bundle=config): @@ -199,7 +160,7 @@ class SessionManager(): return models def make_config_confirm(self, config): - confirm = self.repository.el.get(self.repository.CONFIRMATION, {}) + confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {}) confirm['configuration'] = {} confirm['configuration']['hosts'] = [] confirm['configuration']['name'] = config.name @@ -213,7 +174,7 @@ class SessionManager(): return confirm def make_booking_models(self, booking): - models = self.repository.el.get(self.repository.BOOKING_MODELS, {}) + models = self.active_workflow().repository.el.get(self.active_workflow().repository.BOOKING_MODELS, {}) models['booking'] = booking models['collaborators'] = [] for user in booking.collaborators.all(): @@ -221,7 +182,7 @@ class SessionManager(): return models def make_booking_confirm(self, booking): - confirm = self.repository.el.get(self.repository.CONFIRMATION, {}) + confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {}) confirm['booking'] = {} confirm['booking']['length'] = (booking.end - booking.start).days confirm['booking']['project'] = booking.project |