summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dashboard/src/static/js/dashboard.js588
-rw-r--r--dashboard/src/templates/resource/steps/pod_definition.html617
-rw-r--r--dashboard/src/workflow/resource_bundle_workflow.py90
3 files changed, 670 insertions, 625 deletions
diff --git a/dashboard/src/static/js/dashboard.js b/dashboard/src/static/js/dashboard.js
index 66e95d0..e51a219 100644
--- a/dashboard/src/static/js/dashboard.js
+++ b/dashboard/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/dashboard/src/templates/resource/steps/pod_definition.html b/dashboard/src/templates/resource/steps/pod_definition.html
index f8aaa74..5826ccb 100644
--- a/dashboard/src/templates/resource/steps/pod_definition.html
+++ b/dashboard/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/dashboard/src/workflow/resource_bundle_workflow.py b/dashboard/src/workflow/resource_bundle_workflow.py
index a4657ab..06737d2 100644
--- a/dashboard/src/workflow/resource_bundle_workflow.py
+++ b/dashboard/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)