Make host entries prettier on Define Hardware step
[pharos-tools.git] / dashboard / src / templates / dashboard / multiple_select_filter_widget.html
1 <style>
2 .object_class_wrapper {
3     display: grid;
4     grid-template-columns: 1fr 1fr 1fr;
5     border: 0px;
6 }
7 .class_grid_wrapper {
8     border: 0px;
9     border-left: 1px;
10     border-right: 1px;
11     border-style: solid;
12     border-color: grey;
13     text-align: center;
14 }
15 .grid_wrapper {
16     display: grid;
17     grid-template-columns: 1fr 1fr;
18 }
19 .grid-item {
20     cursor: pointer;
21     border:2px;
22     border-style:none;
23     border-color:black;
24     border-radius: 5px;
25     margin:20px;
26     height: 200px;
27     padding: 7px;
28     box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
29     transition-property: box-shadow, background-color;
30     transition-duration: .2s;
31 }
32
33 .grid-item:hover {
34     box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75);
35     transition-property: box-shadow;
36     transition-duration: .2s;
37
38 }
39
40 .selected_node {
41     box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75);
42     background-color: #CCECD7;
43     transition-property: background-color;
44     transition-duration: .2s;
45 }
46
47 .disabled_node {
48     cursor: not-allowed;
49     background-color: #EFEFEF;
50     box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75);
51     transition-property: box-shadow;
52     transition-duration: .2s;
53 }
54
55 .disabled_node:hover {
56     box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75);
57 }
58
59 .cleared_node {
60     background-color: #FFFFFF;
61 }
62
63 .grid-item-header
64 {
65     font-weight: bold;
66     font-size: 20px;
67     margin-top: 10px;
68 }
69
70 #dropdown_wrapper > div > h5 {
71     margin: 12px;
72     display: inline-block;
73     vertical-align: middle;
74 }
75
76 #dropdown_wrapper > div > button {
77     padding: 7px;
78     margin: 2px;
79     float: right;
80     width: 80px;
81 }
82 #dropdown_wrapper > div > input {
83     padding: 7px;
84     margin: 2px;
85     float: right;
86     width: 300px;
87     width: calc(100% - 240px);
88 }
89
90 #dropdown_wrapper > div {
91     border:2px;
92     border-style:none;
93     border-color:black;
94     border-radius: 5px;
95     margin:20px;
96     padding: 2px;
97     box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
98     transition-property: box-shadow, background-color;
99     transition-duration: .2s;
100     display: inline-block;
101     vertical-align: middle;
102 }
103
104 #dropdown_wrapper {
105     display: grid;
106     grid-template-columns: 3fr 5fr;
107 }
108
109 </style>
110 <input name="filter_field" id="filter_field" type="hidden"/>
111 <div id="grid_wrapper" class="grid_wrapper">
112 {% for object_class, object_list in filter_objects %}
113 <div class="class_grid_wrapper">
114     <div style="display:inline-block;margin:auto">
115         <h4>{{object_class}}</h4>
116     </div>
117     <div id="{{object_class}}" class="object_class_wrapper">
118     {% for obj in object_list %}
119     <div id="object_parent">
120         <div id="{{ obj.id|default:'not_provided' }}" class="grid-item">
121             <p class="grid-item-header">{{obj.name}}</p>
122             <p class="grid-item-description">{{obj.description}}</p>
123             <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>
124         </div>
125         <input type="hidden" name="{{obj.id}}_selected" value="false"/>
126     </div>
127     {% endfor %}
128     </div>
129     </div>
130 {% endfor %}
131 </div>
132
133 <div id="dropdown_wrapper">
134 </div>
135
136 <script>
137 var initialized = false;
138 var mapping = {{ mapping|safe }};
139 var filter_items = {{ filter_items|safe }};
140 var result = {};
141 var selection = {{selection_data|default_if_none:"null"|safe}};
142 var dropdown_count = 0;
143
144 {% if selection_data %}
145 make_selection({{selection_data|safe}});
146 {% endif %}
147
148 function make_selection( selection_data ){
149     if(!initialized) {
150         filter_field_init();
151     }
152     for(var k in selection_data) {
153         selected_items = selection_data[k];
154         for( var selected_item in selected_items ){
155             var node = filter_items[selected_item];
156             if(!node['multiple']){
157                 var input_value = selected_items[selected_item];
158                 if( input_value != 'false' ) {
159                     select(node);
160                     markAndSweep(node);
161                 }
162                 var div = document.getElementById(selected_item)
163                 var inputs = div.parentNode.getElementsByTagName("input")
164                 var input = div.parentNode.getElementsByTagName("input")[0]
165                 input.value = input_value;
166                 updateResult(selected_item);
167             } else {
168                 make_multiple_selection(selected_items, selected_item);
169             }
170         }
171     }
172 }
173
174 function make_multiple_selection(data, item_class){
175     var node = filter_items[item_class];
176     select(node);
177     markAndSweep(node);
178     prepop_data = data[item_class];
179     for(var i=0; i<prepop_data.length; i++){
180         var div = add_item_prepopulate(node, prepop_data[i]);
181         updateObjectResult(div);
182     }
183 }
184
185 function markAndSweep(root){
186     for(var nodeId in filter_items) {
187         node = filter_items[nodeId];
188         node['marked'] = true; //mark all nodes
189         //clears grey background of everything
190     }
191
192     toCheck = [];
193     toCheck.push(root);
194
195     while(toCheck.length > 0){
196         node = toCheck.pop();
197         if(!node['marked']) {
198             //already visited, just continue
199             continue;
200         }
201         node['marked'] = false; //mark as visited
202         if(node['follow'] || node == root){ //add neighbors if we want to follow this node (labs)
203             var mappingId = node.id
204             var neighbors = mapping[mappingId];
205             for(var neighId in neighbors) {
206                 neighId = neighbors[neighId];
207                 var neighbor = filter_items[neighId];
208                 toCheck.push(neighbor);
209             }
210         }
211     }
212
213     //now remove all nodes still marked
214     for(var nodeId in filter_items){
215         node = filter_items[nodeId];
216         if(node['marked']){
217             disable_node(node);
218         }
219     }
220 }
221
222 function process(node) {
223     if(node['selected']) {
224         markAndSweep(node);
225     }
226     else {
227         var selected = []
228         //remember the currently selected, then reset everything and reselect one at a time
229         for(var nodeId in filter_items) {
230             node = filter_items[nodeId];
231             if(node['selected']) {
232                 selected.push(node);
233             }
234             clear(node);
235
236         }
237         for(var i=0; i<selected.length; i++) {
238             node = selected[i];
239             select(node);
240             markAndSweep(selected[i]);
241         }
242     }
243 }
244
245 function select(node) {
246     elem = document.getElementById(node['id']);
247     node['selected'] = true;
248     elem.classList.remove('cleared_node');
249     elem.classList.remove('disabled_node');
250     elem.classList.add('selected_node');
251     var input = elem.parentNode.getElementsByTagName("input")[0];
252     input.disabled = false;
253     input.value = true;
254 }
255
256 function clear(node) {
257     elem = document.getElementById(node['id']);
258     node['selected'] = false;
259     node['selectable'] = true;
260     elem.classList.add('cleared_node')
261     elem.classList.remove('disabled_node');
262     elem.classList.remove('selected_node');
263     elem.parentNode.getElementsByTagName("input")[0].disabled = true;
264 }
265
266 function disable_node(node) {
267     elem = document.getElementById(node['id']);
268     node['selected'] = false;
269     node['selectable'] = false;
270     elem.classList.remove('cleared_node');
271     elem.classList.add('disabled_node');
272     elem.classList.remove('selected_node');
273     elem.parentNode.getElementsByTagName("input")[0].disabled = true;
274 }
275
276 function processClick(id, multiple){
277     if(!initialized){
278         filter_field_init();
279     }
280     var element = document.getElementById(id);
281     var node = filter_items[id];
282     if(!node['selectable']){
283         return;
284     }
285     if(multiple){
286         return processClickMultipleObject(node);
287     }
288     node['selected'] = !node['selected']; //toggle on click
289
290     if(node['selected']) {
291         select(node);
292     }
293     else {
294         clear(node);
295     }
296     process(node);
297     updateResult(id);
298 }
299
300 function processClickMultipleObject(node){
301     select(node);
302     add_node(node);
303     process(node);
304 }
305
306 function add_node(node){
307     return add_item_prepopulate(node, {});
308 }
309
310 inputs = []
311
312 function restrictchars(input)
313 {
314     if( input.validity.patternMismatch )
315     {
316         input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed");
317         input.reportValidity();
318     }
319
320     input.value = input.value.replace(/([^A-Za-z0-9-_.])+/g, "");
321
322     checkunique(input);
323 }
324
325 function checkunique(tocheck)
326 {
327     val = tocheck.value;
328     for( var i = 0; i < inputs.length; i++ )
329     {
330         if( inputs[i].value == val && inputs[i] != tocheck)
331         {
332             tocheck.setCustomValidity("All hostnames must be unique");
333             tocheck.reportValidity();
334             return;
335         }
336     }
337     tocheck.setCustomValidity("");
338 }
339
340 function add_item_prepopulate(node, prepopulate){
341     inputs = [];
342     var div = document.createElement("DIV");
343     div.class = node['id'];
344     div.id = "dropdown_" + dropdown_count;
345     dropdown_count++;
346     var label = document.createElement("H5");
347     label.appendChild(document.createTextNode(node['name']));
348     div.appendChild(label);
349     button = document.createElement("BUTTON");
350     button.type = "button";
351     button.appendChild(document.createTextNode("Remove"));
352     button.classList.add("btn-danger");
353     button.classList.add("btn");
354     div.appendChild(button);
355     for(var i=0; i<node['forms'].length; i++){
356         form = node['forms'][i];
357         var input = document.createElement("INPUT");
358         input.type = form['type'];
359         input.name = form['name'];
360         input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})";
361         input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"
362         input.placeholder = form['placeholder'];
363         inputs.push(input);
364         input.onchange = function() { updateObjectResult(div); restrictchars(this); };
365         input.oninput = function() { restrictchars(this); };
366         if(form['name'] in prepopulate){
367             input.value = prepopulate[form['name']];
368         }
369         div.appendChild(input);
370     }
371     //add class id to dropdown object
372     var hiddenInput = document.createElement("INPUT");
373     hiddenInput.type = "hidden";
374     hiddenInput.name = "class";
375     hiddenInput.value = node['id'];
376     div.appendChild(hiddenInput);
377     button.onclick = function(){
378         remove_dropdown(div.id);
379     }
380     document.getElementById("dropdown_wrapper").appendChild(div);
381     var linebreak = document.createElement("BR");
382     document.getElementById("dropdown_wrapper").appendChild(linebreak);
383     updateObjectResult(div);
384     return div;
385 }
386
387 function remove_dropdown(id){
388     var div = document.getElementById(id);
389     var parent = div.parentNode;
390     div.parentNode.removeChild(div);
391     //checks if we have removed last item in class
392     var deselect_class = true;
393     var div_inputs = div.getElementsByTagName("input");
394     var div_class = div_inputs[div_inputs.length-1].value;
395     var result_class = document.getElementById(div_class).parentNode.parentNode.id;
396     delete result[result_class][div.id];
397     for(var i=0; i<parent.children.length; i++){
398         var inputs = parent.children[i].getElementsByTagName("input");
399         var object_class = "";
400         for(var k=0; k<inputs.length; k++){
401             if(inputs[k].name == "class"){
402                 object_class = inputs[k].value;
403             }
404         }
405         if(object_class == div_class){
406             deselect_class = false;
407         }
408     }
409     if(deselect_class){
410         clear(filter_items[div_class]);
411     }
412 }
413
414 function updateResult(nodeId){
415     if(!initialized){
416         filter_field_init();
417     }
418     if(!filter_items[nodeId]['multiple']){
419         var node = document.getElementById(nodeId);
420         var value = {}
421         value[nodeId] = node.parentNode.getElementsByTagName("input")[0].value;
422         result[node.parentNode.parentNode.id] = {};
423         result[node.parentNode.parentNode.id][nodeId] = value;
424     }
425 }
426
427 function updateObjectResult(parentElem){
428     node_type = document.getElementById(parentElem.class).parentNode.parentNode.id;
429     input = {};
430     inputs = parentElem.getElementsByTagName("input");
431     for(var i in inputs){
432         var e = inputs[i];
433         input[e.name] = e.value;
434     }
435     result[node_type][parentElem.id] = input;
436 }
437
438 function filter_field_init() {
439     for(nodeId in filter_items) {
440         element = document.getElementById(nodeId);
441         node = filter_items[nodeId];
442         result[element.parentNode.parentNode.id] = {}
443         }
444     initialized = true;
445 }
446
447 </script>