summaryrefslogtreecommitdiffstats
path: root/dashboard/src/static/js/dashboard.js
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/src/static/js/dashboard.js')
-rw-r--r--dashboard/src/static/js/dashboard.js588
1 files changed, 588 insertions, 0 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);
+ }
+}