Merge "Redesigns Multiple Select Filter Widget"
[laas.git] / src / templates / resource / steps / pod_definition.html
1 {% extends "workflow/viewport-element.html" %}
2 {% block extrahead %}
3 <link href="/static/css/graph_common.css" rel="stylesheet">
4 <title>Pod Definition Prototype</title>
5
6 <!-- Loads and initializes the library -->
7 <script>
8     var mxLoadStylesheets = false;
9 </script>
10 <script type="text/javascript" src="/static/js/mxClient.min.js" ></script>
11 <style>
12 p {
13     word-break: normal;
14     white-space: normal;
15 }
16 </style>
17 <script type="text/javascript">
18 var currentWindow;
19 var currentGraph;
20 var netCount = 0;
21 var netColors = ['red', 'blue', 'purple', 'green', 'orange', '#8CCDF5', '#1E9BAC'];
22 var hostCount = 0;
23 var lastHostBottom = 100;
24 var networks = new Set([]);
25 var has_public_net = false;
26
27 function main(graphContainer, overviewContainer, toolbarContainer) {
28     //check if the browser is supported
29     if (!mxClient.isBrowserSupported()) {
30         mxUtils.error('Browser is not supported', 200, false);
31         return null;
32     }
33
34     // Workaround for Internet Explorer ignoring certain styles
35     if (mxClient.IS_QUIRKS) {
36         document.body.style.overflow = 'hidden';
37         new mxDivResizer(graphContainer);
38     }
39     var editor = new mxEditor();
40     var graph = editor.graph;
41     var model = graph.getModel();
42     editor.setGraphContainer(graphContainer);
43
44     doGlobalConfig(graph);
45     currentGraph = graph;
46
47     {% if xml %}
48         restoreFromXml('{{xml|safe}}', editor);
49     {% elif hosts %}
50         {% for host in hosts %}
51         var host = {{host|safe}};
52         makeHost(host);
53         {% endfor %}
54     {% endif %}
55     {% if added_hosts %}
56         {% for host in added_hosts %}
57         var host = {{host|safe}}
58         makeHost(host);
59         {% endfor %}
60         updateHosts([]);
61     {% endif %}
62
63     addToolbarButton(editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true);
64     addToolbarButton(editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true);
65
66     {% if debug %}
67     editor.addAction('printXML', function(editor, cell) {
68         mxLog.write(encodeGraph(graph));
69         mxLog.show();
70     });
71     addToolbarButton(editor, toolbarContainer, 'printXML', '', '/static/img/mxgraph/fit_to_size.png', true);
72     {% endif %}
73
74     var outline = new mxOutline(graph, overviewContainer);
75
76     var checkAllowed = function(edge, terminal, source) {
77         //check if other terminal is null, and that they are different
78         otherTerminal = edge.getTerminal(!source);
79         if(terminal != null && otherTerminal != null) {
80             if( terminal.getParent().getId().split('_')[0] == //'host' or 'network'
81                 otherTerminal.getParent().getId().split('_')[0] ) {
82                     //not allowed
83                     graph.removeCells([edge]);
84                     return false;
85                 }
86         }
87         return true;
88     };
89
90     var colorEdge = function(edge, terminal, source) {
91         if(terminal.getParent().getId().indexOf('network') >= 0) {
92             styles = terminal.getParent().getStyle().split(';');
93             color = 'black';
94             for(var i=0; i<styles.length; i++){
95                 kvp = styles[i].split('=');
96                 if(kvp[0] == "fillColor"){
97                     color = kvp[1];
98                 }
99             }
100             edge.setStyle('strokeColor=' + color);
101         }
102     };
103
104     var alertVlan = function(edge, terminal, source) {
105         if( terminal == null || edge.getTerminal(!source) == null) {
106             return;
107         }
108         var vlanHTML = '<form> <input type="radio" name="tagged" value="True" checked> Tagged<br>'
109         vlanHTML += '<input type="radio" name="tagged" value="False"> Untagged </form>'
110         vlanHTML += '<button onclick=parseVlanWindow(' + edge.getId() + ');>Okay</button>'
111         vlanHTML += '<button onclick=deleteVlanWindow(' + edge.getId() + ');>Cancel</button>'
112         content = document.createElement('div');
113         content.innerHTML = vlanHTML;
114         showWindow(graph, "Vlan Selection", content, 200, 200);
115     }
116
117     //sets the edge color to be the same as the network
118     graph.addListener(mxEvent.CELL_CONNECTED, function(sender, event){
119         edge = event.getProperty('edge');
120         terminal = event.getProperty('terminal')
121         source = event.getProperty('source');
122         if(checkAllowed(edge, terminal, source)) {
123             colorEdge(edge, terminal, source);
124             alertVlan(edge, terminal, source);
125         }
126     });
127
128     createDeleteDialog = function(id) {
129         var content = document.createElement('div');
130         var innerHTML = "<button style='width: 46%;' onclick=deleteCell('" + id + "');>Remove</button>"
131         innerHTML += "<button style='width: 46%;' onclick='currentWindow.destroy();'>Cancel</button>"
132         content.innerHTML = innerHTML;
133         showWindow(currentGraph, 'Do you want to delete this network?', content, 200, 62);
134     }
135
136     graph.dblClick = function(evt, cell) {
137
138         if( cell != null ){
139             if( cell.getParent() != null && cell.getParent().getId().indexOf("network") > -1) {
140                 cell = cell.getParent();
141             }
142             if( cell.isEdge() || cell.getId().indexOf("network") > -1 ) {
143                 createDeleteDialog(cell.getId());
144             }
145             else {
146             showDetailWindow(cell);
147            }
148         }
149     };
150
151     updateHosts({{ removed_hosts|default:"[]"|safe }});
152     if(!has_public_net){
153         addPublicNetwork();
154     }
155 }
156
157 function showDetailWindow(cell) {
158     var info = JSON.parse(cell.getValue());
159     var content = document.createElement("div");
160     var inner = "<pre>Name: " + info.name + "\n";
161     inner += "Description:\n" + info.description + "</pre>";
162     inner += '<button onclick="currentWindow.destroy();">Okay</button>'
163     content.innerHTML = inner
164     showWindow(currentGraph, 'Details', content, 400, 400);
165 }
166
167 function restoreFromXml(xml, editor) {
168     var doc = mxUtils.parseXml(xml);
169     var node = doc.documentElement;
170     editor.readGraphModel(node);
171
172     //Iterate over all children, and parse the networks to add them to the sidebar
173     var root = currentGraph.getDefaultParent();
174     for( var i=0; i<root.getChildCount(); i++) {
175         var cell = root.getChildAt(i);
176         if(cell.getId().indexOf("network") > -1) {
177             var info = JSON.parse(cell.getValue());
178             var name = info['name'];
179             networks.add(name);
180             var styles = cell.getStyle().split(";");
181             var color = null;
182             for(var j=0; j< styles.length; j++){
183                 var kvp = styles[j].split('=');
184                 if(kvp[0] == "fillColor") {
185                     color = kvp[1];
186                     break;
187                 }
188             }
189             if(info.public){
190                 has_public_net = true;
191             }
192             netCount++;
193             makeSidebarNetwork(name, color, cell.getId());
194         }
195     }
196 }
197
198 function deleteCell(cellId) {
199     var cell = currentGraph.getModel().getCell(cellId);
200     if( cellId.indexOf("network") > -1 ) {
201         elem = document.getElementById(cellId);
202         elem.parentElement.removeChild(elem);
203     }
204     currentGraph.removeCells([cell]);
205     currentWindow.destroy();
206 }
207
208 function newNetworkWindow() {
209     var innerHtml = 'Name: <input type="text" name="net_name" maxlength="100" id="net_name_input" style="margin:5px;"><br>';
210     innerHtml += '<button style="width: 46%;" onclick="parseNetworkWindow()">Okay</button>';
211     innerHtml += '<button style="width: 46%;" onclick="currentWindow.destroy();">Cancel</button><br>';
212     innerHtml += '<div id="current_window_errors"/>';
213     var content = document.createElement("div");
214     content.innerHTML = innerHtml;
215
216     showWindow(currentGraph, "Network Creation", content, 300, 300);
217 }
218
219 function parseNetworkWindow() {
220     var net_name = document.getElementById("net_name_input").value
221     var error_div = document.getElementById("current_window_errors");
222     if( networks.has(net_name) ){
223         error_div.innerHTML = "All network names must be unique";
224         return;
225     }
226     addNetwork(net_name);
227     currentWindow.destroy();
228 }
229
230 function addToolbarButton(editor, toolbar, action, label, image, isTransparent)
231 {
232     var button = document.createElement('button');
233     button.style.fontSize = '10';
234     if (image != null)
235     {
236         var img = document.createElement('img');
237         img.setAttribute('src', image);
238         img.style.width = '16px';
239         img.style.height = '16px';
240         img.style.verticalAlign = 'middle';
241         img.style.marginRight = '2px';
242         button.appendChild(img);
243     }
244     if (isTransparent)
245     {
246         button.style.background = 'transparent';
247         button.style.color = '#FFFFFF';
248         button.style.border = 'none';
249     }
250     mxEvent.addListener(button, 'click', function(evt)
251     {
252         editor.execute(action);
253     });
254     mxUtils.write(button, label);
255     toolbar.appendChild(button);
256 };
257
258 function encodeGraph(graph) {
259     var encoder = new mxCodec();
260     var xml = encoder.encode(graph.getModel());
261     return mxUtils.getXml(xml);
262 }
263
264 function doGlobalConfig(graph) {
265     //general graph stuff
266     graph.setMultigraph(false);
267     graph.setCellsSelectable(false);
268     graph.setCellsMovable(false);
269
270     //testing
271     graph.vertexLabelIsMovable = true;
272
273
274     //edge behavior
275     graph.setConnectable(true);
276     graph.setAllowDanglingEdges(false);
277     mxEdgeHandler.prototype.snapToTerminals = true;
278     mxConstants.MIN_HOTSPOT_SIZE = 16;
279     mxConstants.DEFAULT_HOTSPOT = 1;
280     //edge 'style' (still affects behavior greatly)
281     style = graph.getStylesheet().getDefaultEdgeStyle();
282     style[mxConstants.STYLE_EDGE] = mxConstants.EDGESTYLE_ELBOW;
283     style[mxConstants.STYLE_ENDARROW] = mxConstants.NONE;
284     style[mxConstants.STYLE_ROUNDED] = true;
285     style[mxConstants.STYLE_FONTCOLOR] = 'black';
286     style[mxConstants.STYLE_STROKECOLOR] = 'red';
287
288     style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = '#FFFFFF';
289     style[mxConstants.STYLE_STROKEWIDTH] = '3';
290     style[mxConstants.STYLE_ROUNDED] = true;
291     style[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
292
293     hostStyle = graph.getStylesheet().getDefaultVertexStyle();
294     hostStyle[mxConstants.STYLE_ROUNDED] = 1;
295
296     // TODO: Proper override
297     graph.convertValueToString = function(cell) {
298         try{
299             //changes value for edges with xml value
300             if(cell.isEdge()) {
301                 if(JSON.parse(cell.getValue())["tagged"]) {
302                     return "tagged";
303                 }
304                 return "untagged";
305             } else{
306                     return JSON.parse(cell.getValue())['name'];
307             }
308         }
309         catch(e){
310                 return cell.getValue();
311         }
312     };
313 }
314
315 function showWindow(graph, title, content, width, height) {
316     //create transparent black background
317     var background = document.createElement('div');
318     background.style.position = 'absolute';
319     background.style.left = '0px';
320     background.style.top = '0px';
321     background.style.right = '0px';
322     background.style.bottom = '0px';
323     background.style.background = 'black';
324     mxUtils.setOpacity(background, 50);
325     document.body.appendChild(background);
326
327     //deal with IE quirk
328     if (mxClient.IS_IE) {
329         new mxDivResizer(background);
330     }
331
332     var x = Math.max(0, document.body.scrollWidth/2-width/2);
333     var y = Math.max(10, (document.body.scrollHeight ||
334                 document.documentElement.scrollHeight)/2-height*2/3);
335
336     var wnd = new mxWindow(title, content, x, y, width, height, false, true);
337     wnd.setClosable(false);
338
339     wnd.addListener(mxEvent.DESTROY, function(evt) {
340         graph.setEnabled(true);
341         mxEffects.fadeOut(background, 50, true, 10, 30, true);
342     });
343     currentWindow = wnd;
344
345     graph.setEnabled(false);
346     wnd.setVisible(true);
347 };
348
349 function closeWindow() {
350     //allows the current window to be destroyed
351     currentWindow.destroy();
352 };
353
354 function othersUntagged(edgeID) {
355     var edge = currentGraph.getModel().getCell(edgeID);
356     var end1 = edge.getTerminal(true);
357     var end2 = edge.getTerminal(false);
358
359     if( end1.getParent().getId().split('_')[0] == 'host' ){
360         var netint = end1;
361     } else {
362         var netint = end2;
363     }
364
365     var edges = netint.edges;
366
367     for( var i=0; i < edges.length; i++ ) {
368         if( edges[i].getValue() ) {
369             var tagged = JSON.parse(edges[i].getValue()).tagged;
370         } else {
371             var tagged = true;
372         }
373         if( !tagged ) {
374             return true;
375         }
376     }
377     return false;
378 };
379
380
381 function deleteVlanWindow(edgeID) {
382     var cell = currentGraph.getModel().getCell(edgeID);
383     currentGraph.removeCells([cell]);
384     currentWindow.destroy();
385 }
386
387 function parseVlanWindow(edgeID) {
388     //do parsing and data manipulation
389     var radios = document.getElementsByName("tagged");
390     edge = currentGraph.getModel().getCell(edgeID);
391
392     for(var i=0; i<radios.length; i++) {
393         if(radios[i].checked) {
394             //set edge to be tagged or untagged
395             //cellValue.setAttribute("tagged", radios[i].value);
396             if( radios[i].value == "False")
397             {
398                 if( othersUntagged(edgeID) )
399                 {
400                     alert("Only one untagged VLAN is allowed per interface");
401                     return;
402                 }
403             }
404             edgeVal = Object();
405             edgeVal['tagged'] = radios[i].value == "True";
406             edge.setValue(JSON.stringify(edgeVal));
407             break;
408         }
409     }
410     currentGraph.refresh(edge);
411     closeWindow();
412 }
413
414 function makeMxNetwork(net_name, public = false) {
415     model = currentGraph.getModel();
416     width = 10;
417     height = 1700;
418     xoff = 400 + (30 * netCount);
419     yoff = -10;
420     var color = netColors[netCount];
421     if( netCount > (netColors.length - 1)) {
422         color = Math.floor(Math.random() * 16777215); //int in possible color space
423         color = '#' + color.toString(16).toUpperCase(); //convert to hex
424         //alert(color);
425     }
426     var net_val = Object();
427     net_val['name'] = net_name;
428     net_val['public'] = public;
429     net = currentGraph.insertVertex(
430         currentGraph.getDefaultParent(),
431         'network_' + netCount,
432         JSON.stringify(net_val),
433         xoff,
434         yoff,
435         width,
436         height,
437         'fillColor=' + color,
438         false
439     );
440     var num_ports = 45;
441     for(var i=0; i<num_ports; i++){
442         port = currentGraph.insertVertex(
443             net,
444             null,
445             '',
446             0,
447             (1/num_ports) * i,
448             10,
449             height / num_ports,
450             'fillColor=black;opacity=0',
451             true
452         );
453     }
454
455     var retVal = Object();
456     retVal['color'] = color;
457     retVal['element_id'] = "network_" + netCount;
458
459     networks.add(net_name);
460
461     netCount++;
462     return retVal;
463 }
464
465 function addPublicNetwork() {
466     var net = makeMxNetwork("public", true);
467     makeSidebarNetwork("public", net['color'], net['element_id']);
468     has_public_net = true;
469 }
470
471 function addNetwork(net_name) {
472     var ret = makeMxNetwork(net_name);
473     var color = ret['color'];
474     var net_id = ret['element_id'];
475     makeSidebarNetwork(net_name, color, net_id);
476 }
477
478 function updateHosts(removed) {
479     for(var i=0; i < removed.length; i++)
480     {
481         var hoststring = removed[i];
482         var hostid = "host_" + hoststring.split("*")[0];
483         var cell = currentGraph.getModel().getCell(hostid);
484         currentGraph.removeCells([cell]);
485     }
486
487     var hosts = currentGraph.getChildVertices(currentGraph.getDefaultParent());
488     var topdist = 100;
489     for(var i=0; i<hosts.length; i++) {
490         var host = hosts[i];
491         if(!host.id.startsWith("host_"))
492         {
493             continue;
494         }
495         var geometry = host.getGeometry();
496         geometry.y = topdist + 50;
497         topdist = geometry.y + geometry.height;
498         host.setGeometry(geometry);
499     }
500 }
501
502 function makeSidebarNetwork(net_name, color, net_id){
503     var newNet = document.createElement("li");
504     var colorBlob = document.createElement("div");
505     colorBlob.className = "colorblob";
506     var textContainer = document.createElement("p");
507     textContainer.className = "network_innertext";
508     newNet.id = net_id;
509     var deletebutton = document.createElement("button");
510     deletebutton.className = "btn btn-danger";
511     deletebutton.style = "float: right; height: 20px; line-height: 8px; vertical-align: middle; width: 20px; padding-left: 5px;";
512     deleteButtonText = document.createTextNode("X");
513     deletebutton.appendChild(deleteButtonText);
514     deletebutton.addEventListener("click", function() {
515             createDeleteDialog(net_id);
516     }, false);
517     var text = net_name;
518     var newNetValue = document.createTextNode(text);
519     textContainer.appendChild(newNetValue);
520     colorBlob.style['background'] = color;
521     newNet.appendChild(colorBlob);
522     newNet.appendChild(textContainer);
523     if( net_name != "public" )
524     {
525         newNet.appendChild(deletebutton);
526     }
527     document.getElementById("network_list").appendChild(newNet);
528 }
529
530 function makeHost(hostInfo) {
531     value = JSON.stringify(hostInfo['value']);
532     interfaces = hostInfo['interfaces'];
533     graph = currentGraph;
534     width = 100;
535     height = (25 * interfaces.length) + 25;
536     xoff = 75;
537     yoff = lastHostBottom + 50;
538     lastHostBottom = yoff + height;
539     host = graph.insertVertex(
540         graph.getDefaultParent(),
541         'host_' + hostInfo['id'],
542         value,
543         xoff,
544         yoff,
545         width,
546         height,
547         'editable=0',
548         false
549     );
550     host.getGeometry().offset = new mxPoint(-50,0);
551     host.setConnectable(false);
552     hostCount++;
553
554     for(var i=0; i<interfaces.length; i++) {
555         port = graph.insertVertex(
556             host,
557             null,
558             JSON.stringify(interfaces[i]),
559             90,
560             (i * 25) + 12,
561             20,
562             20,
563             'fillColor=blue;editable=0',
564             false
565         );
566         port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0);
567         currentGraph.refresh(port);
568     }
569     currentGraph.refresh(host);
570 }
571
572 function submitForm() {
573     var form = document.getElementById("xml_form");
574     var input_elem = document.getElementById("hidden_xml_input");
575     var s = encodeGraph(currentGraph);
576     input_elem.value = s;
577     req = new XMLHttpRequest();
578     req.open("POST", "/wf/workflow/", false);
579     req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
580     req.onerror = function() { alert("problem with form submission"); }
581     var formData = $("#xml_form").serialize();
582     req.send(formData);
583 }
584 </script>
585 {% endblock extrahead %}
586
587 <!-- Calls the main function after the page has loaded. Container is dynamically created. -->
588 {% block content %}
589     <div id="graphParent"
590          style="position:absolute;overflow:hidden;top:0px;bottom:0px;width:75%;left:0px;">
591         <div id="graphContainer"
592             style="position:relative;overflow:hidden;top:36px;bottom:0px;left:0px;right:0px;background-image:url('/static/img/mxgraph/grid.gif');cursor:default;">
593         </div>
594
595
596         <!-- Creates a container for the sidebar -->
597         <div id="toolbarContainer"
598             style="position:absolute;white-space:nowrap;overflow:hidden;top:0px;left:0px;right:0px;padding:6px;">
599         </div>
600
601         <!-- Creates a container for the outline -->
602         <div id="outlineContainer"
603             style="position:absolute;overflow:hidden;top:36px;right:0px;width:200px;height:140px;background:transparent;border-style:solid;border-color:black;">
604         </div>
605     </div>
606
607     <style>
608         #network_select {
609                 background: inherit;
610                 padding: 0px;
611                 padding-top: 0px;
612         }
613         #toolbarContainer {
614                 background: #DDDDDD;
615                 height: 36px;
616         }
617         #toolbar_extension {
618                 height: 36px;
619                 background: #DDDDDD;
620         }
621         #btn_add_network {
622                 width: 100%;
623         }
624         #vlan_notice {
625                 margin: 20px;
626         }
627         #network_list li {
628                 border-radius: 2px;
629                 margin: 5px;
630                 padding: 5px;
631                 vertical-align: middle;
632                 background: #DDDDDD;
633         }
634         #network_list {
635                 list-style-type: none;
636                 padding: 0;
637         }
638         .colorblob {
639                 width: 20px;
640                 height: 20px;
641                 border-radius: 50%;
642                 display: inline-block;
643                 vertical-align: middle;
644         }
645         .network_innertext {
646                 display: inline-block;
647                 padding-left: 10px;
648                 vertical-align: middle;
649                 padding-bottom: 0px;
650                 margin-bottom: 2px;
651         }
652         .mxWindow {
653                 background: #FFFFFF;
654         }
655     </style>
656
657     <div id="network_select" style="position:absolute;top:0px;bottom:0px;width:25%;right:0px;left:auto;">
658         <div id="toolbar_extension">
659         <button id="btn_add_network" type="button" class="btn btn-primary" onclick="newNetworkWindow();">Add Network</button>
660         </div>
661         <ul id="network_list">
662         </ul>
663         <button type="button" style="display: none" onclick="submitForm();">Submit</button>
664     </div>
665     <form id="xml_form" method="post" action="/wf/workflow/">
666         {% csrf_token %}
667         <input type="hidden" id="hidden_xml_input" name="xml" />
668     </form>
669
670 <script>
671     main(
672         document.getElementById('graphContainer'),
673         document.getElementById('outlineContainer'),
674         document.getElementById('toolbarContainer'),
675         document.getElementById('sidebarContainer')
676     )
677 </script>
678 {% endblock content %}
679 {% block onleave %}
680 submitForm();
681 {% endblock %}