diff options
-rw-r--r-- | src/static/js/dashboard.js | 588 | ||||
-rw-r--r-- | src/templates/resource/steps/pod_definition.html | 617 | ||||
-rw-r--r-- | src/workflow/resource_bundle_workflow.py | 90 |
3 files changed, 670 insertions, 625 deletions
diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index 66e95d0..e51a219 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -242,3 +242,591 @@ class MultipleSelectFilterWidget { document.getElementById("filter_field").value = JSON.stringify(this.result); } } + +class NetworkStep { + constructor(debug, xml, hosts, added_hosts, removed_host_ids, graphContainer, overviewContainer, toolbarContainer){ + if(!this.check_support()) + return; + + this.currentWindow = null; + this.netCount = 0; + this.netColors = ['red', 'blue', 'purple', 'green', 'orange', '#8CCDF5', '#1E9BAC']; + this.hostCount = 0; + this.lastHostBottom = 100; + this.networks = new Set(); + this.has_public_net = false; + this.debug = debug; + this.editor = new mxEditor(); + this.graph = this.editor.graph; + + this.editor.setGraphContainer(graphContainer); + this.doGlobalConfig(); + this.prefill(xml, hosts, added_hosts, removed_host_ids); + this.addToolbarButton(this.editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true); + this.addToolbarButton(this.editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true); + + if(this.debug){ + this.editor.addAction('printXML', function(editor, cell) { + mxLog.write(this.encodeGraph()); + mxLog.show(); + }.bind(this)); + this.addToolbarButton(this.editor, toolbarContainer, 'printXML', '', '/static/img/mxgraph/fit_to_size.png', true); + } + + new mxOutline(this.graph, overviewContainer); + //sets the edge color to be the same as the network + this.graph.addListener(mxEvent.CELL_CONNECTED, function(sender, event) {this.cellConnectionHandler(sender, event)}.bind(this)); + //hooks up double click functionality + this.graph.dblClick = function(evt, cell) {this.doubleClickHandler(evt, cell);}.bind(this); + + if(!this.has_public_net){ + this.addPublicNetwork(); + } + } + + check_support(){ + if (!mxClient.isBrowserSupported()) { + mxUtils.error('Browser is not supported', 200, false); + return false; + } + return true; + } + + prefill(xml, hosts, added_hosts, removed_host_ids){ + //populate existing data + if(xml){ + this.restoreFromXml(xml, this.editor); + } else if(hosts){ + for(const host of hosts) + this.makeHost(host); + } + + //apply any changes + if(added_hosts){ + for(const host of added_hosts) + this.makeHost(host); + this.updateHosts([]); //TODO: why? + } + this.updateHosts(removed_host_ids); + } + + cellConnectionHandler(sender, event){ + const edge = event.getProperty('edge'); + const terminal = event.getProperty('terminal') + const source = event.getProperty('source'); + if(this.checkAllowed(edge, terminal, source)) { + this.colorEdge(edge, terminal, source); + this.alertVlan(edge, terminal, source); + } + } + + doubleClickHandler(evt, cell) { + if( cell != null ){ + if( cell.getParent() != null && cell.getParent().getId().indexOf("network") > -1) { + cell = cell.getParent(); + } + if( cell.isEdge() || cell.getId().indexOf("network") > -1 ) { + this.createDeleteDialog(cell.getId()); + } + else { + this.showDetailWindow(cell); + } + } + } + + alertVlan(edge, terminal, source) { + if( terminal == null || edge.getTerminal(!source) == null) { + return; + } + const form = document.createElement("form"); + const tagged = document.createElement("input"); + tagged.type = "radio"; + tagged.name = "tagged"; + tagged.value = "True"; + form.appendChild(tagged); + form.appendChild(document.createTextNode(" Tagged")); + form.appendChild(document.createElement("br")); + + const untagged = document.createElement("input"); + untagged.type = "radio"; + untagged.name = "tagged"; + untagged.value = "False"; + form.appendChild(untagged); + form.appendChild(document.createTextNode(" Untagged")); + form.appendChild(document.createElement("br")); + + const yes_button = document.createElement("button"); + yes_button.onclick = function() {this.parseVlanWindow(edge.getId());}.bind(this); + yes_button.appendChild(document.createTextNode("Okay")); + + const cancel_button = document.createElement("button"); + cancel_button.onclick = function() {this.deleteVlanWindow(edge.getId());}.bind(this); + cancel_button.appendChild(document.createTextNode("Cancel")); + + const error_div = document.createElement("div"); + error_div.id = "current_window_errors"; + form.appendChild(error_div); + + const content = document.createElement('div'); + content.appendChild(form); + content.appendChild(yes_button); + content.appendChild(cancel_button); + this.showWindow("Vlan Selection", content, 200, 200); + } + + createDeleteDialog(id) { + const content = document.createElement('div'); + const remove_button = document.createElement("button"); + remove_button.style.width = '46%'; + remove_button.onclick = function() { this.deleteCell(id);}.bind(this); + remove_button.appendChild(document.createTextNode("Remove")); + const cancel_button = document.createElement("button"); + cancel_button.style.width = '46%'; + cancel_button.onclick = function() { this.closeWindow();}.bind(this); + cancel_button.appendChild(document.createTextNode("Cancel")); + + content.appendChild(remove_button); + content.appendChild(cancel_button); + this.showWindow('Do you want to delete this network?', content, 200, 62); + } + + checkAllowed(edge, terminal, source) { + //check if other terminal is null, and that they are different + const otherTerminal = edge.getTerminal(!source); + if(terminal != null && otherTerminal != null) { + if( terminal.getParent().getId().split('_')[0] == //'host' or 'network' + otherTerminal.getParent().getId().split('_')[0] ) { + //not allowed + this.graph.removeCells([edge]); + return false; + } + } + return true; + } + + colorEdge(edge, terminal, source) { + if(terminal.getParent().getId().indexOf('network') >= 0) { + const styles = terminal.getParent().getStyle().split(';'); + let color = 'black'; + for(let style of styles){ + const kvp = style.split('='); + if(kvp[0] == "fillColor"){ + color = kvp[1]; + } + } + edge.setStyle('strokeColor=' + color); + } + } + + showDetailWindow(cell) { + const info = JSON.parse(cell.getValue()); + const content = document.createElement("div"); + const pre_tag = document.createElement("pre"); + pre_tag.appendChild(document.createTextNode("Name: " + info.name + "\nDescription:\n" + info.description)); + const ok_button = document.createElement("button"); + ok_button.onclick = function() { this.closeWindow();}; + content.appendChild(pre_tag); + content.appendChild(ok_button); + this.showWindow('Details', content, 400, 400); + } + + restoreFromXml(xml, editor) { + const doc = mxUtils.parseXml(xml); + const node = doc.documentElement; + editor.readGraphModel(node); + + //Iterate over all children, and parse the networks to add them to the sidebar + for( const cell of this.graph.getModel().getChildren(this.graph.getDefaultParent())) { + if(cell.getId().indexOf("network") > -1) { + const info = JSON.parse(cell.getValue()); + const name = info['name']; + this.networks.add(name); + const styles = cell.getStyle().split(";"); + let color = null; + for(const style of styles){ + const kvp = style.split('='); + if(kvp[0] == "fillColor") { + color = kvp[1]; + break; + } + } + if(info.public){ + this.has_public_net = true; + } + this.netCount++; + this.makeSidebarNetwork(name, color, cell.getId()); + } + } + } + + deleteCell(cellId) { + var cell = this.graph.getModel().getCell(cellId); + if( cellId.indexOf("network") > -1 ) { + let elem = document.getElementById(cellId); + elem.parentElement.removeChild(elem); + } + this.graph.removeCells([cell]); + this.currentWindow.destroy(); + } + + newNetworkWindow() { + const input = document.createElement("input"); + input.type = "text"; + input.name = "net_name"; + input.maxlength = 100; + input.id = "net_name_input"; + input.style.margin = "5px"; + + const yes_button = document.createElement("button"); + yes_button.onclick = function() {this.parseNetworkWindow();}.bind(this); + yes_button.appendChild(document.createTextNode("Okay")); + + const cancel_button = document.createElement("button"); + cancel_button.onclick = function() {thid.closeWindow();}.bind(this); + cancel_button.appendChild(document.createTextNode("Cancel")); + + const error_div = document.createElement("div"); + error_div.id = "current_window_errors"; + + const content = document.createElement("div"); + content.appendChild(document.createTextNode("Name: ")); + content.appendChild(input); + content.appendChild(document.createElement("br")); + content.appendChild(yes_button); + content.appendChild(cancel_button); + content.appendChild(document.createElement("br")); + content.appendChild(error_div); + + this.showWindow("Network Creation", content, 300, 300); + } + + parseNetworkWindow() { + const net_name = document.getElementById("net_name_input").value + const error_div = document.getElementById("current_window_errors"); + if( this.networks.has(net_name) ){ + error_div.innerHTML = "All network names must be unique"; + return; + } + this.addNetwork(net_name); + this.currentWindow.destroy(); + } + + addToolbarButton(editor, toolbar, action, label, image, isTransparent) { + const button = document.createElement('button'); + button.style.fontSize = '10'; + if (image != null) { + const img = document.createElement('img'); + img.setAttribute('src', image); + img.style.width = '16px'; + img.style.height = '16px'; + img.style.verticalAlign = 'middle'; + img.style.marginRight = '2px'; + button.appendChild(img); + } + if (isTransparent) { + button.style.background = 'transparent'; + button.style.color = '#FFFFFF'; + button.style.border = 'none'; + } + mxEvent.addListener(button, 'click', function(evt) { + editor.execute(action); + }); + mxUtils.write(button, label); + toolbar.appendChild(button); + }; + + encodeGraph() { + const encoder = new mxCodec(); + const xml = encoder.encode(this.graph.getModel()); + return mxUtils.getXml(xml); + } + + doGlobalConfig() { + //general graph stuff + this.graph.setMultigraph(false); + this.graph.setCellsSelectable(false); + this.graph.setCellsMovable(false); + + //testing + this.graph.vertexLabelIsMovable = true; + + //edge behavior + this.graph.setConnectable(true); + this.graph.setAllowDanglingEdges(false); + mxEdgeHandler.prototype.snapToTerminals = true; + mxConstants.MIN_HOTSPOT_SIZE = 16; + mxConstants.DEFAULT_HOTSPOT = 1; + //edge 'style' (still affects behavior greatly) + const style = this.graph.getStylesheet().getDefaultEdgeStyle(); + style[mxConstants.STYLE_EDGE] = mxConstants.EDGESTYLE_ELBOW; + style[mxConstants.STYLE_ENDARROW] = mxConstants.NONE; + style[mxConstants.STYLE_ROUNDED] = true; + style[mxConstants.STYLE_FONTCOLOR] = 'black'; + style[mxConstants.STYLE_STROKECOLOR] = 'red'; + style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = '#FFFFFF'; + style[mxConstants.STYLE_STROKEWIDTH] = '3'; + style[mxConstants.STYLE_ROUNDED] = true; + style[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation; + + const hostStyle = this.graph.getStylesheet().getDefaultVertexStyle(); + hostStyle[mxConstants.STYLE_ROUNDED] = 1; + + this.graph.convertValueToString = function(cell) { + try{ + //changes value for edges with xml value + if(cell.isEdge()) { + if(JSON.parse(cell.getValue())["tagged"]) { + return "tagged"; + } + return "untagged"; + } + else{ + return JSON.parse(cell.getValue())['name']; + } + } + catch(e){ + return cell.getValue(); + } + }; + } + + showWindow(title, content, width, height) { + //create transparent black background + const background = document.createElement('div'); + background.style.position = 'absolute'; + background.style.left = '0px'; + background.style.top = '0px'; + background.style.right = '0px'; + background.style.bottom = '0px'; + background.style.background = 'black'; + mxUtils.setOpacity(background, 50); + document.body.appendChild(background); + + const x = Math.max(0, document.body.scrollWidth/2-width/2); + const y = Math.max(10, (document.body.scrollHeight || + document.documentElement.scrollHeight)/2-height*2/3); + + const wnd = new mxWindow(title, content, x, y, width, height, false, true); + wnd.setClosable(false); + + wnd.addListener(mxEvent.DESTROY, function(evt) { + this.graph.setEnabled(true); + mxEffects.fadeOut(background, 50, true, 10, 30, true); + }.bind(this)); + this.currentWindow = wnd; + + this.graph.setEnabled(false); + this.currentWindow.setVisible(true); + }; + + closeWindow() { + //allows the current window to be destroyed + this.currentWindow.destroy(); + }; + + othersUntagged(edgeID) { + const edge = this.graph.getModel().getCell(edgeID); + const end1 = edge.getTerminal(true); + const end2 = edge.getTerminal(false); + + if( end1.getParent().getId().split('_')[0] == 'host' ){ + var netint = end1; + } else { + var netint = end2; + } + + var edges = netint.edges; + for( let edge of edges) { + if( edge.getValue() ) { + var tagged = JSON.parse(edge.getValue()).tagged; + } else { + var tagged = true; + } + if( !tagged ) { + return true; + } + } + return false; + }; + + + deleteVlanWindow(edgeID) { + const cell = this.graph.getModel().getCell(edgeID); + this.graph.removeCells([cell]); + this.currentWindow.destroy(); + } + + parseVlanWindow(edgeID) { + //do parsing and data manipulation + const radios = document.getElementsByName("tagged"); + const edge = this.graph.getModel().getCell(edgeID); + + for(let radio of radios){ + if(radio.checked) { + //set edge to be tagged or untagged + if( radio.value == "False") { + if( this.othersUntagged(edgeID) ) { + document.getElementById("current_window_errors").innerHTML = "Only one untagged vlan per interface is allowed."; + return; + } + } + const edgeVal = {tagged: radio.value == "True"}; + edge.setValue(JSON.stringify(edgeVal)); + break; + } + } + this.graph.refresh(edge); + this.closeWindow(); + } + + makeMxNetwork(net_name, is_public = false) { + const model = this.graph.getModel(); + const width = 10; + const height = 1700; + const xoff = 400 + (30 * this.netCount); + const yoff = -10; + let color = this.netColors[this.netCount]; + if( this.netCount > (this.netColors.length - 1)) { + color = Math.floor(Math.random() * 16777215); //int in possible color space + color = '#' + color.toString(16).toUpperCase(); //convert to hex + } + const net_val = { name: net_name, public: is_public}; + const net = this.graph.insertVertex( + this.graph.getDefaultParent(), + 'network_' + this.netCount, + JSON.stringify(net_val), + xoff, + yoff, + width, + height, + 'fillColor=' + color, + false + ); + const num_ports = 45; + for(var i=0; i<num_ports; i++){ + let port = this.graph.insertVertex( + net, + null, + '', + 0, + (1/num_ports) * i, + 10, + height / num_ports, + 'fillColor=black;opacity=0', + true + ); + } + + const ret_val = { color: color, element_id: "network_" + this.netCount }; + + this.networks.add(net_name); + this.netCount++; + return ret_val; + } + + addPublicNetwork() { + const net = this.makeMxNetwork("public", true); + this.makeSidebarNetwork("public", net['color'], net['element_id']); + this.has_public_net = true; + } + + addNetwork(net_name) { + const ret = this.makeMxNetwork(net_name); + this.makeSidebarNetwork(...ret); + } + + updateHosts(removed) { + const cells = [] + for(const hostID of removed) { + cells.push(this.graph.getModel().getCell("host_" + hostID)); + } + this.graph.removeCells(cells); + + const hosts = this.graph.getChildVertices(this.graph.getDefaultParent()); + let topdist = 100; + for(const i in hosts) { + const host = hosts[i]; + if(host.id.startsWith("host_")){ + const geometry = host.getGeometry(); + geometry.y = topdist + 50; + topdist = geometry.y + geometry.height; + host.setGeometry(geometry); + } + } + } + + makeSidebarNetwork(net_name, color, net_id){ + const newNet = document.createElement("li"); + const colorBlob = document.createElement("div"); + colorBlob.className = "colorblob"; + const textContainer = document.createElement("p"); + textContainer.className = "network_innertext"; + newNet.id = net_id; + const deletebutton = document.createElement("button"); + deletebutton.className = "btn btn-danger"; + deletebutton.style = "float: right; height: 20px; line-height: 8px; vertical-align: middle; width: 20px; padding-left: 5px;"; + deletebutton.appendChild(document.createTextNode("X")); + deletebutton.addEventListener("click", function() { this.createDeleteDialog(net_id); }.bind(this), false); + textContainer.appendChild(document.createTextNode(net_name)); + colorBlob.style['background'] = color; + newNet.appendChild(colorBlob); + newNet.appendChild(textContainer); + if( net_name != "public" ) { + newNet.appendChild(deletebutton); + } + document.getElementById("network_list").appendChild(newNet); + } + + makeHost(hostInfo) { + const value = JSON.stringify(hostInfo['value']); + const interfaces = hostInfo['interfaces']; + const width = 100; + const height = (25 * interfaces.length) + 25; + const xoff = 75; + const yoff = this.lastHostBottom + 50; + this.lastHostBottom = yoff + height; + const host = this.graph.insertVertex( + this.graph.getDefaultParent(), + 'host_' + hostInfo['id'], + value, + xoff, + yoff, + width, + height, + 'editable=0', + false + ); + host.getGeometry().offset = new mxPoint(-50,0); + host.setConnectable(false); + this.hostCount++; + + for(var i=0; i<interfaces.length; i++) { + const port = this.graph.insertVertex( + host, + null, + JSON.stringify(interfaces[i]), + 90, + (i * 25) + 12, + 20, + 20, + 'fillColor=blue;editable=0', + false + ); + port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0); + this.graph.refresh(port); + } + this.graph.refresh(host); + } + + submitForm() { + const form = document.getElementById("xml_form"); + const input_elem = document.getElementById("hidden_xml_input"); + input_elem.value = this.encodeGraph(this.graph); + const req = new XMLHttpRequest(); + req.open("POST", "/wf/workflow/", false); + req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + req.onerror = function() { alert("problem with form submission"); } + const formData = $("#xml_form").serialize(); + req.send(formData); + } +} diff --git a/src/templates/resource/steps/pod_definition.html b/src/templates/resource/steps/pod_definition.html index f8aaa74..5826ccb 100644 --- a/src/templates/resource/steps/pod_definition.html +++ b/src/templates/resource/steps/pod_definition.html @@ -8,580 +8,7 @@ var mxLoadStylesheets = false; </script> <script type="text/javascript" src="/static/js/mxClient.min.js" ></script> -<style> -p { - word-break: normal; - white-space: normal; -} -</style> -<script type="text/javascript"> -var currentWindow; -var currentGraph; -var netCount = 0; -var netColors = ['red', 'blue', 'purple', 'green', 'orange', '#8CCDF5', '#1E9BAC']; -var hostCount = 0; -var lastHostBottom = 100; -var networks = new Set([]); -var has_public_net = false; - -function main(graphContainer, overviewContainer, toolbarContainer) { - //check if the browser is supported - if (!mxClient.isBrowserSupported()) { - mxUtils.error('Browser is not supported', 200, false); - return null; - } - - // Workaround for Internet Explorer ignoring certain styles - if (mxClient.IS_QUIRKS) { - document.body.style.overflow = 'hidden'; - new mxDivResizer(graphContainer); - } - var editor = new mxEditor(); - var graph = editor.graph; - var model = graph.getModel(); - editor.setGraphContainer(graphContainer); - - doGlobalConfig(graph); - currentGraph = graph; - - {% if xml %} - restoreFromXml('{{xml|safe}}', editor); - {% elif hosts %} - {% for host in hosts %} - var host = {{host|safe}}; - makeHost(host); - {% endfor %} - {% endif %} - {% if added_hosts %} - {% for host in added_hosts %} - var host = {{host|safe}} - makeHost(host); - {% endfor %} - updateHosts([]); - {% endif %} - - addToolbarButton(editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true); - addToolbarButton(editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true); - - {% if debug %} - editor.addAction('printXML', function(editor, cell) { - mxLog.write(encodeGraph(graph)); - mxLog.show(); - }); - addToolbarButton(editor, toolbarContainer, 'printXML', '', '/static/img/mxgraph/fit_to_size.png', true); - {% endif %} - - var outline = new mxOutline(graph, overviewContainer); - - var checkAllowed = function(edge, terminal, source) { - //check if other terminal is null, and that they are different - otherTerminal = edge.getTerminal(!source); - if(terminal != null && otherTerminal != null) { - if( terminal.getParent().getId().split('_')[0] == //'host' or 'network' - otherTerminal.getParent().getId().split('_')[0] ) { - //not allowed - graph.removeCells([edge]); - return false; - } - } - return true; - }; - - var colorEdge = function(edge, terminal, source) { - if(terminal.getParent().getId().indexOf('network') >= 0) { - styles = terminal.getParent().getStyle().split(';'); - color = 'black'; - for(var i=0; i<styles.length; i++){ - kvp = styles[i].split('='); - if(kvp[0] == "fillColor"){ - color = kvp[1]; - } - } - edge.setStyle('strokeColor=' + color); - } - }; - - var alertVlan = function(edge, terminal, source) { - if( terminal == null || edge.getTerminal(!source) == null) { - return; - } - var vlanHTML = '<form> <input type="radio" name="tagged" value="True" checked> Tagged<br>' - vlanHTML += '<input type="radio" name="tagged" value="False"> Untagged </form>' - vlanHTML += '<button onclick=parseVlanWindow(' + edge.getId() + ');>Okay</button>' - vlanHTML += '<button onclick=deleteVlanWindow(' + edge.getId() + ');>Cancel</button>' - content = document.createElement('div'); - content.innerHTML = vlanHTML; - showWindow(graph, "Vlan Selection", content, 200, 200); - } - - //sets the edge color to be the same as the network - graph.addListener(mxEvent.CELL_CONNECTED, function(sender, event){ - edge = event.getProperty('edge'); - terminal = event.getProperty('terminal') - source = event.getProperty('source'); - if(checkAllowed(edge, terminal, source)) { - colorEdge(edge, terminal, source); - alertVlan(edge, terminal, source); - } - }); - - createDeleteDialog = function(id) { - var content = document.createElement('div'); - var innerHTML = "<button style='width: 46%;' onclick=deleteCell('" + id + "');>Remove</button>" - innerHTML += "<button style='width: 46%;' onclick='currentWindow.destroy();'>Cancel</button>" - content.innerHTML = innerHTML; - showWindow(currentGraph, 'Do you want to delete this network?', content, 200, 62); - } - - graph.dblClick = function(evt, cell) { - - if( cell != null ){ - if( cell.getParent() != null && cell.getParent().getId().indexOf("network") > -1) { - cell = cell.getParent(); - } - if( cell.isEdge() || cell.getId().indexOf("network") > -1 ) { - createDeleteDialog(cell.getId()); - } - else { - showDetailWindow(cell); - } - } - }; - - updateHosts({{ removed_hosts|default:"[]"|safe }}); - if(!has_public_net){ - addPublicNetwork(); - } -} - -function showDetailWindow(cell) { - var info = JSON.parse(cell.getValue()); - var content = document.createElement("div"); - var inner = "<pre>Name: " + info.name + "\n"; - inner += "Description:\n" + info.description + "</pre>"; - inner += '<button onclick="currentWindow.destroy();">Okay</button>' - content.innerHTML = inner - showWindow(currentGraph, 'Details', content, 400, 400); -} - -function restoreFromXml(xml, editor) { - var doc = mxUtils.parseXml(xml); - var node = doc.documentElement; - editor.readGraphModel(node); - - //Iterate over all children, and parse the networks to add them to the sidebar - var root = currentGraph.getDefaultParent(); - for( var i=0; i<root.getChildCount(); i++) { - var cell = root.getChildAt(i); - if(cell.getId().indexOf("network") > -1) { - var info = JSON.parse(cell.getValue()); - var name = info['name']; - networks.add(name); - var styles = cell.getStyle().split(";"); - var color = null; - for(var j=0; j< styles.length; j++){ - var kvp = styles[j].split('='); - if(kvp[0] == "fillColor") { - color = kvp[1]; - break; - } - } - if(info.public){ - has_public_net = true; - } - netCount++; - makeSidebarNetwork(name, color, cell.getId()); - } - } -} - -function deleteCell(cellId) { - var cell = currentGraph.getModel().getCell(cellId); - if( cellId.indexOf("network") > -1 ) { - elem = document.getElementById(cellId); - elem.parentElement.removeChild(elem); - } - currentGraph.removeCells([cell]); - currentWindow.destroy(); -} - -function newNetworkWindow() { - var innerHtml = 'Name: <input type="text" name="net_name" maxlength="100" id="net_name_input" style="margin:5px;"><br>'; - innerHtml += '<button style="width: 46%;" onclick="parseNetworkWindow()">Okay</button>'; - innerHtml += '<button style="width: 46%;" onclick="currentWindow.destroy();">Cancel</button><br>'; - innerHtml += '<div id="current_window_errors"/>'; - var content = document.createElement("div"); - content.innerHTML = innerHtml; - - showWindow(currentGraph, "Network Creation", content, 300, 300); -} - -function parseNetworkWindow() { - var net_name = document.getElementById("net_name_input").value - var error_div = document.getElementById("current_window_errors"); - if( networks.has(net_name) ){ - error_div.innerHTML = "All network names must be unique"; - return; - } - addNetwork(net_name); - currentWindow.destroy(); -} - -function addToolbarButton(editor, toolbar, action, label, image, isTransparent) -{ - var button = document.createElement('button'); - button.style.fontSize = '10'; - if (image != null) - { - var img = document.createElement('img'); - img.setAttribute('src', image); - img.style.width = '16px'; - img.style.height = '16px'; - img.style.verticalAlign = 'middle'; - img.style.marginRight = '2px'; - button.appendChild(img); - } - if (isTransparent) - { - button.style.background = 'transparent'; - button.style.color = '#FFFFFF'; - button.style.border = 'none'; - } - mxEvent.addListener(button, 'click', function(evt) - { - editor.execute(action); - }); - mxUtils.write(button, label); - toolbar.appendChild(button); -}; - -function encodeGraph(graph) { - var encoder = new mxCodec(); - var xml = encoder.encode(graph.getModel()); - return mxUtils.getXml(xml); -} - -function doGlobalConfig(graph) { - //general graph stuff - graph.setMultigraph(false); - graph.setCellsSelectable(false); - graph.setCellsMovable(false); - - //testing - graph.vertexLabelIsMovable = true; - - - //edge behavior - graph.setConnectable(true); - graph.setAllowDanglingEdges(false); - mxEdgeHandler.prototype.snapToTerminals = true; - mxConstants.MIN_HOTSPOT_SIZE = 16; - mxConstants.DEFAULT_HOTSPOT = 1; - //edge 'style' (still affects behavior greatly) - style = graph.getStylesheet().getDefaultEdgeStyle(); - style[mxConstants.STYLE_EDGE] = mxConstants.EDGESTYLE_ELBOW; - style[mxConstants.STYLE_ENDARROW] = mxConstants.NONE; - style[mxConstants.STYLE_ROUNDED] = true; - style[mxConstants.STYLE_FONTCOLOR] = 'black'; - style[mxConstants.STYLE_STROKECOLOR] = 'red'; - - style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = '#FFFFFF'; - style[mxConstants.STYLE_STROKEWIDTH] = '3'; - style[mxConstants.STYLE_ROUNDED] = true; - style[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation; - - hostStyle = graph.getStylesheet().getDefaultVertexStyle(); - hostStyle[mxConstants.STYLE_ROUNDED] = 1; - - // TODO: Proper override - graph.convertValueToString = function(cell) { - try{ - //changes value for edges with xml value - if(cell.isEdge()) { - if(JSON.parse(cell.getValue())["tagged"]) { - return "tagged"; - } - return "untagged"; - } else{ - return JSON.parse(cell.getValue())['name']; - } - } - catch(e){ - return cell.getValue(); - } - }; -} - -function showWindow(graph, title, content, width, height) { - //create transparent black background - var background = document.createElement('div'); - background.style.position = 'absolute'; - background.style.left = '0px'; - background.style.top = '0px'; - background.style.right = '0px'; - background.style.bottom = '0px'; - background.style.background = 'black'; - mxUtils.setOpacity(background, 50); - document.body.appendChild(background); - - //deal with IE quirk - if (mxClient.IS_IE) { - new mxDivResizer(background); - } - - var x = Math.max(0, document.body.scrollWidth/2-width/2); - var y = Math.max(10, (document.body.scrollHeight || - document.documentElement.scrollHeight)/2-height*2/3); - - var wnd = new mxWindow(title, content, x, y, width, height, false, true); - wnd.setClosable(false); - - wnd.addListener(mxEvent.DESTROY, function(evt) { - graph.setEnabled(true); - mxEffects.fadeOut(background, 50, true, 10, 30, true); - }); - currentWindow = wnd; - - graph.setEnabled(false); - wnd.setVisible(true); -}; - -function closeWindow() { - //allows the current window to be destroyed - currentWindow.destroy(); -}; - -function othersUntagged(edgeID) { - var edge = currentGraph.getModel().getCell(edgeID); - var end1 = edge.getTerminal(true); - var end2 = edge.getTerminal(false); - - if( end1.getParent().getId().split('_')[0] == 'host' ){ - var netint = end1; - } else { - var netint = end2; - } - - var edges = netint.edges; - - for( var i=0; i < edges.length; i++ ) { - if( edges[i].getValue() ) { - var tagged = JSON.parse(edges[i].getValue()).tagged; - } else { - var tagged = true; - } - if( !tagged ) { - return true; - } - } - return false; -}; - - -function deleteVlanWindow(edgeID) { - var cell = currentGraph.getModel().getCell(edgeID); - currentGraph.removeCells([cell]); - currentWindow.destroy(); -} - -function parseVlanWindow(edgeID) { - //do parsing and data manipulation - var radios = document.getElementsByName("tagged"); - edge = currentGraph.getModel().getCell(edgeID); - - for(var i=0; i<radios.length; i++) { - if(radios[i].checked) { - //set edge to be tagged or untagged - //cellValue.setAttribute("tagged", radios[i].value); - if( radios[i].value == "False") - { - if( othersUntagged(edgeID) ) - { - alert("Only one untagged VLAN is allowed per interface"); - return; - } - } - edgeVal = Object(); - edgeVal['tagged'] = radios[i].value == "True"; - edge.setValue(JSON.stringify(edgeVal)); - break; - } - } - currentGraph.refresh(edge); - closeWindow(); -} - -function makeMxNetwork(net_name, public = false) { - model = currentGraph.getModel(); - width = 10; - height = 1700; - xoff = 400 + (30 * netCount); - yoff = -10; - var color = netColors[netCount]; - if( netCount > (netColors.length - 1)) { - color = Math.floor(Math.random() * 16777215); //int in possible color space - color = '#' + color.toString(16).toUpperCase(); //convert to hex - //alert(color); - } - var net_val = Object(); - net_val['name'] = net_name; - net_val['public'] = public; - net = currentGraph.insertVertex( - currentGraph.getDefaultParent(), - 'network_' + netCount, - JSON.stringify(net_val), - xoff, - yoff, - width, - height, - 'fillColor=' + color, - false - ); - var num_ports = 45; - for(var i=0; i<num_ports; i++){ - port = currentGraph.insertVertex( - net, - null, - '', - 0, - (1/num_ports) * i, - 10, - height / num_ports, - 'fillColor=black;opacity=0', - true - ); - } - - var retVal = Object(); - retVal['color'] = color; - retVal['element_id'] = "network_" + netCount; - - networks.add(net_name); - - netCount++; - return retVal; -} - -function addPublicNetwork() { - var net = makeMxNetwork("public", true); - makeSidebarNetwork("public", net['color'], net['element_id']); - has_public_net = true; -} - -function addNetwork(net_name) { - var ret = makeMxNetwork(net_name); - var color = ret['color']; - var net_id = ret['element_id']; - makeSidebarNetwork(net_name, color, net_id); -} - -function updateHosts(removed) { - for(var i=0; i < removed.length; i++) - { - var hoststring = removed[i]; - var hostid = "host_" + hoststring.split("*")[0]; - var cell = currentGraph.getModel().getCell(hostid); - currentGraph.removeCells([cell]); - } - - var hosts = currentGraph.getChildVertices(currentGraph.getDefaultParent()); - var topdist = 100; - for(var i=0; i<hosts.length; i++) { - var host = hosts[i]; - if(!host.id.startsWith("host_")) - { - continue; - } - var geometry = host.getGeometry(); - geometry.y = topdist + 50; - topdist = geometry.y + geometry.height; - host.setGeometry(geometry); - } -} - -function makeSidebarNetwork(net_name, color, net_id){ - var newNet = document.createElement("li"); - var colorBlob = document.createElement("div"); - colorBlob.className = "colorblob"; - var textContainer = document.createElement("p"); - textContainer.className = "network_innertext"; - newNet.id = net_id; - var deletebutton = document.createElement("button"); - deletebutton.className = "btn btn-danger"; - deletebutton.style = "float: right; height: 20px; line-height: 8px; vertical-align: middle; width: 20px; padding-left: 5px;"; - deleteButtonText = document.createTextNode("X"); - deletebutton.appendChild(deleteButtonText); - deletebutton.addEventListener("click", function() { - createDeleteDialog(net_id); - }, false); - var text = net_name; - var newNetValue = document.createTextNode(text); - textContainer.appendChild(newNetValue); - colorBlob.style['background'] = color; - newNet.appendChild(colorBlob); - newNet.appendChild(textContainer); - if( net_name != "public" ) - { - newNet.appendChild(deletebutton); - } - document.getElementById("network_list").appendChild(newNet); -} - -function makeHost(hostInfo) { - value = JSON.stringify(hostInfo['value']); - interfaces = hostInfo['interfaces']; - graph = currentGraph; - width = 100; - height = (25 * interfaces.length) + 25; - xoff = 75; - yoff = lastHostBottom + 50; - lastHostBottom = yoff + height; - host = graph.insertVertex( - graph.getDefaultParent(), - 'host_' + hostInfo['id'], - value, - xoff, - yoff, - width, - height, - 'editable=0', - false - ); - host.getGeometry().offset = new mxPoint(-50,0); - host.setConnectable(false); - hostCount++; - - for(var i=0; i<interfaces.length; i++) { - port = graph.insertVertex( - host, - null, - JSON.stringify(interfaces[i]), - 90, - (i * 25) + 12, - 20, - 20, - 'fillColor=blue;editable=0', - false - ); - port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0); - currentGraph.refresh(port); - } - currentGraph.refresh(host); -} - -function submitForm() { - var form = document.getElementById("xml_form"); - var input_elem = document.getElementById("hidden_xml_input"); - var s = encodeGraph(currentGraph); - input_elem.value = s; - req = new XMLHttpRequest(); - req.open("POST", "/wf/workflow/", false); - req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - req.onerror = function() { alert("problem with form submission"); } - var formData = $("#xml_form").serialize(); - req.send(formData); -} -</script> +<script type="text/javascript" src="/static/js/dashboard.js" ></script> {% endblock extrahead %} <!-- Calls the main function after the page has loaded. Container is dynamically created. --> @@ -605,6 +32,10 @@ function submitForm() { </div> <style> + p { + word-break: normal; + white-space: normal; + } #network_select { background: inherit; padding: 0px; @@ -656,11 +87,11 @@ function submitForm() { <div id="network_select" style="position:absolute;top:0px;bottom:0px;width:25%;right:0px;left:auto;"> <div id="toolbar_extension"> - <button id="btn_add_network" type="button" class="btn btn-primary" onclick="newNetworkWindow();">Add Network</button> + <button id="btn_add_network" type="button" class="btn btn-primary" onclick="network_step.newNetworkWindow();">Add Network</button> </div> <ul id="network_list"> </ul> - <button type="button" style="display: none" onclick="submitForm();">Submit</button> + <button type="button" style="display: none" onclick="network_step.submitForm();">Submit</button> </div> <form id="xml_form" method="post" action="/wf/workflow/"> {% csrf_token %} @@ -668,14 +99,42 @@ function submitForm() { </form> <script> - main( + //gather context data + let debug = false; + {% if debug %} + debug = true; + {% endif %} + + let xml = ''; + {% if xml %} + xml = '{{xml|safe}}'; + {% endif %} + + let hosts = []; + {% for host in hosts %} + hosts.push({{host|safe}}); + {% endfor %} + + let added_hosts = []; + {% for host in added_hosts %} + added_hosts.push({{host|safe}}); + {% endfor %} + + let removed_host_ids = {{removed_hosts|safe}}; + + network_step = new NetworkStep( + debug, + xml, + hosts, + added_hosts, + removed_host_ids, document.getElementById('graphContainer'), document.getElementById('outlineContainer'), document.getElementById('toolbarContainer'), document.getElementById('sidebarContainer') - ) + ); </script> {% endblock content %} {% block onleave %} -submitForm(); +network_step.submitForm(); {% endblock %} diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py index a4657ab..06737d2 100644 --- a/src/workflow/resource_bundle_workflow.py +++ b/src/workflow/resource_bundle_workflow.py @@ -151,54 +151,55 @@ class Define_Nets(WorkflowStep): except Exception: return None + def make_mx_host_dict(self, generic_host): + host = { + 'id': generic_host.resource.name, + 'interfaces': [], + 'value': { + "name": generic_host.resource.name, + "description": generic_host.profile.description + } + } + for iface in generic_host.profile.interfaceprofile.all(): + host['interfaces'].append({ + "name": iface.name, + "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type + }) + return host + def get_context(self): - # TODO: render *primarily* on hosts in repo models context = super(Define_Nets, self).get_context() - context['form'] = NetworkDefinitionForm() + context.update({ + 'form': NetworkDefinitionForm(), + 'debug': settings.DEBUG, + 'hosts': [], + 'added_hosts': [], + 'removed_hosts': [] + }) + vlans = self.get_vlans() + if vlans: + context['vlans'] = vlans try: - context['hosts'] = [] models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}) - vlans = self.get_vlans() - if vlans: - context['vlans'] = vlans hosts = models.get("hosts", []) - hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None) - added_list = [] - added_dict = {} - context['debug'] = settings.DEBUG - context['added_hosts'] = [] - if hostlist is not None: - new_hostlist = [] - for host in models['hosts']: - intcount = host.profile.interfaceprofile.count() - new_hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount)) - context['removed_hosts'] = list(set(hostlist) - set(new_hostlist)) - added_list = list(set(new_hostlist) - set(hostlist)) - for hoststr in added_list: - key = hoststr.split("*")[0] - added_dict[key] = hoststr + # calculate if the selected hosts have changed + added_hosts = set() + host_set = set(self.repo_get(self.repo.GRB_LAST_HOSTLIST, [])) + if len(host_set): + new_host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']]) + context['removed_hosts'] = [h.split("*")[0] for h in (host_set - new_host_set)] + added_hosts.update([h.split("*")[0] for h in (new_host_set - host_set)]) + + # add all host info to context for generic_host in hosts: - host_profile = generic_host.profile - host = {} - host['id'] = generic_host.resource.name - host['interfaces'] = [] - for iface in host_profile.interfaceprofile.all(): - host['interfaces'].append( - { - "name": iface.name, - "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type - } - ) - host['value'] = {"name": generic_host.resource.name} - host['value']['description'] = generic_host.profile.description - context['hosts'].append(json.dumps(host)) - if host['id'] in added_dict: - context['added_hosts'].append(json.dumps(host)) + host = self.make_mx_host_dict(generic_host) + host_serialized = json.dumps(host) + context['hosts'].append(host_serialized) + if host['id'] in added_hosts: + context['added_hosts'].append(host_serialized) bundle = models.get("bundle", False) - if bundle and bundle.xml: - context['xml'] = bundle.xml - else: - context['xml'] = False + if bundle: + context['xml'] = bundle.xml or False except Exception: pass @@ -208,11 +209,8 @@ class Define_Nets(WorkflowStep): def post_render(self, request): models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}) if 'hosts' in models: - hostlist = [] - for host in models['hosts']: - intcount = host.profile.interfaceprofile.count() - hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount)) - self.repo_put(self.repo.GRB_LAST_HOSTLIST, hostlist) + host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']]) + self.repo_put(self.repo.GRB_LAST_HOSTLIST, host_set) try: xmlData = request.POST.get("xml") self.updateModels(xmlData) |