1 {% extends "workflow/viewport-element.html" %}
3 <link href="/static/css/graph_common.css" rel="stylesheet">
4 <title>Pod Definition Prototype</title>
6 <!-- Loads and initializes the library -->
8 var mxLoadStylesheets = false;
10 <script type="text/javascript" src="/static/js/mxClient.min.js" ></script>
17 <script type="text/javascript">
21 var netColors = ['red', 'blue', 'purple', 'green', 'orange', '#8CCDF5', '#1E9BAC'];
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'}};
30 function main(graphContainer, overviewContainer, toolbarContainer) {
32 for(var i=0; i<vlans.length-1; i++){
33 vlan_string += vlans[i] + ", ";
36 vlan_string += vlans[vlans.length-1];
39 var str = "Available vlans for your POD: " + vlan_string;
40 document.getElementById("vlan_notice").innerHTML = str;
42 //check if the browser is supported
43 if (!mxClient.isBrowserSupported()) {
44 mxUtils.error('Browser is not supported', 200, false);
48 // Workaround for Internet Explorer ignoring certain styles
49 if (mxClient.IS_QUIRKS) {
50 document.body.style.overflow = 'hidden';
51 new mxDivResizer(graphContainer);
53 var editor = new mxEditor();
54 var graph = editor.graph;
55 var model = graph.getModel();
56 editor.setGraphContainer(graphContainer);
59 editor.addAction('printXML', function(editor, cell) {
60 mxLog.write(encodeGraph(graph));
66 doGlobalConfig(graph);
70 restoreFromXml('{{xml|safe}}', editor);
72 {% for host in hosts %}
74 var host = {{host|safe}};
79 {% for host in added_hosts %}
80 var host = {{host|safe}}
86 addToolbarButton(editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true);
87 addToolbarButton(editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true);
90 addToolbarButton(editor, toolbarContainer, 'printXML', '', '/static/img/mxgraph/fit_to_size.png', true);
93 var outline = new mxOutline(graph, overviewContainer);
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] ) {
103 graph.removeCells([edge]);
110 var colorEdge = function(edge, terminal, source) {
111 if(terminal.getParent().getId().indexOf('network') >= 0) {
112 styles = terminal.getParent().getStyle().split(';');
114 for(var i=0; i<styles.length; i++){
115 kvp = styles[i].split('=');
116 if(kvp[0] == "fillColor"){
120 edge.setStyle('strokeColor=' + color);
124 var alertVlan = function(edge, terminal, source) {
125 if( terminal == null || edge.getTerminal(!source) == null) {
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);
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);
148 createDeleteDialog = function(id)
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);
156 graph.dblClick = function(evt, cell) {
159 if( cell.getParent() != null && cell.getParent().getId().indexOf("network") > -1) {
160 cell = cell.getParent();
162 if( cell.isEdge() || cell.getId().indexOf("network") > -1 ) {
163 createDeleteDialog(cell.getId());
166 showDetailWindow(cell);
170 graph.setCellsSelectable(false);
171 graph.setCellsMovable(false);
173 updateHosts({{ removed_hosts|default:"[]"|safe }});
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);
189 function restoreFromXml(xml, editor) {
190 var doc = mxUtils.parseXml(xml);
191 var node = doc.documentElement;
192 editor.readGraphModel(node);
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(";");
206 for(var j=0; j< styles.length; j++){
207 var kvp = styles[j].split('=');
208 if(kvp[0] == "fillColor") {
215 has_public_net = true;
218 makeSidebarNetwork(name, vlan_id, color, cell.getId());
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);
229 currentGraph.removeCells([cell]);
230 currentWindow.destroy();
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;
244 showWindow(currentGraph, "Network Creation", content, 300, 300);
247 vlan_notice = document.getElementById("current_window_vlans");
248 vlan_notice.appendChild(document.createTextNode("Available Vlans: " + vlan_string));
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)
258 vlan_valid = vlan_valid & vlans.indexOf(Number(vlan_id)) >= 0;
262 error_div.innerHTML = "Please only enter an integer in the valid range (default 1-4095) for the VLAN ID";
265 if( networks.has(vlan_id))
267 error_div.innerHTML = "All VLAN IDs must be unique";
270 if( network_names.has(net_name) ){
271 error_div.innerHTML = "All network names must be unique";
274 addNetwork(net_name, vlan_id);
275 currentWindow.destroy();
278 function addToolbarButton(editor, toolbar, action, label, image, isTransparent)
280 var button = document.createElement('button');
281 button.style.fontSize = '10';
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);
294 button.style.background = 'transparent';
295 button.style.color = '#FFFFFF';
296 button.style.border = 'none';
298 mxEvent.addListener(button, 'click', function(evt)
300 editor.execute(action);
302 mxUtils.write(button, label);
303 toolbar.appendChild(button);
306 function encodeGraph(graph) {
307 var encoder = new mxCodec();
308 var xml = encoder.encode(graph.getModel());
309 return mxUtils.getXml(xml);
312 function doGlobalConfig(graph) {
313 //general graph stuff
314 graph.setMultigraph(false);
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';
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;
335 // TODO: Proper override
336 graph.convertValueToString = function(cell) {
338 //changes value for edges with xml value
340 if(JSON.parse(cell.getValue())["tagged"]) {
345 return JSON.parse(cell.getValue())['name'];
349 return cell.getValue();
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);
367 if (mxClient.IS_IE) {
368 new mxDivResizer(background);
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);
375 var wnd = new mxWindow(title, content, x, y, width, height, false, true);
376 wnd.setClosable(false);
378 wnd.addListener(mxEvent.DESTROY, function(evt) {
379 graph.setEnabled(true);
380 mxEffects.fadeOut(background, 50, true, 10, 30, true);
384 graph.setEnabled(false);
385 wnd.setVisible(true);
388 function closeWindow() {
389 //allows the current window to be destroyed
390 currentWindow.destroy();
393 function othersUntagged(edgeID) {
394 var edge = currentGraph.getModel().getCell(edgeID);
395 var end1 = edge.getTerminal(true);
396 var end2 = edge.getTerminal(false);
398 if( end1.getParent().getId().split('_')[0] == 'host' )
407 var edges = netint.edges;
409 for( var i=0; i < edges.length; i++ )
411 if( edges[i].getValue() )
413 var tagged = JSON.parse(edges[i].getValue()).tagged;
428 function deleteVlanWindow(edgeID) {
429 var cell = currentGraph.getModel().getCell(edgeID);
430 currentGraph.removeCells([cell]);
431 currentWindow.destroy();
434 function parseVlanWindow(edgeID) {
435 //do parsing and data manipulation
436 var radios = document.getElementsByName("tagged");
437 edge = currentGraph.getModel().getCell(edgeID);
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")
445 if( othersUntagged(edgeID) )
447 alert("Only one untagged VLAN is allowed per interface");
452 edgeVal['tagged'] = radios[i].value == "True";
453 edge.setValue(JSON.stringify(edgeVal));
457 //edge.setValue(cellValue);
458 currentGraph.refresh(edge);
462 function makeMxNetwork(vlan_id, net_name) {
463 model = currentGraph.getModel();
466 xoff = 400 + (30 * netCount);
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
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),
486 'fillColor=' + color,
490 for(var i=0; i<num_ports; i++){
491 port = currentGraph.insertVertex(
499 'fillColor=black;opacity=0',
504 var retVal = Object();
505 retVal['color'] = color;
506 retVal['element_id'] = "network_" + netCount;
512 function addPublicNetwork() {
513 var net = makeMxNetwork(-1, "public");
514 network_names.add("public");
515 makeSidebarNetwork("public", "", net['color'], net['element_id']);
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);
527 function updateHosts(removed) {
528 for(var i=0; i < removed.length; i++)
530 var hoststring = removed[i];
531 var hostid = "host_" + hoststring.split("*")[0];
532 var cell = currentGraph.getModel().getCell(hostid);
533 currentGraph.removeCells([cell]);
536 var hosts = currentGraph.getChildVertices(currentGraph.getDefaultParent());
538 for(var i=0; i<hosts.length; i++)
541 if(!host.id.startsWith("host_"))
545 var geometry = host.getGeometry();
546 geometry.y = topdist + 50;
547 topdist = geometry.y + geometry.height;
548 host.setGeometry(geometry);
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";
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);
569 text += " : " + vlan_id;
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" )
578 newNet.appendChild(deletebutton);
580 document.getElementById("network_list").appendChild(newNet);
583 function makeHost(hostInfo) {
584 value = JSON.stringify(hostInfo['value']);
585 interfaces = hostInfo['interfaces'];
586 graph = currentGraph;
588 height = (25 * interfaces.length) + 10;
590 yoff = lastHostBottom + 50;
591 lastHostBottom = yoff + height;
592 host = graph.insertVertex(
593 graph.getDefaultParent(),
594 'host_' + hostInfo['id'],
603 host.setConnectable(false);
606 for(var i=0; i<interfaces.length; i++) {
607 port = graph.insertVertex(
610 JSON.stringify(interfaces[i]),
615 'fillColor=blue;editable=0',
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();
634 {% endblock extrahead %}
636 <!-- Calls the main function after the page has loaded. Container is dynamically created. -->
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;">
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;">
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;">
680 vertical-align: middle;
684 list-style-type: none;
691 display: inline-block;
692 vertical-align: middle;
695 display: inline-block;
697 vertical-align: middle;
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>
710 <ul id="network_list">
712 <p id="vlan_notice"></p>
713 <button type="button" style="display: none" onclick="submitForm();">Submit</button>
715 <form id="xml_form" method="post" action="/wf/workflow/">
717 <input type="hidden" id="hidden_xml_input" name="xml" />
722 document.getElementById('graphContainer'),
723 document.getElementById('outlineContainer'),
724 document.getElementById('toolbarContainer'),
725 document.getElementById('sidebarContainer')
728 {% endblock content %}