Move All JS of Networking Step to External File 99/68099/4
authorParker Berberian <pberberian@iol.unh.edu>
Mon, 24 Jun 2019 16:16:08 +0000 (12:16 -0400)
committerParker Berberian <pberberian@iol.unh.edu>
Tue, 25 Jun 2019 19:12:43 +0000 (15:12 -0400)
Cleans up a lot of old code and moves it all into
an object in the dashboard.js file

Change-Id: I9fdbd2242c59eff7b1a95378f132e4f307188dc8
Signed-off-by: Parker Berberian <pberberian@iol.unh.edu>
dashboard/src/static/js/dashboard.js
dashboard/src/templates/resource/steps/pod_definition.html
dashboard/src/workflow/resource_bundle_workflow.py

index 66e95d0..e51a219 100644 (file)
@@ -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);
+    }
+}
index f8aaa74..5826ccb 100644 (file)
@@ -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 %}
index a4657ab..06737d2 100644 (file)
@@ -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)