summaryrefslogtreecommitdiffstats
path: root/dashboard
diff options
context:
space:
mode:
authorParker Berberian <pberberian@iol.unh.edu>2019-06-21 15:11:37 +0000
committerGerrit Code Review <gerrit@opnfv.org>2019-06-21 15:11:37 +0000
commit100095deb2022a13508ad88200ffe254f53ea5d9 (patch)
treea038f54189ba658a9675845244be958c68dc3ebb /dashboard
parentc05b1d090b6848a7d4c34b8812b558dc22049f20 (diff)
parent936c7ca07f96ea183cbe6c33b8af29a9ed6e0e31 (diff)
Merge "Redesigns Multiple Select Filter Widget"
Diffstat (limited to 'dashboard')
-rw-r--r--dashboard/src/booking/forms.py3
-rw-r--r--dashboard/src/booking/quick_deployer.py33
-rw-r--r--dashboard/src/templates/booking/quick_deploy.html121
-rw-r--r--dashboard/src/templates/dashboard/multiple_select_filter_widget.html353
-rw-r--r--dashboard/src/templates/resource/steps/define_hardware.html2
-rw-r--r--dashboard/src/workflow/forms.py169
-rw-r--r--dashboard/src/workflow/resource_bundle_workflow.py80
7 files changed, 297 insertions, 464 deletions
diff --git a/dashboard/src/booking/forms.py b/dashboard/src/booking/forms.py
index e48b293..df88cc6 100644
--- a/dashboard/src/booking/forms.py
+++ b/dashboard/src/booking/forms.py
@@ -48,8 +48,7 @@ class QuickBookingForm(forms.Form):
)
attrs = FormUtils.getLabData(0)
- attrs['selection_data'] = 'false'
- self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(attrs=attrs))
+ self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(**attrs))
self.fields['length'] = forms.IntegerField(
widget=NumberInput(
attrs={
diff --git a/dashboard/src/booking/quick_deployer.py b/dashboard/src/booking/quick_deployer.py
index ac69c8c..11f5437 100644
--- a/dashboard/src/booking/quick_deployer.py
+++ b/dashboard/src/booking/quick_deployer.py
@@ -96,25 +96,22 @@ class BookingPermissionException(Exception):
pass
-def parse_host_field(host_field_contents):
- host_json = json.loads(host_field_contents)
- lab_dict = host_json['labs'][0]
- lab_id = list(lab_dict.keys())[0]
- lab_user_id = int(lab_id.split("_")[-1])
- lab = Lab.objects.get(lab_user__id=lab_user_id)
-
- host_dict = host_json['hosts'][0]
- profile_id = list(host_dict.keys())[0]
- profile_id = int(profile_id.split("_")[-1])
- profile = HostProfile.objects.get(id=profile_id)
-
- # check validity of field data before trying to apply to models
- if len(host_json['labs']) != 1:
+def parse_host_field(host_json):
+ lab, profile = (None, None)
+ lab_dict = host_json['lab']
+ for lab_info in lab_dict.values():
+ if lab_info['selected']:
+ lab = Lab.objects.get(lab_user__id=lab_info['id'])
+
+ host_dict = host_json['host']
+ for host_info in host_dict.values():
+ if host_info['selected']:
+ profile = HostProfile.objects.get(pk=host_info['id'])
+
+ if lab is None:
raise NoLabSelectedError("No lab was selected")
- if not lab:
- raise LabDNE("Lab with provided ID does not exist")
- if not profile:
- raise HostProfileDNE("Host type with provided ID does not exist")
+ if profile is None:
+ raise HostProfileDNE("No Host was selected")
return lab, profile
diff --git a/dashboard/src/templates/booking/quick_deploy.html b/dashboard/src/templates/booking/quick_deploy.html
index b669c8e..ea80af4 100644
--- a/dashboard/src/templates/booking/quick_deploy.html
+++ b/dashboard/src/templates/booking/quick_deploy.html
@@ -79,77 +79,57 @@
</div>
</div>
<script type="text/javascript">
- var normalize = function(data)
- {
- //converts the top level keys in data to map to lists
- var normalized = {}
- for( var key in data ){
- normalized[key] = [];
- for( var subkey in data[key] ){
- normalized[key].push(data[key][subkey]);
- }
- }
- return normalized;
- }
- var update_page_contents = function(response)
- {
- document.open();
- document.write(response);
- document.close();
- }
- //form hamdler code
- submit_form = function()
+ function submit_form()
{
- //altered from initial prototype: form submits automatically,
- //but needs formatting for multiple select field
- var data = normalize(result);
- data = JSON.stringify(data);
- document.getElementById("filter_field").value = data;
+ //formats data for form submission
+ document.getElementById("filter_field").value = JSON.stringify(result);
}
- var sup_image_dict = {{ image_filter|safe }};
- var sup_installer_dict = {{ installer_filter|safe }};
- var sup_scenario_dict = {{ scenario_filter|safe }};
-
- function imageHider() {
- var data = normalize(result);
- var drop = document.getElementById("id_image");
+ function hide_dropdown(drop_id) {
+ var drop = document.getElementById(drop_id);
+ //select 'blank' option
for( var i=0; i < drop.length; i++ )
{
if ( drop.options[i].text == '---------' )
- {
drop.selectedIndex = i;
- }
}
+ //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){
+ for( var attr in result[key] ){
+ if( attr in {} )
+ continue;
+ else
+ 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 }};
+
+ function imageHider() {
+ var drop = document.getElementById("id_image");
+ hide_dropdown("id_image");
- var empty_map = {}
+ 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
{
- var lab_pk = ""
- for( var j in data["labs"][0] )
- {
- if( j in {} ) { continue; }
- else { lab_pk = j; break; }
- }
- var host_pk = "";
- for( var j in data["hosts"][0] )
- {
- if( j in {} ) { continue; }
- else { host_pk = j; break; }
- }
if( image_object.host_profile == host_pk && image_object.lab == lab_pk )
{
drop.childNodes[i].style.display = "inherit";
@@ -180,28 +160,15 @@
document.getElementById('id_installer').addEventListener('change', scenarioHider);
function dropFilter(target, target_filter, master) {
- ob = document.getElementById(target);
+ var dropdown = document.getElementById(target);
- for(var i=0; i<ob.options.length; i++) {
- if ( ob.options[i].text == '---------' ) {
- ob.selectedIndex = i;
- }
- }
+ hide_dropdown(target);
- targ_id = "#" + target;
-
- $(targ_id).children().hide();
-
- for (var i = 0; i < document.getElementById(target).childNodes.length; i++)
- {
- document.getElementById(target).childNodes[i].disabled = true;
- }
var drop = document.getElementById(master);
var opts = target_filter[drop.options[drop.selectedIndex].value];
if (!opts) {
opts = {};
}
- var emptyMap = {}
var map = Object.create(null);
for (var i = 0; i < opts.length; i++) {
@@ -209,34 +176,14 @@
map[j] = true;
}
- for (var i = 0; i < document.getElementById(target).childNodes.length; i++) {
- if (document.getElementById(target).childNodes[i].value in opts && !(document.getElementById(target).childNodes[i].value in emptyMap) ) {
- document.getElementById(target).childNodes[i].style.display = "inherit";
- document.getElementById(target).childNodes[i].disabled = false;
+ for (var i = 0; i < dropdown.childNodes.length; i++) {
+ if (dropdown.childNodes[i].value in opts && !(dropdown.childNodes[i].value in {}) ) {
+ dropdown.childNodes[i].style.display = "inherit";
+ dropdown.childNodes[i].disabled = false;
}
}
}
</script>
<button id="quick_booking_confirm" onclick="submit_form();" class="btn btn-success">Confirm</button>
</form>
-<script>
- //context vars
- var prefill_host_selection = "{{host_select_field_prefill_data|default:""|safe}}";
- var prefill_purpose = "{{prefill_purpose|default:""|safe}}";
- var prefill_project = "{{prefill_project|default:""|safe}}";
- var prefill_hostname = "{{prefill_hostname|default:""|safe}}";
-
- //to handle prefill
- function prefill_host_select_field(data)
- {
- //
- if(data)
- {
- make_selection(data);
- }
- }
-
- //call init functions
- prefill_host_select_field(prefill_host_selection);
-</script>
{% endblock %}
diff --git a/dashboard/src/templates/dashboard/multiple_select_filter_widget.html b/dashboard/src/templates/dashboard/multiple_select_filter_widget.html
index 536fdcc..3a7e148 100644
--- a/dashboard/src/templates/dashboard/multiple_select_filter_widget.html
+++ b/dashboard/src/templates/dashboard/multiple_select_filter_widget.html
@@ -4,6 +4,7 @@
grid-template-columns: 1fr 1fr 1fr;
border: 0px;
}
+
.class_grid_wrapper {
border: 0px;
text-align: center;
@@ -20,96 +21,78 @@
display: grid;
grid-template-columns: 1fr 1fr;
}
+
.grid-item {
cursor: pointer;
- border:1px solid #cccccc;
+ border: 1px solid #cccccc;
border-radius: 5px;
- margin:20px;
+ margin: 20px;
height: 200px;
padding: 7px;
- transition-property: box-shadow, background-color;
- transition-duration: .2s;
-}
-
-.grid-item:hover {
- box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.45);
- transition-property: box-shadow;
- transition-duration: .2s;
-
+ transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
+ box-shadow: 0 1px 1px rgba(0,0,0,.075);
}
.selected_node {
- box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.45);
- background-color: #fff;
- transition-property: background-color;
- transition-duration: .2s;
+ 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;
}
.disabled_node {
cursor: not-allowed;
background-color: #EFEFEF;
- transition-property: box-shadow;
- transition-duration: .2s;
- border: 1px solid #ccc;
}
-.disabled_node:hover {
-}
+.disabled_node:hover {}
.cleared_node {
background-color: #FFFFFF;
}
-.grid-item-header
-{
+.grid-item-header {
font-weight: bold;
font-size: 20px;
margin-top: 10px;
}
-#dropdown_wrapper > div > h5 {
- margin: 12px;
- display: inline-block;
- vertical-align: middle;
+.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_wrapper > div > button {
- padding: 7px;
+.dropdown_item > button {
margin: 2px;
- float: right;
- width: 80px;
+ justify-self: end;
}
-#dropdown_wrapper > div > input {
- padding: 7px;
- margin: 2px;
- float: right;
- width: 300px;
- width: calc(100% - 240px);
+
+.dropdown_item > h5 {
+ margin: auto;
}
-#dropdown_wrapper > div {
- border:2px;
- border-style:none;
- border-color:black;
- border-radius: 5px;
- margin:20px;
- padding: 2px;
- box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
- transition-property: box-shadow, background-color;
- transition-duration: .2s;
- display: inline-block;
- vertical-align: middle;
+.dropdown_item > input {
+ padding: 7px;
+ margin: 2px;
+ width: 90%;
}
#dropdown_wrapper {
display: grid;
- grid-template-columns: 3fr 5fr;
+ 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 filter_objects %}
+{% for object_class, object_list in display_objects %}
<div class="class_grid_wrapper">
<div style="display:inline-block;margin:auto">
<h4>{{object_class}}</h4>
@@ -120,12 +103,8 @@
<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" onclick="processClick(
- '{{obj.id}}',
- {% if obj.multiple %}true
- {% else %}false
- {% endif %});">{% if obj.multiple %}Add{% else %}Select{% endif %}</button>
+ '{{obj.id}}');">{% if obj.multiple %}Add{% else %}Select{% endif %}</button>
</div>
- <input type="hidden" name="{{obj.id}}_selected" value="false"/>
{% endfor %}
</div>
</div>
@@ -137,62 +116,55 @@
<script>
var initialized = false;
-var mapping = {{ mapping|safe }};
+var inputs = [];
+var graph_neighbors = {{ neighbors|safe }};
var filter_items = {{ filter_items|safe }};
var result = {};
-var selection = {{selection_data|default_if_none:"null"|safe}};
var dropdown_count = 0;
-{% if selection_data %}
-make_selection({{selection_data|safe}});
-{% endif %}
+{% if initial_value %}
-function make_selection( selection_data ){
- if(!initialized) {
- filter_field_init();
- }
- for(var k in selection_data) {
- selected_items = selection_data[k];
- for( var selected_item in selected_items ){
- var node = filter_items[selected_item];
- if(!node['multiple']){
- var input_value = selected_items[selected_item];
- if( input_value != 'false' ) {
- select(node);
- markAndSweep(node);
- }
- var div = document.getElementById(selected_item)
- var inputs = div.parentNode.getElementsByTagName("input")
- var input = div.parentNode.getElementsByTagName("input")[0]
- input.value = input_value;
- updateResult(selected_item);
- } else {
- make_multiple_selection(selected_items, selected_item);
+var initial_value = {{ initial_value|safe }};
+
+
+function make_selection( initial_data ){
+ try_init();
+ for(var item_class in initial_data) {
+ var selected_items = initial_data[item_class];
+ for( var node_id in selected_items ){
+ var node = filter_items[node_id];
+ var selection_data = selected_items[node_id]
+ if( selection_data.selected ) {
+ select(node);
+ markAndSweep(node);
+ updateResult(node);
+ }
+ if(node['multiple']){
+ make_multiple_selection(node, selection_data);
}
}
}
}
-function make_multiple_selection(data, item_class){
- var node = filter_items[item_class];
- select(node);
- markAndSweep(node);
- prepop_data = data[item_class];
- for(var i=0; i<prepop_data.length; i++){
- var div = add_item_prepopulate(node, prepop_data[i]);
- updateObjectResult(div);
+function make_multiple_selection(node, selection_data){
+ prepop_data = selection_data.values;
+ for(var k in prepop_data){
+ var div = add_item_prepopulate(node, prepop_data[k]);
+ updateObjectResult(node, div.id, prepop_data[k]);
}
}
+make_selection({{initial_value|safe}});
+
+{% endif %}
+
function markAndSweep(root){
- for(var nodeId in filter_items) {
- node = filter_items[nodeId];
+ for(var i in filter_items) {
+ node = filter_items[i];
node['marked'] = true; //mark all nodes
- //clears grey background of everything
}
toCheck = [root];
-
while(toCheck.length > 0){
node = toCheck.pop();
if(!node['marked']) {
@@ -200,11 +172,10 @@ function markAndSweep(root){
continue;
}
node['marked'] = false; //mark as visited
- if(node['follow'] || node == root){ //add neighbors if we want to follow this node (labs)
- var mappingId = node.id
- var neighbors = mapping[mappingId];
- for(var neighId in neighbors) {
- neighId = neighbors[neighId];
+ if(node['follow'] || node == root){ //add neighbors if we want to follow this node
+ var neighbors = graph_neighbors[node.id];
+ for(var i in neighbors) {
+ var neighId = neighbors[i];
var neighbor = filter_items[neighId];
toCheck.push(neighbor);
}
@@ -212,8 +183,8 @@ function markAndSweep(root){
}
//now remove all nodes still marked
- for(var nodeId in filter_items){
- node = filter_items[nodeId];
+ for(var i in filter_items){
+ node = filter_items[i];
if(node['marked']){
disable_node(node);
}
@@ -224,7 +195,7 @@ function process(node) {
if(node['selected']) {
markAndSweep(node);
}
- else {
+ else { //TODO: make this not dumb
var selected = []
//remember the currently selected, then reset everything and reselect one at a time
for(var nodeId in filter_items) {
@@ -233,7 +204,6 @@ function process(node) {
selected.push(node);
}
clear(node);
-
}
for(var i=0; i<selected.length; i++) {
node = selected[i];
@@ -249,9 +219,6 @@ function select(node) {
elem.classList.remove('cleared_node');
elem.classList.remove('disabled_node');
elem.classList.add('selected_node');
- var input = elem.parentNode.getElementsByTagName("input")[0];
- input.disabled = false;
- input.value = true;
}
function clear(node) {
@@ -261,7 +228,6 @@ function clear(node) {
elem.classList.add('cleared_node')
elem.classList.remove('disabled_node');
elem.classList.remove('selected_node');
- elem.parentNode.getElementsByTagName("input")[0].disabled = true;
}
function disable_node(node) {
@@ -271,21 +237,22 @@ function disable_node(node) {
elem.classList.remove('cleared_node');
elem.classList.add('disabled_node');
elem.classList.remove('selected_node');
- elem.parentNode.getElementsByTagName("input")[0].disabled = true;
}
-function processClick(id, multiple){
- if(!initialized){
- filter_field_init();
- }
- var element = document.getElementById(id);
+function processClick(id){
+ try_init();
var node = filter_items[id];
- if(!node['selectable']){
+ if(!node['selectable'])
return;
+
+ if(node['multiple']){
+ return processClickMultiple(node);
+ } else {
+ return processClickSingle(node);
}
- if(multiple){
- return processClickMultipleObject(node);
- }
+}
+
+function processClickSingle(node){
node['selected'] = !node['selected']; //toggle on click
if(node['selected']) {
@@ -294,21 +261,20 @@ function processClick(id, multiple){
clear(node);
}
process(node);
- updateResult(id);
+ updateResult(node);
}
-function processClickMultipleObject(node){
+function processClickMultiple(node){
select(node);
- add_node(node);
+ var div = add_node(node);
process(node);
+ updateObjectResult(node, div.id, "");
}
function add_node(node){
- return add_item_prepopulate(node, {});
+ return add_item_prepopulate(node, false);
}
-inputs = []
-
function restrictchars(input){
if( input.validity.patternMismatch ){
input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed");
@@ -318,8 +284,7 @@ function restrictchars(input){
checkunique(input);
}
-function checkunique(tocheck)
-{
+function checkunique(tocheck){
val = tocheck.value;
for( var i = 0; i < inputs.length; i++ )
{
@@ -333,109 +298,83 @@ function checkunique(tocheck)
tocheck.setCustomValidity("");
}
-function add_item_prepopulate(node, prepopulate){
- inputs = [];
- var div = document.createElement("DIV");
- div.class = node['id'];
- div.id = "dropdown_" + dropdown_count;
- dropdown_count++;
- var label = document.createElement("H5");
- label.appendChild(document.createTextNode(node['name']));
- div.appendChild(label);
- button = document.createElement("BUTTON");
+function make_remove_button(div, node){
+ var button = document.createElement("BUTTON");
button.type = "button";
button.appendChild(document.createTextNode("Remove"));
button.classList.add("btn-danger");
button.classList.add("btn");
- div.appendChild(button);
- for(var i=0; i<node['forms'].length; i++){
- form = node['forms'][i];
- var input = document.createElement("INPUT");
- input.type = form['type'];
- input.name = form['name'];
- 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 = form['placeholder'];
- inputs.push(input);
- input.onchange = function() { updateObjectResult(div); restrictchars(this); };
- input.oninput = function() { restrictchars(this); };
- if(form['name'] in prepopulate){
- input.value = prepopulate[form['name']];
- }
- div.appendChild(input);
- }
- //add class id to dropdown object
- var hiddenInput = document.createElement("INPUT");
- hiddenInput.type = "hidden";
- hiddenInput.name = "class";
- hiddenInput.value = node['id'];
- div.appendChild(hiddenInput);
button.onclick = function(){
- remove_dropdown(div.id);
+ remove_dropdown(div.id, node.id);
}
+ return button;
+}
+
+function make_input(div, node, prepopulate){
+ var input = document.createElement("INPUT");
+ input.type = node.form.type;
+ input.name = node.id + node.form.name
+ 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;
+ inputs.push(input);
+ input.onchange = function() { updateObjectResult(node, div.id, input.value); restrictchars(this); };
+ input.oninput = function() { restrictchars(this); };
+ if(prepopulate)
+ input.value = prepopulate;
+ return input;
+}
+
+function add_item_prepopulate(node, prepopulate){
+ var div = document.createElement("DIV");
+ div.id = "dropdown_" + dropdown_count;
+ div.classList.add("dropdown_item");
+ dropdown_count++;
+ var label = document.createElement("H5")
+ label.appendChild(document.createTextNode(node['name']))
+ div.appendChild(label);
+ div.appendChild(make_input(div, node, prepopulate));
+ div.appendChild(make_remove_button(div, node));
document.getElementById("dropdown_wrapper").appendChild(div);
- var linebreak = document.createElement("BR");
- document.getElementById("dropdown_wrapper").appendChild(linebreak);
- updateObjectResult(div);
return div;
}
-function remove_dropdown(id){
- var div = document.getElementById(id);
+function remove_dropdown(div_id, node_id){
+ var div = document.getElementById(div_id);
+ var node = filter_items[node_id]
var parent = div.parentNode;
div.parentNode.removeChild(div);
+ delete result[node.class][node.id]['values'][div.id];
+
//checks if we have removed last item in class
- var deselect_class = true;
- var div_inputs = div.getElementsByTagName("input");
- var div_class = div_inputs[div_inputs.length-1].value;
- var result_class = document.getElementById(div_class).parentNode.parentNode.id;
- delete result[result_class][div.id];
- for(var i=0; i<parent.children.length; i++){
- var inputs = parent.children[i].getElementsByTagName("input");
- var object_class = "";
- for(var k=0; k<inputs.length; k++){
- if(inputs[k].name == "class"){
- object_class = inputs[k].value;
- }
- }
- if(object_class == div_class){
- deselect_class = false;
- }
- }
- if(deselect_class){
- clear(filter_items[div_class]);
+ if(jQuery.isEmptyObject(result[node.class][node.id]['values'])){
+ delete result[node.class][node.id];
+ clear(node);
}
}
-
-function updateResult(nodeId){
- if(!initialized){
- filter_field_init();
- }
- if(!filter_items[nodeId]['multiple']){
- var node = document.getElementById(nodeId);
- var value = {}
- value[nodeId] = node.parentNode.getElementsByTagName("input")[0].value;
- result[node.parentNode.id] = {};
- result[node.parentNode.id][nodeId] = value;
+function updateResult(node){
+ try_init();
+ if(!node['multiple']){
+ result[node.class][node.id] = {selected: node.selected, id: node.model_id}
+ if(!node.selected)
+ delete result[node.class][node.id];
}
}
-function updateObjectResult(parentElem){
- node_type = document.getElementById(parentElem.class).parentNode.id;
- input = {};
- inputs = parentElem.getElementsByTagName("input");
- for(var i in inputs){
- var e = inputs[i];
- input[e.name] = e.value;
- }
- result[node_type][parentElem.id] = input;
+function updateObjectResult(node, childKey, childValue){
+ try_init();
+ if(!result[node.class][node.id])
+ result[node.class][node.id] = {selected: true, id: node.model_id, values: {}}
+
+ result[node.class][node.id]['values'][childKey] = childValue;
}
-function filter_field_init() {
+function try_init() {
+ if(initialized) return;
for(nodeId in filter_items) {
- element = document.getElementById(nodeId);
- node = filter_items[nodeId];
- result[element.parentNode.id] = {}
+ var element = document.getElementById(nodeId);
+ var node = filter_items[nodeId];
+ result[node.class] = {}
}
initialized = true;
}
diff --git a/dashboard/src/templates/resource/steps/define_hardware.html b/dashboard/src/templates/resource/steps/define_hardware.html
index f85576e..77df5a2 100644
--- a/dashboard/src/templates/resource/steps/define_hardware.html
+++ b/dashboard/src/templates/resource/steps/define_hardware.html
@@ -26,7 +26,7 @@ var normalize = function(data){
}
return normalized;
}
-var data = normalize(result);
+var data = result;
data = JSON.stringify(data);
document.getElementById("filter_field").value = data;
var formData = $("#define_hardware_form").serialize();
diff --git a/dashboard/src/workflow/forms.py b/dashboard/src/workflow/forms.py
index 0fb45d6..ee44ecd 100644
--- a/dashboard/src/workflow/forms.py
+++ b/dashboard/src/workflow/forms.py
@@ -242,124 +242,101 @@ class BookingMetaForm(forms.Form):
class MultipleSelectFilterWidget(forms.Widget):
- def __init__(self, attrs=None):
- super(MultipleSelectFilterWidget, self).__init__(attrs)
- self.attrs = attrs
+ def __init__(self, *args, display_objects=None, filter_items=None, neighbors=None, **kwargs):
+ super(MultipleSelectFilterWidget, self).__init__(*args, **kwargs)
+ self.display_objects = display_objects
+ self.filter_items = filter_items
+ self.neighbors = neighbors
self.template_name = "dashboard/multiple_select_filter_widget.html"
def render(self, name, value, attrs=None, renderer=None):
- attrs = self.attrs
- self.context = self.get_context(name, value, attrs)
- html = render_to_string(self.template_name, context=self.context)
+ context = self.get_context(name, value, attrs)
+ html = render_to_string(self.template_name, context=context)
return mark_safe(html)
def get_context(self, name, value, attrs):
- return attrs
+ return {
+ 'display_objects': self.display_objects,
+ 'neighbors': self.neighbors,
+ 'filter_items': self.filter_items,
+ 'initial_value': value
+ }
class MultipleSelectFilterField(forms.Field):
- def __init__(self, required=True, widget=None, label=None, initial=None,
- help_text='', error_messages=None, show_hidden_initial=False,
- validators=(), localize=False, disabled=False, label_suffix=None):
- """from the documentation:
- # required -- Boolean that specifies whether the field is required.
- # True by default.
- # widget -- A Widget class, or instance of a Widget class, that should
- # be used for this Field when displaying it. Each Field has a
- # default Widget that it'll use if you don't specify this. In
- # most cases, the default widget is TextInput.
- # label -- A verbose name for this field, for use in displaying this
- # field in a form. By default, Django will use a "pretty"
- # version of the form field name, if the Field is part of a
- # Form.
- # initial -- A value to use in this Field's initial display. This value
- # is *not* used as a fallback if data isn't given.
- # help_text -- An optional string to use as "help; text" for this Field.
- # error_messages -- An optional dictionary to override the default
- # messages that the field will raise.
- # show_hidden_initial -- Boolean that specifies if it is needed to render a
- # hidden widget with initial value after widget.
- # validators -- List of additional validators to use
- # localize -- Boolean that specifies if the field should be localized.
- # disabled -- Boolean that specifies whether the field is disabled, that
- # is its widget is shown in the form but not editable.
- # label_suffix -- Suffix to be added to the label. Overrides
- # form's label_suffix.
- """
- # this is bad, but django forms are annoying
- self.widget = widget
- if self.widget is None:
- self.widget = MultipleSelectFilterWidget()
- super(MultipleSelectFilterField, self).__init__(
- required=required,
- widget=self.widget,
- label=label,
- initial=None,
- help_text=help_text,
- error_messages=error_messages,
- show_hidden_initial=show_hidden_initial,
- validators=validators,
- localize=localize,
- disabled=disabled,
- label_suffix=label_suffix
- )
+ def __init__(self, **kwargs):
+ self.initial = kwargs.get("initial")
+ super().__init__(**kwargs)
- def clean(data):
- """
- This method will raise a django.forms.ValidationError or return clean data
- """
- return data
+ def to_python(self, value):
+ return json.loads(value)
class FormUtils:
@staticmethod
- def getLabData(multiple_selectable_hosts):
+ def getLabData(multiple_hosts=False):
"""
Gets all labs and thier host profiles and returns a serialized version the form can understand.
Should be rewritten with a related query to make it faster
- Should be moved outside of global scope
"""
+ # javascript truthy variables
+ true = 1
+ false = 0
+ if multiple_hosts:
+ multiple_hosts = true
+ else:
+ multiple_hosts = false
labs = {}
hosts = {}
items = {}
- mapping = {}
+ neighbors = {}
for lab in Lab.objects.all():
- slab = {}
- slab['id'] = "lab_" + str(lab.lab_user.id)
- slab['name'] = lab.name
- slab['description'] = lab.description
- slab['selected'] = 0
- slab['selectable'] = 1
- slab['follow'] = 1
- if not multiple_selectable_hosts:
- slab['follow'] = 0
- slab['multiple'] = 0
- items[slab['id']] = slab
- mapping[slab['id']] = []
- labs[slab['id']] = slab
+ lab_node = {
+ 'id': "lab_" + str(lab.lab_user.id),
+ 'model_id': lab.lab_user.id,
+ 'name': lab.name,
+ 'description': lab.description,
+ 'selected': false,
+ 'selectable': true,
+ 'follow': false,
+ 'multiple': false,
+ 'class': 'lab'
+ }
+ if multiple_hosts:
+ # "follow" this lab node to discover more hosts if allowed
+ lab_node['follow'] = true
+ items[lab_node['id']] = lab_node
+ neighbors[lab_node['id']] = []
+ labs[lab_node['id']] = lab_node
+
for host in lab.hostprofiles.all():
- shost = {}
- shost['forms'] = [{"name": "host_name", "type": "text", "placeholder": "hostname"}]
- shost['id'] = "host_" + str(host.id)
- shost['name'] = host.name
- shost['description'] = host.description
- shost['selected'] = 0
- shost['selectable'] = 1
- shost['follow'] = 0
- shost['multiple'] = multiple_selectable_hosts
- items[shost['id']] = shost
- mapping[slab['id']].append(shost['id'])
- if shost['id'] not in mapping:
- mapping[shost['id']] = []
- mapping[shost['id']].append(slab['id'])
- hosts[shost['id']] = shost
-
- filter_objects = [("labs", labs.values()), ("hosts", hosts.values())]
+ host_node = {
+ 'form': {"name": "host_name", "type": "text", "placeholder": "hostname"},
+ 'id': "host_" + str(host.id),
+ 'model_id': host.id,
+ 'name': host.name,
+ 'description': host.description,
+ 'selected': false,
+ 'selectable': true,
+ 'follow': false,
+ 'multiple': multiple_hosts,
+ 'class': 'host'
+ }
+ if multiple_hosts:
+ host_node['values'] = [] # place to store multiple values
+ items[host_node['id']] = host_node
+ neighbors[lab_node['id']].append(host_node['id'])
+ if host_node['id'] not in neighbors:
+ neighbors[host_node['id']] = []
+ neighbors[host_node['id']].append(lab_node['id'])
+ hosts[host_node['id']] = host_node
+
+ display_objects = [("lab", labs.values()), ("host", hosts.values())]
context = {
- 'filter_objects': filter_objects,
- 'mapping': mapping,
+ 'display_objects': display_objects,
+ 'neighbors': neighbors,
'filter_items': items
}
return context
@@ -368,14 +345,10 @@ class FormUtils:
class HardwareDefinitionForm(forms.Form):
def __init__(self, *args, **kwargs):
- selection_data = kwargs.pop("selection_data", False)
super(HardwareDefinitionForm, self).__init__(*args, **kwargs)
- attrs = FormUtils.getLabData(1)
- attrs['selection_data'] = selection_data
+ attrs = FormUtils.getLabData(multiple_hosts=True)
self.fields['filter_field'] = MultipleSelectFilterField(
- widget=MultipleSelectFilterWidget(
- attrs=attrs
- )
+ widget=MultipleSelectFilterWidget(**attrs)
)
diff --git a/dashboard/src/workflow/resource_bundle_workflow.py b/dashboard/src/workflow/resource_bundle_workflow.py
index ced355f..a4657ab 100644
--- a/dashboard/src/workflow/resource_bundle_workflow.py
+++ b/dashboard/src/workflow/resource_bundle_workflow.py
@@ -52,65 +52,47 @@ class Define_Hardware(WorkflowStep):
description = "Choose the type and amount of machines you want"
short_title = "hosts"
+ def __init__(self, *args, **kwargs):
+ self.form = None
+ super().__init__(*args, **kwargs)
+
def get_context(self):
context = super(Define_Hardware, self).get_context()
- selection_data = {"hosts": {}, "labs": {}}
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- hosts = models.get("hosts", [])
- for host in hosts:
- profile_id = "host_" + str(host.profile.id)
- if profile_id not in selection_data['hosts']:
- selection_data['hosts'][profile_id] = []
- selection_data['hosts'][profile_id].append({"host_name": host.resource.name, "class": profile_id})
-
- if models.get("bundle", GenericResourceBundle()).lab:
- selection_data['labs'] = {"lab_" + str(models.get("bundle").lab.lab_user.id): "true"}
-
- form = HardwareDefinitionForm(
- selection_data=selection_data
- )
- context['form'] = form
+ context['form'] = self.form or HardwareDefinitionForm()
return context
- def render(self, request):
- self.context = self.get_context()
- return render(request, self.template, self.context)
-
def update_models(self, data):
- data = json.loads(data['filter_field'])
+ data = data['filter_field']
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
models['hosts'] = [] # This will always clear existing data when this step changes
models['interfaces'] = {}
if "bundle" not in models:
models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))
- host_data = data['hosts']
+ host_data = data['host']
names = {}
- for host_dict in host_data:
- id = host_dict['class']
- # bit of formatting
- id = int(id.split("_")[-1])
+ for host_profile_dict in host_data.values():
+ id = host_profile_dict['id']
profile = HostProfile.objects.get(id=id)
# instantiate genericHost and store in repo
- name = host_dict['host_name']
- if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
- raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
- if name in names:
- raise NonUniqueHostnameException("All hosts must have unique names")
- names[name] = True
- genericResource = GenericResource(bundle=models['bundle'], name=name)
- genericHost = GenericHost(profile=profile, resource=genericResource)
- models['hosts'].append(genericHost)
- for interface_profile in profile.interfaceprofile.all():
- genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
- if genericHost.resource.name not in models['interfaces']:
- models['interfaces'][genericHost.resource.name] = []
- models['interfaces'][genericHost.resource.name].append(genericInterface)
+ for name in host_profile_dict['values'].values():
+ if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
+ raise InvalidHostnameException("Invalid hostname: '" + name + "'")
+ if name in names:
+ raise NonUniqueHostnameException("All hosts must have unique names")
+ names[name] = True
+ genericResource = GenericResource(bundle=models['bundle'], name=name)
+ genericHost = GenericHost(profile=profile, resource=genericResource)
+ models['hosts'].append(genericHost)
+ for interface_profile in profile.interfaceprofile.all():
+ genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
+ if genericHost.resource.name not in models['interfaces']:
+ models['interfaces'][genericHost.resource.name] = []
+ models['interfaces'][genericHost.resource.name].append(genericInterface)
# add selected lab to models
- for lab_dict in data['labs']:
- if list(lab_dict.values())[0]: # True for lab the user selected
- lab_user_id = int(list(lab_dict.keys())[0].split("_")[-1])
- models['bundle'].lab = Lab.objects.get(lab_user__id=lab_user_id)
+ for lab_dict in data['lab'].values():
+ if lab_dict['selected']:
+ models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
break # if somehow we get two 'true' labs, we only use one
# return to repo
@@ -133,15 +115,11 @@ class Define_Hardware(WorkflowStep):
try:
self.form = HardwareDefinitionForm(request.POST)
if self.form.is_valid():
- if len(json.loads(self.form.cleaned_data['filter_field'])['labs']) != 1:
- self.set_invalid("Please select one lab")
- else:
- self.update_models(self.form.cleaned_data)
- self.update_confirmation()
- self.set_valid("Step Completed")
+ self.update_models(self.form.cleaned_data)
+ self.update_confirmation()
+ self.set_valid("Step Completed")
else:
self.set_invalid("Please complete the fields highlighted in red to continue")
- pass
except Exception as e:
self.set_invalid(str(e))
self.context = self.get_context()