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