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