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